diff --git a/config-overrides.js b/config-overrides.js index 122b24445a..e0acfa4544 100644 --- a/config-overrides.js +++ b/config-overrides.js @@ -54,7 +54,7 @@ module.exports = { return config; }, jest: (config) => { - // Some @gravity-ui libs (react-data-table, paranoid) are build in esm only + // Some @gravity-ui libs (react-data-table) are build in esm only // So they need to be transformed // By default jest does not transform anything in node_modules // So this override excludes node_modules except @gravity-ui diff --git a/package-lock.json b/package-lock.json index 614e4a058d..96e2451c35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,11 +15,11 @@ "@gravity-ui/components": "^4.4.0", "@gravity-ui/date-components": "^3.2.3", "@gravity-ui/date-utils": "^2.5.6", + "@gravity-ui/graph": "^1.1.4", "@gravity-ui/i18n": "^1.7.0", "@gravity-ui/icons": "^2.16.0", "@gravity-ui/illustrations": "^2.1.0", "@gravity-ui/navigation": "^3.3.9", - "@gravity-ui/paranoid": "^3.0.0", "@gravity-ui/react-data-table": "^2.2.1", "@gravity-ui/table": "^1.10.1", "@gravity-ui/uikit": "^7.23.0", @@ -3421,6 +3421,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@gravity-ui/graph": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@gravity-ui/graph/-/graph-1.1.4.tgz", + "integrity": "sha512-gZsFFouYo371UZ88/ui9jsx25hBpoILxNDE0MGVZLgGh9WjVWFOnjT3T6YwrYwq+mFrGUo6W86lEQgdpI4NTCw==", + "license": "MIT", + "dependencies": { + "@preact/signals-core": "^1.5.1", + "intersects": "^2.7.2", + "lodash-es": "^4.17.21", + "rbush": "^3.0.1" + }, + "engines": { + "pnpm": "Please use npm instead of pnpm to install dependencies", + "yarn": "Please use npm instead of yarn to install dependencies" + } + }, "node_modules/@gravity-ui/i18n": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@gravity-ui/i18n/-/i18n-1.8.0.tgz", @@ -3472,22 +3488,6 @@ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@gravity-ui/paranoid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@gravity-ui/paranoid/-/paranoid-3.0.0.tgz", - "integrity": "sha512-FczulYruAzp0O/yqVCMNBamKJ190HKCm87H6YXVfHaxT96lcpN0vTpD/WF7UR7jsH5ZuU6X8uQDvOyuyHGWyag==", - "license": "MIT", - "dependencies": { - "copy-to-clipboard": "^3.3.3", - "fabric": "^5.3.0", - "lodash": "^4.17.21", - "resize-observer-polyfill": "^1.5.1" - }, - "peerDependencies": { - "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/@gravity-ui/prettier-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@gravity-ui/prettier-config/-/prettier-config-1.1.0.tgz", @@ -4615,7 +4615,9 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", @@ -5106,6 +5108,16 @@ "node": ">= 8" } }, + "node_modules/@preact/signals-core": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.12.0.tgz", + "integrity": "sha512-etWpENXm469RHMWIZGblgWrapbIGcRcbccEGGaLkFez3PjlI3XkBrUtSiNFsIfV/DN16PxMOxbWAZUIaLFyJDg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz", @@ -5799,15 +5811,6 @@ "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "optional": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -7030,13 +7033,15 @@ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "deprecated": "Use your platform's native atob() and btoa() methods instead", - "devOptional": true + "dev": true }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/accepts": { "version": "1.3.8", @@ -7064,7 +7069,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "devOptional": true, + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -7076,7 +7081,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "devOptional": true, + "dev": true, "dependencies": { "acorn": "^7.1.1", "acorn-walk": "^7.1.1" @@ -7086,7 +7091,7 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "devOptional": true, + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -7107,7 +7112,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.4.0" } @@ -7143,7 +7148,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "devOptional": true, + "dev": true, "dependencies": { "debug": "4" }, @@ -7303,7 +7308,9 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/are-docs-informative": { "version": "0.0.2", @@ -7319,7 +7326,9 @@ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "deprecated": "This package is no longer supported.", + "dev": true, "optional": true, + "peer": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -7332,7 +7341,9 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -7346,7 +7357,9 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -8071,7 +8084,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "devOptional": true + "dev": true }, "node_modules/base64-js": { "version": "1.5.1", @@ -8254,7 +8267,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "devOptional": true + "dev": true }, "node_modules/browserslist": { "version": "4.24.4", @@ -8508,8 +8521,10 @@ "version": "2.11.2", "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "dev": true, "hasInstallScript": true, "optional": true, + "peer": true, "dependencies": { "@mapbox/node-pre-gyp": "^1.0.0", "nan": "^2.17.0", @@ -8622,7 +8637,9 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=10" } @@ -8892,7 +8909,9 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, "optional": true, + "peer": true, "bin": { "color-support": "bin.js" } @@ -9020,7 +9039,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "devOptional": true + "dev": true }, "node_modules/confusing-browser-globals": { "version": "1.0.11", @@ -9041,7 +9060,9 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/content-disposition": { "version": "0.5.4", @@ -9900,17 +9921,11 @@ "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "dev": true }, - "node_modules/cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "optional": true - }, "node_modules/cssstyle": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "devOptional": true, + "dev": true, "dependencies": { "cssom": "~0.3.6" }, @@ -9922,7 +9937,7 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "devOptional": true + "dev": true }, "node_modules/csstype": { "version": "3.1.3", @@ -10397,33 +10412,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", - "optional": true, - "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/data-urls/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "optional": true, - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -10484,7 +10472,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "devOptional": true, + "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -10501,13 +10489,15 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "devOptional": true + "dev": true }, "node_modules/decompress-response": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "mimic-response": "^2.0.0" }, @@ -10621,7 +10611,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/depd": { "version": "2.0.0", @@ -10655,7 +10647,9 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -10820,19 +10814,6 @@ } ] }, - "node_modules/domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "deprecated": "Use your platform's native DOMException instead", - "optional": true, - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/domhandler": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", @@ -11252,7 +11233,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "devOptional": true, + "dev": true, "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -12041,7 +12022,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "devOptional": true, + "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -12078,7 +12059,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "devOptional": true, + "dev": true, "engines": { "node": ">=4.0" } @@ -12093,7 +12074,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.10.0" } @@ -12261,18 +12242,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fabric": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/fabric/-/fabric-5.5.1.tgz", - "integrity": "sha512-R1GL1mlnRpvfSfpqqv0lzFI4ff+TEblmRXlnpKWwpBrCWCE6FcDl0L/pbWXsPO9w/Do8oEsE7RHgBw6p2k7WZQ==", - "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "canvas": "^2.8.0", - "jsdom": "^19.0.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -12903,7 +12872,9 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -12915,7 +12886,9 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -12927,7 +12900,9 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/fs-monkey": { "version": "1.0.6", @@ -12939,7 +12914,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "devOptional": true + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -12997,7 +12972,9 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", "deprecated": "This package is no longer supported.", + "dev": true, "optional": true, + "peer": true, "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", @@ -13017,13 +12994,17 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/gauge/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -13032,7 +13013,9 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -13202,7 +13185,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "devOptional": true, + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -13240,7 +13223,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -13250,7 +13233,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -13499,7 +13482,9 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/hasown": { "version": "2.0.2", @@ -13663,18 +13648,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "optional": true, - "dependencies": { - "whatwg-encoding": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/html-entities": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", @@ -13832,20 +13805,6 @@ "node": ">=8.0.0" } }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "optional": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/http-proxy-middleware": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", @@ -13874,7 +13833,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "devOptional": true, + "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -14075,7 +14034,7 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "devOptional": true, + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -14085,7 +14044,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "devOptional": true + "dev": true }, "node_modules/ini": { "version": "4.1.1", @@ -14119,6 +14078,12 @@ "node": ">=12" } }, + "node_modules/intersects": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/intersects/-/intersects-2.7.2.tgz", + "integrity": "sha512-/LtLDq40iFtvnjhouev9p2R+jP+raVONPiD1t8Mcj879pkrLiav99BTRPBkfMPwSYr5vTNws3USGoW+8usS45A==", + "license": "MIT" + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -14505,7 +14470,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "devOptional": true + "dev": true }, "node_modules/is-regex": { "version": "1.2.1", @@ -17605,52 +17570,6 @@ "node": ">=12.0.0" } }, - "node_modules/jsdom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", - "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==", - "optional": true, - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.5.0", - "acorn-globals": "^6.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.1", - "decimal.js": "^10.3.1", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^3.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^10.0.0", - "ws": "^8.2.3", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -18169,6 +18088,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -18416,7 +18341,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "devOptional": true, + "dev": true, "dependencies": { "semver": "^6.0.0" }, @@ -18431,7 +18356,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "devOptional": true, + "dev": true, "bin": { "semver": "bin/semver.js" } @@ -18624,7 +18549,9 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=8" }, @@ -18695,7 +18622,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "devOptional": true, + "dev": true, "engines": { "node": ">=8" } @@ -18704,7 +18631,9 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -18717,7 +18646,9 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -18729,13 +18660,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "devOptional": true, + "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -18776,7 +18709,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true + "dev": true }, "node_modules/multicast-dns": { "version": "7.2.5", @@ -18806,7 +18739,9 @@ "version": "2.22.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/nanoid": { "version": "3.3.11", @@ -18901,7 +18836,9 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -18921,19 +18858,25 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/node-fetch/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/node-fetch/node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -18974,7 +18917,9 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "abbrev": "1" }, @@ -19223,7 +19168,9 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", "deprecated": "This package is no longer supported.", + "dev": true, "optional": true, + "peer": true, "dependencies": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", @@ -19255,7 +19202,7 @@ "version": "2.2.16", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", - "devOptional": true + "dev": true }, "node_modules/object-assign": { "version": "4.1.1", @@ -19431,7 +19378,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "devOptional": true, + "dev": true, "dependencies": { "wrappy": "1" } @@ -19662,7 +19609,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "devOptional": true + "dev": true }, "node_modules/parseurl": { "version": "1.3.3", @@ -19696,7 +19643,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.10.0" } @@ -21749,7 +21696,7 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "devOptional": true, + "dev": true, "dependencies": { "punycode": "^2.3.1" }, @@ -21761,7 +21708,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } @@ -22009,7 +21956,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "devOptional": true + "dev": true }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -22031,6 +21978,12 @@ } ] }, + "node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", + "license": "ISC" + }, "node_modules/raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -22091,6 +22044,15 @@ "node": ">=0.10.0" } }, + "node_modules/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "license": "MIT", + "dependencies": { + "quickselect": "^2.0.0" + } + }, "node_modules/rc-slider": { "version": "11.1.8", "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.8.tgz", @@ -25085,18 +25047,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "devOptional": true + "dev": true }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" }, - "node_modules/resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" - }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -25292,7 +25249,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "devOptional": true, + "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -25421,7 +25378,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -25592,7 +25549,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "devOptional": true, + "dev": true, "dependencies": { "xmlchars": "^2.2.0" }, @@ -25650,7 +25607,7 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "devOptional": true, + "dev": true, "bin": { "semver": "bin/semver.js" }, @@ -25980,12 +25937,13 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "devOptional": true + "dev": true }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, "funding": [ { "type": "github", @@ -26000,13 +25958,16 @@ "url": "https://feross.org/support" } ], - "optional": true + "optional": true, + "peer": true }, "node_modules/simple-get": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "decompress-response": "^4.2.0", "once": "^1.3.1", @@ -27503,7 +27464,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "devOptional": true + "dev": true }, "node_modules/synckit": { "version": "0.11.8", @@ -27728,7 +27689,9 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -27745,7 +27708,9 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/temp": { "version": "0.9.4", @@ -28183,7 +28148,7 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "devOptional": true, + "dev": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -28198,23 +28163,11 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "devOptional": true, + "dev": true, "engines": { "node": ">= 4.0.0" } }, - "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "optional": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", @@ -28717,7 +28670,7 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "devOptional": true, + "dev": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -28758,7 +28711,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "devOptional": true + "dev": true }, "node_modules/util.promisify": { "version": "1.0.1", @@ -28846,23 +28799,11 @@ "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", - "devOptional": true, + "dev": true, "dependencies": { "browser-process-hrtime": "^1.0.0" } }, - "node_modules/w3c-xmlserializer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", - "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", - "optional": true, - "dependencies": { - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -28899,15 +28840,6 @@ "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-1.1.2.tgz", "integrity": "sha512-PFMKIY+bRSXlMxVAQ+m2aw9c/ioUYfDgrYot0YUa+/xa0sakubWhSDyxAKwzymvXVdF4CZI71g06W+mqhzu6ig==" }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "optional": true, - "engines": { - "node": ">=12" - } - }, "node_modules/webpack": { "version": "5.98.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", @@ -29097,46 +29029,12 @@ "node": ">=0.8.0" } }, - "node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "optional": true, - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/whatwg-fetch": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", "dev": true }, - "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "optional": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz", - "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==", - "optional": true, - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -29251,7 +29149,9 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -29260,13 +29160,17 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/wide-align/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -29275,7 +29179,9 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -29709,7 +29615,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "devOptional": true + "dev": true }, "node_modules/write-file-atomic": { "version": "4.0.2", @@ -29729,7 +29635,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "devOptional": true, + "dev": true, "engines": { "node": ">=10.0.0" }, @@ -29746,20 +29652,11 @@ } } }, - "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "optional": true, - "engines": { - "node": ">=12" - } - }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "devOptional": true + "dev": true }, "node_modules/xtend": { "version": "4.0.2", diff --git a/package.json b/package.json index 831a12837c..fed9d72653 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,11 @@ "@gravity-ui/components": "^4.4.0", "@gravity-ui/date-components": "^3.2.3", "@gravity-ui/date-utils": "^2.5.6", + "@gravity-ui/graph": "^1.1.4", "@gravity-ui/i18n": "^1.7.0", "@gravity-ui/icons": "^2.16.0", "@gravity-ui/illustrations": "^2.1.0", "@gravity-ui/navigation": "^3.3.9", - "@gravity-ui/paranoid": "^3.0.0", "@gravity-ui/react-data-table": "^2.2.1", "@gravity-ui/table": "^1.10.1", "@gravity-ui/uikit": "^7.23.0", diff --git a/src/components/EntityStatus/EntityStatus.tsx b/src/components/EntityStatus/EntityStatus.tsx index 334a36555d..8cf1671124 100644 --- a/src/components/EntityStatus/EntityStatus.tsx +++ b/src/components/EntityStatus/EntityStatus.tsx @@ -38,7 +38,6 @@ function defaultRenderName(name?: string) { return name ?? ''; } -// eslint-disable-next-line complexity export function EntityStatus({ status = EFlag.Grey, name = '', diff --git a/src/components/Graph/BlockComponents/ConnectionBlockComponent.tsx b/src/components/Graph/BlockComponents/ConnectionBlockComponent.tsx new file mode 100644 index 0000000000..9a4140a2a5 --- /dev/null +++ b/src/components/Graph/BlockComponents/ConnectionBlockComponent.tsx @@ -0,0 +1,49 @@ +import { + ArrowsExpandHorizontal, + DatabaseFill, + GripHorizontal, + MapPin, + Shuffle, +} from '@gravity-ui/icons'; +import {Icon} from '@gravity-ui/uikit'; +import type {IconData} from '@gravity-ui/uikit'; + +import {TooltipComponent} from '../TooltipComponent'; +import type {ExtendedTBlock} from '../types'; + +type Props = { + block: ExtendedTBlock; + className: string; +}; + +const getIcon = (name: string): IconData | undefined => { + switch (name) { + case 'Merge': + return DatabaseFill; + case 'UnionAll': + return GripHorizontal; + case 'HashShuffle': + return Shuffle; + case 'Map': + return MapPin; + case 'Broadcast': + return ArrowsExpandHorizontal; + default: + return undefined; + } +}; + +export const ConnectionBlockComponent = ({className, block}: Props) => { + const icon = getIcon(block.name); + const content = ( +
+ {icon && } {block.name} +
+ ); + + if (!block.stats?.length) { + return content; + } + + return {content}; +}; diff --git a/src/components/Graph/BlockComponents/QueryBlockComponent.tsx b/src/components/Graph/BlockComponents/QueryBlockComponent.tsx new file mode 100644 index 0000000000..730eda79a7 --- /dev/null +++ b/src/components/Graph/BlockComponents/QueryBlockComponent.tsx @@ -0,0 +1,7 @@ +type Props = { + className: string; +}; + +export const QueryBlockComponent = ({className}: Props) => { + return
; +}; diff --git a/src/components/Graph/BlockComponents/ResultBlockComponent.tsx b/src/components/Graph/BlockComponents/ResultBlockComponent.tsx new file mode 100644 index 0000000000..8d0ac4f111 --- /dev/null +++ b/src/components/Graph/BlockComponents/ResultBlockComponent.tsx @@ -0,0 +1,10 @@ +import type {TBlock} from '@gravity-ui/graph'; + +type Props = { + block: TBlock; + className: string; +}; + +export const ResultBlockComponent = ({className, block}: Props) => { + return
{block.name}
; +}; diff --git a/src/components/Graph/BlockComponents/StageBlockComponent.tsx b/src/components/Graph/BlockComponents/StageBlockComponent.tsx new file mode 100644 index 0000000000..3150130194 --- /dev/null +++ b/src/components/Graph/BlockComponents/StageBlockComponent.tsx @@ -0,0 +1,32 @@ +import {Text} from '@gravity-ui/uikit'; + +import {TooltipComponent} from '../TooltipComponent'; +import i18n from '../i18n'; +import type {ExtendedTBlock} from '../types'; + +type Props = { + block: ExtendedTBlock; + className: string; +}; + +export const StageBlockComponent = ({className, block}: Props) => { + const content = ( +
+ {block.operators + ? block.operators.map((item) =>
{item}
) + : block.name} + {block.tables ? ( +
+ {i18n('label_tables')}: + {block.tables.join(', ')} +
+ ) : null} +
+ ); + + if (!block.stats?.length) { + return content; + } + + return {content}; +}; diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx deleted file mode 100644 index 33ba32d68b..0000000000 --- a/src/components/Graph/Graph.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; - -import {getTopology, getYdbPlanNodeShape} from '@gravity-ui/paranoid'; -import type {Data, GraphNode, Options, Shapes} from '@gravity-ui/paranoid'; - -interface GraphProps { - data: Data; - opts?: Options; - shapes?: Shapes; -} - -export function Graph(props: GraphProps) { - const containerRef = React.useRef(null); - const containerId = React.useId(); - - const {data, opts, shapes} = props; - - React.useEffect(() => { - const graphRoot = containerRef.current; - if (!graphRoot) { - return undefined; - } - - graphRoot.innerHTML = ''; - // set width and height to screen size to make a canvas bigger then the container - graphRoot.style.setProperty('width', '100vw'); - graphRoot.style.setProperty('height', '100vh'); - - const topology = getTopology(graphRoot.id, data, opts, shapes); - topology.render(); - graphRoot.style.setProperty('width', '100%'); - graphRoot.style.setProperty('height', '100%'); - return () => { - topology.destroy(); - }; - }, [data, opts, shapes]); - - return
; -} - -const renderExplainNode = (node: GraphNode): string => { - const parts = node.name.split('|'); - return parts.length > 1 ? parts[1] : node.name; -}; - -const schemaOptions: Options = { - renderNodeTitle: renderExplainNode, - textOverflow: 'normal' as const, - initialZoomFitsCanvas: true, -}; - -const schemaShapes = { - node: getYdbPlanNodeShape, -}; - -interface YDBGraphProps { - data: Data; -} - -export function YDBGraph(props: YDBGraphProps) { - return {...props} opts={schemaOptions} shapes={schemaShapes} />; -} diff --git a/src/components/Graph/GraphControls.tsx b/src/components/Graph/GraphControls.tsx new file mode 100644 index 0000000000..d5110ee306 --- /dev/null +++ b/src/components/Graph/GraphControls.tsx @@ -0,0 +1,47 @@ +import {useCallback} from 'react'; + +import type {Graph} from '@gravity-ui/graph'; +import {Button, Icon} from '@gravity-ui/uikit'; + +import {cn} from '../../utils/cn'; + +import MagnifierMinusIcon from '@gravity-ui/icons/svgs/magnifier-minus.svg'; +import MagnifierPlusIcon from '@gravity-ui/icons/svgs/magnifier-plus.svg'; + +const b = cn('ydb-gravity-graph'); + +const ZOOM_STEP = 1.25; + +interface Props { + graph: Graph; +} + +export const GraphControls = ({graph}: Props) => { + const onZoomInClick = useCallback(() => { + const cameraScale = graph.cameraService.getCameraScale(); + graph.zoom({scale: cameraScale * ZOOM_STEP}); + }, [graph]); + + const onZoomOutClick = useCallback(() => { + const cameraScale = graph.cameraService.getCameraScale(); + graph.zoom({scale: cameraScale / ZOOM_STEP}); + }, [graph]); + + const onResetZoomClick = useCallback(() => { + graph.zoom({scale: 1}); + }, [graph]); + + return ( +
+ + + +
+ ); +}; diff --git a/src/components/Graph/GravityGraph.scss b/src/components/Graph/GravityGraph.scss new file mode 100644 index 0000000000..ea181b6b7d --- /dev/null +++ b/src/components/Graph/GravityGraph.scss @@ -0,0 +1,124 @@ +.ydb-gravity-graph { + &__block { + cursor: auto; + + border: none; + background: none; + } + + &__block-content { + width: 100%; + padding: 8px 12px; + + font-family: var(--g-font-family); + font-size: var(--g-text-body-short-font-size); + line-height: var(--g-text-body-short-line-height); + + border: 1px solid var(--g-color-line-generic); + background: var(--g-color-base-float); + box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.3); + + &[aria-haspopup='dialog'] { + cursor: pointer; + } + } + + &__block-id { + position: absolute; + top: 4px; + right: 4px; + + font-size: 10px; + line-height: 1; + + color: var(--g-color-text-secondary); + } + + &__block-content.query { + height: 100%; + + border-radius: 50%; + } + + &__block-content.result { + display: flex; + justify-content: center; + align-items: center; + } + + &__block-content.stage { + border-radius: 6px; + } + + &__block-content.connection { + display: flex; + align-items: center; + gap: 4px; + + color: var(--g-color-text-info-heavy); + border: 1px solid var(--g-color-line-info); + border-radius: 6px; + background: var(--g-color-base-info-light); + box-shadow: none; + } + + &__tooltip-content { + overflow: auto; + + width: 300px; + max-height: 90vh; + padding: 0 8px 8px; + + font-family: var(--g-font-family); + font-size: var(--g-text-body-short-font-size); + line-height: var(--g-text-body-short-line-height); + } + + &__tooltip-tabs { + margin-bottom: 8px; + } + + &__tooltip-stat-row { + display: grid; + grid-template-columns: 100px auto; + gap: 8px; + + margin: 4px 0 0; + + span { + overflow: hidden; + + text-overflow: ellipsis; + } + span:nth-child(1) { + color: var(--g-color-text-secondary); + } + span:nth-child(2) { + word-wrap: break-word; + } + } + + &__tooltip-stat-group { + margin-top: 8px; + } + + &__tooltip-stat-group + &__tooltip-stat-group { + padding-top: 8px; + + border-top: 1px dotted var(--g-color-line-generic); + } + + &__tooltip-stat-group-name { + font-weight: bold; + } + + &__zoom-controls { + position: absolute; + z-index: 999; + top: 8px; + right: 50px; + + display: flex; + gap: 2px; + } +} diff --git a/src/components/Graph/GravityGraph.tsx b/src/components/Graph/GravityGraph.tsx new file mode 100644 index 0000000000..64e7078519 --- /dev/null +++ b/src/components/Graph/GravityGraph.tsx @@ -0,0 +1,121 @@ +import React from 'react'; + +import {GraphState} from '@gravity-ui/graph'; +import type {HookGraphParams} from '@gravity-ui/graph/react'; +import {GraphBlock, GraphCanvas, useGraph, useGraphEvent} from '@gravity-ui/graph/react'; + +import {cn} from '../../utils/cn'; + +const b = cn('ydb-gravity-graph'); + +import {ConnectionBlockComponent} from './BlockComponents/ConnectionBlockComponent'; +import {QueryBlockComponent} from './BlockComponents/QueryBlockComponent'; +import {ResultBlockComponent} from './BlockComponents/ResultBlockComponent'; +import {StageBlockComponent} from './BlockComponents/StageBlockComponent'; +import {GraphControls} from './GraphControls'; +import {NonSelectableConnection} from './NonSelectableConnection'; +import {graphColorsConfig} from './colorsConfig'; +import type {Data} from './types'; +import {parseCustomPropertyValue} from './utils'; + +import './GravityGraph.scss'; + +interface Props { + data: Data; + theme?: string; +} + +const config: HookGraphParams = { + settings: { + connection: NonSelectableConnection, + showConnectionArrows: false, + }, +}; + +const renderBlockFn = (graph: any, block: any) => { + const map: Record> = { + query: QueryBlockComponent, + result: ResultBlockComponent, + stage: StageBlockComponent, + connection: ConnectionBlockComponent, + }; + + const Component = map[block.is as keyof typeof map]; + + return ( + + {Component ? ( + <> + + {block.id !== 'undefined' && block.is !== 'result' && ( +
#{block.id}
+ )} + + ) : ( + block.id + )} +
+ ); +}; + +export function GravityGraph({data, theme}: Props) { + const {graph, start} = useGraph(config); + + React.useEffect(() => { + // Just in case, although mounting takes more time than calculation + const worker = new Worker(new URL('./treeLayout', import.meta.url)); + worker.postMessage({ + nodes: data.nodes, + links: data.links, + }); + + worker.onmessage = function (e) { + const {layout, edges} = e.data; + + graph.setEntities({ + blocks: layout, + connections: edges, + }); + }; + + worker.onerror = (err) => { + console.error(err); + }; + + return () => { + worker.terminate(); + }; + }, [data.nodes, data.links, graph]); + + React.useEffect(() => { + graph.setColors(parseCustomPropertyValue(graphColorsConfig)); + }, [graph, theme]); + + useGraphEvent(graph, 'state-change', ({state}) => { + if (state === GraphState.ATTACHED) { + graph.cameraService.set({ + scale: 1, + scaleMax: 1.5, + scaleMin: 0.5, + }); + graph.setConstants({ + block: { + SCALES: [0.125, 0.225, 0.5], // Detailed view stays until zoom = 0.5 + }, + }); + start(); + // graph.zoomTo("center", { padding: 300 }); + } + }); + + return ( + <> + + + + ); +} diff --git a/src/components/Graph/NonSelectableConnection.tsx b/src/components/Graph/NonSelectableConnection.tsx new file mode 100644 index 0000000000..896ad4faa5 --- /dev/null +++ b/src/components/Graph/NonSelectableConnection.tsx @@ -0,0 +1,15 @@ +import {MultipointConnection} from '@gravity-ui/graph/react'; + +/** + * Кастомный класс соединения, который отключает визуальное выделение + * Наследуется от MultipointConnection и переопределяет поведение + */ +export class NonSelectableConnection extends MultipointConnection { + //eslint-disable @typescript-eslint/explicit-member-accessibility + override cursor: 'pointer' = 'pointer'; + + // Переопределяем метод для предотвращения выделения при клике + protected override handleEvent(event: Event) { + event.stopPropagation(); + } +} diff --git a/src/components/Graph/TooltipComponent.tsx b/src/components/Graph/TooltipComponent.tsx new file mode 100644 index 0000000000..5303b6d2c1 --- /dev/null +++ b/src/components/Graph/TooltipComponent.tsx @@ -0,0 +1,83 @@ +import React, {useMemo, useState} from 'react'; + +import {Popover, Tab, TabList, TabPanel, TabProvider} from '@gravity-ui/uikit'; + +import type { + TopologyNodeDataStatsItem, + TopologyNodeDataStatsSection, +} from '../../store/reducers/query/types'; +import {cn} from '../../utils/cn'; + +import type {ExtendedTBlock} from './types'; + +const b = cn('ydb-gravity-graph'); +type Props = { + block: ExtendedTBlock; + children: React.ReactNode; +}; + +const getStatsContent = (stat: TopologyNodeDataStatsItem | TopologyNodeDataStatsSection) => { + if ('value' in stat) { + return ( +

+ {stat.name}: + {stat.value} +

+ ); + } + + return ( +
+
{stat.name}:
+ {stat.items?.map(({name, value}: TopologyNodeDataStatsItem) => ( +

+ {name}: + {value} +

+ ))} +
+ ); +}; + +const useTooltipContent = (block: ExtendedTBlock) => { + const firstTab = block?.stats?.[0]?.group || ''; + const [activeTab, setActiveTab] = useState(firstTab); + + return useMemo( + () => ( + + + {block?.stats?.map((item) => ( + + {item.group} + + ))} + + {block?.stats?.map((item) => ( + + {item.stats?.map(getStatsContent)} + + ))} + + ), + [block?.stats, activeTab], + ); +}; + +export const TooltipComponent = ({block, children}: Props) => { + const content = useTooltipContent(block); + + return ( + + {children as React.ReactElement} + + ); +}; diff --git a/src/components/Graph/colorsConfig.ts b/src/components/Graph/colorsConfig.ts new file mode 100644 index 0000000000..7d12516644 --- /dev/null +++ b/src/components/Graph/colorsConfig.ts @@ -0,0 +1,21 @@ +export type AbstractGraphColorsConfig = Partial>>>; + +export const graphColorsConfig = { + // Default @gravity-ui/graph colors + canvas: { + belowLayerBackground: '#0000', + border: '#0000', + dots: 'var(--g-color-line-generic)', + layerBackground: 'var(--g-color-base-background)', + }, + block: { + text: 'var(--g-color-text-primary)', + background: 'var(--g-color-base-float)', + border: 'var(--g-color-line-generic-solid)', + }, + connection: { + background: 'var(--g-color-line-generic-solid)', + hoverBackground: 'var(--g-color-line-generic-solid)', + selectedBackground: 'var(--g-color-line-generic-solid)', + }, +} as const satisfies AbstractGraphColorsConfig; diff --git a/src/components/Graph/i18n/en.json b/src/components/Graph/i18n/en.json new file mode 100644 index 0000000000..18623f7f8c --- /dev/null +++ b/src/components/Graph/i18n/en.json @@ -0,0 +1,3 @@ +{ + "label_tables": "Tables" +} diff --git a/src/components/Graph/i18n/index.ts b/src/components/Graph/i18n/index.ts new file mode 100644 index 0000000000..a74a615c6f --- /dev/null +++ b/src/components/Graph/i18n/index.ts @@ -0,0 +1,7 @@ +import {registerKeysets} from '../../../utils/i18n'; + +import en from './en.json'; + +const COMPONENT = 'ydb-gravity-graph'; + +export default registerKeysets(COMPONENT, {en}); diff --git a/src/components/Graph/treeLayout.ts b/src/components/Graph/treeLayout.ts new file mode 100644 index 0000000000..df78f52aa9 --- /dev/null +++ b/src/components/Graph/treeLayout.ts @@ -0,0 +1,373 @@ +import type {TBlock, TConnection} from '@gravity-ui/graph'; + +import type {ExtendedTBlock, LayoutOptions, TreeNode} from './types'; +import {prepareBlocks, prepareConnections} from './utils'; + +class TreeLayoutEngine { + private blocks: Map; + private connections: TConnection[]; + private options: Required; + private tree: TreeNode | null; + private levels: TreeNode[][]; + + constructor(blocks: any[], connections: TConnection[], options: LayoutOptions = {}) { + this.blocks = new Map(blocks.map((block: any) => [block.id, {...block}])); + this.connections = connections; + + // Layout settings + this.options = { + horizontalSpacing: options.horizontalSpacing || 40, // horizontal distance between blocks + verticalSpacing: options.verticalSpacing || 20, // vertical distance between levels + ...options, + }; + + this.tree = null; + this.levels = []; + } + + // Building a tree structure + buildTree() { + const childrenMap = new Map(); + const parentMap = new Map(); + + for (const block of this.blocks.values()) { + childrenMap.set(block.id, []); + } + + // Setting connections + for (const connection of this.connections) { + const parent = connection.sourceBlockId; + const child = connection.targetBlockId; + + childrenMap.get(parent).push(child); + parentMap.set(child, parent); + } + + // Find root (a node without a parent) + const rootId = Array.from(this.blocks.keys()).find((id) => !parentMap.has(id)); + + if (!rootId) { + throw new Error('Root node not found'); + } + + // Recursively building a tree + const buildNode = (nodeId: string, level = 0): TreeNode => { + const block = this.blocks.get(nodeId); + const children = childrenMap + .get(nodeId) + .map((childId: string) => buildNode(childId, level + 1)); + + return { + id: nodeId, + block: block, + children: children, + level: level, + x: 0, + y: 0, + subtreeWidth: 0, + }; + }; + + this.tree = buildNode(rootId); + return this.tree; + } + + // Grouping nodes by levels + groupByLevels(node: TreeNode | null = this.tree, levels: TreeNode[][] = []): TreeNode[][] { + if (!node) { + return levels; + } + + if (!levels[node.level]) { + levels[node.level] = []; + } + levels[node.level].push(node); + + for (const child of node.children) { + this.groupByLevels(child, levels); + } + + this.levels = levels; + return levels; + } + + // Calculating the width of the subtree for each node + calculateSubtreeWidths(node: TreeNode = this.tree!): number { + if (node.children.length === 0) { + // Leaf node - width is equal to the width of the block + node.subtreeWidth = node.block.width; + } else { + // Recursively calculating the width for children + for (const child of node.children) { + this.calculateSubtreeWidths(child); + } + + // Subtree width = sum of the widths of the children's subtrees + the margins between them + const childrenWidth = node.children.reduce( + (sum: number, child: TreeNode) => sum + child.subtreeWidth, + 0, + ); + const spacingWidth = (node.children.length - 1) * this.options.horizontalSpacing; + const totalChildrenWidth = childrenWidth + spacingWidth; + + // Subtree width = the maximum of the width of the node itself and the total width of the children + node.subtreeWidth = Math.max(node.block.width, totalChildrenWidth); + } + + return node.subtreeWidth; + } + + // Placement of nodes by position + positionNodes() { + if (!this.tree) { + return; + } + + // Calculating the Y coordinates for each level + let currentY = 0; + const levelY: number[] = []; + + for (let level = 0; level < this.levels.length; level++) { + levelY[level] = currentY; + + // We find the maximum height of the blocks at this level + const maxHeight = Math.max( + ...this.levels[level].map((node: TreeNode) => node.block.height), + ); + currentY += maxHeight + this.options.verticalSpacing; + } + + // Recursively placing nodes + const positionNode = (node: TreeNode, leftX: number): void => { + // Setting the Y coordinate + node.y = levelY[node.level]; + + if (node.children.length === 0) { + // The leaf node is placed in the current position. + node.x = leftX; + } else { + // Place children + let childX = leftX; + + // If the node width is greater than the total width of the children, add an indentation. + const childrenWidth = node.children.reduce( + (sum: number, child: TreeNode) => sum + child.subtreeWidth, + 0, + ); + const spacingWidth = (node.children.length - 1) * this.options.horizontalSpacing; + const totalChildrenWidth = childrenWidth + spacingWidth; + + if (node.block.width > totalChildrenWidth) { + const extraSpace = (node.block.width - totalChildrenWidth) / 2; + childX += extraSpace; + } + + // Place each child + for (const child of node.children) { + positionNode(child, childX); + childX += child.subtreeWidth + this.options.horizontalSpacing; + } + + // Center the parent node relative to the children + const firstChild = node.children[0]; + const lastChild = node.children[node.children.length - 1]; + const childrenCenter = (firstChild.x + lastChild.x + lastChild.block.width) / 2; + node.x = childrenCenter - node.block.width / 2; + } + }; + + positionNode(this.tree, 0); + } + + // Normalization of coordinates (so that the minimum coordinates are >= 0) + normalizeCoordinates() { + if (!this.tree) { + return; + } + + const allNodes: TreeNode[] = []; + + const collectNodes = (node: TreeNode) => { + allNodes.push(node); + for (const child of node.children) { + collectNodes(child); + } + }; + + collectNodes(this.tree); + + const minX = Math.min(...allNodes.map((node) => node.x)); + const minY = Math.min(...allNodes.map((node) => node.y)); + + // Shift all coordinates so that the minimum ones are equal to 0 + const offsetX = minX < 0 ? -minX : 0; + const offsetY = minY < 0 ? -minY : 0; + + for (const node of allNodes) { + node.x += offsetX; + node.y += offsetY; + } + } + + // Main method of composition + layout() { + this.buildTree(); + this.groupByLevels(); + this.calculateSubtreeWidths(); + this.positionNodes(); + this.normalizeCoordinates(); + + return this.getLayoutResult(); + } + + // Getting the result of composition + getLayoutResult(): ExtendedTBlock[] { + if (!this.tree) { + return []; + } + + const result: ExtendedTBlock[] = []; + + const collectResults = (node: TreeNode) => { + result.push({ + id: node.id, + x: node.x, + y: node.y, + width: node.block.width, + height: node.block.height, + level: node.level, + ...node.block, + }); + + for (const child of node.children) { + collectResults(child); + } + }; + + collectResults(this.tree); + + return result; + } +} + +// Function for using the algorithm +function calculateTreeLayout(blocks: TBlock[], connections: TConnection[], options = {}) { + const engine = new TreeLayoutEngine(blocks, connections, options); + return engine.layout(); +} + +function calculateTreeEdges(layoutResult: ExtendedTBlock[], connections: TConnection[]) { + // Create a position map for convenience of search + const positionMap = new Map(layoutResult.map((item) => [item.id, item])); + + // Group connections by parent block + const connectionsByParent = new Map(); + + for (const connection of connections) { + const parentId = connection.sourceBlockId; + if (!connectionsByParent.has(parentId)) { + connectionsByParent.set(parentId, []); + } + connectionsByParent.get(parentId).push(connection); + } + + const connectionPaths: { + connectionId: string | undefined; + sourceBlockId: string | number; + targetBlockId: string | number; + points: {x: number; y: number}[]; + }[] = []; + + // For each parent block, we calculate the paths to the children + for (const [parentId, parentConnections] of connectionsByParent) { + const parent = positionMap.get(parentId); + if (!parent) { + continue; + } + + // Coordinates of the initial point (center of the lower part of the parent) + const startX = parent.x + parent.width / 2; + const startY = parent.y + parent.height; + + if (parentConnections.length === 1) { + // One child block - simple straight line + const connection = parentConnections[0]; + const child = positionMap.get(connection.targetBlockId); + + if (child) { + const endX = child.x + child.width / 2; + const endY = child.y; + + connectionPaths.push({ + connectionId: connection.id, + sourceBlockId: connection.sourceBlockId, + targetBlockId: connection.targetBlockId, + points: [ + {x: startX, y: startY}, + {x: endX, y: endY}, + ], + }); + } + } else { + // Several child blocks - broken lines + + // We find the vertical distance between the parent and the nearest child + const children = parentConnections + .map((conn: TConnection) => positionMap.get(conn.targetBlockId)) + .filter( + (child: ExtendedTBlock | undefined): child is ExtendedTBlock => + child !== undefined, + ); + + if (children.length === 0) { + continue; + } + + // We find the minimum distance to the children by Y + const minChildY = Math.min(...children.map((child: ExtendedTBlock) => child.y)); + + // The point of branching - in the middle between the parent and the children + const branchY = startY + (minChildY - startY) / 2; + + // For each child block, we create a broken line + for (const connection of parentConnections) { + const child = positionMap.get(connection.targetBlockId); + if (!child) { + continue; + } + + const endX = child.x + child.width / 2; + const endY = child.y; + + const points = [ + {x: startX, y: startY}, // Start - center of the lower part of the parent + {x: startX, y: branchY}, // Vertically down to the point of branching + {x: endX, y: branchY}, // Horizontally to the center of the child block + {x: endX, y: endY}, // Vertically down to the center of the upper part of the child block + ]; + + connectionPaths.push({ + connectionId: connection.id, + sourceBlockId: connection.sourceBlockId, + targetBlockId: connection.targetBlockId, + points: points, + }); + } + } + } + + return connectionPaths; +} + +onmessage = function (e) { + const {nodes, links} = e.data; + const blocks = prepareBlocks(nodes); + const connections = prepareConnections(links); + const layout = calculateTreeLayout(blocks, connections); + const edges = calculateTreeEdges(layout, connections); + + postMessage({ + layout, + edges, + }); +}; diff --git a/src/components/Graph/types.ts b/src/components/Graph/types.ts new file mode 100644 index 0000000000..edd40a6357 --- /dev/null +++ b/src/components/Graph/types.ts @@ -0,0 +1,37 @@ +import type {TBlock} from '@gravity-ui/graph'; + +import type {GraphNode, Link, TopologyNodeDataStats} from '../../store/reducers/query/types'; + +// Extended block interface with additional properties +export interface ExtendedTBlock extends TBlock { + stats?: TopologyNodeDataStats[]; + operators?: string[]; + tables?: string[]; +} +export type LinkType = 'arrow' | 'line'; +export interface Data { + links: Link[]; + nodes: GraphNode[]; +} + +// TreeLayout related types +export interface LayoutOptions { + horizontalSpacing?: number; + verticalSpacing?: number; +} + +export interface TreeNode { + id: string; + level: number; + block: any; + children: TreeNode[]; + subtreeWidth: number; + x: number; + y: number; +} + +export interface EdgeResult { + points: Array<{x: number; y: number}>; + sourceBlockId: string; + targetBlockId: string; +} diff --git a/src/components/Graph/utils.ts b/src/components/Graph/utils.ts new file mode 100644 index 0000000000..c96fae610d --- /dev/null +++ b/src/components/Graph/utils.ts @@ -0,0 +1,111 @@ +import type {TBlock, TConnection} from '@gravity-ui/graph'; + +import type {ExplainPlanNodeData} from '../../store/reducers/query/types'; + +import type {AbstractGraphColorsConfig} from './colorsConfig'; +import type {Data} from './types'; + +const BLOCK_TOP_PADDING = 8; +const BLOCK_LINE_HEIGHT = 16; +const BORDER_HEIGHT = 2; + +const getBlockSize = (block: ExplainPlanNodeData) => { + const ONE_LINE_HEIGHT = BLOCK_TOP_PADDING * 2 + BLOCK_LINE_HEIGHT + BORDER_HEIGHT; + const operatorsLength = block.operators?.length ?? 1; + const tablesLength = block.tables?.length ?? 0; + + switch (block.type) { + case 'query': + return { + width: 40, + height: 40, + }; + case 'result': + return { + width: 112, + height: ONE_LINE_HEIGHT, + }; + case 'stage': + return { + width: 248, + height: + BORDER_HEIGHT + + BLOCK_TOP_PADDING * 2 + + (operatorsLength + tablesLength) * BLOCK_LINE_HEIGHT, + }; + case 'connection': + return { + width: 122, + height: ONE_LINE_HEIGHT, + }; + case 'materialize': + return { + width: 190, + height: ONE_LINE_HEIGHT, + }; + default: + return { + width: 100, + height: ONE_LINE_HEIGHT, + }; + } +}; + +export const prepareBlocks = (nodes: Data['nodes']): TBlock[] => { + return nodes.map(({data: {id, name, type, ...rest}, data}) => ({ + id: String(id), + is: type, + name, + ...getBlockSize(data), + ...rest, + })); +}; + +export const prepareConnections = (links: Data['links']): TConnection[] => { + return links.map(({from, to}) => ({ + id: `${from}:${to}`, + sourceBlockId: from, + targetBlockId: to, + })); +}; + +function calculateCurrentCustomPropertyValue( + colorSettings: Partial>, + computedStyle: CSSStyleDeclaration, +): Partial> { + const result: Partial> = {}; + + for (const nestedKey in colorSettings) { + if (Object.prototype.hasOwnProperty.call(colorSettings, nestedKey)) { + const value = colorSettings[nestedKey]; + + if (value !== undefined) { + result[nestedKey] = value?.startsWith('var(') + ? computedStyle.getPropertyValue(value.substring(4, value.length - 1)).trim() + : value; + } + } + } + + return result; +} + +export function parseCustomPropertyValue( + colors: T, + block: HTMLElement = globalThis.document.body, +): T { + const parsed: AbstractGraphColorsConfig = {}; + const computedStyle = window.getComputedStyle(block); + + for (const topKey in colors) { + if (Object.prototype.hasOwnProperty.call(colors, topKey)) { + const nestedObj = colors[topKey]; + + if (nestedObj) { + parsed[topKey] = calculateCurrentCustomPropertyValue(nestedObj, computedStyle); + } + } + } + + return parsed as T; +} diff --git a/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss b/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss index 565424c0e3..5586c013fe 100644 --- a/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss +++ b/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.scss @@ -1,5 +1,7 @@ .ydb-query-explain-graph { &__canvas-container { + position: relative; + overflow-y: auto; width: 100%; diff --git a/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.tsx b/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.tsx index a4d7750e52..5833a602f8 100644 --- a/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.tsx +++ b/src/containers/Tenant/Query/QueryResult/components/Graph/Graph.tsx @@ -1,8 +1,6 @@ import React from 'react'; -import type {Data} from '@gravity-ui/paranoid'; - -import {YDBGraph} from '../../../../../../components/Graph/Graph'; +import {GravityGraph} from '../../../../../../components/Graph/GravityGraph'; import type {PreparedPlan} from '../../../../../../store/reducers/query/types'; import {cn} from '../../../../../../utils/cn'; import i18n from '../../i18n'; @@ -17,14 +15,14 @@ interface GraphProps { theme?: string; } -function isValidGraphData(data: Partial): data is Data { +function isValidGraphData(data: Partial) { return Boolean(data.links && data.nodes && data.nodes.length); } export function Graph({explain = {}, theme}: GraphProps) { const {links, nodes} = explain; - const data = React.useMemo(() => ({links, nodes}), [links, nodes]); + const data = React.useMemo(() => ({links: links || [], nodes: nodes || []}), [links, nodes]); if (!isValidGraphData(data)) { return ; @@ -32,7 +30,7 @@ export function Graph({explain = {}, theme}: GraphProps) { return (
- +
); } diff --git a/src/store/reducers/query/preparePlanData.ts b/src/store/reducers/query/preparePlanData.ts index 6a63777134..6a5238163a 100644 --- a/src/store/reducers/query/preparePlanData.ts +++ b/src/store/reducers/query/preparePlanData.ts @@ -1,10 +1,8 @@ -import type {ExplainPlanNodeData, GraphNode, Link} from '@gravity-ui/paranoid'; - import type {QueryPlan, ScriptPlan, TKqpStatsQuery} from '../../../types/api/query'; import {preparePlan, prepareSimplifiedPlan} from '../../../utils/prepareQueryExplain'; import {parseQueryExplainPlan} from '../../../utils/query'; -import type {PreparedQueryData} from './types'; +import type {ExplainPlanNodeData, GraphNode, Link, PreparedQueryData} from './types'; const explainVersions = { v2: '0.2', diff --git a/src/store/reducers/query/types.ts b/src/store/reducers/query/types.ts index 9d753b5210..a0d8995afe 100644 --- a/src/store/reducers/query/types.ts +++ b/src/store/reducers/query/types.ts @@ -1,5 +1,3 @@ -import type {ExplainPlanNodeData, GraphNode, Link} from '@gravity-ui/paranoid'; - import type { PlanTable, QueryPlan, @@ -17,6 +15,48 @@ export interface QueryInHistory { durationUs?: string | number; } +export interface Link { + from: string; + to: string; +} + +export interface Metric { + name: string; + value: string; + theme?: 'warning' | 'danger'; +} + +export interface GraphNode { + name: string; + status?: string; + meta?: string; + group?: string; + data?: TData; + metrics?: Metric[]; +} + +export interface ExplainPlanNodeData { + id?: number; + type: 'query' | 'result' | 'stage' | 'connection' | 'materialize'; + name?: string; + operators?: string[]; + tables?: string[]; + cte?: string; + stats?: TopologyNodeDataStats[]; +} +export interface TopologyNodeDataStatsItem { + name: string; + value: string | number; +} +export interface TopologyNodeDataStatsSection { + name: string; + items: TopologyNodeDataStatsItem[]; +} +export interface TopologyNodeDataStats { + group: string; + stats: TopologyNodeDataStatsSection[] | TopologyNodeDataStatsItem[]; +} + export interface PreparedPlan { links?: Link[]; nodes?: GraphNode[]; diff --git a/src/utils/prepareQueryExplain.ts b/src/utils/prepareQueryExplain.ts index b4198b03ac..c4100c642d 100644 --- a/src/utils/prepareQueryExplain.ts +++ b/src/utils/prepareQueryExplain.ts @@ -2,12 +2,11 @@ import type { ExplainPlanNodeData, GraphNode, Link, + SimplifiedPlanItem, TopologyNodeDataStats, TopologyNodeDataStatsItem, TopologyNodeDataStatsSection, -} from '@gravity-ui/paranoid'; - -import type {SimplifiedPlanItem} from '../store/reducers/query/types'; +} from '../store/reducers/query/types'; import type {PlanNode, SimplifiedNode} from '../types/api/query'; const CONNECTION_NODE_META_FIELDS = new Set(['PlanNodeId', 'PlanNodeType', 'Node Type', 'Plans']);