From b32c620c3ba5772c13087067515acf4e70eead91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 13:18:17 +0000 Subject: [PATCH 1/5] Bump msw from 2.12.7 to 2.13.6 Bumps [msw](https://github.com/mswjs/msw) from 2.12.7 to 2.13.6. - [Release notes](https://github.com/mswjs/msw/releases) - [Changelog](https://github.com/mswjs/msw/blob/main/CHANGELOG.md) - [Commits](https://github.com/mswjs/msw/compare/v2.12.7...v2.13.6) --- updated-dependencies: - dependency-name: msw dependency-version: 2.12.14 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 198 +++++++++++++++++++++++++++++---------------------- 2 files changed, 114 insertions(+), 86 deletions(-) diff --git a/package.json b/package.json index a2a4283c..e71448ac 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-fixed-jsdom": "^0.0.11", - "msw": "^2.12.7", + "msw": "^2.13.6", "postcss": "^8.5.10", "prettier": "^3.8.1", "prettier-plugin-sort-imports": "^1.8.11", diff --git a/yarn.lock b/yarn.lock index 1d93ac13..15fd01cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -807,38 +807,41 @@ resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz#1860473de7dfa1546767448f333db80cb0ff2161" integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ== -"@inquirer/confirm@^5.0.0": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.1.tgz#18385064b8275eb79fdba505ce527801804eea04" - integrity sha512-vVLSbGci+IKQvDOtzpPTCOiEJCNidHcAq9JYVoWTW0svb5FiwSLotkM+JXNXejfjnzVYV9n0DTBythl9+XgTxg== +"@inquirer/ansi@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@inquirer/ansi/-/ansi-2.0.5.tgz#7b7e121f6a0c40128711daf20325e6ff2cdff8b7" + integrity sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw== + +"@inquirer/confirm@^6.0.11": + version "6.0.12" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-6.0.12.tgz#7a317aec813214cec2f5339b9fa0926c20bf0dbe" + integrity sha512-h9FgGun3QwVYNj5TWIZZ+slii73bMoBFjPfVIGtnFuL4t8gBiNDV9PcSfIzkuxvgquJKt9nr1QzszpBzTbH8Og== dependencies: - "@inquirer/core" "^10.1.2" - "@inquirer/type" "^3.0.2" + "@inquirer/core" "^11.1.9" + "@inquirer/type" "^4.0.5" -"@inquirer/core@^10.1.2": - version "10.1.2" - resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.1.2.tgz#a9c5b9ed814a636e99b5c0a8ca4f1626d99fd75d" - integrity sha512-bHd96F3ezHg1mf/J0Rb4CV8ndCN0v28kUlrHqP7+ECm1C/A+paB7Xh2lbMk6x+kweQC+rZOxM/YeKikzxco8bQ== +"@inquirer/core@^11.1.9": + version "11.1.9" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-11.1.9.tgz#97f099f5217f50f168c12db00ac07f51ab550fbb" + integrity sha512-BDE4fG22uYh1bGSifcj7JSx119TVYNViMhMu85usp4Fswrzh6M0DV3yld64jA98uOAa2GSQ4Bg4bZRm2d2cwSg== dependencies: - "@inquirer/figures" "^1.0.9" - "@inquirer/type" "^3.0.2" - ansi-escapes "^4.3.2" + "@inquirer/ansi" "^2.0.5" + "@inquirer/figures" "^2.0.5" + "@inquirer/type" "^4.0.5" cli-width "^4.1.0" - mute-stream "^2.0.0" + fast-wrap-ansi "^0.2.0" + mute-stream "^3.0.0" signal-exit "^4.1.0" - strip-ansi "^6.0.1" - wrap-ansi "^6.2.0" - yoctocolors-cjs "^2.1.2" -"@inquirer/figures@^1.0.9": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.9.tgz#9d8128f8274cde4ca009ca8547337cab3f37a4a3" - integrity sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ== +"@inquirer/figures@^2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-2.0.5.tgz#d12f4889ac6ea7731ebc52bd9c066ca51d8fdee7" + integrity sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ== -"@inquirer/type@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.2.tgz#baff9f8d70947181deb36772cd9a5b6876d3e60c" - integrity sha512-ZhQ4TvhwHZF+lGhQ2O/rsjo80XoZR5/5qhOY3t6FJuX5XBg5Be8YzYTvaUGJnc12AUGI2nr4QSUE4PhKSigx7g== +"@inquirer/type@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-4.0.5.tgz#a02d90e5da8a36dce27ac8e7237a50c99a9003a3" + integrity sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q== "@isaacs/cliui@^8.0.2": version "8.0.2" @@ -1140,10 +1143,10 @@ resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz#d4f6937353bc4568292654efb0a0e0532adbcba2" integrity sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw== -"@mswjs/interceptors@^0.40.0": - version "0.40.0" - resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.40.0.tgz#1b45f215ba8c2983ed133763ca03af92896083d6" - integrity sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ== +"@mswjs/interceptors@^0.41.3": + version "0.41.6" + resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.41.6.tgz#f126479eed31147dac952780416b759016a7aa05" + integrity sha512-qmDvJIjcNsZ6tXWy2G9yuCgMPTTn35GMA3dPpSLm7QJVpbQzYdw0ALy1bKoivXnEM3U93/OrK+/M719b+fg84Q== dependencies: "@open-draft/deferred-promise" "^2.2.0" "@open-draft/logger" "^0.3.0" @@ -1178,6 +1181,11 @@ resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz#4a822d10f6f0e316be4d67b4d4f8c9a124b073bd" integrity sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA== +"@open-draft/deferred-promise@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-3.0.0.tgz#9725acc5afe8ecde690e9e198a094859fdbf2e45" + integrity sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA== + "@open-draft/logger@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@open-draft/logger/-/logger-0.3.0.tgz#2b3ab1242b360aa0adb28b85f5d7da1c133a0954" @@ -2182,6 +2190,13 @@ dependencies: csstype "^3.2.2" +"@types/set-cookie-parser@^2.4.10": + version "2.4.10" + resolved "https://registry.yarnpkg.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.10.tgz#ad3a807d6d921db9720621ea3374c5d92020bcbc" + integrity sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw== + dependencies: + "@types/node" "*" + "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" @@ -2494,7 +2509,7 @@ alien-signals@^0.4.9: resolved "https://registry.yarnpkg.com/alien-signals/-/alien-signals-0.4.14.tgz#9ff8f72a272300a51692f54bd9bbbada78fbf539" integrity sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q== -ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: +ansi-escapes@^4.2.1: version "4.3.2" resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -3170,10 +3185,10 @@ convert-source-map@^2.0.0: resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -cookie@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610" - integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA== +cookie@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c" + integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== copy-anything@^3.0.5: version "3.0.5" @@ -4098,11 +4113,30 @@ fast-levenshtein@^2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-string-truncated-width@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz#23afe0da67d752ca0727538f1e6967759728ce49" + integrity sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g== + +fast-string-width@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/fast-string-width/-/fast-string-width-3.0.2.tgz#16dbabb491ce5585b5ecb675b65c165d71688eeb" + integrity sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg== + dependencies: + fast-string-truncated-width "^3.0.2" + fast-uri@^3.0.1: version "3.1.2" resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.2.tgz#8af3d4fc9d3e71b11572cc2673b514a7d1a8c8ec" integrity sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ== +fast-wrap-ansi@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz#c0ae3f3982d061c3d657ec927196fbb47e22fe64" + integrity sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w== + dependencies: + fast-string-width "^3.0.2" + fastq@^1.6.0: version "1.17.1" resolved "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -4436,10 +4470,10 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -graphql@^16.12.0: - version "16.12.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.12.0.tgz#28cc2462435b1ac3fdc6976d030cef83a0c13ac7" - integrity sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ== +graphql@^16.13.2: + version "16.13.2" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.13.2.tgz#4d2b73df5796b201f1bc2765f5d7067f689cb55f" + integrity sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig== handlebars@^4.7.9: version "4.7.9" @@ -4532,10 +4566,13 @@ he@^1.2.0: resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -headers-polyfill@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-4.0.3.tgz#922a0155de30ecc1f785bcf04be77844ca95ad07" - integrity sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ== +headers-polyfill@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-5.0.1.tgz#9554eb2892b666db1c7a3380a91b6cfd467a6b19" + integrity sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA== + dependencies: + "@types/set-cookie-parser" "^2.4.10" + set-cookie-parser "^3.0.1" highlight.js@^10.4.1, highlight.js@~10.7.0: version "10.7.3" @@ -5917,27 +5954,27 @@ ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -msw@^2.12.7: - version "2.12.7" - resolved "https://registry.yarnpkg.com/msw/-/msw-2.12.7.tgz#755fa4a0df51133bf76ebc9b7d4bdcc4355f0c8e" - integrity sha512-retd5i3xCZDVWMYjHEVuKTmhqY8lSsxujjVrZiGbbdoxxIBg5S7rCuYy/YQpfrTYIxpd/o0Kyb/3H+1udBMoYg== +msw@^2.13.6: + version "2.13.6" + resolved "https://registry.yarnpkg.com/msw/-/msw-2.13.6.tgz#e43a6fcdabd80b7b9db5148ccdef84fbeb7e871c" + integrity sha512-GAJbQy8Ra/Ydjt0Hb2MGT2qhzd83J3+QZMHdH85uW7r/XkKc846+Ma2PLif5hGvTm5Yqa+wkcstpim0WeLZU9g== dependencies: - "@inquirer/confirm" "^5.0.0" - "@mswjs/interceptors" "^0.40.0" - "@open-draft/deferred-promise" "^2.2.0" + "@inquirer/confirm" "^6.0.11" + "@mswjs/interceptors" "^0.41.3" + "@open-draft/deferred-promise" "^3.0.0" "@types/statuses" "^2.0.6" - cookie "^1.0.2" - graphql "^16.12.0" - headers-polyfill "^4.0.2" + cookie "^1.1.1" + graphql "^16.13.2" + headers-polyfill "^5.0.1" is-node-process "^1.2.0" outvariant "^1.4.3" path-to-regexp "^6.3.0" picocolors "^1.1.1" - rettime "^0.7.0" + rettime "^0.11.7" statuses "^2.0.2" strict-event-emitter "^0.5.1" - tough-cookie "^6.0.0" - type-fest "^5.2.0" + tough-cookie "^6.0.1" + type-fest "^5.5.0" until-async "^3.0.2" yargs "^17.7.2" @@ -5946,10 +5983,10 @@ muggle-string@^0.4.1: resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.4.1.tgz#3b366bd43b32f809dc20659534dd30e7c8a0d328" integrity sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ== -mute-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b" - integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA== +mute-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-3.0.0.tgz#cd8014dd2acb72e1e91bb67c74f0019e620ba2d1" + integrity sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw== mz@^2.7.0: version "2.7.0" @@ -7198,10 +7235,10 @@ ret@~0.1.10: resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -rettime@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/rettime/-/rettime-0.7.0.tgz#c040f1a65e396eaa4b8346dd96ed937edc79d96f" - integrity sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw== +rettime@^0.11.7: + version "0.11.8" + resolved "https://registry.yarnpkg.com/rettime/-/rettime-0.11.8.tgz#a32793a84c7fe8fc19c8410c52ee64fb0b7fba92" + integrity sha512-0fERGXktJTyJ+h8fBEiPxHPEFOu0h15JY7JtwrOVqR5K+vb99ho6IyOo7ekLS3h4sJCzIDy4VWKIbZUfe9njmg== reusify@^1.0.4: version "1.0.4" @@ -7366,6 +7403,11 @@ semver@~7.5.4: dependencies: lru-cache "^6.0.0" +set-cookie-parser@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz#e0b1d94c8660c68e6a24dc4e2b5c9e955ccf7e28" + integrity sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw== + set-function-length@^1.2.1, set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" @@ -7881,10 +7923,10 @@ tough-cookie@^4.1.2: universalify "^0.2.0" url-parse "^1.5.3" -tough-cookie@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-6.0.0.tgz#11e418b7864a2c0d874702bc8ce0f011261940e5" - integrity sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w== +tough-cookie@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-6.0.1.tgz#a495f833836609ed983c19bc65639cfbceb54c76" + integrity sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw== dependencies: tldts "^7.0.5" @@ -7957,10 +7999,10 @@ type-fest@^4.41.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== -type-fest@^5.2.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-5.4.0.tgz#be169ec21eaaae88769b3fc69fe0cbbf05350731" - integrity sha512-wfkA6r0tBpVfGiyO+zbf9e10QkRQSlK9F2UvyfnjoCmrvH2bjHyhPzhugSBOuq1dog3P0+FKckqe+Xf6WKVjwg== +type-fest@^5.5.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-5.6.0.tgz#502f7a003b7309e96a7e17052cc2ab2c7e5c7a31" + integrity sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA== dependencies: tagged-tag "^1.0.0" @@ -8391,15 +8433,6 @@ wordwrap@^1.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -8489,11 +8522,6 @@ yocto-queue@^0.1.0: resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -yoctocolors-cjs@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" - integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== - zod@^3.25.76: version "3.25.76" resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" From b5dfcd02b5f9290c194794cca1465e9a2cec9154 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 19 May 2026 15:35:15 +0200 Subject: [PATCH 2/5] Update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 4792034e..3013780e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ - Dependabot: Bump ws from 8.18.0 to 8.20.1 - Dependabot: Bump fast-uri from 3.0.3 to 3.1.2 - Dependabot: Bump nginx from 1.29.7 to 1.29.8 +- Dependabot: Bump msw from 2.12.7 to 2.13.6 ## 2026-05-19 - 0.24.2 From b283b9448f4a020ccd53f728209cdb59536d9a1b Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 19 May 2026 16:50:13 +0200 Subject: [PATCH 3/5] Add codebase map to .planning/codebase/ 7 structured documents covering stack, integrations, architecture, structure, conventions, testing, and concerns. Co-Authored-By: Claude Sonnet 4.6 --- .planning/codebase/ARCHITECTURE.md | 255 +++++++++++++++++++++++++++++ .planning/codebase/CONCERNS.md | 89 ++++++++++ .planning/codebase/CONVENTIONS.md | 168 +++++++++++++++++++ .planning/codebase/INTEGRATIONS.md | 185 +++++++++++++++++++++ .planning/codebase/STACK.md | 177 ++++++++++++++++++++ .planning/codebase/STRUCTURE.md | 164 +++++++++++++++++++ .planning/codebase/TESTING.md | 127 ++++++++++++++ 7 files changed, 1165 insertions(+) create mode 100644 .planning/codebase/ARCHITECTURE.md create mode 100644 .planning/codebase/CONCERNS.md create mode 100644 .planning/codebase/CONVENTIONS.md create mode 100644 .planning/codebase/INTEGRATIONS.md create mode 100644 .planning/codebase/STACK.md create mode 100644 .planning/codebase/STRUCTURE.md create mode 100644 .planning/codebase/TESTING.md diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md new file mode 100644 index 00000000..69e8b462 --- /dev/null +++ b/.planning/codebase/ARCHITECTURE.md @@ -0,0 +1,255 @@ + +# Architecture + +**Analysis Date:** 2026-05-19 + +## System Overview + +```text +┌─────────────────────────────────────────────────────────────────────┐ +│ Browser SPA │ +│ `src/main.tsx` (entry) │ +├───────────────────────────────┬─────────────────────────────────────┤ +│ App Shell │ Route Views │ +│ `src/App.tsx` │ `src/routes/` (PascalCase dirs) │ +│ Layout, Nav, StatusBar │ Auth, Overview, SQLConsole, │ +│ ClusterHealthManager │ Tables, TablesShards, Nodes, │ +│ NotificationHandler │ Users, Automation/* │ +└────────────┬──────────────────┴──────────────────┬──────────────────┘ + │ │ + ▼ ▼ +┌────────────────────────┐ ┌───────────────────────────────────┐ +│ Shared Components │ │ Data Fetching Layer │ +│ `src/components/` │ │ SWR hooks `src/swr/jwt/` │ +│ ~50 UI components │ │ Axios API `src/hooks/` │ +│ (design system) │ │ Direct SQL `useExecuteSql.ts` │ +└────────────────────────┘ └──────────────┬────────────────────┘ + │ + ┌────────────────────────────────────┘ + ▼ +┌────────────────────────────────────────────────────────────────────┐ +│ Global State (Zustand) │ +│ `src/state/jwtManager.ts` Auth + cluster URL management │ +│ `src/state/clusterHealth.ts` Node health metrics cache │ +│ `src/state/session.ts` UI notifications + display prefs │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌────────────────────────────────────────────────────────────────────┐ +│ External Backends │ +│ CrateDB HTTP API `/_sql` (direct SQL queries) │ +│ Grand Central API `/api/*` (REST — scheduled jobs, policies) │ +└────────────────────────────────────────────────────────────────────┘ +``` + +## Component Responsibilities + +| Component | Responsibility | File | +|-----------|----------------|------| +| App | Root shell, routing, connection check | `src/App.tsx` | +| Layout | Sidebar nav + topbar frame | `src/components/Layout/Layout.tsx` | +| ClusterHealthManager | Polls node status, writes to health store | `src/components/ClusterHealthManager/ClusterHealthManager.tsx` | +| NotificationHandler | Renders toast notifications from session store | `src/components/NotificationHandler/NotificationHandler.tsx` | +| EnterpriseScreen | Guards enterprise-only routes by GC connection status | `src/components/EnterpriseScreen/EnterpriseScreen.tsx` | +| StatusBar | Topbar cluster connection indicator | `src/components/StatusBar/StatusBar.tsx` | +| useJWTManagerStore | JWT auth, cluster URL, connection status | `src/state/jwtManager.ts` | +| useClusterHealthStore | Node load/fs stats accumulation | `src/state/clusterHealth.ts` | +| useSessionStore | Toast notifications + SQL display prefs | `src/state/session.ts` | +| useExecuteSql | Raw POST to CrateDB `/_sql` endpoint | `src/hooks/useExecuteSql.ts` | +| useExecuteMultiSql | Multi-statement parse + sequential SQL execution | `src/hooks/useExecuteMultiSql.ts` | +| useGcApi | Axios instance with JWT Bearer auth for GC REST API | `src/hooks/useGcApi.ts` | +| swr/jwt/* | SWR data hooks that query CrateDB `sys.*` tables via SQL | `src/swr/jwt/` | +| swrHooks (hooks/) | SWR data hooks for Grand Central REST API endpoints | `src/hooks/swrHooks.ts` | + +## Pattern Overview + +**Overall:** Single-Page Application (SPA) — React 19 with client-side routing via React Router v6. + +**Key Characteristics:** +- Dual-mode deployment: standalone admin UI (local CrateDB connection) and embedded cloud component (CrateDB Cloud with JWT auth) +- Ships as both a runnable SPA (`vite build`) and a publishable npm library (`vite build --config vite.config.lib.ts`) +- Feature access gated by GC (Grand Central) connection status via `EnterpriseScreen` +- No server-side rendering — all data fetching happens in the browser + +## Layers + +**Route Views:** +- Purpose: Page-level components corresponding to URL paths +- Location: `src/routes/` +- Contains: Route component folders (Auth, Automation, Help, Nodes, Overview, SQLConsole, Tables, TablesShards, Users) +- Depends on: shared components, hooks, swr hooks, state stores +- Used by: `src/constants/routes.tsx` (route config), `src/App.tsx` + +**Shared Component Library:** +- Purpose: Reusable UI primitives and composite widgets +- Location: `src/components/` +- Contains: ~50 components from primitives (Button, Input, Label) to complex (SQLEditor, DataTable, GCChart) +- Depends on: Ant Design, Radix UI, TanStack Table, Recharts, Tailwind CSS +- Used by: route views, App shell + +**Data Fetching — SWR/CrateDB SQL:** +- Purpose: Fetch cluster metrics and schema data by running SQL against `sys.*` tables +- Location: `src/swr/jwt/` +- Contains: `useClusterNodeStatus`, `useClusterInfo`, `useTables`, `useTablesShards`, `useShards`, `useAllocations`, `useSchemaTree`, `useCurrentUser`, `useQueryStats`, `useUsersRoles` +- Depends on: `swrJWTFetch.ts`, `useJWTManagerStore` (for auth headers and cluster URL) +- Used by: route views, `ClusterHealthManager` + +**Data Fetching — SWR/GC REST API:** +- Purpose: Fetch Grand Central automation data (scheduled jobs, table policies, logs) +- Location: `src/hooks/swrHooks.ts` +- Contains: `useGCGetScheduledJobs`, `useGCGetPolicies`, `useGCGetPoliciesLogs`, `useGCGetSchemas`, etc. +- Depends on: `swrCORSFetch.ts`, `useGcApi`, `useJWTManagerStore` +- Used by: `src/routes/Automation/` views and hooks + +**API Utilities — Imperative REST:** +- Purpose: One-shot axios-based calls for mutations (create/update/delete jobs and policies) +- Location: `src/utils/api.ts` +- Contains: `apiGet`, `apiPost`, `apiPut`, `apiPatch`, `apiDelete`, `apiHead` — all return `ApiOutput` +- Depends on: `useSessionStore` (to dispatch error notifications) +- Used by: form submit handlers in Automation routes + +**Global State (Zustand):** +- Purpose: Cross-component shared state without prop drilling +- Location: `src/state/` +- Contains: `jwtManager.ts`, `clusterHealth.ts`, `session.ts` +- Depends on: nothing (leaf layer) +- Used by: hooks, components, API utils + +**Constants:** +- Purpose: App-wide configuration literals +- Location: `src/constants/` +- Contains: `paths.ts` (path-parser Path objects), `routes.tsx` (route definitions), `navigation.tsx` (nav config), `queries.ts`, `policies.ts`, `database.ts`, `defaults.ts`, `colors.ts` + +**Types:** +- Purpose: TypeScript type definitions and enums +- Location: `src/types/` +- Contains: `cratedb.ts` (CrateDB response shapes), `query.ts` (SQL result types + ColumnType enum), `job.ts`, `route.ts`, `policies/` + +**Utilities:** +- Purpose: Pure helper functions +- Location: `src/utils/` +- Contains: `api.ts`, `nodes.ts`, `statusChecks.ts`, `sqlFormatter.ts`, `bytes.ts`, `numbers.ts`, `compare.ts`, `filtering.ts`, `sorting.ts`, `cron.ts`, `strings.ts`, `arrays.ts`, `cn.ts` + +## Data Flow + +### Primary SQL Query Path (CrateDB `/_sql`) + +1. Component calls `useExecuteSql()` hook (`src/hooks/useExecuteSql.ts`) +2. Hook reads auth headers from `useJWTManagerStore.getState().getHeaders()` +3. Hook reads the target URL from `useJWTManagerStore.getState().getUrl(identifier)` +4. Raw `fetch()` POST to `/_sql?error_trace&types` with JSON body `{ stmt: sql }` +5. Response returned as `ExecuteSqlResult { data, status, success }` + +### SWR Polling Path (cluster metrics) + +1. SWR hook in `src/swr/jwt/` declares a SQL query constant +2. `useSWR(key, swrJWTFetch)` polls on a timer (e.g., 5 s for node status) +3. `swrJWTFetch` (`src/swr/swrJWTFetch.ts`) posts SQL via `useJWTManagerStore` auth +4. Optional `postFetch` transform maps raw `rows[][]` to typed objects +5. SWR cache provides deduplication and revalidation + +### Grand Central REST API Path + +1. Component calls `useGcApi()` to get an axios instance (`src/hooks/useGcApi.ts`) +2. Axios interceptor attaches `Authorization: Bearer ` from JWT store +3. SWR hook in `src/hooks/swrHooks.ts` uses `swrCORSFetch(axiosInstance)` as fetcher +4. Mutations (create/edit/delete) call `apiPost` / `apiPut` / `apiDelete` directly +5. API errors trigger `useSessionStore.setNotification()` → `NotificationHandler` renders toast + +### Connection Mode Branching + +- **Local admin UI mode** (`isLocalConnection: true`): direct HTTP to `http://localhost:4200/_sql`, no JWT header +- **Cloud mode** (`isLocalConnection: false`, `isJWTEnabled: true`): JWT Bearer token to CrateDB cluster FQDN `/_sql`, or proxied through GC URL when JWT not enabled + +**State Management:** +- Zustand stores are module-level singletons (`create()`) +- `useJWTManagerStore` is accessed both as a React hook (components) and imperatively via `.getState()` (hooks, utils that run outside React render cycle) +- No context providers for state — Zustand handles subscription internally + +## Key Abstractions + +**Route:** +- Purpose: Typed route descriptor combining path, element, label, and key +- Examples: `src/constants/routes.tsx`, `src/types/route.ts` +- Pattern: array of `Route[]` iterated in `App.tsx` to render `` + +**Path:** +- Purpose: Typed path objects using `path-parser` library for URL construction and parameter extraction +- Examples: `src/constants/paths.ts` — `root`, `auth`, `automationEditJob` (with `:jobId` param) +- Pattern: `new Path('/automation/scheduled-jobs/edit-job/:jobId')`, use `.path` for string, `.build(params)` for filled URL + +**swrJWTFetch:** +- Purpose: Adapter that runs SQL queries through the JWT auth layer for SWR consumption +- Examples: `src/swr/swrJWTFetch.ts`, used by all `src/swr/jwt/` hooks +- Pattern: `useSWR(key, ([url]) => swrJWTFetch(url, SQL_QUERY, postFetch))` + +**ApiOutput:** +- Purpose: Normalized response envelope for all REST mutations +- Examples: `src/utils/api.ts` — `{ success, data, status, errors? }` +- Pattern: all `apiGet/Post/Put/Patch/Delete` functions return `Promise>` + +## Entry Points + +**Application Entry:** +- Location: `src/main.tsx` +- Triggers: Vite HTML shell (`index.html`) loads the bundle +- Responsibilities: Mounts React root, wraps app in `BrowserRouter` + +**Library Entry:** +- Location: `src/index.ts` +- Triggers: npm consumers import from `@cratedb/crate-gc-admin` +- Responsibilities: Re-exports selected components, hooks, state stores, and types for use in host apps (e.g., CrateDB Cloud UI) + +**App Component:** +- Location: `src/App.tsx` +- Triggers: Rendered by `main.tsx` +- Responsibilities: Checks GC connection on mount, renders `Layout` with navigation, maps route config to ``, mounts `ClusterHealthManager` and `NotificationHandler` + +## Architectural Constraints + +- **Dual-use library/app:** The codebase builds as both a standalone SPA and an ES/UMD library. Components exported from `src/index.ts` must work when consumed by a host app that provides its own `BrowserRouter`. Avoid any component that assumes it is the top-level app. +- **`useJWTManagerStore` used imperatively:** `src/hooks/useExecuteSql.ts` and `src/swr/swrJWTFetch.ts` call `useJWTManagerStore.getState()` outside the React render cycle. This is intentional but means these modules are coupled to the Zustand store singleton. +- **No React Context for state:** All cross-component state flows through Zustand stores, not React context. Do not introduce context providers for state that needs to be shared widely. +- **SWR deduplication:** SWR keys in `src/swr/jwt/` use a tuple `[url, clusterId]`. Changing the key shape breaks caching. Keep key format consistent. +- **Enterprise gating:** Routes that require Grand Central must be wrapped in ``. Do not render GC-dependent UI without this wrapper. +- **Path aliases:** TypeScript path aliases defined in `tsconfig.json` map bare module names (`components`, `hooks`, `routes`, `state`, `types`, `utils`, `constants/*`) to `src/` subdirectories. Always use bare alias imports, not relative paths that cross layer boundaries. + +## Anti-Patterns + +### Calling `useJWTManagerStore` as a hook inside async functions + +**What happens:** Some new code may attempt `const store = useJWTManagerStore()` inside a callback or async function. +**Why it's wrong:** React hooks cannot be called outside render functions. The correct escape hatch already exists. +**Do this instead:** Use `useJWTManagerStore.getState()` for imperative access, as done in `src/hooks/useExecuteSql.ts` and `src/swr/swrJWTFetch.ts`. + +### Fetching data with plain `fetch` or `axios` directly in components + +**What happens:** A component makes its own API call without using an existing hook. +**Why it's wrong:** Bypasses JWT injection, SWR caching/deduplication, and error notification handling. +**Do this instead:** Use an existing SWR hook from `src/swr/jwt/` or `src/hooks/swrHooks.ts`, or create a new SWR hook following the same pattern. For mutations, use `apiPost`/`apiPut` from `src/utils/api.ts` with the `useGcApi()` axios instance. + +### Adding navigation items without a corresponding path constant + +**What happens:** A hardcoded string like `'/nodes'` appears in navigation config. +**Why it's wrong:** Creates a disconnected path that can silently diverge from the route definition. Note: `'/nodes'` in `src/constants/navigation.tsx` line 56 is an existing example of this issue. +**Do this instead:** Define a `new Path(...)` export in `src/constants/paths.ts` and reference `.path` in both `navigation.tsx` and `routes.tsx`. + +## Error Handling + +**Strategy:** Centralized notification dispatch through `useSessionStore`. + +**Patterns:** +- `src/utils/api.ts` catches non-2xx responses and calls `dispatchNotification('error', message)` automatically, so callers do not need to handle HTTP errors individually +- `NotificationHandler` component reads `useSessionStore.notification` and renders an Ant Design toast +- SQL errors from CrateDB are returned in the response body as `{ error: { message, code } }` — callers check `'error' in res.data` to detect failures +- `useExecuteMultiSql` tracks per-statement `QueryStatus` with states: `WAITING | EXECUTING | SUCCESS | ERROR | NOT_EXECUTED` + +## Cross-Cutting Concerns + +**Logging:** No structured logging library — browser `console.*` only. No log aggregation. +**Validation:** `react-hook-form` with `zod` resolvers for form validation in Automation routes. +**Authentication:** JWT stored in `sessionStorage` under a per-cluster key (e.g., `grand-central-token` or `grand-central-token.`). Token validity checked by decoding expiry; refreshed automatically via `getToken()` in `jwtManager`. + +--- + +*Architecture analysis: 2026-05-19* diff --git a/.planning/codebase/CONCERNS.md b/.planning/codebase/CONCERNS.md new file mode 100644 index 00000000..af0e92b0 --- /dev/null +++ b/.planning/codebase/CONCERNS.md @@ -0,0 +1,89 @@ +# Codebase Concerns + +Technical health concerns across 7 categories. Each entry includes location, description, and severity. + +--- + +## 1. Code Quality + +| Severity | Location | Issue | +|----------|----------|-------| +| **High** | `src/utils/api.ts:47` | `axiosError.response!` non-null assertion crashes if network is completely unreachable (no response object) | +| **Medium** | `src/types/query.ts`, `src/hooks/useExecuteMultiSql.ts` | Direct `node_modules/` path imports (`import { Statement } from 'node_modules/@cratedb/cratedb-sqlparse/dist/parser'`) — brittle and non-portable | +| **Medium** | `src/routes/Automation/views/PolicyForm.tsx` (729 lines) | Oversized file — should be split | +| **Medium** | `src/components/SQLEditor/SQLEditor.tsx` (428 lines) | Oversized file | +| **Medium** | `src/components/DataTable/DataTable.tsx` (397 lines) | Oversized file | +| **Medium** | `src/routes/Automation/views/JobsTable.tsx` (392 lines) | Oversized file | +| **Medium** | `src/types/query.ts` | `rows: any[][]` propagates `any` through all SQL result handling | +| **Low** | Various | Inconsistent `src/` prefix vs bare alias imports across 10+ files | +| **Low** | `src/App.tsx`, `src/state/jwtManager.ts`, `src/utils/numbers.ts` | `==` instead of `===` comparisons | + +--- + +## 2. Architecture Concerns + +| Severity | Location | Issue | +|----------|----------|-------| +| **High** | App-wide | No React error boundaries anywhere — any render error blanks the entire UI | +| **High** | `src/hooks/swrHooks.ts:79–123` (`useGCGetPoliciesEnriched`) | N+1 API pattern: one request per policy on every render and SWR refresh. Acknowledged with `// NOTE: This Hook will be removed when API will return the last_execution` comment | +| **Medium** | `src/hooks/useApiCall.ts` | `}, [])` empty dependency array — stale closure bug; URL/method/body changes after mount are silently ignored | +| **Medium** | `src/hooks/useGcApi.ts` | `axios.create()` called on every render without `useMemo`; 14+ components affected | +| **Medium** | App-wide | Two parallel HTTP strategies (Axios + `useGcApi` vs native `fetch` + `swrJWTFetch`) with no documented boundary | +| **Medium** | `src/hooks/useExecuteSql.ts` | Always returns `success: true` regardless of HTTP status | + +--- + +## 3. Dependency Concerns + +| Severity | Package | Issue | +|----------|---------|-------| +| **Medium** | `moment` v2.30.1 | Legacy/maintenance-mode library used in 5 files — ~300KB bundle cost; `date-fns` or `dayjs` preferred | +| **Low** | `lodash` | Full default import (`import _ from 'lodash'`) in `src/components/SQLResults/SQLResultsTable.tsx` and `src/routes/Automation/tablePoliciesUtils/tableTree.tsx` — should use named imports for tree-shaking | +| **Low** | `jest.config.js` | `collectCoverage: false` — the 80% coverage threshold is defined but never enforced | + +--- + +## 4. Test Coverage Gaps + +| Severity | Area | Issue | +|----------|------|-------| +| **High** | `src/hooks/` | All 8 hooks have no tests, including critical `useExecuteSql`, `useExecuteMultiSql`, `swrHooks` | +| **High** | `src/utils/` | All 11 utility files have no tests, including `api.ts`, `nodes.ts` (217 lines of health calculations), `statusChecks.ts` | +| **High** | Routes | 10 route components have no tests: `Overview`, `SQLConsole`, `Auth`, `Tables/*`, `Nodes/Nodes`, `Users/Users`, `TablesShards/TablesShards`, `Help` | +| **High** | `src/state/` | All 3 Zustand stores have no tests (JWT manager, session, cluster health) | + +--- + +## 5. Security Concerns + +| Severity | Location | Issue | +|----------|----------|-------| +| **High** | `src/state/jwtManager.ts:109` | JWT tokens sent as URL query parameters (`?token=...&refresh=...`) — logged by proxies and servers | +| **Medium** | `src/state/jwtManager.ts` | JWT stored in `sessionStorage` — accessible via XSS | + +--- + +## 6. Performance Concerns + +| Severity | Location | Issue | +|----------|----------|-------| +| **Medium** | App-wide | No `React.lazy()` or `Suspense` anywhere — all routes eagerly bundled, inflating initial load | + +--- + +## 7. Operational Concerns + +| Severity | Location | Issue | +|----------|----------|-------| +| **High** | App-wide | No error boundaries — any render crash produces a blank screen with no recovery | +| **Medium** | App-wide | Zero `console.error`/structured logging in production code; API errors dispatch UI notifications but leave no trace for debugging | +| **Low** | `src/hooks/useApiCall.ts` | Requests fire with no `AbortController` cleanup on unmount — memory leak risk | +| **Low** | Config | `gcUrl` hardcoded to `http://localhost:5050` default with no warning if not overridden in production | + +--- + +## Summary + +- **High severity**: 7 issues (no error boundaries, N+1 API calls, JWT in URL params, critical hooks/utils/routes/stores untested) +- **Medium severity**: 11 issues (large files, dual HTTP strategies, stale closures, `moment` dep, XSS-accessible tokens, no lazy loading) +- **Low severity**: 6 issues (import style, `==` comparisons, `lodash` imports, coverage not enforced, memory leaks) diff --git a/.planning/codebase/CONVENTIONS.md b/.planning/codebase/CONVENTIONS.md new file mode 100644 index 00000000..22c94554 --- /dev/null +++ b/.planning/codebase/CONVENTIONS.md @@ -0,0 +1,168 @@ +# Coding Conventions + +**Analysis Date:** 2026-05-19 + +## Naming Patterns + +**Files:** +- React components: PascalCase filename matching the component name — `Button.tsx`, `CardHeader.tsx` +- Component constants: PascalCase with `Constants` suffix — `ButtonConstants.ts`, `ChipConstants.ts` +- Custom hooks: camelCase with `use` prefix — `useButtonStyles.ts`, `useExecuteSql.ts` +- Utility modules: camelCase, topic-scoped — `sorting.ts`, `sqlFormatter.ts`, `statusChecks.ts` +- Test files: same name as subject file with `.test.tsx` suffix — `Button.test.tsx` +- Type files: camelCase — `api.ts`, `query.ts`, `utils.ts` under `src/types/` + +**Directories:** +- Component directories: PascalCase matching component name — `src/components/Button/`, `src/components/DataTable/` +- Route directories: PascalCase, matches feature area — `src/routes/Automation/`, `src/routes/Nodes/` +- Sub-route groups: `routes/` and `views/` subdirectories within route features +- Hook tests: `__test__/` subdirectory inside `src/hooks/` + +**Components:** +- PascalCase: `Button`, `ClusterHealthManager`, `GCSpin` +- Prefix `GC` for app-level global components: `GCSpin`, `GCChart`, `GCStatusIndicator` +- Props types: named `[ComponentName]Props` and exported — `export type ButtonProps = ...` + +**Functions:** +- camelCase for all utility functions — `sortByString`, `cronParser`, `apiGet` +- Constants: SCREAMING_SNAKE_CASE — `BUTTON_KINDS`, `DATE_FORMAT`, `JOBS_TABLE_PAGE_SIZE` +- SWR hook exports: `useGC[ResourceName]` pattern — `useGCGetScheduledJobs`, `useGCGetScheduledJob` + +**Variables:** +- camelCase throughout +- Boolean flags use descriptive adjectives — `disabled`, `loading`, `active`, `enabled` +- Enum-style const objects use SCREAMING_SNAKE_CASE keys — `BUTTON_KINDS.PRIMARY`, `Heading.levels.h3` + +**Types:** +- `type` keyword preferred over `interface` for component props +- Exported types named `[ComponentName]Props` for component props +- `ValueOf` utility type used to derive union types from const objects — `src/types/utils.ts` + +## Code Style + +**Formatting:** +- Tool: Prettier 3.x with `prettier-plugin-sort-imports` and `prettier-plugin-tailwindcss` +- Config: `.prettierrc.yaml` +- `printWidth`: 85 +- `tabWidth`: 2 spaces +- `singleQuote`: true +- `trailingComma`: 'all' +- `bracketSpacing`: true +- `arrowParens`: 'avoid' (omit parens for single-argument arrow functions) +- `stripNewlines`: true + +**Linting:** +- Tool: ESLint 9.x flat config (`eslint.config.mjs`) +- Extends: `eslint:recommended`, `@typescript-eslint/recommended`, Prettier integration +- Plugins: `@typescript-eslint`, `react`, `react-hooks`, `prettier` +- Formatting errors surfaced as ESLint errors (via `eslint-plugin-prettier`) +- Key rules disabled: `react/destructuring-assignment`, `import/no-cycle`, `react/forbid-prop-types`, `react/jsx-filename-extension` +- Run lint: `yarn lint` (targets `src/` with `--ext=.ts --ext=.tsx`) + +## TypeScript Strictness + +**Config:** `tsconfig.json` +- `strict: true` — enables all strict type checks +- `noUnusedLocals: true` — unused local variables are errors +- `noUnusedParameters: true` — unused function parameters are errors +- `noFallthroughCasesInSwitch: true` — switch fallthrough is an error +- `target`: ES2020 +- `module`: ESNext +- `isolatedModules: true` + +**Path Aliases (tsconfig paths):** +- `components` → `./src/components` +- `components/*` → `./src/components/*` +- `constants/*` → `./src/constants/*` +- `contexts` / `contexts/*` → `./src/contexts/*` +- `hooks` / `hooks/*` → `./src/hooks/*` +- `routes` / `routes/*` → `./src/routes/*` +- `state` / `state/*` → `./src/state/*` +- `types` / `types/*` → `./src/types/*` +- `utils` / `utils/*` → `./src/utils/*` +- `__mocks__` / `__mocks__/*` → `./__mocks__/*` + +Use these aliases in all imports — never use relative paths like `../../components`. + +## Import Organization + +**Order (enforced by `prettier-plugin-sort-imports`):** +1. NPM packages (external) — `import React from 'react'` +2. Local value imports — `import Button from 'components/Button'` +3. Local type imports — `import type { ButtonProps } from 'components/Button'` + +**Example from `src/routes/Automation/views/JobsTable.tsx`:** +```typescript +import { CheckOutlined, CloseOutlined } from '@ant-design/icons'; +import { automationCreateJob } from 'constants/paths'; +import { cronParser, cn, apiDelete } from 'utils'; +import { ColumnDef, Table } from '@tanstack/react-table'; +import { Link, useNavigate } from 'react-router-dom'; +import { Popconfirm } from 'antd'; +import { useState } from 'react'; +import { Text, Chip, DataTable } from 'components'; +import { Job } from 'types'; +``` + +Note: the prettier plugin auto-sorts within groups; imports are not manually grouped by comment. + +## Component Patterns + +**Always functional components** — no class components observed. + +**Props type:** Declared as `type [ComponentName]Props = { ... }` and exported. For components accepting `children`, use `PropsWithChildren<{ ... }>`. + +**Default exports:** All components use `export default function ComponentName(...)` or `export default ComponentName`. + +**Named constants attached as properties:** Enum-like option sets are attached to the component function object after definition: +```typescript +Button.sizes = BUTTON_SIZES; +Button.kinds = BUTTON_KINDS; +Heading.levels = HEADING_LEVELS; +Chip.colors = AVAILABLE_CHIP_COLORS; +``` + +**Tailwind for styling:** All styles applied via Tailwind utility classes. The `cn()` utility from `src/utils/cn.ts` (wrapping `clsx` + `tailwind-merge`) is used to compose conditional class names: +```typescript +const cardClasses = cn('bg-white', 'rounded', { 'opacity-50': disabled }, className); +``` + +**Props destructuring:** Props are always destructured in the function signature, never accessed via a `props` variable. + +**Barrel files:** Each component directory exposes a single `index.ts` that re-exports the default and named exports. `src/components/index.ts` is the root barrel for all shared components. + +## Git Commit Style + +Based on recent commit history: + +- Short imperative subject line — `Add ref to Button`, `Fix bug where error trace data was not scrollable` +- PR references in parentheses for squash merges — `Expose ClusterHealthStore (#583)` +- Dependabot bumps prefixed with package name — `Bump msw from 2.12.7 to 2.13.6` +- Fix commits optionally prefixed with `fix:` — `fix: restore dist/style.css output after Vite 5→6 upgrade` +- No enforced conventional-commits standard; tone is informal and descriptive + +## Project-Specific Patterns + +**Constants objects instead of enums:** +```typescript +export const BUTTON_KINDS = { + PRIMARY: 'primary', + SECONDARY: 'secondary', +} as const; +export type ButtonKind = ValueOf; +``` +This pattern is used everywhere instead of TypeScript `enum`. + +**`ValueOf` type helper** (`src/types/utils.ts`): derives a union of value types from a `const` object. + +**`cn()` utility** (`src/utils/cn.ts`): always use for conditional Tailwind class composition. Never concatenate class strings manually. + +**SWR for data fetching:** All API data is fetched via SWR hooks (`src/hooks/swrHooks.ts`). Hook names follow `useGC[Resource][Action]`. + +**Zustand for global state:** Three stores in `src/state/` — `clusterHealth.ts`, `jwtManager.ts`, `session.ts`. State is accessed via hooks; stores reset between tests via `__mocks__/zustand.ts`. + +**GC API vs JWT API:** Two fetch paths exist. `useGcApi` fetches through the GC backend proxy (`/api/`). `useExecuteSql` / `useJWTManagerStore` fetch directly against CrateDB at `localhost:4200` using JWT auth. + +--- + +*Convention analysis: 2026-05-19* diff --git a/.planning/codebase/INTEGRATIONS.md b/.planning/codebase/INTEGRATIONS.md new file mode 100644 index 00000000..a21956e7 --- /dev/null +++ b/.planning/codebase/INTEGRATIONS.md @@ -0,0 +1,185 @@ +# External Integrations + +**Analysis Date:** 2026-05-19 + +## Overview + +This app is a pure browser SPA with no backend of its own. All external communication is to two targets: + +1. **CrateDB HTTP API** — direct SQL queries via `/_sql` endpoint +2. **Grand Central (GC) API** — CrateDB Cloud management backend via `/api/` endpoints + +## CrateDB HTTP API + +**Purpose:** Execute SQL queries against a CrateDB cluster directly from the browser. + +**Protocol:** HTTP POST to `/_sql?error_trace&types` + +**Endpoint pattern:** +``` +{clusterUrl}/_sql?error_trace&types[&ident={identifier}] +``` + +**Auth modes (chosen at runtime):** +- **Local / Admin UI mode** — no authentication header; relies on HTTP Basic Auth handled by the browser/CrateDB directly. Default `clusterUrl` is `http://localhost:4200`. +- **JWT mode** — `Authorization: Bearer {token}` header. Used when `isJWTEnabled=true` (CrateDB >= 5.8.2) or `isLocalConnection=false`. + +**Client:** Native `fetch` API via `src/swr/swrJWTFetch.ts` + +**SWR hooks (all under `src/swr/jwt/`):** +| Hook | SQL target | Poll interval | +|------|-----------|---------------| +| `useClusterInfo.ts` | `sys.cluster` | 2 min | +| `useClusterNodeStatus.ts` | `sys.nodes` | — | +| `useCurrentUser.ts` | `sys.users` / session | — | +| `useQueryStats.ts` | `sys.jobs_log` | — | +| `useSchemaTree.ts` | `information_schema` | — | +| `useShards.ts` | `sys.shards` | — | +| `useTables.ts` | `information_schema.tables` | — | +| `useTablesShards.ts` | `sys.shards` + `information_schema` | — | +| `useUsersRoles.ts` | `sys.users`, `sys.privileges` | — | +| `useAllocations.ts` | `sys.allocations` | — | + +**Query identifier pattern:** Queries include `&ident=/{hook-name}` in the URL for MSW routing in tests. + +## Grand Central (GC) API + +**Purpose:** CrateDB Cloud management plane — scheduled jobs, table policies, schema data, authentication. + +**Base URL:** Configured at runtime via `useJWTManagerStore.gcUrl`. Defaults to `http://localhost:5050` in local mode; dynamically set to `https://{cluster.fqdn.replace('.', '.gc.')}` in cloud mode. + +**Auth:** JWT Bearer token obtained from `/api/v2/clusters/{clusterId}/jwt/` (cloud-ui provides this endpoint) and validated/stored in `sessionStorage`. + +**HTTP Client:** axios instance created per-request in `src/hooks/useGcApi.ts`, with `withCredentials: true` and `Authorization: Bearer {token}` injected via interceptor. + +**Endpoints consumed:** + +| Method | Path | Purpose | Source | +|--------|------|---------|--------| +| GET | `/api/` | Health / connection check | `src/App.tsx` | +| GET/POST | `/api/_sql` | SQL via GC proxy (non-JWT mode) | `src/state/jwtManager.ts` | +| GET | `/api/auth?token=...` | Authenticate JWT token with GC | `src/state/jwtManager.ts` | +| GET | `/api/scheduled-jobs/` | List scheduled jobs | `src/hooks/swrHooks.ts` | +| GET | `/api/scheduled-jobs/{id}` | Get single scheduled job | `src/hooks/swrHooks.ts` | +| POST | `/api/scheduled-jobs/` | Create scheduled job | `src/routes/Automation/views/JobForm.tsx` | +| PUT | `/api/scheduled-jobs/{id}` | Update scheduled job | `src/routes/Automation/views/JobsTable.tsx` | +| DELETE | `/api/scheduled-jobs/{id}` | Delete scheduled job | `src/routes/Automation/views/JobsTable.tsx` | +| GET | `/api/scheduled-jobs/{id}/log` | Job execution log | `src/hooks/swrHooks.ts` | +| GET | `/api/scheduled-jobs/logs?limit=100` | All job logs | `src/hooks/swrHooks.ts` | +| GET | `/api/scheduled-jobs/all/logs?limit=100` | All task logs | `src/hooks/swrHooks.ts` | +| GET | `/api/policies/` | List table policies | `src/hooks/swrHooks.ts` | +| GET | `/api/policies/{id}` | Get single policy | `src/hooks/swrHooks.ts` | +| POST | `/api/policies/` | Create policy | `src/routes/Automation/views/PolicyForm.tsx` | +| PUT | `/api/policies/{id}` | Update policy | `src/routes/Automation/views/PoliciesTable.tsx` | +| DELETE | `/api/policies/{id}` | Delete policy | `src/routes/Automation/views/PoliciesTable.tsx` | +| GET | `/api/policies/{id}/log?limit=2` | Policy execution log | `src/hooks/swrHooks.ts` | +| GET | `/api/policies/logs?limit=100` | All policy logs | `src/hooks/swrHooks.ts` | +| GET | `/api/policies/eligible-columns/` | Columns eligible for policy | `src/routes/Automation/hooks/useEligibleColumns.ts` | +| POST | `/api/policies/preview/` | Preview policy effect | `src/routes/Automation/hooks/usePolicyPreview.ts` | +| GET | `/api/data/schemas/` | Schema list (with tables) | `src/hooks/swrHooks.ts` | + +**Cloud-UI JWT endpoint (external, not GC):** +- GET `/api/v2/clusters/{clusterId}/jwt/` — returns `{ token, refresh }`. This URL is relative, so it is served by whatever host the cloud-ui runs on. Called only in cloud (non-local) mode. + +## Authentication + +**Two authentication modes, determined at runtime by `useJWTManagerStore`:** + +### Local / Admin UI Mode (`isLocalConnection: true`) + +- No token required +- HTTP Basic Auth handled natively by the browser when CrateDB prompts +- `gcStatus` tracks connection state (`PENDING`, `CONNECTED`, `NOT_LOGGED_IN`, `ERROR`) +- Session storage key: `grand_central_token` + +### Cloud / JWT Mode (`isLocalConnection: false`) + +- JWT token fetched from cloud-ui's `/api/v2/clusters/{clusterId}/jwt/` +- Token stored in `sessionStorage` under key `grand-central-token.{clusterId}` +- Token validated on each use via `jwt-decode` — reacquired if expiry < 10 seconds +- Token passed as `Authorization: Bearer` to both GC API (axios) and CrateDB direct (fetch) +- JWT direct-to-CrateDB enabled only when `clusterVersion >= 5.8.2` (`isJWTEnabled` flag) +- Auth flow entry: `src/routes/Auth/Auth.tsx` — accepts `?token=` + `?refresh=` URL params, calls `login()` which hits `{gcUrl}/api/auth` + +**State management:** `src/state/jwtManager.ts` (Zustand store) owns all auth state and is the single source of truth for URLs and headers. + +## Data Storage + +**Databases:** +- CrateDB — the app's only data store; no relational or document DB of its own + +**File Storage:** +- None — no file storage integration + +**Caching:** +- SWR in-memory cache for all API responses +- `sessionStorage` — JWT tokens only (key: `grand_central_token` or `grand-central-token.{clusterId}`) +- No localStorage usage for data (mocked out in tests) + +## Monitoring & Observability + +**Error Tracking:** +- None — no Sentry, Datadog, or similar SDK detected + +**Logs:** +- No structured logging library; errors surfaced to users via Ant Design notification system (managed by `src/state/session.ts`) + +## External Documentation Links + +The app links out to `cratedb.com` docs from constants: +- `CRATEDB_PRIVILEGES_DOCS` — `src/constants/defaults.ts` +- `CRATEDB_ERROR_CODES_DOCS` — `src/constants/defaults.ts` +- `CRATEDB_CLUSTER_DOCS` — `src/constants/defaults.ts` + +These are UI links only, not API integrations. + +## Mock / Stub Layer for Tests + +**Framework:** MSW (Mock Service Worker) 2.13.x — `test/msw/` + +**Server setup:** `test/msw/server.ts`, started/stopped in `test/setup.ts` + +**Handler groups:** +| File | Intercepts | +|------|-----------| +| `test/msw/handlers/queries.ts` | `POST http://localhost:4200/_sql` (JWT) and `POST /api/_sql` (CORS) | +| `test/msw/handlers/scheduledJobs.ts` | GC scheduled job endpoints | +| `test/msw/handlers/policies.ts` | GC policy endpoints | +| `test/msw/handlers/jwt.ts` | JWT auth endpoints | + +**Query routing in tests:** JWT SQL queries are routed by the `?ident=` param to return fixture data from `test/__mocks__/` (e.g., `useClusterNodeStatusMock.ts`, `useTablesShardsMock.ts`). + +**Additional module mocks (`__mocks__/`):** +- `zustand.ts` — resets store state between tests +- `react-router-dom.tsx` — stubs `useLocation`, `useNavigate`, etc. +- `react-ace/index.tsx` — no-op editor +- `react-syntax-highlighter/index.tsx` — no-op highlighter +- `react-resizable-panels/index.tsx` — no-op panels +- `ace-builds/` — empty mode/theme stubs +- `localStorageMock.ts` — in-memory localStorage replacement + +## CI/CD & Deployment + +**Hosting:** +- Standalone: nginx (Docker image `nginx` referenced in Dependabot config) +- Embedded: served from within CrateDB itself as the Admin UI + +**CI Pipeline:** GitHub Actions +- `.github/workflows/pr.yml` — lint, build (app + lib), test on every PR +- `.github/workflows/publish.yml` — publishes to NPM on merge to main + +**NPM Registry:** `https://registry.npmjs.org` (public, package `@cratedb/crate-gc-admin`) + +## Environment Configuration + +**`.env` file:** Present at repo root (contents not read — may contain local dev overrides). + +**No `VITE_*` environment variables** were found referenced in source code. All runtime configuration (cluster URL, GC URL, JWT settings) is passed programmatically via the `useJWTManagerStore.updateCluster()` API rather than build-time env vars. + +**Secrets location:** +- JWT tokens: `sessionStorage` in browser only +- No server-side secrets (frontend-only project) + +--- + +*Integration audit: 2026-05-19* diff --git a/.planning/codebase/STACK.md b/.planning/codebase/STACK.md new file mode 100644 index 00000000..648763a7 --- /dev/null +++ b/.planning/codebase/STACK.md @@ -0,0 +1,177 @@ +# Technology Stack + +**Analysis Date:** 2026-05-19 + +## Languages + +**Primary:** +- TypeScript 5.9.x — all source code in `src/` +- TSX — React component files throughout `src/components/`, `src/routes/` + +**Secondary:** +- JavaScript — `jest.config.js`, `postcss.config.js`, `tailwind.config.js` +- CSS / Less — `src/index.css`, component stylesheets; Less is a runtime dependency for Ant Design theming + +## Runtime + +**Environment:** +- Node.js 24.12.0 (pinned via `.nvmrc`) +- Browser-only at runtime — no server-side rendering; app is a pure SPA + +**Target:** +- Production: `>0.2%` coverage (browserslist), excluding dead/op_mini browsers +- Development: latest Chrome, Firefox, Safari + +**Package Manager:** +- yarn 1.22.22 (pinned in `package.json` `packageManager` field) +- Lockfile: `yarn.lock` present + +## Frameworks + +**Core:** +- React 19.0.x — UI rendering (`src/main.tsx` entry, `src/App.tsx` root component) +- react-router-dom 6.30.x — client-side routing (`src/constants/paths.ts`, `src/constants/routes.tsx`) + +**State Management:** +- zustand 5.0.x — three stores: `src/state/jwtManager.ts`, `src/state/session.ts`, `src/state/clusterHealth.ts` + +**Data Fetching:** +- swr 2.4.x — SWR hooks for polling Grand Central and CrateDB (`src/hooks/swrHooks.ts`, `src/swr/jwt/`) +- axios 1.15.x — HTTP client used via `src/hooks/useGcApi.ts` and `src/utils/api.ts` + +**UI Component Libraries:** +- antd (Ant Design) 5.29.x — primary component library; wrapped via `src/components/GcAdminAntdProvider/` +- @radix-ui/react-dropdown-menu, react-label, react-popover, react-select, react-slot, react-switch, react-tabs — headless UI primitives for shadcn-style components +- shadcn/ui configuration present (`components.json`), using Radix primitives with Tailwind CSS + +**Forms:** +- react-hook-form 7.73.x — form state management +- @hookform/resolvers 3.10.x — Zod integration for validation +- zod 3.25.x — schema validation + +**SQL Editor:** +- react-ace 14.0.x + ace-builds 1.43.x — SQL editor component (`src/components/SQLEditor/`) +- @cratedb/cratedb-sqlparse 0.0.17 — CrateDB-specific SQL parsing +- sql-formatter 15.7.x — SQL formatting (`src/utils/sqlFormatter.ts`) + +**Tables:** +- @tanstack/react-table 8.21.x — data table component (`src/components/DataTable/`, `src/components/Table/`) + +**Charts:** +- recharts 2.15.x — used in `src/components/GCChart/` + +**Utilities:** +- moment 2.30.x — date formatting (constants: `DATE_FORMAT`, `DATE_FORMAT_WITH_TZ`) +- lodash 4.18.x — general utility functions +- jwt-decode 4.0.x — JWT decoding in `src/state/jwtManager.ts` +- compare-versions 6.1.x — semver comparisons in `src/state/jwtManager.ts` +- cronstrue 2.59.x — human-readable cron descriptions (Automation views) +- papaparse 5.5.x — CSV parsing +- pretty-bytes 6.1.x — file size display +- path-parser 6.1.x — typed URL path building (`src/constants/paths.ts`) +- react-intl 7.1.x — i18n/formatting +- react-syntax-highlighter 15.6.x — code display (`src/components/SyntaxHighlighter/`) + +## Build Tooling + +**Bundler:** +- Vite 6.4.x — two configs: + - `vite.config.ts` — dev server (port 5000, `build/` output) and production app build + - `vite.config.lib.ts` — library build (`dist/`) producing ES and UMD bundles for NPM publish + +**Compiler/Transpiler:** +- TypeScript 5.9.x (`tsconfig.json`, `tsconfig.build.json`, `tsconfig.node.json`) + - Target: ES2020; module: ESNext; strict mode on + - `tsconfig.build.json` extends base, excludes test files +- @vitejs/plugin-react-swc 3.11.x — SWC-powered React Fast Refresh (replaces Babel) + +**Type Declarations:** +- vite-plugin-dts 4.5.4 — generates `.d.ts` files for library distribution + +**CSS:** +- Tailwind CSS 3.4.x (`tailwind.config.js`) with custom CrateDB brand colors +- PostCSS 8.5.x (`postcss.config.js`) +- autoprefixer 10.5.x +- tailwindcss-animate 1.0.7 — accordion/animation utilities + +**Path Aliases:** +Configured in `tsconfig.json` `paths` and mirrored for Jest in `jest.config.js`: +- `components/*` → `./src/components/*` +- `hooks/*` → `./src/hooks/*` +- `routes/*` → `./src/routes/*` +- `state/*` → `./src/state/*` +- `types/*` → `./src/types/*` +- `utils/*` → `./src/utils/*` +- `constants/*` → `./src/constants/*` +- `contexts/*` → `./src/contexts/*` +- `__mocks__/*` → `./__mocks__/*` + +## Testing Framework + +**Runner:** Jest 29.7.x (`jest.config.js`) +- Preset: `ts-jest/presets/js-with-ts` +- Environment: `jest-fixed-jsdom` (patched jsdom for React 19) +- Config: `jest.config.js` + +**DOM Utilities:** +- @testing-library/react 16.0.x +- @testing-library/jest-dom 6.9.x +- @testing-library/user-event 14.6.1 +- @testing-library/dom 10.x + +**Network Mocking:** +- msw 2.13.x (Mock Service Worker) — intercepts HTTP in tests (`test/msw/`) + +**Coverage threshold** (when enabled): 80% branches/functions/lines/statements + +## Dev Tools + +**Linter:** +- ESLint 9.39.x — flat config at `eslint.config.mjs` + - Plugins: `@typescript-eslint`, `prettier`, `react`, `react-hooks` + - Extends: `eslint:recommended`, `@typescript-eslint/recommended`, `eslint-config-prettier` + - Airbnb config listed in devDependencies but primary config is flat format + +**Formatter:** +- Prettier 3.8.x — config at `.prettierrc.yaml` + - `singleQuote: true`, `printWidth: 85`, `tabWidth: 2`, `trailingComma: 'all'` + - Plugins: `prettier-plugin-tailwindcss` (class sorting), `prettier-plugin-sort-imports` + - Import order: NPMPackages → localImportsValue → localImportsType + +**Type Checker:** +- TypeScript 5.9.x (`tsc --noemit` via `yarn check-types`) + +## Key Scripts + +```bash +yarn start # Vite dev server on port 5000 (app mode) +yarn build # tsc + vite build → build/ (app bundle) +yarn build-lib # tsc + vite build --config vite.config.lib.ts → dist/ (NPM library) +yarn check-types # tsc --noemit (type check only) +yarn test # Jest (all tests) +yarn lint # ESLint with cache on src/ +yarn prepack # Automatically runs build-lib before npm publish +``` + +## Dual Build Outputs + +This project has two distinct build modes: + +| Mode | Config | Output | Purpose | +|------|--------|--------|---------| +| App | `vite.config.ts` | `build/` | Standalone SPA served by nginx (embedded in CrateDB or standalone) | +| Library | `vite.config.lib.ts` | `dist/` | NPM package `@cratedb/crate-gc-admin` imported by cloud-ui | + +Library externals: `react`, `react-dom`, `react-router-dom` (peer deps, not bundled). +Library exports: `dist/index.es.js`, `dist/index.umd.js`, `dist/index.d.ts`, `dist/style.css`. + +## CI/CD + +- GitHub Actions: `.github/workflows/pr.yml` (lint + build + test on PRs) +- GitHub Actions: `.github/workflows/publish.yml` (auto-publish to NPM on merge) +- Node version sourced from `.nvmrc` in CI +- Dependabot configured for NPM updates (`.github/dependabot.yml`) + +--- + +*Stack analysis: 2026-05-19* diff --git a/.planning/codebase/STRUCTURE.md b/.planning/codebase/STRUCTURE.md new file mode 100644 index 00000000..598f8de0 --- /dev/null +++ b/.planning/codebase/STRUCTURE.md @@ -0,0 +1,164 @@ +# Project Structure + +## Top-Level Directory Map + +``` +crate-gc-admin/ +├── src/ Main application source +├── test/ Global test infrastructure (setup, MSW, utils) +├── __mocks__/ Root-level Jest module mocks +├── public/ Static assets served as-is +├── build/ Library build output (vite.config.lib.ts) +├── dist/ Application build output (vite.config.ts) +├── conf/ nginx configuration for Docker deployment +├── devtools/ Internal developer tools / scripts +├── components.json shadcn/ui component registry config +├── eslint.config.mjs ESLint flat config +├── jest.config.js Jest configuration +├── tailwind.config.js Tailwind CSS configuration +├── tsconfig.json Main TypeScript config (app) +├── tsconfig.build.json TypeScript config for library build +├── tsconfig.node.json TypeScript config for Vite/Node scripts +├── vite.config.ts Vite app build config +├── vite.config.lib.ts Vite library build config +├── postcss.config.js PostCSS (Tailwind pipeline) +├── Dockerfile Production Docker image +├── yarn.lock Yarn dependency lockfile +└── index.html Vite HTML entry point +``` + +## Source Tree (`src/`) + +``` +src/ +├── main.tsx App entry point — mounts React into #root with BrowserRouter +├── App.tsx Root component — route definitions, auth gate, ClusterHealthManager +├── index.css Global CSS imports (Tailwind base + ant design overrides) +├── assets/ Static images and SVG icons +├── components/ Reusable UI components (see below) +├── constants/ App-wide constant values +├── hooks/ Shared React hooks +├── hooks/__test__/ Unit tests for hooks +├── routes/ Page-level route components +├── state/ Zustand global stores +├── swr/ SWR fetcher configuration and JWT handling +├── swr/jwt/ JWT-aware SWR fetcher +├── types/ Shared TypeScript interfaces and types +├── types/policies/ Policy-specific types +└── utils/ Pure utility functions +``` + +## Components (`src/components/`) + +50+ reusable components, each in its own directory with component file + optional test: + +| Component | Purpose | +|-----------|---------| +| `Button` | Styled button variants | +| `Card` | Container card | +| `ClusterHealthManager` | Polls cluster health, stores in Zustand | +| `DataTable` | Generic sortable/paginated data table | +| `GCChart` | Recharts wrapper for cluster metrics | +| `GcAdminAntdProvider` | Ant Design theme provider | +| `GCSpin` | Loading spinner | +| `GCStatusIndicator` | Green/yellow/red health indicator | +| `Layout` | App shell with sidebar navigation | +| `NotificationHandler` | Toast/notification system | +| `SQLEditor` | Ace-based SQL editor (428 lines) | +| `SQLHistory` | SQL query history panel | +| `SQLResults` | Results grid with JSON tree view | +| `StatusBar` | Bottom status bar | +| `Tree` | Collapsible tree component | + +## Routes (`src/routes/`) + +| Route | Path (approximate) | Description | +|-------|-------------------|-------------| +| `Auth` | `/login` | Authentication screen | +| `Overview` | `/` | Cluster health dashboard | +| `Nodes` | `/nodes` | Node list and health | +| `Tables` | `/tables` | Table browser | +| `TablesShards` | `/shards` | Shard distribution view | +| `SQLConsole` | `/sql` | Interactive SQL console | +| `Users` | `/users` | User management | +| `Help` | `/help` | Documentation links | +| `Automation` | `/automation` | Jobs and table policies | +| `Automation/routes` | sub-routes | Job scheduler, policy editor | +| `Automation/views` | — | JobsTable, PolicyForm (729 lines), etc. | +| `Automation/hooks` | — | Automation-specific hooks | + +## State (`src/state/`) + +Zustand stores: +- `jwtManager` — JWT token storage, refresh, URL param extraction +- Session / cluster connection state +- Cluster health state (fed by `ClusterHealthManager`) + +## SWR (`src/swr/`) + +- `swr/jwt/` — JWT-aware fetch wrapper used as SWR fetcher +- Hooks that use SWR live in `src/hooks/swrHooks.ts` + +## Types (`src/types/`) + +- `query.ts` — SQL result shapes (`rows: any[][]`, column metadata) +- `policies/` — Automation policy type definitions +- Other shared domain types + +## Test Infrastructure (`test/`) + +``` +test/ +├── setup.ts Per-file setup (jest-dom, MSW server start, browser mocks) +├── global-setup.ts Global setup (TZ = Europe/Vienna) +├── index.ts Re-exports +├── msw/ +│ ├── server.ts MSW server instance +│ ├── handlers.ts Default handler list +│ ├── handlers/ Per-feature handler files +│ ├── handlerFactory.ts Factory for reusable handlers +│ └── getRequestSpy.ts Spy utility for MSW requests +└── testUtils/ + ├── renderWithTestWrapper.tsx Render wrapper with providers + ├── createLocationTestUtil.ts Mock location factory + ├── actWithFakeTimers.ts Fake timers + act() helper + ├── disableConsole.ts Suppress expected console errors + └── treeUtils.ts Tree query helpers +``` + +## Root Mocks (`__mocks__/`) + +Applied automatically by Jest `moduleNameMapper`: +- `react-router-dom` — navigation mocks +- `localStorageMock` — in-memory localStorage +- `ace-builds` — no-op for browser-only SQL editor +- `empty-module.ts` — stub for SVG/PNG imports + +## Entry Points + +| File | Role | +|------|------| +| `index.html` | Vite HTML template | +| `src/main.tsx` | React mount point, BrowserRouter wrapping | +| `src/App.tsx` | Route definitions, auth gate | +| `src/index.css` | Global styles | + +## File Naming Conventions + +- Components: `PascalCase.tsx` matching directory name (e.g., `Button/Button.tsx`) +- Hooks: `camelCase.ts` with `use` prefix (e.g., `useExecuteSql.ts`) +- Utilities: `camelCase.ts` (e.g., `nodes.ts`, `statusChecks.ts`) +- Tests: `ComponentName.test.tsx` or `hookName.test.ts` +- Types: `camelCase.ts` or `PascalCase.ts` for domain models + +## Where to Add New Code + +| What | Where | +|------|-------| +| New reusable UI component | `src/components/NewComponent/NewComponent.tsx` | +| New page/view | `src/routes/NewRoute/NewRoute.tsx` + register in `src/App.tsx` | +| New global state | `src/state/newStore.ts` (Zustand) | +| New SWR-based data hook | `src/hooks/swrHooks.ts` or new file in `src/hooks/` | +| New utility function | `src/utils/newUtil.ts` | +| New shared types | `src/types/newType.ts` | +| New MSW handlers (for tests) | `test/msw/handlers/newFeature.ts` + import in `handlers.ts` | diff --git a/.planning/codebase/TESTING.md b/.planning/codebase/TESTING.md new file mode 100644 index 00000000..ad9e1a0b --- /dev/null +++ b/.planning/codebase/TESTING.md @@ -0,0 +1,127 @@ +# Testing + +## Framework + +| Tool | Version | Purpose | +|------|---------|---------| +| Jest | 29.x | Test runner and assertion library | +| ts-jest | — | TypeScript transpilation for Jest | +| jest-fixed-jsdom | — | JSDOM environment with React 19 fixes | +| @testing-library/react | — | Component rendering and queries | +| @testing-library/jest-dom | — | DOM matchers (`toBeInTheDocument`, etc.) | +| @testing-library/user-event | — | User interaction simulation | +| MSW 2.x | — | API mocking via Service Worker | + +## Test File Organization + +Tests are co-located with the code they test: +- Component tests: `src/components/ComponentName/ComponentName.test.tsx` +- Route tests: `src/routes/RouteName/RouteName.test.tsx` +- Hook tests: `src/hooks/__test__/hookName.test.ts` +- DataTable tests: `src/components/DataTable/test/*.test.tsx` + +Test files match the regex: `(/__tests__/*.test.js|\.(test))\.(jsx|js|tsx|ts)$` + +## Configuration (jest.config.js) + +``` +testEnvironment: jest-fixed-jsdom +preset: ts-jest/presets/js-with-ts +rootDir: . +collectCoverage: false ← thresholds defined but not enforced by default +globalSetup: ./test/global-setup.ts +setupFilesAfterEnach: ./test/setup.ts +transformIgnorePatterns: node_modules except until-async and pretty-bytes +``` + +Coverage thresholds (active only when `collectCoverage: true`): +- branches: 80% +- functions: 80% +- lines: 80% +- statements: 80% + +## Global Setup (test/global-setup.ts) + +Sets `process.env.TZ = 'Europe/Vienna'` — all date tests assume this timezone. + +## Per-Test Setup (test/setup.ts) + +Runs before every test file: +- Imports `@testing-library/jest-dom` +- Polyfills `window.fetch` via `whatwg-fetch` +- Patches `antd` static APIs (`message`, `notification`) for React 19 compatibility using `unstableSetRender` +- Mocks `window.matchMedia`, `window.ResizeObserver`, `window.open`, `window.scrollTo` +- Mocks `window.localStorage` with a custom `localStorageMock` +- Starts the MSW server via `test/msw/server.ts` + +## Root-Level Mocks (`__mocks__/`) + +Module-level mocks applied automatically: +- `react-router-dom` — provides `useLocation` mock and navigation helpers +- `zustand` — store reset between tests +- `ace-builds` — no-op for the SQL editor (heavy browser dependency) +- `localStorageMock` — synchronous in-memory implementation + +## MSW Mock Strategy (`test/msw/`) + +| File | Purpose | +|------|---------| +| `server.ts` | Creates and exports the MSW `setupServer` instance | +| `handlers.ts` | Default handler list registered on every test | +| `handlers/` | Per-feature handler modules (imported by `handlers.ts`) | +| `handlerFactory.ts` | Factory functions to create reusable request handlers | +| `getRequestSpy.ts` | Utility to spy on specific MSW requests within a test | + +**Server lifecycle pattern:** +```ts +beforeAll(() => server.listen()) +afterEach(() => server.resetHandlers()) +afterAll(() => server.close()) +``` + +Per-test handler overrides: `server.use(...customHandlers)` within individual tests. + +## Test Utilities (`test/testUtils/`) + +| File | Purpose | +|------|---------| +| `renderWithTestWrapper.tsx` | Wraps component under test with providers (Router, theme, etc.) | +| `createLocationTestUtil.ts` | Creates mock location objects for route testing | +| `actWithFakeTimers.ts` | Combines `jest.useFakeTimers()` with `act()` for timed effects | +| `disableConsole.ts` | Suppresses expected console errors in tests | +| `treeUtils.ts` | Helpers for querying tree-structured components | + +## Common Patterns + +**Async render:** +```ts +render(, { wrapper: renderWithTestWrapper }) +await screen.findByText('Expected Text') +``` + +**User interactions:** +```ts +const user = userEvent.setup() +await user.click(screen.getByRole('button', { name: /submit/i })) +``` + +**API spying:** +```ts +const spy = getRequestSpy(server, 'POST', '/api/endpoint') +await user.click(submitButton) +expect(spy).toHaveBeenCalledWith(expect.objectContaining({ body: '...' })) +``` + +**Hook testing:** +```ts +const { result } = renderHook(() => useMyHook(), { wrapper: renderWithTestWrapper }) +await waitFor(() => expect(result.current.data).toBeDefined()) +``` + +## Running Tests + +```bash +yarn test # run all tests (watch mode) +yarn test --ci # single-pass, no watch (CI) +yarn test --coverage # enable coverage collection (enforces 80% thresholds) +``` From 4223539da6ad9e95b82db4d5c9a96fc1a269380c Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 19 May 2026 17:22:21 +0200 Subject: [PATCH 4/5] Fix ESM/CJS issues --- jest.config.js | 6 +++-- .../ClusterHealthManager.test.tsx | 9 ------- .../TablesShards/TablesShardsMetrics.test.tsx | 8 ------ test/esm-to-cjs.cjs | 25 +++++++++++++++++++ test/setup.ts | 2 +- 5 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 test/esm-to-cjs.cjs diff --git a/jest.config.js b/jest.config.js index 060143a5..6ab1475d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,7 +6,7 @@ export default { preset: 'ts-jest/presets/js-with-ts', rootDir: '.', testRegex: '(/__tests__/*.test.js|\\.(test))\\.(jsx|js|tsx|ts)$', - moduleFileExtensions: ['jsx', 'js', 'tsx', 'ts', 'json'], + moduleFileExtensions: ['jsx', 'js', 'mjs', 'tsx', 'ts', 'json'], collectCoverage: false, coverageThreshold: { global: { @@ -44,8 +44,10 @@ export default { }, }, ], + 'node_modules/rettime/.*\\.mjs$': ['/test/esm-to-cjs.cjs'], + 'node_modules/@open-draft/deferred-promise/.*\\.mjs$': ['/test/esm-to-cjs.cjs'], }, - transformIgnorePatterns: ['node_modules/(?!until-async)(?!pretty-bytes/.*)'], + transformIgnorePatterns: ['node_modules/(?!(until-async|pretty-bytes|rettime|@open-draft/deferred-promise)/.*)'], setupFilesAfterEnv: ['/test/setup.ts'], globalSetup: './test/global-setup.ts', }; diff --git a/src/components/ClusterHealthManager/ClusterHealthManager.test.tsx b/src/components/ClusterHealthManager/ClusterHealthManager.test.tsx index f86e034b..58aa7eb4 100644 --- a/src/components/ClusterHealthManager/ClusterHealthManager.test.tsx +++ b/src/components/ClusterHealthManager/ClusterHealthManager.test.tsx @@ -33,21 +33,12 @@ const mockNodeStatus = (rows: [][]) => { }; describe('ClusterHealthManager', () => { - beforeAll(() => { - server.listen(); - }); - afterEach(() => { - server.resetHandlers(); act(() => { useClusterHealthStore.setState({ clusterHealth: {} }); }); }); - afterAll(() => { - server.close(); - }); - describe('load', () => { it('populates the store with load data when nodes are available', async () => { setup(); diff --git a/src/routes/TablesShards/TablesShardsMetrics.test.tsx b/src/routes/TablesShards/TablesShardsMetrics.test.tsx index eb73a0cc..e1a6081a 100644 --- a/src/routes/TablesShards/TablesShardsMetrics.test.tsx +++ b/src/routes/TablesShards/TablesShardsMetrics.test.tsx @@ -1,19 +1,11 @@ import TablesShardsMetrics from './TablesShardsMetrics'; import { render, screen, within } from 'test/testUtils'; -import server from 'test/msw'; const setup = () => { return render(); }; describe('the TablesShardsMetrics component', () => { - beforeAll(() => { - server.listen(); - }); - - afterAll(() => { - server.close(); - }); describe('the statistics panel', () => { it('displays the cluster health', async () => { diff --git a/test/esm-to-cjs.cjs b/test/esm-to-cjs.cjs new file mode 100644 index 00000000..1d1d948e --- /dev/null +++ b/test/esm-to-cjs.cjs @@ -0,0 +1,25 @@ +'use strict'; + +// Minimal ESM-to-CJS transformer for pure-ESM node_modules packages. +// Used specifically for rettime, which msw 2.13.x depends on but ships only as .mjs. +// Handles named imports/exports only — sufficient for rettime's actual surface area. +module.exports = { + process(sourceText) { + let code = sourceText; + + // import { X, Y } from './path' → const { X, Y } = require('./path') + code = code.replace( + /^import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]\s*;?/gm, + (_, namedImports, modulePath) => + `const {${namedImports}} = require('${modulePath}');`, + ); + + // export { X, Y } → module.exports = { X, Y } + code = code.replace( + /^export\s+\{([^}]+)\}\s*;?/gm, + (_, namedExports) => `module.exports = {${namedExports}};`, + ); + + return { code }; + }, +}; diff --git a/test/setup.ts b/test/setup.ts index b1d87820..0a6be561 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -58,8 +58,8 @@ Object.defineProperty(window, 'localStorage', { value: mockLocalStorage, }); +beforeAll(() => server.listen()); beforeEach(() => { - server.listen(); useLocation.mockReturnValue({ pathname: '', }); From ca4ab3a9da9c8f39ec1c9afddf9074a866649d66 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Tue, 19 May 2026 17:23:04 +0200 Subject: [PATCH 5/5] Revert "Add codebase map to .planning/codebase/" This reverts commit b283b9448f4a020ccd53f728209cdb59536d9a1b. --- .planning/codebase/ARCHITECTURE.md | 255 ----------------------------- .planning/codebase/CONCERNS.md | 89 ---------- .planning/codebase/CONVENTIONS.md | 168 ------------------- .planning/codebase/INTEGRATIONS.md | 185 --------------------- .planning/codebase/STACK.md | 177 -------------------- .planning/codebase/STRUCTURE.md | 164 ------------------- .planning/codebase/TESTING.md | 127 -------------- 7 files changed, 1165 deletions(-) delete mode 100644 .planning/codebase/ARCHITECTURE.md delete mode 100644 .planning/codebase/CONCERNS.md delete mode 100644 .planning/codebase/CONVENTIONS.md delete mode 100644 .planning/codebase/INTEGRATIONS.md delete mode 100644 .planning/codebase/STACK.md delete mode 100644 .planning/codebase/STRUCTURE.md delete mode 100644 .planning/codebase/TESTING.md diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md deleted file mode 100644 index 69e8b462..00000000 --- a/.planning/codebase/ARCHITECTURE.md +++ /dev/null @@ -1,255 +0,0 @@ - -# Architecture - -**Analysis Date:** 2026-05-19 - -## System Overview - -```text -┌─────────────────────────────────────────────────────────────────────┐ -│ Browser SPA │ -│ `src/main.tsx` (entry) │ -├───────────────────────────────┬─────────────────────────────────────┤ -│ App Shell │ Route Views │ -│ `src/App.tsx` │ `src/routes/` (PascalCase dirs) │ -│ Layout, Nav, StatusBar │ Auth, Overview, SQLConsole, │ -│ ClusterHealthManager │ Tables, TablesShards, Nodes, │ -│ NotificationHandler │ Users, Automation/* │ -└────────────┬──────────────────┴──────────────────┬──────────────────┘ - │ │ - ▼ ▼ -┌────────────────────────┐ ┌───────────────────────────────────┐ -│ Shared Components │ │ Data Fetching Layer │ -│ `src/components/` │ │ SWR hooks `src/swr/jwt/` │ -│ ~50 UI components │ │ Axios API `src/hooks/` │ -│ (design system) │ │ Direct SQL `useExecuteSql.ts` │ -└────────────────────────┘ └──────────────┬────────────────────┘ - │ - ┌────────────────────────────────────┘ - ▼ -┌────────────────────────────────────────────────────────────────────┐ -│ Global State (Zustand) │ -│ `src/state/jwtManager.ts` Auth + cluster URL management │ -│ `src/state/clusterHealth.ts` Node health metrics cache │ -│ `src/state/session.ts` UI notifications + display prefs │ -└────────────────────────────────────────────────────────────────────┘ - │ - ▼ -┌────────────────────────────────────────────────────────────────────┐ -│ External Backends │ -│ CrateDB HTTP API `/_sql` (direct SQL queries) │ -│ Grand Central API `/api/*` (REST — scheduled jobs, policies) │ -└────────────────────────────────────────────────────────────────────┘ -``` - -## Component Responsibilities - -| Component | Responsibility | File | -|-----------|----------------|------| -| App | Root shell, routing, connection check | `src/App.tsx` | -| Layout | Sidebar nav + topbar frame | `src/components/Layout/Layout.tsx` | -| ClusterHealthManager | Polls node status, writes to health store | `src/components/ClusterHealthManager/ClusterHealthManager.tsx` | -| NotificationHandler | Renders toast notifications from session store | `src/components/NotificationHandler/NotificationHandler.tsx` | -| EnterpriseScreen | Guards enterprise-only routes by GC connection status | `src/components/EnterpriseScreen/EnterpriseScreen.tsx` | -| StatusBar | Topbar cluster connection indicator | `src/components/StatusBar/StatusBar.tsx` | -| useJWTManagerStore | JWT auth, cluster URL, connection status | `src/state/jwtManager.ts` | -| useClusterHealthStore | Node load/fs stats accumulation | `src/state/clusterHealth.ts` | -| useSessionStore | Toast notifications + SQL display prefs | `src/state/session.ts` | -| useExecuteSql | Raw POST to CrateDB `/_sql` endpoint | `src/hooks/useExecuteSql.ts` | -| useExecuteMultiSql | Multi-statement parse + sequential SQL execution | `src/hooks/useExecuteMultiSql.ts` | -| useGcApi | Axios instance with JWT Bearer auth for GC REST API | `src/hooks/useGcApi.ts` | -| swr/jwt/* | SWR data hooks that query CrateDB `sys.*` tables via SQL | `src/swr/jwt/` | -| swrHooks (hooks/) | SWR data hooks for Grand Central REST API endpoints | `src/hooks/swrHooks.ts` | - -## Pattern Overview - -**Overall:** Single-Page Application (SPA) — React 19 with client-side routing via React Router v6. - -**Key Characteristics:** -- Dual-mode deployment: standalone admin UI (local CrateDB connection) and embedded cloud component (CrateDB Cloud with JWT auth) -- Ships as both a runnable SPA (`vite build`) and a publishable npm library (`vite build --config vite.config.lib.ts`) -- Feature access gated by GC (Grand Central) connection status via `EnterpriseScreen` -- No server-side rendering — all data fetching happens in the browser - -## Layers - -**Route Views:** -- Purpose: Page-level components corresponding to URL paths -- Location: `src/routes/` -- Contains: Route component folders (Auth, Automation, Help, Nodes, Overview, SQLConsole, Tables, TablesShards, Users) -- Depends on: shared components, hooks, swr hooks, state stores -- Used by: `src/constants/routes.tsx` (route config), `src/App.tsx` - -**Shared Component Library:** -- Purpose: Reusable UI primitives and composite widgets -- Location: `src/components/` -- Contains: ~50 components from primitives (Button, Input, Label) to complex (SQLEditor, DataTable, GCChart) -- Depends on: Ant Design, Radix UI, TanStack Table, Recharts, Tailwind CSS -- Used by: route views, App shell - -**Data Fetching — SWR/CrateDB SQL:** -- Purpose: Fetch cluster metrics and schema data by running SQL against `sys.*` tables -- Location: `src/swr/jwt/` -- Contains: `useClusterNodeStatus`, `useClusterInfo`, `useTables`, `useTablesShards`, `useShards`, `useAllocations`, `useSchemaTree`, `useCurrentUser`, `useQueryStats`, `useUsersRoles` -- Depends on: `swrJWTFetch.ts`, `useJWTManagerStore` (for auth headers and cluster URL) -- Used by: route views, `ClusterHealthManager` - -**Data Fetching — SWR/GC REST API:** -- Purpose: Fetch Grand Central automation data (scheduled jobs, table policies, logs) -- Location: `src/hooks/swrHooks.ts` -- Contains: `useGCGetScheduledJobs`, `useGCGetPolicies`, `useGCGetPoliciesLogs`, `useGCGetSchemas`, etc. -- Depends on: `swrCORSFetch.ts`, `useGcApi`, `useJWTManagerStore` -- Used by: `src/routes/Automation/` views and hooks - -**API Utilities — Imperative REST:** -- Purpose: One-shot axios-based calls for mutations (create/update/delete jobs and policies) -- Location: `src/utils/api.ts` -- Contains: `apiGet`, `apiPost`, `apiPut`, `apiPatch`, `apiDelete`, `apiHead` — all return `ApiOutput` -- Depends on: `useSessionStore` (to dispatch error notifications) -- Used by: form submit handlers in Automation routes - -**Global State (Zustand):** -- Purpose: Cross-component shared state without prop drilling -- Location: `src/state/` -- Contains: `jwtManager.ts`, `clusterHealth.ts`, `session.ts` -- Depends on: nothing (leaf layer) -- Used by: hooks, components, API utils - -**Constants:** -- Purpose: App-wide configuration literals -- Location: `src/constants/` -- Contains: `paths.ts` (path-parser Path objects), `routes.tsx` (route definitions), `navigation.tsx` (nav config), `queries.ts`, `policies.ts`, `database.ts`, `defaults.ts`, `colors.ts` - -**Types:** -- Purpose: TypeScript type definitions and enums -- Location: `src/types/` -- Contains: `cratedb.ts` (CrateDB response shapes), `query.ts` (SQL result types + ColumnType enum), `job.ts`, `route.ts`, `policies/` - -**Utilities:** -- Purpose: Pure helper functions -- Location: `src/utils/` -- Contains: `api.ts`, `nodes.ts`, `statusChecks.ts`, `sqlFormatter.ts`, `bytes.ts`, `numbers.ts`, `compare.ts`, `filtering.ts`, `sorting.ts`, `cron.ts`, `strings.ts`, `arrays.ts`, `cn.ts` - -## Data Flow - -### Primary SQL Query Path (CrateDB `/_sql`) - -1. Component calls `useExecuteSql()` hook (`src/hooks/useExecuteSql.ts`) -2. Hook reads auth headers from `useJWTManagerStore.getState().getHeaders()` -3. Hook reads the target URL from `useJWTManagerStore.getState().getUrl(identifier)` -4. Raw `fetch()` POST to `/_sql?error_trace&types` with JSON body `{ stmt: sql }` -5. Response returned as `ExecuteSqlResult { data, status, success }` - -### SWR Polling Path (cluster metrics) - -1. SWR hook in `src/swr/jwt/` declares a SQL query constant -2. `useSWR(key, swrJWTFetch)` polls on a timer (e.g., 5 s for node status) -3. `swrJWTFetch` (`src/swr/swrJWTFetch.ts`) posts SQL via `useJWTManagerStore` auth -4. Optional `postFetch` transform maps raw `rows[][]` to typed objects -5. SWR cache provides deduplication and revalidation - -### Grand Central REST API Path - -1. Component calls `useGcApi()` to get an axios instance (`src/hooks/useGcApi.ts`) -2. Axios interceptor attaches `Authorization: Bearer ` from JWT store -3. SWR hook in `src/hooks/swrHooks.ts` uses `swrCORSFetch(axiosInstance)` as fetcher -4. Mutations (create/edit/delete) call `apiPost` / `apiPut` / `apiDelete` directly -5. API errors trigger `useSessionStore.setNotification()` → `NotificationHandler` renders toast - -### Connection Mode Branching - -- **Local admin UI mode** (`isLocalConnection: true`): direct HTTP to `http://localhost:4200/_sql`, no JWT header -- **Cloud mode** (`isLocalConnection: false`, `isJWTEnabled: true`): JWT Bearer token to CrateDB cluster FQDN `/_sql`, or proxied through GC URL when JWT not enabled - -**State Management:** -- Zustand stores are module-level singletons (`create()`) -- `useJWTManagerStore` is accessed both as a React hook (components) and imperatively via `.getState()` (hooks, utils that run outside React render cycle) -- No context providers for state — Zustand handles subscription internally - -## Key Abstractions - -**Route:** -- Purpose: Typed route descriptor combining path, element, label, and key -- Examples: `src/constants/routes.tsx`, `src/types/route.ts` -- Pattern: array of `Route[]` iterated in `App.tsx` to render `` - -**Path:** -- Purpose: Typed path objects using `path-parser` library for URL construction and parameter extraction -- Examples: `src/constants/paths.ts` — `root`, `auth`, `automationEditJob` (with `:jobId` param) -- Pattern: `new Path('/automation/scheduled-jobs/edit-job/:jobId')`, use `.path` for string, `.build(params)` for filled URL - -**swrJWTFetch:** -- Purpose: Adapter that runs SQL queries through the JWT auth layer for SWR consumption -- Examples: `src/swr/swrJWTFetch.ts`, used by all `src/swr/jwt/` hooks -- Pattern: `useSWR(key, ([url]) => swrJWTFetch(url, SQL_QUERY, postFetch))` - -**ApiOutput:** -- Purpose: Normalized response envelope for all REST mutations -- Examples: `src/utils/api.ts` — `{ success, data, status, errors? }` -- Pattern: all `apiGet/Post/Put/Patch/Delete` functions return `Promise>` - -## Entry Points - -**Application Entry:** -- Location: `src/main.tsx` -- Triggers: Vite HTML shell (`index.html`) loads the bundle -- Responsibilities: Mounts React root, wraps app in `BrowserRouter` - -**Library Entry:** -- Location: `src/index.ts` -- Triggers: npm consumers import from `@cratedb/crate-gc-admin` -- Responsibilities: Re-exports selected components, hooks, state stores, and types for use in host apps (e.g., CrateDB Cloud UI) - -**App Component:** -- Location: `src/App.tsx` -- Triggers: Rendered by `main.tsx` -- Responsibilities: Checks GC connection on mount, renders `Layout` with navigation, maps route config to ``, mounts `ClusterHealthManager` and `NotificationHandler` - -## Architectural Constraints - -- **Dual-use library/app:** The codebase builds as both a standalone SPA and an ES/UMD library. Components exported from `src/index.ts` must work when consumed by a host app that provides its own `BrowserRouter`. Avoid any component that assumes it is the top-level app. -- **`useJWTManagerStore` used imperatively:** `src/hooks/useExecuteSql.ts` and `src/swr/swrJWTFetch.ts` call `useJWTManagerStore.getState()` outside the React render cycle. This is intentional but means these modules are coupled to the Zustand store singleton. -- **No React Context for state:** All cross-component state flows through Zustand stores, not React context. Do not introduce context providers for state that needs to be shared widely. -- **SWR deduplication:** SWR keys in `src/swr/jwt/` use a tuple `[url, clusterId]`. Changing the key shape breaks caching. Keep key format consistent. -- **Enterprise gating:** Routes that require Grand Central must be wrapped in ``. Do not render GC-dependent UI without this wrapper. -- **Path aliases:** TypeScript path aliases defined in `tsconfig.json` map bare module names (`components`, `hooks`, `routes`, `state`, `types`, `utils`, `constants/*`) to `src/` subdirectories. Always use bare alias imports, not relative paths that cross layer boundaries. - -## Anti-Patterns - -### Calling `useJWTManagerStore` as a hook inside async functions - -**What happens:** Some new code may attempt `const store = useJWTManagerStore()` inside a callback or async function. -**Why it's wrong:** React hooks cannot be called outside render functions. The correct escape hatch already exists. -**Do this instead:** Use `useJWTManagerStore.getState()` for imperative access, as done in `src/hooks/useExecuteSql.ts` and `src/swr/swrJWTFetch.ts`. - -### Fetching data with plain `fetch` or `axios` directly in components - -**What happens:** A component makes its own API call without using an existing hook. -**Why it's wrong:** Bypasses JWT injection, SWR caching/deduplication, and error notification handling. -**Do this instead:** Use an existing SWR hook from `src/swr/jwt/` or `src/hooks/swrHooks.ts`, or create a new SWR hook following the same pattern. For mutations, use `apiPost`/`apiPut` from `src/utils/api.ts` with the `useGcApi()` axios instance. - -### Adding navigation items without a corresponding path constant - -**What happens:** A hardcoded string like `'/nodes'` appears in navigation config. -**Why it's wrong:** Creates a disconnected path that can silently diverge from the route definition. Note: `'/nodes'` in `src/constants/navigation.tsx` line 56 is an existing example of this issue. -**Do this instead:** Define a `new Path(...)` export in `src/constants/paths.ts` and reference `.path` in both `navigation.tsx` and `routes.tsx`. - -## Error Handling - -**Strategy:** Centralized notification dispatch through `useSessionStore`. - -**Patterns:** -- `src/utils/api.ts` catches non-2xx responses and calls `dispatchNotification('error', message)` automatically, so callers do not need to handle HTTP errors individually -- `NotificationHandler` component reads `useSessionStore.notification` and renders an Ant Design toast -- SQL errors from CrateDB are returned in the response body as `{ error: { message, code } }` — callers check `'error' in res.data` to detect failures -- `useExecuteMultiSql` tracks per-statement `QueryStatus` with states: `WAITING | EXECUTING | SUCCESS | ERROR | NOT_EXECUTED` - -## Cross-Cutting Concerns - -**Logging:** No structured logging library — browser `console.*` only. No log aggregation. -**Validation:** `react-hook-form` with `zod` resolvers for form validation in Automation routes. -**Authentication:** JWT stored in `sessionStorage` under a per-cluster key (e.g., `grand-central-token` or `grand-central-token.`). Token validity checked by decoding expiry; refreshed automatically via `getToken()` in `jwtManager`. - ---- - -*Architecture analysis: 2026-05-19* diff --git a/.planning/codebase/CONCERNS.md b/.planning/codebase/CONCERNS.md deleted file mode 100644 index af0e92b0..00000000 --- a/.planning/codebase/CONCERNS.md +++ /dev/null @@ -1,89 +0,0 @@ -# Codebase Concerns - -Technical health concerns across 7 categories. Each entry includes location, description, and severity. - ---- - -## 1. Code Quality - -| Severity | Location | Issue | -|----------|----------|-------| -| **High** | `src/utils/api.ts:47` | `axiosError.response!` non-null assertion crashes if network is completely unreachable (no response object) | -| **Medium** | `src/types/query.ts`, `src/hooks/useExecuteMultiSql.ts` | Direct `node_modules/` path imports (`import { Statement } from 'node_modules/@cratedb/cratedb-sqlparse/dist/parser'`) — brittle and non-portable | -| **Medium** | `src/routes/Automation/views/PolicyForm.tsx` (729 lines) | Oversized file — should be split | -| **Medium** | `src/components/SQLEditor/SQLEditor.tsx` (428 lines) | Oversized file | -| **Medium** | `src/components/DataTable/DataTable.tsx` (397 lines) | Oversized file | -| **Medium** | `src/routes/Automation/views/JobsTable.tsx` (392 lines) | Oversized file | -| **Medium** | `src/types/query.ts` | `rows: any[][]` propagates `any` through all SQL result handling | -| **Low** | Various | Inconsistent `src/` prefix vs bare alias imports across 10+ files | -| **Low** | `src/App.tsx`, `src/state/jwtManager.ts`, `src/utils/numbers.ts` | `==` instead of `===` comparisons | - ---- - -## 2. Architecture Concerns - -| Severity | Location | Issue | -|----------|----------|-------| -| **High** | App-wide | No React error boundaries anywhere — any render error blanks the entire UI | -| **High** | `src/hooks/swrHooks.ts:79–123` (`useGCGetPoliciesEnriched`) | N+1 API pattern: one request per policy on every render and SWR refresh. Acknowledged with `// NOTE: This Hook will be removed when API will return the last_execution` comment | -| **Medium** | `src/hooks/useApiCall.ts` | `}, [])` empty dependency array — stale closure bug; URL/method/body changes after mount are silently ignored | -| **Medium** | `src/hooks/useGcApi.ts` | `axios.create()` called on every render without `useMemo`; 14+ components affected | -| **Medium** | App-wide | Two parallel HTTP strategies (Axios + `useGcApi` vs native `fetch` + `swrJWTFetch`) with no documented boundary | -| **Medium** | `src/hooks/useExecuteSql.ts` | Always returns `success: true` regardless of HTTP status | - ---- - -## 3. Dependency Concerns - -| Severity | Package | Issue | -|----------|---------|-------| -| **Medium** | `moment` v2.30.1 | Legacy/maintenance-mode library used in 5 files — ~300KB bundle cost; `date-fns` or `dayjs` preferred | -| **Low** | `lodash` | Full default import (`import _ from 'lodash'`) in `src/components/SQLResults/SQLResultsTable.tsx` and `src/routes/Automation/tablePoliciesUtils/tableTree.tsx` — should use named imports for tree-shaking | -| **Low** | `jest.config.js` | `collectCoverage: false` — the 80% coverage threshold is defined but never enforced | - ---- - -## 4. Test Coverage Gaps - -| Severity | Area | Issue | -|----------|------|-------| -| **High** | `src/hooks/` | All 8 hooks have no tests, including critical `useExecuteSql`, `useExecuteMultiSql`, `swrHooks` | -| **High** | `src/utils/` | All 11 utility files have no tests, including `api.ts`, `nodes.ts` (217 lines of health calculations), `statusChecks.ts` | -| **High** | Routes | 10 route components have no tests: `Overview`, `SQLConsole`, `Auth`, `Tables/*`, `Nodes/Nodes`, `Users/Users`, `TablesShards/TablesShards`, `Help` | -| **High** | `src/state/` | All 3 Zustand stores have no tests (JWT manager, session, cluster health) | - ---- - -## 5. Security Concerns - -| Severity | Location | Issue | -|----------|----------|-------| -| **High** | `src/state/jwtManager.ts:109` | JWT tokens sent as URL query parameters (`?token=...&refresh=...`) — logged by proxies and servers | -| **Medium** | `src/state/jwtManager.ts` | JWT stored in `sessionStorage` — accessible via XSS | - ---- - -## 6. Performance Concerns - -| Severity | Location | Issue | -|----------|----------|-------| -| **Medium** | App-wide | No `React.lazy()` or `Suspense` anywhere — all routes eagerly bundled, inflating initial load | - ---- - -## 7. Operational Concerns - -| Severity | Location | Issue | -|----------|----------|-------| -| **High** | App-wide | No error boundaries — any render crash produces a blank screen with no recovery | -| **Medium** | App-wide | Zero `console.error`/structured logging in production code; API errors dispatch UI notifications but leave no trace for debugging | -| **Low** | `src/hooks/useApiCall.ts` | Requests fire with no `AbortController` cleanup on unmount — memory leak risk | -| **Low** | Config | `gcUrl` hardcoded to `http://localhost:5050` default with no warning if not overridden in production | - ---- - -## Summary - -- **High severity**: 7 issues (no error boundaries, N+1 API calls, JWT in URL params, critical hooks/utils/routes/stores untested) -- **Medium severity**: 11 issues (large files, dual HTTP strategies, stale closures, `moment` dep, XSS-accessible tokens, no lazy loading) -- **Low severity**: 6 issues (import style, `==` comparisons, `lodash` imports, coverage not enforced, memory leaks) diff --git a/.planning/codebase/CONVENTIONS.md b/.planning/codebase/CONVENTIONS.md deleted file mode 100644 index 22c94554..00000000 --- a/.planning/codebase/CONVENTIONS.md +++ /dev/null @@ -1,168 +0,0 @@ -# Coding Conventions - -**Analysis Date:** 2026-05-19 - -## Naming Patterns - -**Files:** -- React components: PascalCase filename matching the component name — `Button.tsx`, `CardHeader.tsx` -- Component constants: PascalCase with `Constants` suffix — `ButtonConstants.ts`, `ChipConstants.ts` -- Custom hooks: camelCase with `use` prefix — `useButtonStyles.ts`, `useExecuteSql.ts` -- Utility modules: camelCase, topic-scoped — `sorting.ts`, `sqlFormatter.ts`, `statusChecks.ts` -- Test files: same name as subject file with `.test.tsx` suffix — `Button.test.tsx` -- Type files: camelCase — `api.ts`, `query.ts`, `utils.ts` under `src/types/` - -**Directories:** -- Component directories: PascalCase matching component name — `src/components/Button/`, `src/components/DataTable/` -- Route directories: PascalCase, matches feature area — `src/routes/Automation/`, `src/routes/Nodes/` -- Sub-route groups: `routes/` and `views/` subdirectories within route features -- Hook tests: `__test__/` subdirectory inside `src/hooks/` - -**Components:** -- PascalCase: `Button`, `ClusterHealthManager`, `GCSpin` -- Prefix `GC` for app-level global components: `GCSpin`, `GCChart`, `GCStatusIndicator` -- Props types: named `[ComponentName]Props` and exported — `export type ButtonProps = ...` - -**Functions:** -- camelCase for all utility functions — `sortByString`, `cronParser`, `apiGet` -- Constants: SCREAMING_SNAKE_CASE — `BUTTON_KINDS`, `DATE_FORMAT`, `JOBS_TABLE_PAGE_SIZE` -- SWR hook exports: `useGC[ResourceName]` pattern — `useGCGetScheduledJobs`, `useGCGetScheduledJob` - -**Variables:** -- camelCase throughout -- Boolean flags use descriptive adjectives — `disabled`, `loading`, `active`, `enabled` -- Enum-style const objects use SCREAMING_SNAKE_CASE keys — `BUTTON_KINDS.PRIMARY`, `Heading.levels.h3` - -**Types:** -- `type` keyword preferred over `interface` for component props -- Exported types named `[ComponentName]Props` for component props -- `ValueOf` utility type used to derive union types from const objects — `src/types/utils.ts` - -## Code Style - -**Formatting:** -- Tool: Prettier 3.x with `prettier-plugin-sort-imports` and `prettier-plugin-tailwindcss` -- Config: `.prettierrc.yaml` -- `printWidth`: 85 -- `tabWidth`: 2 spaces -- `singleQuote`: true -- `trailingComma`: 'all' -- `bracketSpacing`: true -- `arrowParens`: 'avoid' (omit parens for single-argument arrow functions) -- `stripNewlines`: true - -**Linting:** -- Tool: ESLint 9.x flat config (`eslint.config.mjs`) -- Extends: `eslint:recommended`, `@typescript-eslint/recommended`, Prettier integration -- Plugins: `@typescript-eslint`, `react`, `react-hooks`, `prettier` -- Formatting errors surfaced as ESLint errors (via `eslint-plugin-prettier`) -- Key rules disabled: `react/destructuring-assignment`, `import/no-cycle`, `react/forbid-prop-types`, `react/jsx-filename-extension` -- Run lint: `yarn lint` (targets `src/` with `--ext=.ts --ext=.tsx`) - -## TypeScript Strictness - -**Config:** `tsconfig.json` -- `strict: true` — enables all strict type checks -- `noUnusedLocals: true` — unused local variables are errors -- `noUnusedParameters: true` — unused function parameters are errors -- `noFallthroughCasesInSwitch: true` — switch fallthrough is an error -- `target`: ES2020 -- `module`: ESNext -- `isolatedModules: true` - -**Path Aliases (tsconfig paths):** -- `components` → `./src/components` -- `components/*` → `./src/components/*` -- `constants/*` → `./src/constants/*` -- `contexts` / `contexts/*` → `./src/contexts/*` -- `hooks` / `hooks/*` → `./src/hooks/*` -- `routes` / `routes/*` → `./src/routes/*` -- `state` / `state/*` → `./src/state/*` -- `types` / `types/*` → `./src/types/*` -- `utils` / `utils/*` → `./src/utils/*` -- `__mocks__` / `__mocks__/*` → `./__mocks__/*` - -Use these aliases in all imports — never use relative paths like `../../components`. - -## Import Organization - -**Order (enforced by `prettier-plugin-sort-imports`):** -1. NPM packages (external) — `import React from 'react'` -2. Local value imports — `import Button from 'components/Button'` -3. Local type imports — `import type { ButtonProps } from 'components/Button'` - -**Example from `src/routes/Automation/views/JobsTable.tsx`:** -```typescript -import { CheckOutlined, CloseOutlined } from '@ant-design/icons'; -import { automationCreateJob } from 'constants/paths'; -import { cronParser, cn, apiDelete } from 'utils'; -import { ColumnDef, Table } from '@tanstack/react-table'; -import { Link, useNavigate } from 'react-router-dom'; -import { Popconfirm } from 'antd'; -import { useState } from 'react'; -import { Text, Chip, DataTable } from 'components'; -import { Job } from 'types'; -``` - -Note: the prettier plugin auto-sorts within groups; imports are not manually grouped by comment. - -## Component Patterns - -**Always functional components** — no class components observed. - -**Props type:** Declared as `type [ComponentName]Props = { ... }` and exported. For components accepting `children`, use `PropsWithChildren<{ ... }>`. - -**Default exports:** All components use `export default function ComponentName(...)` or `export default ComponentName`. - -**Named constants attached as properties:** Enum-like option sets are attached to the component function object after definition: -```typescript -Button.sizes = BUTTON_SIZES; -Button.kinds = BUTTON_KINDS; -Heading.levels = HEADING_LEVELS; -Chip.colors = AVAILABLE_CHIP_COLORS; -``` - -**Tailwind for styling:** All styles applied via Tailwind utility classes. The `cn()` utility from `src/utils/cn.ts` (wrapping `clsx` + `tailwind-merge`) is used to compose conditional class names: -```typescript -const cardClasses = cn('bg-white', 'rounded', { 'opacity-50': disabled }, className); -``` - -**Props destructuring:** Props are always destructured in the function signature, never accessed via a `props` variable. - -**Barrel files:** Each component directory exposes a single `index.ts` that re-exports the default and named exports. `src/components/index.ts` is the root barrel for all shared components. - -## Git Commit Style - -Based on recent commit history: - -- Short imperative subject line — `Add ref to Button`, `Fix bug where error trace data was not scrollable` -- PR references in parentheses for squash merges — `Expose ClusterHealthStore (#583)` -- Dependabot bumps prefixed with package name — `Bump msw from 2.12.7 to 2.13.6` -- Fix commits optionally prefixed with `fix:` — `fix: restore dist/style.css output after Vite 5→6 upgrade` -- No enforced conventional-commits standard; tone is informal and descriptive - -## Project-Specific Patterns - -**Constants objects instead of enums:** -```typescript -export const BUTTON_KINDS = { - PRIMARY: 'primary', - SECONDARY: 'secondary', -} as const; -export type ButtonKind = ValueOf; -``` -This pattern is used everywhere instead of TypeScript `enum`. - -**`ValueOf` type helper** (`src/types/utils.ts`): derives a union of value types from a `const` object. - -**`cn()` utility** (`src/utils/cn.ts`): always use for conditional Tailwind class composition. Never concatenate class strings manually. - -**SWR for data fetching:** All API data is fetched via SWR hooks (`src/hooks/swrHooks.ts`). Hook names follow `useGC[Resource][Action]`. - -**Zustand for global state:** Three stores in `src/state/` — `clusterHealth.ts`, `jwtManager.ts`, `session.ts`. State is accessed via hooks; stores reset between tests via `__mocks__/zustand.ts`. - -**GC API vs JWT API:** Two fetch paths exist. `useGcApi` fetches through the GC backend proxy (`/api/`). `useExecuteSql` / `useJWTManagerStore` fetch directly against CrateDB at `localhost:4200` using JWT auth. - ---- - -*Convention analysis: 2026-05-19* diff --git a/.planning/codebase/INTEGRATIONS.md b/.planning/codebase/INTEGRATIONS.md deleted file mode 100644 index a21956e7..00000000 --- a/.planning/codebase/INTEGRATIONS.md +++ /dev/null @@ -1,185 +0,0 @@ -# External Integrations - -**Analysis Date:** 2026-05-19 - -## Overview - -This app is a pure browser SPA with no backend of its own. All external communication is to two targets: - -1. **CrateDB HTTP API** — direct SQL queries via `/_sql` endpoint -2. **Grand Central (GC) API** — CrateDB Cloud management backend via `/api/` endpoints - -## CrateDB HTTP API - -**Purpose:** Execute SQL queries against a CrateDB cluster directly from the browser. - -**Protocol:** HTTP POST to `/_sql?error_trace&types` - -**Endpoint pattern:** -``` -{clusterUrl}/_sql?error_trace&types[&ident={identifier}] -``` - -**Auth modes (chosen at runtime):** -- **Local / Admin UI mode** — no authentication header; relies on HTTP Basic Auth handled by the browser/CrateDB directly. Default `clusterUrl` is `http://localhost:4200`. -- **JWT mode** — `Authorization: Bearer {token}` header. Used when `isJWTEnabled=true` (CrateDB >= 5.8.2) or `isLocalConnection=false`. - -**Client:** Native `fetch` API via `src/swr/swrJWTFetch.ts` - -**SWR hooks (all under `src/swr/jwt/`):** -| Hook | SQL target | Poll interval | -|------|-----------|---------------| -| `useClusterInfo.ts` | `sys.cluster` | 2 min | -| `useClusterNodeStatus.ts` | `sys.nodes` | — | -| `useCurrentUser.ts` | `sys.users` / session | — | -| `useQueryStats.ts` | `sys.jobs_log` | — | -| `useSchemaTree.ts` | `information_schema` | — | -| `useShards.ts` | `sys.shards` | — | -| `useTables.ts` | `information_schema.tables` | — | -| `useTablesShards.ts` | `sys.shards` + `information_schema` | — | -| `useUsersRoles.ts` | `sys.users`, `sys.privileges` | — | -| `useAllocations.ts` | `sys.allocations` | — | - -**Query identifier pattern:** Queries include `&ident=/{hook-name}` in the URL for MSW routing in tests. - -## Grand Central (GC) API - -**Purpose:** CrateDB Cloud management plane — scheduled jobs, table policies, schema data, authentication. - -**Base URL:** Configured at runtime via `useJWTManagerStore.gcUrl`. Defaults to `http://localhost:5050` in local mode; dynamically set to `https://{cluster.fqdn.replace('.', '.gc.')}` in cloud mode. - -**Auth:** JWT Bearer token obtained from `/api/v2/clusters/{clusterId}/jwt/` (cloud-ui provides this endpoint) and validated/stored in `sessionStorage`. - -**HTTP Client:** axios instance created per-request in `src/hooks/useGcApi.ts`, with `withCredentials: true` and `Authorization: Bearer {token}` injected via interceptor. - -**Endpoints consumed:** - -| Method | Path | Purpose | Source | -|--------|------|---------|--------| -| GET | `/api/` | Health / connection check | `src/App.tsx` | -| GET/POST | `/api/_sql` | SQL via GC proxy (non-JWT mode) | `src/state/jwtManager.ts` | -| GET | `/api/auth?token=...` | Authenticate JWT token with GC | `src/state/jwtManager.ts` | -| GET | `/api/scheduled-jobs/` | List scheduled jobs | `src/hooks/swrHooks.ts` | -| GET | `/api/scheduled-jobs/{id}` | Get single scheduled job | `src/hooks/swrHooks.ts` | -| POST | `/api/scheduled-jobs/` | Create scheduled job | `src/routes/Automation/views/JobForm.tsx` | -| PUT | `/api/scheduled-jobs/{id}` | Update scheduled job | `src/routes/Automation/views/JobsTable.tsx` | -| DELETE | `/api/scheduled-jobs/{id}` | Delete scheduled job | `src/routes/Automation/views/JobsTable.tsx` | -| GET | `/api/scheduled-jobs/{id}/log` | Job execution log | `src/hooks/swrHooks.ts` | -| GET | `/api/scheduled-jobs/logs?limit=100` | All job logs | `src/hooks/swrHooks.ts` | -| GET | `/api/scheduled-jobs/all/logs?limit=100` | All task logs | `src/hooks/swrHooks.ts` | -| GET | `/api/policies/` | List table policies | `src/hooks/swrHooks.ts` | -| GET | `/api/policies/{id}` | Get single policy | `src/hooks/swrHooks.ts` | -| POST | `/api/policies/` | Create policy | `src/routes/Automation/views/PolicyForm.tsx` | -| PUT | `/api/policies/{id}` | Update policy | `src/routes/Automation/views/PoliciesTable.tsx` | -| DELETE | `/api/policies/{id}` | Delete policy | `src/routes/Automation/views/PoliciesTable.tsx` | -| GET | `/api/policies/{id}/log?limit=2` | Policy execution log | `src/hooks/swrHooks.ts` | -| GET | `/api/policies/logs?limit=100` | All policy logs | `src/hooks/swrHooks.ts` | -| GET | `/api/policies/eligible-columns/` | Columns eligible for policy | `src/routes/Automation/hooks/useEligibleColumns.ts` | -| POST | `/api/policies/preview/` | Preview policy effect | `src/routes/Automation/hooks/usePolicyPreview.ts` | -| GET | `/api/data/schemas/` | Schema list (with tables) | `src/hooks/swrHooks.ts` | - -**Cloud-UI JWT endpoint (external, not GC):** -- GET `/api/v2/clusters/{clusterId}/jwt/` — returns `{ token, refresh }`. This URL is relative, so it is served by whatever host the cloud-ui runs on. Called only in cloud (non-local) mode. - -## Authentication - -**Two authentication modes, determined at runtime by `useJWTManagerStore`:** - -### Local / Admin UI Mode (`isLocalConnection: true`) - -- No token required -- HTTP Basic Auth handled natively by the browser when CrateDB prompts -- `gcStatus` tracks connection state (`PENDING`, `CONNECTED`, `NOT_LOGGED_IN`, `ERROR`) -- Session storage key: `grand_central_token` - -### Cloud / JWT Mode (`isLocalConnection: false`) - -- JWT token fetched from cloud-ui's `/api/v2/clusters/{clusterId}/jwt/` -- Token stored in `sessionStorage` under key `grand-central-token.{clusterId}` -- Token validated on each use via `jwt-decode` — reacquired if expiry < 10 seconds -- Token passed as `Authorization: Bearer` to both GC API (axios) and CrateDB direct (fetch) -- JWT direct-to-CrateDB enabled only when `clusterVersion >= 5.8.2` (`isJWTEnabled` flag) -- Auth flow entry: `src/routes/Auth/Auth.tsx` — accepts `?token=` + `?refresh=` URL params, calls `login()` which hits `{gcUrl}/api/auth` - -**State management:** `src/state/jwtManager.ts` (Zustand store) owns all auth state and is the single source of truth for URLs and headers. - -## Data Storage - -**Databases:** -- CrateDB — the app's only data store; no relational or document DB of its own - -**File Storage:** -- None — no file storage integration - -**Caching:** -- SWR in-memory cache for all API responses -- `sessionStorage` — JWT tokens only (key: `grand_central_token` or `grand-central-token.{clusterId}`) -- No localStorage usage for data (mocked out in tests) - -## Monitoring & Observability - -**Error Tracking:** -- None — no Sentry, Datadog, or similar SDK detected - -**Logs:** -- No structured logging library; errors surfaced to users via Ant Design notification system (managed by `src/state/session.ts`) - -## External Documentation Links - -The app links out to `cratedb.com` docs from constants: -- `CRATEDB_PRIVILEGES_DOCS` — `src/constants/defaults.ts` -- `CRATEDB_ERROR_CODES_DOCS` — `src/constants/defaults.ts` -- `CRATEDB_CLUSTER_DOCS` — `src/constants/defaults.ts` - -These are UI links only, not API integrations. - -## Mock / Stub Layer for Tests - -**Framework:** MSW (Mock Service Worker) 2.13.x — `test/msw/` - -**Server setup:** `test/msw/server.ts`, started/stopped in `test/setup.ts` - -**Handler groups:** -| File | Intercepts | -|------|-----------| -| `test/msw/handlers/queries.ts` | `POST http://localhost:4200/_sql` (JWT) and `POST /api/_sql` (CORS) | -| `test/msw/handlers/scheduledJobs.ts` | GC scheduled job endpoints | -| `test/msw/handlers/policies.ts` | GC policy endpoints | -| `test/msw/handlers/jwt.ts` | JWT auth endpoints | - -**Query routing in tests:** JWT SQL queries are routed by the `?ident=` param to return fixture data from `test/__mocks__/` (e.g., `useClusterNodeStatusMock.ts`, `useTablesShardsMock.ts`). - -**Additional module mocks (`__mocks__/`):** -- `zustand.ts` — resets store state between tests -- `react-router-dom.tsx` — stubs `useLocation`, `useNavigate`, etc. -- `react-ace/index.tsx` — no-op editor -- `react-syntax-highlighter/index.tsx` — no-op highlighter -- `react-resizable-panels/index.tsx` — no-op panels -- `ace-builds/` — empty mode/theme stubs -- `localStorageMock.ts` — in-memory localStorage replacement - -## CI/CD & Deployment - -**Hosting:** -- Standalone: nginx (Docker image `nginx` referenced in Dependabot config) -- Embedded: served from within CrateDB itself as the Admin UI - -**CI Pipeline:** GitHub Actions -- `.github/workflows/pr.yml` — lint, build (app + lib), test on every PR -- `.github/workflows/publish.yml` — publishes to NPM on merge to main - -**NPM Registry:** `https://registry.npmjs.org` (public, package `@cratedb/crate-gc-admin`) - -## Environment Configuration - -**`.env` file:** Present at repo root (contents not read — may contain local dev overrides). - -**No `VITE_*` environment variables** were found referenced in source code. All runtime configuration (cluster URL, GC URL, JWT settings) is passed programmatically via the `useJWTManagerStore.updateCluster()` API rather than build-time env vars. - -**Secrets location:** -- JWT tokens: `sessionStorage` in browser only -- No server-side secrets (frontend-only project) - ---- - -*Integration audit: 2026-05-19* diff --git a/.planning/codebase/STACK.md b/.planning/codebase/STACK.md deleted file mode 100644 index 648763a7..00000000 --- a/.planning/codebase/STACK.md +++ /dev/null @@ -1,177 +0,0 @@ -# Technology Stack - -**Analysis Date:** 2026-05-19 - -## Languages - -**Primary:** -- TypeScript 5.9.x — all source code in `src/` -- TSX — React component files throughout `src/components/`, `src/routes/` - -**Secondary:** -- JavaScript — `jest.config.js`, `postcss.config.js`, `tailwind.config.js` -- CSS / Less — `src/index.css`, component stylesheets; Less is a runtime dependency for Ant Design theming - -## Runtime - -**Environment:** -- Node.js 24.12.0 (pinned via `.nvmrc`) -- Browser-only at runtime — no server-side rendering; app is a pure SPA - -**Target:** -- Production: `>0.2%` coverage (browserslist), excluding dead/op_mini browsers -- Development: latest Chrome, Firefox, Safari - -**Package Manager:** -- yarn 1.22.22 (pinned in `package.json` `packageManager` field) -- Lockfile: `yarn.lock` present - -## Frameworks - -**Core:** -- React 19.0.x — UI rendering (`src/main.tsx` entry, `src/App.tsx` root component) -- react-router-dom 6.30.x — client-side routing (`src/constants/paths.ts`, `src/constants/routes.tsx`) - -**State Management:** -- zustand 5.0.x — three stores: `src/state/jwtManager.ts`, `src/state/session.ts`, `src/state/clusterHealth.ts` - -**Data Fetching:** -- swr 2.4.x — SWR hooks for polling Grand Central and CrateDB (`src/hooks/swrHooks.ts`, `src/swr/jwt/`) -- axios 1.15.x — HTTP client used via `src/hooks/useGcApi.ts` and `src/utils/api.ts` - -**UI Component Libraries:** -- antd (Ant Design) 5.29.x — primary component library; wrapped via `src/components/GcAdminAntdProvider/` -- @radix-ui/react-dropdown-menu, react-label, react-popover, react-select, react-slot, react-switch, react-tabs — headless UI primitives for shadcn-style components -- shadcn/ui configuration present (`components.json`), using Radix primitives with Tailwind CSS - -**Forms:** -- react-hook-form 7.73.x — form state management -- @hookform/resolvers 3.10.x — Zod integration for validation -- zod 3.25.x — schema validation - -**SQL Editor:** -- react-ace 14.0.x + ace-builds 1.43.x — SQL editor component (`src/components/SQLEditor/`) -- @cratedb/cratedb-sqlparse 0.0.17 — CrateDB-specific SQL parsing -- sql-formatter 15.7.x — SQL formatting (`src/utils/sqlFormatter.ts`) - -**Tables:** -- @tanstack/react-table 8.21.x — data table component (`src/components/DataTable/`, `src/components/Table/`) - -**Charts:** -- recharts 2.15.x — used in `src/components/GCChart/` - -**Utilities:** -- moment 2.30.x — date formatting (constants: `DATE_FORMAT`, `DATE_FORMAT_WITH_TZ`) -- lodash 4.18.x — general utility functions -- jwt-decode 4.0.x — JWT decoding in `src/state/jwtManager.ts` -- compare-versions 6.1.x — semver comparisons in `src/state/jwtManager.ts` -- cronstrue 2.59.x — human-readable cron descriptions (Automation views) -- papaparse 5.5.x — CSV parsing -- pretty-bytes 6.1.x — file size display -- path-parser 6.1.x — typed URL path building (`src/constants/paths.ts`) -- react-intl 7.1.x — i18n/formatting -- react-syntax-highlighter 15.6.x — code display (`src/components/SyntaxHighlighter/`) - -## Build Tooling - -**Bundler:** -- Vite 6.4.x — two configs: - - `vite.config.ts` — dev server (port 5000, `build/` output) and production app build - - `vite.config.lib.ts` — library build (`dist/`) producing ES and UMD bundles for NPM publish - -**Compiler/Transpiler:** -- TypeScript 5.9.x (`tsconfig.json`, `tsconfig.build.json`, `tsconfig.node.json`) - - Target: ES2020; module: ESNext; strict mode on - - `tsconfig.build.json` extends base, excludes test files -- @vitejs/plugin-react-swc 3.11.x — SWC-powered React Fast Refresh (replaces Babel) - -**Type Declarations:** -- vite-plugin-dts 4.5.4 — generates `.d.ts` files for library distribution - -**CSS:** -- Tailwind CSS 3.4.x (`tailwind.config.js`) with custom CrateDB brand colors -- PostCSS 8.5.x (`postcss.config.js`) -- autoprefixer 10.5.x -- tailwindcss-animate 1.0.7 — accordion/animation utilities - -**Path Aliases:** -Configured in `tsconfig.json` `paths` and mirrored for Jest in `jest.config.js`: -- `components/*` → `./src/components/*` -- `hooks/*` → `./src/hooks/*` -- `routes/*` → `./src/routes/*` -- `state/*` → `./src/state/*` -- `types/*` → `./src/types/*` -- `utils/*` → `./src/utils/*` -- `constants/*` → `./src/constants/*` -- `contexts/*` → `./src/contexts/*` -- `__mocks__/*` → `./__mocks__/*` - -## Testing Framework - -**Runner:** Jest 29.7.x (`jest.config.js`) -- Preset: `ts-jest/presets/js-with-ts` -- Environment: `jest-fixed-jsdom` (patched jsdom for React 19) -- Config: `jest.config.js` - -**DOM Utilities:** -- @testing-library/react 16.0.x -- @testing-library/jest-dom 6.9.x -- @testing-library/user-event 14.6.1 -- @testing-library/dom 10.x - -**Network Mocking:** -- msw 2.13.x (Mock Service Worker) — intercepts HTTP in tests (`test/msw/`) - -**Coverage threshold** (when enabled): 80% branches/functions/lines/statements - -## Dev Tools - -**Linter:** -- ESLint 9.39.x — flat config at `eslint.config.mjs` - - Plugins: `@typescript-eslint`, `prettier`, `react`, `react-hooks` - - Extends: `eslint:recommended`, `@typescript-eslint/recommended`, `eslint-config-prettier` - - Airbnb config listed in devDependencies but primary config is flat format - -**Formatter:** -- Prettier 3.8.x — config at `.prettierrc.yaml` - - `singleQuote: true`, `printWidth: 85`, `tabWidth: 2`, `trailingComma: 'all'` - - Plugins: `prettier-plugin-tailwindcss` (class sorting), `prettier-plugin-sort-imports` - - Import order: NPMPackages → localImportsValue → localImportsType - -**Type Checker:** -- TypeScript 5.9.x (`tsc --noemit` via `yarn check-types`) - -## Key Scripts - -```bash -yarn start # Vite dev server on port 5000 (app mode) -yarn build # tsc + vite build → build/ (app bundle) -yarn build-lib # tsc + vite build --config vite.config.lib.ts → dist/ (NPM library) -yarn check-types # tsc --noemit (type check only) -yarn test # Jest (all tests) -yarn lint # ESLint with cache on src/ -yarn prepack # Automatically runs build-lib before npm publish -``` - -## Dual Build Outputs - -This project has two distinct build modes: - -| Mode | Config | Output | Purpose | -|------|--------|--------|---------| -| App | `vite.config.ts` | `build/` | Standalone SPA served by nginx (embedded in CrateDB or standalone) | -| Library | `vite.config.lib.ts` | `dist/` | NPM package `@cratedb/crate-gc-admin` imported by cloud-ui | - -Library externals: `react`, `react-dom`, `react-router-dom` (peer deps, not bundled). -Library exports: `dist/index.es.js`, `dist/index.umd.js`, `dist/index.d.ts`, `dist/style.css`. - -## CI/CD - -- GitHub Actions: `.github/workflows/pr.yml` (lint + build + test on PRs) -- GitHub Actions: `.github/workflows/publish.yml` (auto-publish to NPM on merge) -- Node version sourced from `.nvmrc` in CI -- Dependabot configured for NPM updates (`.github/dependabot.yml`) - ---- - -*Stack analysis: 2026-05-19* diff --git a/.planning/codebase/STRUCTURE.md b/.planning/codebase/STRUCTURE.md deleted file mode 100644 index 598f8de0..00000000 --- a/.planning/codebase/STRUCTURE.md +++ /dev/null @@ -1,164 +0,0 @@ -# Project Structure - -## Top-Level Directory Map - -``` -crate-gc-admin/ -├── src/ Main application source -├── test/ Global test infrastructure (setup, MSW, utils) -├── __mocks__/ Root-level Jest module mocks -├── public/ Static assets served as-is -├── build/ Library build output (vite.config.lib.ts) -├── dist/ Application build output (vite.config.ts) -├── conf/ nginx configuration for Docker deployment -├── devtools/ Internal developer tools / scripts -├── components.json shadcn/ui component registry config -├── eslint.config.mjs ESLint flat config -├── jest.config.js Jest configuration -├── tailwind.config.js Tailwind CSS configuration -├── tsconfig.json Main TypeScript config (app) -├── tsconfig.build.json TypeScript config for library build -├── tsconfig.node.json TypeScript config for Vite/Node scripts -├── vite.config.ts Vite app build config -├── vite.config.lib.ts Vite library build config -├── postcss.config.js PostCSS (Tailwind pipeline) -├── Dockerfile Production Docker image -├── yarn.lock Yarn dependency lockfile -└── index.html Vite HTML entry point -``` - -## Source Tree (`src/`) - -``` -src/ -├── main.tsx App entry point — mounts React into #root with BrowserRouter -├── App.tsx Root component — route definitions, auth gate, ClusterHealthManager -├── index.css Global CSS imports (Tailwind base + ant design overrides) -├── assets/ Static images and SVG icons -├── components/ Reusable UI components (see below) -├── constants/ App-wide constant values -├── hooks/ Shared React hooks -├── hooks/__test__/ Unit tests for hooks -├── routes/ Page-level route components -├── state/ Zustand global stores -├── swr/ SWR fetcher configuration and JWT handling -├── swr/jwt/ JWT-aware SWR fetcher -├── types/ Shared TypeScript interfaces and types -├── types/policies/ Policy-specific types -└── utils/ Pure utility functions -``` - -## Components (`src/components/`) - -50+ reusable components, each in its own directory with component file + optional test: - -| Component | Purpose | -|-----------|---------| -| `Button` | Styled button variants | -| `Card` | Container card | -| `ClusterHealthManager` | Polls cluster health, stores in Zustand | -| `DataTable` | Generic sortable/paginated data table | -| `GCChart` | Recharts wrapper for cluster metrics | -| `GcAdminAntdProvider` | Ant Design theme provider | -| `GCSpin` | Loading spinner | -| `GCStatusIndicator` | Green/yellow/red health indicator | -| `Layout` | App shell with sidebar navigation | -| `NotificationHandler` | Toast/notification system | -| `SQLEditor` | Ace-based SQL editor (428 lines) | -| `SQLHistory` | SQL query history panel | -| `SQLResults` | Results grid with JSON tree view | -| `StatusBar` | Bottom status bar | -| `Tree` | Collapsible tree component | - -## Routes (`src/routes/`) - -| Route | Path (approximate) | Description | -|-------|-------------------|-------------| -| `Auth` | `/login` | Authentication screen | -| `Overview` | `/` | Cluster health dashboard | -| `Nodes` | `/nodes` | Node list and health | -| `Tables` | `/tables` | Table browser | -| `TablesShards` | `/shards` | Shard distribution view | -| `SQLConsole` | `/sql` | Interactive SQL console | -| `Users` | `/users` | User management | -| `Help` | `/help` | Documentation links | -| `Automation` | `/automation` | Jobs and table policies | -| `Automation/routes` | sub-routes | Job scheduler, policy editor | -| `Automation/views` | — | JobsTable, PolicyForm (729 lines), etc. | -| `Automation/hooks` | — | Automation-specific hooks | - -## State (`src/state/`) - -Zustand stores: -- `jwtManager` — JWT token storage, refresh, URL param extraction -- Session / cluster connection state -- Cluster health state (fed by `ClusterHealthManager`) - -## SWR (`src/swr/`) - -- `swr/jwt/` — JWT-aware fetch wrapper used as SWR fetcher -- Hooks that use SWR live in `src/hooks/swrHooks.ts` - -## Types (`src/types/`) - -- `query.ts` — SQL result shapes (`rows: any[][]`, column metadata) -- `policies/` — Automation policy type definitions -- Other shared domain types - -## Test Infrastructure (`test/`) - -``` -test/ -├── setup.ts Per-file setup (jest-dom, MSW server start, browser mocks) -├── global-setup.ts Global setup (TZ = Europe/Vienna) -├── index.ts Re-exports -├── msw/ -│ ├── server.ts MSW server instance -│ ├── handlers.ts Default handler list -│ ├── handlers/ Per-feature handler files -│ ├── handlerFactory.ts Factory for reusable handlers -│ └── getRequestSpy.ts Spy utility for MSW requests -└── testUtils/ - ├── renderWithTestWrapper.tsx Render wrapper with providers - ├── createLocationTestUtil.ts Mock location factory - ├── actWithFakeTimers.ts Fake timers + act() helper - ├── disableConsole.ts Suppress expected console errors - └── treeUtils.ts Tree query helpers -``` - -## Root Mocks (`__mocks__/`) - -Applied automatically by Jest `moduleNameMapper`: -- `react-router-dom` — navigation mocks -- `localStorageMock` — in-memory localStorage -- `ace-builds` — no-op for browser-only SQL editor -- `empty-module.ts` — stub for SVG/PNG imports - -## Entry Points - -| File | Role | -|------|------| -| `index.html` | Vite HTML template | -| `src/main.tsx` | React mount point, BrowserRouter wrapping | -| `src/App.tsx` | Route definitions, auth gate | -| `src/index.css` | Global styles | - -## File Naming Conventions - -- Components: `PascalCase.tsx` matching directory name (e.g., `Button/Button.tsx`) -- Hooks: `camelCase.ts` with `use` prefix (e.g., `useExecuteSql.ts`) -- Utilities: `camelCase.ts` (e.g., `nodes.ts`, `statusChecks.ts`) -- Tests: `ComponentName.test.tsx` or `hookName.test.ts` -- Types: `camelCase.ts` or `PascalCase.ts` for domain models - -## Where to Add New Code - -| What | Where | -|------|-------| -| New reusable UI component | `src/components/NewComponent/NewComponent.tsx` | -| New page/view | `src/routes/NewRoute/NewRoute.tsx` + register in `src/App.tsx` | -| New global state | `src/state/newStore.ts` (Zustand) | -| New SWR-based data hook | `src/hooks/swrHooks.ts` or new file in `src/hooks/` | -| New utility function | `src/utils/newUtil.ts` | -| New shared types | `src/types/newType.ts` | -| New MSW handlers (for tests) | `test/msw/handlers/newFeature.ts` + import in `handlers.ts` | diff --git a/.planning/codebase/TESTING.md b/.planning/codebase/TESTING.md deleted file mode 100644 index ad9e1a0b..00000000 --- a/.planning/codebase/TESTING.md +++ /dev/null @@ -1,127 +0,0 @@ -# Testing - -## Framework - -| Tool | Version | Purpose | -|------|---------|---------| -| Jest | 29.x | Test runner and assertion library | -| ts-jest | — | TypeScript transpilation for Jest | -| jest-fixed-jsdom | — | JSDOM environment with React 19 fixes | -| @testing-library/react | — | Component rendering and queries | -| @testing-library/jest-dom | — | DOM matchers (`toBeInTheDocument`, etc.) | -| @testing-library/user-event | — | User interaction simulation | -| MSW 2.x | — | API mocking via Service Worker | - -## Test File Organization - -Tests are co-located with the code they test: -- Component tests: `src/components/ComponentName/ComponentName.test.tsx` -- Route tests: `src/routes/RouteName/RouteName.test.tsx` -- Hook tests: `src/hooks/__test__/hookName.test.ts` -- DataTable tests: `src/components/DataTable/test/*.test.tsx` - -Test files match the regex: `(/__tests__/*.test.js|\.(test))\.(jsx|js|tsx|ts)$` - -## Configuration (jest.config.js) - -``` -testEnvironment: jest-fixed-jsdom -preset: ts-jest/presets/js-with-ts -rootDir: . -collectCoverage: false ← thresholds defined but not enforced by default -globalSetup: ./test/global-setup.ts -setupFilesAfterEnach: ./test/setup.ts -transformIgnorePatterns: node_modules except until-async and pretty-bytes -``` - -Coverage thresholds (active only when `collectCoverage: true`): -- branches: 80% -- functions: 80% -- lines: 80% -- statements: 80% - -## Global Setup (test/global-setup.ts) - -Sets `process.env.TZ = 'Europe/Vienna'` — all date tests assume this timezone. - -## Per-Test Setup (test/setup.ts) - -Runs before every test file: -- Imports `@testing-library/jest-dom` -- Polyfills `window.fetch` via `whatwg-fetch` -- Patches `antd` static APIs (`message`, `notification`) for React 19 compatibility using `unstableSetRender` -- Mocks `window.matchMedia`, `window.ResizeObserver`, `window.open`, `window.scrollTo` -- Mocks `window.localStorage` with a custom `localStorageMock` -- Starts the MSW server via `test/msw/server.ts` - -## Root-Level Mocks (`__mocks__/`) - -Module-level mocks applied automatically: -- `react-router-dom` — provides `useLocation` mock and navigation helpers -- `zustand` — store reset between tests -- `ace-builds` — no-op for the SQL editor (heavy browser dependency) -- `localStorageMock` — synchronous in-memory implementation - -## MSW Mock Strategy (`test/msw/`) - -| File | Purpose | -|------|---------| -| `server.ts` | Creates and exports the MSW `setupServer` instance | -| `handlers.ts` | Default handler list registered on every test | -| `handlers/` | Per-feature handler modules (imported by `handlers.ts`) | -| `handlerFactory.ts` | Factory functions to create reusable request handlers | -| `getRequestSpy.ts` | Utility to spy on specific MSW requests within a test | - -**Server lifecycle pattern:** -```ts -beforeAll(() => server.listen()) -afterEach(() => server.resetHandlers()) -afterAll(() => server.close()) -``` - -Per-test handler overrides: `server.use(...customHandlers)` within individual tests. - -## Test Utilities (`test/testUtils/`) - -| File | Purpose | -|------|---------| -| `renderWithTestWrapper.tsx` | Wraps component under test with providers (Router, theme, etc.) | -| `createLocationTestUtil.ts` | Creates mock location objects for route testing | -| `actWithFakeTimers.ts` | Combines `jest.useFakeTimers()` with `act()` for timed effects | -| `disableConsole.ts` | Suppresses expected console errors in tests | -| `treeUtils.ts` | Helpers for querying tree-structured components | - -## Common Patterns - -**Async render:** -```ts -render(, { wrapper: renderWithTestWrapper }) -await screen.findByText('Expected Text') -``` - -**User interactions:** -```ts -const user = userEvent.setup() -await user.click(screen.getByRole('button', { name: /submit/i })) -``` - -**API spying:** -```ts -const spy = getRequestSpy(server, 'POST', '/api/endpoint') -await user.click(submitButton) -expect(spy).toHaveBeenCalledWith(expect.objectContaining({ body: '...' })) -``` - -**Hook testing:** -```ts -const { result } = renderHook(() => useMyHook(), { wrapper: renderWithTestWrapper }) -await waitFor(() => expect(result.current.data).toBeDefined()) -``` - -## Running Tests - -```bash -yarn test # run all tests (watch mode) -yarn test --ci # single-pass, no watch (CI) -yarn test --coverage # enable coverage collection (enforces 80% thresholds) -```