From 2f5998a7c3b6f5480e7397ea0918d27d6859fedb Mon Sep 17 00:00:00 2001 From: theau Date: Mon, 23 Jun 2025 22:59:19 +0200 Subject: [PATCH 1/4] feat: pie chart for Job Monitor --- package-lock.json | 2226 +++++++++++------ .../diracx-web-components/.storybook/main.ts | 2 +- .../.storybook/preview.tsx | 1 - .../{babel.config.cjs => babel.config.js} | 2 +- packages/diracx-web-components/jest.config.js | 15 +- packages/diracx-web-components/package.json | 6 +- .../components/JobMonitor/JobDataTable.tsx | 52 +- .../src/components/JobMonitor/JobMonitor.tsx | 155 +- .../components/JobMonitor/JobSearchBar.tsx | 15 +- .../src/components/JobMonitor/JobSunburst.tsx | 285 +++ .../components/JobMonitor/jobDataService.ts | 144 +- .../components/shared/ChartDisplayLayout.tsx | 85 + .../src/components/shared/ColumnSelector.tsx | 324 +++ .../src/components/shared/DataTable.tsx | 16 +- .../components/shared/SearchBar/SearchBar.tsx | 42 +- .../shared/SearchBar/SearchField.tsx | 1 + .../src/components/shared/SearchBar/index.ts | 1 - .../components/shared/Sunburst/Sunburst.tsx | 363 +++ .../src/components/shared/Sunburst/Utils.tsx | 123 + .../src/components/shared/Sunburst/index.ts | 1 + .../src/components/shared/index.ts | 4 +- .../src/contexts/DiracXWebProviders.tsx | 2 +- .../src/contexts/index.ts | 7 +- .../diracx-web-components/src/global.d.ts | 2 +- .../src/types/SunburstData.ts | 3 + .../src/types/SunburstNode.ts | 8 + .../src/types/SunburstTree.ts | 8 + .../diracx-web-components/src/types/index.ts | 2 + .../stories/ChartDisplayLayout.stories.tsx | 103 + .../stories/Dashboard.stories.tsx | 2 + .../stories/DataTable.stories.tsx | 3 - .../stories/JobMonitor.stories.tsx | 106 +- .../stories/LoginForm.stories.tsx | 2 +- .../stories/Sunburst.stories.tsx | 166 ++ .../stories/mocks/contexts.mock.tsx | 42 + ...ervice.mock.ts => jobDataService.mock.tsx} | 58 +- .../stories/mocks/style.mock.ts | 1 + .../test/ChartDisplayLayout.test.tsx | 27 + .../test/JobMonitor.test.tsx | 8 +- .../test/Sunburst.test.tsx | 175 ++ packages/diracx-web/.gitignore | 1 + packages/diracx-web/test/e2e/jobMonitor.cy.ts | 107 +- packages/diracx-web/test/e2e/loginOut.cy.ts | 27 + .../components/OwnerMonitor/OwnerMonitor.tsx | 1 - 44 files changed, 3654 insertions(+), 1070 deletions(-) rename packages/diracx-web-components/{babel.config.cjs => babel.config.js} (87%) create mode 100644 packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx create mode 100644 packages/diracx-web-components/src/components/shared/ChartDisplayLayout.tsx create mode 100644 packages/diracx-web-components/src/components/shared/ColumnSelector.tsx create mode 100644 packages/diracx-web-components/src/components/shared/Sunburst/Sunburst.tsx create mode 100644 packages/diracx-web-components/src/components/shared/Sunburst/Utils.tsx create mode 100644 packages/diracx-web-components/src/components/shared/Sunburst/index.ts create mode 100644 packages/diracx-web-components/src/types/SunburstData.ts create mode 100644 packages/diracx-web-components/src/types/SunburstNode.ts create mode 100644 packages/diracx-web-components/src/types/SunburstTree.ts create mode 100644 packages/diracx-web-components/stories/ChartDisplayLayout.stories.tsx create mode 100644 packages/diracx-web-components/stories/Sunburst.stories.tsx create mode 100644 packages/diracx-web-components/stories/mocks/contexts.mock.tsx rename packages/diracx-web-components/stories/mocks/{jobDataService.mock.ts => jobDataService.mock.tsx} (82%) create mode 100644 packages/diracx-web-components/stories/mocks/style.mock.ts create mode 100644 packages/diracx-web-components/test/ChartDisplayLayout.test.tsx create mode 100644 packages/diracx-web-components/test/Sunburst.test.tsx diff --git a/package-lock.json b/package-lock.json index 4224a408..e7916c46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -174,23 +174,23 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", "dev": true, "license": "MIT", "engines": { @@ -198,22 +198,22 @@ } }, "node_modules/@babel/core": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.10", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.10", - "@babel/types": "^7.26.10", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -239,15 +239,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -255,27 +255,27 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", - "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -295,18 +295,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", - "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.27.0", + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", "semver": "^6.3.1" }, "engines": { @@ -327,13 +327,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.0.tgz", - "integrity": "sha512-fO8l08T76v48BhpNRW/nQ0MxfnSdoSKUJBMjubOAYffsVuGG5qOfMq7N6Es7UJvi7Y8goXXo07EfcHZXDPuELQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-annotate-as-pure": "^7.27.1", "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, @@ -355,59 +355,68 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", - "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -417,22 +426,22 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, "license": "MIT", "engines": { @@ -440,15 +449,15 @@ } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -458,15 +467,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -476,41 +485,41 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -518,41 +527,41 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", + "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", - "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.0" + "@babel/types": "^7.28.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -562,14 +571,14 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", - "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -579,13 +588,13 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", - "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -595,13 +604,13 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -611,15 +620,15 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -629,14 +638,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", + "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -727,13 +736,13 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", - "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -743,13 +752,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -944,13 +953,13 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -960,15 +969,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", - "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.26.8" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -978,15 +987,15 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", - "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -996,13 +1005,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", - "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1012,13 +1021,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.0.tgz", - "integrity": "sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz", + "integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1028,14 +1037,14 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1045,14 +1054,14 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", - "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", + "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1062,18 +1071,18 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz", + "integrity": "sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "globals": "^11.1.0" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -1083,14 +1092,14 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1100,13 +1109,14 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", + "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -1116,14 +1126,14 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1133,13 +1143,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1149,14 +1159,14 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1166,13 +1176,30 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -1182,13 +1209,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", - "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1198,13 +1225,13 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1214,14 +1241,14 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz", - "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1231,15 +1258,15 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1249,13 +1276,13 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1265,13 +1292,13 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1281,13 +1308,13 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1297,13 +1324,13 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1313,14 +1340,14 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1330,14 +1357,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", - "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1347,16 +1374,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1366,14 +1393,14 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1383,14 +1410,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1400,13 +1427,13 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1416,13 +1443,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.26.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", - "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1432,13 +1459,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1448,15 +1475,17 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz", + "integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -1466,14 +1495,14 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1483,13 +1512,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1499,14 +1528,14 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1516,13 +1545,13 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1532,14 +1561,14 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1549,15 +1578,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1567,13 +1596,13 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1652,14 +1681,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.0.tgz", - "integrity": "sha512-LX/vCajUJQDqE7Aum/ELUMZAY19+cDpghxrnyt5I1tV6X5PyC86AOoWXWFYFeIvauyeSA6/ktn4tQVn/3ZifsA==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.0.tgz", + "integrity": "sha512-LOAozRVbqxEVjSKfhGnuLoE4Kz4Oc5UJzuvFUhSsQzdCdaAQu06mG8zDv2GFSerM62nImUZ7K92vxnQcLSDlCQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "regenerator-transform": "^0.15.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1669,14 +1697,14 @@ } }, "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", - "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1686,13 +1714,13 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1733,13 +1761,13 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1749,14 +1777,14 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1766,13 +1794,13 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1782,13 +1810,13 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz", - "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1798,13 +1826,13 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.0.tgz", - "integrity": "sha512-+LLkxA9rKJpNoGsbLnAgOCdESl73vwYn+V6b+5wHbrE7OGKVDPHIQvbFSzqE6rwqaCw2RE+zdJrlLkcf8YOA0w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1834,13 +1862,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1850,14 +1878,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1867,14 +1895,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1884,14 +1912,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1901,80 +1929,81 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", - "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.0.tgz", + "integrity": "sha512-VmaxeGOwuDqzLl5JUkIRM1X2Qu2uKGxHEQWh+cvvbl7JuJRgKGJSfsEF/bUaxFhJl/XAyxBe7q7qSuTbKFuCyg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", + "@babel/compat-data": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.26.0", - "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.25.9", - "@babel/plugin-transform-async-generator-functions": "^7.26.8", - "@babel/plugin-transform-async-to-generator": "^7.25.9", - "@babel/plugin-transform-block-scoped-functions": "^7.26.5", - "@babel/plugin-transform-block-scoping": "^7.25.9", - "@babel/plugin-transform-class-properties": "^7.25.9", - "@babel/plugin-transform-class-static-block": "^7.26.0", - "@babel/plugin-transform-classes": "^7.25.9", - "@babel/plugin-transform-computed-properties": "^7.25.9", - "@babel/plugin-transform-destructuring": "^7.25.9", - "@babel/plugin-transform-dotall-regex": "^7.25.9", - "@babel/plugin-transform-duplicate-keys": "^7.25.9", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-dynamic-import": "^7.25.9", - "@babel/plugin-transform-exponentiation-operator": "^7.26.3", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-for-of": "^7.26.9", - "@babel/plugin-transform-function-name": "^7.25.9", - "@babel/plugin-transform-json-strings": "^7.25.9", - "@babel/plugin-transform-literals": "^7.25.9", - "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", - "@babel/plugin-transform-member-expression-literals": "^7.25.9", - "@babel/plugin-transform-modules-amd": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", - "@babel/plugin-transform-modules-systemjs": "^7.25.9", - "@babel/plugin-transform-modules-umd": "^7.25.9", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-new-target": "^7.25.9", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", - "@babel/plugin-transform-numeric-separator": "^7.25.9", - "@babel/plugin-transform-object-rest-spread": "^7.25.9", - "@babel/plugin-transform-object-super": "^7.25.9", - "@babel/plugin-transform-optional-catch-binding": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9", - "@babel/plugin-transform-private-methods": "^7.25.9", - "@babel/plugin-transform-private-property-in-object": "^7.25.9", - "@babel/plugin-transform-property-literals": "^7.25.9", - "@babel/plugin-transform-regenerator": "^7.25.9", - "@babel/plugin-transform-regexp-modifiers": "^7.26.0", - "@babel/plugin-transform-reserved-words": "^7.25.9", - "@babel/plugin-transform-shorthand-properties": "^7.25.9", - "@babel/plugin-transform-spread": "^7.25.9", - "@babel/plugin-transform-sticky-regex": "^7.25.9", - "@babel/plugin-transform-template-literals": "^7.26.8", - "@babel/plugin-transform-typeof-symbol": "^7.26.7", - "@babel/plugin-transform-unicode-escapes": "^7.25.9", - "@babel/plugin-transform-unicode-property-regex": "^7.25.9", - "@babel/plugin-transform-unicode-regex": "^7.25.9", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-classes": "^7.28.0", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.0", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.40.0", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", "semver": "^6.3.1" }, "engines": { @@ -1984,6 +2013,20 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@babel/preset-env/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -2063,45 +2106,45 @@ } }, "node_modules/@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz", + "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4501,17 +4544,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -4523,15 +4562,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", @@ -4550,9 +4580,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -7252,84 +7282,343 @@ "@types/node": "*" } }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "dev": true, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", "license": "MIT", "dependencies": { - "@types/ms": "*" + "@types/d3-selection": "*" } }, - "node_modules/@types/doctrine": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", - "integrity": "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", "license": "MIT", "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "@types/d3-selection": "*" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", "license": "MIT", "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "@types/d3-array": "*", + "@types/geojson": "*" } }, - "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", "license": "MIT" }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", "license": "MIT", "dependencies": { - "@types/node": "*" + "@types/d3-selection": "*" } }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "dev": true, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", "license": "MIT" }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", "license": "MIT" }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "*" + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/doctrine": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", + "integrity": "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { @@ -9223,14 +9512,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", - "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.4", + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { @@ -9262,13 +9551,13 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", - "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.4" + "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -9609,9 +9898,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "funding": [ { "type": "opencollective", @@ -9628,10 +9917,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -9847,9 +10136,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001716", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz", - "integrity": "sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "funding": [ { "type": "opencollective", @@ -10403,13 +10692,13 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz", - "integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", + "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.24.4" + "browserslist": "^4.25.0" }, "funding": { "type": "opencollective", @@ -10686,202 +10975,612 @@ "domutils": "^2.8.0", "nth-check": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/cypress": { + "version": "14.3.2", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.3.2.tgz", + "integrity": "sha512-n+yGD2ZFFKgy7I3YtVpZ7BcFYrrDMcKj713eOZdtxPttpBjCyw/R8dLlFSsJPouneGN7A/HOSRyPJ5+3/gKDoA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@cypress/request": "^3.0.8", + "@cypress/xvfb": "^1.2.4", + "@types/sinonjs__fake-timers": "8.1.1", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "buffer": "^5.7.1", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "ci-info": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-table3": "~0.6.5", + "commander": "^6.2.1", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.4", + "enquirer": "^2.3.6", + "eventemitter2": "6.4.7", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.8", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "process": "^0.11.10", + "proxy-from-env": "1.0.0", + "request-progress": "^3.0.0", + "semver": "^7.7.1", + "supports-color": "^8.1.1", + "tmp": "~0.2.3", + "tree-kill": "1.2.2", + "untildify": "^4.0.0", + "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + } + }, + "node_modules/cypress/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cypress/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cypress/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/node_modules/eventemitter2": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cypress/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" } }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", "engines": { - "node": ">= 6" + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "engines": { + "node": ">=12" } }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true, - "license": "MIT" + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", "engines": { - "node": ">=4" + "node": ">=12" } }, - "node_modules/cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true, - "license": "MIT" + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "license": "MIT", + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", "dependencies": { - "cssom": "~0.3.6" + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true, - "license": "MIT" + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/cypress": { - "version": "14.3.2", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.3.2.tgz", - "integrity": "sha512-n+yGD2ZFFKgy7I3YtVpZ7BcFYrrDMcKj713eOZdtxPttpBjCyw/R8dLlFSsJPouneGN7A/HOSRyPJ5+3/gKDoA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", "dependencies": { - "@cypress/request": "^3.0.8", - "@cypress/xvfb": "^1.2.4", - "@types/sinonjs__fake-timers": "8.1.1", - "@types/sizzle": "^2.3.2", - "arch": "^2.2.0", - "blob-util": "^2.0.2", - "bluebird": "^3.7.2", - "buffer": "^5.7.1", - "cachedir": "^2.3.0", - "chalk": "^4.1.0", - "check-more-types": "^2.24.0", - "ci-info": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.5", - "commander": "^6.2.1", - "common-tags": "^1.8.0", - "dayjs": "^1.10.4", - "debug": "^4.3.4", - "enquirer": "^2.3.6", - "eventemitter2": "6.4.7", - "execa": "4.1.0", - "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", - "fs-extra": "^9.1.0", - "getos": "^3.2.1", - "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", - "log-symbols": "^4.0.0", - "minimist": "^1.2.8", - "ospath": "^1.2.2", - "pretty-bytes": "^5.6.0", - "process": "^0.11.10", - "proxy-from-env": "1.0.0", - "request-progress": "^3.0.0", - "semver": "^7.7.1", - "supports-color": "^8.1.1", - "tmp": "~0.2.3", - "tree-kill": "1.2.2", - "untildify": "^4.0.0", - "yauzl": "^2.10.0" - }, - "bin": { - "cypress": "bin/cypress" + "d3-path": "^3.1.0" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": ">=12" } }, - "node_modules/cypress/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", "dependencies": { - "color-convert": "^2.0.1" + "d3-array": "2 - 3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=12" } }, - "node_modules/cypress/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "d3-time": "1 - 3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=12" } }, - "node_modules/cypress/node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" } }, - "node_modules/cypress/node_modules/eventemitter2": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", - "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cypress/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/damerau-levenshtein": { @@ -10993,9 +11692,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -11110,6 +11809,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -11416,9 +12124,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.145", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.145.tgz", - "integrity": "sha512-pZ5EcTWRq/055MvSBgoFEyKf2i4apwfoqJbK/ak2jnFq8oHjZ+vzc3AhRcz37Xn+ZJfL58R666FLJx0YOK9yTw==", + "version": "1.5.179", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz", + "integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==", "license": "ISC" }, "node_modules/elliptic": { @@ -13635,15 +14343,6 @@ "node": ">=10" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -14060,7 +14759,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -14269,6 +14967,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", @@ -21275,16 +21982,6 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "license": "MIT" }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, "node_modules/regex-parser": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.1.tgz", @@ -21691,6 +22388,12 @@ "inherits": "^2.0.1" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.40.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz", @@ -21755,6 +22458,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -21845,7 +22554,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/sass-loader": { @@ -24932,9 +25640,12 @@ "@mui/utils": "^6.1.6", "@mui/x-date-pickers": "^7.28.3", "@tanstack/react-table": "^8.20.5", + "@types/d3": "^7.4.3", "@types/node": "^20.17.6", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", + "d3": "^7.9.0", + "d3-hierarchy": "^3.1.2", "dayjs": "^1.11.13", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -24942,7 +25653,8 @@ "swr": "^2.2.5" }, "devDependencies": { - "@babel/preset-env": "^7.26.9", + "@babel/core": "^7.28.0", + "@babel/preset-env": "^7.28.0", "@babel/preset-react": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "@chromatic-com/storybook": "^3.2.4", diff --git a/packages/diracx-web-components/.storybook/main.ts b/packages/diracx-web-components/.storybook/main.ts index 91784996..9ab99349 100644 --- a/packages/diracx-web-components/.storybook/main.ts +++ b/packages/diracx-web-components/.storybook/main.ts @@ -43,7 +43,7 @@ const config: StorybookConfig = { "../stories/mocks/metadata.mock.tsx", ), "./jobDataService": require.resolve( - "../stories/mocks/jobDataService.mock.ts", + "../stories/mocks/jobDataService.mock.tsx", ), }; return config; diff --git a/packages/diracx-web-components/.storybook/preview.tsx b/packages/diracx-web-components/.storybook/preview.tsx index 208bba51..0f8086fc 100644 --- a/packages/diracx-web-components/.storybook/preview.tsx +++ b/packages/diracx-web-components/.storybook/preview.tsx @@ -1,5 +1,4 @@ import type { Preview } from "@storybook/react"; -import React from "react"; import { CssBaseline } from "@mui/material"; import { ThemeProvider } from "../src/contexts/ThemeProvider"; diff --git a/packages/diracx-web-components/babel.config.cjs b/packages/diracx-web-components/babel.config.js similarity index 87% rename from packages/diracx-web-components/babel.config.cjs rename to packages/diracx-web-components/babel.config.js index 85db31a9..753d9125 100644 --- a/packages/diracx-web-components/babel.config.cjs +++ b/packages/diracx-web-components/babel.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { presets: [ "@babel/preset-env", ["@babel/preset-react", { runtime: "automatic" }], diff --git a/packages/diracx-web-components/jest.config.js b/packages/diracx-web-components/jest.config.js index 313a7a3d..2f219d67 100644 --- a/packages/diracx-web-components/jest.config.js +++ b/packages/diracx-web-components/jest.config.js @@ -13,7 +13,20 @@ const config = { moduleNameMapper: { "^@axa-fr/react-oidc$": "/stories/mocks/react-oidc.mock.tsx", "^../../hooks/metadata$": "/stories/mocks/metadata.mock.tsx", - "^./jobDataService$": "/stories/mocks/jobDataService.mock.ts", + "^./jobDataService$": "/stories/mocks/jobDataService.mock.tsx", + "\\.css$": "/stories/mocks/style.mock.ts", + }, + + // To support ESM modules in Jest + transformIgnorePatterns: [ + "/node_modules/(?!d3|d3-[^/]+|internmap|delaunator|robust-predicates)", + ], + + // Tell Jest how to transform files + // Use ts-jest for TypeScript files and babel-jest for JavaScript files + transform: { + "^.+\\.(ts|tsx)$": "ts-jest", + "^.+\\.(js|jsx)$": "babel-jest", }, }; diff --git a/packages/diracx-web-components/package.json b/packages/diracx-web-components/package.json index 1e0750c9..37752511 100644 --- a/packages/diracx-web-components/package.json +++ b/packages/diracx-web-components/package.json @@ -31,9 +31,12 @@ "@mui/utils": "^6.1.6", "@mui/x-date-pickers": "^7.28.3", "@tanstack/react-table": "^8.20.5", + "@types/d3": "^7.4.3", "@types/node": "^20.17.6", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", + "d3": "^7.9.0", + "d3-hierarchy": "^3.1.2", "dayjs": "^1.11.13", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -41,7 +44,8 @@ "swr": "^2.2.5" }, "devDependencies": { - "@babel/preset-env": "^7.26.9", + "@babel/core": "^7.28.0", + "@babel/preset-env": "^7.28.0", "@babel/preset-react": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "@chromatic-com/storybook": "^3.2.4", diff --git a/packages/diracx-web-components/src/components/JobMonitor/JobDataTable.tsx b/packages/diracx-web-components/src/components/JobMonitor/JobDataTable.tsx index 991d9b11..70af70d2 100644 --- a/packages/diracx-web-components/src/components/JobMonitor/JobDataTable.tsx +++ b/packages/diracx-web-components/src/components/JobMonitor/JobDataTable.tsx @@ -32,7 +32,6 @@ import { deleteJobs, getJobHistory, killJobs, - refreshJobs, rescheduleJobs, useJobs, } from "./jobDataService"; @@ -109,11 +108,15 @@ export function JobDataTable({ // State for job history const [isHistoryDialogOpen, setIsHistoryDialogOpen] = useState(false); const [jobHistoryData, setJobHistoryData] = useState([]); - /** * Fetches the jobs from the /api/jobs/search endpoint */ - const { data, error, isLoading, isValidating } = useJobs( + const { + data: results, + headers: dataHeader, + error: dataError, + isLoading, + } = useJobs( diracxUrl, accessToken, searchBody, @@ -121,9 +124,6 @@ export function JobDataTable({ pagination.pageSize, ); - const dataHeader = data?.headers; - const results = useMemo(() => data?.data || [], [data?.data]); - // Parse the headers to get the first item, last item and number of items const contentRange = dataHeader?.get("content-range"); let totalJobs = 0; @@ -148,13 +148,10 @@ export function JobDataTable({ const selectedIds = Object.keys(rowSelection).map(Number); await deleteJobs(diracxUrl, selectedIds, accessToken, accessTokenPayload); setBackdropOpen(false); - refreshJobs( - diracxUrl, - accessToken, - searchBody, - pagination.pageIndex, - pagination.pageSize, - ); + // Refresh the data manually + setSearchBody((prev) => { + return { ...prev }; + }); clearSelected(); setSnackbarInfo({ open: true, @@ -206,13 +203,12 @@ export function JobDataTable({ ); setBackdropOpen(false); - refreshJobs( - diracxUrl, - accessToken, - searchBody, - pagination.pageIndex, - pagination.pageSize, - ); + + // Refresh the data manually + setSearchBody((prev) => { + return { ...prev }; + }); + clearSelected(); // Handle Snackbar Messaging if (successfulJobs.length > 0 && failedJobs.length > 0) { @@ -278,13 +274,10 @@ export function JobDataTable({ ); setBackdropOpen(false); - refreshJobs( - diracxUrl, - accessToken, - searchBody, - pagination.pageIndex, - pagination.pageSize, - ); + // Refresh the data manually + setSearchBody((prev) => { + return { ...prev }; + }); clearSelected(); // Handle Snackbar Messaging if (successfulJobs.length > 0 && failedJobs.length > 0) { @@ -412,7 +405,7 @@ export function JobDataTable({ * Table instance */ const table = useReactTable({ - data: results, + data: results || [], columns, state: { columnVisibility, @@ -450,9 +443,8 @@ export function JobDataTable({ totalRows={totalJobs} searchBody={searchBody} setSearchBody={setSearchBody} - error={error} + error={dataError} isLoading={isLoading} - isValidating={isValidating} toolbarComponents={toolbarComponents} menuItems={menuItems} /> diff --git a/packages/diracx-web-components/src/components/JobMonitor/JobMonitor.tsx b/packages/diracx-web-components/src/components/JobMonitor/JobMonitor.tsx index d00dde3c..b2af10c8 100644 --- a/packages/diracx-web-components/src/components/JobMonitor/JobMonitor.tsx +++ b/packages/diracx-web-components/src/components/JobMonitor/JobMonitor.tsx @@ -13,7 +13,18 @@ import { lime, amber, } from "@mui/material/colors"; -import { lighten, darken, useTheme, Box } from "@mui/material"; + +import { + lighten, + darken, + useTheme, + Box, + ToggleButtonGroup, + ToggleButton, +} from "@mui/material"; + +import { TableChart, DonutSmall } from "@mui/icons-material"; + import { createColumnHelper, ColumnPinningState, @@ -28,6 +39,7 @@ import { Filter } from "../../types/Filter"; import { Job, SearchBody, CategoryType } from "../../types"; import { JobDataTable } from "./JobDataTable"; import { JobSearchBar } from "./JobSearchBar"; +import { JobSunburst } from "./JobSunburst"; /** * Build the Job Monitor application @@ -96,6 +108,8 @@ export default function JobMonitor() { }, ); + const [chartType, setChartType] = useState("table"); + // Save the state of the app in local storage useEffect(() => { const state = { @@ -122,23 +136,6 @@ export default function JobMonitor() { pagination, ]); - // Handle the application of filters - const handleApplyFilters = () => { - setSearchBody((prev) => ({ - ...prev, - search: filters.map(({ parameter, operator, value, values }) => ({ - parameter: fromHumanReadableText(parameter, columns), - operator, - value, - values, - })), - })); - setPagination((prevState) => ({ - ...prevState, - pageIndex: 0, - })); - }; - // Status colors const statusColors: Record = useMemo( () => ({ @@ -197,7 +194,7 @@ export default function JobMonitor() { columnHelper.accessor("JobID", { id: "JobID", header: "ID", - meta: { type: CategoryType.NUMBER, hideSuggestion: true }, + meta: { type: CategoryType.NUMBER, isQuasiUnique: true }, }), columnHelper.accessor("Status", { id: "Status", @@ -206,7 +203,7 @@ export default function JobMonitor() { meta: { type: CategoryType.STRING, values: Object.keys(statusColors).sort(), - hideSuggestion: false, + isQuasiUnique: false, }, }), columnHelper.accessor("MinorStatus", { @@ -236,17 +233,17 @@ export default function JobMonitor() { columnHelper.accessor("LastUpdateTime", { id: "LastUpdateTime", header: "Last Update Time", - meta: { type: CategoryType.DATE, hideSuggestion: true }, + meta: { type: CategoryType.DATE, isQuasiUnique: true }, }), columnHelper.accessor("HeartBeatTime", { id: "HeartBeatTime", header: "Last Sign of Life", - meta: { type: CategoryType.DATE, hideSuggestion: true }, + meta: { type: CategoryType.DATE, isQuasiUnique: true }, }), columnHelper.accessor("SubmissionTime", { id: "SubmissionTime", header: "Submission Time", - meta: { type: CategoryType.DATE, hideSuggestion: true }, + meta: { type: CategoryType.DATE, isQuasiUnique: true }, }), columnHelper.accessor("Owner", { id: "Owner", @@ -263,12 +260,12 @@ export default function JobMonitor() { columnHelper.accessor("StartExecTime", { id: "StartExecTime", header: "Start Execution Time", - meta: { type: CategoryType.DATE, hideSuggestion: true }, + meta: { type: CategoryType.DATE, isQuasiUnique: true }, }), columnHelper.accessor("EndExecTime", { id: "EndExecTime", header: "End Execution Time", - meta: { type: CategoryType.DATE, hideSuggestion: true }, + meta: { type: CategoryType.DATE, isQuasiUnique: true }, }), columnHelper.accessor("UserPriority", { id: "UserPriority", @@ -284,6 +281,19 @@ export default function JobMonitor() { [columnHelper, renderStatusCell, statusColors], ); + // Handle the application of filters + const handleApplyFilters = useCallback(() => { + setSearchBody((prev) => ({ + ...prev, + search: filters.map(({ parameter, operator, value, values }) => ({ + parameter: fromHumanReadableText(parameter, columns), + operator, + value, + values, + })), + })); + }, [filters, columns]); + return ( - - + + + + + + {chartType === "table" && ( + + )} + {chartType === "sunburst" && ( + + )} ); } @@ -381,3 +409,38 @@ export function fromHumanReadableText( } return name; } + +/** + * Component to select the type of plot + * + * @param plotType The type of the plot + * @param setPlotType The setter for the plot type + * @returns A list of buttons to select the type of plot + */ +export function PlotTypeSelector({ + plotType, + setPlotType, +}: { + plotType: string; + setPlotType: React.Dispatch>; +}) { + return ( + <> + { + if (val !== null) setPlotType(val); + }} + aria-label="text alignment" + > + + + + + + + + + ); +} diff --git a/packages/diracx-web-components/src/components/JobMonitor/JobSearchBar.tsx b/packages/diracx-web-components/src/components/JobMonitor/JobSearchBar.tsx index fe716689..a1ea9fac 100644 --- a/packages/diracx-web-components/src/components/JobMonitor/JobSearchBar.tsx +++ b/packages/diracx-web-components/src/components/JobMonitor/JobSearchBar.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useRef } from "react"; +import { useEffect } from "react"; import { ColumnDef } from "@tanstack/react-table"; import { useOidcAccessToken } from "@axa-fr/react-oidc"; import { useOIDCContext } from "../../hooks/oidcConfiguration"; @@ -44,16 +44,11 @@ export function JobSearchBar({ }: JobSearchBarProps) { const { configuration } = useOIDCContext(); const { accessToken } = useOidcAccessToken(configuration?.scope); - const oldFilters = useRef(""); const diracxUrl = useDiracxUrl(); useEffect(() => { - const currentFilters = JSON.stringify(filters); - if (oldFilters.current !== currentFilters) { - oldFilters.current = currentFilters; - handleApplyFilters(); - } + handleApplyFilters(); }, [filters, handleApplyFilters]); return ( @@ -135,12 +130,12 @@ async function createSuggestions( previousToken.nature === SearchBarTokenNature.CUSTOM || previousToken.nature === SearchBarTokenNature.VALUE ) { - const items = columns.map((column) => column.header as string); + const items = columns.map((column) => String(column.header)); const type = columns.map( (column) => column.meta?.type || CategoryType.STRING, ) as CategoryType[]; const hideSuggestion = columns.map( - (column) => column.meta?.hideSuggestion || false, + (column) => column.meta?.isQuasiUnique || false, ); return { @@ -165,7 +160,7 @@ async function createSuggestions( if (!previousToken.hideSuggestion) { // Load the suggestions for the selected category const category = fromHumanReadableText( - previousEquation.items[0].label as string, + String(previousEquation.items[0].label), columns, ); await fetchJobSummary(category); diff --git a/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx b/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx new file mode 100644 index 00000000..2eba72fd --- /dev/null +++ b/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx @@ -0,0 +1,285 @@ +import { useState, useEffect } from "react"; + +import { scaleOrdinal, quantize, interpolateRainbow } from "d3"; + +import { useOidcAccessToken } from "@axa-fr/react-oidc"; +import { ColumnDef } from "@tanstack/react-table"; +import { useDiracxUrl } from "../../hooks/utils"; + +import type { JobSummary, SearchBody, Job, SunburstTree } from "../../types"; +import { Sunburst } from "../shared/Sunburst"; +import { useOIDCContext } from "../../hooks/oidcConfiguration"; +import { ChartDisplayLayout } from "../shared"; +import { getJobSummary } from "./jobDataService"; + +import { fromHumanReadableText } from "./JobMonitor"; + +/** + * Create the JobSunburst component. + * + * @param searchBody The search body to be used in the search + * @param statusColors The colors to be used for the different job statuses + * @param columns The columns to be used in the table + * @returns + */ +export function JobSunburst({ + searchBody, + statusColors, + columns, +}: { + searchBody: SearchBody; + statusColors: Record; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + columns: ColumnDef[]; +}) { + const { configuration } = useOIDCContext(); + const { accessToken } = useOidcAccessToken(configuration?.scope); + const diracxUrl = useDiracxUrl(); + + const [groupColumns, setGroupColumns] = useState(["Status"]); + const [currentPath, setCurrentPath] = useState([]); + + const [tree, setTree] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + const newSearch = currentPath.map((elt, index) => { + return { + parameter: fromHumanReadableText(groupColumns[index], columns), + operator: "eq", + value: elt, + }; + }); + const newSearchBody: SearchBody = { + ...searchBody, + search: searchBody.search + ? searchBody.search.concat(newSearch) + : newSearch, + }; + async function load() { + setIsLoading(true); + const res = await fetchAndBuildTree( + groupColumns.slice(currentPath.length, currentPath.length + 2), + newSearchBody, + diracxUrl, + accessToken, + columns, + ); + setTree({ + name: "", + children: res, + }); + setIsLoading(false); + } + load(); + }, [ + groupColumns[currentPath.length], + groupColumns[currentPath.length + 1], + currentPath, + searchBody, + diracxUrl, + accessToken, + ]); + + const defaultColors = scaleOrdinal( + quantize(interpolateRainbow, tree?.children?.length || 0 + 1), + ); + + function colorScales(name: string, _size: number, _depth: number): string { + if (statusColors[name]) { + return statusColors[name]; + } + if (tree?.children) { + return defaultColors(name); + } + return "#ccc"; + } + + const columnList = columns + .filter((column) => column.meta?.isQuasiUnique !== true) + .map((column) => String(column.header)); + + const hasHiddenLevels = groupColumns.length > currentPath.length + 2; + + const Chart = ( + + ); + + return ( + + ); +} + +/** + * Builds the tree from a given path + * + * @param groupColumns Array of columns to be used in the group by + * @param searchBody The search body to be used in the search + * @param diracxUrl The URL of the DiracX instance + * @param accessToken The access token to be used for authentication + * @param columns The columns to be used in the table + * @returns + */ +export async function fetchAndBuildTree( + groupColumns: string[], + searchBody: SearchBody, + diracxUrl: string | null, + accessToken: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + columns: ColumnDef[], +): Promise { + if (groupColumns.length === 0) { + return []; + } + + let data: JobSummary[] = []; + + const formatedGroupColumns = groupColumns.map((columnName) => + fromHumanReadableText(columnName, columns), + ); + + if (diracxUrl && accessToken) { + data = await getJobSummary( + diracxUrl, + formatedGroupColumns, + accessToken, + searchBody, + ).then((res) => res.data || []); + return buildTree(data, formatedGroupColumns); + } + return []; +} + +/** + * Builds a tree for the charts + * + * @param data Data to be transformed into a tree + * @param groupColumns Array of columns to be used in the group by + * @param parentPath The path to this Data (optional) + * @returns The tree corresponding or the sum if it's a leaf + */ +function buildTree( + data: JobSummary[], + groupColumns: string[], + parentPath: string[] = [], +): SunburstTree[] { + if (groupColumns.length === 0) return []; + + const current = groupColumns[0]; + + const groupedData = data.reduce>((acc, item) => { + const key: string = String(item[current]); + if (!acc[key]) { + acc[key] = []; + } + acc[key].push(item); + return acc; + }, {}); + + const total = data.reduce((sum, item) => sum + Number(item["count"]), 0); + const threshold = total * 0.05; + + const nodes: SunburstTree[] = []; + let othersValue: SunburstTree | null = null; + + for (const key in groupedData) { + const group = groupedData[key]; + const groupTotal = group.reduce( + (sum, item) => sum + Number(item["count"]), + 0, + ); + + if (groupTotal < threshold) { + // Too small group, add to "Others" + if (othersValue === null) { + if (groupColumns.length === 1) + othersValue = { name: key, value: groupTotal }; + else + othersValue = { + name: "Others", + children: buildTree(group, groupColumns.slice(1), [ + ...parentPath, + key, + ]), + }; + } else if (othersValue) { + othersValue = { + name: "Others", + value: + (othersValue.children + ? othersValue.children[0].value || 0 + : othersValue.value || 0) + groupTotal, + }; + } + } else { + if (groupColumns.length === 1) { + nodes.push({ + name: key, + value: groupTotal, + }); + } else { + nodes.push({ + name: key, + children: buildTree(group, groupColumns.slice(1), [ + ...parentPath, + key, + ]), + }); + } + } + } + + if (othersValue) { + nodes.push(othersValue); + } + + return nodes; +} + +/** + * + * @param size The number of jobs + * @param total The total number of jobs (optional) + * @returns A string with the number of jobs + */ +function sizeToText(size: number, total?: number): string { + if (size > 1e9) + return ( + `${(size / 1e9).toFixed(1)} billion jobs` + + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") + ); + if (size > 1e6) + return ( + `${(size / 1e6).toFixed(1)} million jobs` + + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") + ); + if (size > 1e3) + return ( + `${(size / 1e3).toFixed(1)} thousand\njobs` + + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") + ); + if (size > 1) + return ( + `${size} jobs` + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") + ); + if (size === 1) + return `1 job` + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : ""); + return ""; +} diff --git a/packages/diracx-web-components/src/components/JobMonitor/jobDataService.ts b/packages/diracx-web-components/src/components/JobMonitor/jobDataService.ts index d0c5cbad..091d98b0 100644 --- a/packages/diracx-web-components/src/components/JobMonitor/jobDataService.ts +++ b/packages/diracx-web-components/src/components/JobMonitor/jobDataService.ts @@ -1,9 +1,10 @@ "use client"; -import useSWR, { mutate } from "swr"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; +import { useEffect, useState } from "react"; + dayjs.extend(utc); import { fetcher } from "../../hooks/utils"; import { Filter, SearchBody, Job, JobHistory } from "../../types"; @@ -11,6 +12,11 @@ import type { JobSummary } from "../../types"; type TimeUnit = "minute" | "hour" | "day" | "month" | "year"; +/** + * Convert the 'last' operator in the search body to a date filter. + * @param searchBody The search body to be processed + * @returns The processed search body with adjusted filters + */ function processSearchBody(searchBody: SearchBody) { searchBody.search = searchBody.search?.map((filter: Filter) => { if (filter.operator == "last") { @@ -42,63 +48,6 @@ function processSearchBody(searchBody: SearchBody) { }); } -/** - * Custom hook for fetching jobs data. - * - * @param diracxUrl - The base URL of the DiracX API. - * @param accessToken - The access token for authentication. - * @param searchBody - The search body for filtering jobs. - * @param page - The page number for pagination. - * @param rowsPerPage - The number of rows per page. - * @returns The response from the API call. - */ -export const useJobs = ( - diracxUrl: string | null, - accessToken: string, - searchBody: SearchBody, - page: number, - rowsPerPage: number, -) => { - const urlGetJobs = diracxUrl - ? `${diracxUrl}/api/jobs/search?page=${page + 1}&per_page=${rowsPerPage}` - : null; - - processSearchBody(searchBody); - - return useSWR( - urlGetJobs ? [urlGetJobs, accessToken, "POST", searchBody] : null, - (args) => fetcher(args), - { - revalidateOnFocus: false, - }, - ); -}; - -/** - * Refreshes the jobs by mutating the SWR cache with the search body and pagination values - * - * @param diracxUrl - The base URL of the DiracX API. - * @param accessToken - The access token for authentication. - * @param searchBody - The search body containing the filters and search criteria. - * @param page - The page number for pagination. - * @param rowsPerPage - The number of rows per page for pagination. - */ -export const refreshJobs = ( - diracxUrl: string | null, - accessToken: string, - searchBody: SearchBody, - page: number, - rowsPerPage: number, -) => { - if (!diracxUrl) { - throw new Error("Invalid URL generated for refreshing jobs."); - } - - const urlGetJobs = `${diracxUrl}/api/jobs/search?page=${page + 1}&per_page=${rowsPerPage}`; - processSearchBody(searchBody); - mutate([urlGetJobs, accessToken, "POST", searchBody]); -}; - /** * Deletes jobs with the specified IDs. * @@ -285,3 +234,82 @@ export async function getJobSummary( return { data }; } + +/** + * Custom hook for fetching jobs data. + * + * @param diracxUrl - The base URL of the DiracX API. + * @param accessToken - The access token for authentication. + * @param searchBody - The search body for filtering jobs. + * @param page - The page number for pagination. + * @param rowsPerPage - The number of rows per page. + * @returns The response from the API call. + */ +export function useJobs( + diracxUrl: string | null, + accessToken: string, + searchBody: SearchBody, + page: number, + rowsPerPage: number, +) { + const [data, setData] = useState([]); + const [headers, setHeaders] = useState(new Headers()); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + if (!diracxUrl) { + setData(null); + setIsLoading(false); + setError(Error("Invalid URL generated for fetching jobs.")); + return; + } + + let cancelled = false; + + async function fetchJobs() { + setIsLoading(true); + + const urlGetJobs = `${diracxUrl}/api/jobs/search?page=${page + 1}&per_page=${rowsPerPage}`; + try { + processSearchBody(searchBody); + + const body = { + search: searchBody?.search || [], + sort: searchBody?.sort || [], + }; + + // Expect the response to be an array of objects with all the grouping fields + const res = await fetcher([ + urlGetJobs, + accessToken, + "POST", + body, + ]); + + if (!cancelled) { + setData(res.data); + setIsLoading(false); + setHeaders(res.headers); + setError(null); + } + } catch { + setData(null); + setIsLoading(false); + setError(Error("Failed to fetch jobs")); + } + } + fetchJobs(); + + return () => { + cancelled = true; + }; + }, [diracxUrl, accessToken, searchBody, page, rowsPerPage]); + + return { + headers, + data, + isLoading, + error, + }; +} diff --git a/packages/diracx-web-components/src/components/shared/ChartDisplayLayout.tsx b/packages/diracx-web-components/src/components/shared/ChartDisplayLayout.tsx new file mode 100644 index 00000000..2271753d --- /dev/null +++ b/packages/diracx-web-components/src/components/shared/ChartDisplayLayout.tsx @@ -0,0 +1,85 @@ +import React from "react"; + +import { Box } from "@mui/material"; + +import { ColumnSelector } from "./ColumnSelector"; + +interface ChartDisplayLayoutProps { + /** The chart to be displayed */ + Chart: JSX.Element; + /** List of columns available for selection */ + columnList: string[]; + /** Currently selected group columns */ + groupColumns: string[]; + /** Function to set the group columns */ + setGroupColumns: React.Dispatch>; + /** Function to set the current path in the chart */ + setCurrentPath: React.Dispatch>; + /** Default group columns to be used */ + defaultColumns: string[]; + /** Optional title for the column selector */ + title?: string; +} + +/** + * Creates a layout for displaying a chart alongside a column selector. + * + * @param props Props for the ChartDisplayLayout component + * @see ChartDisplayLayoutProps + * @returns + */ +export function ChartDisplayLayout({ + Chart, + columnList, + groupColumns, + setGroupColumns, + setCurrentPath, + defaultColumns: defaultGroupColumns, + title = "Level selector", +}: ChartDisplayLayoutProps) { + return ( + + {/* Left Section: The chart */} + + {Chart} + + + {/* Right Section: Column selection */} + + + + + ); +} diff --git a/packages/diracx-web-components/src/components/shared/ColumnSelector.tsx b/packages/diracx-web-components/src/components/shared/ColumnSelector.tsx new file mode 100644 index 00000000..54b24ae6 --- /dev/null +++ b/packages/diracx-web-components/src/components/shared/ColumnSelector.tsx @@ -0,0 +1,324 @@ +import React, { useEffect, useRef, useState } from "react"; +import { + Box, + Card, + Typography, + Button, + InputLabel, + MenuItem, + FormControl, + Tooltip, +} from "@mui/material"; +import Select, { SelectChangeEvent } from "@mui/material/Select"; +import RestoreIcon from "@mui/icons-material/Restore"; +import DragIndicatorIcon from "@mui/icons-material/DragIndicator"; +import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; +import { + draggable, + dropTargetForElements, +} from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; +import { DropIndicator } from "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box"; +import { + Edge, + attachClosestEdge, + extractClosestEdge, +} from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; + +interface SelectColumnsProps { + /** The row data*/ + columnList: string[]; + /** The columns used in the group by */ + groupColumns: string[]; + /** Setter for groupColumns */ + setGroupColumns: React.Dispatch>; + /** Setter for the current path in the tree */ + setCurrentPath: React.Dispatch>; + /** Default columns to use */ + defaultColumns: string[]; + /** Optional title for the column selector */ + title?: string; +} + +/** + * This component is used to select the columns to be used in the group by + * + * @param props See SelectColumnProps for more detials + * @see {@link SelectColumnProps} + * @returns A table which managed the group by on the columns + */ +export function ColumnSelector({ + columnList, + groupColumns, + setGroupColumns, + setCurrentPath, + defaultColumns: defaultGroupColumns, + title = "Column Selector", +}: SelectColumnsProps) { + /** + * Change the columns used for the group by + * + * @param event The event which triggers the change + * @param depth The depth in the tree + */ + const handleChange = (event: SelectChangeEvent, depth: number) => { + let newGroups = [...groupColumns]; + if (event.target.value === "None") { + // Delete a column + newGroups = newGroups.filter((_elt, index) => index !== depth); + if (newGroups.length > 0) + setCurrentPath((currentPath) => currentPath.slice(0, depth - 1)); + } else { + // Add or change a column + if (newGroups[depth]) { + // Change the column + newGroups[depth] = event.target.value; + setCurrentPath((currentPath) => currentPath.slice(0, depth)); + } else { + // Add a column + newGroups.push(event.target.value); + } + } + setGroupColumns(newGroups); + }; + + /** + * Reorder columns based on drag and drop + * + * @param fromIndex The original index + * @param toIndex The target index + */ + const handleReorder = (fromIndex: number, toIndex: number) => { + if (fromIndex === toIndex) return; + + const newColumns = [...groupColumns]; + + // Only move existing columns (not the "add new" one) + if (fromIndex < newColumns.length) { + // Remove the item from its original position + const [movedItem] = newColumns.splice(fromIndex, 1); + + newColumns.splice(toIndex, 0, movedItem); + setGroupColumns(newColumns); + + // Reset the current path since we changed the hierarchy + setCurrentPath([]); + } + }; + + const resetColumnsToPlot = () => { + setGroupColumns(defaultGroupColumns); + setCurrentPath([]); + }; + + /** A arrray with one cell per column in the group by */ + const additionalChoice = []; + + for (let i = 0; i < groupColumns.length + 1; i++) { + const availableColumns = columnList.filter( + (column) => column === groupColumns[i] || !groupColumns.includes(column), + ); + + additionalChoice.push( + , + ); + } + + return ( + + + + + {title} + + + + {additionalChoice} + +
+ +
+
+
+ ); +} + +interface ColumnSelectProps { + /** The columns used in the group by */ + groupColumns: string[]; + /** The index of the column in the group by */ + index: number; + /** Function to handle the change of the column */ + handleChange: (event: SelectChangeEvent, index: number) => void; + /** The available columns to select from */ + availableColumns: string[]; + /** Function to handle the reordering of the columns */ + onReorder: (fromIndex: number, toIndex: number) => void; +} + +function ColumnSelect({ + groupColumns, + index, + handleChange, + availableColumns, + onReorder, +}: ColumnSelectProps) { + // Ref to use for the draggable element + const dragRef = useRef(null); + // Ref to use for the handle of the draggable element + const handleRef = useRef(null); + // Represents the closest edge to the mouse cursor + const [closestEdge, setClosestEdge] = useState(null); + + const isLastItem = index === groupColumns.length; + + useEffect(() => { + if (!dragRef.current || !handleRef.current || isLastItem) return; + + const element = dragRef.current; + const handleElement = handleRef.current; + + return combine( + // Makes the element draggable + draggable({ + element, + dragHandle: handleElement, + getInitialData: () => ({ index }), + }), + + // Makes the element a drop target + dropTargetForElements({ + element, + getData: ({ input, element }) => { + return attachClosestEdge( + { index }, + { input, element, allowedEdges: ["top", "bottom"] }, + ); + }, + + onDrag({ self, source }) { + const isSource = source.element === element; + if (isSource) { + setClosestEdge(null); + return; + } + + const closestEdge = extractClosestEdge(self.data); + const sourceIndex = source.data.index; + + if (typeof sourceIndex === "number") { + const isItemBeforeSource = index === sourceIndex - 1; + const isItemAfterSource = index === sourceIndex + 1; + + const isDropIndicatorHidden = + (isItemBeforeSource && closestEdge === "bottom") || + (isItemAfterSource && closestEdge === "top"); + + if (isDropIndicatorHidden) { + setClosestEdge(null); + return; + } + } + + setClosestEdge(closestEdge); + }, + onDragLeave() { + setClosestEdge(null); + }, + onDrop({ self, source }) { + const closestEdge = extractClosestEdge(self.data); + const fromIndex = source.data.index as number; + let toIndex = index; + + if (closestEdge === "bottom") { + toIndex = index + 1; + } + + // Only reorder if from and to indexes are different + if (fromIndex != undefined && toIndex !== fromIndex) { + onReorder(fromIndex, toIndex); + } + + setClosestEdge(null); + }, + }), + ); + }, [index, isLastItem, onReorder]); + + return ( + + {!isLastItem && ( + + + + )} + + {isLastItem && } + + + Level {index + 1} + + + + {closestEdge && } + + ); +} diff --git a/packages/diracx-web-components/src/components/shared/DataTable.tsx b/packages/diracx-web-components/src/components/shared/DataTable.tsx index aae7e16f..e71c5958 100644 --- a/packages/diracx-web-components/src/components/shared/DataTable.tsx +++ b/packages/diracx-web-components/src/components/shared/DataTable.tsx @@ -199,7 +199,7 @@ function DataTableToolbar>({ * @property {function} setSearchBody - the function to call when the search body changes * @property {Column[]} columns - the columns of the table * @property {T[]} rows - the rows of the table - * @property {string | null} error - the error message + * @property {Error | null} error - the error message * @property {JSX.Element} toolbarComponents - the components to display in the toolbar * @property {MenuItem[]} menuItems - the menu items */ @@ -214,10 +214,8 @@ export interface DataTableProps> { searchBody: SearchBody; /** The function to call when the search body changes */ setSearchBody: React.Dispatch>; - /** The error message */ - error: string | null; - /** Whether the table is validating */ - isValidating: boolean; + /** The error or null if no error */ + error: Error | null; /** Whether the table is loading */ isLoading: boolean; /** The components to display in the toolbar */ @@ -239,7 +237,6 @@ export function DataTable>({ setSearchBody, error, isLoading, - isValidating, toolbarComponents, menuItems, }: DataTableProps) { @@ -348,10 +345,10 @@ export function DataTable>({ // Handle no data const noData = !rows || rows.length === 0; - if (isValidating || isLoading || error || noData) { + if (isLoading || error || noData) { return ( - {isValidating || isLoading ? ( + {isLoading ? ( >({ /> ) : error ? ( - An error occurred while fetching data. Reload the page. + {error.message || + "An error occurred while fetching data. Reload the page."} ) : ( diff --git a/packages/diracx-web-components/src/components/shared/SearchBar/SearchBar.tsx b/packages/diracx-web-components/src/components/shared/SearchBar/SearchBar.tsx index 4987f43c..e0dd295e 100644 --- a/packages/diracx-web-components/src/components/shared/SearchBar/SearchBar.tsx +++ b/packages/diracx-web-components/src/components/shared/SearchBar/SearchBar.tsx @@ -3,6 +3,7 @@ import React, { useState, useRef, useEffect } from "react"; import { Box, Menu, MenuItem, IconButton } from "@mui/material"; import DeleteIcon from "@mui/icons-material/Delete"; +import RefreshIcon from "@mui/icons-material/Refresh"; import { Filter, @@ -121,7 +122,7 @@ export function SearchBar({ } if (filters.length !== 0 && tokenEquations.length === 0) load(); - }, [filters, createSuggestions]); + }, []); // Create a list of options based on the current tokens and data useEffect(() => { @@ -134,7 +135,7 @@ export function SearchBar({ setSuggestions(result); } load(); - }, [previousEquation, previousToken, createSuggestions]); + }, [previousEquation, previousToken, createSuggestions, focusedTokenIndex]); // Timer to delay the search function // This effect will trigger the searchFonction after a delay if the equations are valid @@ -157,7 +158,7 @@ export function SearchBar({ const hasChanged = currentEquationsString !== lastSearchedEquationsRef.current; - if (allEquationsValid && searchFunction && hasChanged) { + if (allEquationsValid && hasChanged) { searchTimerRef.current = setTimeout(() => { lastSearchedEquationsRef.current = currentEquationsString; searchFunction(tokenEquations, setFilters); @@ -287,18 +288,20 @@ export function SearchBar({ }} sx={{ width: 1, + height: "auto", display: "flex", border: "1px solid", borderColor: "grey.400", - overflow: "auto", + overflow: "hidden", borderRadius: 1, ":focus-within": { borderColor: "primary.main", }, + alignItems: "center", }} data-testid="search-bar" > - + {tokenEquations.map((equation, index) => ( - {tokenEquations.length !== 0 && ( + { - setInputValue(""); - clearFunction(setFilters, setTokenEquations); - }} - disabled={tokenEquations.length === 0} - sx={{ marginLeft: "auto", width: "40px", height: "40px" }} + onClick={() => searchFunction(tokenEquations, setFilters)} + disabled={ + !tokenEquations.every((eq) => eq.status === EquationStatus.VALID) + } + sx={{ width: "40px", height: "40px" }} > - + - )} + + {tokenEquations.length !== 0 && ( + { + setInputValue(""); + clearFunction(setFilters, setTokenEquations); + }} + sx={{ width: "40px", height: "40px" }} + > + + + )} + ); } diff --git a/packages/diracx-web-components/src/components/shared/SearchBar/SearchField.tsx b/packages/diracx-web-components/src/components/shared/SearchBar/SearchField.tsx index cf154f78..65266eff 100644 --- a/packages/diracx-web-components/src/components/shared/SearchBar/SearchField.tsx +++ b/packages/diracx-web-components/src/components/shared/SearchBar/SearchField.tsx @@ -287,6 +287,7 @@ export default function SearchField({ setInputValue(value); }} sx={{ + marginTop: "2px", minWidth: "180px", width: "auto", maxWidth: 0.9, diff --git a/packages/diracx-web-components/src/components/shared/SearchBar/index.ts b/packages/diracx-web-components/src/components/shared/SearchBar/index.ts index 76b5bf2a..5932700d 100644 --- a/packages/diracx-web-components/src/components/shared/SearchBar/index.ts +++ b/packages/diracx-web-components/src/components/shared/SearchBar/index.ts @@ -1,2 +1 @@ export { SearchBar } from "./SearchBar"; -export type { SearchBarProps } from "./SearchBar"; diff --git a/packages/diracx-web-components/src/components/shared/Sunburst/Sunburst.tsx b/packages/diracx-web-components/src/components/shared/Sunburst/Sunburst.tsx new file mode 100644 index 00000000..d14c1661 --- /dev/null +++ b/packages/diracx-web-components/src/components/shared/Sunburst/Sunburst.tsx @@ -0,0 +1,363 @@ +import React, { MouseEvent, useEffect, useRef, useMemo } from "react"; + +import { + HierarchyNode, + Arc, + select, + hierarchy, + partition, + arc, + quantize, + interpolateRainbow, +} from "d3"; + +import { scaleOrdinal } from "d3-scale"; + +import { Stack, useTheme, Box, Alert, Skeleton } from "@mui/material"; + +import type { SunburstTree, SunburstNode } from "../../../types"; +import { DisplayPath, getPath, sizeToText as defaultSizeToText } from "./Utils"; + +interface SunburstProps { + /** Formatted data to be displayed in the chart */ + tree: SunburstTree; + /** Boolean indicating if there are hidden levels */ + hasHiddenLevels?: boolean; + /** Function to convert the size to text */ + sizeToText?: (size: number, total?: number) => string; + /** The current path in the data tree */ + currentPath?: string[]; + /** Function to handle right-click events on the chart */ + handleRightClick?: (p: SunburstNode) => void; + /** Function to update the current path */ + setCurrentPath?: React.Dispatch>; + /** Function to generate color scales for the chart */ + colorScales?: (name: string, size: number, depth: number) => string; + /** Boolean indicating if the chart is loading */ + isLoading?: boolean; + /** Error object if there is an error */ + error?: Error | null; +} + +/** + * Create the Sunburst component. + * Adapted from https://observablehq.com/@d3/zoomable-sunburst + * + * @param props The props for the Sunburst. See SunburtProps for details + * @see {@link SunburstProps} + * @returns The Sunurst component + */ +export function Sunburst({ + tree, + hasHiddenLevels = true, + sizeToText = defaultSizeToText, + currentPath, + handleRightClick, + setCurrentPath, + colorScales, + isLoading = false, + error = null, +}: SunburstProps) { + // Create a stable default color scale with useMemo + const defaultColorScale = useMemo(() => { + if (!tree?.children) return () => "#ccc"; + + const colorScale = scaleOrdinal( + quantize(interpolateRainbow, tree.children.length + 1), + ); + return (name: string, _size: number, _depth: number) => colorScale(name); + }, [tree?.children?.length]); + + // Use the provided colorScales or the default one + const finalColorScales = colorScales || defaultColorScale; + + const svgRef = useRef(null); + const tooltipRef: React.RefObject = + useRef(null); + + const theme = useTheme(); + + // Dimensions are for the ViewBox (in px) + const width = 800; + const height = 800; + + useEffect(() => { + // Avoid those specific cases + if (error || isLoading || !tree || tree.children?.length === 0) return; + + const radius: number = width / 7; + + // Compute the layout. + const hierarchyStruct = hierarchy(tree) // Create the tree + .sum((d) => d.value || 0) + .sort( + (a: HierarchyNode, b: HierarchyNode) => { + if (a.value && b.value) return b.value - a.value; + if (a.value) return -1; + if (b.value) return 1; + return 0; + }, + ); + + const root: SunburstNode = partition().size([ + 2 * Math.PI, + hierarchyStruct.height + 1, + ])(hierarchyStruct); + root.each((d) => { + d.current = d; + }); + + // Create the arc generator. + const arcGenerator: Arc = arc() + .startAngle((d) => d.x0) + .endAngle((d) => d.x1) + .padAngle((d) => Math.min((d.x1 - d.x0) / 2, 0.005)) + .padRadius(radius * 1.5) + .innerRadius((d) => d.y0 * radius) + .outerRadius((d) => Math.max(d.y0 * radius, d.y1 * radius - 1)); + + // Create the SVG container. + const svg = select(svgRef.current) + .attr("viewBox", [-width / 2, -height / 2, width, width]) + .style("font", "10px sans-serif"); + + tooltipRef.current!.innerHTML = ""; // Delete the previous tooltip + + // Create the tooltip + const tooltip = select(tooltipRef.current) + .append("div") + .style("position", "absolute") + .style("visibility", "hidden") + .style("border-radius", "4px") + .style("padding", "10px") + .style("background-color", "rgba(230, 230, 230, 0.7)") + .style("color", "black") + .style("pointer-events", "none") + .style("z-index", "99") + .style("-webkit-box-shadow", "7px 7px 10px 4px rgba(0, 0, 0, 0.53)") + .style("box-shadow", "7px 7px 10px 4px rgba(0, 0, 0, 0.53)") + .text(""); + + // Remove any previous elements + svg.selectAll("*").remove(); + + // Cercle in the middle of the Sunburst + svg + .append("circle") + .datum(root) + .attr("r", radius) + .style("cursor", "pointer") + .attr("fill", "none") + .attr("pointer-events", "all") + .on("click", unZoom); + + // Append the arcs. + const path = svg + .append("g") + .selectAll("path") + .data(root.descendants().slice(1)) + .join("path") + .attr("fill", (d) => { + while (d.depth > 1) d = d.parent!; + return finalColorScales(d.data.name, d.x1 - d.x0, d.depth); + }) + .attr("fill-opacity", (d) => + arcVisible(d.current!) + ? d.data.name !== "Others" && (d.children || hasHiddenLevels) + ? 0.8 + : 0.4 + : 0, + ) + .attr("pointer-events", (d) => (arcVisible(d.current!) ? "auto" : "none")) + .attr("d", (d) => arcGenerator(d.current!)); + + function zoom(_event: MouseEvent, p: SunburstNode) { + if (setCurrentPath && currentPath) + setCurrentPath(currentPath.concat(getPath(p))); + } + + function unZoom(_event: MouseEvent, _p: SunburstNode) { + if (setCurrentPath && currentPath) + setCurrentPath(currentPath.slice(0, -1)); + } + + if (setCurrentPath) { + // If the chart can be modified + // Make them clickable if they have children. + path + .filter( + (d: SunburstNode) => + d.data.name !== "Others" && + (Array.isArray(d.children) || hasHiddenLevels), + ) + .style("cursor", "pointer") + .on("click", zoom); + } + + // Make them interact with the mouse + path + .on( + "mouseover", + function (this: SVGPathElement, event: MouseEvent, p: SunburstNode) { + mouseOn.call(this, event, p); + }, + ) + .on( + "mouseout", + function (this: SVGPathElement, event: MouseEvent, p: SunburstNode) { + mouseOut.call(this, event, p); + }, + ) + .on("mousemove", mouseMove) + .on("contextmenu", rightClicked); + + // Text on the chart quarters + svg + .append("g") + .attr("pointer-events", "none") + .attr("text-anchor", "middle") + .style("user-select", "none") + .selectAll("text") + .data(root.descendants().slice(1)) + .join("text") + .attr("dy", "0.35em") + .attr("fill-opacity", (d) => +labelVisible(d.current!)) + .attr("transform", (d) => labelTransform(d.current!)) + .attr("fill", theme.palette.text.primary) + .text((d) => d.data.name); + + // Text with the size in the middle (multi-line support) + const centerText = sizeToText(root.value || 0); + const lines = centerText.split("\n"); + + const textGroup = svg + .append("g") + .attr("text-anchor", "middle") + .attr("fill", theme.palette.text.primary); + + lines.forEach((line, index) => { + textGroup + .append("text") + .attr("x", 0) + .attr("y", (index - (lines.length - 1) / 2) * 35) + .attr("dominant-baseline", "middle") + .attr("font-size", "30px") + .text(line); + }); + + function arcVisible(d: SunburstNode): boolean { + return d.y1 <= 3 && d.y0 >= 1 && d.x1 > d.x0; + } + + function labelVisible(d: SunburstNode): boolean { + return d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.03; + } + + // Move the label to the right place + function labelTransform(d: SunburstNode): string { + const x = (((d.x0 + d.x1) / 2) * 180) / Math.PI; + const y = ((d.y0 + d.y1) / 2) * radius; + return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`; + } + + function mouseOn( + this: SVGPathElement | null, + event: MouseEvent, + p: SunburstNode, + ) { + if (p.children && setCurrentPath) { + select(this).transition().duration(30).attr("opacity", "0.85"); + } + tooltip.style("visibility", "visible"); + tooltip + .style("top", event.pageY - 50 + "px") + .style("left", event.pageX - 50 + "px"); + tooltip.text( + (currentPath || []).concat(getPath(p)).join("/") + + ": " + + sizeToText(p.value || 0, root.value), + ); + } + + function mouseOut( + this: SVGPathElement, + _event: MouseEvent, + _p: SunburstNode, + ) { + select(this).transition().duration(30).attr("opacity", "1"); + tooltip.style("visibility", "hidden"); + } + + function mouseMove(event: MouseEvent, _p: SunburstNode) { + tooltip + .style("top", event.pageY - 50 + "px") + .style("left", event.pageX - 50 + "px"); + } + + function rightClicked(event: MouseEvent, p: SunburstNode) { + event.preventDefault(); + if (handleRightClick) handleRightClick(p); + tooltip.style("visibility", "hidden"); + } + }, [ + width, + height, + tree, + handleRightClick, + currentPath, + setCurrentPath, + theme, + finalColorScales, + sizeToText, + ]); + + if (error) { + return ( + + {error.message || "An error occurred while loading the data."} + + ); + } + + if (!tree.children || tree.children.length === 0) + return ( + + No data or no results match your filters. + + ); + + return ( + + + {currentPath && setCurrentPath && ( + + )} + +
+ {isLoading ? ( + + ) : ( + + )} +
+
+ + ); +} diff --git a/packages/diracx-web-components/src/components/shared/Sunburst/Utils.tsx b/packages/diracx-web-components/src/components/shared/Sunburst/Utils.tsx new file mode 100644 index 00000000..d86ea0fb --- /dev/null +++ b/packages/diracx-web-components/src/components/shared/Sunburst/Utils.tsx @@ -0,0 +1,123 @@ +import { Breadcrumbs, Link } from "@mui/material"; + +import { SunburstNode } from "../../../types"; + +/** + * Display the path in the breadcrumb + * + * @param path The path to display + * @param setPath The function to set the path + * @returns The breadcrumb component + */ +export function DisplayPath({ + path, + setPath, +}: { + path: string[]; + setPath?: React.Dispatch>; +}) { + return ( + + { + if (setPath) setPath([]); + }} + sx={{ + cursor: "pointer", + "&:hover": { + textDecoration: "underline", + }, + }} + variant="h6" + > + Top + + {path.map((elt, index) => ( + { + if (setPath) setPath((oldPath) => oldPath.slice(0, index + 1)); + }} + sx={{ + cursor: "pointer", + "&:hover": { + textDecoration: "underline", + }, + }} + variant="h6" + > + {elt} + + ))} + + ); +} + +/** + * Gives the complete path to a node + * + * @param p The target node + * @returns The path + */ +export function getPath(p: SunburstNode): string[] { + const path = [p.data.name]; + let elt: SunburstNode = p; + while (elt.depth > 0) { + elt = elt.parent!; + if (elt.data.name !== "") path.push(elt.data.name); + } + return path.reverse(); +} + +/** + * Converts a size in Bytes to a human-readable format + * + * @param size The size in Bytes + * @param total The total size (optional) to calculate the percentage + * @returns A string with the size in a human-readable format + */ +export function sizeToText(size: number, total?: number): string { + if (size >= 1e18) + return ( + (size / 1e18).toFixed(2) + + " EB" + + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") + ); + if (size >= 1e15) + return ( + (size / 1e15).toFixed(2) + + " PB" + + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") + ); + if (size >= 1e12) + return ( + (size / 1e12).toFixed(2) + + " TB" + + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") + ); + if (size >= 1e9) + return ( + (size / 1e9).toFixed(2) + + " GB" + + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") + ); + if (size >= 1e6) + return ( + (size / 1e6).toFixed(2) + + " MB" + + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") + ); + if (size >= 1e3) + return ( + (size / 1e3).toFixed(2) + + " KB" + + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") + ); + if (size < 1e3 && size >= 0) + return ( + size.toFixed(2) + + "B" + + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") + ); + return "none"; +} diff --git a/packages/diracx-web-components/src/components/shared/Sunburst/index.ts b/packages/diracx-web-components/src/components/shared/Sunburst/index.ts new file mode 100644 index 00000000..9ff5138b --- /dev/null +++ b/packages/diracx-web-components/src/components/shared/Sunburst/index.ts @@ -0,0 +1 @@ +export { Sunburst } from "./Sunburst"; diff --git a/packages/diracx-web-components/src/components/shared/index.ts b/packages/diracx-web-components/src/components/shared/index.ts index a44c4698..3fde471d 100644 --- a/packages/diracx-web-components/src/components/shared/index.ts +++ b/packages/diracx-web-components/src/components/shared/index.ts @@ -1,4 +1,6 @@ export { DataTable } from "./DataTable"; export { ErrorBox } from "./ErrorBox"; export { ApplicationSelector } from "./ApplicationSelector"; -export * from "./SearchBar"; +export { ChartDisplayLayout } from "./ChartDisplayLayout"; +export { SearchBar } from "./SearchBar"; +export { Sunburst } from "./Sunburst"; diff --git a/packages/diracx-web-components/src/contexts/DiracXWebProviders.tsx b/packages/diracx-web-components/src/contexts/DiracXWebProviders.tsx index 856a7e2c..947cf839 100644 --- a/packages/diracx-web-components/src/contexts/DiracXWebProviders.tsx +++ b/packages/diracx-web-components/src/contexts/DiracXWebProviders.tsx @@ -3,9 +3,9 @@ import type { ApplicationMetadata, DashboardGroup } from "../types"; import { OIDCSecure } from "../components"; +import { ThemeProvider } from "./ThemeProvider"; import { OIDCConfigurationProvider, - ThemeProvider, NavigationProvider, ApplicationsProvider, } from "./index"; diff --git a/packages/diracx-web-components/src/contexts/index.ts b/packages/diracx-web-components/src/contexts/index.ts index 63c0780d..28acf6d0 100644 --- a/packages/diracx-web-components/src/contexts/index.ts +++ b/packages/diracx-web-components/src/contexts/index.ts @@ -6,10 +6,5 @@ export { OIDCConfigurationContext, OIDCConfigurationProvider, } from "./OIDCConfigurationProvider"; -export { - ThemeContext, - ThemeProvider, - type ThemeProviderProps, -} from "./ThemeProvider"; +export { DiracXWebProviders } from "./DiracXWebProviders"; export * from "./NavigationProvider"; -export * from "./DiracXWebProviders"; diff --git a/packages/diracx-web-components/src/global.d.ts b/packages/diracx-web-components/src/global.d.ts index cab8932a..1163ed64 100644 --- a/packages/diracx-web-components/src/global.d.ts +++ b/packages/diracx-web-components/src/global.d.ts @@ -12,6 +12,6 @@ declare module "@tanstack/react-table" { interface ColumnMeta { type?: CategoryType; values?: string[]; // Optional values for category-type fields - hideSuggestion?: boolean; // Whether to hide suggestions for this column + isQuasiUnique?: boolean; // Some columns are quasi-unique, meaning they can have multiple values but are not fully unique } } diff --git a/packages/diracx-web-components/src/types/SunburstData.ts b/packages/diracx-web-components/src/types/SunburstData.ts new file mode 100644 index 00000000..982c5fed --- /dev/null +++ b/packages/diracx-web-components/src/types/SunburstData.ts @@ -0,0 +1,3 @@ +export type SunburstData = { + [key: string]: string | number | boolean; +}; diff --git a/packages/diracx-web-components/src/types/SunburstNode.ts b/packages/diracx-web-components/src/types/SunburstNode.ts new file mode 100644 index 00000000..e758fe7b --- /dev/null +++ b/packages/diracx-web-components/src/types/SunburstNode.ts @@ -0,0 +1,8 @@ +import { HierarchyRectangularNode } from "d3-hierarchy"; + +import type { SunburstTree } from "./SunburstTree"; + +export interface SunburstNode extends HierarchyRectangularNode { + /** The current node in the hierarchy */ + current?: SunburstNode; +} diff --git a/packages/diracx-web-components/src/types/SunburstTree.ts b/packages/diracx-web-components/src/types/SunburstTree.ts new file mode 100644 index 00000000..2ffce20b --- /dev/null +++ b/packages/diracx-web-components/src/types/SunburstTree.ts @@ -0,0 +1,8 @@ +export type SunburstTree = { + /** The name of the node */ + name: string; + /** The value of the node if it's a leaf */ + value?: number; + /** The children of the node */ + children?: SunburstTree[]; +}; diff --git a/packages/diracx-web-components/src/types/index.ts b/packages/diracx-web-components/src/types/index.ts index 880b578e..29227e87 100644 --- a/packages/diracx-web-components/src/types/index.ts +++ b/packages/diracx-web-components/src/types/index.ts @@ -15,3 +15,5 @@ export * from "./EquationStatus"; export * from "./operators"; export * from "./SearchBarTokenNature"; export * from "./CategoryType"; +export * from "./SunburstTree"; +export * from "./SunburstNode"; diff --git a/packages/diracx-web-components/stories/ChartDisplayLayout.stories.tsx b/packages/diracx-web-components/stories/ChartDisplayLayout.stories.tsx new file mode 100644 index 00000000..68d2418d --- /dev/null +++ b/packages/diracx-web-components/stories/ChartDisplayLayout.stories.tsx @@ -0,0 +1,103 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { useState } from "react"; +import { ChartDisplayLayout } from "../src/components/shared/ChartDisplayLayout"; +import { ThemeProvider } from "../src/contexts/ThemeProvider"; +import { Sunburst } from "../src/components"; + +// Mock data for the story +const mockTree = { + name: "", + children: [ + { + name: "Production", + value: 1500, + children: [ + { name: "Running", value: 800 }, + { name: "Completed", value: 500 }, + { name: "Failed", value: 200 }, + ], + }, + { + name: "Development", + value: 800, + children: [ + { name: "Testing", value: 400 }, + { name: "Debugging", value: 300 }, + { name: "Review", value: 100 }, + ], + }, + { + name: "Maintenance", + value: 600, + children: [ + { name: "Updates", value: 300 }, + { name: "Backups", value: 200 }, + { name: "Monitoring", value: 100 }, + ], + }, + ], +}; + +const meta = { + title: "Shared/ChartDisplayLayout", + component: ChartDisplayLayout, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + decorators: [ + (Story) => { + return ; + }, + ], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + Chart:
Nothing
, + columnList: ["Column 1", "Column 2", "Column 3"], + groupColumns: ["Column 1"], + setGroupColumns: () => {}, + setCurrentPath: () => {}, + defaultColumns: ["Column 1"], + title: "Select Columns", + }, + argTypes: { + Chart: { + control: { type: "select" }, + options: ["Sunburst", "None"], + mapping: { + Sunburst: , + None:
No Chart
, + }, + }, + setGroupColumns: { + control: { disable: true }, + }, + setCurrentPath: { + control: { disable: true }, + }, + }, + render: (args) => { + const [groupColumns, setGroupColumns] = useState(args.groupColumns); + + return ( + + + + ); + }, +}; diff --git a/packages/diracx-web-components/stories/Dashboard.stories.tsx b/packages/diracx-web-components/stories/Dashboard.stories.tsx index 1a05227c..baf7f53d 100644 --- a/packages/diracx-web-components/stories/Dashboard.stories.tsx +++ b/packages/diracx-web-components/stories/Dashboard.stories.tsx @@ -18,6 +18,8 @@ const meta = { argTypes: { children: { control: false }, drawerWidth: { control: { type: "range", min: 200, max: 500, step: 10 } }, + logoURL: { control: { disable: true } }, + documentationURL: { control: { disable: true } }, }, decorators: [ (Story) => { diff --git a/packages/diracx-web-components/stories/DataTable.stories.tsx b/packages/diracx-web-components/stories/DataTable.stories.tsx index fca78756..b59e583c 100644 --- a/packages/diracx-web-components/stories/DataTable.stories.tsx +++ b/packages/diracx-web-components/stories/DataTable.stories.tsx @@ -47,8 +47,6 @@ const meta: Meta> = { totalRows: { control: "number" }, searchBody: { control: false }, setSearchBody: { control: false }, - error: { control: "text" }, - isValidating: { control: "boolean" }, isLoading: { control: "boolean" }, toolbarComponents: { control: false }, menuItems: { control: "object" }, @@ -74,7 +72,6 @@ export const Default: Story = { searchBody: { sort: [{ parameter: "id", direction: "asc" }] }, setSearchBody: () => {}, error: null, - isValidating: false, isLoading: false, toolbarComponents: <>, menuItems: [{ label: "Edit", onClick: () => {} }], diff --git a/packages/diracx-web-components/stories/JobMonitor.stories.tsx b/packages/diracx-web-components/stories/JobMonitor.stories.tsx index b84e860a..15cd3d2b 100644 --- a/packages/diracx-web-components/stories/JobMonitor.stories.tsx +++ b/packages/diracx-web-components/stories/JobMonitor.stories.tsx @@ -2,7 +2,7 @@ import { StoryObj, Meta } from "@storybook/react"; import { Box } from "@mui/material"; import { ThemeProvider } from "../src/contexts/ThemeProvider"; import JobMonitor from "../src/components/JobMonitor/JobMonitor"; -import { setJobsMock, setJobHistoryMock } from "./mocks/jobDataService.mock"; +import { JobMockProvider } from "./mocks/contexts.mock"; const meta = { title: "Job Monitor/JobMonitor", @@ -105,90 +105,74 @@ const jobHistory = [ Source: "Storybook", }, ]; - -export const Loading: Story = { +export const Default: Story = { args: {}, decorators: [ (Story) => { - setJobsMock({ - jobs: null, - error: null, - isLoading: true, - }); - - setJobHistoryMock({ - jobHistory: jobHistory, - error: null, - isLoading: false, - }); - - return ; + return ( + + + + ); }, ], }; -export const Error: Story = { +export const Loading: Story = { args: {}, decorators: [ (Story) => { - setJobsMock({ - jobs: null, - error: { - message: "Error loading jobs", - name: "Error", - }, - isLoading: false, - }); - - setJobHistoryMock({ - jobHistory: jobHistory, - error: null, - isLoading: false, - }); - - return ; + return ( + + + + ); }, ], }; -export const Empty: Story = { +export const WithError: Story = { args: {}, decorators: [ (Story) => { - setJobsMock({ - jobs: [], - error: null, - isLoading: false, - }); - - setJobHistoryMock({ - jobHistory: [], - error: null, - isLoading: false, - }); - - return ; + return ( + + + + ); }, ], }; -export const Default: Story = { +export const Empty: Story = { args: {}, decorators: [ (Story) => { - setJobsMock({ - jobs: jobs, - error: null, - isLoading: false, - }); - - setJobHistoryMock({ - jobHistory: jobHistory, - error: null, - isLoading: false, - }); - - return ; + return ( + + + + ); }, ], }; diff --git a/packages/diracx-web-components/stories/LoginForm.stories.tsx b/packages/diracx-web-components/stories/LoginForm.stories.tsx index 8e88738f..4a28ab20 100644 --- a/packages/diracx-web-components/stories/LoginForm.stories.tsx +++ b/packages/diracx-web-components/stories/LoginForm.stories.tsx @@ -68,7 +68,7 @@ const meta = { }, tags: ["autodocs"], argTypes: { - logoURL: { control: "text" }, + logoURL: { control: { disable: true } }, }, decorators: [ (Story) => { diff --git a/packages/diracx-web-components/stories/Sunburst.stories.tsx b/packages/diracx-web-components/stories/Sunburst.stories.tsx new file mode 100644 index 00000000..704890c3 --- /dev/null +++ b/packages/diracx-web-components/stories/Sunburst.stories.tsx @@ -0,0 +1,166 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { useState, useEffect } from "react"; +import { Sunburst } from "../src/components/shared/Sunburst/Sunburst"; + +// Mock data for the story +const mockTree = { + name: "", + children: [ + { + name: "Production", + value: 1500, + children: [ + { name: "Running", value: 800 }, + { name: "Completed", value: 500 }, + { name: "Failed", value: 200 }, + ], + }, + { + name: "Development", + value: 800, + children: [ + { name: "Testing", value: 400 }, + { name: "Debugging", value: 300 }, + { name: "Review", value: 100 }, + ], + }, + { + name: "Maintenance", + value: 600, + children: [ + { name: "Updates", value: 300 }, + { name: "Backups", value: 200 }, + { name: "Monitoring", value: 100 }, + ], + }, + ], +}; + +function customSizeToText(size: number): string { + return `${size} owners`; +} + +function customColorScales(name: string, _size: number, depth: number) { + // Custom color logic based on depth and name + const colors = { + 0: "#FF6B6B", // Root level + 1: "#4ECDC4", // First level + 2: "#45B7D1", // Second level + }; + + // Different colors for different categories + if (name.includes("Production")) return "#FF6B6B"; + if (name.includes("Development")) return "#4ECDC4"; + if (name.includes("Maintenance")) return "#45B7D1"; + if (name.includes("Running")) return "#2ECC71"; + if (name.includes("Failed")) return "#E74C3C"; + if (name.includes("Completed")) return "#F39C12"; + + return colors[depth as keyof typeof colors] || "#95A5A6"; +} + +const meta: Meta = { + title: "Shared/Sunburst", + component: Sunburst, + parameters: { + layout: "centered", + docs: { + description: { + component: + "A D3-based sunburst chart for hierarchical data visualization.", + }, + }, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + tags: ["autodocs"], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + tree: mockTree, + error: null, + isLoading: false, + hasHiddenLevels: false, + sizeToText: undefined, + colorScales: undefined, + }, + argTypes: { + tree: { + control: "select", + options: ["Default", "Empty"], + mapping: { + Default: mockTree, + Empty: { name: "", children: [] }, + }, + }, + sizeToText: { + control: "select", + options: ["Default", "Custom"], + mapping: { + Default: undefined, + Custom: customSizeToText, + }, + }, + colorScales: { + control: "select", + options: ["Default", "Custom"], + mapping: { + Default: undefined, + Custom: customColorScales, + }, + }, + error: { + control: "select", + options: ["None", "Error", "Custom Error"], + mapping: { + None: null, + Error: new Error(), + "Custom Error": new Error("Custom error message"), + }, + }, + }, + + render: (args) => { + const [currentPath, setCurrentPath] = useState([]); + const [tree, setTree] = useState(args.tree); + + useEffect(() => { + if (currentPath.length === 0) { + setTree(args.tree); + return; + } + const newChildren = + mockTree.children.filter( + (child) => currentPath[currentPath.length - 1] === child.name, + )[0]?.children || []; + const newTree = { + name: "", + children: newChildren, + }; + + setTree(newTree); + }, [currentPath, args.tree]); + + return ( + + ); + }, +}; diff --git a/packages/diracx-web-components/stories/mocks/contexts.mock.tsx b/packages/diracx-web-components/stories/mocks/contexts.mock.tsx new file mode 100644 index 00000000..7febcab7 --- /dev/null +++ b/packages/diracx-web-components/stories/mocks/contexts.mock.tsx @@ -0,0 +1,42 @@ +import React, { createContext, useContext } from "react"; +import { Job, JobHistory } from "../../src/types"; + +interface JobMockContextType { + jobs: Job[] | null; + jobHistory: JobHistory[] | null; + error: Error | null; + isLoading: boolean; +} + +interface MockProviderProps { + children: React.ReactNode; + jobs: Job[] | null; + jobHistory: JobHistory[] | null; + error?: Error | null; + isLoading?: boolean; +} + +const JobMockContext = createContext(undefined); + +export const useJobMockContext = () => { + const context = useContext(JobMockContext); + if (!context) { + console.error("useMockContext must be used within a MockProvider"); + throw new Error("useMockContext must be used within a MockProvider"); + } + return context; +}; + +export const JobMockProvider: React.FC = ({ + children, + jobs, + jobHistory, + error = null, + isLoading = false, +}): JSX.Element => { + return ( + + {children} + + ); +}; diff --git a/packages/diracx-web-components/stories/mocks/jobDataService.mock.ts b/packages/diracx-web-components/stories/mocks/jobDataService.mock.tsx similarity index 82% rename from packages/diracx-web-components/stories/mocks/jobDataService.mock.ts rename to packages/diracx-web-components/stories/mocks/jobDataService.mock.tsx index 786c3394..452f6a39 100644 --- a/packages/diracx-web-components/stories/mocks/jobDataService.mock.ts +++ b/packages/diracx-web-components/stories/mocks/jobDataService.mock.tsx @@ -1,15 +1,13 @@ /* eslint-disable */ import { Job, JobHistory, SearchBody, JobSummary } from "../../src/types"; +import { useJobMockContext } from "./contexts.mock"; + // Mock data store for jobs let mockJobsResponse: { jobs: Job[] | null; - error: Error | null; - isLoading: boolean; } = { jobs: null, - error: null, - isLoading: false, }; // Mock data store for job history @@ -24,11 +22,7 @@ let mockJobHistoryResponse: { }; // Function to set mock jobs data -export function setJobsMock(data: { - jobs: Job[] | null; - error: Error | null; - isLoading: boolean; -}) { +export function setJobsMock(data: { jobs: Job[] | null }) { mockJobsResponse = data; } @@ -41,42 +35,37 @@ export function setJobHistoryMock(data: { mockJobHistoryResponse = data; } -// Mock implementation of `useJobs` -export const useJobs = ( +export function useJobs( _diracxUrl: string | null, _accessToken: string, _searchBody: any, _page: number, _rowsPerPage: number, -) => { - if (mockJobsResponse.error) { - return { - data: undefined, - error: mockJobsResponse.error, - isLoading: mockJobsResponse.isLoading, - isValidating: false, - }; +) { + const { jobs, isLoading, error } = useJobMockContext(); + + if (isLoading) { + return { data: undefined, error: null, isLoading: true }; + } + + if (error) { + return { data: undefined, error, isLoading: false }; + } + + if (!jobs || jobs.length === 0) { + return { data: [], error: null, isLoading: false }; } - // Create headers with content-range for pagination const headers = new Headers(); - headers.append( - "content-range", - `jobs 0-${mockJobsResponse.jobs?.length || 0}/${mockJobsResponse.jobs?.length || 0}`, - ); + headers.append("content-range", `jobs 0-${jobs.length}/${jobs.length}`); return { - data: mockJobsResponse.jobs - ? { - headers, - data: mockJobsResponse.jobs, - } - : undefined, + headers, + data: jobs, error: null, - isLoading: mockJobsResponse.isLoading, - isValidating: false, + isLoading: false, }; -}; +} // Mock implementation of `getJobHistory` export const getJobHistory = async ( @@ -177,6 +166,7 @@ export async function getJobSummary( VO: "VOA", UserPriority: 100, RescheduleCounter: 0, + count: 10, }, { Status: "Completed", @@ -191,6 +181,7 @@ export async function getJobSummary( VO: "VOB", UserPriority: 200, RescheduleCounter: 1, + count: 5, }, { Status: "Failed", @@ -205,6 +196,7 @@ export async function getJobSummary( VO: "VOC", UserPriority: 300, RescheduleCounter: 2, + count: 2, }, ], }); diff --git a/packages/diracx-web-components/stories/mocks/style.mock.ts b/packages/diracx-web-components/stories/mocks/style.mock.ts new file mode 100644 index 00000000..f053ebf7 --- /dev/null +++ b/packages/diracx-web-components/stories/mocks/style.mock.ts @@ -0,0 +1 @@ +module.exports = {}; diff --git a/packages/diracx-web-components/test/ChartDisplayLayout.test.tsx b/packages/diracx-web-components/test/ChartDisplayLayout.test.tsx new file mode 100644 index 00000000..c5c1927e --- /dev/null +++ b/packages/diracx-web-components/test/ChartDisplayLayout.test.tsx @@ -0,0 +1,27 @@ +import { render, screen } from "@testing-library/react"; +import { composeStories } from "@storybook/react"; +import * as stories from "../stories/ChartDisplayLayout.stories"; +import "@testing-library/jest-dom"; + +// Compose the stories to get actual Storybook behavior (decorators, args, etc) +const { Default } = composeStories(stories); + +describe("ChartDisplayLayout", () => { + it("renders the element", () => { + render(); + expect(screen.getByText("Select Columns")).toBeInTheDocument(); + expect(screen.getByText("Level 1")).toBeInTheDocument(); + }); + + it("renders custom title", () => { + render(); + expect(screen.getByText("Custom title")).toBeInTheDocument(); + }); + + it("renders with columns", () => { + render(); + + expect(screen.getByDisplayValue("Custom Column 1")).toBeInTheDocument(); + expect(screen.getByDisplayValue("Custom Column 2")).toBeInTheDocument(); + }); +}); diff --git a/packages/diracx-web-components/test/JobMonitor.test.tsx b/packages/diracx-web-components/test/JobMonitor.test.tsx index 3452bd5a..75af4751 100644 --- a/packages/diracx-web-components/test/JobMonitor.test.tsx +++ b/packages/diracx-web-components/test/JobMonitor.test.tsx @@ -11,7 +11,7 @@ import * as stories from "../stories/JobMonitor.stories"; import "@testing-library/jest-dom"; // Compose Storybook stories (includes all decorators/args) -const { Default, Loading, Empty, Error } = composeStories(stories); +const { Default, Loading, Empty, WithError } = composeStories(stories); describe("JobMonitor", () => { it("renders the job monitor component", async () => { @@ -34,13 +34,11 @@ describe("JobMonitor", () => { }); it("renders error state when data fetch fails", async () => { - const { getByText } = render(); + const { getByText } = render(); // Verify error message await waitFor(() => { - expect( - getByText("An error occurred while fetching data. Reload the page."), - ).toBeInTheDocument(); + expect(getByText("Custom error message here")).toBeInTheDocument(); }); }); diff --git a/packages/diracx-web-components/test/Sunburst.test.tsx b/packages/diracx-web-components/test/Sunburst.test.tsx new file mode 100644 index 00000000..64fd0ac9 --- /dev/null +++ b/packages/diracx-web-components/test/Sunburst.test.tsx @@ -0,0 +1,175 @@ +import { render, screen } from "@testing-library/react"; +import { Sunburst } from "../src/components/shared/Sunburst/Sunburst"; +import { SunburstTree } from "../src/types"; +import "@testing-library/jest-dom"; + +// Sample tree data for testing +const mockTree: SunburstTree = { + name: "Root", + value: 2900, + children: [ + { + name: "Production", + value: 1500, + children: [ + { name: "Running", value: 800 }, + { name: "Completed", value: 500 }, + { name: "Failed", value: 200 }, + ], + }, + { + name: "Development", + value: 800, + children: [ + { name: "Testing", value: 400 }, + { name: "Debugging", value: 300 }, + { name: "Review", value: 100 }, + ], + }, + { + name: "Maintenance", + value: 600, + children: [ + { name: "Updates", value: 300 }, + { name: "Backups", value: 200 }, + { name: "Monitoring", value: 100 }, + ], + }, + ], +}; + +// Sample tree data with small segments that should be grouped into "Others" + +// Empty tree for testing +const emptyTree: SunburstTree = { + name: "Empty", + value: 0, + children: [], +}; + +// Custom size to text function for testing +const customSizeToText = (size: number, total?: number) => { + if (total) { + return `${size} of ${total} (${Math.round((size / total) * 100)}%)`; + } + return `${size} items`; +}; + +describe("Sunburst Component", () => { + describe("Rendering States", () => { + test("renders loading skeleton when isLoading is true", () => { + render(); + const loadingSkeleton = screen.getByTestId("loading-skeleton"); + expect(loadingSkeleton).toBeInTheDocument(); + }); + + test("renders error message when error is provided", () => { + const errorMessage = "Failed to load data"; + render( + , + ); + const errorAlert = screen.getByText(errorMessage); + expect(errorAlert).toBeInTheDocument(); + }); + + test("renders default error message when error object has no message", () => { + render( + , + ); + const defaultErrorMessage = screen.getByText( + "An error occurred while loading the data.", + ); + expect(defaultErrorMessage).toBeInTheDocument(); + }); + + test("renders the sunburst chart when data is provided and not loading or error", () => { + render(); + // Since D3 is mocked, we check that SVG is rendered + const svg = document.querySelector("svg"); + expect(svg).toBeInTheDocument(); + }); + + test("handles empty tree data", () => { + render(); + const svg = document.querySelector("svg"); + expect(svg).toBeInTheDocument(); + // We don't expect any errors to be thrown + }); + }); + + describe("Custom Rendering", () => { + test("uses custom size to text function when provided", () => { + render( + , + ); + + // Since D3 is mocked, we can't directly test the text content + // but we can verify the component rendered without errors + const svg = document.querySelector("svg"); + expect(svg).toBeInTheDocument(); + }); + }); + + describe("Performance", () => { + test("handles large datasets without crashing", () => { + // Create a large dataset + const largeTree: SunburstTree = { + name: "Root", + value: 0, + children: [], + }; + + // Add 5000 children + for (let i = 0; i < 5000; i++) { + largeTree.children!.push({ + name: `Node ${i}`, + value: i + 1, + }); + } + + // This should render without crashing + render(); + const svg = document.querySelector("svg"); + expect(svg).toBeInTheDocument(); + }); + }); + + describe("Component Lifecycle", () => { + test("component should update when current path changes", () => { + const setCurrentPathMock = jest.fn(); + const { rerender } = render( + , + ); + + // Rerender with different current path + rerender( + , + ); + + // In a real test environment, we would check if the visualization has updated + const svg = document.querySelector("svg"); + expect(svg).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/diracx-web/.gitignore b/packages/diracx-web/.gitignore index 145d1eae..5642b75c 100644 --- a/packages/diracx-web/.gitignore +++ b/packages/diracx-web/.gitignore @@ -7,6 +7,7 @@ # testing /coverage +/cypress # next.js /.next/ diff --git a/packages/diracx-web/test/e2e/jobMonitor.cy.ts b/packages/diracx-web/test/e2e/jobMonitor.cy.ts index fba480c9..889cd132 100644 --- a/packages/diracx-web/test/e2e/jobMonitor.cy.ts +++ b/packages/diracx-web/test/e2e/jobMonitor.cy.ts @@ -200,6 +200,8 @@ describe("Job Monitor", () => { } }); + cy.wait(1000); // Wait for the table to update + let firstValue: number; let lastValue: number; @@ -213,7 +215,7 @@ describe("Job Monitor", () => { firstValue = parseInt(text.trim(), 10); }); - // Scroll and get the last visible row value (e.g. 6) + // Scroll and get the last visible row value cy.get('[data-testid="virtuoso-scroller"]') .wait(100) .scrollTo("bottom", { ensureScrollable: false }); @@ -256,9 +258,9 @@ describe("Job Monitor", () => { }); it("should delete jobs", () => { - cy.get("[data-index=1]").as("jobItem1"); - cy.get("[data-index=2]").as("jobItem2"); - cy.get("[data-index=3]").as("jobItem3"); + cy.get("[data-index=0]").as("jobItem1"); + cy.get("[data-index=1]").as("jobItem2"); + cy.get("[data-index=2]").as("jobItem3"); cy.get("@jobItem1").click({ force: true }); cy.get("@jobItem2").click({ force: true }); cy.get("@jobItem3").click({ force: true }); @@ -272,20 +274,42 @@ describe("Job Monitor", () => { cy.get("@jobItem3").find("td").eq(2).should("contain", "Deleted"); }); - // FIXME - // This test can't pass because the reschedule functionality is not completly working yet + // ### FIXME: The reschedule functionality is not working as expected ### + // The test below would be decommented once the reschedule functionality is fixed in diracx + // it("should reschedule jobs", () => { - // cy.get("[data-index=1]").click({ force: true }); - // cy.get("[data-index=2]").click({ force: true }); - // cy.get("[data-index=3]").click({ force: true }); + // cy.wait(1000); // Wait for the table to load + + // cy.get("[data-testid=search-field]").type("Reschedule Counter{enter}!={enter}3{enter}"); + + // cy.wait(1000); // Wait for the search to complete + + // // Create aliases for the job items + // cy.get("[data-index=0]").as("jobItem1"); + // cy.get("[data-index=1]").as("jobItem2"); + // cy.get("[data-index=2]").as("jobItem3"); + // // First, kill the jobs to ensure they can be rescheduled + // cy.get("@jobItem1").click({ force: true }); + // cy.get("@jobItem2").click({ force: true }); + // cy.get("@jobItem3").click({ force: true }); + + // cy.get('[data-testid="ClearIcon"] > path').click(); + + // // Then, select the jobs to reschedule + // cy.get("@jobItem1").click({ force: true }); + // cy.get("@jobItem2").click({ force: true }); + // cy.get("@jobItem3").click({ force: true }); + + // cy.get('[data-testid="ReplayIcon"] > path').click({ force: true }); + // cy.get('[aria-label="Reschedule"]').click({ force: true }); // cy.get('[data-testid="ReplayIcon"] > path').click({ force: true }); // cy.get('[aria-label="Reschedule"]').click({ force: true }); // // Make sure the job status is "Received" + // cy.get("[data-index=0]").find("td").eq(2).should("contain", "Received"); // cy.get("[data-index=1]").find("td").eq(2).should("contain", "Received"); // cy.get("[data-index=2]").find("td").eq(2).should("contain", "Received"); - // cy.get("[data-index=3]").find("td").eq(2).should("contain", "Received"); // }); /** Column interactions */ @@ -301,17 +325,23 @@ describe("Job Monitor", () => { } }); + cy.wait(1000); // Wait for the table to load + // Click on the visibility icon cy.get('[data-testid="VisibilityIcon"] > path').click(); cy.get('[data-testid="column-visibility-popover"]').should("be.visible"); // Hide the "Status" column and Show the "VO" column - cy.get('[data-testid="column-visibility-popover"]').within(() => { - cy.contains("VO").parent().find('input[type="checkbox"]').click(); - }); - cy.get('[data-testid="column-visibility-popover"]').within(() => { - cy.contains("Status").parent().find('input[type="checkbox"]').click(); - }); + cy.get('[data-testid="column-visibility-popover"]') + .contains("Status") + .parent() + .find('input[type="checkbox"]') + .click(); + cy.get('[data-testid="column-visibility-popover"]') + .contains("VO") + .parent() + .find('input[type="checkbox"]') + .click(); // Close the popover by clicking outside cy.get("body").click(0, 0); @@ -405,7 +435,7 @@ describe("Job Monitor", () => { cy.get("[data-testid=search-field]").type("ID{enter}={enter}1{enter}"); - cy.get('[role="group"]').find("button").should("have.length", 3); + cy.get('[role="group"]').find("button").should("have.length", 5); }); it("should handle filter editing", () => { @@ -422,13 +452,11 @@ describe("Job Monitor", () => { cy.get("[data-testid=search-field]").type("Name{enter}={enter}test{enter}"); - cy.get("[data-testid=search-field]").type("ID{enter}={enter}1{enter}"); - - cy.get('[role="group"]').find("button").should("have.length", 6); + cy.get('[role="group"]').find("button").should("have.length", 5); cy.get('[data-testid="DeleteIcon"]').click(); - cy.get('[role="group"]').should("not.exist"); + cy.get('[role="group"]').find("button").should("have.length", 2); }); it("should handle filter apply and persist", () => { @@ -447,7 +475,7 @@ describe("Job Monitor", () => { `ID{enter}={enter}${jobID}{enter}`, ); }); - cy.get('[role="group"]').find("button").should("have.length", 3); + cy.get('[role="group"]').find("button").should("have.length", 5); cy.get('[role="group"]').find("button").contains("ID").should("exist"); // Wait for the filter to apply @@ -459,20 +487,32 @@ describe("Job Monitor", () => { it("should handle filter apply and save filters in dashboard", () => { cy.get("table").should("be.visible"); - cy.get("[data-testid=search-field]").type("ID{enter}={enter}5{enter}"); + let jobID: string; + cy.get("table tbody tr") + .first() + .find("td") + .eq(1) + .invoke("text") + .then((text) => { + jobID = text.trim(); + + cy.get("[data-testid=search-field]").type( + `ID{enter}={enter}${jobID}{enter}`, + ); + }); // Wait for the filter to apply cy.wait(1000); - cy.get('[role="group"]').find("button").should("have.length", 3); + cy.get('[role="group"]').find("button").should("have.length", 5); cy.get(".MuiButtonBase-root").contains("Job Monitor 2").click(); - cy.get('[role="group"]').should("not.exist"); + cy.get('[role="group"]').find("button").should("have.length", 2); cy.get(".MuiButtonBase-root").contains("Job Monitor").click(); - cy.get('[role="group"]').find("button").should("have.length", 3); + cy.get('[role="group"]').find("button").should("have.length", 5); }); it("should control the in the last operator utilization", () => { @@ -484,8 +524,21 @@ describe("Job Monitor", () => { // Wait for the filter to apply cy.wait(1000); - cy.get('[role="group"]').find("button").should("have.length", 3); + cy.get('[role="group"]').find("button").should("have.length", 5); cy.get("table").should("be.visible"); }); + + /** Sunburst */ + + it("should render the sunburst chart", () => { + // Click on the sunburst button + cy.get('[role="group"]').last().click(); + + // Make sure the sunburst chart is visible + cy.get('[data-testid="sunburst-chart"]').should("be.visible"); + + // Make sure the column selector is visible + cy.get('[data-testid="column-selector"]').should("be.visible"); + }); }); diff --git a/packages/diracx-web/test/e2e/loginOut.cy.ts b/packages/diracx-web/test/e2e/loginOut.cy.ts index db192f25..a0c77368 100644 --- a/packages/diracx-web/test/e2e/loginOut.cy.ts +++ b/packages/diracx-web/test/e2e/loginOut.cy.ts @@ -69,4 +69,31 @@ describe("Login and Logout", () => { cy.visit("/"); cy.url().should("include", "/auth"); }); + + it("prevent infinite login loop", () => { + // The user is redirected to the /auth page because not authenticated + // Make sure we are on the /auth page + cy.url().should("include", "/auth"); + + cy.url().then((currentUrl) => { + cy.wait(2000); // Wait 2 seconds to let a possible infinite loop happen + + // Check that the URL has not changed + cy.url().should("eq", currentUrl); + }); + + // Login + cy.get('[data-testid="button-login"]').click(); + cy.get("#login").type("admin@example.com"); + cy.get("#password").type("password"); + + // Find the login button and click on it + cy.get("button").click(); + // Grant access + cy.get(":nth-child(1) > form > .dex-btn").click(); + cy.url().should("include", "/auth"); + + // The user is redirected to the dashboard page + cy.url().should("eq", Cypress.config().baseUrl + "/"); + }); }); diff --git a/packages/extensions/src/gubbins/components/OwnerMonitor/OwnerMonitor.tsx b/packages/extensions/src/gubbins/components/OwnerMonitor/OwnerMonitor.tsx index ef49e8e0..c7171b6e 100644 --- a/packages/extensions/src/gubbins/components/OwnerMonitor/OwnerMonitor.tsx +++ b/packages/extensions/src/gubbins/components/OwnerMonitor/OwnerMonitor.tsx @@ -142,7 +142,6 @@ export default function OwnerMonitor() { setSearchBody={() => {}} error={null} isLoading={isLoading} - isValidating={isLoading} toolbarComponents={<>} menuItems={[]} /> From c8a8a67f1d4ecd86ad9c159b9177060e8f516795 Mon Sep 17 00:00:00 2001 From: theau Date: Mon, 21 Jul 2025 10:21:48 +0200 Subject: [PATCH 2/4] fix: review suggestions --- docs/user/web/monitor_jobs.md | 3 ++ .../components/JobMonitor/JobDataTable.tsx | 2 +- .../src/components/JobMonitor/JobMonitor.tsx | 29 ++++++++++++------- .../src/components/JobMonitor/JobSunburst.tsx | 11 +++---- .../components/JobMonitor/jobDataService.ts | 6 ++-- .../components/shared/ChartDisplayLayout.tsx | 10 +++++-- .../src/components/shared/ColumnSelector.tsx | 12 ++++++-- .../stories/ChartDisplayLayout.stories.tsx | 11 ++++--- .../stories/JobMonitor.stories.tsx | 2 +- .../test/ChartDisplayLayout.test.tsx | 6 ++-- 10 files changed, 59 insertions(+), 33 deletions(-) diff --git a/docs/user/web/monitor_jobs.md b/docs/user/web/monitor_jobs.md index 95fc67c4..e82ea856 100644 --- a/docs/user/web/monitor_jobs.md +++ b/docs/user/web/monitor_jobs.md @@ -17,6 +17,7 @@ ## Use the table +By default, the jobs are displayed in a table. If you are viewing them in another chart, you can click the table button next to the search bar to switch back to the table view. The table displays the jobs that match the criteria specified in the search bar. Each row represents a job, and the columns show various attributes of the job, such as its ID, status, type, and submission date. ### Actions on the table @@ -32,3 +33,5 @@ You can select one or more jobs by clicking on the checkboxes next to each job. - **Kill**: This button will kill the selected jobs. - **Delete**: This button will delete the selected jobs. +## Use the Pie Chart +You can change the visualization to use a pie chart with the button next to the search bar. The pie chart provides a hierarchical view of the jobs based on their attributes. The `Columns to plot` component lets you choose your criteria for visualizing the jobs. The chart can display two levels, and you can then click on a section of the chart to zoom into that category and see more details. diff --git a/packages/diracx-web-components/src/components/JobMonitor/JobDataTable.tsx b/packages/diracx-web-components/src/components/JobMonitor/JobDataTable.tsx index 70af70d2..9ac7749d 100644 --- a/packages/diracx-web-components/src/components/JobMonitor/JobDataTable.tsx +++ b/packages/diracx-web-components/src/components/JobMonitor/JobDataTable.tsx @@ -405,7 +405,7 @@ export function JobDataTable({ * Table instance */ const table = useReactTable({ - data: results || [], + data: useMemo(() => results || [], [results]), columns, state: { columnVisibility, diff --git a/packages/diracx-web-components/src/components/JobMonitor/JobMonitor.tsx b/packages/diracx-web-components/src/components/JobMonitor/JobMonitor.tsx index b2af10c8..e8f2d543 100644 --- a/packages/diracx-web-components/src/components/JobMonitor/JobMonitor.tsx +++ b/packages/diracx-web-components/src/components/JobMonitor/JobMonitor.tsx @@ -21,6 +21,7 @@ import { Box, ToggleButtonGroup, ToggleButton, + Tooltip, } from "@mui/material"; import { TableChart, DonutSmall } from "@mui/icons-material"; @@ -108,7 +109,7 @@ export default function JobMonitor() { }, ); - const [chartType, setChartType] = useState("table"); + const [chartType, setChartType] = useState("CHART_TYPE_TABLE"); // Save the state of the app in local storage useEffect(() => { @@ -292,7 +293,11 @@ export default function JobMonitor() { values, })), })); - }, [filters, columns]); + setPagination((prev) => ({ + ...prev, + pageIndex: 0, // Reset to the first page when applying filters + })); + }, [filters, columns, setSearchBody, setPagination]); return ( - {chartType === "table" && ( + {chartType === "CHART_TYPE_TABLE" && ( )} - {chartType === "sunburst" && ( + {chartType === "CHART_TYPE_SUNBURST" && ( - - - - - - + + + + + + + + + + ); diff --git a/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx b/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx index 2eba72fd..a2bdfc9e 100644 --- a/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx +++ b/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx @@ -82,7 +82,7 @@ export function JobSunburst({ ]); const defaultColors = scaleOrdinal( - quantize(interpolateRainbow, tree?.children?.length || 0 + 1), + quantize(interpolateRainbow, (tree?.children?.length ?? 0) + 1), ); function colorScales(name: string, _size: number, _depth: number): string { @@ -116,10 +116,11 @@ export function JobSunburst({ return ( 1e9) return ( - `${(size / 1e9).toFixed(1)} billion jobs` + + `${(size / 1e9).toFixed(2)}B \njobs` + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") ); if (size > 1e6) return ( - `${(size / 1e6).toFixed(1)} million jobs` + + `${(size / 1e6).toFixed(2)}M \njobs` + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") ); if (size > 1e3) return ( - `${(size / 1e3).toFixed(1)} thousand\njobs` + + `${(size / 1e3).toFixed(2)}k \njobs` + (total ? ` (${((size / total) * 100).toFixed(2)}%)` : "") ); if (size > 1) diff --git a/packages/diracx-web-components/src/components/JobMonitor/jobDataService.ts b/packages/diracx-web-components/src/components/JobMonitor/jobDataService.ts index 091d98b0..17d7b675 100644 --- a/packages/diracx-web-components/src/components/JobMonitor/jobDataService.ts +++ b/packages/diracx-web-components/src/components/JobMonitor/jobDataService.ts @@ -252,7 +252,7 @@ export function useJobs( page: number, rowsPerPage: number, ) { - const [data, setData] = useState([]); + const [data, setData] = useState(null); const [headers, setHeaders] = useState(new Headers()); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -261,7 +261,7 @@ export function useJobs( if (!diracxUrl) { setData(null); setIsLoading(false); - setError(Error("Invalid URL generated for fetching jobs.")); + setError(new Error("Invalid URL generated for fetching jobs.")); return; } @@ -296,7 +296,7 @@ export function useJobs( } catch { setData(null); setIsLoading(false); - setError(Error("Failed to fetch jobs")); + setError(new Error("Failed to fetch jobs")); } } fetchJobs(); diff --git a/packages/diracx-web-components/src/components/shared/ChartDisplayLayout.tsx b/packages/diracx-web-components/src/components/shared/ChartDisplayLayout.tsx index 2271753d..4dff4b4d 100644 --- a/packages/diracx-web-components/src/components/shared/ChartDisplayLayout.tsx +++ b/packages/diracx-web-components/src/components/shared/ChartDisplayLayout.tsx @@ -6,13 +6,15 @@ import { ColumnSelector } from "./ColumnSelector"; interface ChartDisplayLayoutProps { /** The chart to be displayed */ - Chart: JSX.Element; + chart: JSX.Element; /** List of columns available for selection */ columnList: string[]; /** Currently selected group columns */ groupColumns: string[]; /** Function to set the group columns */ setGroupColumns: React.Dispatch>; + /** The current path in the chart */ + currentPath: string[]; /** Function to set the current path in the chart */ setCurrentPath: React.Dispatch>; /** Default group columns to be used */ @@ -29,10 +31,11 @@ interface ChartDisplayLayoutProps { * @returns */ export function ChartDisplayLayout({ - Chart, + chart, columnList, groupColumns, setGroupColumns, + currentPath, setCurrentPath, defaultColumns: defaultGroupColumns, title = "Level selector", @@ -57,7 +60,7 @@ export function ChartDisplayLayout({ overflow: "auto", }} > - {Chart} + {chart} {/* Right Section: Column selection */} @@ -75,6 +78,7 @@ export function ChartDisplayLayout({ columnList={columnList} groupColumns={groupColumns} setGroupColumns={setGroupColumns} + currentPath={currentPath} setCurrentPath={setCurrentPath} defaultColumns={defaultGroupColumns} title={title} diff --git a/packages/diracx-web-components/src/components/shared/ColumnSelector.tsx b/packages/diracx-web-components/src/components/shared/ColumnSelector.tsx index 54b24ae6..f8ddd260 100644 --- a/packages/diracx-web-components/src/components/shared/ColumnSelector.tsx +++ b/packages/diracx-web-components/src/components/shared/ColumnSelector.tsx @@ -31,6 +31,8 @@ interface SelectColumnsProps { groupColumns: string[]; /** Setter for groupColumns */ setGroupColumns: React.Dispatch>; + /** The current path in the tree */ + currentPath: string[]; /** Setter for the current path in the tree */ setCurrentPath: React.Dispatch>; /** Default columns to use */ @@ -50,6 +52,7 @@ export function ColumnSelector({ columnList, groupColumns, setGroupColumns, + currentPath, setCurrentPath, defaultColumns: defaultGroupColumns, title = "Column Selector", @@ -65,14 +68,17 @@ export function ColumnSelector({ if (event.target.value === "None") { // Delete a column newGroups = newGroups.filter((_elt, index) => index !== depth); - if (newGroups.length > 0) - setCurrentPath((currentPath) => currentPath.slice(0, depth - 1)); + if (newGroups.length > 0 && currentPath.length > depth) + setCurrentPath((currentPath) => + currentPath.slice(0, Math.max(0, depth - 1)), + ); } else { // Add or change a column if (newGroups[depth]) { // Change the column newGroups[depth] = event.target.value; - setCurrentPath((currentPath) => currentPath.slice(0, depth)); + if (currentPath.length > depth) + setCurrentPath((currentPath) => currentPath.slice(0, depth)); } else { // Add a column newGroups.push(event.target.value); diff --git a/packages/diracx-web-components/stories/ChartDisplayLayout.stories.tsx b/packages/diracx-web-components/stories/ChartDisplayLayout.stories.tsx index 68d2418d..7f3d0f76 100644 --- a/packages/diracx-web-components/stories/ChartDisplayLayout.stories.tsx +++ b/packages/diracx-web-components/stories/ChartDisplayLayout.stories.tsx @@ -59,16 +59,17 @@ type Story = StoryObj; export const Default: Story = { args: { - Chart:
Nothing
, + chart:
Nothing
, columnList: ["Column 1", "Column 2", "Column 3"], groupColumns: ["Column 1"], setGroupColumns: () => {}, + currentPath: [], setCurrentPath: () => {}, defaultColumns: ["Column 1"], title: "Select Columns", }, argTypes: { - Chart: { + chart: { control: { type: "select" }, options: ["Sunburst", "None"], mapping: { @@ -85,15 +86,17 @@ export const Default: Story = { }, render: (args) => { const [groupColumns, setGroupColumns] = useState(args.groupColumns); + const [currentPath, setCurrentPath] = useState(args.currentPath); return ( diff --git a/packages/diracx-web-components/stories/JobMonitor.stories.tsx b/packages/diracx-web-components/stories/JobMonitor.stories.tsx index 15cd3d2b..a741b957 100644 --- a/packages/diracx-web-components/stories/JobMonitor.stories.tsx +++ b/packages/diracx-web-components/stories/JobMonitor.stories.tsx @@ -149,7 +149,7 @@ export const WithError: Story = { diff --git a/packages/diracx-web-components/test/ChartDisplayLayout.test.tsx b/packages/diracx-web-components/test/ChartDisplayLayout.test.tsx index c5c1927e..d50b3892 100644 --- a/packages/diracx-web-components/test/ChartDisplayLayout.test.tsx +++ b/packages/diracx-web-components/test/ChartDisplayLayout.test.tsx @@ -19,9 +19,9 @@ describe("ChartDisplayLayout", () => { }); it("renders with columns", () => { - render(); + render(); - expect(screen.getByDisplayValue("Custom Column 1")).toBeInTheDocument(); - expect(screen.getByDisplayValue("Custom Column 2")).toBeInTheDocument(); + expect(screen.getByDisplayValue("Column 1")).toBeInTheDocument(); + expect(screen.getByDisplayValue("Column 2")).toBeInTheDocument(); }); }); From 254368b5648e5853c9a0e59f1c2bb341017e21bc Mon Sep 17 00:00:00 2001 From: theau Date: Thu, 31 Jul 2025 16:40:13 +0200 Subject: [PATCH 3/4] fix: use Others only when there is more than 1 small category --- .../src/components/JobMonitor/JobSunburst.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx b/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx index a2bdfc9e..2c9abef3 100644 --- a/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx +++ b/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx @@ -214,7 +214,7 @@ function buildTree( othersValue = { name: key, value: groupTotal }; else othersValue = { - name: "Others", + name: key, children: buildTree(group, groupColumns.slice(1), [ ...parentPath, key, From cf0fec08e014246fc30fea948c9c0a106170909d Mon Sep 17 00:00:00 2001 From: theau Date: Thu, 7 Aug 2025 15:17:37 +0200 Subject: [PATCH 4/4] fix: review suggestions --- .../src/components/JobMonitor/JobMonitor.tsx | 77 +++----- .../components/JobMonitor/JobSearchBar.tsx | 12 ++ .../src/components/JobMonitor/JobSunburst.tsx | 4 +- .../ChartView.tsx} | 10 +- .../shared/{ => ChartView}/ColumnSelector.tsx | 0 .../src/components/shared/ChartView/index.ts | 1 + .../shared/SearchBar/PlotTypeSelector.tsx | 45 +++++ .../components/shared/SearchBar/SearchBar.tsx | 180 ++++++++++-------- .../shared/Sunburst/BreadCrumbsTrail.tsx | 52 +++++ .../components/shared/Sunburst/Sunburst.tsx | 5 +- .../src/components/shared/Sunburst/Utils.tsx | 53 ------ .../src/components/shared/index.ts | 2 +- .../src/types/JobMonitorChartType.ts | 7 + .../diracx-web-components/src/types/index.ts | 1 + ...yout.stories.tsx => ChartView.stories.tsx} | 10 +- .../stories/mocks/style.mock.ts | 1 - ...playLayout.test.tsx => ChartView.test.tsx} | 4 +- .../test/Sunburst.test.tsx | 19 ++ packages/diracx-web/test/e2e/loginOut.cy.ts | 27 --- 19 files changed, 281 insertions(+), 229 deletions(-) rename packages/diracx-web-components/src/components/shared/{ChartDisplayLayout.tsx => ChartView/ChartView.tsx} (89%) rename packages/diracx-web-components/src/components/shared/{ => ChartView}/ColumnSelector.tsx (100%) create mode 100644 packages/diracx-web-components/src/components/shared/ChartView/index.ts create mode 100644 packages/diracx-web-components/src/components/shared/SearchBar/PlotTypeSelector.tsx create mode 100644 packages/diracx-web-components/src/components/shared/Sunburst/BreadCrumbsTrail.tsx create mode 100644 packages/diracx-web-components/src/types/JobMonitorChartType.ts rename packages/diracx-web-components/stories/{ChartDisplayLayout.stories.tsx => ChartView.stories.tsx} (91%) delete mode 100644 packages/diracx-web-components/stories/mocks/style.mock.ts rename packages/diracx-web-components/test/{ChartDisplayLayout.test.tsx => ChartView.test.tsx} (89%) diff --git a/packages/diracx-web-components/src/components/JobMonitor/JobMonitor.tsx b/packages/diracx-web-components/src/components/JobMonitor/JobMonitor.tsx index e8f2d543..9ff741fd 100644 --- a/packages/diracx-web-components/src/components/JobMonitor/JobMonitor.tsx +++ b/packages/diracx-web-components/src/components/JobMonitor/JobMonitor.tsx @@ -14,15 +14,7 @@ import { amber, } from "@mui/material/colors"; -import { - lighten, - darken, - useTheme, - Box, - ToggleButtonGroup, - ToggleButton, - Tooltip, -} from "@mui/material"; +import { lighten, darken, useTheme, Box } from "@mui/material"; import { TableChart, DonutSmall } from "@mui/icons-material"; @@ -37,7 +29,12 @@ import { import { useApplicationId } from "../../hooks/application"; import { Filter } from "../../types/Filter"; -import { Job, SearchBody, CategoryType } from "../../types"; +import { + Job, + SearchBody, + CategoryType, + JobMonitorChartType, +} from "../../types"; import { JobDataTable } from "./JobDataTable"; import { JobSearchBar } from "./JobSearchBar"; import { JobSunburst } from "./JobSunburst"; @@ -109,7 +106,7 @@ export default function JobMonitor() { }, ); - const [chartType, setChartType] = useState("CHART_TYPE_TABLE"); + const [chartType, setChartType] = useState(JobMonitorChartType.TABLE); // Save the state of the app in local storage useEffect(() => { @@ -320,11 +317,24 @@ export default function JobMonitor() { searchBody={searchBody} handleApplyFilters={handleApplyFilters} columns={columns} + plotTypeSelectorProps={{ + plotType: chartType, + setPlotType: setChartType, + buttonList: [ + { + plotName: JobMonitorChartType.TABLE, + icon: , + }, + { + plotName: JobMonitorChartType.SUNBURST, + icon: , + }, + ], + }} /> - - {chartType === "CHART_TYPE_TABLE" && ( + {chartType === JobMonitorChartType.TABLE && ( )} - {chartType === "CHART_TYPE_SUNBURST" && ( + {chartType === JobMonitorChartType.SUNBURST && ( >; -}) { - return ( - <> - { - if (val !== null) setPlotType(val); - }} - aria-label="text alignment" - > - - - - - - - - - - - - - ); -} diff --git a/packages/diracx-web-components/src/components/JobMonitor/JobSearchBar.tsx b/packages/diracx-web-components/src/components/JobMonitor/JobSearchBar.tsx index a1ea9fac..ac8810f7 100644 --- a/packages/diracx-web-components/src/components/JobMonitor/JobSearchBar.tsx +++ b/packages/diracx-web-components/src/components/JobMonitor/JobSearchBar.tsx @@ -17,6 +17,7 @@ import { Operators, SearchBarTokenNature, CategoryType, + JobMonitorChartType, } from "../../types"; import { getJobSummary } from "./jobDataService"; import { fromHumanReadableText } from "./JobMonitor"; @@ -33,6 +34,15 @@ interface JobSearchBarProps { /** The columns to display in the job monitor */ // eslint-disable-next-line @typescript-eslint/no-explicit-any columns: ColumnDef[]; + /** Props for the plot type selector */ + plotTypeSelectorProps?: { + /** The type of the plot */ + plotType: JobMonitorChartType; + /** Function to set the plot type */ + setPlotType: React.Dispatch>; + /** List of buttons to select the type of plot */ + buttonList?: { plotName: JobMonitorChartType; icon: React.ReactNode }[]; + }; } export function JobSearchBar({ @@ -41,6 +51,7 @@ export function JobSearchBar({ setFilters, handleApplyFilters, columns, + plotTypeSelectorProps, }: JobSearchBarProps) { const { configuration } = useOIDCContext(); const { accessToken } = useOidcAccessToken(configuration?.scope); @@ -71,6 +82,7 @@ export function JobSearchBar({ ) } allowKeyWordSearch={false} // Disable keyword search for job monitor + plotTypeSelectorProps={plotTypeSelectorProps} /> ); } diff --git a/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx b/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx index 2c9abef3..95ac449b 100644 --- a/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx +++ b/packages/diracx-web-components/src/components/JobMonitor/JobSunburst.tsx @@ -9,7 +9,7 @@ import { useDiracxUrl } from "../../hooks/utils"; import type { JobSummary, SearchBody, Job, SunburstTree } from "../../types"; import { Sunburst } from "../shared/Sunburst"; import { useOIDCContext } from "../../hooks/oidcConfiguration"; -import { ChartDisplayLayout } from "../shared"; +import { ChartView } from "../shared"; import { getJobSummary } from "./jobDataService"; import { fromHumanReadableText } from "./JobMonitor"; @@ -115,7 +115,7 @@ export function JobSunburst({ ); return ( - { + /** The type of the plot */ + plotType: T; + /** Function to set the plot type */ + setPlotType: React.Dispatch>; + /** List of name and JSX elements to display as buttons */ + buttonList?: { plotName: T; icon: ReactNode }[]; +} + +/** + * Component to select the type of plot. + * + * @param plotType The type of the plot. + * @param setPlotType The setter for the plot type. + * @param buttonList List of buttons to select the type of plot. + * @returns A selector for the plot type. + */ +export function PlotTypeSelector({ + plotType, + setPlotType, + buttonList = [], +}: PlotTypeSelectorProps) { + return ( + { + if (val !== null) setPlotType(val); + }} + aria-label="text alignment" + > + {buttonList.map((button) => ( + + + {button.icon} + + + ))} + + ); +} diff --git a/packages/diracx-web-components/src/components/shared/SearchBar/SearchBar.tsx b/packages/diracx-web-components/src/components/shared/SearchBar/SearchBar.tsx index e0dd295e..411dddd9 100644 --- a/packages/diracx-web-components/src/components/shared/SearchBar/SearchBar.tsx +++ b/packages/diracx-web-components/src/components/shared/SearchBar/SearchBar.tsx @@ -30,8 +30,9 @@ import { } from "./defaultFunctions"; import SearchField from "./SearchField"; +import { PlotTypeSelector } from "./PlotTypeSelector"; -export interface SearchBarProps { +export interface SearchBarProps { /** The filters to be applied to the search */ filters: Filter[]; /** The function to set the filters */ @@ -56,6 +57,11 @@ export interface SearchBarProps { ) => void; /** Whether to allow keyword search or not (default is true) */ allowKeyWordSearch?: boolean; + plotTypeSelectorProps?: { + plotType: T; + setPlotType: React.Dispatch>; + buttonList?: { plotName: T; icon: React.ReactNode }[]; + }; } /** @@ -65,14 +71,15 @@ export interface SearchBarProps { * @param props - The properties for the SearchBar component. * @returns The rendered SearchBar component. */ -export function SearchBar({ +export function SearchBar({ filters, setFilters, createSuggestions, searchFunction = convertAndApplyFilters, clearFunction = defaultClearFunction, allowKeyWordSearch = true, -}: SearchBarProps) { + plotTypeSelectorProps, +}: SearchBarProps) { const [inputValue, setInputValue] = useState(""); const [anchorEl, setAnchorEl] = useState(null); const [clickedTokenIndex, setClickedTokenIndex] = @@ -283,90 +290,107 @@ export function SearchBar({ return ( { - inputRef.current?.focus(); - }} sx={{ - width: 1, - height: "auto", display: "flex", - border: "1px solid", - borderColor: "grey.400", - overflow: "hidden", - borderRadius: 1, - ":focus-within": { - borderColor: "primary.main", - }, - alignItems: "center", + flexDirection: "row", + width: 1, }} - data-testid="search-bar" > - - {tokenEquations.map((equation, index) => ( - - handleOptionMenuOpen(e, index, tokenIndex) - } - handleRightClick={() => - setTokenEquations((prev) => [ - ...prev.filter((_, i) => i !== index), - ]) - } // Remove the equation on right click - equationIndex={index} - DynamicSearchField={DynamicSearchField} // The dynamic search field can be in the middle of the equations - focusedTokenIndex={focusedTokenIndex} - /> - ))} - {!focusedTokenIndex && DynamicSearchField} - {/* Otherwise, the search field is at the end */} - - {clickedTokenIndex !== null && - currentSuggestions.items.map((option, idx) => ( - - handleOptionSelect( - option, - currentSuggestions.nature[idx], - currentSuggestions.type[idx], - currentSuggestions.hideSuggestion[idx], - ) - } - > - {option} - - ))} - - - - searchFunction(tokenEquations, setFilters)} - disabled={ - !tokenEquations.every((eq) => eq.status === EquationStatus.VALID) - } - sx={{ width: "40px", height: "40px" }} - > - - - - {tokenEquations.length !== 0 && ( + {/* The search bar */} + { + inputRef.current?.focus(); + }} + sx={{ + width: 1, + height: "auto", + display: "flex", + border: "1px solid", + borderColor: "grey.400", + overflow: "hidden", + borderRadius: 1, + ":focus-within": { + borderColor: "primary.main", + }, + alignItems: "center", + }} + data-testid="search-bar" + > + + {tokenEquations.map((equation, index) => ( + + handleOptionMenuOpen(e, index, tokenIndex) + } + handleRightClick={() => + setTokenEquations((prev) => [ + ...prev.filter((_, i) => i !== index), + ]) + } // Remove the equation on right click + equationIndex={index} + DynamicSearchField={DynamicSearchField} // The dynamic search field can be in the middle of the equations + focusedTokenIndex={focusedTokenIndex} + /> + ))} + {!focusedTokenIndex && DynamicSearchField} + {/* Otherwise, the search field is at the end */} + + {clickedTokenIndex !== null && + currentSuggestions.items.map((option, idx) => ( + + handleOptionSelect( + option, + currentSuggestions.nature[idx], + currentSuggestions.type[idx], + currentSuggestions.hideSuggestion[idx], + ) + } + > + {option} + + ))} + + + { - setInputValue(""); - clearFunction(setFilters, setTokenEquations); - }} + onClick={() => searchFunction(tokenEquations, setFilters)} + disabled={ + !tokenEquations.every((eq) => eq.status === EquationStatus.VALID) + } sx={{ width: "40px", height: "40px" }} > - + - )} + + {tokenEquations.length !== 0 && ( + { + setInputValue(""); + clearFunction(setFilters, setTokenEquations); + }} + sx={{ width: "40px", height: "40px" }} + > + + + )} + + {/* Plot type selector if provided */} + {plotTypeSelectorProps && ( + + )} ); } diff --git a/packages/diracx-web-components/src/components/shared/Sunburst/BreadCrumbsTrail.tsx b/packages/diracx-web-components/src/components/shared/Sunburst/BreadCrumbsTrail.tsx new file mode 100644 index 00000000..aaaa7a5f --- /dev/null +++ b/packages/diracx-web-components/src/components/shared/Sunburst/BreadCrumbsTrail.tsx @@ -0,0 +1,52 @@ +import { Breadcrumbs, Link } from "@mui/material"; + +/** + * Display a path in the breadcrumb. + * + * @param path The path to display. + * @param setPath The function to set the path. + * @returns The breadcrumb component. + */ +export function BreadCrumbsTrail({ + path, + setPath, +}: { + path: string[]; + setPath?: React.Dispatch>; +}) { + return ( + + { + if (setPath) setPath([]); + }} + sx={{ + cursor: "pointer", + "&:hover": { + textDecoration: "underline", + }, + }} + variant="h6" + > + Top + + {path.map((elt, index) => ( + { + if (setPath) setPath((oldPath) => oldPath.slice(0, index + 1)); + }} + sx={{ + cursor: "pointer", + "&:hover": { + textDecoration: "underline", + }, + }} + variant="h6" + > + {elt} + + ))} + + ); +} diff --git a/packages/diracx-web-components/src/components/shared/Sunburst/Sunburst.tsx b/packages/diracx-web-components/src/components/shared/Sunburst/Sunburst.tsx index d14c1661..a93f5334 100644 --- a/packages/diracx-web-components/src/components/shared/Sunburst/Sunburst.tsx +++ b/packages/diracx-web-components/src/components/shared/Sunburst/Sunburst.tsx @@ -16,7 +16,8 @@ import { scaleOrdinal } from "d3-scale"; import { Stack, useTheme, Box, Alert, Skeleton } from "@mui/material"; import type { SunburstTree, SunburstNode } from "../../../types"; -import { DisplayPath, getPath, sizeToText as defaultSizeToText } from "./Utils"; +import { getPath, sizeToText as defaultSizeToText } from "./Utils"; +import { BreadCrumbsTrail } from "./BreadCrumbsTrail"; interface SunburstProps { /** Formatted data to be displayed in the chart */ @@ -335,7 +336,7 @@ export function Sunburst({ }} > {currentPath && setCurrentPath && ( - + )}
>; -}) { - return ( - - { - if (setPath) setPath([]); - }} - sx={{ - cursor: "pointer", - "&:hover": { - textDecoration: "underline", - }, - }} - variant="h6" - > - Top - - {path.map((elt, index) => ( - { - if (setPath) setPath((oldPath) => oldPath.slice(0, index + 1)); - }} - sx={{ - cursor: "pointer", - "&:hover": { - textDecoration: "underline", - }, - }} - variant="h6" - > - {elt} - - ))} - - ); -} - /** * Gives the complete path to a node * diff --git a/packages/diracx-web-components/src/components/shared/index.ts b/packages/diracx-web-components/src/components/shared/index.ts index 3fde471d..f0ff014e 100644 --- a/packages/diracx-web-components/src/components/shared/index.ts +++ b/packages/diracx-web-components/src/components/shared/index.ts @@ -1,6 +1,6 @@ export { DataTable } from "./DataTable"; export { ErrorBox } from "./ErrorBox"; export { ApplicationSelector } from "./ApplicationSelector"; -export { ChartDisplayLayout } from "./ChartDisplayLayout"; +export { ChartView } from "./ChartView/ChartView"; export { SearchBar } from "./SearchBar"; export { Sunburst } from "./Sunburst"; diff --git a/packages/diracx-web-components/src/types/JobMonitorChartType.ts b/packages/diracx-web-components/src/types/JobMonitorChartType.ts new file mode 100644 index 00000000..7dc2385e --- /dev/null +++ b/packages/diracx-web-components/src/types/JobMonitorChartType.ts @@ -0,0 +1,7 @@ +/** + * Enum representing the types of charts available in the Job Monitor. + */ +export enum JobMonitorChartType { + TABLE = "CHART_TYPE_TABLE", + SUNBURST = "CHART_TYPE_SUNBURST", +} diff --git a/packages/diracx-web-components/src/types/index.ts b/packages/diracx-web-components/src/types/index.ts index 29227e87..71b7ff4c 100644 --- a/packages/diracx-web-components/src/types/index.ts +++ b/packages/diracx-web-components/src/types/index.ts @@ -17,3 +17,4 @@ export * from "./SearchBarTokenNature"; export * from "./CategoryType"; export * from "./SunburstTree"; export * from "./SunburstNode"; +export * from "./JobMonitorChartType"; diff --git a/packages/diracx-web-components/stories/ChartDisplayLayout.stories.tsx b/packages/diracx-web-components/stories/ChartView.stories.tsx similarity index 91% rename from packages/diracx-web-components/stories/ChartDisplayLayout.stories.tsx rename to packages/diracx-web-components/stories/ChartView.stories.tsx index 7f3d0f76..8133c3ca 100644 --- a/packages/diracx-web-components/stories/ChartDisplayLayout.stories.tsx +++ b/packages/diracx-web-components/stories/ChartView.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { useState } from "react"; -import { ChartDisplayLayout } from "../src/components/shared/ChartDisplayLayout"; +import { ChartView } from "../src/components/shared"; import { ThemeProvider } from "../src/contexts/ThemeProvider"; import { Sunburst } from "../src/components"; @@ -40,8 +40,8 @@ const mockTree = { }; const meta = { - title: "Shared/ChartDisplayLayout", - component: ChartDisplayLayout, + title: "Shared/ChartView", + component: ChartView, parameters: { layout: "centered", }, @@ -51,7 +51,7 @@ const meta = { return ; }, ], -} satisfies Meta; +} satisfies Meta; export default meta; @@ -90,7 +90,7 @@ export const Default: Story = { return ( - { +describe("ChartView", () => { it("renders the element", () => { render(); expect(screen.getByText("Select Columns")).toBeInTheDocument(); diff --git a/packages/diracx-web-components/test/Sunburst.test.tsx b/packages/diracx-web-components/test/Sunburst.test.tsx index 64fd0ac9..c7aa9402 100644 --- a/packages/diracx-web-components/test/Sunburst.test.tsx +++ b/packages/diracx-web-components/test/Sunburst.test.tsx @@ -1,6 +1,8 @@ import { render, screen } from "@testing-library/react"; +import { composeStories } from "@storybook/react"; import { Sunburst } from "../src/components/shared/Sunburst/Sunburst"; import { SunburstTree } from "../src/types"; +import * as stories from "../stories/Sunburst.stories"; // Importing all stories to use in tests import "@testing-library/jest-dom"; // Sample tree data for testing @@ -56,6 +58,8 @@ const customSizeToText = (size: number, total?: number) => { }; describe("Sunburst Component", () => { + const { Default } = composeStories(stories); + describe("Rendering States", () => { test("renders loading skeleton when isLoading is true", () => { render(); @@ -172,4 +176,19 @@ describe("Sunburst Component", () => { expect(svg).toBeInTheDocument(); }); }); + + // The previous tests cover the main functionalities of the Sunburst component. + // Here we just ensure that the story renders correctly. + describe("Storybook Integration", () => { + test("renders the Default story correctly", () => { + render(); + expect(screen.getByText("Top")).toBeInTheDocument(); + expect(screen.getByTestId("sunburst-chart")).toBeInTheDocument(); + }); + + test("renders while loading", () => { + render(); + expect(screen.getByTestId("loading-skeleton")).toBeInTheDocument(); + }); + }); }); diff --git a/packages/diracx-web/test/e2e/loginOut.cy.ts b/packages/diracx-web/test/e2e/loginOut.cy.ts index a0c77368..db192f25 100644 --- a/packages/diracx-web/test/e2e/loginOut.cy.ts +++ b/packages/diracx-web/test/e2e/loginOut.cy.ts @@ -69,31 +69,4 @@ describe("Login and Logout", () => { cy.visit("/"); cy.url().should("include", "/auth"); }); - - it("prevent infinite login loop", () => { - // The user is redirected to the /auth page because not authenticated - // Make sure we are on the /auth page - cy.url().should("include", "/auth"); - - cy.url().then((currentUrl) => { - cy.wait(2000); // Wait 2 seconds to let a possible infinite loop happen - - // Check that the URL has not changed - cy.url().should("eq", currentUrl); - }); - - // Login - cy.get('[data-testid="button-login"]').click(); - cy.get("#login").type("admin@example.com"); - cy.get("#password").type("password"); - - // Find the login button and click on it - cy.get("button").click(); - // Grant access - cy.get(":nth-child(1) > form > .dex-btn").click(); - cy.url().should("include", "/auth"); - - // The user is redirected to the dashboard page - cy.url().should("eq", Cypress.config().baseUrl + "/"); - }); });