diff --git a/package-lock.json b/package-lock.json index 1fb5aee..f8ac093 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "smarter-frontend", - "version": "0.2.2", + "version": "0.3.0-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "smarter-frontend", - "version": "0.2.2", + "version": "0.3.0-0", "dependencies": { "@angular/animations": "^14.2.0", "@angular/cdk": "^14.2.3", @@ -26,7 +26,7 @@ "zone.js": "^0.11.8" }, "devDependencies": { - "@angular-devkit/build-angular": "^14.2.10", + "@angular-devkit/build-angular": "^14.2.13", "@angular/cli": "~14.2.4", "@angular/compiler-cli": "^14.2.12", "@types/jasmine": "~4.0.0", @@ -40,9 +40,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "node_modules/@ampproject/remapping": { @@ -124,15 +124,15 @@ "peer": true }, "node_modules/@angular-devkit/build-angular": { - "version": "14.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-14.2.12.tgz", - "integrity": "sha512-ei8/FaL80Q6si/aF6FLZgtT4Kr2rudlyGMqQM4Rd2Zvt8mCh3TgM7QdLhoI11t9A0LWz6RIdROlDimMyyOEF6Q==", + "version": "14.2.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-14.2.13.tgz", + "integrity": "sha512-FJZKQ3xYFvEJ807sxVy4bCVyGU2NMl3UUPNfLIdIdzwwDEP9tx/cc+c4VtVPEZZfU8jVenu8XOvL6L0vpjt3yg==", "dev": true, "dependencies": { "@ampproject/remapping": "2.2.0", - "@angular-devkit/architect": "0.1402.12", - "@angular-devkit/build-webpack": "0.1402.12", - "@angular-devkit/core": "14.2.12", + "@angular-devkit/architect": "0.1402.13", + "@angular-devkit/build-webpack": "0.1402.13", + "@angular-devkit/core": "14.2.13", "@babel/core": "7.18.10", "@babel/generator": "7.18.12", "@babel/helper-annotate-as-pure": "7.18.6", @@ -143,7 +143,7 @@ "@babel/runtime": "7.18.9", "@babel/template": "7.18.10", "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "14.2.12", + "@ngtools/webpack": "14.2.13", "ansi-colors": "4.1.3", "babel-loader": "8.2.5", "babel-plugin-istanbul": "6.1.1", @@ -168,7 +168,7 @@ "ora": "5.4.1", "parse5-html-rewriting-stream": "6.0.1", "piscina": "3.2.0", - "postcss": "8.4.16", + "postcss": "8.4.31", "postcss-import": "15.0.0", "postcss-loader": "7.0.1", "postcss-preset-env": "7.8.0", @@ -232,12 +232,12 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { - "version": "0.1402.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.12.tgz", - "integrity": "sha512-LuK26pyaqyClEbY0n4/WIh3irUuA8wwmMmEj8uW4boziuJWv7U42lJJRF3VwkchiyOIp8qiKg995K6IoeXkWgA==", + "version": "0.1402.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.13.tgz", + "integrity": "sha512-n0ISBuvkZHoOpAzuAZql1TU9VLHUE9e/a9g4VNOPHewjMzpN02VqeGKvJfOCKtzkCs6gVssIlILm2/SXxkIFxQ==", "dev": true, "dependencies": { - "@angular-devkit/core": "14.2.12", + "@angular-devkit/core": "14.2.13", "rxjs": "6.6.7" }, "engines": { @@ -247,9 +247,9 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "14.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.12.tgz", - "integrity": "sha512-tg1+deEZdm3fgk2BQ6y7tujciL6qhtN5Ums266lX//kAZeZ4nNNXTBT+oY5xgfjvmLbW+xKg0XZrAS0oIRKY5g==", + "version": "14.2.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.13.tgz", + "integrity": "sha512-aIefeZcbjghQg/V6U9CTLtyB5fXDJ63KwYqVYkWP+i0XriS5A9puFgq2u/OVsWxAfYvqpDqp5AdQ0g0bi3CAsA==", "dev": true, "dependencies": { "ajv": "8.11.0", @@ -313,12 +313,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1402.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1402.12.tgz", - "integrity": "sha512-xBkbSwOhHgUsJk1tTtITqbHHiA0OdjwdrYZYceyfDASAglyRX6rT4Q9/Ppf7TSck6M1tUR76efYOn3D3ial29w==", + "version": "0.1402.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1402.13.tgz", + "integrity": "sha512-K27aJmuw86ZOdiu5PoGeGDJ2v7g2ZCK0bGwc8jzkjTLRfvd4FRKIIZumGv3hbQ3vQRLikiU6WMDRTFyCZky/EA==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1402.12", + "@angular-devkit/architect": "0.1402.13", "rxjs": "6.6.7" }, "engines": { @@ -332,12 +332,12 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { - "version": "0.1402.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.12.tgz", - "integrity": "sha512-LuK26pyaqyClEbY0n4/WIh3irUuA8wwmMmEj8uW4boziuJWv7U42lJJRF3VwkchiyOIp8qiKg995K6IoeXkWgA==", + "version": "0.1402.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.13.tgz", + "integrity": "sha512-n0ISBuvkZHoOpAzuAZql1TU9VLHUE9e/a9g4VNOPHewjMzpN02VqeGKvJfOCKtzkCs6gVssIlILm2/SXxkIFxQ==", "dev": true, "dependencies": { - "@angular-devkit/core": "14.2.12", + "@angular-devkit/core": "14.2.13", "rxjs": "6.6.7" }, "engines": { @@ -347,9 +347,9 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { - "version": "14.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.12.tgz", - "integrity": "sha512-tg1+deEZdm3fgk2BQ6y7tujciL6qhtN5Ums266lX//kAZeZ4nNNXTBT+oY5xgfjvmLbW+xKg0XZrAS0oIRKY5g==", + "version": "14.2.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.13.tgz", + "integrity": "sha512-aIefeZcbjghQg/V6U9CTLtyB5fXDJ63KwYqVYkWP+i0XriS5A9puFgq2u/OVsWxAfYvqpDqp5AdQ0g0bi3CAsA==", "dev": true, "dependencies": { "ajv": "8.11.0", @@ -854,12 +854,13 @@ "dev": true }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -1078,48 +1079,48 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", - "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name/node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1277,30 +1278,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", - "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1359,13 +1360,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -1373,9 +1374,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", - "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -2554,19 +2555,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", - "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.5", - "@babel/helper-environment-visitor": "^7.21.5", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.5", - "@babel/types": "^7.21.5", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -2575,12 +2576,12 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", - "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.21.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -2604,13 +2605,13 @@ } }, "node_modules/@babel/types": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", - "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.21.5", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -3045,9 +3046,9 @@ "dev": true }, "node_modules/@ngtools/webpack": { - "version": "14.2.12", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.2.12.tgz", - "integrity": "sha512-d/NRQAjS3BsMDUpLrhza+bvI7HKIV+lyRAvD3LYj5FE9kMoEGw4zRo9JG8ookCzvT2FjNiXQ7DWDZSAeMOy+WQ==", + "version": "14.2.13", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.2.13.tgz", + "integrity": "sha512-RQx/rGX7K/+R55x1R6Ax1JzyeHi8cW11dEXpzHWipyuSpusQLUN53F02eMB4VTakXsL3mFNWWy4bX3/LSq8/9w==", "dev": true, "engines": { "node": "^14.15.0 || >=16.10.0", @@ -3356,9 +3357,9 @@ } }, "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz", + "integrity": "sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==", "dev": true, "dependencies": { "@types/connect": "*", @@ -3366,9 +3367,9 @@ } }, "node_modules/@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.11.tgz", + "integrity": "sha512-isGhjmBtLIxdHBDl2xGwUzEM8AOyOvWsADWq7rqirdi/ZQoHnLWErHvsThcEzTX8juDRiZtzp2Qkv5bgNh6mAg==", "dev": true, "dependencies": { "@types/node": "*" @@ -3435,9 +3436,9 @@ "dev": true }, "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "version": "4.17.18", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.18.tgz", + "integrity": "sha512-Sxv8BSLLgsBYmcnGdGjjEjqET2U+AKAdCRODmMiq02FgjwuV75Ut85DRpvFjyw/Mk0vgUOliGRU0UUmuuZHByQ==", "dev": true, "dependencies": { "@types/body-parser": "*", @@ -3447,9 +3448,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.36", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz", - "integrity": "sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==", + "version": "4.17.37", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.37.tgz", + "integrity": "sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg==", "dev": true, "dependencies": { "@types/node": "*", @@ -3459,15 +3460,15 @@ } }, "node_modules/@types/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz", + "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==", "dev": true }, "node_modules/@types/http-proxy": { - "version": "1.17.11", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", - "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", + "version": "1.17.12", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.12.tgz", + "integrity": "sha512-kQtujO08dVtQ2wXAuSFfk9ASy3sug4+ogFR8Kd8UgP8PEuc1/G/8yjYRmp//PcDNJEUKOza/MrQu15bouEUCiw==", "dev": true, "dependencies": { "@types/node": "*" @@ -3486,9 +3487,9 @@ "dev": true }, "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz", + "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==", "dev": true }, "node_modules/@types/node": { @@ -3510,9 +3511,9 @@ "dev": true }, "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.5.tgz", + "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==", "dev": true }, "node_modules/@types/retry": { @@ -3522,9 +3523,9 @@ "dev": true }, "node_modules/@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.2.tgz", + "integrity": "sha512-aAG6yRf6r0wQ29bkS+x97BIs64ZLxeE/ARwyS6wrldMm3C1MdKwCcnnEwMC1slI8wuxJOpiUH9MioC0A0i+GJw==", "dev": true, "dependencies": { "@types/mime": "^1", @@ -3532,18 +3533,18 @@ } }, "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-asaEIoc6J+DbBKXtO7p2shWUpKacZOoMBEGBgPG91P8xhO53ohzHWGCs4ScZo5pQMf5ukQzVT9fhX1WzpHihig==", "dev": true, "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", - "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.3.tgz", + "integrity": "sha512-yVRvFsEMrv7s0lGhzrggJjNOSmZCdgCjw9xWrPr/kNNLp6FaDfMC1KaYl3TSJ0c58bECwNBMoQrZJ8hA8E1eFg==", "dev": true, "dependencies": { "@types/http-errors": "*", @@ -3552,18 +3553,18 @@ } }, "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "version": "0.3.34", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.34.tgz", + "integrity": "sha512-R+n7qBFnm/6jinlteC9DBL5dGiDGjWAvjo4viUanpnc/dG1y7uDoacXPIQ/PQEg1fI912SMHIa014ZjRpvDw4g==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/ws": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", - "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.6.tgz", + "integrity": "sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==", "dev": true, "dependencies": { "@types/node": "*" @@ -6477,9 +6478,9 @@ } }, "node_modules/fs-monkey": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", - "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", "dev": true }, "node_modules/fs.realpath": { @@ -9551,9 +9552,9 @@ } }, "node_modules/postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "funding": [ { @@ -9563,10 +9564,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -12676,9 +12681,9 @@ }, "dependencies": { "@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "@ampproject/remapping": { @@ -12738,15 +12743,15 @@ } }, "@angular-devkit/build-angular": { - "version": "14.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-14.2.12.tgz", - "integrity": "sha512-ei8/FaL80Q6si/aF6FLZgtT4Kr2rudlyGMqQM4Rd2Zvt8mCh3TgM7QdLhoI11t9A0LWz6RIdROlDimMyyOEF6Q==", + "version": "14.2.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-14.2.13.tgz", + "integrity": "sha512-FJZKQ3xYFvEJ807sxVy4bCVyGU2NMl3UUPNfLIdIdzwwDEP9tx/cc+c4VtVPEZZfU8jVenu8XOvL6L0vpjt3yg==", "dev": true, "requires": { "@ampproject/remapping": "2.2.0", - "@angular-devkit/architect": "0.1402.12", - "@angular-devkit/build-webpack": "0.1402.12", - "@angular-devkit/core": "14.2.12", + "@angular-devkit/architect": "0.1402.13", + "@angular-devkit/build-webpack": "0.1402.13", + "@angular-devkit/core": "14.2.13", "@babel/core": "7.18.10", "@babel/generator": "7.18.12", "@babel/helper-annotate-as-pure": "7.18.6", @@ -12757,7 +12762,7 @@ "@babel/runtime": "7.18.9", "@babel/template": "7.18.10", "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "14.2.12", + "@ngtools/webpack": "14.2.13", "ansi-colors": "4.1.3", "babel-loader": "8.2.5", "babel-plugin-istanbul": "6.1.1", @@ -12783,7 +12788,7 @@ "ora": "5.4.1", "parse5-html-rewriting-stream": "6.0.1", "piscina": "3.2.0", - "postcss": "8.4.16", + "postcss": "8.4.31", "postcss-import": "15.0.0", "postcss-loader": "7.0.1", "postcss-preset-env": "7.8.0", @@ -12809,19 +12814,19 @@ }, "dependencies": { "@angular-devkit/architect": { - "version": "0.1402.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.12.tgz", - "integrity": "sha512-LuK26pyaqyClEbY0n4/WIh3irUuA8wwmMmEj8uW4boziuJWv7U42lJJRF3VwkchiyOIp8qiKg995K6IoeXkWgA==", + "version": "0.1402.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.13.tgz", + "integrity": "sha512-n0ISBuvkZHoOpAzuAZql1TU9VLHUE9e/a9g4VNOPHewjMzpN02VqeGKvJfOCKtzkCs6gVssIlILm2/SXxkIFxQ==", "dev": true, "requires": { - "@angular-devkit/core": "14.2.12", + "@angular-devkit/core": "14.2.13", "rxjs": "6.6.7" } }, "@angular-devkit/core": { - "version": "14.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.12.tgz", - "integrity": "sha512-tg1+deEZdm3fgk2BQ6y7tujciL6qhtN5Ums266lX//kAZeZ4nNNXTBT+oY5xgfjvmLbW+xKg0XZrAS0oIRKY5g==", + "version": "14.2.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.13.tgz", + "integrity": "sha512-aIefeZcbjghQg/V6U9CTLtyB5fXDJ63KwYqVYkWP+i0XriS5A9puFgq2u/OVsWxAfYvqpDqp5AdQ0g0bi3CAsA==", "dev": true, "requires": { "ajv": "8.11.0", @@ -12869,29 +12874,29 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.1402.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1402.12.tgz", - "integrity": "sha512-xBkbSwOhHgUsJk1tTtITqbHHiA0OdjwdrYZYceyfDASAglyRX6rT4Q9/Ppf7TSck6M1tUR76efYOn3D3ial29w==", + "version": "0.1402.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1402.13.tgz", + "integrity": "sha512-K27aJmuw86ZOdiu5PoGeGDJ2v7g2ZCK0bGwc8jzkjTLRfvd4FRKIIZumGv3hbQ3vQRLikiU6WMDRTFyCZky/EA==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1402.12", + "@angular-devkit/architect": "0.1402.13", "rxjs": "6.6.7" }, "dependencies": { "@angular-devkit/architect": { - "version": "0.1402.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.12.tgz", - "integrity": "sha512-LuK26pyaqyClEbY0n4/WIh3irUuA8wwmMmEj8uW4boziuJWv7U42lJJRF3VwkchiyOIp8qiKg995K6IoeXkWgA==", + "version": "0.1402.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.13.tgz", + "integrity": "sha512-n0ISBuvkZHoOpAzuAZql1TU9VLHUE9e/a9g4VNOPHewjMzpN02VqeGKvJfOCKtzkCs6gVssIlILm2/SXxkIFxQ==", "dev": true, "requires": { - "@angular-devkit/core": "14.2.12", + "@angular-devkit/core": "14.2.13", "rxjs": "6.6.7" } }, "@angular-devkit/core": { - "version": "14.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.12.tgz", - "integrity": "sha512-tg1+deEZdm3fgk2BQ6y7tujciL6qhtN5Ums266lX//kAZeZ4nNNXTBT+oY5xgfjvmLbW+xKg0XZrAS0oIRKY5g==", + "version": "14.2.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.13.tgz", + "integrity": "sha512-aIefeZcbjghQg/V6U9CTLtyB5fXDJ63KwYqVYkWP+i0XriS5A9puFgq2u/OVsWxAfYvqpDqp5AdQ0g0bi3CAsA==", "dev": true, "requires": { "ajv": "8.11.0", @@ -13217,12 +13222,13 @@ "dev": true }, "@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" } }, "@babel/compat-data": { @@ -13392,41 +13398,41 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", - "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "dependencies": { "@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } } } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -13549,24 +13555,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", - "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -13612,20 +13618,20 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.21.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", - "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { @@ -14423,30 +14429,30 @@ } }, "@babel/traverse": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", - "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.5", - "@babel/helper-environment-visitor": "^7.21.5", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.5", - "@babel/types": "^7.21.5", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { "@babel/generator": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", - "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.21.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -14466,13 +14472,13 @@ } }, "@babel/types": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", - "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.21.5", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -14731,9 +14737,9 @@ "dev": true }, "@ngtools/webpack": { - "version": "14.2.12", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.2.12.tgz", - "integrity": "sha512-d/NRQAjS3BsMDUpLrhza+bvI7HKIV+lyRAvD3LYj5FE9kMoEGw4zRo9JG8ookCzvT2FjNiXQ7DWDZSAeMOy+WQ==", + "version": "14.2.13", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.2.13.tgz", + "integrity": "sha512-RQx/rGX7K/+R55x1R6Ax1JzyeHi8cW11dEXpzHWipyuSpusQLUN53F02eMB4VTakXsL3mFNWWy4bX3/LSq8/9w==", "dev": true, "requires": {} }, @@ -14954,9 +14960,9 @@ "dev": true }, "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz", + "integrity": "sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==", "dev": true, "requires": { "@types/connect": "*", @@ -14964,9 +14970,9 @@ } }, "@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.11.tgz", + "integrity": "sha512-isGhjmBtLIxdHBDl2xGwUzEM8AOyOvWsADWq7rqirdi/ZQoHnLWErHvsThcEzTX8juDRiZtzp2Qkv5bgNh6mAg==", "dev": true, "requires": { "@types/node": "*" @@ -15033,9 +15039,9 @@ "dev": true }, "@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "version": "4.17.18", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.18.tgz", + "integrity": "sha512-Sxv8BSLLgsBYmcnGdGjjEjqET2U+AKAdCRODmMiq02FgjwuV75Ut85DRpvFjyw/Mk0vgUOliGRU0UUmuuZHByQ==", "dev": true, "requires": { "@types/body-parser": "*", @@ -15045,9 +15051,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.36", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz", - "integrity": "sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==", + "version": "4.17.37", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.37.tgz", + "integrity": "sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg==", "dev": true, "requires": { "@types/node": "*", @@ -15057,15 +15063,15 @@ } }, "@types/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz", + "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==", "dev": true }, "@types/http-proxy": { - "version": "1.17.11", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", - "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", + "version": "1.17.12", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.12.tgz", + "integrity": "sha512-kQtujO08dVtQ2wXAuSFfk9ASy3sug4+ogFR8Kd8UgP8PEuc1/G/8yjYRmp//PcDNJEUKOza/MrQu15bouEUCiw==", "dev": true, "requires": { "@types/node": "*" @@ -15084,9 +15090,9 @@ "dev": true }, "@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz", + "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==", "dev": true }, "@types/node": { @@ -15108,9 +15114,9 @@ "dev": true }, "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.5.tgz", + "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==", "dev": true }, "@types/retry": { @@ -15120,9 +15126,9 @@ "dev": true }, "@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.2.tgz", + "integrity": "sha512-aAG6yRf6r0wQ29bkS+x97BIs64ZLxeE/ARwyS6wrldMm3C1MdKwCcnnEwMC1slI8wuxJOpiUH9MioC0A0i+GJw==", "dev": true, "requires": { "@types/mime": "^1", @@ -15130,18 +15136,18 @@ } }, "@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-asaEIoc6J+DbBKXtO7p2shWUpKacZOoMBEGBgPG91P8xhO53ohzHWGCs4ScZo5pQMf5ukQzVT9fhX1WzpHihig==", "dev": true, "requires": { "@types/express": "*" } }, "@types/serve-static": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", - "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.3.tgz", + "integrity": "sha512-yVRvFsEMrv7s0lGhzrggJjNOSmZCdgCjw9xWrPr/kNNLp6FaDfMC1KaYl3TSJ0c58bECwNBMoQrZJ8hA8E1eFg==", "dev": true, "requires": { "@types/http-errors": "*", @@ -15150,18 +15156,18 @@ } }, "@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "version": "0.3.34", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.34.tgz", + "integrity": "sha512-R+n7qBFnm/6jinlteC9DBL5dGiDGjWAvjo4viUanpnc/dG1y7uDoacXPIQ/PQEg1fI912SMHIa014ZjRpvDw4g==", "dev": true, "requires": { "@types/node": "*" } }, "@types/ws": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", - "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.6.tgz", + "integrity": "sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==", "dev": true, "requires": { "@types/node": "*" @@ -17281,9 +17287,9 @@ } }, "fs-monkey": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", - "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", "dev": true }, "fs.realpath": { @@ -19599,12 +19605,12 @@ } }, "postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "requires": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } diff --git a/package.json b/package.json index 3b3cae8..73da443 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "smarter-frontend", - "version": "0.2.2", + "version": "0.3.0-0", "scripts": { "ng": "ng", "start": "ng serve", @@ -28,7 +28,7 @@ "zone.js": "^0.11.8" }, "devDependencies": { - "@angular-devkit/build-angular": "^14.2.10", + "@angular-devkit/build-angular": "^14.2.13", "@angular/cli": "~14.2.4", "@angular/compiler-cli": "^14.2.12", "@types/jasmine": "~4.0.0", diff --git a/src/app/about/about.component.html b/src/app/about/about.component.html index 1b4b053..628b417 100644 --- a/src/app/about/about.component.html +++ b/src/app/about/about.component.html @@ -1,35 +1,37 @@ -

About the SMARTER database

+

About the SMARTER Database

+

The SMARTER project site is a place where WP4 partners of the - Smarter project - can browse and access their data. + SMARTER project + can browse and access their data. This dataset is now open and available + to the community.

-

The SMARTER project

+

The SMARTER Project

- Small ruminant populations play a fundamental role for the livelihood and + Small ruminant populations play a fundamental role in the livelihood and socio-economic well-being of human settlements, especially in marginal areas of Europe. Under-utilized sheep and goat breeds may be highly valuable in - increasing the profitability of small ruminant farming in such marginal areas. - These breeds are valuable because they have peculiar and often atypical - genetic make-up which make them a potentially extraordinary resource to - be exploited for adaptation to (harsh) environments, resilience to farming + increasing the profitability of small ruminant farming in these marginal areas. + These breeds are valuable because they have unique and often atypical + genetic make-ups, making them potentially extraordinary resources for + adaptation to harsh environments, resilience to farming conditions, resistance to biotic and abiotic stressors, and the production - of quality of products of animal origin. + of high-quality animal products.

- In this particular context, WP4 aims to address knowledge gaps concerning + In this context, WP4 aims to address knowledge gaps concerning genetic diversity in goats and sheep. Its objective is to identify genetic factors that contribute to their ability to adapt to climate conditions and, more broadly, to withstand environmental disturbances. - Available datasets relevant to the genomic characterization - were identified and furthermore comprehensive data on hardy and under-utilized + Available datasets relevant to genomic characterization + were identified, and comprehensive data on hardy and under-utilized breeds were collected, including genotypes, phenotypes, and environmental measurements associated with rearing conditions. To ensure consistency, the recording of this data was standardized: genotypes @@ -40,78 +42,89 @@

The SMARTER project

- Data are made available to the community using three different instruments: + Data are made available to the community using four different instruments:

- + -

- Those instrument can be used to collect metadata and other samples information: genotypes - are available for both Goat and Sheep species as a whole PLINK binary file for each - of the supported assemblies. Users are required to identify their samples of interest and then - subset the genotype files according their needs. Additional filtering steps - on the selected genotypes can be done to get rid of missing samples or variants. -

+

+ These instruments can be used to collect metadata and other sample information. Genotypes + are available for both Goat and Sheep species as whole PLINK binary files for each + of the supported assemblies. You can download the genotype files from the + FTP site for the available assemblies. + Users are required to identify their samples of interest using the + SMARTER frontend, + the SMARTER backend, or the + smarterapi R package. + These are three different ways to access the same data: the first one is a web interface, + the second one is a REST API service, and the third one is an R package to interact with the API service. + Users can then subset the genotype files according to their needs. Additional filtering steps + on the selected genotypes can be done to remove missing samples or variants. +

-

About this site

+

About This Site

- You can browse WP4 SMARTER using the different tabs. Data like samples, variants - and breed can be browsed by species (Goat, Sheep): you will find - a button on the top of the page which will select the data you can browse. - Data like datasets are available in the same page for both species. Here's + You can browse WP4 SMARTER data using the different tabs. Data like samples, variants, + and breeds can be browsed by species (Goat, Sheep). You will find + a button on the top of the page to select the data you want to browse. + Data like datasets are available on the same page for both species. Here's a list of the site sections:

diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index c2e0485..a9fe9ea 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,10 +1,8 @@ -import { Component, NgModule } from '@angular/core'; +import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { AuthGuard } from './auth/auth.guard'; import { HomeComponent } from './home/home.component'; import { AboutComponent } from './about/about.component'; -import { LoginComponent } from './auth/login/login.component'; import { DatasetsComponent } from './datasets/datasets.component'; import { DatasetDetailComponent } from './datasets/dataset-detail/dataset-detail.component'; import { DatasetResolver } from './datasets/dataset-detail/dataset-resolver.service'; @@ -21,43 +19,35 @@ import { VariantResolver } from './variants/variant-detail/variant-resolver.serv const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'about', component: AboutComponent }, - { path: 'login', component: LoginComponent }, { path: 'breeds', - component: BreedsComponent, - canActivate: [ AuthGuard ] + component: BreedsComponent }, { path: 'datasets', - component: DatasetsComponent, - canActivate: [ AuthGuard ] + component: DatasetsComponent }, { path: 'datasets/:_id', component: DatasetDetailComponent, - canActivate: [ AuthGuard ], resolve: { dataset: DatasetResolver } }, { path: 'samples', component: SamplesComponent, - canActivate: [ AuthGuard ] }, { path: 'samples/:species/:_id', component: SampleDetailComponent, - canActivate: [ AuthGuard ], resolve: { sample: SampleResolver } }, { path: 'variants', component: VariantsComponent, - canActivate: [ AuthGuard ], }, { path: 'variants/:species/:_id', component: VariantDetailComponent, - canActivate: [ AuthGuard ], resolve: { variant: VariantResolver } }, { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 55194c2..091a2f2 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,19 +1,11 @@ -import { Component, OnInit } from '@angular/core'; - -import { AuthService } from './auth/auth.service'; +import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) -export class AppComponent implements OnInit { +export class AppComponent { title = 'SMARTER-frontend'; - constructor(private authService: AuthService) { } - - ngOnInit(): void { - // try authologin when starting application - this.authService.autoLogin(); - } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index da8b55c..c341e9b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -9,11 +9,9 @@ import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { MaterialModule } from './material/material.module'; import { HomeComponent } from './home/home.component'; -import { LoginComponent } from './auth/login/login.component'; import { HeaderComponent } from './navigation/header/header.component'; import { SidenavListComponent } from './navigation/sidenav-list/sidenav-list.component'; import { DatasetsComponent } from './datasets/datasets.component'; -import { AuthInterceptorService } from './auth/auth-interceptor.service'; import { NotFoundComponent } from './not-found/not-found.component'; import { ShortenPipe } from './shared/shorten.pipe'; import { BreedsComponent } from './breeds/breeds.component'; @@ -33,7 +31,6 @@ import { AboutComponent } from './about/about.component'; declarations: [ AppComponent, HomeComponent, - LoginComponent, HeaderComponent, SidenavListComponent, DatasetsComponent, @@ -62,12 +59,7 @@ import { AboutComponent } from './about/about.component'; MaterialModule ], providers: [ - { - provide: HTTP_INTERCEPTORS, - useClass: AuthInterceptorService, - // required, even if it is the only interceptor defined - multi: true - } + ], bootstrap: [AppComponent] }) diff --git a/src/app/auth/auth-data.model.ts b/src/app/auth/auth-data.model.ts deleted file mode 100644 index 6c34d22..0000000 --- a/src/app/auth/auth-data.model.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface AuthData { - username: string; - password: string; - redirectTo?: string; -} diff --git a/src/app/auth/auth-interceptor.service.spec.ts b/src/app/auth/auth-interceptor.service.spec.ts deleted file mode 100644 index e400c5f..0000000 --- a/src/app/auth/auth-interceptor.service.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; - -import { MaterialModule } from '../material/material.module'; -import { AuthInterceptorService } from './auth-interceptor.service'; - -describe('AuthInterceptorService', () => { - let service: AuthInterceptorService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - HttpClientTestingModule, - RouterTestingModule, - MaterialModule, - ], - providers: [ - AuthInterceptorService - ], - }); - service = TestBed.inject(AuthInterceptorService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/src/app/auth/auth-interceptor.service.ts b/src/app/auth/auth-interceptor.service.ts deleted file mode 100644 index 8034b85..0000000 --- a/src/app/auth/auth-interceptor.service.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from '@angular/common/http'; - -import { exhaustMap, take } from 'rxjs/operators'; - -import { AuthService } from './auth.service'; - -// don't provide interceptors in root, it needs a custom configuration in -// app.module 'providers' section -@Injectable() -export class AuthInterceptorService implements HttpInterceptor { - - constructor(private authService: AuthService) {} - - intercept(req: HttpRequest, next: HttpHandler) { - // this will be executed for each request - return this.authService.user.pipe( - // take: subscribe to user subject, get N objects and then unsubscribe - take(1), - // take the results of the first subscribe and returns a new observable - exhaustMap(user => { - // during login, I don't have a token. So I need to return the unmodified request - if (!user) { - return next.handle(req); - } - - // copy the request in a new object that I can modify - const modifiedReq = req.clone({ - headers: new HttpHeaders({ - 'Authorization': 'Bearer ' + user.token - }) - }); - - // now I will add a token to each requests - return next.handle(modifiedReq); - }) - ); - } -} diff --git a/src/app/auth/auth.guard.ts b/src/app/auth/auth.guard.ts deleted file mode 100644 index fcb3be5..0000000 --- a/src/app/auth/auth.guard.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Injectable } from "@angular/core"; -import { ActivatedRouteSnapshot, CanActivate, Params, Router, RouterStateSnapshot, UrlTree } from "@angular/router"; - -import { Observable } from "rxjs"; -import { map, take } from "rxjs/operators"; - -import { AuthService } from "./auth.service"; - -@Injectable({ - providedIn: 'root' -}) -export class AuthGuard implements CanActivate { - - constructor( - private authService: AuthService, - private router: Router, - ) { } - - canActivate( - route: ActivatedRouteSnapshot, - router: RouterStateSnapshot - ): boolean | Promise | Observable { - // determine if a user is authenticated or not by watching user BehaviourSubject - return this.authService.user.pipe( - // take the latest user value and then unsusbscribe - take(1), - map(user => { - // convert a value in a true boolean, or a null/underfined value in false - const isAuth = !!user; - - if (isAuth) { - return true; - } - - // get the requested url to redirect after login - const params: Params = { - next: router.url - }; - - // if not authenticated, redirect to login page - return this.router.createUrlTree(['/login'], {queryParams: params}); - }) - ); - } -} diff --git a/src/app/auth/auth.service.spec.ts b/src/app/auth/auth.service.spec.ts deleted file mode 100644 index 2e2ff7e..0000000 --- a/src/app/auth/auth.service.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ - -import { TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; - -import { MaterialModule } from '../material/material.module'; -import { AuthService, AuthResponseData } from './auth.service'; -import { AuthData } from './auth-data.model'; -import { environment } from 'src/environments/environment'; -import { LoginComponent } from './login/login.component'; - -describe('AuthService', () => { - let service: AuthService; - let controller: HttpTestingController; - - let authData: AuthData = { - username: 'test', - password: 'test', - redirectTo: "/" - } - let authResponse: AuthResponseData = { - token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY2NTY3MDMyNCwianRpIjoibWlhbyIsInR5cGUiOiJhY2Nlc3MiLCJzdWIiOiI2MGU4MThlMWYyMzBiZjQ2OWMwODNiYjUiLCJuYmYiOjE2NjU2NzAzMjQsImV4cCI6MTY2NjI3NTEyNH0.UVrnF8Ss6sNGul5-Ab59L4vZQEziRAHAkQ_egGBhAcY', - expires: new Date().toLocaleString() - } - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - RouterTestingModule.withRoutes([ - { path: 'login', component: LoginComponent }, - ]), - HttpClientTestingModule, - MaterialModule, - ], - }); - service = TestBed.inject(AuthService); - controller = TestBed.inject(HttpTestingController); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); - - it('test authentication', () => { - service.user.subscribe(user => { - if (user) { - expect(user.username).toBe('test'); - } - }) - - const expectedUrl = `${environment.backend_url}/auth/login`; - - service.login(authData); - const request = controller.expectOne(expectedUrl); - - // Answer the request so the Observable emits a value. - request.flush(authResponse); - }); - - it('test logout', () => { - // fake a login - const expectedUrl = `${environment.backend_url}/auth/login`; - service.login(authData); - const request = controller.expectOne(expectedUrl); - request.flush(authResponse); - - // do a logout - service.logout(); - - service.user.subscribe(user => { - expect(user).toBeNull(); - }); - }); -}); diff --git a/src/app/auth/auth.service.ts b/src/app/auth/auth.service.ts deleted file mode 100644 index 95b3bf3..0000000 --- a/src/app/auth/auth.service.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Router } from '@angular/router'; -import { HttpClient, HttpErrorResponse } from '@angular/common/http'; - -import { BehaviorSubject } from 'rxjs'; - -import { environment } from '../../environments/environment'; -import { UIService } from '../shared/ui.service'; -import { AuthData } from './auth-data.model'; -import { User } from './user.model'; - -// declared here and not exported: I don't need it outside this module -export interface AuthResponseData { - token: string; - expires: string; -} - -@Injectable({ - providedIn: 'root' -}) -export class AuthService { - // A variant of Subject that requires an initial value and emits its current - // value whenever it is subscribed to. A normal Subject is optimal - // to see status changes, like when user login and logout since UI need to change - // immediately. However, I need a user to do my request, so I need this value - // even after I made the subscription (for instance, when fetching data after - // login) - user = new BehaviorSubject(null); - - // track the timer for autoLogout. If I logout manually, this need to be cleared - private tokenExpirationTimer: any; - - constructor( - private http: HttpClient, - private uiService: UIService, - private router: Router, - ) { } - - login(authData: AuthData) { - // set loading state (required to show progress spinner during queries) - this.uiService.loadingStateChanged.next(true); - - // sending the auth request - this.http.post(environment.backend_url + '/auth/login', authData) - .subscribe({ - next: (authResponseData: AuthResponseData) => { - // create a new user object - const user = new User(authData.username, authResponseData.token); - - // emit user as a currently logged user - this.user.next(user); - - // every time I emit a new user, I need to set the timer for autoLogout - // passing expiresIn (milliseconds) - this.autoLogout(user.expiresIn); - - /* we need also to save data somewhere since when I reload the page, the application - start a new instance and so all the data I have (ie, the token) is lost. I can - use localStorage which is a persistent location on the browser which can store - key->value pairs. 'userData' is the key. The value can't be a JS object, need to - be converted as a string with JSON.stringify method, which can serialize a JS object */ - localStorage.setItem('userData', JSON.stringify(user)); - - // redirect to a path or "/" - this.router.navigate([authData.redirectTo]); - }, - error: (error: HttpErrorResponse) => { - this.uiService.showSnackbar(error.message, "Dismiss"); - } - }); - - // reset loading state - this.uiService.loadingStateChanged.next(false); - } - - /* when application starts (or is reloaded), search for userData saved in localStorage - an try to setUp a user object */ - autoLogin() { - const userData: { - username: string; - _token: string; - _tokenExpirationDate: string; - // https://stackoverflow.com/a/46915314/4385116 - // JSON.parse need a string as an argument - } = JSON.parse(localStorage.getItem('userData') || '{}'); - - if (!userData._token) { - // no user data: you must sign in - return; - } - - const loadedUser = new User( - userData.username, - userData._token - ); - - // check token validity. token property is a getter method, which returns - // null if token is expired. So: - if (loadedUser.token) { - // emit loaded user with our subject - this.user.next(loadedUser); - - // set the autoLogout timer - this.autoLogout(loadedUser.expiresIn); - } - } - - autoLogout(expirationDuration: number) { - // set a timer to log out the user after a certain time. however, if I log out - // manually, this timer need to be disabled - this.tokenExpirationTimer = setTimeout(() => { - this.logout(); - }, expirationDuration) - } - - logout() { - this.user.next(null); - this.router.navigate(["/login"]); - - // if I logout, I need to clear out the localStorage from user data, since - // the token won't be valid forever - localStorage.removeItem('userData'); - - // clear the logout timer - if (this.tokenExpirationTimer) { - clearTimeout(this.tokenExpirationTimer); - this.tokenExpirationTimer = null; - } - } -} diff --git a/src/app/auth/login/login.component.html b/src/app/auth/login/login.component.html deleted file mode 100644 index c20e673..0000000 --- a/src/app/auth/login/login.component.html +++ /dev/null @@ -1,32 +0,0 @@ -
-
- - - Please enter a valid user name - Invalid or missing user name - - - - - Please enter your password - Missing password - - - -
-
diff --git a/src/app/auth/login/login.component.scss b/src/app/auth/login/login.component.scss deleted file mode 100644 index c219af1..0000000 --- a/src/app/auth/login/login.component.scss +++ /dev/null @@ -1,5 +0,0 @@ - -mat-form-field { - margin-top: 2rem; - width: 300px; -} diff --git a/src/app/auth/login/login.component.spec.ts b/src/app/auth/login/login.component.spec.ts deleted file mode 100644 index cfa1c0b..0000000 --- a/src/app/auth/login/login.component.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ReactiveFormsModule } from '@angular/forms'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; - -import { MaterialModule } from '../../material/material.module'; -import { LoginComponent } from './login.component'; - -describe('LoginComponent', () => { - let component: LoginComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - RouterTestingModule.withRoutes([]), - BrowserAnimationsModule, - HttpClientTestingModule, - ReactiveFormsModule, - MaterialModule, - ], - declarations: [ LoginComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(LoginComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/auth/login/login.component.ts b/src/app/auth/login/login.component.ts deleted file mode 100644 index f7d60a0..0000000 --- a/src/app/auth/login/login.component.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { ActivatedRoute } from '@angular/router'; -import { Subscription } from 'rxjs'; - -import { UIService } from '../../shared/ui.service'; -import { AuthService } from '../auth.service'; - -@Component({ - selector: 'app-login', - templateUrl: './login.component.html', - styleUrls: ['./login.component.scss'] -}) -export class LoginComponent implements OnInit, OnDestroy { - // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#definite-assignment-assertions - // tell TS that this property will be used as this, even if I don't assign a value here or in constructor - loginForm!: FormGroup; - isLoading = false; - private loadingSubscription!: Subscription; - redirectTo!: string; - hide = true; - - constructor( - private route: ActivatedRoute, - private authService: AuthService, - private uiService: UIService - ) { } - - ngOnInit(): void { - // partially inspired from https://jasonwatmore.com/post/2016/12/08/angular-2-redirect-to-previous-url-after-login-with-auth-guard - // get return url from route parameters or default to '/' - this.redirectTo = this.route.snapshot.queryParams['next'] || '/'; - - // subscribe to see when request are performed by the server - this.loadingSubscription = this.uiService.loadingStateChanged.subscribe(isLoading => { - this.isLoading = isLoading; - }); - - this.loginForm = new FormGroup({ - // same ways to define validators - username: new FormControl('', {validators: [Validators.required]}), - password: new FormControl('', [Validators.required]) - }); - } - - onSubmit(): void { - this.authService.login({ - // this is reactive approach - username: this.loginForm.value.username, - password: this.loginForm.value.password, - redirectTo: this.redirectTo - }); - } - - ngOnDestroy() { - if (this.loadingSubscription) { - this.loadingSubscription.unsubscribe(); - } - } - -} diff --git a/src/app/auth/user.model.ts b/src/app/auth/user.model.ts deleted file mode 100644 index 318d0d7..0000000 --- a/src/app/auth/user.model.ts +++ /dev/null @@ -1,46 +0,0 @@ - -import jwt_decode, { JwtPayload } from 'jwt-decode' - -export class User { - private _tokenExpirationDate: Date; - private decoded: JwtPayload; - - // constructor enable the new keyword to create an object - constructor( - public username: string, - /* _ and private: I don't want to access the token from outside, if I need the - Token, I will call a method that will also check the validity before returning - a token string */ - private _token: string - ) { - // determining expiration time from token - // https://github.com/auth0/jwt-decode/issues/82#issuecomment-782941154 - this.decoded = jwt_decode(_token); - this._tokenExpirationDate = new Date(Number(this.decoded.exp) * 1000); - } - - // getter: is like a function but is accessed as a property (ex user.token) - // code will be executed when accessing this property. Assign a value to - // token will throw and error: we need a setter method to set this property - get token() { - // if i don't have an expiration date or this is less that current time (new Date()) - if (!this._tokenExpirationDate || new Date() > this._tokenExpirationDate) { - // the token is expired, return null even if I have a token - return null - } - - // return value of a private string - return this._token; - } - - get expiresIn() { - const now = new Date().getTime(); - const expiresIn = this._tokenExpirationDate.getTime() - now; - - if (expiresIn < 0) { - return 0; - } else { - return expiresIn; - } - } -} diff --git a/src/app/breeds/breeds-example.json b/src/app/breeds/breeds-example.json new file mode 100644 index 0000000..bc1d3e1 --- /dev/null +++ b/src/app/breeds/breeds-example.json @@ -0,0 +1,131 @@ +{ + "items": [ + { + "_id": { + "$oid": "66544d35719e7ca41a58d257" + }, + "aliases": [ + { + "dataset_id": { + "$oid": "604f75a61a08c53cebd09b67" + }, + "fid": "TEXEL_UY" + }, + { + "dataset_id": { + "$oid": "638a3fc844981838fd398504" + }, + "fid": "TEX" + }, + { + "dataset_id": { + "$oid": "638a3fc844981838fd398505" + }, + "fid": "TEX" + }, + { + "dataset_id": { + "$oid": "638a3fc944981838fd398506" + }, + "fid": "TEX" + }, + { + "dataset_id": { + "$oid": "638a3fca44981838fd398507" + }, + "fid": "TEX" + }, + { + "dataset_id": { + "$oid": "638a3fcb44981838fd398508" + }, + "fid": "TEX" + }, + { + "dataset_id": { + "$oid": "638a3fcc44981838fd398509" + }, + "fid": "TEX" + }, + { + "country": "Netherlands", + "dataset_id": { + "$oid": "604f75a61a08c53cebd09b58" + }, + "fid": "TEX" + }, + { + "country": "Netherlands", + "dataset_id": { + "$oid": "632addae76fa33fed2d2cc6d" + }, + "fid": "TEX" + }, + { + "country": "Unknown", + "dataset_id": { + "$oid": "632c869f0b8c366746d6e8b7" + }, + "fid": "TEX" + } + ], + "code": "TEX", + "n_individuals": 686, + "name": "Texel", + "species": "Sheep" + }, + { + "_id": { + "$oid": "66544d36655c76b775dbebd1" + }, + "aliases": [ + { + "dataset_id": { + "$oid": "604f74db1a08c53cebd09ae1" + }, + "fid": "0" + }, + { + "dataset_id": { + "$oid": "614ece6d6ac707ecf33bd641" + }, + "fid": "FRI" + }, + { + "dataset_id": { + "$oid": "614ece766ac707ecf33bd642" + }, + "fid": "FRI" + }, + { + "dataset_id": { + "$oid": "618d4568d65f4170c18fb2a4" + }, + "fid": "0" + }, + { + "dataset_id": { + "$oid": "6197b5a58aaeeb2699e4c925" + }, + "fid": "FRI" + }, + { + "dataset_id": { + "$oid": "621d081f020817ce8440ca9b" + }, + "fid": "FRZ" + } + ], + "code": "FRZ", + "n_individuals": 688, + "name": "Frizarta", + "species": "Sheep" + } + ], + "next": "/smarter-api/breeds?species=Sheep&size=2&page=2", + "page": 1, + "pages": 1, + "prev": null, + "size": 2, + "total": 2 +} diff --git a/src/app/breeds/breeds.component.html b/src/app/breeds/breeds.component.html index f70b54a..c6948e5 100644 --- a/src/app/breeds/breeds.component.html +++ b/src/app/breeds/breeds.component.html @@ -1,5 +1,5 @@ -

+

Searching {{ speciesControl.value }} breeds

diff --git a/src/app/breeds/breeds.model.ts b/src/app/breeds/breeds.model.ts index 9c570ed..7943ac6 100644 --- a/src/app/breeds/breeds.model.ts +++ b/src/app/breeds/breeds.model.ts @@ -10,10 +10,10 @@ export interface Breed { export interface BreedsAPI { items: Breed[]; - next?: string; + next?: string | null; page: number; pages: number; - prev?: string; + prev?: string | null; size: number; total: number; } diff --git a/src/app/datasets/dataset-detail/dataset-detail.component.spec.ts b/src/app/datasets/dataset-detail/dataset-detail.component.spec.ts index d4c935e..60d2d80 100644 --- a/src/app/datasets/dataset-detail/dataset-detail.component.spec.ts +++ b/src/app/datasets/dataset-detail/dataset-detail.component.spec.ts @@ -1,12 +1,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { SpyLocation } from '@angular/common/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { of } from 'rxjs'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Location } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { DatasetDetailComponent } from './dataset-detail.component'; import { Dataset } from '../datasets.model'; +import { SamplesComponent } from 'src/app/samples/samples.component'; const dataset: Dataset = { "_id": { @@ -38,11 +41,17 @@ const route = { data: of({ dataset: dataset }) }; describe('DatasetDetailComponent', () => { let component: DatasetDetailComponent; let fixture: ComponentFixture; + let location: SpyLocation; + let mockRouter = { + navigate: jasmine.createSpy('navigate') // Create a spy for the navigate method + }; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ - RouterTestingModule, + RouterTestingModule.withRoutes( + [{path: 'samples', component: SamplesComponent}] + ), HttpClientTestingModule, ], declarations: [ DatasetDetailComponent ], @@ -50,12 +59,15 @@ describe('DatasetDetailComponent', () => { schemas: [ CUSTOM_ELEMENTS_SCHEMA ], providers: [ { provide: ActivatedRoute, useValue: route }, + { provide: Location, useClass: SpyLocation }, + { provide: Router, useValue: mockRouter }, ] }) .compileComponents(); fixture = TestBed.createComponent(DatasetDetailComponent); component = fixture.componentInstance; + location = TestBed.inject(Location); fixture.detectChanges(); }); @@ -63,10 +75,24 @@ describe('DatasetDetailComponent', () => { expect(component).toBeTruthy(); }); - describe('ngOnInit', () => { - it('should get dataset data', () => { - component.ngOnInit(); - expect(component.dataset).toEqual(dataset); - }); + it('should get dataset data', () => { + component.ngOnInit(); + expect(component.dataset).toEqual(dataset); }); + + // https://codeutility.org/unit-testing-angular-6-location-go-back-stack-overflow/ + it('should go back to previous page on back button click', () => { + spyOn(location, 'back'); + component.goBack(); + expect(location.back).toHaveBeenCalled(); + }); + + it('should navigate to /samples with query params when getSamples is called', () => { + component.getSamples(); + expect(mockRouter.navigate).toHaveBeenCalledWith( + ['/samples'], + { queryParams: { dataset: component.dataset._id.$oid, species: component.dataset.species } } + ); + }); + }); diff --git a/src/app/datasets/datasets.component.html b/src/app/datasets/datasets.component.html index 21d62e5..786dbb9 100644 --- a/src/app/datasets/datasets.component.html +++ b/src/app/datasets/datasets.component.html @@ -1,5 +1,5 @@ -

+

Searching datasets

diff --git a/src/app/datasets/datasets.component.spec.ts b/src/app/datasets/datasets.component.spec.ts index 8a773a1..e112134 100644 --- a/src/app/datasets/datasets.component.spec.ts +++ b/src/app/datasets/datasets.component.spec.ts @@ -1,25 +1,50 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Observable } from 'rxjs/internal/Observable'; +import { of } from 'rxjs/internal/observable/of'; +import { map } from 'rxjs/operators'; import { MaterialModule } from '../material/material.module'; import { DatasetsComponent } from './datasets.component'; +import { DatasetsService } from './datasets.service'; describe('DatasetsComponent', () => { let component: DatasetsComponent; let fixture: ComponentFixture; + let datasetsService: DatasetsService; + let router: Router; + + // Creates an observable that will be used for testing ActivatedRoute params + const paramsMock = new Observable((observer) => { + observer.next({ + page: 1, + size: 5, + sort: 'name', + order: 'desc', + search: null + }); + observer.complete(); + }); beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ BrowserAnimationsModule, HttpClientTestingModule, - RouterTestingModule, + RouterTestingModule.withRoutes( + [{path: 'datasets', component: DatasetsComponent}] + ), MaterialModule, ], declarations: [ DatasetsComponent ], + providers: [ + { provide: DatasetsService, useValue: { getDatasets: () => of([]) } }, + { provide: ActivatedRoute, useValue: { queryParams: paramsMock }} + ], // https://testing-angular.com/testing-components-with-children/#unit-test schemas: [ CUSTOM_ELEMENTS_SCHEMA ], }) @@ -27,12 +52,45 @@ describe('DatasetsComponent', () => { }); beforeEach(() => { + router = jasmine.createSpyObj('Router', ['navigate']); fixture = TestBed.createComponent(DatasetsComponent); component = fixture.componentInstance; + datasetsService = TestBed.inject(DatasetsService); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should have the correct columns', () => { + expect(component.displayedColumns).toEqual(['file', 'species', 'breed', 'country', 'type']); + }); + + it('should call getDatasets on init', fakeAsync(() => { + spyOn(datasetsService, 'getDatasets').and.callThrough(); + component.ngAfterViewInit(); + tick(1); + expect(datasetsService.getDatasets).toHaveBeenCalled(); + })); + + it('should update searchValue and navigate to /datasets with query params', () => { + const event = { target: { value: 'test' } }; + const queryParams = component.getQueryParams(); + + of(event).pipe( + map((event: any) => { + component.searchValue = event.target.value; + router.navigate( + ["/datasets"], + { + queryParams: queryParams + } + ) + }) + ).subscribe(); + + expect(component.searchValue).toBe('test'); + expect(router.navigate).toHaveBeenCalledWith(['/datasets'], { queryParams: queryParams }); + }); }); diff --git a/src/app/datasets/datasets.service.spec.ts b/src/app/datasets/datasets.service.spec.ts index de66d65..6891122 100644 --- a/src/app/datasets/datasets.service.spec.ts +++ b/src/app/datasets/datasets.service.spec.ts @@ -22,13 +22,13 @@ describe('DatasetsService', () => { }); it('Test for searching datasets', () => { - const pageIndex = 0; - const pageSize = 10; - const sortActive = ''; + const pageIndex = 1; + const pageSize = 2; + const sortActive = 'file'; const sortDirection: SortDirection = "desc"; const searchValue = 'adaptmap'; - const expectedUrl = `${environment.backend_url}/datasets?size=${pageSize}&search=${searchValue}`; + const expectedUrl = `${environment.backend_url}/datasets?page=${pageSize}&size=${pageSize}&sort=${sortActive}&order=${sortDirection}&search=${searchValue}`; service.getDatasets(sortActive, sortDirection, pageIndex, pageSize, searchValue).subscribe( (datasets) => {}); diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 4f62461..b27d740 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -1,6 +1,11 @@
SMAll Ruminants breeding for Efficiency and Resilience -

Welcome to the SMARTER database

+

Welcome to the SMARTER Database

+

+ The SMARTER database is a collection of data from small ruminant research projects.
+ The data is open and available to the community. See our + about page for more information. +

diff --git a/src/app/home/home.component.scss b/src/app/home/home.component.scss index 7c9fabe..85a6d03 100644 --- a/src/app/home/home.component.scss +++ b/src/app/home/home.component.scss @@ -8,3 +8,15 @@ width: 85%; height: auto; } + +h1 { + text-align: center; + font-size: 2.5rem; + margin: 1rem; +} + +p { + text-align: center; + margin: 1rem; + padding-top: 1rem; +} diff --git a/src/app/home/home.component.spec.ts b/src/app/home/home.component.spec.ts index bb6a041..3e142f7 100644 --- a/src/app/home/home.component.spec.ts +++ b/src/app/home/home.component.spec.ts @@ -27,6 +27,6 @@ describe('HomeComponent', () => { const fixture = TestBed.createComponent(HomeComponent); fixture.detectChanges(); const compiled = fixture.nativeElement; - expect(compiled.querySelector('h1').textContent).toContain('Welcome to the SMARTER database'); + expect(compiled.querySelector('h1').textContent).toContain('Welcome to the SMARTER Database'); }); }); diff --git a/src/app/navigation/header/header.component.html b/src/app/navigation/header/header.component.html index b751c4c..a6feb7c 100644 --- a/src/app/navigation/header/header.component.html +++ b/src/app/navigation/header/header.component.html @@ -26,12 +26,6 @@
  • Variants
  • -
  • - Login -
  • -
  • - Logout -
  • diff --git a/src/app/navigation/header/header.component.spec.ts b/src/app/navigation/header/header.component.spec.ts index 0fec6ad..d7748bd 100644 --- a/src/app/navigation/header/header.component.spec.ts +++ b/src/app/navigation/header/header.component.spec.ts @@ -2,6 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { By } from '@angular/platform-browser'; import { MaterialModule } from '../../material/material.module'; import { HeaderComponent } from './header.component'; @@ -32,4 +33,19 @@ describe('HeaderComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should call onToggleSidenav when the menu button is clicked', () => { + spyOn(component, 'onToggleSidenav'); + const button = fixture.debugElement.query(By.css('button[mat-icon-button]')).nativeElement; + button.click(); + expect(component.onToggleSidenav).toHaveBeenCalled(); + }); + + it('should emit sidenavToggle event when onToggleSidenav is called', () => { + spyOn(component.sidenavToggle, 'emit'); + + component.onToggleSidenav(); + + expect(component.sidenavToggle.emit).toHaveBeenCalled(); + }); }); diff --git a/src/app/navigation/header/header.component.ts b/src/app/navigation/header/header.component.ts index 28d669c..b671866 100644 --- a/src/app/navigation/header/header.component.ts +++ b/src/app/navigation/header/header.component.ts @@ -1,42 +1,22 @@ -import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Output } from '@angular/core'; import { Subscription } from 'rxjs'; -import { AuthService } from '../../auth/auth.service'; - @Component({ selector: 'app-header', templateUrl: './header.component.html', styleUrls: ['./header.component.scss'] }) -export class HeaderComponent implements OnInit, OnDestroy { - // listed to sidenav frout outside +export class HeaderComponent { + // listed to sidenav from outside @Output() sidenavToggle = new EventEmitter(); // mind authentication isAuthenticated = false; authSubscription!: Subscription; - constructor(private authService: AuthService) { } - - ngOnInit(): void { - this.authSubscription = this.authService.user.subscribe(user => { - // if I have a user, I'm authenticated - this.isAuthenticated = !user ? false : true; - }); - } - onToggleSidenav() { this.sidenavToggle.emit(); } - onLogout() { - this.authService.logout(); - } - - ngOnDestroy() { - if (this.authSubscription) { - this.authSubscription.unsubscribe(); - } - } } diff --git a/src/app/navigation/sidenav-list/sidenav-list.component.html b/src/app/navigation/sidenav-list/sidenav-list.component.html index 39e7ad0..a7e2ccb 100644 --- a/src/app/navigation/sidenav-list/sidenav-list.component.html +++ b/src/app/navigation/sidenav-list/sidenav-list.component.html @@ -23,14 +23,4 @@ list Variants - - face - Login - - - - diff --git a/src/app/navigation/sidenav-list/sidenav-list.component.spec.ts b/src/app/navigation/sidenav-list/sidenav-list.component.spec.ts index 3010d17..8058175 100644 --- a/src/app/navigation/sidenav-list/sidenav-list.component.spec.ts +++ b/src/app/navigation/sidenav-list/sidenav-list.component.spec.ts @@ -32,4 +32,21 @@ describe('SidenavListComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should emit closeSidenav event when onClose is called', () => { + spyOn(component.closeSidenav, 'emit'); + + component.onClose(); + + expect(component.closeSidenav.emit).toHaveBeenCalled(); + }); + + it('should call onClose when onLogout is called', () => { + spyOn(component, 'onClose'); + + component.onLogout(); + + expect(component.onClose).toHaveBeenCalled(); + }); + }); diff --git a/src/app/navigation/sidenav-list/sidenav-list.component.ts b/src/app/navigation/sidenav-list/sidenav-list.component.ts index 0e2773d..81e477d 100644 --- a/src/app/navigation/sidenav-list/sidenav-list.component.ts +++ b/src/app/navigation/sidenav-list/sidenav-list.component.ts @@ -1,30 +1,19 @@ -import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Output } from '@angular/core'; import { Subscription } from 'rxjs'; -import { AuthService } from '../../auth/auth.service'; - @Component({ selector: 'app-sidenav-list', templateUrl: './sidenav-list.component.html', styleUrls: ['./sidenav-list.component.scss'] }) -export class SidenavListComponent implements OnInit, OnDestroy { +export class SidenavListComponent { @Output() closeSidenav = new EventEmitter(); // mind authentication isAuthenticated = false; authSubscription!: Subscription; - constructor(private authService: AuthService) { } - - ngOnInit(): void { - this.authSubscription = this.authService.user.subscribe(user => { - // if I have a user, I'm authenticated - this.isAuthenticated = !user ? false : true; - }); - } - onClose() { this.closeSidenav.emit(); } @@ -32,13 +21,6 @@ export class SidenavListComponent implements OnInit, OnDestroy { onLogout() { // required to close the sidebar this.onClose(); - this.authService.logout(); - } - - ngOnDestroy() { - if (this.authSubscription) { - this.authSubscription.unsubscribe(); - } } } diff --git a/src/app/samples/countries-example.json b/src/app/samples/countries-example.json new file mode 100644 index 0000000..8abb726 --- /dev/null +++ b/src/app/samples/countries-example.json @@ -0,0 +1,32 @@ +{ + "items": [ + { + "_id": { + "$oid": "665679be2c1c55629d917d14" + }, + "alpha_2": "AL", + "alpha_3": "ALB", + "name": "Albania", + "numeric": 8, + "official_name": "Republic of Albania", + "species": ["Sheep"] + }, + { + "_id": { + "$oid": "665679be2c1c55629d917d15" + }, + "alpha_2": "DZ", + "alpha_3": "DZA", + "name": "Algeria", + "numeric": 12, + "official_name": "People's Democratic Republic of Algeria", + "species": ["Sheep"] + } + ], + "next": "/smarter-api/countries?species=Sheep&size=2&page=2", + "page": 1, + "pages": 1, + "prev": null, + "size": 2, + "total": 2 +} diff --git a/src/app/samples/samples.component.html b/src/app/samples/samples.component.html index 70712d6..0abe8ce 100644 --- a/src/app/samples/samples.component.html +++ b/src/app/samples/samples.component.html @@ -1,5 +1,5 @@ -

    +

    Searching {{ speciesControl.value }} samples

    diff --git a/src/app/samples/samples.model.ts b/src/app/samples/samples.model.ts index e54fa6c..06b4791 100644 --- a/src/app/samples/samples.model.ts +++ b/src/app/samples/samples.model.ts @@ -55,10 +55,10 @@ export interface Country { export interface CountriesAPI { items: Country[]; - next?: string; + next?: string | null; page: number; pages: number; - prev?: string; + prev?: string | null; size: number; total: number; } diff --git a/src/app/samples/samples.service.spec.ts b/src/app/samples/samples.service.spec.ts index 872cacb..a0a0678 100644 --- a/src/app/samples/samples.service.spec.ts +++ b/src/app/samples/samples.service.spec.ts @@ -4,11 +4,16 @@ import { SortDirection } from '@angular/material/sort'; import { environment } from '../../environments/environment'; import { SamplesService } from './samples.service'; -import { SamplesSearch } from './samples.model'; +import { CountriesAPI, SamplesSearch } from './samples.model'; +import countriesData from './countries-example.json'; +import breedsData from '../breeds/breeds-example.json'; +import { BreedsAPI } from '../breeds/breeds.model'; describe('SamplesService', () => { let service: SamplesService; let controller: HttpTestingController; + let mockCountries: CountriesAPI = countriesData; + let mockBreeds: BreedsAPI = breedsData; beforeEach(() => { TestBed.configureTestingModule({ @@ -23,15 +28,15 @@ describe('SamplesService', () => { }); it('Test for searching samples', () => { - const pageIndex = 0; - const pageSize = 10; - const sortActive = ''; + const pageIndex = 1; + const pageSize = 2; + const sortActive = 'sample_id'; const sortDirection: SortDirection = "desc"; const samplesSearch: SamplesSearch = { breed: "Merino" }; - const expectedUrl = `${environment.backend_url}/samples/${service.selectedSpecie.toLowerCase()}?size=${pageSize}&breed=Merino`; + const expectedUrl = `${environment.backend_url}/samples/${service.selectedSpecie.toLowerCase()}?page=2&size=2&sort=sample_id&order=desc&breed=Merino`; service.getSamples(sortActive, sortDirection, pageIndex, pageSize, samplesSearch).subscribe( (samples) => {}); @@ -55,6 +60,8 @@ describe('SamplesService', () => { service.getCountries(); const request = controller.expectOne(expectedUrl); + request.flush(mockCountries); + expect(service.countries.length).toBe(mockCountries.total) controller.verify(); }); @@ -63,6 +70,8 @@ describe('SamplesService', () => { service.getBreeds(); const request = controller.expectOne(expectedUrl); + request.flush(mockBreeds); + expect(service.breeds.length).toBe(mockBreeds.total); controller.verify(); }) }); diff --git a/src/app/shared/overlay.service.spec.ts b/src/app/shared/overlay.service.spec.ts new file mode 100644 index 0000000..1e8cab4 --- /dev/null +++ b/src/app/shared/overlay.service.spec.ts @@ -0,0 +1,60 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { Overlay } from '@angular/cdk/overlay'; +import { OverlayService } from './overlay.service'; +import { Component, ViewChild, TemplateRef, ViewContainerRef } from '@angular/core'; + +// A fake component with ViewContainerRef +@Component({ + template: ` +
    + + ` +}) +class TestComponent { + @ViewChild('testContainer', { read: ViewContainerRef }) vcRef!: ViewContainerRef; + @ViewChild('testTemplate') templateRef!: TemplateRef; +} + +describe('OverlayService', () => { + let component: TestComponent; + let fixture: ComponentFixture; + let service: OverlayService; + let overlay: Overlay; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestComponent], + providers: [OverlayService, Overlay] + }).compileComponents(); + + fixture = TestBed.createComponent(TestComponent); + component = fixture.componentInstance; + service = TestBed.inject(OverlayService); + overlay = TestBed.inject(Overlay); + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should create an overlay', () => { + const spy = spyOn(overlay, 'create').and.callThrough(); + service.createOverlay({}); + expect(spy).toHaveBeenCalled(); + }); + + it('should attach a template portal', () => { + const overlayRef = service.createOverlay({}); + const spy = spyOn(overlayRef, 'attach').and.callThrough(); + const templateRef = component.templateRef; + const vcRef = component.vcRef; + service.attachTemplatePortal(overlayRef, templateRef, vcRef); + expect(spy).toHaveBeenCalled(); + }); + + it('should position globally center', () => { + const positionStrategy = service.positionGloballyCenter(); + expect(positionStrategy).toBeTruthy(); + }); +}); diff --git a/src/app/shared/progress-spinner/progress-spinner.component.spec.ts b/src/app/shared/progress-spinner/progress-spinner.component.spec.ts index 54a5e19..88eab8b 100644 --- a/src/app/shared/progress-spinner/progress-spinner.component.spec.ts +++ b/src/app/shared/progress-spinner/progress-spinner.component.spec.ts @@ -1,25 +1,74 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Overlay } from '@angular/cdk/overlay'; - +import { Overlay, OverlayRef, PositionStrategy } from '@angular/cdk/overlay'; import { ProgressSpinnerComponent } from './progress-spinner.component'; +import { OverlayService } from '../overlay.service'; describe('ProgressSpinnerComponent', () => { let component: ProgressSpinnerComponent; let fixture: ComponentFixture; + let overlayService: OverlayService; + let overlayRefSpy: jasmine.SpyObj; + let positionStrategySpy: jasmine.SpyObj; + + beforeEach(() => { + overlayRefSpy = jasmine.createSpyObj( + 'OverlayRef', + ['hasAttached', 'detach'] + ); + positionStrategySpy = jasmine.createSpyObj('PositionStrategy', ['global', 'centerHorizontally', 'centerVertically']); - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ ProgressSpinnerComponent ], - providers: [ Overlay ], - }) - .compileComponents(); + TestBed.configureTestingModule({ + declarations: [ProgressSpinnerComponent], + providers: [ + { + provide: Overlay, + useValue: { + create: () => overlayRefSpy, + position: () => positionStrategySpy + } + }, + OverlayService + ] + }); fixture = TestBed.createComponent(ProgressSpinnerComponent); component = fixture.componentInstance; - fixture.detectChanges(); + overlayService = TestBed.inject(OverlayService); + + // add a custom overlayRef to the component for testing purposes + component.setOverlayRefForTesting(overlayRefSpy); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should attach the template portal when displayProgressSpinner is true', () => { + component.displayProgressSpinner = true; + overlayRefSpy.hasAttached.and.returnValue(false); + const attachSpy = spyOn(overlayService, 'attachTemplatePortal'); + + component.ngDoCheck(); + + expect(attachSpy).toHaveBeenCalled(); + }); + + it('should detach the template portal when displayProgressSpinner is false', () => { + component.displayProgressSpinner = false; + overlayRefSpy.hasAttached.and.returnValue(true); + + component.ngDoCheck(); + + expect(overlayRefSpy.detach).toHaveBeenCalled(); + }); + + it('should create overlay on init', () => { + const createSpy = spyOn(overlayService, 'createOverlay'); + const positionSpy = spyOn(overlayService, 'positionGloballyCenter'); + + component.ngOnInit(); + + expect(createSpy).toHaveBeenCalled(); + expect(positionSpy).toHaveBeenCalled(); + }); }); diff --git a/src/app/shared/progress-spinner/progress-spinner.component.ts b/src/app/shared/progress-spinner/progress-spinner.component.ts index 0d2dd90..9930855 100644 --- a/src/app/shared/progress-spinner/progress-spinner.component.ts +++ b/src/app/shared/progress-spinner/progress-spinner.component.ts @@ -29,6 +29,11 @@ export class ProgressSpinnerComponent { private vcRef: ViewContainerRef, private overlayService: OverlayService) { } + // For testing purposes + public setOverlayRefForTesting(overlayRef: OverlayRef): void { + this.overlayRef = overlayRef; + } + ngOnInit() { // Config for Overlay Service this.progressSpinnerOverlayConfig = { diff --git a/src/app/shared/shared.model.spec.ts b/src/app/shared/shared.model.spec.ts new file mode 100644 index 0000000..fa92705 --- /dev/null +++ b/src/app/shared/shared.model.spec.ts @@ -0,0 +1,21 @@ +import { ObjectDate } from './shared.model'; + +describe('ObjectDate', () => { + it('should create an instance with a Date object', () => { + const date = new Date(); + const objectDate = new ObjectDate(date); + expect(objectDate.$date).toEqual(date); + }); + + it('should create an instance with a string', () => { + const dateString = '2022-01-01T00:00:00Z'; + const objectDate = new ObjectDate(dateString); + expect(objectDate.$date).toEqual(new Date(dateString)); + }); + + it('should copy the original date for for invalid date string', () => { + const invalidDateString = 'invalid-date'; + const objectDate = new ObjectDate(invalidDateString); + expect(objectDate.$date).toEqual(invalidDateString); + }); +}); diff --git a/src/app/shared/shared.model.ts b/src/app/shared/shared.model.ts index a4043b7..67fdba8 100644 --- a/src/app/shared/shared.model.ts +++ b/src/app/shared/shared.model.ts @@ -5,6 +5,23 @@ export interface ObjectID { $oid: string; } +export class ObjectDate { + $date: Date | string; + + constructor(value: string | Date) { + if (value instanceof Date) { + this.$date = value; + } else { + const date = new Date(value); + if (isNaN(date.getTime())) { + this.$date = value; // Store the original string + } else { + this.$date = date; + } + } + } +} + // https://dev.to/ankittanna/how-to-create-a-type-for-complex-json-object-in-typescript-d81 export type JSONValue = | string diff --git a/src/app/shared/ui.service.spec.ts b/src/app/shared/ui.service.spec.ts new file mode 100644 index 0000000..1023ac0 --- /dev/null +++ b/src/app/shared/ui.service.spec.ts @@ -0,0 +1,35 @@ +import { TestBed } from '@angular/core/testing'; +import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { UIService } from './ui.service'; + +describe('UIService', () => { + let service: UIService; + let snackBar: MatSnackBar; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [MatSnackBarModule, NoopAnimationsModule], + providers: [UIService] + }); + + service = TestBed.inject(UIService); + snackBar = TestBed.inject(MatSnackBar); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should show a snackbar', () => { + const spy = spyOn(snackBar, 'open').and.callThrough(); + service.showSnackbar('Test message', 'Test action', 3000); + expect(spy).toHaveBeenCalledWith('Test message', 'Test action', { duration: 3000 }); + }); + + it('should show a snackbar with default duration when no duration is provided', () => { + const spy = spyOn(snackBar, 'open').and.callThrough(); + service.showSnackbar('Test message', 'Test action'); + expect(spy).toHaveBeenCalledWith('Test message', 'Test action', { duration: 5000 }); + }); +}); diff --git a/src/app/variants/variant-detail/variant-detail.component.html b/src/app/variants/variant-detail/variant-detail.component.html index 4ca3d54..1fbec0b 100644 --- a/src/app/variants/variant-detail/variant-detail.component.html +++ b/src/app/variants/variant-detail/variant-detail.component.html @@ -48,6 +48,7 @@

    id: {{ variant._id.$oid }}

    Chrom: {{ location.chrom }} Position: {{ location.position }} + Date: {{ location.date.$date | date:'longDate':'en-US' }} Illumina: {{ location.illumina }} Illumina TOP: {{ location.illumina_top }} Illumina Forward: {{ location.illumina_forward }} diff --git a/src/app/variants/variant-detail/variant-detail.component.spec.ts b/src/app/variants/variant-detail/variant-detail.component.spec.ts index 6414b0e..cb5a5f1 100644 --- a/src/app/variants/variant-detail/variant-detail.component.spec.ts +++ b/src/app/variants/variant-detail/variant-detail.component.spec.ts @@ -21,7 +21,7 @@ const variant: Variant = { { "chrom": "15", "date": { - "$date": "2009-01-07T00:00:00Z" + "$date": new Date("2009-01-07T00:00:00Z") }, "illumina": "A/G", "illumina_strand": "TOP", @@ -47,7 +47,7 @@ const variant: Variant = { { "chrom": "15", "date": { - "$date": "2017-03-13T00:00:00Z" + "$date": new Date("2017-03-13T00:00:00Z") }, "illumina": "A/G", "illumina_strand": "TOP", diff --git a/src/app/variants/variants.component.html b/src/app/variants/variants.component.html index ef08881..4f0cf0b 100644 --- a/src/app/variants/variants.component.html +++ b/src/app/variants/variants.component.html @@ -1,4 +1,4 @@ -

    +

    Searching {{ speciesControl.value }} SNPs in {{ selectedAssembly }} assembly

    diff --git a/src/app/variants/variants.model.ts b/src/app/variants/variants.model.ts index daf87c2..73f98f8 100644 --- a/src/app/variants/variants.model.ts +++ b/src/app/variants/variants.model.ts @@ -1,5 +1,5 @@ -import { JSONObject, ObjectID } from "../shared/shared.model"; +import { JSONObject, ObjectID, ObjectDate } from "../shared/shared.model"; export interface Location { ss_id?: string; @@ -14,7 +14,7 @@ export interface Location { affymetrix_ab?: string; strand?: string; imported_from: string; - date?: JSONObject; + date?: ObjectDate; } export interface Probeset { @@ -56,7 +56,7 @@ export interface VariantsSearch { export interface SupportedChip { _id: ObjectID; - manifacturer?: string; + manufacturer?: string; n_of_snps?: number; name: string; species: string; diff --git a/src/app/variants/variants.service.ts b/src/app/variants/variants.service.ts index 458989c..e1c44d4 100644 --- a/src/app/variants/variants.service.ts +++ b/src/app/variants/variants.service.ts @@ -1,3 +1,4 @@ +import { map } from 'rxjs/operators'; import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { SortDirection } from '@angular/material/sort'; @@ -5,6 +6,7 @@ import { environment } from 'src/environments/environment'; import { SupportedChip, SupportedChipsAPI, Variant, VariantsAPI, VariantsSearch } from './variants.model'; import { Observable, Subject, forkJoin } from 'rxjs'; +import { ObjectDate } from '../shared/shared.model'; @Injectable({ providedIn: 'root' @@ -69,7 +71,16 @@ export class VariantsService { getVariant(_id: string, species: string) { const url = environment.backend_url + '/variants/' + species + "/" + _id; - return this.http.get(url); + return this.http.get(url).pipe( + map((variant: Variant) => { + variant.locations.forEach((location) => { + if (location.date) { + location.date = new ObjectDate(location.date.$date); + } + }); + return variant; + }) + ); } getSupportedChips(species: string,): void { diff --git a/src/styles.scss b/src/styles.scss index 07993b9..47a734e 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -10,6 +10,18 @@ $my-accent: mat.define-palette(mat.$orange-palette, A200, A100, A400); // The "warn" palette is optional and defaults to red if not specified. $my-warn: mat.define-palette(mat.$red-palette); +// define the light theme +$my-theme: mat.define-light-theme(( + color: ( + primary: $my-primary, + accent: $my-accent, + warn: $my-warn, + ) +)); + +// includes styles for all components in the library +@include mat.all-component-themes($my-theme); + // A mixin to deal with mat-row hover // https://github.com/angular/components/issues/8204 @mixin mat-table-hover($is-dark) { @@ -27,18 +39,6 @@ $my-warn: mat.define-palette(mat.$red-palette); } } -// define the light theme -$my-theme: mat.define-light-theme(( - color: ( - primary: $my-primary, - accent: $my-accent, - warn: $my-warn, - ) -)); - -// includes styles for all components in the library -@include mat.all-component-themes($my-theme); - // include custom mixins @include mat-table-hover(false); @@ -46,7 +46,7 @@ html, body { height: 100%; } body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } -.free-text { +.small-padding { padding-left: 15px; }