diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aeacb362d0..878c6fecbb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -108,36 +108,36 @@ importers: '@radix-ui/react-select': specifier: ^2.2.5 version: 2.2.5(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@tailwindcss/postcss': - specifier: ^4.1.11 - version: 4.1.11 - '@tailwindcss/vite': - specifier: ^4.1.11 - version: 4.1.11(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0)) '@tanstack/react-query': specifier: ^5.83.0 version: 5.83.0(react@18.3.1) '@tanstack/react-router': - specifier: ^1.129.8 - version: 1.129.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.131.26 + version: 1.132.47(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/react-router-devtools': specifier: ^1.131.26 - version: 1.131.26(@tanstack/react-router@1.129.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@tanstack/router-core@1.129.8)(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(solid-js@1.9.7)(tiny-invariant@1.3.3) + version: 1.131.26(@tanstack/react-router@1.132.47(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@tanstack/router-core@1.132.47)(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(solid-js@1.9.7)(tiny-invariant@1.3.3) '@tanstack/react-virtual': specifier: ^3.13.12 version: 3.13.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/router-plugin': - specifier: ^1.129.8 - version: 1.129.8(@tanstack/react-router@1.129.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))(webpack@5.99.8(esbuild@0.25.8)) + specifier: ^1.131.26 + version: 1.132.47(@tanstack/react-router@1.132.47(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))(webpack@5.99.8(esbuild@0.25.8)) apache-arrow: specifier: ^19.0.1 version: 19.0.1 clsx: specifier: ^2.1.1 version: 2.1.1 + cronstrue: + specifier: 3.3.0 + version: 3.3.0 elkjs: specifier: ^0.8.2 version: 0.8.2 + lucide-react: + specifier: 0.542.0 + version: 0.542.0(react@18.3.1) orval: specifier: ^7.10.0 version: 7.10.0(openapi-types@12.1.3) @@ -150,12 +150,9 @@ importers: react-router: specifier: ^7.7.0 version: 7.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - reactflow: - specifier: ^11.11.4 - version: 11.11.4(@types/react@18.3.23)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tailwindcss: - specifier: ^4.1.11 - version: 4.1.11 + specifier: 3.4.17 + version: 3.4.17 vscode-uri: specifier: ^3.1.0 version: 3.1.0 @@ -174,7 +171,7 @@ importers: version: 9.0.18(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.6.2)) '@storybook/addon-vitest': specifier: ^9.0.18 - version: 9.0.18(@vitest/browser@3.2.3)(@vitest/runner@3.2.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.6.2))(vitest@3.2.4) + version: 9.0.18(@vitest/browser@3.2.4)(@vitest/runner@3.2.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.6.2))(vitest@3.2.4) '@storybook/react-vite': specifier: ^9.0.18 version: 9.0.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.45.1)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.6.2))(typescript@5.8.3)(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0)) @@ -194,11 +191,11 @@ importers: specifier: ^4.7.0 version: 4.7.0(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0)) '@vitest/browser': - specifier: 3.2.3 - version: 3.2.3(playwright@1.54.1)(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4) + specifier: 3.2.4 + version: 3.2.4(playwright@1.54.1)(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4) '@vitest/coverage-v8': - specifier: 3.2.3 - version: 3.2.3(@vitest/browser@3.2.3)(vitest@3.2.4) + specifier: 3.2.4 + version: 3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4) jsdom: specifier: ^26.1.0 version: 26.1.0 @@ -216,7 +213,7 @@ importers: version: 6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.1.0)(@vitest/browser@3.2.3)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.1.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) web-vitals: specifier: ^4.2.4 version: 4.2.4 @@ -1158,10 +1155,6 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@isaacs/fs-minipass@4.0.1': - resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} - engines: {node: '>=18.0.0'} - '@istanbuljs/schema@0.1.3': resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} @@ -1197,9 +1190,6 @@ packages: '@jridgewell/trace-mapping@0.3.29': resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} - '@jridgewell/trace-mapping@0.3.30': - resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} - '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -2244,106 +2234,13 @@ packages: peerDependencies: tailwindcss: '>=3.2.0' - '@tailwindcss/node@4.1.11': - resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==} - - '@tailwindcss/oxide-android-arm64@4.1.11': - resolution: {integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.1.11': - resolution: {integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.1.11': - resolution: {integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.1.11': - resolution: {integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': - resolution: {integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': - resolution: {integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-musl@4.1.11': - resolution: {integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-gnu@4.1.11': - resolution: {integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-musl@4.1.11': - resolution: {integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-wasm32-wasi@4.1.11': - resolution: {integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': - resolution: {integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.1.11': - resolution: {integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.1.11': - resolution: {integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==} - engines: {node: '>= 10'} - - '@tailwindcss/postcss@4.1.11': - resolution: {integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==} - '@tailwindcss/typography@0.5.16': resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==} peerDependencies: tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' - '@tailwindcss/vite@4.1.11': - resolution: {integrity: sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==} - peerDependencies: - vite: ^5.2.0 || ^6 || ^7 - - '@tanstack/history@1.129.7': - resolution: {integrity: sha512-I3YTkbe4RZQN54Qw4+IUhOjqG2DdbG2+EBWuQfew4MEk0eddLYAQVa50BZVww4/D2eh5I9vEk2Fd1Y0Wty7pug==} + '@tanstack/history@1.132.31': + resolution: {integrity: sha512-UCHM2uS0t/uSszqPEo+SBSSoQVeQ+LlOWAVBl5SA7+AedeAbKafIPjFn8huZCXNLAYb0WKV2+wETr7lDK9uz7g==} engines: {node: '>=12'} '@tanstack/query-core@5.83.0': @@ -2362,8 +2259,8 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-router@1.129.8': - resolution: {integrity: sha512-d5mfM+67h3wq7aHkLjRKXD1ddbzx1YuxaEbNvW45jjZXMgaikZSVfJrZBiUWXE/nhV1sTdbMQ48JcPagvGPmYQ==} + '@tanstack/react-router@1.132.47': + resolution: {integrity: sha512-mjCN1ueVLHBOK1gqLeacCrUPBZietMKTkr7xZlC32dCGn4e+83zMSlRTS2TrEl7+wEH+bqjnoyx8ALYTSiQ1Cg==} engines: {node: '>=12'} peerDependencies: react: '>=18.0.0 || >=19.0.0' @@ -2388,8 +2285,8 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/router-core@1.129.8': - resolution: {integrity: sha512-Izqf5q8TzJv0DJURynitJioPJT3dPAefrzHi2wlY/Q5+7nEG41SkjYMotTX2Q9i/Pjl91lW8gERCHpksszRdRw==} + '@tanstack/router-core@1.132.47': + resolution: {integrity: sha512-8YKFHmG6VUqXaWAJzEqjyW6w31dARS2USd2mtI5ZeZcihqMbskK28N4iotBXNn+sSKJnPRjc7A4jTnnEf8Mn8Q==} engines: {node: '>=12'} '@tanstack/router-devtools-core@1.131.26': @@ -2404,18 +2301,18 @@ packages: csstype: optional: true - '@tanstack/router-generator@1.129.8': - resolution: {integrity: sha512-i4QTtJeRq3jdRTuUXHKcmPNm6STS0jLJNTKEdeUCIzuVBiiP53oujMOd84e5ARP83k2IB2XcMHekTSzDlWD2fg==} + '@tanstack/router-generator@1.132.47': + resolution: {integrity: sha512-t3HHDWRQ4CDkm141I7pl1xQf6vehNG54m5h/2DqJGugYkP4C1x0jxqzgCbek2SuuGocS1P+NrWQeyNFmkUIgEA==} engines: {node: '>=12'} - '@tanstack/router-plugin@1.129.8': - resolution: {integrity: sha512-DdO6el2slgBO2mIqIGdGyHCzsbQLsTNxsgbNz9ZY9y324iP4G+p3iEYopHWgzLKM2DKinMs9F7AxjLow4V3klQ==} + '@tanstack/router-plugin@1.132.47': + resolution: {integrity: sha512-E/BDgWavv7t0Szp4daIzSoeNiyJaKnN1gofb/ViLbepgHFQUAxuBwqIf+o+hYDggvENcFrYnai1T03PsSyuZ3Q==} engines: {node: '>=12'} peerDependencies: '@rsbuild/core': '>=1.0.2' - '@tanstack/react-router': ^1.129.8 - vite: '>=5.0.0 || >=6.0.0' - vite-plugin-solid: ^2.11.2 + '@tanstack/react-router': ^1.132.47 + vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' + vite-plugin-solid: ^2.11.8 webpack: '>=5.92.0' peerDependenciesMeta: '@rsbuild/core': @@ -2429,8 +2326,8 @@ packages: webpack: optional: true - '@tanstack/router-utils@1.129.7': - resolution: {integrity: sha512-I2OyQF5U6sxHJApXKCUmCncTHKcpj4681FwyxpYg5QYOatHcn/zVMl7Rj4h36fu8/Lo2ZRLxUMd5kmXgp5Pb/A==} + '@tanstack/router-utils@1.132.31': + resolution: {integrity: sha512-uf8mQ3wV58K8TL5XXBoWhkYxmCV7LLWbbf6AvcxdhnCnBNmXBGlY+T8RdsRnXyI2Iyp2HfHaVZ+8H3CEQedXfw==} engines: {node: '>=12'} '@tanstack/store@0.7.2': @@ -2443,8 +2340,8 @@ packages: '@tanstack/virtual-core@3.13.12': resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==} - '@tanstack/virtual-file-routes@1.129.7': - resolution: {integrity: sha512-a+MxoAXG+Sq94Jp67OtveKOp2vQq75AWdVI8DRt6w19B0NEqpfm784FTLbVp/qdR1wmxCOmKAvElGSIiBOx5OQ==} + '@tanstack/virtual-file-routes@1.132.31': + resolution: {integrity: sha512-rxS8Cm2nIXroLqkm9pE/8X2lFNuvcTIIiFi5VH4PwzvKscAuaW3YRMN1WmaGDI2mVEn+GLaoY6Kc3jOczL5i4w==} engines: {node: '>=12'} '@testing-library/dom@10.4.1': @@ -2821,21 +2718,6 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - '@vitest/browser@3.2.3': - resolution: {integrity: sha512-5HpUb0ixGF8JWSAjb/P1x/VPuTYUkL4pL0+YO6DJiuvQgqJN3PREaUEcXwfXjU4nBc37EahfpRbAwdE9pHs9lQ==} - peerDependencies: - playwright: '*' - safaridriver: '*' - vitest: 3.2.3 - webdriverio: ^7.0.0 || ^8.0.0 || ^9.0.0 - peerDependenciesMeta: - playwright: - optional: true - safaridriver: - optional: true - webdriverio: - optional: true - '@vitest/browser@3.2.4': resolution: {integrity: sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==} peerDependencies: @@ -2851,11 +2733,11 @@ packages: webdriverio: optional: true - '@vitest/coverage-v8@3.2.3': - resolution: {integrity: sha512-D1QKzngg8PcDoCE8FHSZhREDuEy+zcKmMiMafYse41RZpBE5EDJyKOTdqK3RQfsV2S2nyKor5KCs8PyPRFqKPg==} + '@vitest/coverage-v8@3.2.4': + resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} peerDependencies: - '@vitest/browser': 3.2.3 - vitest: 3.2.3 + '@vitest/browser': 3.2.4 + vitest: 3.2.4 peerDependenciesMeta: '@vitest/browser': optional: true @@ -2863,17 +2745,6 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/mocker@3.2.3': - resolution: {integrity: sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - '@vitest/mocker@3.2.4': resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: @@ -2885,9 +2756,6 @@ packages: vite: optional: true - '@vitest/pretty-format@3.2.3': - resolution: {integrity: sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng==} - '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} @@ -2897,9 +2765,6 @@ packages: '@vitest/snapshot@3.2.4': resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/spy@3.2.3': - resolution: {integrity: sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw==} - '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} @@ -2908,9 +2773,6 @@ packages: peerDependencies: vitest: 3.2.4 - '@vitest/utils@3.2.3': - resolution: {integrity: sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==} - '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} @@ -3430,10 +3292,6 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - chownr@3.0.0: - resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} - engines: {node: '>=18'} - chromatic@12.2.0: resolution: {integrity: sha512-GswmBW9ZptAoTns1BMyjbm55Z7EsIJnUvYKdQqXIBZIKbGErmpA+p4c0BYA+nzw5B0M+rb3Iqp1IaH8TFwIQew==} hasBin: true @@ -3539,8 +3397,8 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-es@1.2.2: - resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + cookie-es@2.0.0: + resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} cookie@1.0.2: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} @@ -5096,18 +4954,9 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@3.0.2: - resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} - engines: {node: '>= 18'} - mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - mkdirp@3.0.1: - resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} - engines: {node: '>=10'} - hasBin: true - mlly@1.7.4: resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} @@ -5819,12 +5668,6 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - seroval-plugins@1.3.2: - resolution: {integrity: sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==} - engines: {node: '>=10'} - peerDependencies: - seroval: ^1.0 - seroval-plugins@1.3.3: resolution: {integrity: sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==} engines: {node: '>=10'} @@ -6159,9 +6002,6 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - tailwindcss@4.1.11: - resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==} - tapable@2.2.2: resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} engines: {node: '>=6'} @@ -6177,10 +6017,6 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - tar@7.4.3: - resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} - engines: {node: '>=18'} - terminal-link@4.0.0: resolution: {integrity: sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==} engines: {node: '>=18'} @@ -6815,10 +6651,6 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yallist@5.0.0: - resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} - engines: {node: '>=18'} - yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -6909,8 +6741,8 @@ snapshots: '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 '@apidevtools/json-schema-ref-parser@11.7.2': dependencies: @@ -7480,7 +7312,7 @@ snapshots: '@eslint/config-array@0.21.0': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -7494,7 +7326,7 @@ snapshots: '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -7609,10 +7441,6 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@isaacs/fs-minipass@4.0.1': - dependencies: - minipass: 7.1.2 - '@istanbuljs/schema@0.1.3': {} '@joshwooding/vite-plugin-react-docgen-typescript@0.6.1(typescript@5.8.3)(vite@6.3.5(@types/node@20.11.25)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))': @@ -7641,7 +7469,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.2': {} @@ -7659,11 +7487,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 - '@jridgewell/trace-mapping@0.3.30': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -7784,7 +7607,7 @@ snapshots: ajv: 8.17.1 chalk: 4.1.2 compare-versions: 6.1.1 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 esbuild: 0.25.8 esutils: 2.0.3 fs-extra: 11.3.0 @@ -8761,7 +8584,7 @@ snapshots: dependencies: storybook: 9.0.18(@testing-library/dom@10.4.1)(prettier@3.6.2) - '@storybook/addon-vitest@9.0.18(@vitest/browser@3.2.3)(@vitest/runner@3.2.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.6.2))(vitest@3.2.4)': + '@storybook/addon-vitest@9.0.18(@vitest/browser@3.2.4)(@vitest/runner@3.2.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.0.18(@testing-library/dom@10.4.1)(prettier@3.6.2))(vitest@3.2.4)': dependencies: '@storybook/global': 5.0.0 '@storybook/icons': 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -8769,9 +8592,9 @@ snapshots: storybook: 9.0.18(@testing-library/dom@10.4.1)(prettier@3.6.2) ts-dedent: 2.2.0 optionalDependencies: - '@vitest/browser': 3.2.3(playwright@1.54.1)(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4) + '@vitest/browser': 3.2.4(playwright@1.54.1)(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4) '@vitest/runner': 3.2.4 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.1.0)(@vitest/browser@3.2.3)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.1.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - react - react-dom @@ -8940,78 +8763,6 @@ snapshots: dependencies: tailwindcss: 3.4.17 - '@tailwindcss/node@4.1.11': - dependencies: - '@ampproject/remapping': 2.3.0 - enhanced-resolve: 5.18.2 - jiti: 2.4.2 - lightningcss: 1.30.1 - magic-string: 0.30.17 - source-map-js: 1.2.1 - tailwindcss: 4.1.11 - - '@tailwindcss/oxide-android-arm64@4.1.11': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.1.11': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.1.11': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.1.11': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.1.11': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.1.11': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.1.11': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.1.11': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.1.11': - optional: true - - '@tailwindcss/oxide@4.1.11': - dependencies: - detect-libc: 2.0.4 - tar: 7.4.3 - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.11 - '@tailwindcss/oxide-darwin-arm64': 4.1.11 - '@tailwindcss/oxide-darwin-x64': 4.1.11 - '@tailwindcss/oxide-freebsd-x64': 4.1.11 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.11 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.11 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.11 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.11 - '@tailwindcss/oxide-linux-x64-musl': 4.1.11 - '@tailwindcss/oxide-wasm32-wasi': 4.1.11 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.11 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.11 - - '@tailwindcss/postcss@4.1.11': - dependencies: - '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.1.11 - '@tailwindcss/oxide': 4.1.11 - postcss: 8.5.6 - tailwindcss: 4.1.11 - '@tailwindcss/typography@0.5.16(tailwindcss@3.4.17)': dependencies: lodash.castarray: 4.4.0 @@ -9020,14 +8771,7 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 3.4.17 - '@tailwindcss/vite@4.1.11(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))': - dependencies: - '@tailwindcss/node': 4.1.11 - '@tailwindcss/oxide': 4.1.11 - tailwindcss: 4.1.11 - vite: 6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) - - '@tanstack/history@1.129.7': {} + '@tanstack/history@1.132.31': {} '@tanstack/query-core@5.83.0': {} @@ -9036,10 +8780,10 @@ snapshots: '@tanstack/query-core': 5.83.0 react: 18.3.1 - '@tanstack/react-router-devtools@1.131.26(@tanstack/react-router@1.129.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@tanstack/router-core@1.129.8)(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(solid-js@1.9.7)(tiny-invariant@1.3.3)': + '@tanstack/react-router-devtools@1.131.26(@tanstack/react-router@1.132.47(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@tanstack/router-core@1.132.47)(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(solid-js@1.9.7)(tiny-invariant@1.3.3)': dependencies: - '@tanstack/react-router': 1.129.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@tanstack/router-devtools-core': 1.131.26(@tanstack/router-core@1.129.8)(csstype@3.1.3)(solid-js@1.9.7)(tiny-invariant@1.3.3) + '@tanstack/react-router': 1.132.47(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/router-devtools-core': 1.131.26(@tanstack/router-core@1.132.47)(csstype@3.1.3)(solid-js@1.9.7)(tiny-invariant@1.3.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: @@ -9048,11 +8792,11 @@ snapshots: - solid-js - tiny-invariant - '@tanstack/react-router@1.129.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@tanstack/react-router@1.132.47(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@tanstack/history': 1.129.7 + '@tanstack/history': 1.132.31 '@tanstack/react-store': 0.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@tanstack/router-core': 1.129.8 + '@tanstack/router-core': 1.132.47 isbot: 5.1.28 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -9078,19 +8822,19 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@tanstack/router-core@1.129.8': + '@tanstack/router-core@1.132.47': dependencies: - '@tanstack/history': 1.129.7 + '@tanstack/history': 1.132.31 '@tanstack/store': 0.7.2 - cookie-es: 1.2.2 + cookie-es: 2.0.0 seroval: 1.3.2 - seroval-plugins: 1.3.2(seroval@1.3.2) + seroval-plugins: 1.3.3(seroval@1.3.2) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/router-devtools-core@1.131.26(@tanstack/router-core@1.129.8)(csstype@3.1.3)(solid-js@1.9.7)(tiny-invariant@1.3.3)': + '@tanstack/router-devtools-core@1.131.26(@tanstack/router-core@1.132.47)(csstype@3.1.3)(solid-js@1.9.7)(tiny-invariant@1.3.3)': dependencies: - '@tanstack/router-core': 1.129.8 + '@tanstack/router-core': 1.132.47 clsx: 2.1.1 goober: 2.1.16(csstype@3.1.3) solid-js: 1.9.7 @@ -9098,11 +8842,11 @@ snapshots: optionalDependencies: csstype: 3.1.3 - '@tanstack/router-generator@1.129.8': + '@tanstack/router-generator@1.132.47': dependencies: - '@tanstack/router-core': 1.129.8 - '@tanstack/router-utils': 1.129.7 - '@tanstack/virtual-file-routes': 1.129.7 + '@tanstack/router-core': 1.132.47 + '@tanstack/router-utils': 1.132.31 + '@tanstack/virtual-file-routes': 1.132.31 prettier: 3.6.2 recast: 0.23.11 source-map: 0.7.4 @@ -9111,7 +8855,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.129.8(@tanstack/react-router@1.129.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))(webpack@5.99.8(esbuild@0.25.8))': + '@tanstack/router-plugin@1.132.47(@tanstack/react-router@1.132.47(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))(webpack@5.99.8(esbuild@0.25.8))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) @@ -9119,22 +8863,22 @@ snapshots: '@babel/template': 7.27.2 '@babel/traverse': 7.28.0 '@babel/types': 7.28.1 - '@tanstack/router-core': 1.129.8 - '@tanstack/router-generator': 1.129.8 - '@tanstack/router-utils': 1.129.7 - '@tanstack/virtual-file-routes': 1.129.7 + '@tanstack/router-core': 1.132.47 + '@tanstack/router-generator': 1.132.47 + '@tanstack/router-utils': 1.132.31 + '@tanstack/virtual-file-routes': 1.132.31 babel-dead-code-elimination: 1.0.10 chokidar: 3.6.0 unplugin: 2.3.5 zod: 3.25.76 optionalDependencies: - '@tanstack/react-router': 1.129.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-router': 1.132.47(react-dom@18.3.1(react@18.3.1))(react@18.3.1) vite: 6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) webpack: 5.99.8(esbuild@0.25.8) transitivePeerDependencies: - supports-color - '@tanstack/router-utils@1.129.7': + '@tanstack/router-utils@1.132.31': dependencies: '@babel/core': 7.28.0 '@babel/generator': 7.28.0 @@ -9142,6 +8886,8 @@ snapshots: '@babel/preset-typescript': 7.27.1(@babel/core@7.28.0) ansis: 4.1.0 diff: 8.0.2 + fast-glob: 3.3.3 + pathe: 2.0.3 transitivePeerDependencies: - supports-color @@ -9151,7 +8897,7 @@ snapshots: '@tanstack/virtual-core@3.13.12': {} - '@tanstack/virtual-file-routes@1.129.7': {} + '@tanstack/virtual-file-routes@1.132.31': {} '@testing-library/dom@10.4.1': dependencies: @@ -9437,7 +9183,6 @@ snapshots: '@types/node@24.1.0': dependencies: undici-types: 7.8.0 - optional: true '@types/normalize-package-data@2.4.4': {} @@ -9491,7 +9236,7 @@ snapshots: '@typescript-eslint/types': 8.38.0 '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.38.0 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) eslint: 9.31.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: @@ -9501,7 +9246,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.8.3) '@typescript-eslint/types': 8.38.0 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -9520,7 +9265,7 @@ snapshots: '@typescript-eslint/types': 8.38.0 '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) '@typescript-eslint/utils': 8.38.0(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) eslint: 9.31.0(jiti@2.4.2) ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 @@ -9535,7 +9280,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.8.3) '@typescript-eslint/types': 8.38.0 '@typescript-eslint/visitor-keys': 8.38.0 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -9635,25 +9380,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/browser@3.2.3(playwright@1.54.1)(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4)': - dependencies: - '@testing-library/dom': 10.4.1 - '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/mocker': 3.2.3(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0)) - '@vitest/utils': 3.2.3 - magic-string: 0.30.17 - sirv: 3.0.1 - tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.1.0)(@vitest/browser@3.2.3)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) - ws: 8.18.3 - optionalDependencies: - playwright: 1.54.1 - transitivePeerDependencies: - - bufferutil - - msw - - utf-8-validate - - vite - '@vitest/browser@3.2.4(playwright@1.54.1)(vite@6.3.5(@types/node@20.11.25)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4)': dependencies: '@testing-library/dom': 10.4.1 @@ -9691,14 +9417,13 @@ snapshots: - msw - utf-8-validate - vite - optional: true - '@vitest/coverage-v8@3.2.3(@vitest/browser@3.2.3)(vitest@3.2.4)': + '@vitest/coverage-v8@3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4)': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 ast-v8-to-istanbul: 0.3.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -9708,9 +9433,9 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.1.0)(@vitest/browser@3.2.3)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.1.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) optionalDependencies: - '@vitest/browser': 3.2.3(playwright@1.54.1)(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4) + '@vitest/browser': 3.2.4(playwright@1.54.1)(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4) transitivePeerDependencies: - supports-color @@ -9722,14 +9447,6 @@ snapshots: chai: 5.2.1 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.3(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))': - dependencies: - '@vitest/spy': 3.2.3 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - vite: 6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) - '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@20.11.25)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.2.4 @@ -9746,10 +9463,6 @@ snapshots: optionalDependencies: vite: 6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) - '@vitest/pretty-format@3.2.3': - dependencies: - tinyrainbow: 2.0.0 - '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 @@ -9766,10 +9479,6 @@ snapshots: magic-string: 0.30.17 pathe: 2.0.3 - '@vitest/spy@3.2.3': - dependencies: - tinyspy: 4.0.3 - '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.3 @@ -9783,13 +9492,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.11.25)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) - - '@vitest/utils@3.2.3': - dependencies: - '@vitest/pretty-format': 3.2.3 - loupe: 3.1.4 - tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.1.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) '@vitest/utils@3.2.4': dependencies: @@ -10197,7 +9900,7 @@ snapshots: ast-v8-to-istanbul@0.3.3: dependencies: - '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 js-tokens: 9.0.1 @@ -10433,8 +10136,6 @@ snapshots: chownr@1.1.4: optional: true - chownr@3.0.0: {} - chromatic@12.2.0: {} chrome-trace-event@1.0.4: {} @@ -10521,7 +10222,7 @@ snapshots: convert-source-map@2.0.0: {} - cookie-es@1.2.2: {} + cookie-es@2.0.0: {} cookie@1.0.2: {} @@ -10694,7 +10395,8 @@ snapshots: dequal@2.0.3: {} - detect-libc@2.0.4: {} + detect-libc@2.0.4: + optional: true detect-node-es@1.1.0: {} @@ -10992,7 +10694,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -11640,8 +11342,8 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: - '@jridgewell/trace-mapping': 0.3.29 - debug: 4.4.1(supports-color@8.1.1) + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.1 istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -11669,13 +11371,14 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.11.25 + '@types/node': 24.1.0 merge-stream: 2.0.0 supports-color: 8.1.1 jiti@1.21.7: {} - jiti@2.4.2: {} + jiti@2.4.2: + optional: true jju@1.4.0: {} @@ -11857,6 +11560,7 @@ snapshots: lightningcss-linux-x64-musl: 1.30.1 lightningcss-win32-arm64-msvc: 1.30.1 lightningcss-win32-x64-msvc: 1.30.1 + optional: true lilconfig@3.1.3: {} @@ -12259,15 +11963,9 @@ snapshots: minipass@7.1.2: {} - minizlib@3.0.2: - dependencies: - minipass: 7.1.2 - mkdirp-classic@0.5.3: optional: true - mkdirp@3.0.1: {} - mlly@1.7.4: dependencies: acorn: 8.15.0 @@ -13139,10 +12837,6 @@ snapshots: dependencies: randombytes: 2.1.0 - seroval-plugins@1.3.2(seroval@1.3.2): - dependencies: - seroval: 1.3.2 - seroval-plugins@1.3.3(seroval@1.3.2): dependencies: seroval: 1.3.2 @@ -13585,8 +13279,6 @@ snapshots: transitivePeerDependencies: - ts-node - tailwindcss@4.1.11: {} - tapable@2.2.2: {} tapable@2.2.3: {} @@ -13608,15 +13300,6 @@ snapshots: readable-stream: 3.6.2 optional: true - tar@7.4.3: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.2 - minizlib: 3.0.2 - mkdirp: 3.0.1 - yallist: 5.0.0 - terminal-link@4.0.0: dependencies: ansi-escapes: 7.0.0 @@ -13861,8 +13544,7 @@ snapshots: undici-types@6.21.0: {} - undici-types@7.8.0: - optional: true + undici-types@7.8.0: {} undici@7.12.0: {} @@ -13985,7 +13667,7 @@ snapshots: vite-node@3.2.4(@types/node@20.11.25)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: cac: 6.7.14 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) es-module-lexer: 1.7.0 pathe: 2.0.3 vite: 6.3.5(@types/node@20.11.25)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) @@ -14006,7 +13688,7 @@ snapshots: vite-node@3.2.4(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: cac: 6.7.14 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 vite: 6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) @@ -14035,7 +13717,7 @@ snapshots: '@volar/typescript': 2.4.23 '@vue/language-core': 2.2.0(typescript@5.8.3) compare-versions: 6.1.1 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) kolorist: 1.8.0 local-pkg: 1.1.1 magic-string: 0.30.17 @@ -14101,7 +13783,7 @@ snapshots: '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 chai: 5.2.1 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) expect-type: 1.2.2 magic-string: 0.30.17 pathe: 2.0.3 @@ -14135,51 +13817,6 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.1.0)(@vitest/browser@3.2.3)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0): - dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.2.1 - debug: 4.4.1(supports-color@8.1.1) - expect-type: 1.2.2 - magic-string: 0.30.17 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.14 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) - vite-node: 3.2.4(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 24.1.0 - '@vitest/browser': 3.2.3(playwright@1.54.1)(vite@6.3.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4) - '@vitest/ui': 3.2.4(vitest@3.2.4) - jsdom: 26.1.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.1.0)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 @@ -14191,7 +13828,7 @@ snapshots: '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 chai: 5.2.1 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1 expect-type: 1.2.2 magic-string: 0.30.17 pathe: 2.0.3 @@ -14405,8 +14042,6 @@ snapshots: yallist@4.0.0: {} - yallist@5.0.0: {} - yaml@1.10.2: {} yaml@2.8.0: {} diff --git a/vscode/extension/tests/lineage_settings.spec.ts b/vscode/extension/tests/lineage_settings.spec.ts index c3237f13dc..1af9fed8f1 100644 --- a/vscode/extension/tests/lineage_settings.spec.ts +++ b/vscode/extension/tests/lineage_settings.spec.ts @@ -47,9 +47,22 @@ test('Settings button is visible in the lineage view', async ({ if (activeFrame) { try { await activeFrame - .getByRole('button', { - name: 'Settings', - }) + .getByRole('button', { name: 'Zoom In' }) + .waitFor({ timeout: 1000 }) + await activeFrame + .getByRole('button', { name: 'Zoom Out' }) + .waitFor({ timeout: 1000 }) + await activeFrame + .getByRole('button', { name: 'Only selected nodes' }) + .waitFor({ timeout: 1000 }) + await activeFrame + .getByRole('button', { name: 'Zoom to selected node' }) + .waitFor({ timeout: 1000 }) + await activeFrame + .getByRole('button', { name: 'Show columns' }) + .waitFor({ timeout: 1000 }) + await activeFrame + .getByRole('button', { name: 'Reset' }) .waitFor({ timeout: 1000 }) settingsCount++ } catch { diff --git a/vscode/react/package.json b/vscode/react/package.json index e12dd12179..d7e302444e 100644 --- a/vscode/react/package.json +++ b/vscode/react/package.json @@ -18,22 +18,21 @@ "@headlessui/react": "^2.2.5", "@heroicons/react": "^2.2.0", "@radix-ui/react-select": "^2.2.5", - "@tailwindcss/postcss": "^4.1.11", - "@tailwindcss/vite": "^4.1.11", "@tanstack/react-query": "^5.83.0", - "@tanstack/react-router": "^1.129.8", + "@tanstack/react-router": "^1.131.26", "@tanstack/react-router-devtools": "^1.131.26", "@tanstack/react-virtual": "^3.13.12", - "@tanstack/router-plugin": "^1.129.8", + "@tanstack/router-plugin": "^1.131.26", "apache-arrow": "^19.0.1", "clsx": "^2.1.1", + "cronstrue": "3.3.0", "elkjs": "^0.8.2", + "lucide-react": "0.542.0", "orval": "^7.10.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router": "^7.7.0", - "reactflow": "^11.11.4", - "tailwindcss": "^4.1.11", + "tailwindcss": "3.4.17", "vscode-uri": "^3.1.0" }, "devDependencies": { @@ -48,8 +47,8 @@ "@types/react": "^18.3.23", "@types/react-dom": "^18.3.7", "@vitejs/plugin-react": "^4.7.0", - "@vitest/browser": "3.2.3", - "@vitest/coverage-v8": "3.2.3", + "@vitest/browser": "3.2.4", + "@vitest/coverage-v8": "3.2.4", "jsdom": "^26.1.0", "playwright": "^1.54.1", "storybook": "^9.0.18", diff --git a/vscode/react/postcss.config.js b/vscode/react/postcss.config.js new file mode 100644 index 0000000000..2e7af2b7f1 --- /dev/null +++ b/vscode/react/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/vscode/react/src/App.css b/vscode/react/src/App.css index c49ccd115b..24119bf9ef 100644 --- a/vscode/react/src/App.css +++ b/vscode/react/src/App.css @@ -1,5 +1,156 @@ -@import 'tailwindcss'; -@config "../tailwind.config.cjs"; +:root { + /* + Keep commented for reference. + + --color-metadata-label: rgba(100, 100, 100, 1); + --color-metadata-value: rgba(10, 10, 10, 1); + + --color-tooltip-background: rgba(150, 150, 150, 1); + --color-tooltip-foreground: rgba(255, 255, 255, 1); + + --color-model-name-grayscale-link-underline: rgb(24, 13, 13); + --color-model-name-grayscale-link-underline-hover: rgba(125, 125, 125, 1); + --color-model-name-link-underline: rgba(0, 0, 0, 1); + --color-model-name-link-underline-hover: rgba(0, 0, 0, 1); + --color-model-name-catalog: rgba(0, 0, 0, 1); + --color-model-name-schema: rgba(0, 0, 0, 1); + --color-model-name-model: rgba(0, 0, 0, 1); + --color-model-name-grayscale-catalog: rgba(100, 100, 100, 1); + --color-model-name-grayscale-schema: rgba(50, 50, 50, 1); + */ + + --color-model-name-grayscale-model: var(--vscode-foreground); + --color-model-name-copy-icon: var(--vscode-foreground); + --color-model-name-copy-icon-hover: var(--vscode-foreground); + --color-model-name-copy-icon-background: transparent; + --color-model-name-copy-icon-background-hover: rgba(255, 255, 255, 0.1); + + --color-filterable-list-counter-background: var(--vscode-input-background); + --color-filterable-list-counter-foreground: var(--vscode-input-foreground); + + --color-filterable-list-input-background: var(--vscode-input-background); + --color-filterable-list-input-foreground: var(--vscode-input-foreground); + --color-filterable-list-input-placeholder: var( + --vscode-input-placeholderForeground + ); + --color-filterable-list-input-border: var(--vscode-input-border); + + --color-lineage-background: transparent; + --color-lineage-border: transparent; + --color-lineage-foreground: var(--vscode-foreground); + --color-lineage-divider: var(--vscode-text-separatorForeground); + + --color-lineage-grid-dot: rgba(0, 0, 0, 0.1); + + --color-lineage-control-background: var(--vscode-editor-background); + --color-lineage-control-border: var(--vscode-foreground); + --color-lineage-control-background-hover: var(--vscode-editor-background); + --color-lineage-control-icon-background: var(--vscode-foreground); + --color-lineage-control-icon-foreground: var(--vscode-editor-background); + --color-lineage-control-button-tooltip-background: var( + --vscode-editor-background + ); + --color-lineage-control-button-tooltip-foreground: var(--vscode-foreground); + --color-lineage-control-button-tooltip-border: var(--vscode-foreground); + + --xy-controls-button-background-color: var(--vscode-editor-background); + --xy-controls-button-background-color-hover-default: var( + --vscode-editor-background + ); + --xy-controls-button-background-color-hover-props: var( + --vscode-editor-background + ); + --xy-controls-button-color-default: var(--vscode-foreground); + --xy-attribution-background-color: var(--vscode-editor-background); + --xy-controls-button-border-color-default: var(--vscode-editor-background); + --xy-controls-button-border-color-props: var(--vscode-editor-background); + --xy-controls-button-border-color: var(--vscode-editor-background); + + --color-lineage-node-background: var(--vscode-editor-background); + --color-lineage-node-foreground: var(--vscode-foreground); + --color-lineage-node-border: var(--vscode-text-separatorForeground); + --color-lineage-node-border-hover: var(--vscode-text-separatorForeground); + + --color-lineage-node-current-background: var( + --vscode-editorLightBulb-foreground + ); + --color-lineage-node-current-foreground: var(--vscode-editor-background); + + --color-lineage-node-selected-border: var( + --vscode-editorLightBulb-foreground + ); + + --color-lineage-node-badge-background: var(--vscode-badge-background); + --color-lineage-node-badge-foreground: var(--vscode-badge-foreground); + + --color-lineage-node-appendix-background: transparent; + + --color-lineage-node-type-background-sql: var(--vscode-minimap-infoHighlight); + --color-lineage-node-type-foreground-sql: var(--vscode-minimap-infoHighlight); + --color-lineage-node-type-border-sql: var(--vscode-minimap-infoHighlight); + + --color-lineage-node-type-background-python: var( + --vscode-editorWarning-foreground + ); + --color-lineage-node-type-foreground-python: var( + --vscode-editorWarning-foreground + ); + --color-lineage-node-type-border-python: var( + --vscode-editorWarning-foreground + ); + + --color-lineage-node-type-background-source: var( + --vscode-minimap-errorHighlight + ); + --color-lineage-node-type-foreground-source: var( + --vscode-minimap-errorHighlight + ); + --color-lineage-node-type-border-source: var(--vscode-minimap-errorHighlight); + + --color-lineage-node-type-handle-icon-background: var( + --vscode-editor-background + ); + --color-lineage-node-type-handle-icon-foreground: var(--vscode-foreground); + + --color-lineage-edge: rgba(0, 0, 0, 0.1); + + --color-lineage-node-port-background: rgba(0, 0, 0, 0.1); + --color-lineage-node-port-handle-source: var(--vscode-progressBar-background); + --color-lineage-node-port-handle-target: var(--vscode-progressBar-background); + --color-lineage-node-port-edge-source: var(--vscode-progressBar-background); + --color-lineage-node-port-edge-target: var(--vscode-progressBar-background); + + --color-lineage-model-column-error-background: var( + --vscode-editor-background + ); + --color-lineage-model-column-source-background: var( + --vscode-editor-background + ); + --color-lineage-model-column-expression-background: var( + --vscode-editor-background + ); + --color-lineage-model-column-error-icon: var( + --vscode-activityErrorBadge-background + ); + --color-lineage-model-column-active-background: var( + --vscode-selection-background + ); + --color-lineage-model-column-active-foreground: var( + --vscode-editorLightBulb-foreground + ); + --color-lineage-model-column-icon: var(--vscode-text-separatorForeground); + --color-lineage-model-column-icon-active: var(--vscode-foreground); + + --color-lineage-model-column-badge-background: var(--vscode-input-background); + --color-lineage-model-column-badge-foreground: var(--vscode-input-foreground); + + --color-lineage-model-column-metadata-label: var( + --vscode-input-placeholderForeground + ); + --color-lineage-model-column-metadata-value: var(--vscode-input-foreground); + + --color-lineage-model-column-information-info: var(--vscode-input-foreground); +} @tailwind base; @tailwind components; @@ -65,10 +216,3 @@ @apply outline-none ring-offset-2 ring-4; } } - -:root { - --color-graph-edge-secondary: var(--vscode-disabledForeground); - --color-graph-edge-main: var(--vscode-disabledForeground); - --color-graph-edge-selected: var(--vscode-textLink-foreground); - --color-graph-edge-direct: var(--vscode-disabledForeground); -} diff --git a/vscode/react/src/components/graph/CogIcon.tsx b/vscode/react/src/components/graph/CogIcon.tsx deleted file mode 100644 index 75d3e952bd..0000000000 --- a/vscode/react/src/components/graph/CogIcon.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as React from 'react' - -/** - * CogIcon as taken from https://heroicons.com/. Slightly modified to remove fill color. - * - * @param props - SVG props - * @returns SVG element - */ -export function CogIcon(props: React.SVGProps): JSX.Element { - return ( - - ) -} diff --git a/vscode/react/src/components/graph/Graph.css b/vscode/react/src/components/graph/Graph.css deleted file mode 100644 index bef07d33b8..0000000000 --- a/vscode/react/src/components/graph/Graph.css +++ /dev/null @@ -1,49 +0,0 @@ -.react-flow__node { - z-index: 10 !important; - background-color: var(--vscode-editor-background, #ffffff); -} -.react-flow__handle { - background-color: currentColor; -} -.react-flow__node.react-flow__node-model:hover, -.react-flow__node.react-flow__node-model:active { - z-index: 20 !important; -} -react-flow__attribution { - background: transparent; -} -.lineage__column-source b { - font-weight: 900; - /* color: var(--color-primary); */ - color: black; -} -.react-flow__edge { - pointer-events: none !important; - z-index: -1 !important; -} -.react-flow__background path { - stroke: inherit; -} - -.react-flow__controls-button { - box-shadow: none; - border: var(--vscode-button-border); - background: var(--vscode-button-background); - color: var(--vscode-foreground); -} -.react-flow__controls-button:hover { - background: var(--vscode-button-hoverBackground); - border: var(--vscode-button-hoverBorder); -} -.react-flow__controls-button:active { - background: var(--vscode-button-activeBackground); - border: var(--vscode-button-activeBorder); -} -.react-flow__controls { - box-shadow: none; -} -.react-flow__panel { - box-shadow: none !important; - border: none !important; - padding: 0 !important; -} diff --git a/vscode/react/src/components/graph/ModelColumns.tsx b/vscode/react/src/components/graph/ModelColumns.tsx deleted file mode 100644 index e0e180de51..0000000000 --- a/vscode/react/src/components/graph/ModelColumns.tsx +++ /dev/null @@ -1,585 +0,0 @@ -import React, { useEffect, useMemo, useCallback } from 'react' -import { Handle, Position, useUpdateNodeInternals } from 'reactflow' -import 'reactflow/dist/base.css' -import { mergeLineageWithColumns, mergeConnections } from './help' -import { - debounceSync, - isArrayNotEmpty, - isFalse, - isNil, - isNotNil, - truncate, -} from '@/utils/index' -import { toID, type PartialColumnHandleId, type Side } from './types' -import { NoSymbolIcon } from '@heroicons/react/24/solid' -import { ClockIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline' -import clsx from 'clsx' -import { - type ColumnDescription, - type ColumnLineageApiLineageModelNameColumnNameGet200, - type LineageColumn, -} from '@/api/client' -import Loading from '@/components/loading/Loading' -import Spinner from '@/components/logo/Spinner' -import './Graph.css' -import { - type InitialSQLMeshModel, - type ModelSQLMeshModel, -} from '@/domain/sqlmesh-model' -import { useLineageFlow } from './context' -import { useApiColumnLineage } from '@/api/index' -import SourceList from '@/components/sourceList/SourceList' -import type { Lineage } from '@/domain/lineage' -import type { Column, ColumnName } from '@/domain/column' -import type { ModelEncodedFQN } from '@/domain/models' - -export function ModelColumns({ - nodeId, - columns, - disabled, - className, - limit, - withHandles = false, - withDescription = true, - maxHeight = '50vh', -}: { - nodeId: ModelEncodedFQN - columns: Column[] - disabled?: boolean - className?: string - limit: number - withHandles?: boolean - withDescription?: boolean - maxHeight?: string -}): JSX.Element { - const { - mainNode, - connections, - isActiveColumn, - setConnections, - manuallySelectedColumn, - setManuallySelectedColumn, - setLineage, - removeActiveEdges, - addActiveEdges, - lineage, - lineageCache, - setLineageCache, - } = useLineageFlow() - - const [columnsSelected = [], columnsRest = []] = useMemo(() => { - const active: Column[] = [] - const rest: Column[] = [] - - columns.forEach(column => { - if (isActiveColumn(nodeId, column.name)) { - active.push(column) - } else { - rest.push(column) - } - }) - - return [active, rest] - }, [nodeId, columns, isActiveColumn]) - - function updateColumnLineage( - columnLineage: Record> = {}, - ): void { - let newLineageCache = lineageCache - let currentConnections - let currentLineage - - if (isNil(lineageCache)) { - const mainNodeLineage = isNil(mainNode) - ? undefined - : (lineage[mainNode] ?? lineageCache?.[mainNode]) - - newLineageCache = lineage - currentConnections = new Map() - currentLineage = - isNil(mainNode) || isNil(mainNodeLineage) - ? {} - : { [mainNode]: { models: [] } } - } else { - currentConnections = connections - currentLineage = structuredClone(lineage) - } - - const { connections: newConnections, activeEdges } = mergeConnections( - currentConnections, - columnLineage, - ) - - if (newConnections.size === 0 && activeEdges.length === 0) { - currentLineage = structuredClone(lineage) - newLineageCache = undefined - } else { - setConnections(newConnections) - addActiveEdges(activeEdges) - } - - const mergedLineage = mergeLineageWithColumns(currentLineage, columnLineage) - - setLineageCache(newLineageCache) - setLineage(mergedLineage) - } - - const isSelectManually = useCallback( - function isSelectManually(columnName: ColumnName): boolean { - if (isNil(manuallySelectedColumn)) return false - - const [selectedModel, selectedColumn] = manuallySelectedColumn - - if (isNil(selectedModel) || isNil(selectedColumn)) return false - - return selectedModel.fqn === nodeId && selectedColumn.name === columnName - }, - [nodeId, manuallySelectedColumn], - ) - - const removeEdges = useCallback( - function removeEdges(columnId: PartialColumnHandleId): void { - const visited = new Set() - - removeActiveEdges(walk(columnId, 'left').concat(walk(columnId, 'right'))) - - if (connections.size === 0 && isNotNil(lineageCache)) { - setLineage(lineageCache) - setLineageCache(undefined) - } - - setConnections(connections) - - function walk(id: string, side: Side): Array<[string, string]> { - if (visited.has(id)) return [] - - const edges = connections.get(id)?.[side] ?? [] - - connections.delete(id) - - visited.add(id) - - return edges - .map(edge => - [ - side === 'left' - ? [toID('left', id), toID('right', edge)] - : [toID('left', edge), toID('right', id)], - ].concat(walk(edge, side)), - ) - .flat() as Array<[PartialColumnHandleId, PartialColumnHandleId]> - } - }, - [removeActiveEdges, connections], - ) - - return ( -
- {isArrayNotEmpty(columnsSelected) && ( -
- {columnsSelected.map(column => ( - - ))} -
- )} - {columnsRest.length <= limit && ( -
- {columnsRest.map(column => ( - - ))} -
- )} - {columnsRest.length > limit && ( -
- - keyId="name" - keyName="name" - items={columnsRest} - withCounter={false} - withFilter={columnsRest.length > limit} - disabled={disabled} - listItem={({ disabled, item }) => ( - - )} - /> -
- )} -
- ) -} - -function ModelColumn({ - id, - nodeId, - column, - className, - disabled = false, - isActive = false, - hasLeft = false, - hasRight = false, - isEmpty = false, - updateColumnLineage, - removeEdges, - selectManually, - withHandles = false, - withDescription = true, -}: { - id: PartialColumnHandleId - nodeId: ModelEncodedFQN - column: Column - disabled?: boolean - isActive?: boolean - hasLeft?: boolean - hasRight?: boolean - isEmpty?: boolean - withHandles?: boolean - withDescription?: boolean - updateColumnLineage: ( - lineage: ColumnLineageApiLineageModelNameColumnNameGet200, - ) => void - removeEdges: (columnId: PartialColumnHandleId) => void - selectManually?: React.Dispatch< - React.SetStateAction< - [ModelSQLMeshModel, Column] | undefined - > - > - className?: string -}): JSX.Element { - const { - refetch: getColumnLineage, - isFetching, - isError, - } = useApiColumnLineage(nodeId, column.name, { models_only: true }) - - useEffect(() => { - if (isNil(selectManually)) return - - toggleColumnLineage() - selectManually(undefined) - }, [selectManually]) - - function toggleColumnLineage(): void { - if (disabled) return - - if (isActive) { - removeEdges(id) - } else { - void getColumnLineage().then(({ data }) => - updateColumnLineage(data ?? {}), - ) - } - } - - const showHandles = withHandles && (hasLeft || hasRight) - - return ( -
-
- {showHandles ? ( - - - - - ) : ( - <> - - - - )} -
-
- ) -} - -function ColumnHandles({ - nodeId, - id, - hasLeft = false, - hasRight = false, - disabled = false, - children, - className, -}: { - nodeId: ModelEncodedFQN - id: PartialColumnHandleId - children: React.ReactNode - className?: string - hasLeft?: boolean - hasRight?: boolean - disabled?: boolean -}): JSX.Element { - const updateNodeInternals = useUpdateNodeInternals() - - useEffect(() => { - // TODO: This is a hack to fix the issue where the handles are not rendered yet - setTimeout(() => { - updateNodeInternals(nodeId) - }, 100) - }, [hasLeft, hasRight]) - - return ( -
- {hasLeft && ( - - )} - {children} - {hasRight && ( - - )} -
- ) -} - -function ColumnDisplay({ - columnName, - columnType, - columnDescription, - className, - disabled = false, - withDescription = true, -}: { - columnName: ColumnName - columnType: string - columnDescription?: ColumnDescription - disabled?: boolean - withDescription?: boolean - className?: string -}): JSX.Element { - return ( -
-
- - {disabled && ( - - )} - {truncate(columnName, 50, 20)} - - - {truncate(columnType, 20, 10)} - -
- {isNotNil(columnDescription) && withDescription && ( -

{columnDescription}

- )} -
- ) -} - -function ColumnStatus({ - isFetching = false, - isError = false, - isTimeout = false, -}: { - isFetching: boolean - isError: boolean - isTimeout: boolean -}): JSX.Element { - return ( - <> - {isFetching && ( - - - - )} - {isTimeout && isFalse(isFetching) && ( - - )} - {isError && isFalse(isFetching) && ( - - )} - - ) -} - -function getColumnFromLineage( - lineage: Record, - nodeId: string, - columnName: string, -): LineageColumn | undefined { - return lineage?.[nodeId]?.columns?.[columnName as ColumnName] -} diff --git a/vscode/react/src/components/graph/ModelLineage.tsx b/vscode/react/src/components/graph/ModelLineage.tsx deleted file mode 100644 index 3d157d3869..0000000000 --- a/vscode/react/src/components/graph/ModelLineage.tsx +++ /dev/null @@ -1,413 +0,0 @@ -import { useApiModelLineage, useApiModels } from '@/api/index' -import { useEffect, useMemo, useState } from 'react' -import { type ModelSQLMeshModel } from '@/domain/sqlmesh-model' -import { type HighlightedNodes, useLineageFlow } from './context' -import ReactFlow, { - Controls, - Background, - BackgroundVariant, - type EdgeChange, - applyEdgeChanges, - applyNodeChanges, - type NodeChange, - useReactFlow, - type Edge, - type Node, - ReactFlowProvider, -} from 'reactflow' -import Loading from '@/components/loading/Loading' -import Spinner from '@/components/logo/Spinner' -import { createLineageWorker } from '@/components/graph/workers/index' -import { isArrayEmpty, isFalse, isNil, isNotNil } from '@/utils/index' -import clsx from 'clsx' -import ModelNode from './ModelNode' -import { - getEdges, - getLineageIndex, - getActiveNodes, - getUpdatedNodes, - getUpdatedEdges, - createGraphLayout, -} from './help' -import { SettingsControl } from '@/components/graph/SettingsControl' -import { - toModelLineage, - type ModelLineage as ModelLineageType, -} from '@/domain/lineage' -import './Graph.css' -import { - toKeys, - type LineageWorkerMessage, - type LineageWorkerRequestMessage, - type LineageWorkerResponseMessage, - type LineageWorkerErrorMessage, -} from './types' -import { encode } from '@/domain/models' - -const WITH_COLUMNS_LIMIT = 30 - -export function ModelLineage({ - model, - highlightedNodes, -}: { - model: ModelSQLMeshModel - highlightedNodes?: HighlightedNodes -}): JSX.Element { - const { - setActiveNodes, - setActiveEdges, - setConnections, - setLineage, - handleError, - setSelectedNodes, - setMainNode, - setWithColumns, - setHighlightedNodes, - setNodeConnections, - setLineageCache, - setUnknownModels, - models, - unknownModels, - setWithSecondary, - setWithConnected, - setWithImpacted, - } = useLineageFlow() - - useEffect(() => { - setWithColumns(true) - setWithSecondary(true) - setWithConnected(true) - setWithImpacted(true) - }, [setWithSecondary]) - - const { refetch: getModelLineage, isFetching: isFetchingModelLineage } = - useApiModelLineage(model.name) - const { isFetching: isFetchingModels } = useApiModels() - - const [isMergingModels, setIsMergingModels] = useState(false) - const [modelLineage, setModelLineage] = useState< - ModelLineageType | undefined - >(undefined) - - useEffect(() => { - const lineageWorker = new createLineageWorker() - - lineageWorker.addEventListener('message', handleLineageWorkerMessage) - - getModelLineage() - .then(({ data }) => { - setModelLineage(data ? toModelLineage(data) : undefined) - if (isNil(data)) return - - setIsMergingModels(true) - - const message: LineageWorkerRequestMessage = { - topic: 'lineage', - payload: { - currentLineage: {}, - newLineage: data, - mainNode: model.fqn, - }, - } - lineageWorker.postMessage(message) - }) - .catch(error => { - handleError?.(error) - }) - .finally(() => { - setActiveNodes(new Set()) - setActiveEdges(new Map()) - setConnections(new Map()) - setSelectedNodes(new Set()) - setLineageCache(undefined) - setMainNode(model.fqn) - }) - - return () => { - lineageWorker.removeEventListener('message', handleLineageWorkerMessage) - lineageWorker.terminate() - - setLineage({}) - setNodeConnections({}) - setMainNode(undefined) - setHighlightedNodes({}) - } - }, [model.name, model.hash]) - - useEffect(() => { - const modelNames = toKeys(modelLineage ?? {}) - for (const modelName of modelNames) { - const encodedModelName = encode(modelName) - if ( - isFalse(encodedModelName in models) && - isFalse(encodedModelName in unknownModels) - ) { - unknownModels.add(encodedModelName) - } - } - - setUnknownModels(new Set(unknownModels)) - }, [modelLineage, models]) - - useEffect(() => { - setHighlightedNodes(highlightedNodes ?? {}) - }, [highlightedNodes]) - - function handleLineageWorkerMessage( - e: MessageEvent, - ): void { - if (e.data.topic === 'lineage') { - const message = e.data as LineageWorkerResponseMessage - setIsMergingModels(false) - setNodeConnections(message.payload.nodesConnections) - setLineage(message.payload.lineage) - - if ( - Object.values(message.payload.lineage ?? {}).length > WITH_COLUMNS_LIMIT - ) { - setWithColumns(false) - } - } - - if (e.data.topic === 'error') { - const message = e.data as LineageWorkerErrorMessage - handleError?.(message.error) - setIsMergingModels(false) - } - } - - const isFetching = - isFetchingModelLineage || isFetchingModels || isMergingModels - - return ( -
- {isFetching && ( -
- - - -

- {isFetching ? "Loading Model's Lineage..." : "Merging Model's..."} -

-
-
- )} - - - -
- ) -} - -function ModelColumnLineage(): JSX.Element { - const { - withColumns, - lineage, - mainNode, - selectedEdges, - selectedNodes, - withConnected, - withImpacted, - withSecondary, - hasBackground, - activeEdges, - connectedNodes, - connections, - nodesMap, - handleError, - setActiveNodes, - setWithColumns, - } = useLineageFlow() - - const { setCenter } = useReactFlow() - - const [isBuildingLayout, setIsBuildingLayout] = useState(false) - - const nodeTypes = useMemo(() => ({ model: ModelNode }), []) - - const allEdges = useMemo(() => getEdges(lineage), [lineage]) - const lineageIndex = useMemo(() => getLineageIndex(lineage), [lineage]) - - const [nodes, setNodes] = useState([]) - const [edges, setEdges] = useState([]) - - useEffect(() => { - if (isArrayEmpty(allEdges) || isNil(mainNode)) return - - setIsBuildingLayout(true) - - const newActiveNodes = getActiveNodes( - allEdges, - activeEdges, - selectedEdges, - nodesMap, - ) - const newNodes = getUpdatedNodes( - Object.values(nodesMap), - newActiveNodes, - mainNode, - connectedNodes, - selectedNodes, - connections, - withConnected, - withImpacted, - withSecondary, - ) - const newEdges = getUpdatedEdges( - allEdges, - connections, - activeEdges, - newActiveNodes, - selectedEdges, - selectedNodes, - connectedNodes, - withConnected, - withImpacted, - withSecondary, - ) - const createLayout = createGraphLayout({ - nodesMap, - nodes: newNodes, - edges: newEdges, - }) - - createLayout - .create() - .then(layout => { - setEdges(layout.edges) - setNodes(layout.nodes) - }) - .catch(error => { - handleError?.(error) - setEdges([]) - setNodes([]) - }) - .finally(() => { - const node = isNil(mainNode) ? undefined : nodesMap[mainNode] - - if (isNotNil(node)) { - setCenter(node.position.x, node.position.y, { - zoom: 0.5, - duration: 0, - }) - } - - setTimeout(() => { - setIsBuildingLayout(false) - }, 100) - }) - - return () => { - createLayout.terminate() - - setEdges([]) - setNodes([]) - } - }, [activeEdges, nodesMap, lineageIndex]) - - useEffect(() => { - if (isNil(mainNode) || isArrayEmpty(nodes)) return - - const newActiveNodes = getActiveNodes( - allEdges, - activeEdges, - selectedEdges, - nodesMap, - ) - const newNodes = getUpdatedNodes( - nodes, - newActiveNodes, - mainNode, - connectedNodes, - selectedNodes, - connections, - withConnected, - withImpacted, - withSecondary, - ) - - const newEdges = getUpdatedEdges( - allEdges, - connections, - activeEdges, - newActiveNodes, - selectedEdges, - selectedNodes, - connectedNodes, - withConnected, - withImpacted, - withSecondary, - ) - - setEdges(newEdges) - setNodes(newNodes) - setActiveNodes(newActiveNodes) - }, [ - connections, - nodesMap, - allEdges, - activeEdges, - selectedNodes, - selectedEdges, - connectedNodes, - withConnected, - withImpacted, - withSecondary, - withColumns, - mainNode, - ]) - - function onNodesChange(changes: NodeChange[]): void { - setNodes(applyNodeChanges(changes, nodes)) - } - - function onEdgesChange(changes: EdgeChange[]): void { - setEdges(applyEdgeChanges(changes, edges)) - } - - return ( - <> - {isBuildingLayout && ( -
- - - -

Building Lineage...

-
-
- )} - - - - - - - - ) -} diff --git a/vscode/react/src/components/graph/ModelNode.tsx b/vscode/react/src/components/graph/ModelNode.tsx deleted file mode 100644 index 864b1437fa..0000000000 --- a/vscode/react/src/components/graph/ModelNode.tsx +++ /dev/null @@ -1,234 +0,0 @@ -import { isNil, isArrayNotEmpty, isNotNil, isFalse } from '@/utils/index' -import clsx from 'clsx' -import { useMemo, useCallback, useState } from 'react' -import { ModelType, type Model } from '@/api/client' -import { useLineageFlow } from './context' -import { type GraphNodeData } from './help' -import { Position, type NodeProps } from 'reactflow' -import { ModelNodeHeaderHandles } from './ModelNodeHeaderHandles' -import { ModelColumns } from './ModelColumns' -import { fromAPIColumn, type Column } from '@/domain/column' -import { decode, type ModelEncodedFQN } from '@/domain/models' -import { toKeys } from './types' -import { MAX_VISIBLE_COLUMNS } from './constants' - -export const EnumLineageNodeModelType = { - ...ModelType, - cte: 'cte', - unknown: 'unknown', -} as const - -export const EnumColumnType = { - UNKNOWN: 'UNKNOWN', - STRUCT: 'STRUCT', -} as const - -export type LineageNodeModelType = keyof typeof EnumLineageNodeModelType -export type ColumnType = keyof typeof EnumColumnType - -export default function ModelNode({ - id: idProp, - data, - sourcePosition, - targetPosition, -}: NodeProps): JSX.Element { - const id = idProp as ModelEncodedFQN - const nodeData: GraphNodeData = data ?? { - label: '', - type: EnumLineageNodeModelType.unknown, - withColumns: false, - } - const { - // connections, - models, - handleClickModel, - lineage, - lineageCache, - selectedNodes, - setSelectedNodes, - mainNode, - withConnected, - connectedNodes, - highlightedNodes, - activeNodes, - } = useLineageFlow() - - const columns: Column[] = useMemo(() => { - const modelsArray = Object.values(models) - const decodedId = decode(id) - const model = modelsArray.find((m: Model) => m.fqn === decodedId) - const modelColumns = model?.columns?.map(fromAPIColumn) ?? [] - - toKeys(lineage[decodedId]?.columns ?? {}).forEach(column => { - const found = modelColumns.find(({ name }) => name === column) - if (isNil(found)) { - modelColumns.push( - fromAPIColumn({ name: column, type: EnumColumnType.UNKNOWN }), - ) - } - }) - return modelColumns.map(column => { - let columnType = column.type ?? EnumColumnType.UNKNOWN - if (columnType.startsWith(EnumColumnType.STRUCT)) { - columnType = EnumColumnType.STRUCT - } - return { - ...column, - type: columnType, - } - }) - }, [id, models, lineage]) - - const highlightedNodeModels = useMemo( - () => Object.values(highlightedNodes).flat(), - [highlightedNodes], - ) - - const [isMouseOver, setIsMouseOver] = useState(false) - - const handleClick = useCallback( - (e: React.MouseEvent) => { - e.stopPropagation() - if (handleClickModel) { - handleClickModel(id) - } - }, - [handleClickModel, id, data.isInteractive], - ) - - const handleSelect = useCallback( - (e: React.MouseEvent) => { - e.stopPropagation() - - if (highlightedNodeModels.includes(id) || mainNode === id) return - - setSelectedNodes(current => { - if (current.has(id)) { - current.delete(id) - } else { - current.add(id) - } - - return new Set(current) - }) - }, - [setSelectedNodes, highlightedNodeModels], - ) - - const splat = highlightedNodes['*'] - // const hasSelectedColumns = columns.some(({ name }) => - // connections.get(toID(id, name)), - // ) - const hasHighlightedNodes = Object.keys(highlightedNodes).length > 0 - const highlighted = Object.keys(highlightedNodes).find(key => - highlightedNodes[key]!.includes(id), - ) - const isMainNode = mainNode === id - const isHighlightedNode = highlightedNodeModels.includes(id) - const isSelected = selectedNodes.has(id) - // Ensure nodeData.type is a valid LineageNodeModelType - const nodeType: LineageNodeModelType = Object.values( - EnumLineageNodeModelType, - ).includes(nodeData.type) - ? (nodeData.type as LineageNodeModelType) - : EnumLineageNodeModelType.unknown - - const isModelSQL = nodeType === EnumLineageNodeModelType.sql - const isCTE = nodeType === EnumLineageNodeModelType.cte - const isModelExternal = nodeType === EnumLineageNodeModelType.external - const isModelSeed = nodeType === EnumLineageNodeModelType.seed - const isModelUnknown = nodeType === EnumLineageNodeModelType.unknown - const showColumns = - nodeData.withColumns && - isArrayNotEmpty(columns) && - isFalse(hasHighlightedNodes) - const isActiveNode = - selectedNodes.size > 0 || activeNodes.size > 0 || withConnected - ? isSelected || - activeNodes.has(id as ModelEncodedFQN) || - (withConnected && connectedNodes.has(id)) - : connectedNodes.has(id) - const isInteractive = true - // mainNode !== id && - // isNotNil(handleClickModel) && - // isFalse(isCTE) && - // isFalse(isModelUnknown) - const shouldDisableColumns = isFalse(isModelSQL) - - return ( -
setIsMouseOver(true)} - onMouseLeave={() => setIsMouseOver(false)} - className={clsx( - 'text-xs font-semibold border-4', - isMouseOver ? 'z-50' : 'z-1', - showColumns ? 'rounded-xl' : 'rounded-2xl', - (hasHighlightedNodes ? isHighlightedNode : isActiveNode) || isMainNode - ? 'opacity-100' - : 'opacity-40 hover:opacity-100', - isNil(highlighted) - ? hasHighlightedNodes - ? splat - : [ - isCTE - ? 'border-accent-500 bg-accent-500 text-accent-500 dark:border-accent-300 dark:bg-accent-300 dark:text-accent-300' - : isModelUnknown - ? 'border-neutral-500 bg-neutral-500 text-neutral-500 dark:border-neutral-300 dark:bg-neutral-300 dark:text-neutral-300' - : 'border-secondary-500 bg-secondary-500 text-secondary-500 dark:bg-primary-500 dark:border-primary-500 dark:text-primary-500', - isMainNode - ? 'ring-8 ring-brand-50' - : isModelExternal || isModelSeed - ? 'ring-8 ring-accent-50' - : '', - ] - : highlighted, - isSelected && isCTE - ? 'ring-8 ring-accent-50' - : isSelected && isModelUnknown - ? 'ring-8 ring-neutral-50' - : isSelected && 'ring-8 ring-secondary-50 dark:ring-primary-50', - )} - style={{ - maxWidth: isNil(nodeData.width) - ? 'auto' - : `${nodeData.width as number}px`, - }} - > - - {showColumns && ( - - )} -
- ) -} diff --git a/vscode/react/src/components/graph/ModelNodeHeaderHandles.tsx b/vscode/react/src/components/graph/ModelNodeHeaderHandles.tsx deleted file mode 100644 index a23d6af5c4..0000000000 --- a/vscode/react/src/components/graph/ModelNodeHeaderHandles.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { type MouseEvent } from 'react' -import { Handle, Position } from 'reactflow' -import 'reactflow/dist/base.css' -import { getModelNodeTypeTitle } from './help' -import { isNotNil, truncate } from '@/utils/index' -import { toID } from './types' -import { ArrowRightCircleIcon } from '@heroicons/react/24/solid' -import clsx from 'clsx' -import { type LineageNodeModelType } from './ModelNode' -import type { ModelEncodedFQN } from '@/domain/models' - -export function ModelNodeHeaderHandles({ - id, - className, - hasLeft = false, - hasRight = false, - isSelected = false, - isDraggable = false, - label, - type, - numberOfColumns, - handleClick, - handleSelect, -}: { - id: ModelEncodedFQN - label: string - type?: LineageNodeModelType - hasLeft?: boolean - hasRight?: boolean - numberOfColumns?: number - className?: string - isSelected?: boolean - isDraggable?: boolean - handleClick?: (e: MouseEvent) => void - handleSelect?: (e: MouseEvent) => void -}): JSX.Element { - return ( -
- {hasLeft && ( - - - - )} -
- {isNotNil(handleSelect) && ( - - - - )} - - {isNotNil(type) && ( - - {getModelNodeTypeTitle(type)} - - )} - - {truncate(decodeURI(label), 50, 20)} - - {isNotNil(numberOfColumns) && ( - - {numberOfColumns} - - )} - -
- {hasRight && ( - - - - )} -
- ) -} diff --git a/vscode/react/src/components/graph/SettingsControl.tsx b/vscode/react/src/components/graph/SettingsControl.tsx deleted file mode 100644 index 3016a96ee7..0000000000 --- a/vscode/react/src/components/graph/SettingsControl.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react' -import { CheckIcon } from '@heroicons/react/24/outline' -import { CogIcon } from '@/components/graph/CogIcon' -import clsx from 'clsx' - -interface SettingsControlProps { - showColumns: boolean - onWithColumnsChange: (value: boolean) => void -} - -export function SettingsControl({ - showColumns, - onWithColumnsChange, -}: SettingsControlProps): JSX.Element { - return ( - - - - - onWithColumnsChange(!showColumns)} - > - Show Columns - {showColumns && ( - - - - ) -} diff --git a/vscode/react/src/components/graph/constants.ts b/vscode/react/src/components/graph/constants.ts deleted file mode 100644 index 0927ac027e..0000000000 --- a/vscode/react/src/components/graph/constants.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Space between nodes. - */ -export const NODE_BALANCE_SPACE = 64 -/** - * Height of a column line. - */ -export const COLUMN_LINE_HEIGHT = 24 -/** - * Assumed width of a character. - */ -export const CHAR_WIDTH = 8 -/** - * Maximum number of columns that can be visible in a node. - */ -export const MAX_VISIBLE_COLUMNS = 5 diff --git a/vscode/react/src/components/graph/context.tsx b/vscode/react/src/components/graph/context.tsx deleted file mode 100644 index 9ab4f0722e..0000000000 --- a/vscode/react/src/components/graph/context.tsx +++ /dev/null @@ -1,330 +0,0 @@ -import { - createContext, - useState, - useContext, - useCallback, - useMemo, -} from 'react' -import { getNodeMap, hasActiveEdge, hasActiveEdgeConnector } from './help' -import { type Node } from 'reactflow' -import type { Lineage } from '@/domain/lineage' -import type { ModelSQLMeshModel } from '@/domain/sqlmesh-model' -import type { Column } from '@/domain/column' -import type { ModelEncodedFQN, ModelName } from '@/domain/models' -import type { ColumnName } from '@/domain/column' -import type { Model } from '@/api/client' -import { toID, toKeys } from './types' -import type { ConnectedNode } from '@/components/graph/types' - -export interface Connections { - left: string[] - right: string[] -} -export type ActiveColumns = Map -export type ActiveEdges = Map> -export type ActiveNodes = Set -export type SelectedNodes = Set -export type HighlightedNodes = Record - -interface LineageFlow { - lineage: Record - lineageCache?: Record - mainNode?: ModelEncodedFQN - connectedNodes: Set - activeEdges: ActiveEdges - activeNodes: ActiveNodes - selectedNodes: SelectedNodes - selectedEdges: ConnectedNode[] - models: Record - unknownModels: Set - connections: Map - withConnected: boolean - withColumns: boolean - hasBackground: boolean - withImpacted: boolean - withSecondary: boolean - manuallySelectedColumn?: [ModelSQLMeshModel, Column] - highlightedNodes: HighlightedNodes - nodesMap: Record - setHighlightedNodes: React.Dispatch> - setActiveNodes: React.Dispatch> - setWithConnected: React.Dispatch> - setMainNode: React.Dispatch> - setSelectedNodes: React.Dispatch> - setWithColumns: React.Dispatch> - setHasBackground: React.Dispatch> - setWithImpacted: React.Dispatch> - setWithSecondary: React.Dispatch> - setConnections: React.Dispatch>> - hasActiveEdge: (edge: [string | undefined, string | undefined]) => boolean - addActiveEdges: (edges: Array<[string, string]>) => void - removeActiveEdges: (edges: Array<[string, string]>) => void - setActiveEdges: React.Dispatch> - setUnknownModels: React.Dispatch>> - setLineage: React.Dispatch>> - setLineageCache: React.Dispatch< - React.SetStateAction | undefined> - > - handleClickModel?: (modelName: ModelEncodedFQN) => void - handleError?: (error: any) => void - setManuallySelectedColumn: React.Dispatch< - React.SetStateAction<[ModelSQLMeshModel, Column] | undefined> - > - setNodeConnections: React.Dispatch> - isActiveColumn: ( - modelName: ModelEncodedFQN, - columnName: ColumnName, - ) => boolean -} - -export const LineageFlowContext = createContext({ - selectedEdges: [], - lineage: {}, - lineageCache: undefined, - withColumns: false, - withConnected: false, - withImpacted: true, - withSecondary: false, - hasBackground: true, - mainNode: undefined, - activeEdges: new Map(), - activeNodes: new Set(), - models: {}, - unknownModels: new Set(), - manuallySelectedColumn: undefined, - connections: new Map(), - selectedNodes: new Set(), - connectedNodes: new Set(), - highlightedNodes: {}, - nodesMap: {}, - setHighlightedNodes: () => {}, - setWithColumns: () => false, - setHasBackground: () => false, - setWithImpacted: () => false, - setWithSecondary: () => false, - setWithConnected: () => false, - hasActiveEdge: () => false, - addActiveEdges: () => {}, - removeActiveEdges: () => {}, - setActiveEdges: () => {}, - handleClickModel: () => {}, - setManuallySelectedColumn: () => {}, - handleError: error => console.error(error), - setLineage: () => {}, - setLineageCache: () => {}, - isActiveColumn: () => false, - setConnections: () => {}, - setSelectedNodes: () => {}, - setMainNode: () => {}, - setActiveNodes: () => {}, - setNodeConnections: () => {}, - setUnknownModels: () => {}, -}) - -export default function LineageFlowProvider({ - handleError, - handleClickModel, - children, - showColumns = true, - showConnected = false, - showControls = true, - models, -}: { - children: React.ReactNode - handleClickModel?: (modelName: ModelEncodedFQN) => void - handleError?: (error: any) => void - showColumns?: boolean - showConnected?: boolean - showControls?: boolean - models: Record -}): JSX.Element { - const [lineage, setLineage] = useState>({}) - const [unknownModels, setUnknownModels] = useState(new Set()) - const [lineageCache, setLineageCache] = useState< - Record | undefined - >(undefined) - const [nodesConnections, setNodeConnections] = useState< - Record - >({}) - const [withColumns, setWithColumns] = useState(showColumns) - const [mainNode, setMainNode] = useState() - const [manuallySelectedColumn, setManuallySelectedColumn] = - useState<[ModelSQLMeshModel, Column]>() - const [activeEdges, setActiveEdges] = useState(new Map()) - const [connections, setConnections] = useState>( - new Map(), - ) - const [withConnected, setWithConnected] = useState(showConnected) - const [selectedNodes, setSelectedNodes] = useState(new Set()) - const [activeNodes, setActiveNodes] = useState(new Set()) - const [highlightedNodes, setHighlightedNodes] = useState({}) - const [hasBackground, setHasBackground] = useState(true) - const [withImpacted, setWithImpacted] = useState(true) - const [withSecondary, setWithSecondary] = useState(false) - - const nodesMap = useMemo( - () => - getNodeMap({ - lineage, - // @ts-expect-error TODO: fix this, should move to internal representation - models, - unknownModels, - withColumns, - }), - [lineage, models, withColumns, unknownModels], - ) - - const checkActiveEdge = useCallback( - function checkActiveEdge( - edge: [string | undefined, string | undefined], - ): boolean { - return hasActiveEdge(activeEdges, edge) - }, - [activeEdges], - ) - - const addActiveEdges = useCallback( - function addActiveEdges(edges: Array<[string, string]>): void { - setActiveEdges(activeEdges => { - edges.forEach(([leftConnect, rightConnect]) => { - const left = activeEdges.get(leftConnect) ?? [] - const right = activeEdges.get(rightConnect) ?? [] - const hasDuplicateLeft = left.some( - ([left, right]) => left === leftConnect && right === rightConnect, - ) - const hasDuplicateRight = right.some( - ([left, right]) => left === leftConnect && right === rightConnect, - ) - - if (!hasDuplicateLeft) { - left.push([leftConnect, rightConnect]) - } - - if (!hasDuplicateRight) { - right.push([leftConnect, rightConnect]) - } - - activeEdges.set(leftConnect, left) - activeEdges.set(rightConnect, right) - }) - - return new Map(activeEdges) - }) - }, - [setActiveEdges], - ) - - const removeActiveEdges = useCallback( - function removeActiveEdges(edges: Array<[string, string]>): void { - setActiveEdges(activeEdges => { - edges.forEach(([left, right]) => { - const edgesLeft = (activeEdges.get(left) ?? []).filter( - e => e[0] !== left && e[1] !== right, - ) - const edgesRight = (activeEdges.get(right) ?? []).filter( - e => e[0] !== left && e[1] !== right, - ) - - activeEdges.set(left, edgesLeft) - activeEdges.set(right, edgesRight) - }) - - return new Map(activeEdges) - }) - - setConnections(connections => { - edges.forEach(([left, right]) => { - connections.delete(left) - connections.delete(right) - }) - - return new Map(connections) - }) - }, - [setActiveEdges, setConnections], - ) - - const isActiveColumn = useCallback( - function isActive( - modelName: ModelEncodedFQN, - columnName: ColumnName, - ): boolean { - const leftConnector = toID('left', modelName, columnName) - const rightConnector = toID('right', modelName, columnName) - return ( - hasActiveEdgeConnector(activeEdges, leftConnector) || - hasActiveEdgeConnector(activeEdges, rightConnector) - ) - }, - [checkActiveEdge, activeEdges], - ) - - const connectedNodes = useMemo( - () => new Set(toKeys(nodesConnections)), - [nodesConnections], - ) - - const selectedEdges = useMemo( - () => - Array.from(selectedNodes) - .flatMap(id => nodesConnections[id]) - .filter(Boolean) as any[], - [nodesConnections, selectedNodes], - ) - - return ( - - {children} - - ) -} - -export function useLineageFlow(): LineageFlow { - return useContext(LineageFlowContext) -} diff --git a/vscode/react/src/components/graph/help.ts b/vscode/react/src/components/graph/help.ts deleted file mode 100644 index 93e5c4db45..0000000000 --- a/vscode/react/src/components/graph/help.ts +++ /dev/null @@ -1,739 +0,0 @@ -import ELK, { type ElkNode } from 'elkjs/lib/elk.bundled.js' -import { - isArrayNotEmpty, - isFalse, - isNil, - isNotNil, - isObjectEmpty, -} from '@/utils/index' -import { type LineageColumn } from '@/api/client' -import { Position, type Edge, type Node, type XYPosition } from 'reactflow' -import { type ActiveEdges, type Connections } from './context' -import { toID, toKeys } from './types' -import { - EnumLineageNodeModelType, - type LineageNodeModelType, -} from './ModelNode' -import type { Lineage } from '@/domain/lineage' -import type { ConnectedNode } from '@/components/graph/types' -import { encode, type ModelEncodedFQN, type ModelURI } from '@/domain/models' -import type { Column, ColumnName } from '@/domain/column' -import type { ModelSQLMeshModel } from '@/domain/sqlmesh-model' -import { - CHAR_WIDTH, - COLUMN_LINE_HEIGHT, - MAX_VISIBLE_COLUMNS, - NODE_BALANCE_SPACE, -} from './constants' - -export interface GraphNodeData { - label: string - type: LineageNodeModelType - withColumns: boolean - width?: number - height?: number - [key: string]: any -} - -export function createGraphLayout({ - nodesMap, - nodes = [], - edges = [], -}: { - nodesMap: Record - nodes: Node[] - edges: Edge[] -}): { - create: () => Promise<{ nodes: Node[]; edges: Edge[] }> - terminate: () => void -} { - // https://eclipse.dev/elk/reference/options.html - const elk: any = new ELK() - - return { - terminate: () => elk.worker.terminate(), - create: async () => - new Promise((resolve, reject) => { - elk - .layout({ - id: 'root', - layoutOptions: { - 'elk.algorithm': 'layered', - 'elk.layered.layering.strategy': 'NETWORK_SIMPLEX', - 'elk.layered.crossingMinimization.strategy': 'INTERACTIVE', - 'elk.direction': 'RIGHT', - // https://eclipse.dev/elk/reference/options/org-eclipse-elk-layered-considerModelOrder-strategy.html - 'elk.layered.considerModelOrder.strategy': 'PREFER_NODES', - 'elk.layered.nodePlacement.strategy': 'SIMPLE', - }, - children: nodes.map(node => ({ - id: node.id, - width: node.data.width, - height: node.data.height, - })), - edges: edges.map(edge => ({ - id: edge.id, - sources: [edge.source], - targets: [edge.target], - })), - }) - .then((layout: any) => - resolve({ - edges, - nodes: repositionNodes(layout.children, nodesMap), - }), - ) - .catch(reject) - }), - } -} - -export function getEdges( - lineage: Record = {}, -): Edge[] { - const modelNames = toKeys(lineage) - const outputEdges: Edge[] = [] - - for (const targetModelName of modelNames) { - const targetModel = lineage[targetModelName]! - - targetModel.models.forEach(sourceModelName => { - outputEdges.push(createGraphEdge(sourceModelName, targetModelName)) - }) - - const targetColumnNames = toKeys(targetModel.columns ?? {}) - for (const targetColumnName of targetColumnNames) { - const sourceModel = targetModel.columns?.[targetColumnName] - - if (isNil(sourceModel) || isNil(sourceModel.models)) continue - - const sourceModelNames = toKeys(sourceModel.models) - for (const sourceModelName of sourceModelNames) { - const sourceColumns = sourceModel.models[sourceModelName] - - if (isNil(sourceColumns)) continue - - for (const sourceColumnName of sourceColumns) { - const sourceHandler = toID('right', sourceModelName, sourceColumnName) - const targetHandler = toID('left', targetModelName, targetColumnName) - outputEdges.push( - createGraphEdge( - sourceModelName, - targetModelName, - sourceHandler, - targetHandler, - true, - { - columnSource: sourceColumnName, - columnTarget: targetColumnName, - }, - ), - ) - } - } - } - } - - return outputEdges -} - -export function getNodeMap({ - lineage, - models, - unknownModels, - withColumns, -}: { - models: Record - withColumns: boolean - unknownModels: Set - lineage?: Record -}): Record { - if (isNil(lineage)) return {} - - const sources = new Set(Object.values(lineage).flatMap(l => l.models)) - const modelNames = Object.keys(lineage) - - return modelNames.reduce((acc: Record, modelName: string) => { - const decodedModelName = modelName.includes('%') - ? decodeURI(modelName) - : modelName - const model = Object.values(models).find(m => m.fqn === decodedModelName) - const nodeType: LineageNodeModelType = isNotNil(model) - ? (model.type as LineageNodeModelType) - : // If model name present in lineage but not in global models - // it means either this is a CTE or model is UNKNOWN - // CTEs only have connections between columns - // where UNKNOWN model has connection only from another model - unknownModels.has(modelName) - ? EnumLineageNodeModelType.unknown - : EnumLineageNodeModelType.cte - - const node = createGraphNode(modelName, { - label: model?.name ?? modelName, - withColumns, - type: nodeType, - }) - const columnsCount = withColumns - ? (models[modelName]?.columns?.length ?? 0) - : 0 - - const maxWidth = Math.min( - getNodeMaxWidth(modelName, columnsCount === 0, models), - 320, - ) - const maxHeight = getNodeMaxHeight(columnsCount) - - node.data.width = maxWidth + NODE_BALANCE_SPACE * 3 - node.data.height = withColumns - ? maxHeight + NODE_BALANCE_SPACE * 2 - : NODE_BALANCE_SPACE - - if (isArrayNotEmpty(lineage[node.id]?.models)) { - node.targetPosition = Position.Left - } - - if (sources.has(node.id as ModelEncodedFQN)) { - node.sourcePosition = Position.Right - } - - acc[modelName] = node - - return acc - }, {}) -} - -function getNodeMaxWidth( - label: string, - hasColumns: boolean = false, - models: Record = {}, -): number { - const defaultWidth = label.length * CHAR_WIDTH - const columns = models[label]?.columns ?? [] - - return hasColumns - ? Math.max(...columns.map(getColumnWidth), defaultWidth) - : defaultWidth -} - -function getColumnWidth(column: Column): number { - return ( - (column.name.length + column.type.length) * CHAR_WIDTH + NODE_BALANCE_SPACE - ) -} - -function getNodeMaxHeight(columnsCount: number): number { - return ( - COLUMN_LINE_HEIGHT * Math.min(columnsCount, MAX_VISIBLE_COLUMNS) + - NODE_BALANCE_SPACE - ) -} - -function repositionNodes( - elkNodes: ElkNode[] = [], - nodesMap: Record, -): Node[] { - const nodes: Node[] = [] - - elkNodes.forEach(node => { - const output = nodesMap[node.id] - - if (isNil(output)) return - - if (isNotNil(node.x) && output.position.x === 0) { - output.position.x = node.x - } - - if (isNotNil(node.y) && output.position.y === 0) { - output.position.y = node.y - } - - nodes.push(output) - }) - - return nodes -} - -function createGraphNode( - id: string, - data: GraphNodeData, - position: XYPosition = { x: 0, y: 0 }, - hidden: boolean = false, -): Node { - return { - id, - dragHandle: '.drag-handle', - type: 'model', - position, - hidden, - data, - connectable: false, - selectable: false, - deletable: false, - focusable: false, - zIndex: -1, - } -} - -function createGraphEdge( - source: string, - target: string, - sourceHandle?: string, - targetHandle?: string, - hidden: boolean = false, - data?: Data, -): Edge { - const output: Edge = { - id: toID(source, target, sourceHandle, targetHandle), - source, - target, - hidden, - data, - type: 'smoothstep', - style: { - strokeWidth: isNil(sourceHandle) || isNil(targetHandle) ? 2 : 4, - }, - } - - if (sourceHandle != null) { - output.sourceHandle = sourceHandle - } - - if (targetHandle != null) { - output.targetHandle = targetHandle - } - - return output -} - -export function mergeLineageWithColumns( - currentLineage: Record = {}, - newLineage: Record> = {}, -): Record { - for (const targetModelName in newLineage) { - const targetModelNameEncoded = encodeURI(targetModelName) - - if (isNil(currentLineage[targetModelNameEncoded])) { - currentLineage[targetModelNameEncoded] = { columns: {}, models: [] } - } - - const currentLineageModel = currentLineage[targetModelNameEncoded]! - const newLineageModel = newLineage[targetModelName]! - - for (const targetColumnName in newLineageModel) { - const targetColumnNameEncoded = encodeURI(targetColumnName) - const newLineageModelColumn = newLineageModel[targetColumnName]! - - if (isNil(currentLineageModel.columns)) { - currentLineageModel.columns = {} - } - - // New Column Lineage delivers fresh data, so we can just assign it - currentLineageModel.columns[targetColumnNameEncoded as ColumnName] = { - expression: newLineageModelColumn.expression ?? undefined, - source: newLineageModelColumn.source ?? undefined, - models: {}, - } - - // If there are no models in new lineage, skip - if (isObjectEmpty(newLineageModelColumn.models)) continue - - const currentLineageModelColumn = - currentLineageModel.columns[targetColumnNameEncoded as ColumnName]! - const currentLineageModelColumnModels = currentLineageModelColumn.models - - for (const sourceColumnName in newLineageModelColumn.models) { - const sourceColumnNameEncoded = encodeURI(sourceColumnName) - const currentLineageModelColumnModel = - currentLineageModelColumnModels[ - sourceColumnNameEncoded as ModelEncodedFQN - ]! - const newLineageModelColumnModel = - newLineageModelColumn.models[sourceColumnName]! - - // @ts-expect-error TODO: fix this - currentLineageModelColumnModels[ - sourceColumnNameEncoded as ModelEncodedFQN - ] = Array.from( - new Set( - isNil(currentLineageModelColumnModel) - ? newLineageModelColumnModel - : currentLineageModelColumnModel.concat( - newLineageModelColumnModel as ColumnName[], - ), - ), - ).map(uri => encode(uri as ModelURI)) - } - } - } - - return currentLineage -} - -export function mergeConnections( - connections: Map, - lineage: Record> = {}, -): { - connections: Map - activeEdges: Array<[string, string]> -} { - const activeEdges: Array<[string, string]> = [] - - // We are getting lineage in format of target -> source - for (const targetModelName in lineage) { - const targetModelNameEncoded = encodeURI(targetModelName) - const model = lineage[targetModelName]! - - for (const targetColumnName in model) { - const targetColumnNameEncoded = encodeURI(targetColumnName) - const column = model[targetColumnName] - - // We don't have any connectins so we skip - if (isNil(column?.models)) continue - - // At this point our Node is model -> {modelName} and column -> {columnName} - // It is a target (left handler) - // but it can also be a source (right handler) for other connections - const modelColumnIdTarget = toID( - targetModelNameEncoded, - targetColumnNameEncoded, - ) - - // We need to check if {modelColumnIdTarget} is already a source/target for other connections - // Left and Right coresponds to node's handlers for {columnName} column - const connectionsModelTarget = connections.get(modelColumnIdTarget) ?? { - left: [], - right: [], - } - - Object.entries(column.models).forEach(([sourceModelName, columns]) => { - const sourceModelNameEncoded = encodeURI(sourceModelName) - columns.forEach(sourceColumnName => { - const sourceColumnNameEncoded = encodeURI(sourceColumnName) - // It is a source (right handler) - // but it can also be a target (left handler) for other connections - const modelColumnIdSource = toID( - sourceModelNameEncoded, - sourceColumnNameEncoded, - ) - - // We need to check if {modelColumnIdSource} is already a source/target for other connections - // Left and Right coresponds to node's handlers for {column} column - const connectionsModelSource = connections.get( - modelColumnIdSource, - ) ?? { left: [], right: [] } - - // we need to add {modelColumnIdTarget} to {connectionsModelSource}'s right handlers - connectionsModelSource.right = Array.from( - new Set(connectionsModelSource.right.concat(modelColumnIdTarget)), - ) - - // We need to add {modelColumnIdSource} to {connectionsModelTarget}'s right handlers - connectionsModelTarget.left = Array.from( - new Set(connectionsModelTarget.left.concat(modelColumnIdSource)), - ) - - connections.set(modelColumnIdSource, connectionsModelSource) - connections.set(modelColumnIdTarget, connectionsModelTarget) - - // Now we need to update active edges from connections - // Left bucket contains references to all sources (right handlers) - // And right bucket contains references to all targets (left handlers) - connectionsModelSource.left.forEach(id => { - activeEdges.push([ - toID('left', modelColumnIdSource), - toID('right', id), - ]) - }) - connectionsModelSource.right.forEach(id => { - activeEdges.push([ - toID('left', id), - toID('right', modelColumnIdSource), - ]) - }) - }) - }) - } - } - - return { - connections, - activeEdges, - } -} - -export function getLineageIndex(lineage: Record = {}): string { - return Object.keys(lineage) - .reduce((acc: string[], key) => { - const { models = [], columns = {} } = lineage[key]! - const allModels = new Set() - - models.forEach(m => allModels.add(m)) - - if (isNotNil(columns)) { - toKeys(columns).forEach(columnName => { - const column = columns[columnName] - if (isNotNil(column) && isNotNil(column.models)) { - toKeys(column.models).forEach(m => allModels.add(m)) - } - }) - } - - return acc.concat(Array.from(allModels)) - }, []) - .sort() - .join('') -} - -export function getModelAncestors( - lineage: Record = {}, - name: string, - output = new Set(), -): Set { - const model = lineage[name] - const models = model?.models ?? [] - - for (const modelName of models) { - if (output.has(modelName)) continue - - getModelAncestors(lineage, modelName, output).add(modelName) - } - - return output -} - -export function getActiveNodes( - edges: Edge[] = [], - activeEdges: ActiveEdges, - selectedEdges: ConnectedNode[], - nodesMap: Record, -): Set { - return new Set( - edges.reduce((acc: ModelEncodedFQN[], edge) => { - const sourceNode = isNil(edge.sourceHandle) - ? undefined - : nodesMap[edge.sourceHandle] - const targetNode = isNil(edge.targetHandle) - ? undefined - : nodesMap[edge.targetHandle] - - if ( - isNotNil(sourceNode) && - isNotNil(edge.sourceHandle) && - sourceNode.data.type === EnumLineageNodeModelType.external && - hasActiveEdgeConnector(activeEdges, edge.sourceHandle) - ) { - acc.push(edge.source as ModelEncodedFQN) - } else if ( - isNotNil(targetNode) && - isNotNil(edge.targetHandle) && - targetNode.data.type === EnumLineageNodeModelType.external && - hasActiveEdgeConnector(activeEdges, edge.targetHandle) - ) { - acc.push(edge.target as ModelEncodedFQN) - } else { - const isActiveEdge = hasActiveEdge(activeEdges, [ - edge.targetHandle, - edge.sourceHandle, - ]) - - if (isActiveEdge || hasEdge(selectedEdges, edge.id)) { - if (isNotNil(edge.source)) { - acc.push(edge.source as ModelEncodedFQN) - } - - if (isNotNil(edge.target)) { - acc.push(edge.target as ModelEncodedFQN) - } - } - } - return acc - }, []), - ) -} - -export function getUpdatedEdges( - edges: Edge[] = [], - connections: Map, - activeEdges: ActiveEdges, - activeNodes: Set, - selectedEdges: ConnectedNode[], - selectedNodes: Set, - connectedNodes: Set, - withConnected: boolean = false, - withImpacted: boolean = false, - withSecondary: boolean = false, -): Edge[] { - const tempEdges = edges.map(edge => { - const isActiveEdge = hasActiveEdge(activeEdges, [ - edge.targetHandle, - edge.sourceHandle, - ]) - - edge.hidden = true - - if (isNil(edge.sourceHandle) && isNil(edge.targetHandle)) { - // Edge between models - const hasSelections = - selectedNodes.size > 0 || connections.size > 0 || activeNodes.size > 0 - const isImpactedEdge = - connectedNodes.has(edge.source) || connectedNodes.has(edge.target) - const isSecondaryEdge = - isFalse(connectedNodes.has(edge.source)) || - isFalse(connectedNodes.has(edge.target)) - const withoutImpactedNodes = - isFalse(withImpacted) && - isFalse(withConnected) && - isFalse(hasSelections) - const withoutSecondaryNodes = - isFalse(withSecondary) && isFalse(hasSelections) - const shouldHideSecondary = isSecondaryEdge && withoutSecondaryNodes - const shouldHideImpacted = isImpactedEdge && withoutImpactedNodes - const isVisibleEdge = - selectedNodes.size > 0 && - hasEdge(selectedEdges, edge.id) && - activeNodes.has(edge.source) && - activeNodes.has(edge.target) - - if ( - isFalse(shouldHideImpacted) && - isFalse(shouldHideSecondary) && - (isFalse(hasSelections) || isVisibleEdge) - ) { - edge.hidden = false - } - } else { - // Edge between columns - if (connections.size > 0 && isActiveEdge) { - edge.hidden = false - } - } - - let stroke = 'var(--color-graph-edge-main)' - let strokeWidth = 2 - - const isConnectedSource = connectedNodes.has(edge.source) - const isConnectedTarget = connectedNodes.has(edge.target) - - if ( - hasEdge(selectedEdges, edge.id) || - (withConnected && isConnectedSource && isConnectedTarget) - ) { - strokeWidth = 4 - stroke = 'var(--color-graph-edge-selected)' - edge.zIndex = 10 - } else { - if (isActiveEdge) { - stroke = 'var(--color-graph-edge-secondary)' - } else if (isConnectedSource && isConnectedTarget) { - strokeWidth = 4 - stroke = 'var(--color-graph-edge-direct)' - edge.zIndex = 10 - } - } - - edge.style = { - ...edge.style, - stroke, - strokeWidth, - } - - return edge - }) - - return tempEdges -} - -export function getUpdatedNodes( - nodes: Node[] = [], - activeNodes: Set, - mainNode: ModelEncodedFQN, - connectedNodes: Set, - selectedNodes: Set, - connections: Map, - withConnected: boolean, - withImpacted: boolean, - withSecondary: boolean, -): Node[] { - return nodes.map(node => { - node.hidden = true - - const hasSelections = selectedNodes.size > 0 || connections.size > 0 - const isActiveNode = activeNodes.size === 0 || activeNodes.has(node.id) - const isImpactedNode = connectedNodes.has(node.id) - const isSecondaryNode = isFalse(connectedNodes.has(node.id)) - const withoutImpactedNodes = - isFalse(withImpacted) && isFalse(withConnected) && isFalse(hasSelections) - const withoutSecondaryNodes = - isFalse(withSecondary) && isFalse(hasSelections) - const shouldHideSecondary = isSecondaryNode && withoutSecondaryNodes - const shouldHideImpacted = isImpactedNode && withoutImpactedNodes - - if (isFalse(shouldHideImpacted) && isFalse(shouldHideSecondary)) { - node.hidden = isFalse(isActiveNode) - } - - if (node.data.type === EnumLineageNodeModelType.cte) { - node.hidden = isFalse(activeNodes.has(node.id)) - } - - if (mainNode === node.id) { - node.hidden = false - } - - return node - }) -} - -export function hasActiveEdge( - activeEdges: ActiveEdges = new Map(), - [leftConnect, rightConnect]: [ - string | undefined | null, - string | undefined | null, - ], -): boolean { - if (isNil(leftConnect) && isNil(rightConnect)) return false - - const left = isNil(leftConnect) ? undefined : activeEdges.get(leftConnect) - const right = isNil(rightConnect) ? undefined : activeEdges.get(rightConnect) - - if (isNil(left) && isNil(right)) return false - - const inLeft = Boolean( - left?.some(([l, r]) => l === leftConnect && r === rightConnect), - ) - const inRight = Boolean( - right?.some(([l, r]) => l === leftConnect && r === rightConnect), - ) - - return inLeft || inRight -} - -export function hasActiveEdgeConnector( - activeEdges: ActiveEdges = new Map(), - connector: string, -): boolean { - return (activeEdges.get(connector) ?? []).length > 0 -} - -export function getModelNodeTypeTitle(type: LineageNodeModelType): string { - switch (type) { - case EnumLineageNodeModelType.python: - return 'PYTHON' - case EnumLineageNodeModelType.sql: - return 'SQL' - case EnumLineageNodeModelType.seed: - return 'SEED' - case EnumLineageNodeModelType.cte: - return 'CTE' - case EnumLineageNodeModelType.external: - return 'EXTERNAL' - case EnumLineageNodeModelType.source: - return 'SOURCE' - default: - return 'UNKNOWN' - } -} - -function hasEdge(nodes: ConnectedNode[], edge: string): boolean { - return nodes.some(node => node.id === edge || hasEdge(node.edges, edge)) -} diff --git a/vscode/react/src/components/graph/types.ts b/vscode/react/src/components/graph/types.ts deleted file mode 100644 index 6e188b31c8..0000000000 --- a/vscode/react/src/components/graph/types.ts +++ /dev/null @@ -1,101 +0,0 @@ -import type { ColumnName } from '@/domain/column' -import type { ModelEncodedFQN } from '@/domain/models' -import type { Branded } from '@bus/brand' -import type { Lineage } from '@/domain/lineage' - -export type Side = 'left' | 'right' - -export type Direction = 'upstream' | 'downstream' - -export type NodeId = string - -export type EdgeId = string - -/** - * Partial column handle id that isn't complete yet as it's missing the left/right side - * definition. - */ -export type PartialColumnHandleId = Branded -export type ColumnHandleId = Branded -export type ModelHandleId = Branded - -/** - * Converts a list of strings to a single string with a double underscore - * Outlines with types, the type of ids that can be created. - * @param args - * @returns - */ -export function toID( - leftOrRight: Side, - modelName: ModelEncodedFQN, - columnName: ColumnName, -): NodeId -export function toID( - modelName: ModelEncodedFQN, - columnName: ColumnName, -): PartialColumnHandleId -export function toID( - leftOrRight: Side, - partialColumnHandleId: PartialColumnHandleId, -): ColumnHandleId -export function toID( - leftOrRight: Side, - modelName: ModelEncodedFQN, -): ModelHandleId -export function toID(source: NodeId, target: NodeId): NodeId -export function toID( - source: NodeId, - target: NodeId, - sourceHandle: string | undefined, - targetHandle: string | undefined, -): EdgeId -export function toID(...args: Array): string { - return args.filter(Boolean).join('__') -} - -export function toKeys(obj: Record): K[] { - return Object.keys(obj) as K[] -} - -export type ModelLineage = Record - -// Worker Message Types -export interface ConnectedNode { - id?: string - edges: ConnectedNode[] -} - -export interface LineageWorkerRequestPayload { - currentLineage: Record - newLineage: Record - mainNode: string -} - -export interface LineageWorkerResponsePayload { - lineage: Record - nodesConnections: Record -} - -export interface LineageWorkerErrorPayload { - error: Error -} - -export interface LineageWorkerRequestMessage { - topic: 'lineage' - payload: LineageWorkerRequestPayload -} - -export interface LineageWorkerResponseMessage { - topic: 'lineage' - payload: LineageWorkerResponsePayload -} - -export interface LineageWorkerErrorMessage { - topic: 'error' - error: Error -} - -export type LineageWorkerMessage = - | LineageWorkerRequestMessage - | LineageWorkerResponseMessage - | LineageWorkerErrorMessage diff --git a/vscode/react/src/components/graph/workers/index.ts b/vscode/react/src/components/graph/workers/index.ts deleted file mode 100644 index 9eb76d5287..0000000000 --- a/vscode/react/src/components/graph/workers/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import createLineageWorker from './lineage.ts?worker&inline' - -export { createLineageWorker } diff --git a/vscode/react/src/components/graph/workers/lineage.ts b/vscode/react/src/components/graph/workers/lineage.ts deleted file mode 100644 index fe8337b72d..0000000000 --- a/vscode/react/src/components/graph/workers/lineage.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { isFalse, isNil } from '@/utils/index' -import { type Lineage } from '@/domain/lineage' -import type { ModelEncodedFQN } from '@/domain/models' -import { - toID, - type NodeId, - type LineageWorkerMessage, - type LineageWorkerRequestMessage, - type LineageWorkerResponseMessage, - type LineageWorkerErrorMessage, - type ConnectedNode, -} from '@/components/graph/types' -import type { Direction } from '../types' - -interface WorkerScope { - onmessage: ((e: MessageEvent) => void) | null - postMessage: (message: LineageWorkerMessage) => void -} - -const scope = self as unknown as WorkerScope - -scope.onmessage = async (e: MessageEvent) => { - if (e.data.topic === 'lineage') { - try { - const message = e.data as LineageWorkerRequestMessage - const { currentLineage, newLineage, mainNode } = message.payload - const lineage = await mergeLineageWithModels(currentLineage, newLineage) - const nodesConnections = await getNodesConnections(mainNode, lineage) - - const responseMessage: LineageWorkerResponseMessage = { - topic: 'lineage', - payload: { - lineage, - nodesConnections, - }, - } - scope.postMessage(responseMessage) - } catch (error) { - const errorMessage: LineageWorkerErrorMessage = { - topic: 'error', - error: error as Error, - } - scope.postMessage(errorMessage) - } - } -} - -async function mergeLineageWithModels( - currentLineage: Record = {}, - data: Record = {}, -): Promise> { - return Object.entries(data).reduce( - (acc: Record, [key, models = []]) => { - key = encodeURI(key) - - acc[key] = { - models: models.map(encodeURI) as ModelEncodedFQN[], - columns: currentLineage?.[key]?.columns ?? undefined, - } - - return acc - }, - {}, - ) -} - -async function getNodesConnections( - mainNode: string, - lineage: Record = {}, -): Promise> { - return new Promise((resolve, reject) => { - if (isNil(lineage) || isNil(mainNode)) return {} - - const distances: Record = {} - - try { - getConnectedNodes('upstream', mainNode, lineage, distances) - getConnectedNodes('downstream', mainNode, lineage, distances) - } catch (error) { - reject(error) - } - - resolve(distances) - }) -} - -function getConnectedNodes( - direction: Direction = 'downstream', - node: string, - lineage: Record = {}, - result: Record = {}, -): void { - const isDownstream = direction === 'downstream' - let models: string[] = [] - - if (isDownstream) { - models = Object.keys(lineage).filter(key => - lineage[key]!.models.includes(node as ModelEncodedFQN), - ) - } else { - models = lineage[node]?.models ?? [] - } - - if (isFalse(node in result)) { - result[node] = { edges: [] } - } - - for (const model of models) { - const connectedNode = isDownstream - ? createConnectedNode(node, model, [result[node]!]) - : createConnectedNode(model, node, [result[node]!]) - - if (model in result) { - result[model]!.edges.push(connectedNode) - } else { - result[model] = connectedNode - getConnectedNodes(direction, model, lineage, result) - } - } -} - -function createConnectedNode( - source: NodeId, - target: NodeId, - edges: ConnectedNode[] = [], -): ConnectedNode { - const id = toID(source, target) - return { id, edges } -} diff --git a/vscode/react/src/main.tsx b/vscode/react/src/main.tsx index 5e24fc648f..477f2ce62f 100644 --- a/vscode/react/src/main.tsx +++ b/vscode/react/src/main.tsx @@ -5,6 +5,10 @@ import { EventBusProvider } from './hooks/eventBus.tsx' import { TableDiffPage } from './pages/tablediff.tsx' import { LineagePage } from './pages/lineage.tsx' +import '@sqlmesh-common/styles/index.css' + +import './App.css' + // Detect panel type declare global { interface Window { diff --git a/vscode/react/src/pages/ModelLineage.tsx b/vscode/react/src/pages/ModelLineage.tsx new file mode 100644 index 0000000000..e5e13c05a6 --- /dev/null +++ b/vscode/react/src/pages/ModelLineage.tsx @@ -0,0 +1,443 @@ +import React from 'react' + +import { Focus, Rows2, Rows3 } from 'lucide-react' + +import { + type LineageNode, + type LineageEdge, + type LineageNodesMap, + MAX_COLUMNS_TO_DISPLAY, + ZOOM_THRESHOLD, + FactoryEdgeWithGradient, + EdgeWithGradient, + useColumnLevelLineage, + createNode, + toPortID, + calculateNodeBaseHeight, + calculateNodeDetailsHeight, + calculateSelectedColumnsHeight, + buildLayout, + calculateColumnsHeight, + calculateNodeColumnsCount, + createEdge, + getEdgesFromColumnLineage, + getOnlySelectedNodes, + getTransformedModelEdgesTargetSources, + getTransformedNodes, + toNodeID, + LineageControlButton, + LineageControlIcon, + LineageLayout, + type LineageAdjacencyList, + type LineageDetails, + type ColumnLevelLineageAdjacencyList, +} from '@sqlmesh-common/components/Lineage' + +import { + type ModelColumnName, + type EdgeData, + type ModelColumnID, + type ModelColumnLeftHandleId, + type ModelColumnRightHandleId, + type ModelEdgeId, + type ModelLineageNodeDetails, + type ModelNodeId, + type NodeData, + ModelLineageContext, +} from './ModelLineageContext' +import { ModelNode } from './ModelNode' +import { useModelLineage } from './ModelLineageContext' +import type { ModelFQN } from '@/domain/models' +import { NODE_TYPE_COLOR_VAR } from './help' + +const nodeTypes = { + node: ModelNode, +} +const edgeTypes = { + edge: FactoryEdgeWithGradient(useModelLineage), + port: EdgeWithGradient, +} + +export const ModelLineage = ({ + selectedModelName, + adjacencyList, + lineageDetails, + className, + onNodeClick, +}: { + adjacencyList: LineageAdjacencyList + lineageDetails: LineageDetails + selectedModelName?: ModelFQN + className?: string + onNodeClick?: ( + event: React.MouseEvent, + node: LineageNode, + ) => void +}) => { + const currentNodeId = selectedModelName + ? toNodeID(selectedModelName) + : null + + // Store all nodes to keep track of the position and to reuse nodes + const allNodesMap = React.useRef>({}) + + const [zoom, setZoom] = React.useState(ZOOM_THRESHOLD) + const [isBuildingLayout, setIsBuildingLayout] = React.useState(false) + const [edges, setEdges] = React.useState< + LineageEdge< + EdgeData, + ModelEdgeId, + ModelNodeId, + ModelNodeId, + ModelColumnRightHandleId, + ModelColumnLeftHandleId + >[] + >([]) + const [nodesMap, setNodesMap] = React.useState< + LineageNodesMap + >({}) + const [showOnlySelectedNodes, setShowOnlySelectedNodes] = + React.useState(false) + const [selectedNodes, setSelectedNodes] = React.useState>( + new Set(), + ) + const [selectedEdges, setSelectedEdges] = React.useState>( + new Set(), + ) + const [selectedNodeId, setSelectedNodeId] = + React.useState(currentNodeId) + + const [showColumns, setShowColumns] = React.useState(false) + const [columnLevelLineage, setColumnLevelLineage] = React.useState< + Map< + ModelColumnID, + ColumnLevelLineageAdjacencyList + > + >(new Map()) + const [fetchingColumns, setFetchingColumns] = React.useState< + Set + >(new Set()) + + const { + adjacencyListColumnLevel, + selectedColumns, + adjacencyListKeysColumnLevel, + } = useColumnLevelLineage< + ModelFQN, + ModelColumnName, + ModelColumnID, + ColumnLevelLineageAdjacencyList + >(columnLevelLineage) + + const adjacencyListKeys = React.useMemo(() => { + let keys: ModelFQN[] = [] + + if (adjacencyListKeysColumnLevel.length > 0) { + keys = adjacencyListKeysColumnLevel + } else { + keys = Object.keys(adjacencyList) as ModelFQN[] + } + + return keys + }, [adjacencyListKeysColumnLevel, adjacencyList]) + + const transformNode = React.useCallback( + (nodeId: ModelNodeId, detail: ModelLineageNodeDetails) => { + const columns = detail.columns + + const node = createNode('node', nodeId, { + name: detail.name, + displayName: detail.display_name, + model_type: detail.model_type, + identifier: detail.identifier, + kind: detail.kind, + cron: detail.cron, + owner: detail.owner, + dialect: detail.dialect, + version: detail.version, + tags: detail.tags || [], + columns, + }) + + const selectedColumnsCount = new Set( + Object.keys(columns ?? {}).map(k => toPortID(detail.name, k)), + ).intersection(selectedColumns).size + // We are trying to project the node hight so we are including the ceiling and floor heights + const nodeBaseHeight = calculateNodeBaseHeight({ + includeNodeFooterHeight: false, + includeCeilingHeight: true, + includeFloorHeight: false, + }) + const nodeDetailsHeight = calculateNodeDetailsHeight({ + nodeDetailsCount: 0, + }) + const selectedColumnsHeight = + calculateSelectedColumnsHeight(selectedColumnsCount) + + const columnsHeight = calculateColumnsHeight({ + columnsCount: calculateNodeColumnsCount( + Object.keys(columns ?? {}).length, + ), + hasColumnsFilter: + Object.keys(columns ?? {}).length > MAX_COLUMNS_TO_DISPLAY, + }) + + node.height = + nodeBaseHeight + + nodeDetailsHeight + + selectedColumnsHeight + + columnsHeight + + return node + }, + [selectedColumns], + ) + + const transformedNodesMap = React.useMemo(() => { + return getTransformedNodes< + ModelFQN, + ModelLineageNodeDetails, + NodeData, + ModelNodeId + >(adjacencyListKeys, lineageDetails, transformNode, allNodesMap.current) + }, [adjacencyListKeys, lineageDetails, transformNode]) + + const transformEdge = React.useCallback( + ( + edgeType: string, + edgeId: ModelEdgeId, + sourceId: ModelNodeId, + targetId: ModelNodeId, + sourceHandleId?: ModelColumnRightHandleId, + targetHandleId?: ModelColumnLeftHandleId, + ) => { + const sourceNode = transformedNodesMap[sourceId] + const targetNode = transformedNodesMap[targetId] + const data: EdgeData = {} + + if (sourceHandleId) { + data.startColor = 'var(--color-lineage-node-port-edge-source)' + } else { + if (sourceNode?.data?.model_type) { + data.startColor = NODE_TYPE_COLOR_VAR[sourceNode.data.model_type] + } + } + + if (targetHandleId) { + data.endColor = 'var(--color-lineage-node-port-edge-target)' + } else { + if (targetNode?.data?.model_type) { + data.endColor = NODE_TYPE_COLOR_VAR[targetNode.data.model_type] + } + } + + if (sourceHandleId && targetHandleId) { + data.strokeWidth = 2 + } + + return createEdge< + EdgeData, + ModelEdgeId, + ModelNodeId, + ModelNodeId, + ModelColumnRightHandleId, + ModelColumnLeftHandleId + >( + edgeType, + edgeId, + sourceId, + targetId, + sourceHandleId, + targetHandleId, + data, + ) + }, + [transformedNodesMap], + ) + + const edgesColumnLevel = React.useMemo( + () => + getEdgesFromColumnLineage< + ModelFQN, + ModelColumnName, + EdgeData, + ModelEdgeId, + ModelNodeId, + ModelNodeId, + ModelColumnRightHandleId, + ModelColumnLeftHandleId, + ColumnLevelLineageAdjacencyList + >({ + columnLineage: adjacencyListColumnLevel, + transformEdge, + }), + [adjacencyListColumnLevel, transformEdge], + ) + + const transformedEdges = React.useMemo(() => { + return edgesColumnLevel.length > 0 + ? edgesColumnLevel + : getTransformedModelEdgesTargetSources< + ModelFQN, + EdgeData, + ModelEdgeId, + ModelNodeId, + ModelNodeId, + ModelColumnRightHandleId, + ModelColumnLeftHandleId + >(adjacencyListKeys, adjacencyList, transformEdge) + }, [adjacencyListKeys, adjacencyList, transformEdge, edgesColumnLevel]) + + const calculateLayout = React.useCallback( + ( + eds: LineageEdge< + EdgeData, + ModelEdgeId, + ModelNodeId, + ModelNodeId, + ModelColumnRightHandleId, + ModelColumnLeftHandleId + >[], + nds: LineageNodesMap, + buildingLayoutId: NodeJS.Timeout, + ) => { + clearTimeout(buildingLayoutId) + + const layoutNodesMap = buildLayout< + NodeData, + EdgeData, + ModelEdgeId, + ModelNodeId, + ModelNodeId, + ModelColumnRightHandleId, + ModelColumnLeftHandleId + >({ edges: eds, nodesMap: nds }) + + allNodesMap.current = { ...allNodesMap.current, ...layoutNodesMap } + + setEdges(eds) + setNodesMap(layoutNodesMap) + setIsBuildingLayout(false) + }, + [allNodesMap.current], + ) + + const nodes = React.useMemo(() => { + return Object.values(nodesMap) + }, [nodesMap]) + + const selectedNode = selectedNodeId + ? allNodesMap.current[selectedNodeId] + : null + + const handleReset = React.useCallback(() => { + setShowColumns(false) + setEdges([]) + setNodesMap({}) + setShowOnlySelectedNodes(false) + setSelectedNodes(new Set()) + setSelectedEdges(new Set()) + setSelectedNodeId(currentNodeId) + setColumnLevelLineage(new Map()) + }, [currentNodeId]) + + React.useEffect(() => { + const buildingLayoutId = setTimeout(() => { + setIsBuildingLayout(true) + }, 500) + + if (showOnlySelectedNodes) { + const onlySelectedNodesMap = getOnlySelectedNodes( + transformedNodesMap, + selectedNodes, + ) + const onlySelectedEdges = transformedEdges.filter(edge => + selectedEdges.has(edge.id as ModelEdgeId), + ) + calculateLayout(onlySelectedEdges, onlySelectedNodesMap, buildingLayoutId) + } else { + calculateLayout(transformedEdges, transformedNodesMap, buildingLayoutId) + } + }, [showOnlySelectedNodes, transformedEdges, transformedNodesMap]) + + React.useEffect(() => { + handleReset() + }, [handleReset]) + + function toggleColumns() { + setShowColumns(prev => !prev) + } + + return ( + + + useLineage={useModelLineage} + nodeTypes={nodeTypes} + edgeTypes={edgeTypes} + className={className} + onNodeClick={onNodeClick} + isBuildingLayout={isBuildingLayout} + showControlOnlySelectedNodes={selectedColumns.size === 0} + controls={ + <> + toggleColumns()} + disabled={isBuildingLayout} + > + {showColumns ? ( + + ) : ( + + )} + + handleReset()} + disabled={isBuildingLayout} + > + + + + } + /> + + ) +} diff --git a/vscode/react/src/pages/ModelLineageContext.ts b/vscode/react/src/pages/ModelLineageContext.ts new file mode 100644 index 0000000000..850fe42b95 --- /dev/null +++ b/vscode/react/src/pages/ModelLineageContext.ts @@ -0,0 +1,107 @@ +import type { ModelType } from '@/api/client' +import type { ModelFQN, ModelName } from '@/domain/models' +import type { Branded } from '@bus/brand' +import { + type Column, + type ColumnLevelLineageContextValue, + type LineageContextValue, + type PathType, + getInitial as getLineageContextInitial, + getColumnLevelLineageContextInitial, + createLineageContext, + type ColumnLevelLineageAdjacencyList, +} from '@sqlmesh-common/components/Lineage' + +export type ModelColumnName = Branded +export type ModelColumnID = Branded +export type ModelNodeId = Branded +export type ModelEdgeId = Branded +export type ModelColumn = Column & { + id: ModelColumnID + name: ModelColumnName +} +export type ModelColumnLeftHandleId = Branded +export type ModelColumnRightHandleId = Branded< + string, + 'ModelColumnRightHandleId' +> + +export type ModelLineageNodeDetails = { + name: ModelFQN + display_name: ModelName + model_type: ModelType + identifier?: string | null + version?: string | null + dialect?: string | null + cron?: string | null + owner?: string | null + kind?: string | null + tags?: string[] + columns?: Record +} + +export type NodeData = { + name: ModelFQN + displayName: ModelName + model_type: ModelType + identifier?: string | null + version?: string | null + kind?: string | null + cron?: string | null + owner?: string | null + dialect?: string | null + tags?: string[] + columns?: Record +} + +export type EdgeData = { + pathType?: PathType + startColor?: string + endColor?: string + strokeWidth?: number +} + +export type ModelLineageContextValue = ColumnLevelLineageContextValue< + ModelFQN, + ModelColumnName, + ModelColumnID, + ColumnLevelLineageAdjacencyList +> & + LineageContextValue< + NodeData, + EdgeData, + ModelNodeId, + ModelEdgeId, + ModelNodeId, + ModelNodeId, + ModelColumnRightHandleId, + ModelColumnLeftHandleId + > + +export const initial = { + ...getLineageContextInitial(), + ...getColumnLevelLineageContextInitial< + ModelFQN, + ModelColumnName, + ModelColumnID, + ColumnLevelLineageAdjacencyList + >(), +} + +export const { Provider, useLineage } = createLineageContext< + NodeData, + EdgeData, + ModelNodeId, + ModelEdgeId, + ModelNodeId, + ModelNodeId, + ModelColumnRightHandleId, + ModelColumnLeftHandleId, + ModelLineageContextValue +>(initial) + +export const ModelLineageContext = { + Provider, +} + +export const useModelLineage = useLineage diff --git a/vscode/react/src/pages/ModelNode.tsx b/vscode/react/src/pages/ModelNode.tsx new file mode 100644 index 0000000000..eb5fc18588 --- /dev/null +++ b/vscode/react/src/pages/ModelNode.tsx @@ -0,0 +1,277 @@ +import cronstrue from 'cronstrue' +import React from 'react' + +import { + useModelLineage, + type ModelNodeId, + type NodeData, + type ModelColumn, + type ModelColumnID, + type ModelColumnName, +} from './ModelLineageContext' +import { + calculateColumnsHeight, + calculateNodeBaseHeight, + calculateNodeColumnsCount, + calculateNodeDetailsHeight, + calculateSelectedColumnsHeight, + MAX_COLUMNS_TO_DISPLAY, + useColumns, + useNodeMetadata, + ZOOM_THRESHOLD, + NodeContainer, + NodeBase, + NodeHandleIcon, + NodeHandles, + NodeHeader, + type Column, + NodeAppendix, + NodeBadge, + NodePorts, + type NodeProps, +} from '@sqlmesh-common/components/Lineage' +import { + Badge, + cn, + HorizontalContainer, + ModelName, + Tooltip, + VerticalContainer, +} from '@sqlmesh-common/index' +import { ModelNodeColumn } from './ModelNodeColumn' +import type { ModelFQN } from '@/domain/models' +import { NODE_TYPE_COLOR } from './help' +import type { ModelType } from '@/api/client' + +export const ModelNode = React.memo(function ModelNode({ + id, + data, + ...props +}: NodeProps) { + const { + selectedColumns, + zoom, + currentNodeId, + selectedNodeId, + selectedNodes, + showColumns, + fetchingColumns, + } = useModelLineage() + + const [showNodeColumns, setShowNodeColumns] = React.useState(showColumns) + const [isHovered, setIsHovered] = React.useState(false) + + const nodeId = id as ModelNodeId + + const { + leftId, + rightId, + isCurrent, + isSelected, // if selected from inside the lineage and node is selcted + isActive, // if selected from inside the lineage and node is not selected but in path + } = useNodeMetadata(nodeId, currentNodeId, selectedNodeId, selectedNodes) + + const { + columns, + selectedColumns: modelSelectedColumns, + columnNames, + } = useColumns( + selectedColumns, + data.name, + data.columns, + ) + + const hasSelectedColumns = selectedColumns.intersection(columnNames).size > 0 + const hasFetchingColumns = fetchingColumns.intersection(columnNames).size > 0 + + React.useEffect(() => { + setShowNodeColumns(showColumns || isSelected) + }, [columnNames, isSelected, showColumns]) + + const shouldShowColumns = + showNodeColumns || hasSelectedColumns || hasFetchingColumns || isHovered + const modelType = data.model_type?.toLowerCase() as ModelType + const hasColumnsFilter = + shouldShowColumns && columns.length > MAX_COLUMNS_TO_DISPLAY + + // We are not including the footer, because we need actual height to dynamically adjust node container height + const nodeBaseHeight = calculateNodeBaseHeight({ + includeNodeFooterHeight: false, + includeCeilingHeight: false, + includeFloorHeight: false, + }) + const nodeDetailsHeight = + zoom > ZOOM_THRESHOLD + ? calculateNodeDetailsHeight({ + nodeDetailsCount: 0, + }) + : 0 + const selectedColumnsHeight = calculateSelectedColumnsHeight( + modelSelectedColumns.length, + ) + const columnsHeight = + zoom > ZOOM_THRESHOLD && shouldShowColumns + ? calculateColumnsHeight({ + columnsCount: calculateNodeColumnsCount(columns.length), + hasColumnsFilter, + }) + : 0 + + // If zoom is less than ZOOM_THRESHOLD, we are making node looks bigger + const nodeHeight = + (zoom > ZOOM_THRESHOLD ? nodeBaseHeight : nodeBaseHeight * 2) + + nodeDetailsHeight + + selectedColumnsHeight + + columnsHeight + + return ( + + + + {modelType && ( + ZOOM_THRESHOLD ? '2xs' : 'm'} + className={cn( + 'text-[white] font-black', + NODE_TYPE_COLOR[modelType], + )} + > + {modelType.toUpperCase()} + + )} + {zoom > ZOOM_THRESHOLD && ( + <> + {data.kind && {data.kind.toUpperCase()}} + {data.cron && ( + + {data.cron} + + } + className="text-xs p-2 rounded-md font-semibold" + > + + UTC Time + {cronstrue.toString(data.cron, { + dayOfWeekStartIndexZero: true, + use24HourTimeFormat: true, + verbose: true, + })} + + + )} + + )} + + + + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + ZOOM_THRESHOLD ? 'shrink-0 h-7' : 'h-full')} + > + } + rightIcon={} + handleClassName="top-4" + > + + ZOOM_THRESHOLD + ? ' text-xs' + : 'text-2xl justify-center', + )} + /> + + + + {shouldShowColumns && ( + <> + {modelSelectedColumns.length > 0 && ( + + {modelSelectedColumns.map(column => ( + + ))} + + )} + {columns.length > 0 && zoom > ZOOM_THRESHOLD && ( + + ports={columns} + estimatedListItemHeight={24} + isFilterable={hasColumnsFilter} + filterOptions={{ + keys: ['name', 'description'], + threshold: 0.3, + }} + renderPort={column => ( + + )} + className="border-t border-lineage-divider" + /> + )} + + )} + + + + ) +}) diff --git a/vscode/react/src/pages/ModelNodeColumn.tsx b/vscode/react/src/pages/ModelNodeColumn.tsx new file mode 100644 index 0000000000..256143fd47 --- /dev/null +++ b/vscode/react/src/pages/ModelNodeColumn.tsx @@ -0,0 +1,120 @@ +import React from 'react' + +import { + FactoryColumn, + type ColumnLevelLineageAdjacencyList, +} from '@sqlmesh-common/components/Lineage' +import { cn } from '@sqlmesh-common/utils' +import { + useModelLineage, + type ModelColumnID, + type ModelNodeId, + type ModelColumnName, + type ModelColumnRightHandleId, + type ModelColumnLeftHandleId, +} from './ModelLineageContext' +import { useApiColumnLineage } from '@/api/index' +import type { ModelFQN } from '@/domain/models' + +const ModelColumn = FactoryColumn< + ModelFQN, + ModelColumnName, + ModelNodeId, + ModelColumnID, + ModelColumnLeftHandleId, + ModelColumnRightHandleId, + ColumnLevelLineageAdjacencyList +>(useModelLineage) + +export const ModelNodeColumn = React.memo(function ModelNodeColumn({ + id, + nodeId, + modelName, + name, + description, + type, + className, +}: { + id: ModelColumnID + nodeId: ModelNodeId + modelName: ModelFQN + name: ModelColumnName + type: string + description?: string | null + className?: string +}) { + const { selectedColumns, setColumnLevelLineage, setFetchingColumns } = + useModelLineage() + + const isSelectedColumn = selectedColumns.has(id) + + const { + refetch: getColumnLineage, + isFetching: isColumnLineageFetching, + error: columnLineageError, + } = useApiColumnLineage(nodeId, name, { models_only: true }) + + const [columnLineageData, setColumnLineageData] = React.useState< + ColumnLevelLineageAdjacencyList | undefined + >(undefined) + + const toggleSelectedColumn = React.useCallback(async () => { + if (isSelectedColumn) { + setColumnLevelLineage(prev => { + prev.delete(id) + return new Map(prev) + }) + } else { + if (columnLineageData == null) { + setTimeout(() => { + setFetchingColumns(prev => new Set(prev.add(id))) + }) + + const { data } = (await getColumnLineage()) as { + data: + | ColumnLevelLineageAdjacencyList + | undefined + } + + setColumnLineageData(data) + + setFetchingColumns(prev => { + prev.delete(id) + return new Set(prev) + }) + + if (data != null) { + setColumnLevelLineage(prev => new Map(prev).set(id, data)) + } + } + } + }, [ + isSelectedColumn, + id, + setColumnLevelLineage, + columnLineageData, + setFetchingColumns, + getColumnLineage, + ]) + + return ( +
Error: {error.message}
} + /> + ) +}) diff --git a/vscode/react/src/pages/help.ts b/vscode/react/src/pages/help.ts new file mode 100644 index 0000000000..410d97ed07 --- /dev/null +++ b/vscode/react/src/pages/help.ts @@ -0,0 +1,32 @@ +import type { ModelType } from '@/api/client' + +type NodeType = ModelType + +export const NODE_TYPE_COLOR_VAR: Record = { + sql: 'var(--color-lineage-node-type-background-sql)', + python: 'var(--color-lineage-node-type-background-python)', + source: 'var(--color-lineage-node-type-background-source)', + seed: 'var(--color-lineage-node-type-background-source)', + external: 'var(--color-lineage-node-type-background-source)', +} +export const NODE_TYPE_COLOR: Record = { + sql: 'bg-lineage-node-type-background-sql', + python: 'bg-lineage-node-type-background-python', + source: 'bg-lineage-node-type-background-source', + seed: 'bg-lineage-node-type-background-source', + external: 'bg-lineage-node-type-background-source', +} +export const NODE_TYPE_TEXT_COLOR: Record = { + sql: 'text-lineage-node-type-foreground-sql', + python: 'text-lineage-node-type-foreground-python', + source: 'text-lineage-node-type-foreground-source', + seed: 'text-lineage-node-type-foreground-source', + external: 'text-lineage-node-type-foreground-source', +} +export const NODE_TYPE_BORDER_COLOR: Record = { + sql: 'border-lineage-node-type-border-sql', + python: 'border-lineage-node-type-border-python', + source: 'border-lineage-node-type-border-source', + seed: 'border-lineage-node-type-border-source', + external: 'border-lineage-node-type-border-source', +} diff --git a/vscode/react/src/pages/lineage.tsx b/vscode/react/src/pages/lineage.tsx index 18925f28da..637af22b33 100644 --- a/vscode/react/src/pages/lineage.tsx +++ b/vscode/react/src/pages/lineage.tsx @@ -1,14 +1,10 @@ -import '../App.css' import { QueryCache, QueryClient, QueryClientProvider, useQueryClient, } from '@tanstack/react-query' -import { useApiModels } from '@/api' -import LineageFlowProvider from '@/components/graph/context' -import { ModelLineage } from '@/components/graph/ModelLineage' -import { useVSCode } from '@/hooks/vscode' +import { useApiModelLineage, useApiModels } from '@/api' import React, { useState } from 'react' import { ModelSQLMeshModel } from '@/domain/sqlmesh-model' import { useEventBus } from '@/hooks/eventBus' @@ -21,8 +17,24 @@ import { type ModelFullPath, type ModelName, type ModelEncodedFQN, + type ModelFQN, } from '@/domain/models' +import { ModelLineage } from './ModelLineage' +import type { + ModelColumnName, + ModelLineageNodeDetails, + ModelNodeId, + NodeData, +} from './ModelLineageContext' +import type { + Column, + LineageAdjacencyList, + LineageDetails, + LineageNode, +} from '@sqlmesh-common/components/Lineage' +import { useVSCode } from '@/hooks/vscode' + export function LineagePage() { const { emit } = useEventBus() @@ -73,7 +85,7 @@ export function LineagePage() { } function Lineage() { - const [selectedModel, setSelectedModel] = useState( + const [selectedModel, setSelectedModel] = useState( undefined, ) const { on } = useEventBus() @@ -88,14 +100,14 @@ function Lineage() { React.useEffect(() => { const fetchFirstTimeModelIfNotSet = async ( models: Model[], - ): Promise => { + ): Promise => { if (!Array.isArray(models)) { return undefined } const activeFile = await rpc('get_active_file', {}) // @ts-ignore if (!activeFile.fileUri) { - return models[0].name + return models[0].fqn as ModelFQN } // @ts-ignore const fileUri: string = activeFile.fileUri @@ -107,7 +119,7 @@ function Lineage() { return URI.file(m.full_path).path === filePath }) if (model) { - return model.name + return model.fqn as ModelFQN } return undefined } @@ -116,7 +128,7 @@ function Lineage() { if (modelName && selectedModel === undefined) { setSelectedModel(modelName) } else { - setSelectedModel(models[0].name) + setSelectedModel(models[0].fqn as ModelFQN) } }) } @@ -126,20 +138,20 @@ function Lineage() { Array.isArray(models) && models.reduce( (acc, model) => { - acc[model.name] = model + acc[model.fqn as ModelFQN] = model return acc }, - {} as Record, + {} as Record, ) React.useEffect(() => { const handleChangeFocusedFile = (fileUri: { fileUri: string }) => { const full_path = URI.parse(fileUri.fileUri).path - const model = Object.values(modelsRecord).find( + const model: Model | undefined = Object.values(modelsRecord).find( m => URI.file(m.full_path).path === full_path, ) if (model) { - setSelectedModel(model.name) + setSelectedModel(model.fqn as ModelFQN) } } @@ -188,21 +200,10 @@ export function LineageComponentFromWeb({ selectedModel, models, }: { - selectedModel: string - models: Record -}): JSX.Element { + selectedModel: ModelFQN + models: Record +}) { const vscode = useVSCode() - function handleClickModel(id: string): void { - const decodedId = decodeURIComponent(id) - const model = Object.values(models).find(m => m.fqn === decodedId) - if (!model) { - throw new Error('Model not found') - } - if (!model.full_path) { - return - } - vscode('openFile', { uri: URI.file(model.full_path).toString() }) - } function handleError(error: any): void { console.error(error) @@ -222,17 +223,86 @@ export function LineageComponentFromWeb({ full_path: model.full_path as ModelFullPath, }) + const { refetch: getModelLineage } = useApiModelLineage(model?.name ?? '') + + const [modelLineage, setModelLineage] = useState< + LineageAdjacencyList | undefined + >(undefined) + + const handleNodeClick = React.useCallback( + ( + event: React.MouseEvent, + node: LineageNode, + ) => { + event.stopPropagation() + + const decodedId = decodeURIComponent(node.id) + const model = (Object.values(models) as Model[]).find( + m => m.fqn === decodedId, + ) + if (!model) { + throw new Error('Model not found') + } + if (!model.full_path) { + return + } + vscode('openFile', { uri: URI.file(model.full_path).toString() }) + }, + [models, vscode], + ) + + React.useEffect(() => { + if (model === undefined) return + + getModelLineage() + .then(({ data }) => { + setModelLineage(data as unknown as LineageAdjacencyList) + }) + .catch(handleError) + }, [model?.name, model?.hash]) + + const lineageDetails = (Object.values(models) as Model[]).reduce( + (acc, model) => { + const modelFQN = model.fqn as ModelFQN + acc[modelFQN] = { + name: modelFQN, + display_name: model.name as ModelName, + model_type: model.type, + identifier: undefined, + version: undefined, + dialect: model.dialect, + cron: model.details?.cron, + owner: model.details?.owner, + kind: model.details?.kind, + tags: [], + columns: model.columns.reduce( + (acc, column) => { + const columnName = decodeURI(column.name) as ModelColumnName + acc[columnName] = { + data_type: column.type, + description: column.description, + } + return acc + }, + {} as Record, + ), + } + return acc + }, + {} as LineageDetails, + ) + + if (!modelLineage || !lineageDetails) { + return null + } + return ( -
- - - -
+ ) } diff --git a/vscode/react/tailwind.config.cjs b/vscode/react/tailwind.config.cjs index c31bd9f9ec..d336abdd35 100644 --- a/vscode/react/tailwind.config.cjs +++ b/vscode/react/tailwind.config.cjs @@ -1,6 +1,11 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + presets: [require('../../web/common/tailwind.base.config.js')], + content: [ + './index.html', + './src/**/*.{js,ts,jsx,tsx}', + '../../web/common/src/**/*.{js,ts,jsx,tsx}', + ], darkMode: ['class', '[mode="dark"]'], theme: { colors: { @@ -177,6 +182,37 @@ module.exports = { 800: 'var(--color-warning-800)', 900: 'var(--color-warning-900)', }, + lineage: { + node: { + current: { + background: 'var(--color-lineage-node-current-background)', + foreground: 'var(--color-lineage-node-current-foreground)', + }, + type: { + background: { + sql: 'var(--color-lineage-node-type-background-sql)', + python: 'var(--color-lineage-node-type-background-python)', + 'cte-subquery': + 'var(--color-lineage-node-type-background-cte-subquery)', + source: 'var(--color-lineage-node-type-background-source)', + }, + foreground: { + sql: 'var(--color-lineage-node-type-foreground-sql)', + python: 'var(--color-lineage-node-type-foreground-python)', + 'cte-subquery': + 'var(--color-lineage-node-type-foreground-cte-subquery)', + source: 'var(--color-lineage-node-type-foreground-source)', + }, + border: { + sql: 'var(--color-lineage-node-type-border-sql)', + python: 'var(--color-lineage-node-type-border-python)', + 'cte-subquery': + 'var(--color-lineage-node-type-border-cte-subquery)', + source: 'var(--color-lineage-node-type-border-source)', + }, + }, + }, + }, }, fontFamily: { mono: ['JetBrains Mono', 'monospace'], diff --git a/vscode/react/tsconfig.json b/vscode/react/tsconfig.json index b57d3316b0..0c6439b9c4 100644 --- a/vscode/react/tsconfig.json +++ b/vscode/react/tsconfig.json @@ -4,7 +4,7 @@ "target": "ES2022", "jsx": "react-jsx", "module": "ESNext", - "lib": ["ES2022", "DOM", "DOM.Iterable"], + "lib": ["ESNext", "DOM", "DOM.Iterable"], "types": ["vite/client", "react", "react-dom"], /* Bundler mode */ @@ -23,7 +23,8 @@ "baseUrl": ".", "paths": { "@/*": ["./src/*"], - "@bus/*": ["../bus/src/*"] + "@bus/*": ["../bus/src/*"], + "@sqlmesh-common/*": ["../../web/common/src/*"] } } } diff --git a/vscode/react/vite.config.js b/vscode/react/vite.config.js index 84568f6cdd..d0d38c66d8 100644 --- a/vscode/react/vite.config.js +++ b/vscode/react/vite.config.js @@ -2,15 +2,10 @@ import { defineConfig } from 'vite' import viteReact from '@vitejs/plugin-react' import { TanStackRouterVite } from '@tanstack/router-plugin/vite' import { resolve } from 'node:path' -import tailwindcss from '@tailwindcss/vite' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [ - TanStackRouterVite({ autoCodeSplitting: false }), - viteReact(), - tailwindcss(), - ], + plugins: [TanStackRouterVite({ autoCodeSplitting: false }), viteReact()], test: { globals: true, environment: 'jsdom', @@ -21,6 +16,7 @@ export default defineConfig({ alias: { '@': resolve(__dirname, './src'), '@bus': resolve(__dirname, '../bus/src'), + '@sqlmesh-common': resolve(__dirname, '../../web/common/src'), }, },