diff --git a/shell-ui/package-lock.json b/shell-ui/package-lock.json index c6f654a267..e8e53123ff 100644 --- a/shell-ui/package-lock.json +++ b/shell-ui/package-lock.json @@ -8,7 +8,7 @@ "name": "shell-ui", "version": "1.0.0", "dependencies": { - "@scality/core-ui": "0.167.0", + "@scality/core-ui": "0.171.0", "@scality/module-federation": "^1.4.1", "downshift": "^8.0.0", "history": "^5.3.0", @@ -2106,42 +2106,46 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" }, "node_modules/@floating-ui/core": { - "version": "1.6.9", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", - "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.9" + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.6.13", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", - "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.9" + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/react": { - "version": "0.26.28", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", - "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "version": "0.27.16", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.16.tgz", + "integrity": "sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==", + "license": "MIT", "dependencies": { - "@floating-ui/react-dom": "^2.1.2", - "@floating-ui/utils": "^0.2.8", + "@floating-ui/react-dom": "^2.1.6", + "@floating-ui/utils": "^0.2.10", "tabbable": "^6.0.0" }, "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "react": ">=17.0.0", + "react-dom": ">=17.0.0" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", - "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.0.0" + "@floating-ui/dom": "^1.7.4" }, "peerDependencies": { "react": ">=16.8.0", @@ -2149,9 +2153,10 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==" + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" }, "node_modules/@formatjs/ecma402-abstract": { "version": "1.11.4", @@ -4203,13 +4208,12 @@ } }, "node_modules/@scality/core-ui": { - "version": "0.167.0", - "resolved": "https://registry.npmjs.org/@scality/core-ui/-/core-ui-0.167.0.tgz", - "integrity": "sha512-kbRkp68yQaZgQEE2Y5AyJayWzoSWfu8A4WIcteBeXaKrGjHYoIyZs079azp7SHM1f8C921O7M4uoQrAeuaTDPQ==", + "version": "0.171.0", + "resolved": "git+ssh://git@github.com/scality/core-ui.git#0215c8ce0cd1a7de73ad2d78b44b020a8fbaf617", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@floating-ui/dom": "^1.6.3", - "@floating-ui/react": "^0.26.28", + "@floating-ui/react": "^0.27.15", "@fortawesome/fontawesome-free": "^5.10.2", "@fortawesome/fontawesome-svg-core": "^1.2.35", "@fortawesome/free-regular-svg-icons": "^5.15.3", @@ -4221,7 +4225,7 @@ "polished": "3.4.1", "pretty-bytes": "^5.6.0", "react-dropzone": "^14.2.3", - "react-hook-form": "^7.49.2", + "react-hook-form": "^7.62.0", "react-query": "^3.34.0", "react-router": "7.8.1", "react-router-dom": "7.8.1", @@ -4233,6 +4237,7 @@ "recharts": "^3.0.2", "styled-components": "^5.2.1", "styled-system": "^5.1.5", + "uuid": "^13.0.0", "vega": "^5.17.3", "vega-embed": "6.0.0", "vega-lite": "5.0.0", @@ -4268,6 +4273,19 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/@scality/core-ui/node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/@scality/module-federation": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@scality/module-federation/-/module-federation-1.4.1.tgz", @@ -14722,9 +14740,10 @@ } }, "node_modules/react-hook-form": { - "version": "7.53.2", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.2.tgz", - "integrity": "sha512-YVel6fW5sOeedd1524pltpHX+jgU2u3DSDtXEaBORNdqiNrsX/nUI/iGXONegttg0mJVnfrIkiV0cmTU6Oo2xw==", + "version": "7.62.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz", + "integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==", + "license": "MIT", "engines": { "node": ">=18.0.0" }, @@ -16760,7 +16779,8 @@ "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" }, "node_modules/tapable": { "version": "2.2.1", diff --git a/shell-ui/package.json b/shell-ui/package.json index 1ea8114473..e7e3e3d5e8 100644 --- a/shell-ui/package.json +++ b/shell-ui/package.json @@ -45,7 +45,7 @@ "ts-node": "^10.9.2" }, "dependencies": { - "@scality/core-ui": "0.167.0", + "@scality/core-ui": "0.171.0", "@scality/module-federation": "^1.4.1", "downshift": "^8.0.0", "history": "^5.3.0", diff --git a/ui/package-lock.json b/ui/package-lock.json index 1319359840..c9868513db 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -12,7 +12,7 @@ "@hookform/resolvers": "^3.1.0", "@js-temporal/polyfill": "^0.4.4", "@kubernetes/client-node": "github:scality/kubernetes-client-javascript.git#browser-0.10.4-64-ge7c6721", - "@scality/core-ui": "0.167.0", + "@scality/core-ui": "0.171.0", "@scality/module-federation": "^1.4.1", "axios": "^0.21.1", "formik": "2.2.5", @@ -2563,42 +2563,46 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.6.9", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", - "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.9" + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.6.13", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", - "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.9" + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/react": { - "version": "0.26.28", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", - "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "version": "0.27.16", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.16.tgz", + "integrity": "sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==", + "license": "MIT", "dependencies": { - "@floating-ui/react-dom": "^2.1.2", - "@floating-ui/utils": "^0.2.8", + "@floating-ui/react-dom": "^2.1.6", + "@floating-ui/utils": "^0.2.10", "tabbable": "^6.0.0" }, "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "react": ">=17.0.0", + "react-dom": ">=17.0.0" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", - "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.0.0" + "@floating-ui/dom": "^1.7.4" }, "peerDependencies": { "react": ">=16.8.0", @@ -2606,9 +2610,10 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==" + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" }, "node_modules/@formatjs/ecma402-abstract": { "version": "1.9.3", @@ -5334,13 +5339,12 @@ "dev": true }, "node_modules/@scality/core-ui": { - "version": "0.167.0", - "resolved": "https://registry.npmjs.org/@scality/core-ui/-/core-ui-0.167.0.tgz", - "integrity": "sha512-kbRkp68yQaZgQEE2Y5AyJayWzoSWfu8A4WIcteBeXaKrGjHYoIyZs079azp7SHM1f8C921O7M4uoQrAeuaTDPQ==", + "version": "0.171.0", + "resolved": "git+ssh://git@github.com/scality/core-ui.git#0215c8ce0cd1a7de73ad2d78b44b020a8fbaf617", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@floating-ui/dom": "^1.6.3", - "@floating-ui/react": "^0.26.28", + "@floating-ui/react": "^0.27.15", "@fortawesome/fontawesome-free": "^5.10.2", "@fortawesome/fontawesome-svg-core": "^1.2.35", "@fortawesome/free-regular-svg-icons": "^5.15.3", @@ -5352,7 +5356,7 @@ "polished": "3.4.1", "pretty-bytes": "^5.6.0", "react-dropzone": "^14.2.3", - "react-hook-form": "^7.49.2", + "react-hook-form": "^7.62.0", "react-query": "^3.34.0", "react-router": "7.8.1", "react-router-dom": "7.8.1", @@ -5364,6 +5368,7 @@ "recharts": "^3.0.2", "styled-components": "^5.2.1", "styled-system": "^5.1.5", + "uuid": "^13.0.0", "vega": "^5.17.3", "vega-embed": "6.0.0", "vega-lite": "5.0.0", @@ -5374,6 +5379,19 @@ "react-dom": "^18.0.0 || ^19.0.0" } }, + "node_modules/@scality/core-ui/node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/@scality/module-federation": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@scality/module-federation/-/module-federation-1.4.1.tgz", @@ -22203,19 +22221,19 @@ "license": "MIT" }, "node_modules/react-hook-form": { - "version": "7.49.3", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.49.3.tgz", - "integrity": "sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==", + "version": "7.62.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz", + "integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==", + "license": "MIT", "engines": { - "node": ">=18", - "pnpm": "8" + "node": ">=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/react-hook-form" }, "peerDependencies": { - "react": "^16.8.0 || ^17 || ^18" + "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "node_modules/react-input-autosize": { @@ -24704,7 +24722,8 @@ "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" }, "node_modules/table": { "version": "6.7.1", @@ -29088,44 +29107,44 @@ } }, "@floating-ui/core": { - "version": "1.6.9", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", - "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", "requires": { - "@floating-ui/utils": "^0.2.9" + "@floating-ui/utils": "^0.2.10" } }, "@floating-ui/dom": { - "version": "1.6.13", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", - "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "requires": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.9" + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" } }, "@floating-ui/react": { - "version": "0.26.28", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", - "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "version": "0.27.16", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.16.tgz", + "integrity": "sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==", "requires": { - "@floating-ui/react-dom": "^2.1.2", - "@floating-ui/utils": "^0.2.8", + "@floating-ui/react-dom": "^2.1.6", + "@floating-ui/utils": "^0.2.10", "tabbable": "^6.0.0" } }, "@floating-ui/react-dom": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", - "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", "requires": { - "@floating-ui/dom": "^1.0.0" + "@floating-ui/dom": "^1.7.4" } }, "@floating-ui/utils": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==" + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==" }, "@formatjs/ecma402-abstract": { "version": "1.9.3", @@ -31265,12 +31284,11 @@ "dev": true }, "@scality/core-ui": { - "version": "0.167.0", - "resolved": "https://registry.npmjs.org/@scality/core-ui/-/core-ui-0.167.0.tgz", - "integrity": "sha512-kbRkp68yQaZgQEE2Y5AyJayWzoSWfu8A4WIcteBeXaKrGjHYoIyZs079azp7SHM1f8C921O7M4uoQrAeuaTDPQ==", + "version": "git+ssh://git@github.com/scality/core-ui.git#0215c8ce0cd1a7de73ad2d78b44b020a8fbaf617", + "from": "@scality/core-ui@0.171.0", "requires": { "@floating-ui/dom": "^1.6.3", - "@floating-ui/react": "^0.26.28", + "@floating-ui/react": "^0.27.15", "@fortawesome/fontawesome-free": "^5.10.2", "@fortawesome/fontawesome-svg-core": "^1.2.35", "@fortawesome/free-regular-svg-icons": "^5.15.3", @@ -31282,7 +31300,7 @@ "polished": "3.4.1", "pretty-bytes": "^5.6.0", "react-dropzone": "^14.2.3", - "react-hook-form": "^7.49.2", + "react-hook-form": "^7.62.0", "react-query": "^3.34.0", "react-router": "7.8.1", "react-router-dom": "7.8.1", @@ -31294,10 +31312,18 @@ "recharts": "^3.0.2", "styled-components": "^5.2.1", "styled-system": "^5.1.5", + "uuid": "^13.0.0", "vega": "^5.17.3", "vega-embed": "6.0.0", "vega-lite": "5.0.0", "vega-tooltip": "0.27.0" + }, + "dependencies": { + "uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==" + } } }, "@scality/module-federation": { @@ -43742,9 +43768,9 @@ "version": "2.0.4" }, "react-hook-form": { - "version": "7.49.3", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.49.3.tgz", - "integrity": "sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==" + "version": "7.62.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz", + "integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==" }, "react-input-autosize": { "version": "3.0.0", diff --git a/ui/package.json b/ui/package.json index 87952a4e5e..b7b6e387b1 100644 --- a/ui/package.json +++ b/ui/package.json @@ -7,7 +7,7 @@ "@hookform/resolvers": "^3.1.0", "@js-temporal/polyfill": "^0.4.4", "@kubernetes/client-node": "github:scality/kubernetes-client-javascript.git#browser-0.10.4-64-ge7c6721", - "@scality/core-ui": "0.167.0", + "@scality/core-ui": "0.171.0", "@scality/module-federation": "^1.4.1", "axios": "^0.21.1", "formik": "2.2.5", @@ -133,7 +133,7 @@ }, "jest": { "transformIgnorePatterns": [ - "/node_modules/(?!(vega-lite|@scality)/)" + "/node_modules/(?!(vega-lite|@scality|uuid)/)" ], "testMatch": [ "/src/**/__tests__/**/*.[jt]s?(x)", diff --git a/ui/src/components/DashboardBandwidthChart.tsx b/ui/src/components/DashboardBandwidthChart.tsx index 532c656390..988353b136 100644 --- a/ui/src/components/DashboardBandwidthChart.tsx +++ b/ui/src/components/DashboardBandwidthChart.tsx @@ -1,19 +1,25 @@ -import React, { useEffect, useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { LineTemporalChart } from '@scality/core-ui/dist/next'; -import { UNIT_RANGE_BS } from '@scality/core-ui/dist/components/linetemporalchart/LineTemporalChart.component'; +import { Stack } from '@scality/core-ui'; import { - getMultipleSymmetricalSeries, - getNodesInterfacesString, - renderTooltipSeperationLine, -} from '../services/graphUtils'; + ChartLegend, + ChartLegendWrapper, + LineTimeSerieChart, + useMetricsTimeSpan, +} from '@scality/core-ui/dist/next'; +import { fontSize } from '@scality/core-ui/dist/style/theme'; +import { useCallback, useEffect, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { fetchNodesAction } from '../ducks/app/nodes'; import { useNodeAddressesSelector, + useNodeColors, useNodes, useShowQuantileChart, useSymetricalChartSeries, } from '../hooks'; +import { + getMultipleSymmetricalSeries, + getNodesInterfacesString, +} from '../services/graphUtils'; import { getNodesPlanesBandwidthInOutpassingThresholdQuery, getNodesPlanesBandwidthInQuantileQuery, @@ -23,8 +29,7 @@ import { getNodesPlanesBandwidthOutQuery, } from '../services/platformlibrary/metrics'; import SymmetricalQuantileChart from './SymmetricalQuantileChart'; -import { defaultRenderTooltipSerie } from '@scality/core-ui/dist/components/linetemporalchart/tooltip'; -import { useTheme } from 'styled-components'; +import { HEIGHT_SYMMETRICAL_CHART, UNIT_RANGE_BS } from '../constants'; const DashboardBandwidthChartWithoutQuantile = ({ title, @@ -33,39 +38,38 @@ const DashboardBandwidthChartWithoutQuantile = ({ title: string; plane: 'controlPlane' | 'workloadPlane'; }) => { - const theme = useTheme(); const nodes = useNodes(); const nodeAddresses = useNodeAddressesSelector(nodes); // @ts-expect-error - FIXME when you are working on it const nodeIPsInfo = useSelector((state) => state.app.nodes.IPsInfo); const devices = getNodesInterfacesString(nodeIPsInfo); - const nodesPlaneInterface = {}; + const nodesPlaneInterface = useMemo(() => { + const nodesPlaneInterface = {}; + for (const [key, value] of Object.entries(nodeIPsInfo)) { + nodesPlaneInterface[key] = + // @ts-expect-error - FIXME when you are working on it + plane === 'controlPlane' ? value.controlPlane : value.workloadPlane; + } + return nodesPlaneInterface; + }, [nodeIPsInfo, plane]); - for (const [key, value] of Object.entries(nodeIPsInfo)) { - nodesPlaneInterface[key] = - // @ts-expect-error - FIXME when you are working on it - plane === 'controlPlane' ? value.controlPlane : value.workloadPlane; - } + const { interval, duration } = useMetricsTimeSpan(); - // will be used to draw the line speration lines - // @ts-expect-error - FIXME when you are working on it - const lastNodeName = nodes?.slice(-1)[0]?.metadata?.name; const { isLoading, series, startingTimeStamp } = useSymetricalChartSeries({ getAboveQueries: (timeSpanProps) => [ - // @ts-expect-error - FIXME when you are working on it getNodesPlanesBandwidthInQuery(timeSpanProps, devices), ], getBelowQueries: (timeSpanProps) => [ - // @ts-expect-error - FIXME when you are working on it getNodesPlanesBandwidthOutQuery(timeSpanProps, devices), ], + // @ts-expect-error - FIXME when you are working on it transformPrometheusDataToSeries: useCallback( ([prometheusResultAbove], [prometheusResultBelow]) => { if (!prometheusResultAbove || !prometheusResultBelow) { return []; } - return getMultipleSymmetricalSeries( + const allSeries = getMultipleSymmetricalSeries( prometheusResultAbove, prometheusResultBelow, 'in', @@ -73,35 +77,39 @@ const DashboardBandwidthChartWithoutQuantile = ({ nodeAddresses, nodesPlaneInterface, ); + + return allSeries; }, + // eslint-disable-next-line react-hooks/exhaustive-deps [JSON.stringify(nodeAddresses), JSON.stringify(nodesPlaneInterface)], ), }); + const colorSet = useNodeColors(nodes); + return ( - { - if (serie.key === `${lastNodeName}-in`) { - return `${defaultRenderTooltipSerie( - serie, - )}${renderTooltipSeperationLine(theme.border)}`; - } else { - return defaultRenderTooltipSerie(serie); - } - }, - [lastNodeName, theme], - )} - /> + + + + + + + ); }; @@ -117,6 +125,7 @@ const DashboardBandwidthChart = ({ dispatch(fetchNodesAction()); }, [dispatch]); const { isShowQuantileChart } = useShowQuantileChart(); + return ( <> {isShowQuantileChart ? ( diff --git a/ui/src/components/DashboardChartCpuUsage.tsx b/ui/src/components/DashboardChartCpuUsage.tsx index 19cff61f02..860d1082e0 100644 --- a/ui/src/components/DashboardChartCpuUsage.tsx +++ b/ui/src/components/DashboardChartCpuUsage.tsx @@ -1,5 +1,11 @@ -import React, { useCallback } from 'react'; -import { LineTemporalChart } from '@scality/core-ui/dist/next'; +import { + LineTimeSerieChart, + useMetricsTimeSpan, + useChartId, +} from '@scality/core-ui/dist/next'; +import { useChartLegendRegistration } from '../hooks/useChartLegendRegistration'; + +import { useCallback } from 'react'; import { getMultiResourceSeriesForChart } from '../services/graphUtils'; import { useNodeAddressesSelector, @@ -13,6 +19,7 @@ import { getNodesCPUUsageQuery, } from '../services/platformlibrary/metrics'; import NonSymmetricalQuantileChart from './NonSymmetricalQuantileChart'; +import { HEIGHT_DEFAULT_CHART } from '../constants'; const DashboardChartCpuUsage = () => { const { isShowQuantileChart } = useShowQuantileChart(); @@ -20,9 +27,7 @@ const DashboardChartCpuUsage = () => { <> {isShowQuantileChart ? ( { }; const DashboardChartCpuUsageWithoutQuantils = () => { - const nodeAddresses = useNodeAddressesSelector(useNodes()); + const chartId = useChartId(); + const nodes = useNodes(); + const nodeAddresses = useNodeAddressesSelector(nodes); + + const { interval, duration } = useMetricsTimeSpan(); const { isLoading, series, startingTimeStamp } = useSingleChartSerie({ - // @ts-expect-error - FIXME when you are working on it getQuery: (timeSpanProps) => getNodesCPUUsageQuery(timeSpanProps), transformPrometheusDataToSeries: useCallback( (prometheusResult) => - getMultiResourceSeriesForChart(prometheusResult, nodeAddresses), // eslint-disable-next-line react-hooks/exhaustive-deps + getMultiResourceSeriesForChart(prometheusResult, nodeAddresses), + //Expect warning because of complex dependency + // eslint-disable-next-line react-hooks/exhaustive-deps [JSON.stringify(nodeAddresses)], ), }); + + useChartLegendRegistration({ chartId, series, isSymmetrical: false }); + return ( - ); }; diff --git a/ui/src/components/DashboardChartMemory.tsx b/ui/src/components/DashboardChartMemory.tsx index 26addeb53e..51a500bf7a 100644 --- a/ui/src/components/DashboardChartMemory.tsx +++ b/ui/src/components/DashboardChartMemory.tsx @@ -1,5 +1,10 @@ -import React, { useCallback } from 'react'; -import { LineTemporalChart } from '@scality/core-ui/dist/next'; +import { + LineTimeSerieChart, + useMetricsTimeSpan, + useChartId, +} from '@scality/core-ui/dist/next'; +import { useChartLegendRegistration } from '../hooks/useChartLegendRegistration'; +import { useCallback } from 'react'; import { useNodes, useNodeAddressesSelector, @@ -13,6 +18,7 @@ import { } from '../services/platformlibrary/metrics'; import { getMultiResourceSeriesForChart } from '../services/graphUtils'; import NonSymmetricalQuantileChart from './NonSymmetricalQuantileChart'; +import { HEIGHT_DEFAULT_CHART } from '../constants'; const DashboardChartMemory = () => { const { isShowQuantileChart } = useShowQuantileChart(); @@ -20,9 +26,7 @@ const DashboardChartMemory = () => { <> {isShowQuantileChart ? ( { }; const DashboardChartMemoryWithoutQuantiles = () => { - const nodeAddresses = useNodeAddressesSelector(useNodes()); + const chartId = useChartId(); + const nodes = useNodes(); + const nodeAddresses = useNodeAddressesSelector(nodes); + + const { interval, duration } = useMetricsTimeSpan(); const { isLoading, series, startingTimeStamp } = useSingleChartSerie({ - // @ts-expect-error - FIXME when you are working on it getQuery: (timeSpanProps) => getNodesMemoryQuery(timeSpanProps), transformPrometheusDataToSeries: useCallback( (prometheusResult) => { @@ -46,19 +53,26 @@ const DashboardChartMemoryWithoutQuantiles = () => { nodeAddresses, ); return result; - }, // eslint-disable-next-line react-hooks/exhaustive-deps + }, + //Expect warning because of complex dependency + // eslint-disable-next-line react-hooks/exhaustive-deps [JSON.stringify(nodeAddresses)], ), }); + + useChartLegendRegistration({ chartId, series, isSymmetrical: false }); + return ( - ); }; diff --git a/ui/src/components/DashboardChartSystemLoad.tsx b/ui/src/components/DashboardChartSystemLoad.tsx index 0eb025bd69..314114c455 100644 --- a/ui/src/components/DashboardChartSystemLoad.tsx +++ b/ui/src/components/DashboardChartSystemLoad.tsx @@ -1,4 +1,10 @@ -import { LineTemporalChart } from '@scality/core-ui/dist/next'; +import { + LineTimeSerieChart, + useMetricsTimeSpan, + useChartId, +} from '@scality/core-ui/dist/next'; +import { useChartLegendRegistration } from '../hooks/useChartLegendRegistration'; + import { useCallback } from 'react'; import { useNodeAddressesSelector, @@ -13,6 +19,7 @@ import { getNodesSystemLoadQuery, } from '../services/platformlibrary/metrics'; import NonSymmetricalQuantileChart from './NonSymmetricalQuantileChart'; +import { HEIGHT_DEFAULT_CHART } from '../constants'; const DashboardChartSystemLoad = () => { const { isShowQuantileChart } = useShowQuantileChart(); @@ -20,11 +27,10 @@ const DashboardChartSystemLoad = () => { <> {isShowQuantileChart ? ( ) : ( @@ -34,25 +40,34 @@ const DashboardChartSystemLoad = () => { }; const DashboardChartSystemLoadWithoutQuantiles = () => { - const nodeAddresses = useNodeAddressesSelector(useNodes()); + const chartId = useChartId(); + const nodes = useNodes(); + const nodeAddresses = useNodeAddressesSelector(nodes); + + const { interval, duration } = useMetricsTimeSpan(); const { isLoading, series, startingTimeStamp } = useSingleChartSerie({ - getQuery: (timeSpanProps) => - // @ts-expect-error - FIXME when you are working on it - getNodesSystemLoadQuery(timeSpanProps), + getQuery: (timeSpanProps) => getNodesSystemLoadQuery(timeSpanProps), transformPrometheusDataToSeries: useCallback( (prometheusResult) => - getMultiResourceSeriesForChart(prometheusResult, nodeAddresses), // eslint-disable-next-line react-hooks/exhaustive-deps + getMultiResourceSeriesForChart(prometheusResult, nodeAddresses), + //Expect warning because of complex dependency + // eslint-disable-next-line react-hooks/exhaustive-deps [JSON.stringify(nodeAddresses)], ), }); + + useChartLegendRegistration({ chartId, series, isSymmetrical: false }); + return ( - ); }; diff --git a/ui/src/components/DashboardChartThroughput.tsx b/ui/src/components/DashboardChartThroughput.tsx index de0f53401d..862e0922f4 100644 --- a/ui/src/components/DashboardChartThroughput.tsx +++ b/ui/src/components/DashboardChartThroughput.tsx @@ -1,11 +1,11 @@ -import React, { useCallback } from 'react'; -import { useTheme } from 'styled-components'; -import { defaultRenderTooltipSerie } from '@scality/core-ui/dist/components/linetemporalchart/tooltip'; -import { LineTemporalChart } from '@scality/core-ui/dist/next'; import { - UNIT_RANGE_BS, - YAXIS_TITLE_READ_WRITE, -} from '@scality/core-ui/dist/components/linetemporalchart/LineTemporalChart.component'; + LineTimeSerieChart, + useMetricsTimeSpan, + useChartId, +} from '@scality/core-ui/dist/next'; +import { useChartLegendRegistration } from '../hooks/useChartLegendRegistration'; + +import { useCallback } from 'react'; import { useNodeAddressesSelector, useNodes, @@ -22,6 +22,11 @@ import { } from '../services/platformlibrary/metrics'; import { getMultipleSymmetricalSeries } from '../services/graphUtils'; import SymmetricalQuantileChart from './SymmetricalQuantileChart'; +import { + HEIGHT_SYMMETRICAL_CHART, + UNIT_RANGE_BS, + YAXIS_TITLE_READ_WRITE, +} from '../constants'; const DashboardChartThroughput = () => { const { isShowQuantileChart } = useShowQuantileChart(); @@ -50,56 +55,61 @@ const DashboardChartThroughput = () => { }; const DashboardChartThroughputWithoutQuantile = () => { - const theme = useTheme(); + const chartId = useChartId(); const nodes = useNodes(); const nodeAddresses = useNodeAddressesSelector(nodes); - // @ts-expect-error - FIXME when you are working on it - const lastNodeName = nodes?.slice(-1)[0]?.metadata?.name; + + const { interval, duration } = useMetricsTimeSpan(); const { isLoading, series, startingTimeStamp } = useSymetricalChartSeries({ getAboveQueries: (timeSpanProps) => [ - // @ts-expect-error - FIXME when you are working on it getNodesThroughputWriteQuery(timeSpanProps), ], getBelowQueries: (timeSpanProps) => [ - // @ts-expect-error - FIXME when you are working on it getNodesThroughputReadQuery(timeSpanProps), ], transformPrometheusDataToSeries: useCallback( ([prometheusResultAbove], [prometheusResultBelow]) => { - return getMultipleSymmetricalSeries( + if (!prometheusResultAbove || !prometheusResultBelow) { + return { + above: [], + below: [], + }; + } + + const allSeries = getMultipleSymmetricalSeries( prometheusResultAbove, prometheusResultBelow, 'write', 'read', nodeAddresses, ); + + return allSeries; }, + //Expect warning because of complex dependency + // eslint-disable-next-line react-hooks/exhaustive-deps [JSON.stringify(nodeAddresses)], ), }); + + useChartLegendRegistration({ chartId, series, isSymmetrical: true }); + return ( - { - if (serie.key === `${lastNodeName}-write`) { - return `${defaultRenderTooltipSerie(serie)} -
`; - } else { - return defaultRenderTooltipSerie(serie); - } - }, - [lastNodeName, theme], - )} + syncId="dashboard" /> ); }; diff --git a/ui/src/components/DashboardMetrics.tsx b/ui/src/components/DashboardMetrics.tsx index f93632cb4f..300aeaa756 100644 --- a/ui/src/components/DashboardMetrics.tsx +++ b/ui/src/components/DashboardMetrics.tsx @@ -1,9 +1,15 @@ import React from 'react'; import styled from 'styled-components'; -import { Box, Button } from '@scality/core-ui/dist/next'; -import { padding } from '@scality/core-ui/dist/style/theme'; +import { + Box, + Button, + ChartLegend, + ChartLegendWrapper, +} from '@scality/core-ui/dist/next'; + import { useIntl } from 'react-intl'; import { GRAFANA_DASHBOARDS } from '../constants'; +import { createColorSet } from '../services/graphUtils'; import { PageSubtitle, GraphsWrapper, @@ -16,7 +22,7 @@ import { useShowQuantileChart, useTypedSelector } from '../hooks'; import { DashboardScrollableArea } from '../containers/DashboardPage'; import { Icon, SmallerText, Stack, IconHelp, spacing } from '@scality/core-ui'; const MetricsContainer = styled.div` - padding: 2px ${padding.smaller}; + padding: 2px ${spacing.f4}; display: flex; flex-direction: column; flex-grow: 1; @@ -24,7 +30,7 @@ const MetricsContainer = styled.div` `; const PanelActions = styled.div` display: flex; - padding: ${padding.small}; + padding: ${spacing.f8}; align-items: center; justify-content: space-between; `; @@ -57,6 +63,7 @@ const DashboardMetrics = () => { // App config, used to generated Advanced metrics button link const { url_grafana } = useTypedSelector((state) => state.config.api); const { isShowQuantileChart } = useShowQuantileChart(); + return ( @@ -88,10 +95,15 @@ const DashboardMetrics = () => { - - - - + + + + + + + + + diff --git a/ui/src/components/DashboardNetwork.tsx b/ui/src/components/DashboardNetwork.tsx index f4a7be7235..31d7fc832a 100644 --- a/ui/src/components/DashboardNetwork.tsx +++ b/ui/src/components/DashboardNetwork.tsx @@ -1,17 +1,14 @@ import React from 'react'; import styled from 'styled-components'; import { useIntl } from 'react-intl'; -import { - PageSubtitle, - GraphsWrapper, -} from '../components/style/CommonLayoutStyle'; +import { PageSubtitle } from '../components/style/CommonLayoutStyle'; import DashboardPlaneHealth from './DashboardPlaneHealth'; import DashboardBandwidthChart from './DashboardBandwidthChart'; import { DashboardScrollableArea } from '../containers/DashboardPage'; import { useShowQuantileChart } from '../hooks'; import { QuantileHelpTooltip } from './DashboardMetrics'; import { Box } from '@scality/core-ui/dist/next'; -import { spacing } from '@scality/core-ui'; +import { spacing, Stack } from '@scality/core-ui'; export const NetworkContainer = styled.div` padding: ${spacing.r2} ${spacing.r4}; display: flex; @@ -48,7 +45,7 @@ const DashboardNetwork = () => { - + { title="WorkloadPlane Bandwidth" plane="workloadPlane" /> - + ); diff --git a/ui/src/components/MetricChart.tsx b/ui/src/components/MetricChart.tsx index c6e62faa35..53bc5d81c3 100644 --- a/ui/src/components/MetricChart.tsx +++ b/ui/src/components/MetricChart.tsx @@ -1,10 +1,20 @@ -import { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import type { UseQueryOptions } from 'react-query'; import 'react-query'; -import { LineTemporalChart } from '@scality/core-ui/dist/next'; +import { + LineTimeSerieChart, + useChartId, + useMetricsTimeSpan, +} from '@scality/core-ui/dist/next'; import { convertPrometheusResultToSerieWithAverage } from '../services/graphUtils'; -import { HEIGHT_DEFAULT_CHART } from '../constants'; +import { + CLUSTER_AVERAGE, + HEIGHT_DEFAULT_CHART, + NODE_SYNC_ID, +} from '../constants'; import { useChartSeries } from '../hooks'; +import { TimeSpanProps } from '../services/platformlibrary/metrics'; +import { useChartLegendRegistration } from '../hooks/useChartLegendRegistration'; const MetricChart = ({ title, @@ -21,31 +31,34 @@ const MetricChart = ({ nodeName: string; instanceIP: string; showAvg: boolean; - getMetricQuery: UseQueryOptions; - getMetricAvgQuery: UseQueryOptions; + getMetricQuery: ( + instanceIP: string, + timeSpanProps: TimeSpanProps, + ) => UseQueryOptions; + getMetricAvgQuery: ( + timeSpanProps: TimeSpanProps, + showAvg: boolean, + ) => UseQueryOptions; unitRange?: { threshold: number; label: string; }[]; }) => { + const chartId = useChartId(); + const { interval, duration } = useMetricsTimeSpan(); const { isLoading, series, startingTimeStamp } = useChartSeries({ getQueries: useCallback( (timeSpanProps) => { if (showAvg) { return [ - // @ts-expect-error - FIXME when you are working on it getMetricQuery(instanceIP, timeSpanProps), - // @ts-expect-error - FIXME when you are working on it getMetricAvgQuery(timeSpanProps, showAvg), ]; } else { - return [ - // @ts-expect-error - FIXME when you are working on it - getMetricQuery(instanceIP, timeSpanProps), - ]; + return [getMetricQuery(instanceIP, timeSpanProps)]; } }, - [instanceIP, showAvg], + [instanceIP, showAvg, getMetricQuery, getMetricAvgQuery], ), transformPrometheusDataToSeries: useCallback( ([result, resultAvg]) => { @@ -62,18 +75,30 @@ const MetricChart = ({ [nodeName, showAvg], ), }); + const additionalNames = useMemo( + () => (showAvg ? [CLUSTER_AVERAGE] : []), + [showAvg], + ); + useChartLegendRegistration({ + chartId, + series, + isSymmetrical: false, + additionalNames, + }); return ( - ); }; -export default MetricChart; +export default React.memo(MetricChart); diff --git a/ui/src/components/MetricSymmetricalChart.tsx b/ui/src/components/MetricSymmetricalChart.tsx index 85bbe70d66..d8761e1bcc 100644 --- a/ui/src/components/MetricSymmetricalChart.tsx +++ b/ui/src/components/MetricSymmetricalChart.tsx @@ -1,11 +1,21 @@ -import { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import type { UseQueryOptions } from 'react-query'; import 'react-query'; -import { LineTemporalChart } from '@scality/core-ui/dist/next'; +import { + LineTimeSerieChart, + useChartId, + useMetricsTimeSpan, +} from '@scality/core-ui/dist/next'; import { getSeriesForSymmetricalChart } from '../services/graphUtils'; -import { HEIGHT_SYMMETRICAL_CHART } from '../constants'; +import { + CLUSTER_AVERAGE, + HEIGHT_SYMMETRICAL_CHART, + NODE_SYNC_ID, +} from '../constants'; import { NodesState } from '../ducks/app/nodes'; import { useSymetricalChartSeries } from '../hooks'; +import { TimeSpanProps } from '../services/platformlibrary/metrics'; +import { useChartLegendRegistration } from '../hooks/useChartLegendRegistration'; const MetricSymmetricalChart = ({ title, @@ -30,10 +40,28 @@ const MetricSymmetricalChart = ({ instanceIP: string; showAvg: boolean; nodesIPsInfo: NodesState['IPsInfo']; - getMetricAboveQuery: UseQueryOptions; - getMetricBelowQuery: UseQueryOptions; - getMetricAboveAvgQuery: UseQueryOptions; - getMetricBelowAvgQuery: UseQueryOptions; + getMetricAboveQuery: ( + instanceIP: string, + timeSpanProps: TimeSpanProps, + planeInterface: string, + ) => UseQueryOptions; + getMetricBelowQuery: ( + instanceIP: string, + timeSpanProps: TimeSpanProps, + planeInterface: string, + ) => UseQueryOptions; + getMetricAboveAvgQuery: ( + timeSpanProps: TimeSpanProps, + showAvg: boolean, + instanceIP: string, + nodesIPsInfo: NodesState['IPsInfo'], + ) => UseQueryOptions; + getMetricBelowAvgQuery: ( + timeSpanProps: TimeSpanProps, + showAvg: boolean, + instanceIP: string, + nodesIPsInfo: NodesState['IPsInfo'], + ) => UseQueryOptions; metricPrefixAbove: string; metricPrefixBelow: string; unitRange?: { @@ -43,14 +71,15 @@ const MetricSymmetricalChart = ({ planeInterface?: string; isPlaneInterfaceRequired?: boolean; }) => { + const chartId = useChartId(); + const { interval, duration } = useMetricsTimeSpan(); const { isLoading, series, startingTimeStamp } = useSymetricalChartSeries({ getAboveQueries: useCallback( (timeSpanProps) => { if (showAvg) { return [ - // @ts-expect-error - FIXME when you are working on it getMetricAboveQuery(instanceIP, timeSpanProps, planeInterface), - // @ts-expect-error - FIXME when you are working on it + getMetricAboveAvgQuery( timeSpanProps, showAvg, @@ -60,20 +89,25 @@ const MetricSymmetricalChart = ({ ]; } else { return [ - // @ts-expect-error - FIXME when you are working on it getMetricAboveQuery(instanceIP, timeSpanProps, planeInterface), ]; } }, - [instanceIP, showAvg, planeInterface, JSON.stringify(nodesIPsInfo)], + [ + instanceIP, + showAvg, + planeInterface, + nodesIPsInfo, + getMetricAboveQuery, + getMetricAboveAvgQuery, + ], ), getBelowQueries: useCallback( (timeSpanProps) => { if (showAvg) { return [ - // @ts-expect-error - FIXME when you are working on it getMetricBelowQuery(instanceIP, timeSpanProps, planeInterface), - // @ts-expect-error - FIXME when you are working on it + getMetricBelowAvgQuery( timeSpanProps, showAvg, @@ -83,19 +117,26 @@ const MetricSymmetricalChart = ({ ]; } else { return [ - // @ts-expect-error - FIXME when you are working on it getMetricBelowQuery(instanceIP, timeSpanProps, planeInterface), ]; } }, - [instanceIP, showAvg, planeInterface, JSON.stringify(nodesIPsInfo)], + [ + instanceIP, + showAvg, + planeInterface, + nodesIPsInfo, + getMetricBelowQuery, + getMetricBelowAvgQuery, + ], ), transformPrometheusDataToSeries: useCallback( (resultsAbove, resultsBelow) => { + let allSeries; if (showAvg) { const [resultAbove, resultAboveAvg] = resultsAbove; const [resultBelow, resultBelowAvg] = resultsBelow; - return getSeriesForSymmetricalChart( + allSeries = getSeriesForSymmetricalChart( resultAbove, resultBelow, nodeName, @@ -107,7 +148,7 @@ const MetricSymmetricalChart = ({ } else { const [resultAbove] = resultsAbove; const [resultBelow] = resultsBelow; - return getSeriesForSymmetricalChart( + allSeries = getSeriesForSymmetricalChart( resultAbove, resultBelow, nodeName, @@ -115,24 +156,39 @@ const MetricSymmetricalChart = ({ metricPrefixBelow, ); } + return allSeries; }, [showAvg, nodeName, metricPrefixAbove, metricPrefixBelow], ), }); + const additionalNames = useMemo( + () => (showAvg ? [CLUSTER_AVERAGE] : []), + [showAvg], + ); + useChartLegendRegistration({ + chartId, + series, + isSymmetrical: true, + additionalNames, + }); return ( - ); }; -export default MetricSymmetricalChart; +export default React.memo(MetricSymmetricalChart); diff --git a/ui/src/components/NonSymmetricalQuantileChart.tsx b/ui/src/components/NonSymmetricalQuantileChart.tsx index 255350ab1b..e47f7621f9 100644 --- a/ui/src/components/NonSymmetricalQuantileChart.tsx +++ b/ui/src/components/NonSymmetricalQuantileChart.tsx @@ -1,65 +1,87 @@ -import React, { useCallback } from 'react'; -import { LineTemporalChart } from '@scality/core-ui/dist/next'; -import { useTheme } from 'styled-components'; -import { PORT_NODE_EXPORTER } from '../constants'; +import { ChartLegendWrapper } from '@scality/core-ui/dist/components/chartlegend/ChartLegendWrapper'; import { - useChartSeries, - useNodeAddressesSelector, - useNodes, - useQuantileOnHover, -} from '../hooks'; + ChartLegend, + LineTimeSerieChart, + useMetricsTimeSpan, +} from '@scality/core-ui/dist/next'; +import { useCallback, useMemo } from 'react'; +import { UseQueryOptions } from 'react-query'; +import { useSelector } from 'react-redux'; +import { + DASHBOARD_QUANTILE_SYNC_ID, + HEIGHT_DEFAULT_CHART, + PORT_NODE_EXPORTER, +} from '../constants'; +import { useChartSeries, useNodeAddressesSelector, useNodes } from '../hooks'; import { convertPrometheusResultToSerie, - renderOutpassingThresholdTitle, - renderQuantileData, - renderTooltipSerie, + createColorSet, + getNodesInterfacesString, + getTimeFormatForInterval, } from '../services/graphUtils'; -import { useIntl } from 'react-intl'; -import { UseQueryOptions } from 'react-query'; +import { QuantileTooltip } from './QuantileTooltip'; const NonSymmetricalQuantileChart = ({ getQuantileQuery, getQuantileHoverQuery, title, yAxisType, - isLegendHidden, - // @ts-expect-error - FIXME when you are working on it helpText, + unitRange, }: { - getQuantileQuery: UseQueryOptions['queryFn']; - getQuantileHoverQuery: UseQueryOptions['queryFn']; + getQuantileQuery: (timeSpanProps: any, threshold: number) => UseQueryOptions; + getQuantileHoverQuery: ( + timeSpanProps: any, + threshold: number, + operator?: '>' | '<', + isOnHoverFetchingRequired?: boolean, + ) => UseQueryOptions; title: string; - yAxisType: string; - isLegendHidden: boolean; + yAxisType: 'default' | 'percentage'; + helpText?: string; + unitRange?: { + threshold: number; + label: string; + }[]; }) => { - const theme = useTheme(); - const intl = useIntl(); - const nodeAddresses = useNodeAddressesSelector(useNodes()); - const nodeMapPerIp = nodeAddresses.reduce( - (agg, current) => ({ - ...agg, - [current.internalIP + `:${PORT_NODE_EXPORTER}`]: current.name, - }), - {}, + const { interval, duration } = useMetricsTimeSpan(); + + const nodes = useNodes(); + const nodeAddresses = useNodeAddressesSelector(nodes); + const nodeMapPerIp = useMemo( + () => + nodeAddresses.reduce( + (agg, current) => ({ + ...agg, + [current.internalIP + `:${PORT_NODE_EXPORTER}`]: current.name, + }), + {}, + ), + [nodeAddresses], ); + + const nodeIPsInfo = useSelector((state: any) => state.app.nodes.IPsInfo); + const devices = useMemo(() => { + if (!nodeIPsInfo) { + return []; // Return empty array if no nodeIPsInfo + } + return getNodesInterfacesString(nodeIPsInfo); // Keep as array for metrics functions + }, [nodeIPsInfo]); const { isLoading: isLoadingQuantile, series: seriesQuantile, startingTimeStamp: startingTimeStampQuantile, } = useChartSeries({ getQueries: (timeSpanProps) => [ - // @ts-expect-error - FIXME when you are working on it getQuantileQuery(timeSpanProps, 0.9), - // @ts-expect-error - FIXME when you are working on it getQuantileQuery(timeSpanProps, 0.5), - // @ts-expect-error - FIXME when you are working on it getQuantileQuery(timeSpanProps, 0.05), ], transformPrometheusDataToSeries: useCallback( ([ - prometheusResultQuantile90, - prometheusResultMedian, prometheusResultQuantile5, + prometheusResultMedian, + prometheusResultQuantile90, ]) => { return [ convertPrometheusResultToSerie(prometheusResultQuantile90, 'Q90'), @@ -70,114 +92,86 @@ const NonSymmetricalQuantileChart = ({ [], ), }); - const { - onHover, - quantile5Result: { - isIdle: isIdleQuantile5, - isLoading: isLoadingQuantile5, - isSuccess: isSuccessQuantile5, - isError: isErrorQuanile5, - data: quantile5Data, - }, - quantile90Result: { - isIdle: isIdleQuantile90, - isLoading: isLoadingQuantile90, - isSuccess: isSuccessQuantile90, - isError: isErrorQuanile90, - data: quantile90Data, + + const { valueBase, unitLabel } = useMemo(() => { + if (yAxisType === 'percentage') { + return { valueBase: 1, unitLabel: '%' }; + } + + if (!unitRange || !seriesQuantile.length) { + return { valueBase: 1, unitLabel: '' }; + } + + const allValues = seriesQuantile.flatMap((serie: any) => + serie.data + .map(([_, value]: [number, any]) => + typeof value === 'string' ? parseFloat(value) : value, + ) + .filter((v: any) => v !== null && !isNaN(v)), + ); + + const maxValue = Math.max(...allValues); + const unit = unitRange + .slice() + .reverse() + .find((range) => maxValue >= range.threshold); + + return { + valueBase: unit ? unit.threshold || 1 : 1, + unitLabel: unit ? unit.label : '', + }; + }, [unitRange, seriesQuantile, yAxisType]); + + const timeFormat = useMemo(() => { + return getTimeFormatForInterval(interval); + }, [interval]); + // Create custom tooltip renderer + const renderTooltip = useCallback( + (tooltipProps: any) => { + return ( + + ); }, - isOnHoverFetchingNeeded, - } = useQuantileOnHover({ - // @ts-expect-error - FIXME when you are working on it - getQuantileHoverQuery, - }); - return ( - { - if (serie.key === 'Q90') { - return ( - renderTooltipSerie(serie) + - renderOutpassingThresholdTitle( - `Nodes above ${serie.key}`, - isOnHoverFetchingNeeded, - theme, - ) + - (isOnHoverFetchingNeeded - ? // @ts-expect-error - FIXME when you are working on it - `${renderQuantileData( - isIdleQuantile90, - isLoadingQuantile90, - isSuccessQuantile90, - isErrorQuanile90, - quantile90Data, - nodeMapPerIp, - theme, - 1, - serie.unitLabel, - )} - ` - : '') - ); - } + [ + getQuantileHoverQuery, + nodeMapPerIp, + devices, + valueBase, + unitLabel, + timeFormat, + ], + ); - if (serie.key === 'Q5') { - return ( - renderTooltipSerie(serie) + - renderOutpassingThresholdTitle( - `Nodes below ${serie.key}`, - isOnHoverFetchingNeeded, - theme, - ) + - (isOnHoverFetchingNeeded - ? `${renderQuantileData( - isIdleQuantile5, - isLoadingQuantile5, - isSuccessQuantile5, - isErrorQuanile5, - quantile5Data, - nodeMapPerIp, - theme, - 1, - serie.unitLabel, - intl, - )} - ` - : '') - ); - } + const colorSet = useMemo(() => { + return createColorSet(['Q90', 'Median', 'Q5']); + }, []); - return renderTooltipSerie(serie); - }, - [ - isIdleQuantile90, - isLoadingQuantile90, - isSuccessQuantile90, - isErrorQuanile90, - isIdleQuantile5, - isLoadingQuantile5, - isSuccessQuantile5, - isErrorQuanile5, - // @ts-expect-error - FIXME when you are working on it - JSON.stringify(quantile5Data?.data), - // @ts-expect-error - FIXME when you are working on it - JSON.stringify(quantile90Data?.data), - JSON.stringify(nodeMapPerIp), - isOnHoverFetchingNeeded, - ], - )} - /> + return ( + + + + ); }; diff --git a/ui/src/components/QuantileTooltip.tsx b/ui/src/components/QuantileTooltip.tsx new file mode 100644 index 0000000000..7fe3aa0c63 --- /dev/null +++ b/ui/src/components/QuantileTooltip.tsx @@ -0,0 +1,112 @@ +import React, { useMemo } from 'react'; +import { useQuery } from 'react-query'; +import type { TooltipContentProps } from 'recharts'; +import { + QuantileTooltipRenderer, + transformRegularPayload, + sortPayloadEntries, +} from './shared/QuantileTooltipShared'; + +type QuantileTooltipProps = { + tooltipProps: TooltipContentProps; + getQuantileHoverQuery: ( + timestamp?: string, + threshold?: number, + operator?: '>' | '<', + isOnHoverFetchingRequired?: boolean, + devices?: string | string[], + ) => any; + nodeMapPerIp: Record; + devices: string | string[]; + valueBase: number; + unitLabel: string; + timeFormat?: + | 'day-month-abbreviated-hour-minute' + | 'day-month-abbreviated-hour-minute-second' + | 'long-date-without-weekday'; +}; + +export const QuantileTooltip: React.FC = ({ + tooltipProps, + nodeMapPerIp, + devices, + valueBase, + unitLabel, + timeFormat, + getQuantileHoverQuery, +}) => { + const { active, payload, label } = tooltipProps; + + // Extract quantile values from payload + const quantileData = useMemo(() => { + if (!payload || !payload.length) return null; + return transformRegularPayload(payload, label); + }, [payload, label]); + + // Determine if additional fetching is needed + const isOnHoverFetchingNeeded = useMemo(() => { + if (!quantileData) return false; + return ( + quantileData.median !== quantileData.q90 || + quantileData.median !== quantileData.q5 + ); + }, [quantileData]); + + // Fetch quantile hover data + const quantile90Result = useQuery( + getQuantileHoverQuery( + quantileData?.timestamp?.toString(), + quantileData?.q90, + '>', + isOnHoverFetchingNeeded, + devices, + ), + { + enabled: !!quantileData && isOnHoverFetchingNeeded && !!quantileData.q90, + }, + ); + + const quantile5Result = useQuery( + getQuantileHoverQuery( + quantileData?.timestamp?.toString(), + quantileData?.q5, + '<', + isOnHoverFetchingNeeded, + devices, + ), + { + enabled: !!quantileData && isOnHoverFetchingNeeded && !!quantileData.q5, + }, + ); + + if (!active || !payload || !payload.length || !label || !quantileData) { + return null; + } + + // Sort payload using shared sorting logic (Q90-Q50-Q5) + const sortedPayload = sortPayloadEntries([...payload]); + + // Add quantile results to payload entries + const enrichedPayload = sortedPayload.map((entry) => ({ + ...entry, + quantileResult: + entry.name === 'Q90' + ? quantile90Result + : entry.name === 'Q5' + ? quantile5Result + : null, + })); + + return ( + + ); +}; diff --git a/ui/src/components/SymmetricalQuantileChart.tsx b/ui/src/components/SymmetricalQuantileChart.tsx index 394ba9ef97..638998f344 100644 --- a/ui/src/components/SymmetricalQuantileChart.tsx +++ b/ui/src/components/SymmetricalQuantileChart.tsx @@ -1,24 +1,29 @@ -import React, { useCallback } from 'react'; -import { useSelector } from 'react-redux'; +import { ChartLegendWrapper } from '@scality/core-ui/dist/components/chartlegend/ChartLegendWrapper'; import { UNIT_RANGE_BS } from '@scality/core-ui/dist/components/linetemporalchart/LineTemporalChart.component'; -import { LineTemporalChart } from '@scality/core-ui/dist/next'; -import { useTheme } from 'styled-components'; -import { PORT_NODE_EXPORTER } from '../constants'; +import { + ChartLegend, + LineTimeSerieChart, + useMetricsTimeSpan, +} from '@scality/core-ui/dist/next'; +import { useCallback, useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { + DASHBOARD_QUANTILE_SYNC_ID, + HEIGHT_SYMMETRICAL_CHART, + PORT_NODE_EXPORTER, +} from '../constants'; import { useNodeAddressesSelector, useNodes, - useQuantileOnHover, useSymetricalChartSeries, } from '../hooks'; import { + createSymmetricalQuantileColorSet, getNodesInterfacesString, getQuantileSymmetricalSeries, - renderQuantileData, - renderTooltipSerie, - renderOutpassingThresholdTitle, - renderTooltipSeperationLine, + getTimeFormatForInterval, } from '../services/graphUtils'; -import { useIntl } from 'react-intl'; +import { SymmetricalQuantileTooltip } from './SymmetricalQuantileTooltip'; const SymmetricalQuantileChart = ({ getAboveQuantileQuery, @@ -29,7 +34,6 @@ const SymmetricalQuantileChart = ({ metricPrefixBelow, title, yAxisTitle, - isLegendHidden, }: { getAboveQuantileQuery; getBelowQuantileQuery; @@ -39,20 +43,29 @@ const SymmetricalQuantileChart = ({ metricPrefixBelow: string; title: string; yAxisTitle: string; - isLegendHidden?: Boolean; }) => { - const theme = useTheme(); - const intl = useIntl(); - const nodeAddresses = useNodeAddressesSelector(useNodes()); - // @ts-expect-error - FIXME when you are working on it - const nodeIPsInfo = useSelector((state) => state.app.nodes.IPsInfo); - const devices = getNodesInterfacesString(nodeIPsInfo); - const nodeMapPerIp = nodeAddresses.reduce( - (agg, current) => ({ - ...agg, - [current.internalIP + `:${PORT_NODE_EXPORTER}`]: current.name, - }), - {}, + const { interval, duration } = useMetricsTimeSpan(); + + // Get nodeIPsInfo for devices (still needed for quantile queries) + const nodes = useNodes(); + const nodeAddresses = useNodeAddressesSelector(nodes); + const nodeIPsInfo = useSelector((state: any) => state.app.nodes.IPsInfo); + const devices = useMemo(() => { + if (!nodeIPsInfo) { + return []; // Return empty array if no nodeIPsInfo + } + return getNodesInterfacesString(nodeIPsInfo); // Keep as array for metrics functions + }, [nodeIPsInfo]); + const nodeMapPerIp = useMemo( + () => + nodeAddresses.reduce( + (agg, current) => ({ + ...agg, + [current.internalIP + `:${PORT_NODE_EXPORTER}`]: current.name, + }), + {}, + ), + [nodeAddresses], ); const { isLoading: isLoadingQuantile, @@ -69,6 +82,7 @@ const SymmetricalQuantileChart = ({ getBelowQuantileQuery(timeSpanProps, 0.5, devices), getBelowQuantileQuery(timeSpanProps, 0.9, devices), ], + // @ts-expect-error - FIXME when you are working on it transformPrometheusDataToSeries: useCallback( (prometheusResultAbove, prometheusResultBelow) => { if (!prometheusResultAbove || !prometheusResultBelow) { @@ -89,198 +103,81 @@ const SymmetricalQuantileChart = ({ [metricPrefixAbove, metricPrefixBelow], ), }); - const { - quantile5Result: { - isIdle: isIdleQuantile5, - isLoading: isLoadingQuantile5, - isSuccess: isSuccessQuantile5, - isError: isErrorQuantile5, - data: quantile5Data, - }, - quantile90Result: { - isIdle: isIdleQuantile90In, - isLoading: isLoadingQuantile90In, - isSuccess: isSuccessQuantile90In, - isError: isErrorQuantile90In, - data: quantile90InData, - }, - valueBase, - isOnHoverFetchingNeeded, - onHover: onHoverIn, - } = useQuantileOnHover({ - getQuantileHoverQuery: getAboveQuantileHoverQuery, - metricPrefix: metricPrefixAbove, - }); - const { - quantile5Result: { - isIdle: isIdleQuantile5Out, - isLoading: isLoadingQuantile5Out, - isSuccess: isSuccessQuantile5Out, - isError: isErrorQuantile5Out, - data: quantile5OutData, - }, - quantile90Result: { - isIdle: isIdleQuantile90Out, - isLoading: isLoadingQuantile90Out, - isSuccess: isSuccessQuantile90Out, - isError: isErrorQuantile90Out, - data: quantile90OutData, - }, - onHover: onHoverOut, - isOnHoverFetchingNeeded: isOnHoverFetchingNeededOut, - } = useQuantileOnHover({ - getQuantileHoverQuery: getBelowQuantileHoverQuery, - metricPrefix: metricPrefixBelow, - }); - const renderTooltip = useCallback( - ( - serie, - isIdle, - isLoading, - isSuccess, - isError, - data, - aboveOrBelow, - isBelow, - ) => { - const isOutpassingDataDisplayed = - (!isBelow && isOnHoverFetchingNeeded) || - (isBelow && isOnHoverFetchingNeededOut); - return ( - renderTooltipSerie(serie) + - renderOutpassingThresholdTitle( - `Nodes ${aboveOrBelow} ${serie.key}`, - isOutpassingDataDisplayed, - theme, - ) + - (isOutpassingDataDisplayed - ? `${renderQuantileData( - isIdle, - isLoading, - isSuccess, - isError, - data, - nodeMapPerIp, - theme, - valueBase, - serie.unitLabel, - intl, - )}` - : ``) - ); - }, - [ - nodeMapPerIp, - theme, - valueBase, - isOnHoverFetchingNeeded, - isOnHoverFetchingNeededOut, - ], - ); - return ( - { - onHoverIn(datum); - onHoverOut(datum); - }, - [onHoverIn, onHoverOut], - )} - yAxisTitle={yAxisTitle} - // @ts-expect-error - FIXME when you are working on it - isLegendHidden={isLegendHidden} - unitRange={UNIT_RANGE_BS} - renderTooltipSerie={useCallback( - (serie) => { - if (serie.key === `Q90-${metricPrefixAbove}`) { - return renderTooltip( - serie, - isIdleQuantile90In, - isLoadingQuantile90In, - isSuccessQuantile90In, - isErrorQuantile90In, - quantile90InData, - 'above', - false, - ); - } - if (serie.key === `Q5-${metricPrefixAbove}`) { - return `${renderTooltip( - serie, - isIdleQuantile5, - isLoadingQuantile5, - isSuccessQuantile5, - isErrorQuantile5, - quantile5Data, - 'below', - false, - )}${renderTooltipSeperationLine(theme.border)} - `; - } + const { valueBase, unitLabel } = useMemo(() => { + if (!seriesQuantile.above?.length && !seriesQuantile.below?.length) { + return { valueBase: 1, unitLabel: '' }; + } - if (serie.key === `Q90-${metricPrefixBelow}`) { - return renderTooltip( - serie, - isIdleQuantile90Out, - isLoadingQuantile90Out, - isSuccessQuantile90Out, - isErrorQuantile90Out, - quantile90OutData, - 'above', - true, - ); - } + const allSeries = [ + ...(seriesQuantile.above || []), + ...(seriesQuantile.below || []), + ]; + const allValues = allSeries.flatMap((serie: any) => + serie.data + .map(([_, value]: [number, any]) => + typeof value === 'string' ? parseFloat(value) : Math.abs(value), + ) + .filter((v: any) => v !== null && !isNaN(v)), + ); - if (serie.key === `Q5-${metricPrefixBelow}`) { - return renderTooltip( - serie, - isIdleQuantile5Out, - isLoadingQuantile5Out, - isSuccessQuantile5Out, - isErrorQuantile5Out, - quantile5OutData, - 'below', - true, - ); - } + const maxValue = Math.max(...allValues); + const unit = UNIT_RANGE_BS.slice() + .reverse() + .find((range: any) => maxValue >= range.threshold); - return renderTooltipSerie(serie); - }, - [ - isIdleQuantile90In, - isLoadingQuantile90In, - isSuccessQuantile90In, - isIdleQuantile5, - isLoadingQuantile5, - isSuccessQuantile5, - isIdleQuantile90Out, - isLoadingQuantile90Out, - isSuccessQuantile90Out, - isIdleQuantile5Out, - isLoadingQuantile5Out, - isSuccessQuantile5Out, - // @ts-expect-error - FIXME when you are working on it - JSON.stringify(quantile5Data?.data), - // @ts-expect-error - FIXME when you are working on it - JSON.stringify(quantile90InData?.data), - // @ts-expect-error - FIXME when you are working on it - JSON.stringify(quantile90OutData?.data), - // @ts-expect-error - FIXME when you are working on it - JSON.stringify(quantile5OutData?.data), - JSON.stringify(nodeMapPerIp), - metricPrefixAbove, - metricPrefixBelow, - ], - )} - /> + return { + valueBase: unit ? unit.threshold || 1 : 1, + unitLabel: unit ? unit.label : '', + }; + }, [seriesQuantile]); + + const colorSet = useMemo(() => { + return createSymmetricalQuantileColorSet( + seriesQuantile.above || [], + seriesQuantile.below || [], + ); + }, [seriesQuantile]); + + const timeFormat = useMemo(() => { + return getTimeFormatForInterval(interval); + }, [interval]); + return ( + + { + return ( + + ); + }} + /> + + ); }; diff --git a/ui/src/components/SymmetricalQuantileTooltip.tsx b/ui/src/components/SymmetricalQuantileTooltip.tsx new file mode 100644 index 0000000000..a8311c6b7f --- /dev/null +++ b/ui/src/components/SymmetricalQuantileTooltip.tsx @@ -0,0 +1,255 @@ +import React, { useMemo } from 'react'; +import { useQuery } from 'react-query'; +import type { TooltipContentProps } from 'recharts'; +import { + QuantileTooltipRenderer, + transformSymmetricalPayload, + sortPayloadEntries, +} from './shared/QuantileTooltipShared'; + +type SymmetricalQuantileTooltipProps = { + tooltipProps: TooltipContentProps; + metricPrefixAbove: string; + metricPrefixBelow: string; + valueBase: number; + unitLabel: string; + timeFormat?: + | 'day-month-abbreviated-hour-minute' + | 'day-month-abbreviated-hour-minute-second' + | 'long-date-without-weekday'; + getAboveQuantileHoverQuery?: ( + timestamp?: string, + threshold?: number, + operator?: '>' | '<', + isOnHoverFetchingRequired?: boolean, + devices?: string | string[], + ) => any; + getBelowQuantileHoverQuery?: ( + timestamp?: string, + threshold?: number, + operator?: '>' | '<', + isOnHoverFetchingRequired?: boolean, + devices?: string | string[], + ) => any; + nodeMapPerIp?: Record; + devices?: string | string[]; +}; + +export const SymmetricalQuantileTooltip: React.FC< + SymmetricalQuantileTooltipProps +> = ({ + tooltipProps, + nodeMapPerIp, + devices, + valueBase, + unitLabel, + timeFormat, + metricPrefixAbove, + metricPrefixBelow, + getAboveQuantileHoverQuery, + getBelowQuantileHoverQuery, +}) => { + const { active, payload, label } = tooltipProps; + + // Extract quantile data for symmetrical chart + const quantileData = useMemo(() => { + if (!payload || !payload.length) return null; + return transformSymmetricalPayload( + payload, + label, + metricPrefixAbove, + metricPrefixBelow, + ); + }, [payload, label, metricPrefixAbove, metricPrefixBelow]); + + // Determine if additional fetching is needed + const isOnHoverFetchingNeeded = useMemo(() => { + if (!quantileData) return false; + return ( + quantileData.median.above !== quantileData.q90.above || + quantileData.median.above !== quantileData.q5.above || + quantileData.median.below !== quantileData.q90.below || + quantileData.median.below !== quantileData.q5.below + ); + }, [quantileData]); + + // Fetch quantile hover data for above queries + const quantile90AboveResult = useQuery( + getAboveQuantileHoverQuery?.( + quantileData?.timestamp?.toString(), + quantileData?.q90.above, + '>', + isOnHoverFetchingNeeded, + devices, + ), + { + enabled: + !!quantileData && + isOnHoverFetchingNeeded && + !!getAboveQuantileHoverQuery && + !!quantileData.q90.above, + }, + ); + + const quantile5AboveResult = useQuery( + getAboveQuantileHoverQuery?.( + quantileData?.timestamp?.toString(), + quantileData?.q5.above, + '<', + isOnHoverFetchingNeeded, + devices, + ), + { + enabled: + !!quantileData && + isOnHoverFetchingNeeded && + !!getAboveQuantileHoverQuery && + !!quantileData.q5.above, + }, + ); + + // Fetch quantile hover data for below queries + const quantile90BelowResult = useQuery( + getBelowQuantileHoverQuery?.( + quantileData?.timestamp?.toString(), + quantileData?.q90.below, + '>', + isOnHoverFetchingNeeded, + devices, + ), + { + enabled: + !!quantileData && + isOnHoverFetchingNeeded && + !!getBelowQuantileHoverQuery && + !!quantileData.q90.below, + }, + ); + + const quantile5BelowResult = useQuery( + getBelowQuantileHoverQuery?.( + quantileData?.timestamp?.toString(), + quantileData?.q5.below, + '<', + isOnHoverFetchingNeeded, + devices, + ), + { + enabled: + !!quantileData && + isOnHoverFetchingNeeded && + !!getBelowQuantileHoverQuery && + !!quantileData.q5.below, + }, + ); + + // Create sorted payload + const sortedPayload = useMemo(() => { + if (!quantileData) return []; + const entries = []; + + // Add Q90 entries + if (quantileData.q90.above !== null) { + entries.push({ + name: `Q90-${metricPrefixAbove}`, + dataKey: `Q90-${metricPrefixAbove}`, + value: quantileData.q90.above, + color: quantileData.q90.aboveColor, + quantileType: 'Q90', + metricPrefix: metricPrefixAbove, + quantileResult: quantile90AboveResult, + }); + } + + // Add Median entries + if (quantileData.median.above !== null) { + entries.push({ + name: `Median-${metricPrefixAbove}`, + dataKey: `Median-${metricPrefixAbove}`, + value: quantileData.median.above, + color: quantileData.median.aboveColor, + quantileType: 'Median', + metricPrefix: metricPrefixAbove, + quantileResult: null, + }); + } + + // Add Q5 entries + if (quantileData.q5.above !== null) { + entries.push({ + name: `Q5-${metricPrefixAbove}`, + dataKey: `Q5-${metricPrefixAbove}`, + value: quantileData.q5.above, + color: quantileData.q5.aboveColor, + quantileType: 'Q5', + metricPrefix: metricPrefixAbove, + quantileResult: quantile5AboveResult, + }); + } + if (quantileData.q5.below !== null) { + entries.push({ + name: `Q5-${metricPrefixBelow}`, + dataKey: `Q5-${metricPrefixBelow}`, + value: quantileData.q5.below, + color: quantileData.q5.belowColor, + quantileType: 'Q5', + metricPrefix: metricPrefixBelow, + quantileResult: quantile5BelowResult, + }); + } + if (quantileData.median.below !== null) { + entries.push({ + name: `Median-${metricPrefixBelow}`, + dataKey: `Median-${metricPrefixBelow}`, + value: quantileData.median.below, + color: quantileData.median.belowColor, + quantileType: 'Median', + metricPrefix: metricPrefixBelow, + quantileResult: null, + }); + } + if (quantileData.q90.below !== null) { + entries.push({ + name: `Q90-${metricPrefixBelow}`, + dataKey: `Q90-${metricPrefixBelow}`, + value: quantileData.q90.below, + color: quantileData.q90.belowColor, + quantileType: 'Q90', + metricPrefix: metricPrefixBelow, + quantileResult: quantile90BelowResult, + }); + } + + return sortPayloadEntries(entries, { + metricPrefixAbove, + metricPrefixBelow, + }); + }, [ + quantileData, + metricPrefixAbove, + metricPrefixBelow, + quantile90AboveResult, + quantile90BelowResult, + quantile5AboveResult, + quantile5BelowResult, + ]); + + if (!active || !payload || !payload.length || !label || !quantileData) { + return null; + } + + return ( + + ); +}; diff --git a/ui/src/components/VolumeCharts.tsx b/ui/src/components/VolumeCharts.tsx index a477743afc..29caec0acf 100644 --- a/ui/src/components/VolumeCharts.tsx +++ b/ui/src/components/VolumeCharts.tsx @@ -1,9 +1,22 @@ -import React, { useCallback } from 'react'; -import { LineTemporalChart } from '@scality/core-ui/dist/next'; import { - getSeriesForSymmetricalChart, + LineTimeSerieChart, + useMetricsTimeSpan, + useChartId, +} from '@scality/core-ui/dist/next'; +import { useCallback } from 'react'; +import { + HEIGHT_DEFAULT_CHART, + HEIGHT_SYMMETRICAL_CHART, + UNIT_RANGE_BS, + UNIT_RANGE_SECONDS, + YAXIS_TITLE_READ_WRITE, +} from '../constants'; +import { useSingleChartSerie, useSymetricalChartSeries } from '../hooks'; +import { convertPrometheusResultToSerieWithAverage, + getSeriesForSymmetricalChart, } from '../services/graphUtils'; +import type { TimeSpanProps } from '../services/platformlibrary/metrics'; import { getVolumeIOPSReadQuery, getVolumeIOPSWriteQuery, @@ -13,12 +26,10 @@ import { getVolumeThroughputWriteQuery, getVolumeUsageQuery, } from '../services/platformlibrary/metrics'; -import type { TimeSpanProps } from '../services/platformlibrary/metrics'; -import { - UNIT_RANGE_BS, - YAXIS_TITLE_READ_WRITE, -} from '@scality/core-ui/dist/components/linetemporalchart/LineTemporalChart.component'; -import { useSingleChartSerie, useSymetricalChartSeries } from '../hooks'; +import { useChartLegendRegistration } from '../hooks/useChartLegendRegistration'; + +const VOLUME_SYNC_ID = 'volume'; + export const VolumeThroughputChart = ({ instanceIp, deviceName, @@ -28,39 +39,46 @@ export const VolumeThroughputChart = ({ deviceName: string; volumeName: string; }) => { + const chartId = useChartId(); + const { interval, duration } = useMetricsTimeSpan(); const { series, startingTimeStamp, isLoading } = useSymetricalChartSeries({ - // @ts-expect-error - FIXME when you are working on it getAboveQueries: (timeSpanProps: TimeSpanProps) => [ getVolumeThroughputWriteQuery(instanceIp, deviceName, timeSpanProps), ], - // @ts-expect-error - FIXME when you are working on it getBelowQueries: (timeSpanProps: TimeSpanProps) => [ getVolumeThroughputReadQuery(instanceIp, deviceName, timeSpanProps), ], transformPrometheusDataToSeries: useCallback( - ([prometheusResultAbove], [prometheusResultBelow]) => - getSeriesForSymmetricalChart( + ([prometheusResultAbove], [prometheusResultBelow]) => { + const allSeries = getSeriesForSymmetricalChart( prometheusResultAbove, prometheusResultBelow, volumeName, 'write', 'read', - ), + ); + + return allSeries; + }, [volumeName], ), }); + + useChartLegendRegistration({ chartId, series, isSymmetrical: true }); + return ( - ); }; @@ -73,56 +91,46 @@ export const VolumeLatencyChart = ({ deviceName: string; volumeName: string; }) => { + const chartId = useChartId(); + const { interval, duration } = useMetricsTimeSpan(); const { series, startingTimeStamp, isLoading } = useSymetricalChartSeries({ - // @ts-expect-error - FIXME when you are working on it getAboveQueries: (timeSpanProps: TimeSpanProps) => [ getVolumeLatencyWriteQuery(instanceIp, deviceName, timeSpanProps), ], - // @ts-expect-error - FIXME when you are working on it getBelowQueries: (timeSpanProps: TimeSpanProps) => [ getVolumeLatencyReadQuery(instanceIp, deviceName, timeSpanProps), ], transformPrometheusDataToSeries: useCallback( - ([prometheusResultAbove], [prometheusResultBelow]) => - getSeriesForSymmetricalChart( + ([prometheusResultAbove], [prometheusResultBelow]) => { + const allSeries = getSeriesForSymmetricalChart( prometheusResultAbove, prometheusResultBelow, volumeName, 'write', 'read', - ), + ); + + return allSeries; + }, [volumeName], ), }); + + useChartLegendRegistration({ chartId, series, isSymmetrical: true }); + return ( - ); }; @@ -135,38 +143,45 @@ export const VolumeIOPSChart = ({ deviceName: string; volumeName: string; }) => { + const chartId = useChartId(); + const { interval, duration } = useMetricsTimeSpan(); const { series, startingTimeStamp, isLoading } = useSymetricalChartSeries({ - // @ts-expect-error - FIXME when you are working on it getAboveQueries: (timeSpanProps: TimeSpanProps) => [ getVolumeIOPSWriteQuery(instanceIp, deviceName, timeSpanProps), ], - // @ts-expect-error - FIXME when you are working on it getBelowQueries: (timeSpanProps: TimeSpanProps) => [ getVolumeIOPSReadQuery(instanceIp, deviceName, timeSpanProps), ], transformPrometheusDataToSeries: useCallback( - ([prometheusResultAbove], [prometheusResultBelow]) => - getSeriesForSymmetricalChart( + ([prometheusResultAbove], [prometheusResultBelow]) => { + const allSeries = getSeriesForSymmetricalChart( prometheusResultAbove, prometheusResultBelow, volumeName, 'write', 'read', - ), + ); + + return allSeries; + }, [volumeName], ), }); + + useChartLegendRegistration({ chartId, series, isSymmetrical: true }); + return ( - ); }; @@ -179,8 +194,9 @@ export const VolumeUsageChart = ({ namespace: string; volumeName: string; }) => { + const chartId = useChartId(); + const { interval, duration } = useMetricsTimeSpan(); const { series, startingTimeStamp, isLoading } = useSingleChartSerie({ - // @ts-expect-error - FIXME when you are working on it getQuery: (timeSpanProps: TimeSpanProps) => getVolumeUsageQuery(pvcName, namespace, timeSpanProps), transformPrometheusDataToSeries: useCallback( @@ -189,15 +205,20 @@ export const VolumeUsageChart = ({ [volumeName], ), }); + + useChartLegendRegistration({ chartId, series, isSymmetrical: false }); + return ( - ); }; diff --git a/ui/src/components/VolumeMetricsTab.tsx b/ui/src/components/VolumeMetricsTab.tsx index 34bbd9a0b9..5553560c05 100644 --- a/ui/src/components/VolumeMetricsTab.tsx +++ b/ui/src/components/VolumeMetricsTab.tsx @@ -1,10 +1,14 @@ -import React from 'react'; +import { + Button, + ChartLegend, + ChartLegendWrapper, +} from '@scality/core-ui/dist/next'; import { useSelector } from 'react-redux'; -import styled from 'styled-components'; -import { Button } from '@scality/core-ui/dist/next'; - -import { VOLUME_CONDITION_LINK, GRAFANA_DASHBOARDS } from '../constants'; +import { Icon, spacing } from '@scality/core-ui'; import { useIntl } from 'react-intl'; +import { GRAFANA_DASHBOARDS, VOLUME_CONDITION_LINK } from '../constants'; + +import TimespanSelector from '../containers/TimespanSelector'; import { MetricsActionContainer, NotBoundContainer, @@ -15,36 +19,8 @@ import { VolumeThroughputChart, VolumeUsageChart, } from './VolumeCharts'; -import { SyncedCursorCharts } from '@scality/core-ui/dist/components/vegachartv2/SyncedCursorCharts'; -import TimespanSelector from '../containers/TimespanSelector'; -import { Icon, spacing } from '@scality/core-ui'; - -const GraphGrid = styled.div` - display: grid; - gap: 8px; - grid-template: - 'usage latency' 1fr - 'throughput iops' 1fr - / 1fr 1fr; - .sc-vegachart svg { - background-color: inherit !important; - } - .usage { - grid-area: usage; - } - .latency { - grid-area: latency; - } - .throughput { - grid-area: throughput; - } - .iops { - grid-area: iops; - } - padding-left: ${spacing.r12}; - height: calc(100% - 3rem); //100% - padding - action container height - overflow: auto; -`; +import { GraphGrid, ChartContainer } from '../containers/NodePageMetricsTab'; +import { createColorSet } from '../services/graphUtils'; const MetricsTab = (props) => { const { @@ -58,30 +34,34 @@ const MetricsTab = (props) => { const intl = useIntl(); // @ts-expect-error - FIXME when you are working on it const config = useSelector((state) => state.config); + return ( <> {volumeCondition === VOLUME_CONDITION_LINK ? ( - <> - - {config.api?.url_grafana && volumeNamespace && volumePVCName && ( - - - - - - `; -} -export const renderQuantileData = ( - isIdle, - isLoading, - isSuccess, - isError, - data, - nodeMapPerIp, - theme, - valueBase, - unitLabel, - intl, -) => { - const hoverQuantileValue = (data) => { - return unitLabel - ? // @ts-expect-error - FIXME when you are working on it - `${parseFloat(data.value[1] / (valueBase || 1)).toFixed( - 2, - )} ${unitLabel}` - : // @ts-expect-error - FIXME when you are working on it - `${parseFloat(data.value[1] / (valueBase || 1)).toFixed(2)}`; - }; + }); - return `${ - isLoading || isIdle - ? `` - : '' + // Below series colors: Q5 = red, Median = gold, Q90 = orange + belowSeries.forEach((serie) => { + const name = serie.resource || serie.name; + if (name.includes('Q5')) { + colorMapping[name] = lineColor6; // red + } else if (name.includes('Median')) { + colorMapping[name] = lineColor2; // gold + } else if (name.includes('Q90')) { + colorMapping[name] = lineColor7; // orange + } + }); + + return colorMapping; +}; + +// Utility function to determine time format based on interval +export const getTimeFormatForInterval = ( + interval: number, +): + | 'day-month-abbreviated-hour-minute' + | 'day-month-abbreviated-hour-minute-second' + | 'long-date-without-weekday' => { + if ( + interval === SAMPLE_FREQUENCY_LAST_SEVEN_DAYS || + interval === SAMPLE_FREQUENCY_LAST_TWENTY_FOUR_HOURS + ) { + return 'day-month-abbreviated-hour-minute'; } - ${ - isSuccess - ? data?.data?.result - ?.map( - (data) => - ``, - ) - .join('') - : '' + if (interval === SAMPLE_FREQUENCY_LAST_ONE_HOUR) { + return 'day-month-abbreviated-hour-minute-second'; } - ${isError ? intl.formatMessage('error_occur_outpassing_threshold') : ''} - `; -}; -export const renderOutpassingThresholdTitle = ( - title, - isOutpassingDataDisplayed, - theme, -) => { - // Hide the Outpassing threshold node list when isOnHoverFetchingNeeded is false - return isOutpassingDataDisplayed - ? `` - : ``; -}; -export const renderTooltipSeperationLine = (seperationLineColor) => { - return `
- ${ - color !== undefined - ? `` - : '' + +// Shared function to create color mapping for chart series +export const createColorSet = ( + seriesNames: string[], +): Record => { + const colorMapping: Record = {}; + seriesNames.forEach((name, index) => { + // Cycle through available colors + const colorIndex = index % CHART_COLOR_VALUES.length; + colorMapping[name] = CHART_COLOR_VALUES[colorIndex]; + }); + return colorMapping; +}; + +// Custom color mapping for symmetrical quantile chart +export const createSymmetricalQuantileColorSet = ( + aboveSeries: any[], + belowSeries: any[], +): Record => { + const colorMapping: Record = {}; + + // Above series colors: Q90 = cyan, Median = yellow, Q5 = blue + aboveSeries.forEach((serie) => { + const name = serie.resource || serie.name; + if (name.includes('Q90')) { + colorMapping[name] = lineColor3; // cyan + } else if (name.includes('Median')) { + colorMapping[name] = lineColor5; + } else if (name.includes('Q5')) { + colorMapping[name] = lineColor4; // blue } - - ${name} - - ${value} -
Loading...
${ - nodeMapPerIp[data.metric.instance] - }${hoverQuantileValue( - data, - )}
${title}

`; + return 'long-date-without-weekday'; }; diff --git a/ui/src/services/platformlibrary/metrics.ts b/ui/src/services/platformlibrary/metrics.ts index 7e6aad19e1..9f84d771b3 100644 --- a/ui/src/services/platformlibrary/metrics.ts +++ b/ui/src/services/platformlibrary/metrics.ts @@ -10,11 +10,24 @@ import { addMissingDataPoint } from '@scality/core-ui/dist/components/linetempor import { generateSelectWithKey, getNaNSegments, getSegments } from '../utils'; import { getFormattedLokiAlert } from '../loki/api'; import { NAN_STRING } from '@scality/core-ui/dist/components/constants'; + export type TimeSpanProps = { startingTimeISO: string; currentTimeISO: string; frequency: number; }; +export const generateQuantileSelectWithKey = ( + queryName: string, + quantile: number, +) => { + const quantileKey = quantile === 0.9 ? '90' : quantile === 0.5 ? '50' : '05'; + return { + select: (data) => ({ + ...data, + key: `${queryName}${quantileKey}`, + }), + }; +}; const _getPromRangeMatrixQuery = ( queryKey: string[], @@ -85,7 +98,6 @@ export const getCPUUsageQuery = ( refetchOnMount: false, refetchOnWindowFocus: false, enabled: instanceIP !== '', - // @ts-expect-error - FIXME when you are working on it ...generateSelectWithKey('cpuUsage'), }; }; @@ -111,13 +123,18 @@ export const getNodesCPUUsageQuantileQuery = ( timespanProps: TimeSpanProps, quantile: number, ) => { + const queryName = 'NodesCpuUsageQuantile'; const cpuNodesUsagePromQL = `quantile(${quantile}, 100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100))`; - return _getPromRangeMatrixQuery( - // @ts-expect-error - FIXME when you are working on it - ['NodesCpuUsageQuantile', quantile], + const query = _getPromRangeMatrixQuery( + [queryName, String(quantile)], cpuNodesUsagePromQL, timespanProps, ); + + return { + ...query, + ...generateQuantileSelectWithKey(queryName, quantile), + }; }; export const getNodesCPUUsageOutpassingThresholdQuery = ( timestamp?: string, @@ -181,7 +198,6 @@ export const getSystemLoadQuery = ( }, refetchOnMount: false, refetchOnWindowFocus: false, - // @ts-expect-error - FIXME when you are working on it ...generateSelectWithKey('systemLoad'), }; }; @@ -206,13 +222,18 @@ export const getNodesSystemLoadQuantileQuery = ( timespanProps: TimeSpanProps, quantile: number, ) => { + const queryName = 'SystemLoadQuantile'; const systemLoadQuantilePromQL = `quantile(${quantile}, (avg(node_load1) by (instance) / ignoring(container,endpoint,job,namespace,pod,service,prometheus) count(node_cpu_seconds_total{mode="idle"}) without(cpu,mode))) * 100`; - return _getPromRangeMatrixQuery( - // @ts-expect-error - FIXME when you are working on it - ['SystemLoadQuantile', quantile], + const query = _getPromRangeMatrixQuery( + [queryName, `${quantile}`], systemLoadQuantilePromQL, timespanProps, ); + + return { + ...query, + ...generateQuantileSelectWithKey(queryName, quantile), + }; }; export const getNodesSystemLoadOutpassingThresholdQuery = ( timestamp?: string, @@ -275,7 +296,6 @@ export const getMemoryQuery = ( }, refetchOnMount: false, refetchOnWindowFocus: false, - // @ts-expect-error - FIXME when you are working on it ...generateSelectWithKey('memory'), }; }; @@ -300,13 +320,18 @@ export const getNodesMemoryQuantileQuery = ( timespanProps: TimeSpanProps, quantile: number, ) => { + const queryName = 'NodesMemoryQuantile'; const nodesMemoryQuantilePromQL = `quantile(${quantile}, sum(100 - ((node_memory_MemAvailable_bytes * 100) / node_memory_MemTotal_bytes)) by(instance))`; - return _getPromRangeMatrixQuery( - // @ts-expect-error - FIXME when you are working on it - ['NodesMemoryQuantile', quantile], + const query = _getPromRangeMatrixQuery( + [queryName, String(quantile)], nodesMemoryQuantilePromQL, timespanProps, ); + + return { + ...query, + ...generateQuantileSelectWithKey(queryName, quantile), + }; }; export const getNodesMemoryOutpassingThresholdQuery = ( timestamp?: string, @@ -386,7 +411,6 @@ export const getControlPlaneBandWidthInQuery = ( refetchOnMount: false, refetchOnWindowFocus: false, enabled: planeInterface !== '', - // @ts-expect-error - FIXME when you are working on it ...generateSelectWithKey('controlPlaneBandwidthIn'), }; }; @@ -409,7 +433,6 @@ export const getControlPlaneBandWidthOutQuery = ( refetchOnMount: false, refetchOnWindowFocus: false, enabled: planeInterface !== '', - // @ts-expect-error - FIXME when you are working on it ...generateSelectWithKey('controlPlaneBandwidthOut'), }; }; @@ -656,17 +679,20 @@ export const getNodesPlanesBandwidthInQuantileQuery = ( quantile: number, devices: string[], ) => { + const queryName = 'NodesPlanesBandwidthIn'; const nodesPlanesBandwidthInQuery = `quantile(${quantile}, avg(irate(node_network_receive_bytes_total{device=~"${devices.join( '|', )}"}[5m])) by (instance,device))`; + const query = _getPromRangeMatrixQuery( + [queryName, ...devices, `${quantile}`], + nodesPlanesBandwidthInQuery, + timespanProps, + ); + return { - ..._getPromRangeMatrixQuery( - // @ts-expect-error - FIXME when you are working on it - ['NodesPlanesBandwidthIn', ...devices, quantile], - nodesPlanesBandwidthInQuery, - timespanProps, - ), + ...query, enabled: !!devices?.length, + ...generateQuantileSelectWithKey(queryName, quantile), }; }; export const getNodesPlanesBandwidthOutQuantileQuery = ( @@ -674,17 +700,20 @@ export const getNodesPlanesBandwidthOutQuantileQuery = ( quantile: number, devices: string[], ) => { + const queryName = 'NodesPlanesBandwidthOut'; const nodePlanesBandwidthOutQuery = `quantile(${quantile}, avg(irate(node_network_transmit_bytes_total{device=~"${devices.join( '|', )}"}[5m])) by (instance,device))`; + const query = _getPromRangeMatrixQuery( + [queryName, ...devices, `${quantile}`], + nodePlanesBandwidthOutQuery, + timespanProps, + ); + return { - ..._getPromRangeMatrixQuery( - // @ts-expect-error - FIXME when you are working on it - ['NodesPlanesBandwidthOut', ...devices, quantile], - nodePlanesBandwidthOutQuery, - timespanProps, - ), + ...query, enabled: !!devices?.length, + ...generateQuantileSelectWithKey(queryName, quantile), }; }; export const getNodesPlanesBandwidthInOutpassingThresholdQuery = ( @@ -700,8 +729,12 @@ export const getNodesPlanesBandwidthInOutpassingThresholdQuery = ( )}"}[5m])) by (instance,device) ${operator}= ${threshold}`; return { ..._getInstantValueQuery( - // @ts-expect-error - FIXME when you are working on it - ['NodesBandwidthInOutpassingThreshold', timestamp, threshold, operator], + [ + 'NodesBandwidthInOutpassingThreshold', + timestamp, + `${threshold}`, + operator, + ], nodesBandwidthInOutpassingThresholdPromQL, timestamp, ), @@ -715,18 +748,21 @@ export const getNodesPlanesBandwidthInOutpassingThresholdQuery = ( export const getNodesPlanesBandwidthOutOutpassingThresholdQuery = ( timestamp?: string, threshold?: number, - // @ts-expect-error - FIXME when you are working on it - operator: '>' | '<', - isOnHoverFetchingNeeded: boolean, - devices: string[], + operator?: '>' | '<', + isOnHoverFetchingNeeded?: boolean, + devices?: string[], ) => { const nodesBandwidthOutOutpassingThresholdPromQL = `avg(irate(node_network_transmit_bytes_total{device=~"${devices.join( '|', )}"}[5m])) by (instance,device) ${operator}= ${threshold}`; return { ..._getInstantValueQuery( - // @ts-expect-error - FIXME when you are working on it - ['NodesBandwidthOutOutpassingThreshold', timestamp, threshold, operator], + [ + 'NodesBandwidthOutOutpassingThreshold', + timestamp, + `${threshold}`, + operator, + ], nodesBandwidthOutOutpassingThresholdPromQL, timestamp, ), @@ -755,7 +791,6 @@ export const getIOPSWriteQuery = ( }, refetchOnMount: false, refetchOnWindowFocus: false, - // @ts-expect-error - FIXME when you are working on it ...generateSelectWithKey('IOPSWrite'), }; }; @@ -777,7 +812,6 @@ export const getIOPSReadQuery = ( }, refetchOnMount: false, refetchOnWindowFocus: false, - // @ts-expect-error - FIXME when you are working on it ...generateSelectWithKey('IOPSRead'), }; }; @@ -900,25 +934,36 @@ export const getNodesThroughputWriteQuantileQuery = ( timespanProps: TimeSpanProps, quantile: number, ) => { + const queryName = 'NodesThroughputWriteQuantile'; const nodesThroughputWritePromQL = `quantile(${quantile},sum(sum(irate(node_disk_written_bytes_total[1m])) by (instance, device))by(instance))`; - return _getPromRangeMatrixQuery( - // @ts-expect-error - FIXME when you are working on it - ['NodesThroughputWriteQuantile', quantile], + const query = _getPromRangeMatrixQuery( + [queryName, `${quantile}`], nodesThroughputWritePromQL, timespanProps, ); + + return { + ...query, + ...generateQuantileSelectWithKey(queryName, quantile), + }; }; + export const getNodesThroughputReadQuantileQuery = ( timespanProps: TimeSpanProps, quantile: number, ) => { + const queryName = 'NodesThroughputReadQueryQuantile'; const nodesThroughputReadPromQL = `quantile(${quantile},sum(sum(irate(node_disk_read_bytes_total[1m])) by (instance, device))by(instance))`; - return _getPromRangeMatrixQuery( - // @ts-expect-error - FIXME when you are working on it - ['NodesThroughputReadQueryQuantile', quantile], + const query = _getPromRangeMatrixQuery( + [queryName, `${quantile}`], nodesThroughputReadPromQL, timespanProps, ); + + return { + ...query, + ...generateQuantileSelectWithKey(queryName, quantile), + }; }; export const getNodesThroughputWriteOutpassingThresholdQuery = ( timestamp?: string, @@ -930,8 +975,7 @@ export const getNodesThroughputWriteOutpassingThresholdQuery = ( const nodesThroughputWriteAboveBelowPromQL = `sum(sum(irate(node_disk_written_bytes_total[1m])) by (instance, device))by(instance) ${operator}= ${threshold}`; return { ..._getInstantValueQuery( - // @ts-expect-error - FIXME when you are working on it - ['NodesThroughputWriteAboveBelow', timestamp, threshold, operator], + ['NodesThroughputWriteAboveBelow', timestamp, `${threshold}`, operator], nodesThroughputWriteAboveBelowPromQL, timestamp, ), @@ -952,8 +996,7 @@ export const getNodesThroughputOutpassingThresholdQuery = ( const nodesThroughputReadAboveBelowPromQL = `sum(sum(irate(node_disk_read_bytes_total[1m])) by (instance, device))by(instance) ${operator}= ${threshold}`; return { ..._getInstantValueQuery( - // @ts-expect-error - FIXME when you are working on it - ['NodesThroughputReadAboveBelow', timestamp, threshold, operator], + ['NodesThroughputReadAboveBelow', timestamp, `${threshold}`, operator], nodesThroughputReadAboveBelowPromQL, timestamp, ), @@ -1047,20 +1090,18 @@ export const getAlertsHistoryQuery = ({ frequency, encodeURIComponent(query), )?.then((resolve) => { - // @ts-expect-error - FIXME when you are working on it - if (resolve.error) { - // @ts-expect-error - FIXME when you are working on it + if (resolve.status === 'error') { throw resolve.error; } - + if (resolve.data.resultType !== 'matrix') { + throw new Error('Failed to fetch data from Prometheus'); + } const points = addMissingDataPoint( - // @ts-expect-error - FIXME when you are working on it resolve.data.result[0].values, Date.parse(startingTimeISO) / 1000, Date.parse(currentTimeISO) / 1000 - Date.parse(startingTimeISO) / 1000, frequency, ); - // @ts-expect-error - FIXME when you are working on it return getNaNSegments(points).map((segment) => ({ startsAt: new Date(segment.startsAt * 1000).toISOString(), endsAt: new Date(segment.endsAt * 1000).toISOString(), @@ -1167,25 +1208,26 @@ export const getClusterAlertSegmentQuery = (duration: number) => { frequency, encodeURIComponent(query), )?.then((resolve) => { - // @ts-expect-error - FIXME when you are working on it - if (resolve.error) { - // @ts-expect-error - FIXME when you are working on it + if (resolve.status === 'error') { throw resolve.error; } - // @ts-expect-error - FIXME when you are working on it + if (resolve.data.resultType !== 'matrix') { + throw new Error('Failed to fetch data from Prometheus'); + } + const clusterAtRiskResult = resolve.data.result.find( (result) => result.metric.alertname === 'ClusterAtRisk', ) || { values: [], }; - // @ts-expect-error - FIXME when you are working on it + const clusterDegradedResult = resolve.data.result.find( (result) => result.metric.alertname === 'ClusterDegraded', ) || { values: [], }; - // @ts-expect-error - FIXME when you are working on it + const watchdogResult = resolve.data.result.find( (result) => result.metric.alertname === 'Watchdog', ) || { diff --git a/ui/src/services/utils.test.ts b/ui/src/services/utils.test.ts index 45320c5554..da956ec516 100644 --- a/ui/src/services/utils.test.ts +++ b/ui/src/services/utils.test.ts @@ -101,7 +101,6 @@ describe('getNaNSegments', () => { //S const segments = []; //E - // @ts-expect-error - FIXME when you are working on it const nullSegments = getNaNSegments(segments); //V expect(nullSegments).toHaveLength(0); diff --git a/ui/src/services/utils.ts b/ui/src/services/utils.ts index a342575271..bbfe4415c5 100644 --- a/ui/src/services/utils.ts +++ b/ui/src/services/utils.ts @@ -277,7 +277,7 @@ export function addMissingDataPoint( return newValues; } -export function getNaNSegments(points: [[number, number | null]]): { +export function getNaNSegments(points: [number, number | string | null][]): { startsAt: number; endsAt?: number; }[] { @@ -298,7 +298,6 @@ export function getNaNSegments(points: [[number, number | null]]): { }; }); const nullSegments = segments.filter( - // @ts-expect-error - FIXME when you are working on it (segment) => segment.value === NAN_STRING, ); return nullSegments.reduce((mergedNullSegments, segment) => { @@ -568,6 +567,6 @@ export const linuxDrivesNamingIncrement = (devicePath, increment) => { * (useQueries returns an array and the order of response objects is not always the * same as the order in which the request were performed) */ -export const generateSelectWithKey = (key, isAverage) => ({ +export const generateSelectWithKey = (key, isAverage = false) => ({ select: (data) => ({ ...data, key: !isAverage ? key : `${key}Avg` }), });