diff --git a/package-lock.json b/package-lock.json index cd7fee423..4281e1141 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,7 @@ "lodash-es": "^4.17.21", "mixpanel-browser": "^2.45.0", "ngx-color": "7.0.0", + "rudder-sdk-js": "^2.3.0", "rxjs": "~6.6.7", "tslib": "^2.3.1", "uuid": "^8.3.2", @@ -92,7 +93,7 @@ "jest-junit": "^13.0.0", "jest-preset-angular": "^8.4.0", "lodash": "^4.17.21", - "ng-mocks": "^13.3.0", + "ng-mocks": "^13.4.0", "ng-packagr": "^12.2.5", "prettier": "^2.2.1", "pretty-quick": "^3.1.3", @@ -100,7 +101,7 @@ "tslint": "~6.1.3", "tslint-config-prettier": "^1.18.0", "typescript": "~4.3.5", - "typescript-tslint-plugin": "^1.0.1" + "typescript-tslint-plugin": "^1.0.2" } }, "node_modules/@aduh95/viz.js": { @@ -21322,9 +21323,9 @@ "dev": true }, "node_modules/ng-mocks": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/ng-mocks/-/ng-mocks-13.3.0.tgz", - "integrity": "sha512-MtHoa4dL0OTrGwwbT7d6BbSOI71pY8KTftf/VbDPbK/XalPj/429UcbDsZuaSRV+tgMFHQ6LlhJMUCD0/i4f/g==", + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/ng-mocks/-/ng-mocks-13.4.0.tgz", + "integrity": "sha512-l4Iwg4a6CQ8w8Q0KzZ/FJVju8SqIW91GydoInR7PeDBhzO/cGAwGeHxtLPvXFKVdltgg3LzIzl88hpP+V66Gmg==", "dev": true, "peerDependencies": { "@angular/common": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14", @@ -28872,9 +28873,10 @@ } }, "node_modules/typescript-tslint-plugin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typescript-tslint-plugin/-/typescript-tslint-plugin-1.0.1.tgz", - "integrity": "sha512-akj+J1FDoEv8y5uSs6f6SfkY3ywnhnTifDvOmwejSs4U+a18p73YoetrllKCDf14iq0VMNwIgCxNY/v88K6QYg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typescript-tslint-plugin/-/typescript-tslint-plugin-1.0.2.tgz", + "integrity": "sha512-M4IhSOCG/2snlD1nTRszQNDuV5ITe/GjHethOyC3Ugq9RFThyx4zjn8Mg/ij5ng/puoYmq1jnW90W1KWxonhxw==", + "deprecated": "TSLint has been deprecated in favor of ESLint. This plugin has also been deprecated in favor of ESLint's tooling", "dev": true, "dependencies": { "minimatch": "^3.0.4", @@ -32956,26 +32958,13 @@ "execa": "^5.0.0" }, "dependencies": { - "@angular-devkit/core": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.1.3.tgz", - "integrity": "sha512-o14jGDk4h14dVYYQafOn+2rq9CDmDMbDV6logqKYCLzTDRlK8gccDnqJM/QKAlfWCzbllZqcHDmg6FyoRLO9RQ==", + "@ampproject/remapping": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", + "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", "dev": true, "requires": { - "ajv": "8.8.2", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } + "@jridgewell/trace-mapping": "^0.3.0" } }, "get-stream": { @@ -33021,266 +33010,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "@angular-devkit/core": "13.1.3", - "jsonc-parser": "3.0.0", - "magic-string": "0.25.7", - "ora": "5.4.1", - "rxjs": "6.6.7" - } - }, - "@babel/core": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz", - "integrity": "sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.7", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.7.tgz", - "integrity": "sha512-/ST3Sg8MLGY5HVYmrjOgL60ENux/HfO/CsUh7y4MalThufhE/Ff/6EibFDHi4jiDCaWfJKoqbE6oTh21c5hrRg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.0.tgz", - "integrity": "sha512-7hfT8lUljl/tM3h+izTX/pO3W3frz2ok6Pk+gzys8iJqDfZrZy2pXjRTZAvG2YmfHun1X4q8/UZRLatMfqc5Tg==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.7.tgz", - "integrity": "sha512-TTXBT3A5c11eqRzaC6beO6rlFT3Mo9C2e8eB44tTr52ESXSK2CIc2fOp1ynpAwQA8HhBMho+WXhMHWlAe3xkpw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.7", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.7.tgz", - "integrity": "sha512-pFEfjnK4DfXCfAlA5I98BYdDJD8NltMzx19gt6DAmfE+2lXRfPUoa0/5SUjT4+TDE1W/rcxU/1lgN55vpAjjdg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.7" - } - }, - "@babel/preset-env": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.7.tgz", - "integrity": "sha512-urX3Cee4aOZbRWOSa3mKPk0aqDikfILuo+C7qq7HY0InylGNZ1fekq9jmlr3pLWwZHF4yD7heQooc2Pow2KMyQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-async-generator-functions": "^7.16.7", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-class-static-block": "^7.16.7", - "@babel/plugin-proposal-dynamic-import": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.16.7", - "@babel/plugin-proposal-json-strings": "^7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.16.7", - "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-private-methods": "^7.16.7", - "@babel/plugin-proposal-private-property-in-object": "^7.16.7", - "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.16.7", - "@babel/plugin-transform-async-to-generator": "^7.16.7", - "@babel/plugin-transform-block-scoped-functions": "^7.16.7", - "@babel/plugin-transform-block-scoping": "^7.16.7", - "@babel/plugin-transform-classes": "^7.16.7", - "@babel/plugin-transform-computed-properties": "^7.16.7", - "@babel/plugin-transform-destructuring": "^7.16.7", - "@babel/plugin-transform-dotall-regex": "^7.16.7", - "@babel/plugin-transform-duplicate-keys": "^7.16.7", - "@babel/plugin-transform-exponentiation-operator": "^7.16.7", - "@babel/plugin-transform-for-of": "^7.16.7", - "@babel/plugin-transform-function-name": "^7.16.7", - "@babel/plugin-transform-literals": "^7.16.7", - "@babel/plugin-transform-member-expression-literals": "^7.16.7", - "@babel/plugin-transform-modules-amd": "^7.16.7", - "@babel/plugin-transform-modules-commonjs": "^7.16.7", - "@babel/plugin-transform-modules-systemjs": "^7.16.7", - "@babel/plugin-transform-modules-umd": "^7.16.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.7", - "@babel/plugin-transform-new-target": "^7.16.7", - "@babel/plugin-transform-object-super": "^7.16.7", - "@babel/plugin-transform-parameters": "^7.16.7", - "@babel/plugin-transform-property-literals": "^7.16.7", - "@babel/plugin-transform-regenerator": "^7.16.7", - "@babel/plugin-transform-reserved-words": "^7.16.7", - "@babel/plugin-transform-shorthand-properties": "^7.16.7", - "@babel/plugin-transform-spread": "^7.16.7", - "@babel/plugin-transform-sticky-regex": "^7.16.7", - "@babel/plugin-transform-template-literals": "^7.16.7", - "@babel/plugin-transform-typeof-symbol": "^7.16.7", - "@babel/plugin-transform-unicode-escapes": "^7.16.7", - "@babel/plugin-transform-unicode-regex": "^7.16.7", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.16.7", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.4.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", - "core-js-compat": "^3.19.1", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@compodoc/compodoc": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/@compodoc/compodoc/-/compodoc-1.1.19.tgz", - "integrity": "sha512-09vdSIgoAXWD1MiLZNhiljLNQ1XzHw/w5shw5IPcUImr/I+1Y52srUL46mEXN8AXo0hbHb5LZcgs70mmrOvY7Q==", - "dev": true, - "requires": { - "@angular-devkit/schematics": "^13.2.4", - "@babel/core": "^7.17.5", - "@babel/preset-env": "^7.16.11", - "@compodoc/live-server": "^1.2.3", - "@compodoc/ngd-transformer": "^2.1.0", - "chalk": "4.1.2", - "cheerio": "^1.0.0-rc.10", - "chokidar": "^3.5.3", - "colors": "1.4.0", - "commander": "^9.0.0", - "cosmiconfig": "^7.0.1", - "decache": "^4.6.1", - "fancy-log": "^2.0.0", - "findit2": "^2.2.3", - "fs-extra": "^10.0.1", - "glob": "^7.2.0", - "handlebars": "^4.7.7", - "html-entities": "^2.3.2", - "i18next": "^21.6.11", - "inside": "^1.0.0", - "json5": "^2.2.0", - "lodash": "^4.17.21", - "loglevel": "^1.8.0", - "loglevel-plugin-prefix": "^0.8.4", - "lunr": "^2.3.9", - "marked": "^4.0.12", - "minimist": "^1.2.5", - "opencollective-postinstall": "^2.0.3", - "os-name": "4.0.1", - "pdfjs-dist": "^2.12.313", - "pdfmake": "^0.2.4", - "semver": "^7.3.5", - "traverse": "^0.6.6", - "ts-morph": "^13.0.3", - "uuid": "^8.3.2" - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.0" - } - }, - "@angular-devkit/core": { - "version": "13.2.5", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.2.5.tgz", - "integrity": "sha512-WuWp/1R0FtCHPBcJLF13lTLHETtDGFUX0ULfGPRaYB5OVCSQcovVp5UbZTTy/Ss3ub3EOEmJlU8kMJfBrWuq+A==", - "dev": true, "requires": { "ajv": "8.9.0", "ajv-formats": "2.1.1", @@ -33377,226 +33106,10 @@ } } }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", - "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", - "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8" - } - }, - "@babel/preset-env": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", - "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-async-generator-functions": "^7.16.8", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-class-static-block": "^7.16.7", - "@babel/plugin-proposal-dynamic-import": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.16.7", - "@babel/plugin-proposal-json-strings": "^7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.16.7", - "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-private-methods": "^7.16.11", - "@babel/plugin-proposal-private-property-in-object": "^7.16.7", - "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.16.7", - "@babel/plugin-transform-async-to-generator": "^7.16.8", - "@babel/plugin-transform-block-scoped-functions": "^7.16.7", - "@babel/plugin-transform-block-scoping": "^7.16.7", - "@babel/plugin-transform-classes": "^7.16.7", - "@babel/plugin-transform-computed-properties": "^7.16.7", - "@babel/plugin-transform-destructuring": "^7.16.7", - "@babel/plugin-transform-dotall-regex": "^7.16.7", - "@babel/plugin-transform-duplicate-keys": "^7.16.7", - "@babel/plugin-transform-exponentiation-operator": "^7.16.7", - "@babel/plugin-transform-for-of": "^7.16.7", - "@babel/plugin-transform-function-name": "^7.16.7", - "@babel/plugin-transform-literals": "^7.16.7", - "@babel/plugin-transform-member-expression-literals": "^7.16.7", - "@babel/plugin-transform-modules-amd": "^7.16.7", - "@babel/plugin-transform-modules-commonjs": "^7.16.8", - "@babel/plugin-transform-modules-systemjs": "^7.16.7", - "@babel/plugin-transform-modules-umd": "^7.16.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", - "@babel/plugin-transform-new-target": "^7.16.7", - "@babel/plugin-transform-object-super": "^7.16.7", - "@babel/plugin-transform-parameters": "^7.16.7", - "@babel/plugin-transform-property-literals": "^7.16.7", - "@babel/plugin-transform-regenerator": "^7.16.7", - "@babel/plugin-transform-reserved-words": "^7.16.7", - "@babel/plugin-transform-shorthand-properties": "^7.16.7", - "@babel/plugin-transform-spread": "^7.16.7", - "@babel/plugin-transform-sticky-regex": "^7.16.7", - "@babel/plugin-transform-template-literals": "^7.16.7", - "@babel/plugin-transform-typeof-symbol": "^7.16.7", - "@babel/plugin-transform-unicode-escapes": "^7.16.7", - "@babel/plugin-transform-unicode-regex": "^7.16.7", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.16.8", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.5.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", - "core-js-compat": "^3.20.2", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "ajv": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", - "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "requires": { - "ajv": "^8.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.3.1", - "semver": "^6.1.1" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", - "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.1", - "core-js-compat": "^3.21.0" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "commander": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.0.0.tgz", - "integrity": "sha512-JJfP2saEKbQqvW+FI93OYUB4ByV5cizMpFMiiJI8xDbBvQvSkIk0VvQdn1CZ8mqAO8Loq2h0gYTYtDFUZUeERw==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { @@ -33960,21 +33473,17 @@ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "color-support": "^1.1.3" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "color-name": "~1.1.4" } }, "color-name": { @@ -47913,9 +47422,9 @@ "dev": true }, "ng-mocks": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/ng-mocks/-/ng-mocks-13.3.0.tgz", - "integrity": "sha512-MtHoa4dL0OTrGwwbT7d6BbSOI71pY8KTftf/VbDPbK/XalPj/429UcbDsZuaSRV+tgMFHQ6LlhJMUCD0/i4f/g==", + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/ng-mocks/-/ng-mocks-13.4.0.tgz", + "integrity": "sha512-l4Iwg4a6CQ8w8Q0KzZ/FJVju8SqIW91GydoInR7PeDBhzO/cGAwGeHxtLPvXFKVdltgg3LzIzl88hpP+V66Gmg==", "dev": true, "requires": {} }, @@ -53623,9 +53132,9 @@ "dev": true }, "typescript-tslint-plugin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typescript-tslint-plugin/-/typescript-tslint-plugin-1.0.1.tgz", - "integrity": "sha512-akj+J1FDoEv8y5uSs6f6SfkY3ywnhnTifDvOmwejSs4U+a18p73YoetrllKCDf14iq0VMNwIgCxNY/v88K6QYg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typescript-tslint-plugin/-/typescript-tslint-plugin-1.0.2.tgz", + "integrity": "sha512-M4IhSOCG/2snlD1nTRszQNDuV5ITe/GjHethOyC3Ugq9RFThyx4zjn8Mg/ij5ng/puoYmq1jnW90W1KWxonhxw==", "dev": true, "requires": { "minimatch": "^3.0.4", diff --git a/package.json b/package.json index 209807284..8f14438d0 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "jest-junit": "^13.0.0", "jest-preset-angular": "^8.4.0", "lodash": "^4.17.21", - "ng-mocks": "^13.3.0", + "ng-mocks": "^13.4.0", "ng-packagr": "^12.2.5", "prettier": "^2.2.1", "pretty-quick": "^3.1.3", @@ -116,7 +116,7 @@ "tslint": "~6.1.3", "tslint-config-prettier": "^1.18.0", "typescript": "~4.3.5", - "typescript-tslint-plugin": "^1.0.1" + "typescript-tslint-plugin": "^1.0.2" }, "config": { "commitizen": { diff --git a/projects/common/src/constants/application-constants.ts b/projects/common/src/constants/application-constants.ts index aa81c82c3..88baa2baf 100644 --- a/projects/common/src/constants/application-constants.ts +++ b/projects/common/src/constants/application-constants.ts @@ -1,7 +1,3 @@ -import { InjectionToken } from '@angular/core'; - -export const GLOBAL_HEADER_HEIGHT = new InjectionToken('Global Header Height'); - export const enum ApplicationFeature { PageTimeRange = 'ui.page-time-range' } diff --git a/projects/common/src/global-header-height/global-header-height-provider.service.ts b/projects/common/src/global-header-height/global-header-height-provider.service.ts new file mode 100644 index 000000000..cc21de454 --- /dev/null +++ b/projects/common/src/global-header-height/global-header-height-provider.service.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class GlobalHeaderHeightProviderService { + protected headerHeight: string = '56px'; + + public get globalHeaderHeight(): string { + return this.headerHeight; + } +} diff --git a/projects/common/src/navigation/ht-route-data.ts b/projects/common/src/navigation/ht-route-data.ts index 72af980d3..5365c6569 100644 --- a/projects/common/src/navigation/ht-route-data.ts +++ b/projects/common/src/navigation/ht-route-data.ts @@ -7,4 +7,5 @@ export interface HtRouteData { features?: string[]; title?: string; defaultTimeRange?: TimeRange; + shouldSavePageTimeRange?: boolean; } diff --git a/projects/common/src/navigation/navigation.service.ts b/projects/common/src/navigation/navigation.service.ts index 11a4cf84e..a8f1f5508 100644 --- a/projects/common/src/navigation/navigation.service.ts +++ b/projects/common/src/navigation/navigation.service.ts @@ -128,7 +128,7 @@ export class NavigationService { return { path: params.path, extras: { - queryParams: params?.queryParams ?? this.buildQueryParam(), + queryParams: { ...this.buildQueryParam(), ...(params?.queryParams ?? {}) }, queryParamsHandling: params?.queryParamsHandling, replaceUrl: params?.replaceCurrentHistory, relativeTo: params?.relativeTo diff --git a/projects/common/src/public-api.ts b/projects/common/src/public-api.ts index 54a6e1260..8de3d2296 100644 --- a/projects/common/src/public-api.ts +++ b/projects/common/src/public-api.ts @@ -128,3 +128,6 @@ export * from './utilities/validators'; // Color Palette export * from './color/color-palette'; + +// Global header height provider +export * from './global-header-height/global-header-height-provider.service'; diff --git a/projects/common/src/time/page-time-range-preference-service.test.ts b/projects/common/src/time/page-time-range-preference-service.test.ts index d9f4f4b17..55ca18ae1 100644 --- a/projects/common/src/time/page-time-range-preference-service.test.ts +++ b/projects/common/src/time/page-time-range-preference-service.test.ts @@ -21,9 +21,7 @@ describe('Page time range preference service', () => { service: PageTimeRangePreferenceService, providers: [ mockProvider(NavigationService, { - getCurrentActivatedRoute: jest - .fn() - .mockReturnValue({ snapshot: { data: { defaultTimeRange: defaultPageTimeRange } } }) + getRouteConfig: jest.fn().mockReturnValue({ data: { defaultTimeRange: defaultPageTimeRange } }) }), mockProvider(FeatureStateResolver, { getFeatureState: jest.fn().mockReturnValue(of(FeatureState.Enabled)) diff --git a/projects/common/src/time/page-time-range-preference.service.ts b/projects/common/src/time/page-time-range-preference.service.ts index 9978b4f42..a30aae325 100644 --- a/projects/common/src/time/page-time-range-preference.service.ts +++ b/projects/common/src/time/page-time-range-preference.service.ts @@ -1,5 +1,4 @@ import { Injectable } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; import { isNil } from 'lodash-es'; import { combineLatest, Observable } from 'rxjs'; import { map, shareReplay, take } from 'rxjs/operators'; @@ -38,7 +37,7 @@ export class PageTimeRangePreferenceService { map(([pageTimeRangeStringDictionary, featureState]) => { if (featureState === FeatureState.Enabled) { if (isNil(pageTimeRangeStringDictionary[rootLevelPath])) { - return () => this.getDefaultTimeRangeForCurrentRoute(); + return () => this.getDefaultTimeRangeForPath(rootLevelPath); } return () => this.timeRangeService.timeRangeFromUrlString(pageTimeRangeStringDictionary[rootLevelPath]); @@ -78,11 +77,14 @@ export class PageTimeRangePreferenceService { .pipe(shareReplay(1)); } - public getDefaultTimeRangeForCurrentRoute(): TimeRange { - const currentRoute: ActivatedRoute = this.navigationService.getCurrentActivatedRoute(); - // Right side for when FF is enabled but 'defaultTimeRange' is not set on AR data + public getDefaultTimeRangeForPath(rootLevelPath: string): TimeRange { + const routeConfigForPath = this.navigationService.getRouteConfig( + [rootLevelPath], + this.navigationService.rootRoute() + ); - return currentRoute.snapshot.data?.defaultTimeRange ?? this.getGlobalDefaultTimeRange(); + // Right side for when FF is enabled but 'defaultTimeRange' is not set on AR data + return routeConfigForPath?.data?.defaultTimeRange ?? this.getGlobalDefaultTimeRange(); } public getGlobalDefaultTimeRange(): TimeRange { diff --git a/projects/common/src/time/time-range.service.test.ts b/projects/common/src/time/time-range.service.test.ts index f6b37c48e..dde30abaa 100644 --- a/projects/common/src/time/time-range.service.test.ts +++ b/projects/common/src/time/time-range.service.test.ts @@ -1,13 +1,13 @@ import { ActivatedRoute, convertToParamMap } from '@angular/router'; -import { runFakeRxjs } from '@hypertrace/test-utils'; +import { recordObservable, runFakeRxjs } from '@hypertrace/test-utils'; import { createServiceFactory, mockProvider } from '@ngneat/spectator/jest'; -import { NEVER, Observable, of } from 'rxjs'; +import { NEVER, Observable, of, Subject } from 'rxjs'; import { map } from 'rxjs/operators'; -import { NavigationService } from '../navigation/navigation.service'; +import { NavigationService, QueryParamObject } from '../navigation/navigation.service'; import { FixedTimeRange } from './fixed-time-range'; import { TimeRangeService } from './time-range.service'; -describe('Time range service', () => { +describe('Time range(TR) service', () => { let timeRange$: Observable = NEVER; const buildService = createServiceFactory({ service: TimeRangeService, @@ -18,12 +18,19 @@ describe('Time range service', () => { map( initialTrString => // tslint:disable-next-line: no-object-literal-type-assertion - ({ - queryParamMap: of(convertToParamMap({ time: initialTrString })) - } as ActivatedRoute) + (({ + queryParamMap: of(convertToParamMap({ time: initialTrString, refresh: 'true' })), + snapshot: { queryParamMap: convertToParamMap({ time: initialTrString, refresh: 'true' }) } + } as unknown) as ActivatedRoute) ) ); - } + }, + getQueryParameter: jest + .fn() + .mockReturnValueOnce('1573255100253-1573255111159') + .mockReturnValue('1573255111159-1573455111990'), + getCurrentActivatedRoute: () => + (({ snapshot: { queryParams: { time: 'test-value' } } } as unknown) as ActivatedRoute) }) ] }); @@ -34,57 +41,120 @@ describe('Time range service', () => { }); test('returns time range when requested after init', () => { - timeRange$ = of('1573255100253-1573255111159'); - const spectator = buildService(); - expect(spectator.service.getCurrentTimeRange()).toEqual( - new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)) - ); - }); - - test('returns observable that emits future time range changes including initialization', () => { - const lateArrivingTimeRange = new FixedTimeRange(new Date(1573255111159), new Date(1573255111160)); runFakeRxjs(({ cold, expectObservable }) => { - timeRange$ = cold('1ms x', { + timeRange$ = cold('x|', { x: '1573255100253-1573255111159' }); const spectator = buildService(); + expect(() => spectator.service.getCurrentTimeRange()).toThrow(); - cold('5ms x').subscribe(() => - spectator.service.setFixedRange(lateArrivingTimeRange.startTime, lateArrivingTimeRange.endTime) - ); + expectObservable(spectator.service.getTimeRangeAndChanges()).toBe('x', { + x: new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)) + }); + }); + }); - expectObservable(spectator.service.getTimeRangeAndChanges()).toBe('1ms x 3ms y', { + test('returns observable that emits future time range changes including initialization', () => { + const firstArrivingTimeRange = new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)); + const secondArrivingTimeRange = new FixedTimeRange(new Date(1573255111159), new Date(1573455111990)); + + runFakeRxjs(({ cold, expectObservable }) => { + const spectator = buildService({ + providers: [ + mockProvider(NavigationService, { + navigation$: cold('-x---y', { + x: ({ + queryParamMap: of(convertToParamMap({ time: firstArrivingTimeRange.toUrlString(), refresh: 'true' })), + snapshot: { + queryParamMap: convertToParamMap({ time: firstArrivingTimeRange.toUrlString(), refresh: 'true' }) + } + } as unknown) as ActivatedRoute, + y: ({ + queryParamMap: of(convertToParamMap({ time: secondArrivingTimeRange.toUrlString(), refresh: 'true' })), + snapshot: { + queryParamMap: convertToParamMap({ time: secondArrivingTimeRange.toUrlString(), refresh: 'true' }) + } + } as unknown) as ActivatedRoute + }), + getQueryParameter: jest + .fn() + .mockReturnValueOnce(firstArrivingTimeRange.toUrlString()) + .mockReturnValue(secondArrivingTimeRange.toUrlString()), + getCurrentActivatedRoute: () => + (({ snapshot: { queryParams: { time: 'test-value' } } } as unknown) as ActivatedRoute) + }) + ] + }); + + const recordedTimeRanges = recordObservable(spectator.service.getTimeRangeAndChanges()); + + expect(() => spectator.service.getCurrentTimeRange()).toThrow(); + + expectObservable(recordedTimeRanges).toBe('-x----y', { x: new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)), - y: lateArrivingTimeRange + y: secondArrivingTimeRange }); }); }); - test('returns observable that emits current time range and later changes', () => { - const lateArrivingTimeRange = new FixedTimeRange(new Date(1573255111159), new Date(1573255111160)); - runFakeRxjs(({ cold, expectObservable }) => { - timeRange$ = of('1573255100253-1573255111159'); + test('Emits default TR when set, then subsequent first and second TRs from query param changes', () => { + const defaultTimeRange = new FixedTimeRange(new Date(1573277100277), new Date(1573277100277)); + const firstArrivingTimeRange = new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)); + const secondArrivingTimeRange = new FixedTimeRange(new Date(1573255111159), new Date(1573455111990)); + const mockNavigation$ = new Subject(); + runFakeRxjs(({ expectObservable, cold }) => { + const spectator = buildService({ + providers: [ + mockProvider(NavigationService, { + navigation$: mockNavigation$.asObservable().pipe( + map( + timeRangeString => + (({ + queryParamMap: of(convertToParamMap({ time: timeRangeString, refresh: 'true' })), + snapshot: { + queryParamMap: convertToParamMap({ time: timeRangeString, refresh: 'true' }) + } + } as unknown) as ActivatedRoute) + ) + ), + addQueryParametersToUrl: (newParams: QueryParamObject) => mockNavigation$.next(newParams.time as string), + getQueryParameter: jest + .fn() + .mockReturnValueOnce('1573277100277-1573277100277') + .mockReturnValueOnce('1573255100253-1573255111159') + .mockReturnValue('1573255111159-1573455111990'), + getCurrentActivatedRoute: () => + (({ snapshot: { queryParams: { time: 'test-value' } } } as unknown) as ActivatedRoute), + replaceQueryParametersInUrl: jest.fn() + }) + ] + }); - const spectator = buildService(); - expect(spectator.service.getCurrentTimeRange()).toEqual( - new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)) + cold('x').subscribe(() => spectator.service.setDefaultTimeRange(defaultTimeRange)); + + cold('2ms y').subscribe(() => + spectator.service.setFixedRange(firstArrivingTimeRange.startTime, firstArrivingTimeRange.endTime) ); - cold('5ms x').subscribe(() => - spectator.service.setFixedRange(lateArrivingTimeRange.startTime, lateArrivingTimeRange.endTime) + + cold('5ms z').subscribe(() => + spectator.service.setFixedRange(secondArrivingTimeRange.startTime, secondArrivingTimeRange.endTime) ); - expectObservable(spectator.service.getTimeRangeAndChanges()).toBe('x 4ms y', { - x: new FixedTimeRange(new Date(1573255100253), new Date(1573255111159)), - y: lateArrivingTimeRange + expectObservable(spectator.service.getTimeRangeAndChanges()).toBe('x 1ms y 2ms z', { + x: defaultTimeRange, + y: firstArrivingTimeRange, + z: secondArrivingTimeRange }); }); }); test('returns custom time filter', () => { const spectator = buildService(); - expect(spectator.service.toQueryParams(new Date(1642296703000), new Date(1642396703000))).toStrictEqual({ + expect( + spectator.service.toQueryParams(new FixedTimeRange(new Date(1642296703000), new Date(1642396703000))) + ).toStrictEqual({ ['time']: new FixedTimeRange(new Date(1642296703000), new Date(1642396703000)).toUrlString() }); }); diff --git a/projects/common/src/time/time-range.service.ts b/projects/common/src/time/time-range.service.ts index 95195734b..ead04524c 100644 --- a/projects/common/src/time/time-range.service.ts +++ b/projects/common/src/time/time-range.service.ts @@ -1,7 +1,8 @@ import { Injectable } from '@angular/core'; -import { isEmpty, isNil } from 'lodash-es'; -import { EMPTY, ReplaySubject } from 'rxjs'; -import { catchError, filter, map, switchMap, take } from 'rxjs/operators'; +import { ParamMap } from '@angular/router'; +import { isEmpty, isNil, omit } from 'lodash-es'; +import { concat, EMPTY, Observable, ReplaySubject } from 'rxjs'; +import { catchError, distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators'; import { NavigationService, QueryParamObject } from '../navigation/navigation.service'; import { ReplayObservable } from '../utilities/rxjs/rxjs-utils'; import { FixedTimeRange } from './fixed-time-range'; @@ -16,6 +17,7 @@ import { TimeUnit } from './time-unit.type'; }) export class TimeRangeService { private static readonly TIME_RANGE_QUERY_PARAM: string = 'time'; + private static readonly REFRESH_ON_NAVIGATION: string = 'refresh'; private readonly timeRangeSubject$: ReplaySubject = new ReplaySubject(1); private currentTimeRange?: TimeRange; @@ -51,31 +53,65 @@ export class TimeRangeService { } public setRelativeRange(value: number, unit: TimeUnit): this { - return this.setTimeRange(TimeRangeService.toRelativeTimeRange(value, unit)); + return this.setTimeRangeInUrl(TimeRangeService.toRelativeTimeRange(value, unit)); } public setFixedRange(startTime: Date, endTime: Date): this { - return this.setTimeRange(TimeRangeService.toFixedTimeRange(startTime, endTime)); + return this.setTimeRangeInUrl(TimeRangeService.toFixedTimeRange(startTime, endTime)); } public refresh(): void { - this.setTimeRange(this.getCurrentTimeRange()); + const currentStringTimeRange = this.getCurrentTimeRange().toUrlString(); + this.applyTimeRangeChange(this.timeRangeFromUrlString(currentStringTimeRange)); + } + + private getInitialTimeRange(): Observable { + return this.navigationService.navigation$.pipe( + take(1), // Wait for first navigation + switchMap(activatedRoute => activatedRoute.queryParamMap), // Get the params from it + take(1), // Only the first set of params + map(paramMap => paramMap.get(TimeRangeService.TIME_RANGE_QUERY_PARAM)), // Extract the time range value from it + filter((timeRangeString): timeRangeString is string => !isEmpty(timeRangeString)), // Only valid time ranges + map(timeRangeString => this.timeRangeFromUrlString(timeRangeString)), + catchError(() => EMPTY) + ); + } + + private getPageTimeRangeChanges(): Observable { + return this.navigationService.navigation$.pipe( + map(activeRoute => activeRoute.snapshot.queryParamMap), + filter(queryParamMap => !isNil(queryParamMap.get(TimeRangeService.TIME_RANGE_QUERY_PARAM))), + distinctUntilChanged((prevParamMap, currParamMap) => this.shouldNotUpdateTimeRange(prevParamMap, currParamMap)), + map(currQueryParamMap => { + const timeRangeQueryParamString = currQueryParamMap.get(TimeRangeService.TIME_RANGE_QUERY_PARAM); + + return this.timeRangeFromUrlString(timeRangeQueryParamString!); + }) + ); } private initializeTimeRange(): void { - this.navigationService.navigation$ - .pipe( - take(1), // Wait for first navigation - switchMap(activatedRoute => activatedRoute.queryParamMap), // Get the params from it - take(1), // Only the first set of params - map(paramMap => paramMap.get(TimeRangeService.TIME_RANGE_QUERY_PARAM)), // Extract the time range value from it - filter((timeRangeString): timeRangeString is string => !isEmpty(timeRangeString)), // Only valid time ranges - map(timeRangeString => this.timeRangeFromUrlString(timeRangeString)), - catchError(() => EMPTY) - ) - .subscribe(timeRange => { - this.setTimeRange(timeRange); - }); + concat(this.getInitialTimeRange(), this.getPageTimeRangeChanges()).subscribe(timeRange => { + if (!this.timeRangeMatchesCurrentUrl(timeRange)) { + this.setTimeRangeInUrl(timeRange); + } else { + this.applyTimeRangeChange(timeRange); + + const queryParams = this.navigationService.getCurrentActivatedRoute().snapshot.queryParams; + if (TimeRangeService.REFRESH_ON_NAVIGATION in queryParams) { + this.navigationService.replaceQueryParametersInUrl(omit(queryParams, TimeRangeService.REFRESH_ON_NAVIGATION)); + } + } + }); + } + + private shouldNotUpdateTimeRange(prevParamMap: ParamMap, currParamMap: ParamMap): boolean { + const refreshQueryParam = currParamMap.get(TimeRangeService.REFRESH_ON_NAVIGATION); + + return ( + prevParamMap.get(TimeRangeService.TIME_RANGE_QUERY_PARAM) === + currParamMap.get(TimeRangeService.TIME_RANGE_QUERY_PARAM) && isEmpty(refreshQueryParam) + ); } public timeRangeFromUrlString(timeRangeFromUrl: string): TimeRange { @@ -91,11 +127,16 @@ export class TimeRangeService { throw new Error(); // Caught in observable } - private setTimeRange(newTimeRange: TimeRange): this { + private applyTimeRangeChange(newTimeRange: TimeRange): this { this.currentTimeRange = newTimeRange; this.timeRangeSubject$.next(newTimeRange); + + return this; + } + + private setTimeRangeInUrl(timeRange: TimeRange): this { this.navigationService.addQueryParametersToUrl({ - [TimeRangeService.TIME_RANGE_QUERY_PARAM]: newTimeRange.toUrlString() + [TimeRangeService.TIME_RANGE_QUERY_PARAM]: timeRange.toUrlString() }); return this; @@ -103,7 +144,7 @@ export class TimeRangeService { public setDefaultTimeRange(timeRange: TimeRange): void { if (!this.currentTimeRange) { - this.setTimeRange(timeRange); + this.setTimeRangeInUrl(timeRange); } } @@ -115,12 +156,22 @@ export class TimeRangeService { return new FixedTimeRange(startTime, endTime); } - public toQueryParams(startTime: Date, endTime: Date): QueryParamObject { - const newTimeRange = new FixedTimeRange(startTime, endTime); - - return { - [TimeRangeService.TIME_RANGE_QUERY_PARAM]: newTimeRange.toUrlString() + public toQueryParams(timeRange: TimeRange, refreshTimeOnNavigationParam?: boolean): QueryParamObject { + const queryParams: QueryParamObject = { + [TimeRangeService.TIME_RANGE_QUERY_PARAM]: timeRange.toUrlString() }; + + if (refreshTimeOnNavigationParam) { + return { ...queryParams, [TimeRangeService.REFRESH_ON_NAVIGATION]: true }; + } + + return queryParams; + } + + private timeRangeMatchesCurrentUrl(timeRange: TimeRange): boolean { + return ( + this.navigationService.getQueryParameter(TimeRangeService.TIME_RANGE_QUERY_PARAM, '') === timeRange.toUrlString() + ); } public isInitialized(): boolean { diff --git a/projects/components/src/header/application/application-header.component.ts b/projects/components/src/header/application/application-header.component.ts index e6fbef9b5..d5a136c1f 100644 --- a/projects/components/src/header/application/application-header.component.ts +++ b/projects/components/src/header/application/application-header.component.ts @@ -1,9 +1,9 @@ -import { ChangeDetectionStrategy, Component, Inject, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { ApplicationFeature, FeatureState, FeatureStateResolver, - GLOBAL_HEADER_HEIGHT, + GlobalHeaderHeightProviderService, NavigationService } from '@hypertrace/common'; import { Observable } from 'rxjs'; @@ -14,7 +14,7 @@ import { map } from 'rxjs/operators'; changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ['./application-header.component.scss'], template: ` -
+
@@ -101,17 +99,6 @@ export class NavigationComponent { this.isCollapsed$ = this.preferenceService.get(NavigationComponent.COLLAPSED_PREFERENCE, false); } - public setPageTimeRangeForSelectedNavItem(navItemLink: NavItemLinkConfig): void { - if (!isNil(navItemLink.timeRangeResolver) && navItemLink.pageLevelTimeRangeIsEnabled) { - const timeRange = navItemLink.timeRangeResolver(); - if (timeRange instanceof FixedTimeRange) { - this.timeRangeService.setFixedRange(timeRange.startTime, timeRange.endTime); - } else if (timeRange instanceof RelativeTimeRange) { - this.timeRangeService.setRelativeRange(timeRange.duration.value, timeRange.duration.unit); - } - } - } - public updateDefaultTimeRangeIfUnset(activeItem: NavItemLinkConfig): void { // Initialize the time range service // Depending on FF status, the TR will be either global or page level for the init