diff --git a/web/client/package-lock.json b/web/client/package-lock.json index 2ea9ee5fa6..7c2f4339a3 100644 --- a/web/client/package-lock.json +++ b/web/client/package-lock.json @@ -21,19 +21,21 @@ "@tanstack/react-table": "^8.7.9", "@uiw/react-codemirror": "^4.19.7", "clsx": "^1.2.1", + "elkjs": "^0.8.2", "pluralize": "^8.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.6.2", "react-split": "^2.0.14", + "reactflow": "^11.5.6", "zustand": "^4.3.2" }, "devDependencies": { "@tailwindcss/typography": "^0.5.9", - "@tanstack/react-query-devtools": "^4.22.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^14.4.3", + "@types/dagre": "^0.7.48", "@types/pluralize": "^0.0.29", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.9", @@ -1163,6 +1165,85 @@ "@orval/core": "6.11.1" } }, + "node_modules/@reactflow/background": { + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.1.8.tgz", + "integrity": "sha512-NYZwiEeKVc1qJbDRrRX5RgHbMMzofhzOAqz3teWtUIGju5d+kEf/vcx/35bLM+CZuhucL+OvJpRgCjKmViiTIw==", + "dependencies": { + "@reactflow/core": "11.5.5", + "classcat": "^5.0.3", + "zustand": "^4.3.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/controls": { + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.1.8.tgz", + "integrity": "sha512-QCG4q52HS/zmuBAFzmTFh4wkR6thmNDxSKHQPxTwfVIuQtV/oGpfz7zMaoU0ZSN84qSWl5UdzmV4PAC50tOAkQ==", + "dependencies": { + "@reactflow/core": "11.5.5", + "classcat": "^5.0.3" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/core": { + "version": "11.5.5", + "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.5.5.tgz", + "integrity": "sha512-/FPnpvO9I4E6/mmfZInbsVusR214gzIZ2e2xgl9XJdBo90cWaqHgo0c5F2YPXX19R3mItzxveN+WlENFEOvdPg==", + "dependencies": { + "@types/d3": "^7.4.0", + "@types/d3-drag": "^3.0.1", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.3.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/minimap": { + "version": "11.3.8", + "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.3.8.tgz", + "integrity": "sha512-hOW3FVP/ObRK3oZxvKSSKIIR/DRe1OR4KU+3AIHxTK6K2kt/D48zQU37fOmEasfohjBjsqEopK7Ux8tapTT0EA==", + "dependencies": { + "@reactflow/core": "11.5.5", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.3.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-toolbar": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.1.8.tgz", + "integrity": "sha512-/Aj5dfarrBRvPeyDk+CZef7InP4LXlhMnlMPw6hnT/P9lVVChe02knzzkeKiVGmiWKXWL/gOCDXBFp9tMtIAsQ==", + "dependencies": { + "@reactflow/core": "11.5.5", + "classcat": "^5.0.3", + "zustand": "^4.3.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, "node_modules/@remix-run/router": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.2.1.tgz", @@ -2077,22 +2158,6 @@ "tailwindcss": ">=3.0.0 || insiders" } }, - "node_modules/@tanstack/match-sorter-utils": { - "version": "8.7.6", - "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.7.6.tgz", - "integrity": "sha512-2AMpRiA6QivHOUiBpQAVxjiHAA68Ei23ZUMNaRJrN6omWiSFLoYrxGcT6BXtuzp0Jw4h6HZCmGGIM/gbwebO2A==", - "dev": true, - "dependencies": { - "remove-accents": "0.4.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kentcdodds" - } - }, "node_modules/@tanstack/query-core": { "version": "4.22.0", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.22.0.tgz", @@ -2128,26 +2193,6 @@ } } }, - "node_modules/@tanstack/react-query-devtools": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.22.0.tgz", - "integrity": "sha512-YeYFBnfqvb+ZlA0IiJqiHNNSzepNhI1p2o9i8NlhQli9+Zrn230M47OBaBUs8qr3DD1dC2zGB1Dis50Ktz8gAA==", - "dev": true, - "dependencies": { - "@tanstack/match-sorter-utils": "^8.7.0", - "superjson": "^1.10.0", - "use-sync-external-store": "^1.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "@tanstack/react-query": "4.22.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/@tanstack/react-table": { "version": "8.7.9", "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.7.9.tgz", @@ -2294,6 +2339,234 @@ "@types/chai": "*" } }, + "node_modules/@types/d3": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz", + "integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.4.tgz", + "integrity": "sha512-nwvEkG9vYOc0Ic7G7kwgviY4AQlTfYGIZ0fqB7CQHXGyYM6nO7kJh5EguSNA3jfh4rq7Sb7eMVq8isuvg2/miQ==" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.2.tgz", + "integrity": "sha512-uGC7DBh0TZrU/LY43Fd8Qr+2ja1FKmH07q2FoZFHo1eYl8aj87GhfVoY1saJVJiq24rp1+wpI6BvQJMKgQm8oA==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.2.tgz", + "integrity": "sha512-2TEm8KzUG3N7z0TrSKPmbxByBx54M+S9lHoP2J55QuLU0VSQ9mE96EJSAOVNEqd1bbynMjeTS9VHmz8/bSw8rA==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.2.tgz", + "integrity": "sha512-abT/iLHD3sGZwqMTX1TYCMEulr+wBd0SzyOQnjYNLp7sngdOHYtNkMRI5v3w5thoN+BWtlHVDx2Osvq6fxhZWw==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.2.tgz", + "integrity": "sha512-k6/bGDoAGJZnZWaKzeB+9glgXCYGvh6YlluxzBREiVo8f/X2vpTEdgPy9DN7Z2i42PZOZ4JDhVdlTSTSkLDPlQ==", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.2.tgz", + "integrity": "sha512-rxN6sHUXEZYCKV05MEh4z4WpPSqIw+aP7n9ZN6WYAAvZoEAghEK1WeVZMZcHRBwyaKflU43PCUAJNjFxCzPDjg==" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.2.tgz", + "integrity": "sha512-qmODKEDvyKWVHcWWCOVcuVcOwikLVsyc4q4EBJMREsoQnR2Qoc2cZQUyFUPgO9q4S3qdSqJKBsuefv+h0Qy+tw==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-76pBHCMTvPLt44wFOieouXcGXWOF0AJCceUvaFkxSZEu4VDUdv93JfpMa6VGNFs01FHfuP4a5Ou68eRG1KBfTw==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.2.tgz", + "integrity": "sha512-gllwYWozWfbep16N9fByNBDTkJW/SyhH6SGRlXloR7WdtAaBui4plTP+gbUgiEot7vGw/ZZop1yDZlgXXSuzjA==", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.4.tgz", + "integrity": "sha512-q7xbVLrWcXvSBBEoadowIUJ7sRpS1yvgMWnzHJggFy5cUZBq2HZL5k/pBSm0GdYWS1vs5/EDwMjSKF55PDY4Aw==" + }, + "node_modules/@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==" + }, + "node_modules/@types/d3-geo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.3.tgz", + "integrity": "sha512-bK9uZJS3vuDCNeeXQ4z3u0E7OeJZXjUgzFdSOtNtMCJCLvDtWDwfpRVWlyt3y8EvRzI0ccOu9xlMVirawolSCw==", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-9hjRTVoZjRFR6xo8igAJyNXQyPX6Aq++Nhb5ebrUF414dv4jr2MitM2fWiOY475wa3Za7TOS2Gh9fmqEhLTt0A==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz", + "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz", + "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==" + }, + "node_modules/@types/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.3.tgz", + "integrity": "sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.4.tgz", + "integrity": "sha512-ZeykX7286BCyMg9sH5fIAORyCB6hcATPSRQpN47jwBA2bMbAT0s+EvtDP5r1FZYJ95R8QoEE1CKJX+n0/M5Vhg==" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.1.tgz", + "integrity": "sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz", + "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.3.tgz", + "integrity": "sha512-/S90Od8Id1wgQNvIA8iFv9jRhCiZcGhPd2qX0bKF/PS+y0W5CrXKgIiELd2CvG1mlQrWK/qlYh3VxicqG1ZvgA==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.2.tgz", + "integrity": "sha512-t09DDJVBI6AkM7N8kuPsnq/3d/ehtRKBN1xSiYjjMCgbiw6HM6Ged5VhvswmhprfKyGvzeTEL/4WBaK9llWvlA==", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/dagre": { + "version": "0.7.48", + "resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.48.tgz", + "integrity": "sha512-rF3yXSwHIrDxEkN6edCE4TXknb5YSEpiXfLaspw1I08grC49ZFuAVGOQCmZGIuLUGoFgcqGlUFBL/XrpgYpQgw==", + "dev": true + }, "node_modules/@types/es-aggregate-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/es-aggregate-error/-/es-aggregate-error-1.0.2.tgz", @@ -2309,6 +2582,11 @@ "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", "dev": true }, + "node_modules/@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -3368,6 +3646,11 @@ "node": ">=8" } }, + "node_modules/classcat": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.4.tgz", + "integrity": "sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g==" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -3469,21 +3752,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/copy-anything": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.3.tgz", - "integrity": "sha512-fpW2W/BqEzqPp29QS+MwwfisHCQZtiduTe/m8idFo0xbti9fIZ2WVhAsCv4ggFVH3AgCkVdpoOCtQC6gBrdhjw==", - "dev": true, - "dependencies": { - "is-what": "^4.1.8" - }, - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3564,6 +3832,102 @@ "deprecated": "Cuid and other k-sortable and non-cryptographic ids (Ulid, ObjectId, KSUID, all UUIDs) are all insecure. Use @paralleldrive/cuid2 instead.", "dev": true }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/data-uri-to-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", @@ -3930,6 +4294,11 @@ "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", "dev": true }, + "node_modules/elkjs": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz", + "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -6336,18 +6705,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-what": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.8.tgz", - "integrity": "sha512-yq8gMao5upkPoGEU9LsB2P+K3Kt8Q3fQFCGyNCWOAnJAMzEXVV9drYb0TXr42TTliLLhKIBvulgAXgtLLnwzGA==", - "dev": true, - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -8226,6 +8583,22 @@ "react": "*" } }, + "node_modules/reactflow": { + "version": "11.5.6", + "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.5.6.tgz", + "integrity": "sha512-my4LUKT7H7t2mK/qy4n+bfAMgjqhHOhYGYrvzSWB4yPhOhamPGjs0Ted9G8JWEw15Svn7pHf8DppTHUfk5zH2g==", + "dependencies": { + "@reactflow/background": "11.1.8", + "@reactflow/controls": "11.1.8", + "@reactflow/core": "11.5.5", + "@reactflow/minimap": "11.3.8", + "@reactflow/node-toolbar": "1.1.8" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -8321,12 +8694,6 @@ "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/remove-accents": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", - "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==", - "dev": true - }, "node_modules/require-all": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/require-all/-/require-all-3.0.0.tgz", @@ -8943,18 +9310,6 @@ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==" }, - "node_modules/superjson": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.2.tgz", - "integrity": "sha512-ugvUo9/WmvWOjstornQhsN/sR9mnGtWGYeTxFuqLb4AiT4QdUavjGFRALCPKWWnAiUJ4HTpytj5e0t5HoMRkXg==", - "dev": true, - "dependencies": { - "copy-anything": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/web/client/package.json b/web/client/package.json index 20e4bf94e0..32b4406dc9 100644 --- a/web/client/package.json +++ b/web/client/package.json @@ -1,10 +1,6 @@ { "name": "tobiko", "version": "0.0.0", - "engines": { - "node": "18", - "npm": "8" - }, "scripts": { "dev": "npm run generate:api && vite", "build": "npm run generate:api && tsc && vite build", @@ -29,19 +25,21 @@ "@tanstack/react-table": "^8.7.9", "@uiw/react-codemirror": "^4.19.7", "clsx": "^1.2.1", + "elkjs": "^0.8.2", "pluralize": "^8.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.6.2", "react-split": "^2.0.14", + "reactflow": "^11.5.6", "zustand": "^4.3.2" }, "devDependencies": { "@tailwindcss/typography": "^0.5.9", - "@tanstack/react-query-devtools": "^4.22.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^14.4.3", + "@types/dagre": "^0.7.48", "@types/pluralize": "^0.0.29", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.9", diff --git a/web/client/src/api/index.ts b/web/client/src/api/index.ts index 1e1a4d99e6..7a1c0e6aa5 100644 --- a/web/client/src/api/index.ts +++ b/web/client/src/api/index.ts @@ -12,6 +12,8 @@ import { getPlanApiPlanGet, getApiContextApiContextGet, ContextEnvironment, + DagApiDagGet200, + dagApiDagGet, } from './client' import type { File, Directory, Context } from './client' @@ -36,6 +38,13 @@ export function useApiFiles(): UseQueryResult { }) } +export function useApiDag(): UseQueryResult { + return useQuery({ + queryKey: ['/api/dag'], + queryFn: dagApiDagGet, + }) +} + export function useApiContext(): UseQueryResult { return useQuery({ queryKey: ['/api/context'], diff --git a/web/client/src/library/components/graph/Graph.tsx b/web/client/src/library/components/graph/Graph.tsx new file mode 100644 index 0000000000..9d849c7284 --- /dev/null +++ b/web/client/src/library/components/graph/Graph.tsx @@ -0,0 +1,132 @@ +import { MouseEvent, useEffect, useMemo, useState } from 'react' +import ReactFlow, { + Controls, + Background, + useNodesState, + useEdgesState, + Panel, + Handle, + Position, + BackgroundVariant, +} from 'reactflow' +import { Button } from '../button/Button' +import { useApiDag } from '../../../api' +import 'reactflow/dist/base.css' +import { getNodesAndEdges } from './help' +import { isFalse, isNil } from '../../../utils' + +export default function Graph({ closeGraph }: any): JSX.Element { + const { data } = useApiDag() + const [graph, setGraph] = useState<{ nodes: any[]; edges: any[] }>() + const [algorithm, setAlgorithm] = useState('layered') + const nodeTypes = useMemo(() => ({ model: ModelNode }), []) + const [nodes, setNodes, onNodesChange] = useNodesState([]) + const [edges, setEdges, onEdgesChange] = useEdgesState([]) + + useEffect(() => { + if (isNil(data)) return + + let active = true + + void load() + + return () => { + active = false + } + + async function load(): Promise { + console.log('start') + + setGraph(undefined) + + const graph = await getNodesAndEdges({ data, algorithm }) + + if (isFalse(active)) return + + setGraph(graph) + } + }, [data, algorithm]) + + useEffect(() => { + if (graph == null) return + + setNodes(graph.nodes) + setEdges(graph.edges) + }, [graph]) + + return ( +
+ + + + + + + + +
+ ) +} + +function ModelNode({ data, sourcePosition, targetPosition }: any): JSX.Element { + return ( +
+ {targetPosition === Position.Left && ( + + )} +
{data.label}
+ {sourcePosition === Position.Right && ( + + )} +
+ ) +} diff --git a/web/client/src/library/components/graph/help.ts b/web/client/src/library/components/graph/help.ts new file mode 100644 index 0000000000..a7b8870915 --- /dev/null +++ b/web/client/src/library/components/graph/help.ts @@ -0,0 +1,139 @@ +import ELK from 'elkjs/lib/elk-api' +import { isArrayNotEmpty } from '../../../utils' + +interface GraphNodeData { + label: string + [key: string]: any +} + +interface GraphNodePosition { + x: number + y: number +} + +interface GraphNode { + id: string + type: string + position: GraphNodePosition + data: GraphNodeData + connectable: boolean + selectable: boolean + deletable: boolean + focusable: boolean + sourcePosition?: 'left' | 'right' + targetPosition?: 'left' | 'right' +} + +interface GraphEdge { + id: string + source: string + target: string + style: { + strokeWidth: number + stroke: string + } +} + +interface GraphOptions { + data?: Record + nodeWidth?: number + nodeHeight?: number + algorithm?: string +} + +const elk = new ELK({ + workerUrl: '/node_modules/elkjs/lib/elk-worker.min.js', +}) + +export async function getNodesAndEdges({ + data, + nodeWidth = 172, + nodeHeight = 32, + algorithm = 'layered', +}: GraphOptions): Promise<{ nodes: GraphNode[]; edges: GraphEdge[] }> { + if (data == null) return await Promise.resolve({ nodes: [], edges: [] }) + + const targets = new Set(Object.values(data).flat()) + const models = Object.keys(data) + const nodesMap: Record = models.reduce( + (acc, label) => Object.assign(acc, { [label]: toGraphNode({ label }) }), + {}, + ) + const edges = models.map(source => getNodeEdges(data[source], source)).flat() + + const graph = { + id: 'root', + layoutOptions: { algorithm }, + children: Object.values(nodesMap).map(node => ({ + id: node.id, + width: nodeWidth, + height: nodeHeight, + })), + edges: edges.map(edge => ({ + id: edge.id, + sources: [edge.source], + targets: [edge.target], + })), + } + + const layout = await elk.layout(graph) + const nodes: GraphNode[] = [] + + layout.children?.forEach((node, idx: number) => { + const output = nodesMap[node.id] + + if (output == null) return + + if (isArrayNotEmpty(data[node.id])) { + output.sourcePosition = 'right' + } + + if (targets.has(node.id)) { + output.targetPosition = 'left' + } + + output.position = { + x: node.x ?? 0, + y: node.y ?? 0, + } + + nodes.push(output) + }) + + return { nodes, edges } +} + +function getNodeEdges(targets: string[] = [], source: string): GraphEdge[] { + return targets.map(target => toGraphEdge(source, target)) +} + +function toGraphNode( + data: GraphNodeData, + type: string = 'model', + position: GraphNodePosition = { x: 0, y: 0 }, +): GraphNode { + return { + id: data.label, + type, + position, + data, + connectable: false, + selectable: false, + deletable: false, + focusable: false, + } +} + +function toGraphEdge(source: string, target: string): GraphEdge { + const id = `${source}_${target}` + + return { + id, + source, + target, + style: { + strokeWidth: 2, + stroke: 'hsl(260, 100%, 80%)', + }, + } +} diff --git a/web/client/src/library/components/ide/IDE.tsx b/web/client/src/library/components/ide/IDE.tsx index 2ba3b3e0d9..019a0fbccc 100644 --- a/web/client/src/library/components/ide/IDE.tsx +++ b/web/client/src/library/components/ide/IDE.tsx @@ -2,25 +2,26 @@ import { Button } from '../button/Button' import { Divider } from '../divider/Divider' import { Editor } from '../editor/Editor' import { FolderTree } from '../folderTree/FolderTree' - -import { Fragment, useEffect, MouseEvent } from 'react' +import { Fragment, useEffect, MouseEvent, useState, lazy } from 'react' import clsx from 'clsx' import { PlayIcon } from '@heroicons/react/24/solid' import { EnumSize } from '../../../types/enum' import { Transition, Dialog, Popover } from '@headlessui/react' import { useApiFiles } from '../../../api' -import { Plan } from '../plan/Plan' +import fetchAPI from '../../../api/instance' import { EnumPlanState, EnumPlanAction, useStorePlan, } from '../../../context/plan' -import { Progress } from '../progress/Progress' -import Spinner from '../logo/Spinner' import { useChannel } from '../../../api/channels' -import fetchAPI from '../../../api/instance' import SplitPane from '../splitPane/SplitPane' +const Plan = lazy(async () => await import('../plan/Plan')) +const Graph = lazy(async () => await import('../graph/Graph')) +const Spinner = lazy(async () => await import('../logo/Spinner')) +const Progress = lazy(async () => await import('../progress/Progress')) + export function IDE(): JSX.Element { const planState = useStorePlan(s => s.state) const planAction = useStorePlan(s => s.action) @@ -32,6 +33,8 @@ export function IDE(): JSX.Element { const setEnvironment = useStorePlan(s => s.setEnvironment) const updateTasks = useStorePlan(s => s.updateTasks) + const [isGraphOpen, setIsGraphOpen] = useState(false) + const [subscribe, getChannel, unsubscribe] = useChannel( '/api/tasks', updateTasks, @@ -78,6 +81,14 @@ export function IDE(): JSX.Element { setEnvironment(undefined) } + function showGraph(): void { + setIsGraphOpen(true) + } + + function closeGraph(): void { + setIsGraphOpen(false) + } + return ( <>
@@ -88,34 +99,20 @@ export function IDE(): JSX.Element {
-
-
    - {['Editor', 'Graph', 'Audits', 'Tests'].map((name, i) => ( -
  • -
    - {i > 0 && ( - - )} - {name} -
    -
  • - ))} -
-
-
+
diff --git a/web/client/src/library/components/plan/Plan.tsx b/web/client/src/library/components/plan/Plan.tsx index e6ea7ba598..7081636105 100644 --- a/web/client/src/library/components/plan/Plan.tsx +++ b/web/client/src/library/components/plan/Plan.tsx @@ -20,7 +20,7 @@ import { useChannel } from '../../../api/channels' import { getActionName } from './help' import SplitPane from '../splitPane/SplitPane' -export function Plan({ +export default function Plan({ onClose, onCancel, }: { diff --git a/web/client/src/library/components/plan/PlanWizard.tsx b/web/client/src/library/components/plan/PlanWizard.tsx index 39eae1f630..71ac256895 100644 --- a/web/client/src/library/components/plan/PlanWizard.tsx +++ b/web/client/src/library/components/plan/PlanWizard.tsx @@ -17,7 +17,7 @@ import { } from '../../../utils' import { Divider } from '../divider/Divider' import Spinner from '../logo/Spinner' -import { Progress } from '../progress/Progress' +import Progress from '../progress/Progress' import { isModified } from './help' export function PlanWizard({ id }: { id: string }): JSX.Element { diff --git a/web/client/src/library/components/progress/Progress.tsx b/web/client/src/library/components/progress/Progress.tsx index 3c068db333..3aa44ec895 100644 --- a/web/client/src/library/components/progress/Progress.tsx +++ b/web/client/src/library/components/progress/Progress.tsx @@ -1,12 +1,12 @@ import clsx from 'clsx' -export interface PropsProgress extends React.ButtonHTMLAttributes { +export interface PropsProgress extends React.HTMLAttributes { progress: number delay?: number duration?: number } -export function Progress({ +export default function Progress({ progress = 0, delay = 150, duration = 500, diff --git a/web/client/src/main.tsx b/web/client/src/main.tsx index 9be0596f94..ccdb270167 100644 --- a/web/client/src/main.tsx +++ b/web/client/src/main.tsx @@ -2,7 +2,6 @@ import React from 'react' import ReactDOM from 'react-dom/client' import { RouterProvider } from 'react-router-dom' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { router } from './routes' import './index.css' @@ -21,7 +20,6 @@ ReactDOM.createRoot(document.getElementById('root')).render( - , ) diff --git a/web/client/tailwind.config.js b/web/client/tailwind.config.js index 99581603db..dacb8d7d37 100644 --- a/web/client/tailwind.config.js +++ b/web/client/tailwind.config.js @@ -1,5 +1,6 @@ /** @type {import('tailwindcss').Config} */ module.exports = { + important: true, content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], theme: { extend: { diff --git a/web/server/api/endpoints/commands.py b/web/server/api/endpoints/commands.py index 74988ad985..b8eb933a77 100644 --- a/web/server/api/endpoints/commands.py +++ b/web/server/api/endpoints/commands.py @@ -142,3 +142,15 @@ async def fetchdf( raise HTTPException( status_code=HTTP_422_UNPROCESSABLE_ENTITY, detail=traceback.format_exc() ) + + +@router.get("/dag") +async def dag( + context: Context = Depends(get_loaded_context), +) -> t.Dict[str, t.Set[str]]: + try: + return context.dag.graph + except Exception: + raise HTTPException( + status_code=HTTP_422_UNPROCESSABLE_ENTITY, detail=traceback.format_exc() + )