From a504e313b84a2da5d0699d562d95ba6918ee3c87 Mon Sep 17 00:00:00 2001 From: Max Mykal Date: Wed, 15 Feb 2023 18:13:22 -0800 Subject: [PATCH 01/10] add graph component --- web/client/package-lock.json | 534 +++++++++++++++++++++++++++++------ web/client/package.json | 8 +- 2 files changed, 453 insertions(+), 89 deletions(-) diff --git a/web/client/package-lock.json b/web/client/package-lock.json index 2ea9ee5fa6..a0f5897eff 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", + "dagre": "^0.8.5", "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,111 @@ "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/dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, "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", @@ -5618,6 +5991,14 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "dependencies": { + "lodash": "^4.17.15" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6336,18 +6717,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", @@ -6818,8 +7187,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.castarray": { "version": "4.4.0", @@ -8226,6 +8594,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 +8705,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 +9321,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..b1894306cf 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", + "dagre": "^0.8.5", "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", From ae0c095774190736489b72b8f692bb18ffa71623 Mon Sep 17 00:00:00 2001 From: Max Mykal Date: Wed, 15 Feb 2023 18:14:08 -0800 Subject: [PATCH 02/10] add graph component --- web/client/src/api/index.ts | 9 + .../src/library/components/ide/Graph.tsx | 180 ++++++++++++++++++ web/client/src/library/components/ide/IDE.tsx | 74 ++++--- web/client/src/main.tsx | 2 - 4 files changed, 240 insertions(+), 25 deletions(-) create mode 100644 web/client/src/library/components/ide/Graph.tsx 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/ide/Graph.tsx b/web/client/src/library/components/ide/Graph.tsx new file mode 100644 index 0000000000..7cc13c5516 --- /dev/null +++ b/web/client/src/library/components/ide/Graph.tsx @@ -0,0 +1,180 @@ +import { MouseEvent, useEffect, useMemo } 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 { isArrayNotEmpty } from '../../../utils' +import dagre from 'dagre' + +const dagreGraph = new dagre.graphlib.Graph() + +dagreGraph.setDefaultEdgeLabel(() => ({})) + +const nodeWidth = 172 +const nodeHeight = 32 + +export default function Graph({ closeGraph }: any): JSX.Element { + const { data } = useApiDag() + + const { nodes: initialNodes, edges: initialEdges } = useMemo(() => { + if (data == null) return [] + + const nodes = data.sorted.reduce((acc: any, label: string, idx: number) => { + const node = { + id: label, + type: 'model', + position: { x: 0, y: 0 }, + data: { label }, + connectable: false, + selectable: false, + deletable: false, + focusable: false, + } + + acc[label] = node + + return acc + }, {}) + + const edges = Object.keys(data.graph).reduce((acc: any, source: string) => { + if (isArrayNotEmpty(data.graph[source])) { + data.graph[source].forEach((target: string) => { + const id = `${source}_${target}` + const edge = { + id, + source, + target, + style: { + strokeWidth: 2, + stroke: 'hsl(260, 100%, 80%)', + }, + } + acc[id] = edge + }) + } + + return acc + }, {}) + + return getLayoutedElements( + Object.values(nodes), + Object.values(edges), + data.graph, + ) + }, [data]) + const nodeTypes = useMemo(() => ({ model: ModelNode }), []) + + const [nodes, setNodes, onNodesChange] = useNodesState([]) + const [edges, setEdges, onEdgesChange] = useEdgesState([]) + + useEffect(() => { + setNodes(initialNodes) + setEdges(initialEdges) + }, [initialNodes, initialEdges]) + + return ( +
+ + + + + + + +
+ ) +} + +function getLayoutedElements(nodes: any, edges: any, graph: any): any { + const targets = new Set(Object.values(graph).flat()) + + dagreGraph.setGraph({ rankdir: 'LR' }) + + nodes.forEach((node: any) => { + dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight }) + }) + + edges.forEach((edge: any) => { + dagreGraph.setEdge(edge.source, edge.target) + }) + + dagre.layout(dagreGraph) + + nodes.forEach((node: any) => { + const nodeWithPosition = dagreGraph.node(node.id) + + if (isArrayNotEmpty(graph[node.id])) { + node.sourcePosition = 'right' + } + + if (targets.has(node.id)) { + node.targetPosition = 'left' + } + + node.position = { + x: nodeWithPosition.x - nodeWidth / 2, + y: nodeWithPosition.y - nodeHeight / 2, + } + + return node + }) + + return { nodes, edges } +} + +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/ide/IDE.tsx b/web/client/src/library/components/ide/IDE.tsx index 2ba3b3e0d9..3400b16eef 100644 --- a/web/client/src/library/components/ide/IDE.tsx +++ b/web/client/src/library/components/ide/IDE.tsx @@ -2,8 +2,7 @@ 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 } from 'react' import clsx from 'clsx' import { PlayIcon } from '@heroicons/react/24/solid' import { EnumSize } from '../../../types/enum' @@ -20,6 +19,7 @@ import Spinner from '../logo/Spinner' import { useChannel } from '../../../api/channels' import fetchAPI from '../../../api/instance' import SplitPane from '../splitPane/SplitPane' +import Graph from './Graph' export function IDE(): JSX.Element { const planState = useStorePlan(s => s.state) @@ -32,6 +32,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 +80,16 @@ export function IDE(): JSX.Element { setEnvironment(undefined) } + function showGraph(): void { + setIsGraphOpen(true) + // setPlanAction(EnumPlanAction.Graph) + } + + function closeGraph(): void { + setIsGraphOpen(false) + // setPlanAction(EnumPlanAction.Graph) + } + return ( <>
@@ -96,9 +108,9 @@ export function IDE(): JSX.Element { className={clsx( 'mx-2 text-sm opacity-85 flex', name === 'Editor' && - 'font-bold opacity-100 border-b-2 border-secondary-500 text-secondary-500 cursor-default', + 'font-bold opacity-100 border-b-2 border-secondary-500 text-secondary-500 cursor-default', ['Audits', 'Graph', 'Tests'].includes(name) && - 'opacity-25 cursor-not-allowed', + 'opacity-25 cursor-not-allowed', )} > {i > 0 && ( @@ -116,6 +128,18 @@ export function IDE(): JSX.Element {
+ @@ -153,15 +176,15 @@ export function IDE(): JSX.Element { className={clsx( 'inline-block ml-1 px-2 py-[3px] rounded-[4px] text-xs font-bold', planState === EnumPlanState.Finished && - 'bg-success-500 text-white', + 'bg-success-500 text-white', planState === EnumPlanState.Failed && - 'bg-danger-500 text-white', + 'bg-danger-500 text-white', planState === EnumPlanState.Applying && - 'bg-secondary-500 text-white', + 'bg-secondary-500 text-white', planState !== EnumPlanState.Finished && - planState !== EnumPlanState.Failed && - planState !== EnumPlanState.Applying && - 'bg-gray-100 text-gray-500', + planState !== EnumPlanState.Failed && + planState !== EnumPlanState.Applying && + 'bg-gray-100 text-gray-500', )} > {plan == null ? 0 : 1} @@ -201,7 +224,7 @@ export function IDE(): JSX.Element { (t: any) => t.completed === t.total, ).length / Object.values(plan.tasks).length) * - 100, + 100, )} />
@@ -272,8 +295,9 @@ export function IDE(): JSX.Element { { @@ -309,10 +333,14 @@ export function IDE(): JSX.Element { leaveTo="opacity-0 scale-95" > - + {isGraphOpen && } + {planAction !== EnumPlanAction.None && + planAction !== EnumPlanAction.Closing && ( + + )}
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( - , ) From 60e0b12e0cb5def14eb196678e8e9114394ff224 Mon Sep 17 00:00:00 2001 From: Max Mykal Date: Wed, 15 Feb 2023 18:30:04 -0800 Subject: [PATCH 03/10] add dag endpoint --- web/server/api/endpoints/commands.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/web/server/api/endpoints/commands.py b/web/server/api/endpoints/commands.py index 74988ad985..c1dc30db29 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.Any]: + try: + return dict(graph=context.dag.graph, sorted=context.dag.sorted()) + except Exception: + raise HTTPException( + status_code=HTTP_422_UNPROCESSABLE_ENTITY, detail=traceback.format_exc() + ) From 80bc79e967dced55b7aed1964b3cc1801bee36b2 Mon Sep 17 00:00:00 2001 From: Max Mykal Date: Wed, 15 Feb 2023 18:36:29 -0800 Subject: [PATCH 04/10] clean up --- web/client/src/library/components/ide/IDE.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/client/src/library/components/ide/IDE.tsx b/web/client/src/library/components/ide/IDE.tsx index 3400b16eef..5acdef65f7 100644 --- a/web/client/src/library/components/ide/IDE.tsx +++ b/web/client/src/library/components/ide/IDE.tsx @@ -82,12 +82,10 @@ export function IDE(): JSX.Element { function showGraph(): void { setIsGraphOpen(true) - // setPlanAction(EnumPlanAction.Graph) } function closeGraph(): void { setIsGraphOpen(false) - // setPlanAction(EnumPlanAction.Graph) } return ( @@ -102,7 +100,7 @@ export function IDE(): JSX.Element {
    - {['Editor', 'Graph', 'Audits', 'Tests'].map((name, i) => ( + {['Editor', 'Audits', 'Tests'].map((name, i) => (
  • Date: Thu, 16 Feb 2023 11:01:04 -0800 Subject: [PATCH 05/10] replace graph layout package --- web/client/package-lock.json | 27 ++++++++------------------- web/client/package.json | 2 +- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/web/client/package-lock.json b/web/client/package-lock.json index a0f5897eff..7c2f4339a3 100644 --- a/web/client/package-lock.json +++ b/web/client/package-lock.json @@ -21,7 +21,7 @@ "@tanstack/react-table": "^8.7.9", "@uiw/react-codemirror": "^4.19.7", "clsx": "^1.2.1", - "dagre": "^0.8.5", + "elkjs": "^0.8.2", "pluralize": "^8.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -3928,15 +3928,6 @@ "node": ">=12" } }, - "node_modules/dagre": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", - "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", - "dependencies": { - "graphlib": "^2.1.8", - "lodash": "^4.17.15" - } - }, "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", @@ -4303,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", @@ -5991,14 +5987,6 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, - "node_modules/graphlib": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", - "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", - "dependencies": { - "lodash": "^4.17.15" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -7187,7 +7175,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "node_modules/lodash.castarray": { "version": "4.4.0", diff --git a/web/client/package.json b/web/client/package.json index b1894306cf..32b4406dc9 100644 --- a/web/client/package.json +++ b/web/client/package.json @@ -25,7 +25,7 @@ "@tanstack/react-table": "^8.7.9", "@uiw/react-codemirror": "^4.19.7", "clsx": "^1.2.1", - "dagre": "^0.8.5", + "elkjs": "^0.8.2", "pluralize": "^8.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", From fdd2455d5713a52b1b21890db211e22494738917 Mon Sep 17 00:00:00 2001 From: Max Mykal Date: Thu, 16 Feb 2023 11:01:58 -0800 Subject: [PATCH 06/10] only send dag json --- web/server/api/endpoints/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/server/api/endpoints/commands.py b/web/server/api/endpoints/commands.py index c1dc30db29..5ac79d8856 100644 --- a/web/server/api/endpoints/commands.py +++ b/web/server/api/endpoints/commands.py @@ -149,7 +149,7 @@ async def dag( context: Context = Depends(get_loaded_context), ) -> t.Dict[str, t.Any]: try: - return dict(graph=context.dag.graph, sorted=context.dag.sorted()) + return context.dag.graph except Exception: raise HTTPException( status_code=HTTP_422_UNPROCESSABLE_ENTITY, detail=traceback.format_exc() From a75c56e27eee12a3e5af6c1ad548ca9f21ae12da Mon Sep 17 00:00:00 2001 From: Max Mykal Date: Thu, 16 Feb 2023 11:08:53 -0800 Subject: [PATCH 07/10] apply dag layout package --- .../src/library/components/ide/Graph.tsx | 142 ++++++------------ web/client/src/library/components/ide/IDE.tsx | 13 +- web/client/src/library/components/ide/help.ts | 139 +++++++++++++++++ .../src/library/components/plan/Plan.tsx | 2 +- .../library/components/plan/PlanWizard.tsx | 2 +- .../library/components/progress/Progress.tsx | 4 +- web/client/tailwind.config.js | 1 + 7 files changed, 196 insertions(+), 107 deletions(-) create mode 100644 web/client/src/library/components/ide/help.ts diff --git a/web/client/src/library/components/ide/Graph.tsx b/web/client/src/library/components/ide/Graph.tsx index 7cc13c5516..38627a566b 100644 --- a/web/client/src/library/components/ide/Graph.tsx +++ b/web/client/src/library/components/ide/Graph.tsx @@ -1,5 +1,4 @@ -import { MouseEvent, useEffect, useMemo } from 'react' - +import { MouseEvent, useEffect, useMemo, useState } from 'react' import ReactFlow, { Controls, Background, @@ -13,74 +12,43 @@ import ReactFlow, { import { Button } from '../button/Button' import { useApiDag } from '../../../api' import 'reactflow/dist/base.css' -import { isArrayNotEmpty } from '../../../utils' -import dagre from 'dagre' - -const dagreGraph = new dagre.graphlib.Graph() - -dagreGraph.setDefaultEdgeLabel(() => ({})) - -const nodeWidth = 172 -const nodeHeight = 32 +import { getNodesAndEdges } from './help' +import { isFalse } 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([]) - const { nodes: initialNodes, edges: initialEdges } = useMemo(() => { - if (data == null) return [] - - const nodes = data.sorted.reduce((acc: any, label: string, idx: number) => { - const node = { - id: label, - type: 'model', - position: { x: 0, y: 0 }, - data: { label }, - connectable: false, - selectable: false, - deletable: false, - focusable: false, - } + useEffect(() => { + let active = true - acc[label] = node + void load() - return acc - }, {}) + return () => { + active = false + } - const edges = Object.keys(data.graph).reduce((acc: any, source: string) => { - if (isArrayNotEmpty(data.graph[source])) { - data.graph[source].forEach((target: string) => { - const id = `${source}_${target}` - const edge = { - id, - source, - target, - style: { - strokeWidth: 2, - stroke: 'hsl(260, 100%, 80%)', - }, - } - acc[id] = edge - }) - } + async function load(): Promise { + setGraph(undefined) - return acc - }, {}) + const graph = await getNodesAndEdges({ data, algorithm }) - return getLayoutedElements( - Object.values(nodes), - Object.values(edges), - data.graph, - ) - }, [data]) - const nodeTypes = useMemo(() => ({ model: ModelNode }), []) + if (isFalse(active)) return - const [nodes, setNodes, onNodesChange] = useNodesState([]) - const [edges, setEdges, onEdgesChange] = useEdgesState([]) + setGraph(graph) + } + }, [data, algorithm]) useEffect(() => { - setNodes(initialNodes) - setEdges(initialEdges) - }, [initialNodes, initialEdges]) + if (graph == null) return + + setNodes(graph.nodes) + setEdges(graph.edges) + }, [graph]) return (
    @@ -93,11 +61,28 @@ export default function Graph({ closeGraph }: any): JSX.Element { nodeTypes={nodeTypes} fitView > - + + @@ -175,15 +149,15 @@ export function IDE(): JSX.Element { className={clsx( 'inline-block ml-1 px-2 py-[3px] rounded-[4px] text-xs font-bold', planState === EnumPlanState.Finished && - 'bg-success-500 text-white', + 'bg-success-500 text-white', planState === EnumPlanState.Failed && - 'bg-danger-500 text-white', + 'bg-danger-500 text-white', planState === EnumPlanState.Applying && - 'bg-secondary-500 text-white', + 'bg-secondary-500 text-white', planState !== EnumPlanState.Finished && - planState !== EnumPlanState.Failed && - planState !== EnumPlanState.Applying && - 'bg-gray-100 text-gray-500', + planState !== EnumPlanState.Failed && + planState !== EnumPlanState.Applying && + 'bg-gray-100 text-gray-500', )} > {plan == null ? 0 : 1} @@ -223,7 +197,7 @@ export function IDE(): JSX.Element { (t: any) => t.completed === t.total, ).length / Object.values(plan.tasks).length) * - 100, + 100, )} />
    From 82efcf66ccac351d25560e09c61a8f7f0e6343fe Mon Sep 17 00:00:00 2001 From: Max Mykal Date: Thu, 16 Feb 2023 20:32:32 -0800 Subject: [PATCH 09/10] PR comments --- web/client/src/library/components/{ide => graph}/Graph.tsx | 6 +++++- web/client/src/library/components/{ide => graph}/help.ts | 4 ++-- web/client/src/library/components/ide/IDE.tsx | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) rename web/client/src/library/components/{ide => graph}/Graph.tsx (96%) rename web/client/src/library/components/{ide => graph}/help.ts (98%) diff --git a/web/client/src/library/components/ide/Graph.tsx b/web/client/src/library/components/graph/Graph.tsx similarity index 96% rename from web/client/src/library/components/ide/Graph.tsx rename to web/client/src/library/components/graph/Graph.tsx index 38627a566b..9d849c7284 100644 --- a/web/client/src/library/components/ide/Graph.tsx +++ b/web/client/src/library/components/graph/Graph.tsx @@ -13,7 +13,7 @@ import { Button } from '../button/Button' import { useApiDag } from '../../../api' import 'reactflow/dist/base.css' import { getNodesAndEdges } from './help' -import { isFalse } from '../../../utils' +import { isFalse, isNil } from '../../../utils' export default function Graph({ closeGraph }: any): JSX.Element { const { data } = useApiDag() @@ -24,6 +24,8 @@ export default function Graph({ closeGraph }: any): JSX.Element { const [edges, setEdges, onEdgesChange] = useEdgesState([]) useEffect(() => { + if (isNil(data)) return + let active = true void load() @@ -33,6 +35,8 @@ export default function Graph({ closeGraph }: any): JSX.Element { } async function load(): Promise { + console.log('start') + setGraph(undefined) const graph = await getNodesAndEdges({ data, algorithm }) diff --git a/web/client/src/library/components/ide/help.ts b/web/client/src/library/components/graph/help.ts similarity index 98% rename from web/client/src/library/components/ide/help.ts rename to web/client/src/library/components/graph/help.ts index 9ce34ceb3d..a7b8870915 100644 --- a/web/client/src/library/components/ide/help.ts +++ b/web/client/src/library/components/graph/help.ts @@ -13,7 +13,7 @@ interface GraphNodePosition { interface GraphNode { id: string - interface: string + type: string position: GraphNodePosition data: GraphNodeData connectable: boolean @@ -100,7 +100,7 @@ export async function getNodesAndEdges({ nodes.push(output) }) - return { edges, nodes } + return { nodes, edges } } function getNodeEdges(targets: string[] = [], source: string): GraphEdge[] { diff --git a/web/client/src/library/components/ide/IDE.tsx b/web/client/src/library/components/ide/IDE.tsx index 9645fdc36d..019a0fbccc 100644 --- a/web/client/src/library/components/ide/IDE.tsx +++ b/web/client/src/library/components/ide/IDE.tsx @@ -18,7 +18,7 @@ import { useChannel } from '../../../api/channels' import SplitPane from '../splitPane/SplitPane' const Plan = lazy(async () => await import('../plan/Plan')) -const Graph = lazy(async () => await import('./Graph')) +const Graph = lazy(async () => await import('../graph/Graph')) const Spinner = lazy(async () => await import('../logo/Spinner')) const Progress = lazy(async () => await import('../progress/Progress')) From 9b0c2fd789f7f1a111a74b5b1a88001e549c20b1 Mon Sep 17 00:00:00 2001 From: Max Mykal Date: Thu, 16 Feb 2023 20:34:07 -0800 Subject: [PATCH 10/10] change return type --- web/server/api/endpoints/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/server/api/endpoints/commands.py b/web/server/api/endpoints/commands.py index 5ac79d8856..b8eb933a77 100644 --- a/web/server/api/endpoints/commands.py +++ b/web/server/api/endpoints/commands.py @@ -147,7 +147,7 @@ async def fetchdf( @router.get("/dag") async def dag( context: Context = Depends(get_loaded_context), -) -> t.Dict[str, t.Any]: +) -> t.Dict[str, t.Set[str]]: try: return context.dag.graph except Exception: