@datarecce/ui 0.1.30 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (549) hide show
  1. package/dist/api.d.mts +1 -1
  2. package/dist/api.d.ts +1 -1
  3. package/dist/components.d.mts +1 -1
  4. package/dist/components.d.ts +1 -1
  5. package/dist/hooks.d.mts +1 -1
  6. package/dist/hooks.d.ts +1 -1
  7. package/dist/{index-BNUP2V_N.d.ts → index-B9lSPJTi.d.ts} +184 -2
  8. package/dist/index-B9lSPJTi.d.ts.map +1 -0
  9. package/dist/{index-DOPZuhD8.d.mts → index-IIXVIoOL.d.mts} +253 -71
  10. package/dist/index-IIXVIoOL.d.mts.map +1 -0
  11. package/dist/index.d.mts +2 -2
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +8 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.mjs +3 -2
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/styles.css +4 -0
  18. package/dist/theme.d.mts +2 -185
  19. package/dist/theme.d.ts +2 -185
  20. package/dist/types.d.mts +1 -1
  21. package/dist/types.d.ts +1 -1
  22. package/package.json +4 -2
  23. package/recce-source/.editorconfig +26 -0
  24. package/recce-source/.flake8 +37 -0
  25. package/recce-source/.github/ISSUE_TEMPLATE/bug_report.yml +67 -0
  26. package/recce-source/.github/ISSUE_TEMPLATE/custom.md +10 -0
  27. package/recce-source/.github/ISSUE_TEMPLATE/feature_request.yml +42 -0
  28. package/recce-source/.github/PULL_REQUEST_TEMPLATE.md +21 -0
  29. package/recce-source/.github/copilot-instructions.md +331 -0
  30. package/recce-source/.github/instructions/backend-instructions.md +541 -0
  31. package/recce-source/.github/instructions/frontend-instructions.md +317 -0
  32. package/recce-source/.github/workflows/build-statics.yaml +72 -0
  33. package/recce-source/.github/workflows/bump.yaml +48 -0
  34. package/recce-source/.github/workflows/integration-tests-cloud.yaml +92 -0
  35. package/recce-source/.github/workflows/integration-tests-sqlmesh.yaml +33 -0
  36. package/recce-source/.github/workflows/integration-tests.yaml +52 -0
  37. package/recce-source/.github/workflows/nightly.yaml +246 -0
  38. package/recce-source/.github/workflows/release.yaml +196 -0
  39. package/recce-source/.github/workflows/tests-js.yaml +58 -0
  40. package/recce-source/.github/workflows/tests-python.yaml +128 -0
  41. package/recce-source/.pre-commit-config.yaml +26 -0
  42. package/recce-source/CLAUDE.md +483 -0
  43. package/recce-source/CODE_OF_CONDUCT.md +128 -0
  44. package/recce-source/CONTRIBUTING.md +107 -0
  45. package/recce-source/LICENSE +201 -0
  46. package/recce-source/Makefile +126 -0
  47. package/recce-source/README.md +182 -0
  48. package/recce-source/RECCE_CLOUD.md +81 -0
  49. package/recce-source/SECURITY.md +25 -0
  50. package/recce-source/docs/PACKAGING.md +340 -0
  51. package/recce-source/docs/README.md +1 -0
  52. package/recce-source/integration_tests/dbt/dbt_project.yml +26 -0
  53. package/recce-source/integration_tests/dbt/models/customers.sql +69 -0
  54. package/recce-source/integration_tests/dbt/models/docs.md +14 -0
  55. package/recce-source/integration_tests/dbt/models/orders.sql +56 -0
  56. package/recce-source/integration_tests/dbt/models/schema.yml +82 -0
  57. package/recce-source/integration_tests/dbt/models/staging/schema.yml +31 -0
  58. package/recce-source/integration_tests/dbt/models/staging/stg_customers.sql +22 -0
  59. package/recce-source/integration_tests/dbt/models/staging/stg_orders.sql +23 -0
  60. package/recce-source/integration_tests/dbt/models/staging/stg_payments.sql +25 -0
  61. package/recce-source/integration_tests/dbt/packages.yml +7 -0
  62. package/recce-source/integration_tests/dbt/profiles.yml +8 -0
  63. package/recce-source/integration_tests/dbt/seeds/raw_customers.csv +101 -0
  64. package/recce-source/integration_tests/dbt/seeds/raw_orders.csv +100 -0
  65. package/recce-source/integration_tests/dbt/seeds/raw_payments.csv +114 -0
  66. package/recce-source/integration_tests/dbt/seeds/raw_statuses.csv +5 -0
  67. package/recce-source/integration_tests/dbt/smoke_test.sh +72 -0
  68. package/recce-source/integration_tests/dbt/smoke_test_cloud.sh +71 -0
  69. package/recce-source/integration_tests/sqlmesh/__init__.py +0 -0
  70. package/recce-source/integration_tests/sqlmesh/audits/assert_item_price_above_zero.sql +9 -0
  71. package/recce-source/integration_tests/sqlmesh/audits/items.sql +7 -0
  72. package/recce-source/integration_tests/sqlmesh/audits/order_items.sql +7 -0
  73. package/recce-source/integration_tests/sqlmesh/config.py +171 -0
  74. package/recce-source/integration_tests/sqlmesh/helper.py +20 -0
  75. package/recce-source/integration_tests/sqlmesh/hooks/__init__.py +0 -0
  76. package/recce-source/integration_tests/sqlmesh/macros/__init__.py +0 -0
  77. package/recce-source/integration_tests/sqlmesh/macros/macros.py +8 -0
  78. package/recce-source/integration_tests/sqlmesh/macros/macros.sql +8 -0
  79. package/recce-source/integration_tests/sqlmesh/macros/utils.py +11 -0
  80. package/recce-source/integration_tests/sqlmesh/metrics/metrics.sql +25 -0
  81. package/recce-source/integration_tests/sqlmesh/models/customer_revenue_by_day.sql +41 -0
  82. package/recce-source/integration_tests/sqlmesh/models/customer_revenue_lifetime.sql +60 -0
  83. package/recce-source/integration_tests/sqlmesh/models/customers.sql +32 -0
  84. package/recce-source/integration_tests/sqlmesh/models/items.py +95 -0
  85. package/recce-source/integration_tests/sqlmesh/models/marketing.sql +15 -0
  86. package/recce-source/integration_tests/sqlmesh/models/order_items.py +95 -0
  87. package/recce-source/integration_tests/sqlmesh/models/orders.py +70 -0
  88. package/recce-source/integration_tests/sqlmesh/models/raw_marketing.py +62 -0
  89. package/recce-source/integration_tests/sqlmesh/models/top_waiters.sql +23 -0
  90. package/recce-source/integration_tests/sqlmesh/models/waiter_as_customer_by_day.sql +29 -0
  91. package/recce-source/integration_tests/sqlmesh/models/waiter_names.sql +10 -0
  92. package/recce-source/integration_tests/sqlmesh/models/waiter_revenue_by_day.sql +29 -0
  93. package/recce-source/integration_tests/sqlmesh/models/waiters.py +62 -0
  94. package/recce-source/integration_tests/sqlmesh/prep_env.sh +16 -0
  95. package/recce-source/integration_tests/sqlmesh/schema.yaml +5 -0
  96. package/recce-source/integration_tests/sqlmesh/seeds/waiter_names.csv +11 -0
  97. package/recce-source/integration_tests/sqlmesh/test_server.sh +29 -0
  98. package/recce-source/integration_tests/sqlmesh/tests/test_customer_revenue_by_day.yaml +63 -0
  99. package/recce-source/integration_tests/sqlmesh/tests/test_order_items.yaml +72 -0
  100. package/recce-source/js/.editorconfig +27 -0
  101. package/recce-source/js/.env.development +5 -0
  102. package/recce-source/js/.husky/pre-commit +29 -0
  103. package/recce-source/js/.nvmrc +1 -0
  104. package/recce-source/js/README.md +39 -0
  105. package/recce-source/js/app/(mainComponents)/DisplayModeToggle.tsx +65 -0
  106. package/recce-source/js/app/(mainComponents)/NavBar.tsx +228 -0
  107. package/recce-source/js/app/(mainComponents)/RecceVersionBadge.tsx +107 -0
  108. package/recce-source/js/app/(mainComponents)/TopBar.tsx +252 -0
  109. package/recce-source/js/app/@lineage/default.tsx +20 -0
  110. package/recce-source/js/app/@lineage/page.tsx +14 -0
  111. package/recce-source/js/app/MainLayout.tsx +170 -0
  112. package/recce-source/js/app/Providers.tsx +49 -0
  113. package/recce-source/js/app/checks/page.tsx +296 -0
  114. package/recce-source/js/app/error.tsx +93 -0
  115. package/recce-source/js/app/favicon.ico +0 -0
  116. package/recce-source/js/app/global-error.tsx +115 -0
  117. package/recce-source/js/app/global.css +82 -0
  118. package/recce-source/js/app/layout.tsx +48 -0
  119. package/recce-source/js/app/lineage/page.tsx +15 -0
  120. package/recce-source/js/app/page.tsx +12 -0
  121. package/recce-source/js/app/query/page.tsx +8 -0
  122. package/recce-source/js/biome.json +313 -0
  123. package/recce-source/js/jest.config.js +34 -0
  124. package/recce-source/js/jest.globals.d.ts +32 -0
  125. package/recce-source/js/jest.setup.js +91 -0
  126. package/recce-source/js/next.config.js +16 -0
  127. package/recce-source/js/package-lock.json +13843 -0
  128. package/recce-source/js/package.json +123 -0
  129. package/recce-source/js/pnpm-lock.yaml +9235 -0
  130. package/recce-source/js/pnpm-workspace.yaml +6 -0
  131. package/recce-source/js/postcss.config.js +5 -0
  132. package/recce-source/js/public/auth_callback.html +68 -0
  133. package/recce-source/js/public/imgs/feedback/thumbs-down.png +0 -0
  134. package/recce-source/js/public/imgs/feedback/thumbs-up.png +0 -0
  135. package/recce-source/js/public/imgs/reload-image.svg +4 -0
  136. package/recce-source/js/public/logo/recce-logo-white.png +0 -0
  137. package/recce-source/js/src/components/AuthModal/AuthModal.tsx +202 -0
  138. package/recce-source/js/src/components/app/AvatarDropdown.tsx +159 -0
  139. package/recce-source/js/src/components/app/EnvInfo.tsx +357 -0
  140. package/recce-source/js/src/components/app/Filename.tsx +388 -0
  141. package/recce-source/js/src/components/app/SetupConnectionPopover.tsx +91 -0
  142. package/recce-source/js/src/components/app/StateExporter.tsx +57 -0
  143. package/recce-source/js/src/components/app/StateImporter.tsx +198 -0
  144. package/recce-source/js/src/components/app/StateSharing.tsx +145 -0
  145. package/recce-source/js/src/components/app/StateSynchronizer.tsx +205 -0
  146. package/recce-source/js/src/components/charts/HistogramChart.tsx +291 -0
  147. package/recce-source/js/src/components/charts/SquareIcon.tsx +51 -0
  148. package/recce-source/js/src/components/charts/TopKSummaryList.tsx +457 -0
  149. package/recce-source/js/src/components/charts/chartTheme.ts +74 -0
  150. package/recce-source/js/src/components/check/CheckBreadcrumb.tsx +97 -0
  151. package/recce-source/js/src/components/check/CheckDescription.tsx +134 -0
  152. package/recce-source/js/src/components/check/CheckDetail.tsx +797 -0
  153. package/recce-source/js/src/components/check/CheckEmptyState.tsx +84 -0
  154. package/recce-source/js/src/components/check/CheckList.tsx +320 -0
  155. package/recce-source/js/src/components/check/LineageDiffView.tsx +32 -0
  156. package/recce-source/js/src/components/check/PresetCheckTemplateView.tsx +48 -0
  157. package/recce-source/js/src/components/check/SchemaDiffView.tsx +290 -0
  158. package/recce-source/js/src/components/check/check.ts +25 -0
  159. package/recce-source/js/src/components/check/timeline/CheckTimeline.tsx +163 -0
  160. package/recce-source/js/src/components/check/timeline/CommentInput.tsx +84 -0
  161. package/recce-source/js/src/components/check/timeline/TimelineEvent.tsx +468 -0
  162. package/recce-source/js/src/components/check/timeline/index.ts +12 -0
  163. package/recce-source/js/src/components/check/utils.ts +12 -0
  164. package/recce-source/js/src/components/data-grid/ScreenshotDataGrid.tsx +333 -0
  165. package/recce-source/js/src/components/data-grid/agGridStyles.css +55 -0
  166. package/recce-source/js/src/components/data-grid/agGridTheme.ts +43 -0
  167. package/recce-source/js/src/components/editor/CodeEditor.tsx +107 -0
  168. package/recce-source/js/src/components/editor/DiffEditor.tsx +162 -0
  169. package/recce-source/js/src/components/editor/index.ts +12 -0
  170. package/recce-source/js/src/components/errorboundary/ErrorBoundary.tsx +87 -0
  171. package/recce-source/js/src/components/histogram/HistogramDiffForm.tsx +147 -0
  172. package/recce-source/js/src/components/histogram/HistogramDiffResultView.tsx +63 -0
  173. package/recce-source/js/src/components/icons/index.tsx +142 -0
  174. package/recce-source/js/src/components/lineage/ActionControl.tsx +63 -0
  175. package/recce-source/js/src/components/lineage/ActionTag.tsx +141 -0
  176. package/recce-source/js/src/components/lineage/ChangeStatusLegend.tsx +46 -0
  177. package/recce-source/js/src/components/lineage/ColumnLevelLineageControl.tsx +327 -0
  178. package/recce-source/js/src/components/lineage/ColumnLevelLineageLegend.tsx +57 -0
  179. package/recce-source/js/src/components/lineage/GraphColumnNode.tsx +199 -0
  180. package/recce-source/js/src/components/lineage/GraphEdge.tsx +59 -0
  181. package/recce-source/js/src/components/lineage/GraphNode.tsx +555 -0
  182. package/recce-source/js/src/components/lineage/LineagePage.tsx +10 -0
  183. package/recce-source/js/src/components/lineage/LineageView.tsx +1384 -0
  184. package/recce-source/js/src/components/lineage/LineageViewContext.tsx +86 -0
  185. package/recce-source/js/src/components/lineage/LineageViewContextMenu.tsx +637 -0
  186. package/recce-source/js/src/components/lineage/LineageViewNotification.tsx +64 -0
  187. package/recce-source/js/src/components/lineage/LineageViewTopBar.tsx +596 -0
  188. package/recce-source/js/src/components/lineage/NodeSqlView.tsx +136 -0
  189. package/recce-source/js/src/components/lineage/NodeTag.tsx +278 -0
  190. package/recce-source/js/src/components/lineage/NodeView.tsx +642 -0
  191. package/recce-source/js/src/components/lineage/SandboxView.tsx +436 -0
  192. package/recce-source/js/src/components/lineage/ServerDisconnectedModalContent.tsx +105 -0
  193. package/recce-source/js/src/components/lineage/SetupConnectionBanner.tsx +52 -0
  194. package/recce-source/js/src/components/lineage/SingleEnvironmentQueryView.tsx +152 -0
  195. package/recce-source/js/src/components/lineage/graph.test.ts +31 -0
  196. package/recce-source/js/src/components/lineage/graph.ts +58 -0
  197. package/recce-source/js/src/components/lineage/lineage.test.ts +169 -0
  198. package/recce-source/js/src/components/lineage/lineage.ts +521 -0
  199. package/recce-source/js/src/components/lineage/styles.css +42 -0
  200. package/recce-source/js/src/components/lineage/styles.tsx +165 -0
  201. package/recce-source/js/src/components/lineage/useMultiNodesAction.ts +352 -0
  202. package/recce-source/js/src/components/lineage/useValueDiffAlertDialog.tsx +108 -0
  203. package/recce-source/js/src/components/onboarding-guide/Notification.tsx +62 -0
  204. package/recce-source/js/src/components/profile/ProfileDiffForm.tsx +134 -0
  205. package/recce-source/js/src/components/profile/ProfileDiffResultView.tsx +245 -0
  206. package/recce-source/js/src/components/query/ChangedOnlyCheckbox.tsx +29 -0
  207. package/recce-source/js/src/components/query/DiffText.tsx +120 -0
  208. package/recce-source/js/src/components/query/QueryDiffResultView.tsx +470 -0
  209. package/recce-source/js/src/components/query/QueryForm.tsx +80 -0
  210. package/recce-source/js/src/components/query/QueryPage.tsx +282 -0
  211. package/recce-source/js/src/components/query/QueryResultView.tsx +180 -0
  212. package/recce-source/js/src/components/query/SetupConnectionGuide.tsx +57 -0
  213. package/recce-source/js/src/components/query/SqlEditor.tsx +245 -0
  214. package/recce-source/js/src/components/query/ToggleSwitch.tsx +84 -0
  215. package/recce-source/js/src/components/query/styles.css +21 -0
  216. package/recce-source/js/src/components/routing/DirectUrlAccess.test.tsx +428 -0
  217. package/recce-source/js/src/components/routing/LineageStatePreservation.test.tsx +311 -0
  218. package/recce-source/js/src/components/routing/Navigation.test.tsx +256 -0
  219. package/recce-source/js/src/components/rowcount/RowCountDiffResultView.tsx +109 -0
  220. package/recce-source/js/src/components/rowcount/delta.ts +11 -0
  221. package/recce-source/js/src/components/run/RunList.tsx +303 -0
  222. package/recce-source/js/src/components/run/RunModal.tsx +191 -0
  223. package/recce-source/js/src/components/run/RunPage.tsx +26 -0
  224. package/recce-source/js/src/components/run/RunResultPane.tsx +454 -0
  225. package/recce-source/js/src/components/run/RunStatusAndDate.tsx +106 -0
  226. package/recce-source/js/src/components/run/RunToolbar.tsx +70 -0
  227. package/recce-source/js/src/components/run/RunView.tsx +196 -0
  228. package/recce-source/js/src/components/run/registry.ts +214 -0
  229. package/recce-source/js/src/components/run/types.ts +14 -0
  230. package/recce-source/js/src/components/schema/ColumnNameCell.test.tsx +169 -0
  231. package/recce-source/js/src/components/schema/ColumnNameCell.tsx +198 -0
  232. package/recce-source/js/src/components/schema/SchemaView.tsx +337 -0
  233. package/recce-source/js/src/components/schema/schemaDiff.ts +32 -0
  234. package/recce-source/js/src/components/schema/style.css +134 -0
  235. package/recce-source/js/src/components/screenshot/ScreenshotBox.tsx +39 -0
  236. package/recce-source/js/src/components/shared/HistoryToggle.tsx +35 -0
  237. package/recce-source/js/src/components/split/Split.tsx +40 -0
  238. package/recce-source/js/src/components/split/styles.css +24 -0
  239. package/recce-source/js/src/components/summary/ChangeSummary.tsx +264 -0
  240. package/recce-source/js/src/components/summary/SchemaSummary.tsx +123 -0
  241. package/recce-source/js/src/components/summary/SummaryView.tsx +29 -0
  242. package/recce-source/js/src/components/timeout/IdleTimeoutBadge.tsx +48 -0
  243. package/recce-source/js/src/components/top-k/TopKDiffForm.tsx +58 -0
  244. package/recce-source/js/src/components/top-k/TopKDiffResultView.tsx +73 -0
  245. package/recce-source/js/src/components/ui/dataGrid/DataFrameColumnGroupHeader.tsx +228 -0
  246. package/recce-source/js/src/components/ui/dataGrid/DataFrameColumnHeader.tsx +113 -0
  247. package/recce-source/js/src/components/ui/dataGrid/defaultRenderCell.tsx +72 -0
  248. package/recce-source/js/src/components/ui/dataGrid/index.ts +23 -0
  249. package/recce-source/js/src/components/ui/dataGrid/inlineRenderCell.test.tsx +607 -0
  250. package/recce-source/js/src/components/ui/dataGrid/inlineRenderCell.tsx +211 -0
  251. package/recce-source/js/src/components/ui/dataGrid/schemaCells.test.tsx +452 -0
  252. package/recce-source/js/src/components/ui/dataGrid/schemaCells.tsx +142 -0
  253. package/recce-source/js/src/components/ui/dataGrid/valueDiffCells.test.tsx +178 -0
  254. package/recce-source/js/src/components/ui/dataGrid/valueDiffCells.tsx +275 -0
  255. package/recce-source/js/src/components/ui/markdown/ExternalLinkConfirmDialog.tsx +134 -0
  256. package/recce-source/js/src/components/ui/markdown/MarkdownContent.tsx +364 -0
  257. package/recce-source/js/src/components/ui/mui/index.ts +13 -0
  258. package/recce-source/js/src/components/ui/mui-provider.tsx +67 -0
  259. package/recce-source/js/src/components/ui/mui-theme.ts +1039 -0
  260. package/recce-source/js/src/components/ui/mui-utils.ts +113 -0
  261. package/recce-source/js/src/components/ui/toaster.tsx +288 -0
  262. package/recce-source/js/src/components/valuediff/ValueDiffDetailResultView.tsx +217 -0
  263. package/recce-source/js/src/components/valuediff/ValueDiffForm.tsx +246 -0
  264. package/recce-source/js/src/components/valuediff/ValueDiffResultView.tsx +82 -0
  265. package/recce-source/js/src/components/valuediff/shared.ts +33 -0
  266. package/recce-source/js/src/constants/tooltipMessage.ts +3 -0
  267. package/recce-source/js/src/constants/urls.ts +1 -0
  268. package/recce-source/js/src/lib/UrlHash.ts +12 -0
  269. package/recce-source/js/src/lib/api/adhocQuery.ts +70 -0
  270. package/recce-source/js/src/lib/api/axiosClient.ts +9 -0
  271. package/recce-source/js/src/lib/api/cacheKeys.ts +13 -0
  272. package/recce-source/js/src/lib/api/checkEvents.ts +252 -0
  273. package/recce-source/js/src/lib/api/checks.ts +129 -0
  274. package/recce-source/js/src/lib/api/cll.ts +53 -0
  275. package/recce-source/js/src/lib/api/connectToCloud.ts +13 -0
  276. package/recce-source/js/src/lib/api/flag.ts +37 -0
  277. package/recce-source/js/src/lib/api/info.ts +198 -0
  278. package/recce-source/js/src/lib/api/instanceInfo.ts +25 -0
  279. package/recce-source/js/src/lib/api/keepAlive.ts +108 -0
  280. package/recce-source/js/src/lib/api/lineagecheck.ts +35 -0
  281. package/recce-source/js/src/lib/api/localStorageKeys.ts +7 -0
  282. package/recce-source/js/src/lib/api/models.ts +59 -0
  283. package/recce-source/js/src/lib/api/profile.ts +65 -0
  284. package/recce-source/js/src/lib/api/rowcount.ts +19 -0
  285. package/recce-source/js/src/lib/api/runs.ts +174 -0
  286. package/recce-source/js/src/lib/api/schemacheck.ts +31 -0
  287. package/recce-source/js/src/lib/api/select.ts +25 -0
  288. package/recce-source/js/src/lib/api/sessionStorageKeys.ts +8 -0
  289. package/recce-source/js/src/lib/api/state.ts +117 -0
  290. package/recce-source/js/src/lib/api/track.ts +281 -0
  291. package/recce-source/js/src/lib/api/types.ts +284 -0
  292. package/recce-source/js/src/lib/api/user.ts +42 -0
  293. package/recce-source/js/src/lib/api/valuediff.ts +46 -0
  294. package/recce-source/js/src/lib/api/version.ts +40 -0
  295. package/recce-source/js/src/lib/const.ts +9 -0
  296. package/recce-source/js/src/lib/dataGrid/crossFunctionConsistency.test.ts +626 -0
  297. package/recce-source/js/src/lib/dataGrid/dataGridFactory.test.ts +2140 -0
  298. package/recce-source/js/src/lib/dataGrid/dataGridFactory.ts +397 -0
  299. package/recce-source/js/src/lib/dataGrid/generators/rowCountUtils.test.ts +132 -0
  300. package/recce-source/js/src/lib/dataGrid/generators/rowCountUtils.ts +126 -0
  301. package/recce-source/js/src/lib/dataGrid/generators/toDataDiffGrid.test.ts +1627 -0
  302. package/recce-source/js/src/lib/dataGrid/generators/toDataDiffGrid.ts +140 -0
  303. package/recce-source/js/src/lib/dataGrid/generators/toDataGrid.ts +67 -0
  304. package/recce-source/js/src/lib/dataGrid/generators/toRowCountDataGrid.test.ts +142 -0
  305. package/recce-source/js/src/lib/dataGrid/generators/toRowCountDataGrid.ts +71 -0
  306. package/recce-source/js/src/lib/dataGrid/generators/toRowCountDiffDataGrid.test.ts +258 -0
  307. package/recce-source/js/src/lib/dataGrid/generators/toRowCountDiffDataGrid.ts +153 -0
  308. package/recce-source/js/src/lib/dataGrid/generators/toSchemaDataGrid.test.ts +951 -0
  309. package/recce-source/js/src/lib/dataGrid/generators/toSchemaDataGrid.ts +221 -0
  310. package/recce-source/js/src/lib/dataGrid/generators/toValueDataGrid.test.ts +395 -0
  311. package/recce-source/js/src/lib/dataGrid/generators/toValueDataGrid.ts +184 -0
  312. package/recce-source/js/src/lib/dataGrid/generators/toValueDiffGrid.test.ts +884 -0
  313. package/recce-source/js/src/lib/dataGrid/generators/toValueDiffGrid.ts +113 -0
  314. package/recce-source/js/src/lib/dataGrid/index.ts +51 -0
  315. package/recce-source/js/src/lib/dataGrid/propertyBased.test.ts +858 -0
  316. package/recce-source/js/src/lib/dataGrid/shared/columnBuilders.test.ts +482 -0
  317. package/recce-source/js/src/lib/dataGrid/shared/columnBuilders.ts +345 -0
  318. package/recce-source/js/src/lib/dataGrid/shared/dataTypeEdgeCases.test.ts +698 -0
  319. package/recce-source/js/src/lib/dataGrid/shared/diffColumnBuilder.test.tsx +820 -0
  320. package/recce-source/js/src/lib/dataGrid/shared/diffColumnBuilder.tsx +277 -0
  321. package/recce-source/js/src/lib/dataGrid/shared/gridUtils.test.ts +785 -0
  322. package/recce-source/js/src/lib/dataGrid/shared/gridUtils.ts +370 -0
  323. package/recce-source/js/src/lib/dataGrid/shared/index.ts +81 -0
  324. package/recce-source/js/src/lib/dataGrid/shared/rowBuilders.test.ts +909 -0
  325. package/recce-source/js/src/lib/dataGrid/shared/rowBuilders.ts +325 -0
  326. package/recce-source/js/src/lib/dataGrid/shared/simpleColumnBuilder.tsx +240 -0
  327. package/recce-source/js/src/lib/dataGrid/shared/toDiffColumn.test.tsx +719 -0
  328. package/recce-source/js/src/lib/dataGrid/shared/toDiffColumn.tsx +231 -0
  329. package/recce-source/js/src/lib/dataGrid/shared/validation.test.ts +559 -0
  330. package/recce-source/js/src/lib/dataGrid/shared/validation.ts +367 -0
  331. package/recce-source/js/src/lib/dataGrid/warehouseNamingConventions.test.ts +1117 -0
  332. package/recce-source/js/src/lib/formatSelect.ts +50 -0
  333. package/recce-source/js/src/lib/hooks/ApiConfigContext.tsx +181 -0
  334. package/recce-source/js/src/lib/hooks/IdleTimeoutContext.tsx +177 -0
  335. package/recce-source/js/src/lib/hooks/LineageGraphContext.tsx +512 -0
  336. package/recce-source/js/src/lib/hooks/RecceActionContext.tsx +269 -0
  337. package/recce-source/js/src/lib/hooks/RecceCheckContext.tsx +33 -0
  338. package/recce-source/js/src/lib/hooks/RecceContextProvider.tsx +54 -0
  339. package/recce-source/js/src/lib/hooks/RecceInstanceContext.tsx +129 -0
  340. package/recce-source/js/src/lib/hooks/RecceQueryContext.tsx +98 -0
  341. package/recce-source/js/src/lib/hooks/RecceShareStateContext.tsx +59 -0
  342. package/recce-source/js/src/lib/hooks/ScreenShot.tsx +399 -0
  343. package/recce-source/js/src/lib/hooks/useAppRouter.test.ts +211 -0
  344. package/recce-source/js/src/lib/hooks/useAppRouter.ts +200 -0
  345. package/recce-source/js/src/lib/hooks/useCheckEvents.ts +99 -0
  346. package/recce-source/js/src/lib/hooks/useCheckToast.tsx +14 -0
  347. package/recce-source/js/src/lib/hooks/useClipBoardToast.tsx +27 -0
  348. package/recce-source/js/src/lib/hooks/useCountdownToast.tsx +102 -0
  349. package/recce-source/js/src/lib/hooks/useFeedbackCollectionToast.tsx +130 -0
  350. package/recce-source/js/src/lib/hooks/useGuideToast.tsx +45 -0
  351. package/recce-source/js/src/lib/hooks/useIdleDetection.tsx +185 -0
  352. package/recce-source/js/src/lib/hooks/useModelColumns.tsx +113 -0
  353. package/recce-source/js/src/lib/hooks/useRecceInstanceInfo.tsx +13 -0
  354. package/recce-source/js/src/lib/hooks/useRecceServerFlag.tsx +13 -0
  355. package/recce-source/js/src/lib/hooks/useRun.tsx +89 -0
  356. package/recce-source/js/src/lib/hooks/useThemeColors.ts +115 -0
  357. package/recce-source/js/src/lib/mergeKeys.test.ts +89 -0
  358. package/recce-source/js/src/lib/mergeKeys.ts +86 -0
  359. package/recce-source/js/src/lib/result/ResultErrorFallback.tsx +9 -0
  360. package/recce-source/js/src/lib/utils/formatTime.ts +84 -0
  361. package/recce-source/js/src/lib/utils/urls.ts +16 -0
  362. package/recce-source/js/src/utils/DropdownValuesInput.tsx +297 -0
  363. package/recce-source/js/src/utils/formatters.tsx +237 -0
  364. package/recce-source/js/src/utils/transforms.ts +81 -0
  365. package/recce-source/js/tsconfig.json +47 -0
  366. package/recce-source/macros/README.md +8 -0
  367. package/recce-source/macros/recce_athena.sql +73 -0
  368. package/recce-source/pyproject.toml +109 -0
  369. package/recce-source/recce/VERSION +1 -0
  370. package/recce-source/recce/__init__.py +84 -0
  371. package/recce-source/recce/adapter/__init__.py +0 -0
  372. package/recce-source/recce/adapter/base.py +109 -0
  373. package/recce-source/recce/adapter/dbt_adapter/__init__.py +1699 -0
  374. package/recce-source/recce/adapter/dbt_adapter/dbt_version.py +42 -0
  375. package/recce-source/recce/adapter/sqlmesh_adapter.py +141 -0
  376. package/recce-source/recce/apis/__init__.py +0 -0
  377. package/recce-source/recce/apis/check_api.py +203 -0
  378. package/recce-source/recce/apis/check_events_api.py +353 -0
  379. package/recce-source/recce/apis/check_func.py +130 -0
  380. package/recce-source/recce/apis/run_api.py +130 -0
  381. package/recce-source/recce/apis/run_func.py +258 -0
  382. package/recce-source/recce/artifact.py +266 -0
  383. package/recce-source/recce/cli.py +1846 -0
  384. package/recce-source/recce/config.py +127 -0
  385. package/recce-source/recce/connect_to_cloud.py +138 -0
  386. package/recce-source/recce/core.py +334 -0
  387. package/recce-source/recce/diff.py +26 -0
  388. package/recce-source/recce/event/CONFIG +1 -0
  389. package/recce-source/recce/event/SENTRY_DNS +1 -0
  390. package/recce-source/recce/event/__init__.py +304 -0
  391. package/recce-source/recce/event/collector.py +184 -0
  392. package/recce-source/recce/event/track.py +158 -0
  393. package/recce-source/recce/exceptions.py +21 -0
  394. package/recce-source/recce/git.py +77 -0
  395. package/recce-source/recce/github.py +222 -0
  396. package/recce-source/recce/mcp_server.py +861 -0
  397. package/recce-source/recce/models/__init__.py +6 -0
  398. package/recce-source/recce/models/check.py +473 -0
  399. package/recce-source/recce/models/run.py +46 -0
  400. package/recce-source/recce/models/types.py +218 -0
  401. package/recce-source/recce/pull_request.py +124 -0
  402. package/recce-source/recce/run.py +390 -0
  403. package/recce-source/recce/server.py +877 -0
  404. package/recce-source/recce/state/__init__.py +31 -0
  405. package/recce-source/recce/state/cloud.py +644 -0
  406. package/recce-source/recce/state/const.py +26 -0
  407. package/recce-source/recce/state/local.py +56 -0
  408. package/recce-source/recce/state/state.py +119 -0
  409. package/recce-source/recce/state/state_loader.py +174 -0
  410. package/recce-source/recce/summary.py +575 -0
  411. package/recce-source/recce/tasks/__init__.py +23 -0
  412. package/recce-source/recce/tasks/core.py +134 -0
  413. package/recce-source/recce/tasks/dataframe.py +170 -0
  414. package/recce-source/recce/tasks/histogram.py +433 -0
  415. package/recce-source/recce/tasks/lineage.py +19 -0
  416. package/recce-source/recce/tasks/profile.py +298 -0
  417. package/recce-source/recce/tasks/query.py +450 -0
  418. package/recce-source/recce/tasks/rowcount.py +277 -0
  419. package/recce-source/recce/tasks/schema.py +65 -0
  420. package/recce-source/recce/tasks/top_k.py +172 -0
  421. package/recce-source/recce/tasks/utils.py +147 -0
  422. package/recce-source/recce/tasks/valuediff.py +497 -0
  423. package/recce-source/recce/util/__init__.py +4 -0
  424. package/recce-source/recce/util/api_token.py +80 -0
  425. package/recce-source/recce/util/breaking.py +330 -0
  426. package/recce-source/recce/util/cache.py +25 -0
  427. package/recce-source/recce/util/cll.py +355 -0
  428. package/recce-source/recce/util/cloud/__init__.py +15 -0
  429. package/recce-source/recce/util/cloud/base.py +115 -0
  430. package/recce-source/recce/util/cloud/check_events.py +190 -0
  431. package/recce-source/recce/util/cloud/checks.py +242 -0
  432. package/recce-source/recce/util/io.py +120 -0
  433. package/recce-source/recce/util/lineage.py +83 -0
  434. package/recce-source/recce/util/logger.py +25 -0
  435. package/recce-source/recce/util/onboarding_state.py +45 -0
  436. package/recce-source/recce/util/perf_tracking.py +85 -0
  437. package/recce-source/recce/util/pydantic_model.py +22 -0
  438. package/recce-source/recce/util/recce_cloud.py +454 -0
  439. package/recce-source/recce/util/singleton.py +18 -0
  440. package/recce-source/recce/util/startup_perf.py +121 -0
  441. package/recce-source/recce/yaml/__init__.py +58 -0
  442. package/recce-source/recce_cloud/README.md +780 -0
  443. package/recce-source/recce_cloud/VERSION +1 -0
  444. package/recce-source/recce_cloud/__init__.py +24 -0
  445. package/recce-source/recce_cloud/api/__init__.py +17 -0
  446. package/recce-source/recce_cloud/api/base.py +132 -0
  447. package/recce-source/recce_cloud/api/client.py +186 -0
  448. package/recce-source/recce_cloud/api/exceptions.py +26 -0
  449. package/recce-source/recce_cloud/api/factory.py +63 -0
  450. package/recce-source/recce_cloud/api/github.py +106 -0
  451. package/recce-source/recce_cloud/api/gitlab.py +111 -0
  452. package/recce-source/recce_cloud/artifact.py +57 -0
  453. package/recce-source/recce_cloud/ci_providers/__init__.py +9 -0
  454. package/recce-source/recce_cloud/ci_providers/base.py +82 -0
  455. package/recce-source/recce_cloud/ci_providers/detector.py +147 -0
  456. package/recce-source/recce_cloud/ci_providers/github_actions.py +136 -0
  457. package/recce-source/recce_cloud/ci_providers/gitlab_ci.py +130 -0
  458. package/recce-source/recce_cloud/cli.py +434 -0
  459. package/recce-source/recce_cloud/download.py +230 -0
  460. package/recce-source/recce_cloud/hatch_build.py +20 -0
  461. package/recce-source/recce_cloud/pyproject.toml +49 -0
  462. package/recce-source/recce_cloud/upload.py +214 -0
  463. package/recce-source/test.py +0 -0
  464. package/recce-source/tests/__init__.py +0 -0
  465. package/recce-source/tests/adapter/__init__.py +0 -0
  466. package/recce-source/tests/adapter/dbt_adapter/__init__.py +0 -0
  467. package/recce-source/tests/adapter/dbt_adapter/conftest.py +17 -0
  468. package/recce-source/tests/adapter/dbt_adapter/dbt_test_helper.py +298 -0
  469. package/recce-source/tests/adapter/dbt_adapter/test_dbt_adapter.py +25 -0
  470. package/recce-source/tests/adapter/dbt_adapter/test_dbt_cll.py +717 -0
  471. package/recce-source/tests/adapter/dbt_adapter/test_proj/dbt_project.yml +4 -0
  472. package/recce-source/tests/adapter/dbt_adapter/test_proj/manifest.json +1 -0
  473. package/recce-source/tests/adapter/dbt_adapter/test_proj/package-lock.yml +8 -0
  474. package/recce-source/tests/adapter/dbt_adapter/test_proj/packages.yml +7 -0
  475. package/recce-source/tests/adapter/dbt_adapter/test_proj/profiles.yml +6 -0
  476. package/recce-source/tests/adapter/dbt_adapter/test_selector.py +205 -0
  477. package/recce-source/tests/apis/__init__.py +0 -0
  478. package/recce-source/tests/apis/row_count_diff.json +59 -0
  479. package/recce-source/tests/apis/test_check_events_api.py +615 -0
  480. package/recce-source/tests/apis/test_run_func.py +433 -0
  481. package/recce-source/tests/catalog.json +527 -0
  482. package/recce-source/tests/data/manifest/base/catalog.json +1 -0
  483. package/recce-source/tests/data/manifest/base/manifest.json +1 -0
  484. package/recce-source/tests/data/manifest/pr2/catalog.json +1 -0
  485. package/recce-source/tests/data/manifest/pr2/manifest.json +1 -0
  486. package/recce-source/tests/manifest.json +10655 -0
  487. package/recce-source/tests/models/__init__.py +0 -0
  488. package/recce-source/tests/models/test_check.py +731 -0
  489. package/recce-source/tests/models/test_run_models.py +295 -0
  490. package/recce-source/tests/recce_cloud/__init__.py +0 -0
  491. package/recce-source/tests/recce_cloud/test_ci_providers.py +351 -0
  492. package/recce-source/tests/recce_cloud/test_cli.py +735 -0
  493. package/recce-source/tests/recce_cloud/test_client.py +379 -0
  494. package/recce-source/tests/recce_cloud/test_platform_clients.py +483 -0
  495. package/recce-source/tests/recce_state.json +1 -0
  496. package/recce-source/tests/state/test_cloud.py +719 -0
  497. package/recce-source/tests/state/test_local.py +164 -0
  498. package/recce-source/tests/state/test_state_loader.py +211 -0
  499. package/recce-source/tests/tasks/__init__.py +0 -0
  500. package/recce-source/tests/tasks/conftest.py +4 -0
  501. package/recce-source/tests/tasks/test_histogram.py +129 -0
  502. package/recce-source/tests/tasks/test_lineage.py +55 -0
  503. package/recce-source/tests/tasks/test_preset_checks.py +64 -0
  504. package/recce-source/tests/tasks/test_profile.py +397 -0
  505. package/recce-source/tests/tasks/test_query.py +528 -0
  506. package/recce-source/tests/tasks/test_row_count.py +133 -0
  507. package/recce-source/tests/tasks/test_schema.py +122 -0
  508. package/recce-source/tests/tasks/test_top_k.py +77 -0
  509. package/recce-source/tests/tasks/test_utils.py +439 -0
  510. package/recce-source/tests/tasks/test_valuediff.py +361 -0
  511. package/recce-source/tests/test_cli.py +236 -0
  512. package/recce-source/tests/test_cli_mcp_optional.py +45 -0
  513. package/recce-source/tests/test_cloud_listing_cli.py +324 -0
  514. package/recce-source/tests/test_config.py +43 -0
  515. package/recce-source/tests/test_connect_to_cloud.py +82 -0
  516. package/recce-source/tests/test_core.py +174 -0
  517. package/recce-source/tests/test_dbt.py +36 -0
  518. package/recce-source/tests/test_mcp_server.py +505 -0
  519. package/recce-source/tests/test_pull_request.py +130 -0
  520. package/recce-source/tests/test_server.py +202 -0
  521. package/recce-source/tests/test_server_lifespan.py +138 -0
  522. package/recce-source/tests/test_summary.py +73 -0
  523. package/recce-source/tests/util/__init__.py +0 -0
  524. package/recce-source/tests/util/cloud/__init__.py +0 -0
  525. package/recce-source/tests/util/cloud/test_check_events.py +255 -0
  526. package/recce-source/tests/util/cloud/test_checks.py +204 -0
  527. package/recce-source/tests/util/test_api_token.py +119 -0
  528. package/recce-source/tests/util/test_breaking.py +1427 -0
  529. package/recce-source/tests/util/test_cll.py +706 -0
  530. package/recce-source/tests/util/test_lineage.py +122 -0
  531. package/recce-source/tests/util/test_onboarding_state.py +84 -0
  532. package/recce-source/tests/util/test_recce_cloud.py +231 -0
  533. package/recce-source/tox.ini +40 -0
  534. package/recce-source/uv.lock +3928 -0
  535. package/src/api/index.ts +32 -0
  536. package/src/components/index.ts +154 -0
  537. package/src/global.d.ts +14 -0
  538. package/src/hooks/index.ts +56 -0
  539. package/src/index.ts +17 -0
  540. package/src/lib/hooks/RouteConfigContext.ts +139 -0
  541. package/src/lib/hooks/useAppRouter.ts +240 -0
  542. package/src/mui-augmentation.d.ts +139 -0
  543. package/src/theme/index.ts +13 -0
  544. package/src/theme.ts +23 -0
  545. package/src/types/index.ts +23 -0
  546. package/dist/index-BNUP2V_N.d.ts.map +0 -1
  547. package/dist/index-DOPZuhD8.d.mts.map +0 -1
  548. package/dist/theme.d.mts.map +0 -1
  549. package/dist/theme.d.ts.map +0 -1
@@ -0,0 +1,1384 @@
1
+ import Box from "@mui/material/Box";
2
+ import Button from "@mui/material/Button";
3
+ import CircularProgress from "@mui/material/CircularProgress";
4
+ import Divider from "@mui/material/Divider";
5
+ import Stack from "@mui/material/Stack";
6
+ import Typography from "@mui/material/Typography";
7
+ import {
8
+ Background,
9
+ BackgroundVariant,
10
+ ControlButton,
11
+ Controls,
12
+ getNodesBounds,
13
+ MiniMap,
14
+ Node,
15
+ Panel,
16
+ ReactFlow,
17
+ useEdgesState,
18
+ useNodesState,
19
+ useReactFlow,
20
+ } from "@xyflow/react";
21
+ import React, {
22
+ forwardRef,
23
+ Ref,
24
+ RefObject,
25
+ useCallback,
26
+ useEffect,
27
+ useImperativeHandle,
28
+ useLayoutEffect,
29
+ useMemo,
30
+ useRef,
31
+ useState,
32
+ } from "react";
33
+ import {
34
+ isLineageGraphColumnNode,
35
+ isLineageGraphNode,
36
+ LineageGraphColumnNode,
37
+ LineageGraphEdge,
38
+ LineageGraphNode,
39
+ LineageGraphNodes,
40
+ layout,
41
+ NodeColumnSetMap,
42
+ selectDownstream,
43
+ selectUpstream,
44
+ toReactFlow,
45
+ } from "./lineage";
46
+ import "@xyflow/react/dist/style.css";
47
+ import "./styles.css";
48
+ import { useMutation } from "@tanstack/react-query";
49
+ import { AxiosError } from "axios";
50
+ import { FiCopy } from "react-icons/fi";
51
+ import { colors } from "@/components/ui/mui-theme";
52
+ import { toaster } from "@/components/ui/toaster";
53
+ import { Check } from "@/lib/api/checks";
54
+ import { CllInput, ColumnLineageData, getCll } from "@/lib/api/cll";
55
+ import {
56
+ createLineageDiffCheck,
57
+ LineageDiffViewOptions,
58
+ } from "@/lib/api/lineagecheck";
59
+ import { createSchemaDiffCheck } from "@/lib/api/schemacheck";
60
+ import { select } from "@/lib/api/select";
61
+ import {
62
+ type LineageViewRenderProps,
63
+ trackCopyToClipboard,
64
+ trackLineageViewRender,
65
+ trackMultiNodesAction,
66
+ } from "@/lib/api/track";
67
+ import {
68
+ isHistogramDiffRun,
69
+ isProfileDiffRun,
70
+ isTopKDiffRun,
71
+ isValueDiffDetailRun,
72
+ isValueDiffRun,
73
+ } from "@/lib/api/types";
74
+ import { useApiConfig } from "@/lib/hooks/ApiConfigContext";
75
+ import { useLineageGraphContext } from "@/lib/hooks/LineageGraphContext";
76
+ import { useRecceActionContext } from "@/lib/hooks/RecceActionContext";
77
+ import { useRecceInstanceContext } from "@/lib/hooks/RecceInstanceContext";
78
+ import {
79
+ IGNORE_SCREENSHOT_CLASS,
80
+ useCopyToClipboard,
81
+ } from "@/lib/hooks/ScreenShot";
82
+ import { useAppLocation } from "@/lib/hooks/useAppRouter";
83
+ import { useClipBoardToast } from "@/lib/hooks/useClipBoardToast";
84
+ import { useRun } from "@/lib/hooks/useRun";
85
+ import { useThemeColors } from "@/lib/hooks/useThemeColors";
86
+ import { HSplit } from "../split/Split";
87
+ import { ActionControl } from "./ActionControl";
88
+ import { ChangeStatusLegend } from "./ChangeStatusLegend";
89
+ import { ColumnLevelLineageControl } from "./ColumnLevelLineageControl";
90
+ import { ColumnLevelLineageLegend } from "./ColumnLevelLineageLegend";
91
+ import { GraphColumnNode } from "./GraphColumnNode";
92
+ import GraphEdge from "./GraphEdge";
93
+ import { GraphNode } from "./GraphNode";
94
+ import { union } from "./graph";
95
+ import {
96
+ LineageViewContext,
97
+ LineageViewContextType,
98
+ } from "./LineageViewContext";
99
+ import {
100
+ LineageViewContextMenu,
101
+ useLineageViewContextMenu,
102
+ } from "./LineageViewContextMenu";
103
+ import { LineageViewNotification } from "./LineageViewNotification";
104
+ import { LineageViewTopBar } from "./LineageViewTopBar";
105
+ import { NodeView } from "./NodeView";
106
+ import SetupConnectionBanner from "./SetupConnectionBanner";
107
+ import { BaseEnvironmentSetupNotification } from "./SingleEnvironmentQueryView";
108
+ import { getIconForChangeStatus } from "./styles";
109
+ import { useMultiNodesAction } from "./useMultiNodesAction";
110
+ import useValueDiffAlertDialog from "./useValueDiffAlertDialog";
111
+
112
+ export interface LineageViewProps {
113
+ viewOptions?: LineageDiffViewOptions;
114
+ interactive?: boolean;
115
+ weight?: number;
116
+ height?: number;
117
+
118
+ // to be removed
119
+ // viewMode?: "changed_models" | "all"; // deprecated
120
+ filterNodes?: (key: string, node: LineageGraphNode) => boolean;
121
+ }
122
+
123
+ export interface LineageViewRef {
124
+ copyToClipboard: () => void;
125
+ }
126
+
127
+ const nodeTypes = {
128
+ lineageGraphNode: GraphNode,
129
+ lineageGraphColumnNode: GraphColumnNode,
130
+ };
131
+ const initialNodes: LineageGraphNode[] = [];
132
+ const edgeTypes = {
133
+ lineageGraphEdge: GraphEdge,
134
+ };
135
+ const nodeColor = (node: LineageGraphNode) => {
136
+ return node.data.changeStatus
137
+ ? getIconForChangeStatus(node.data.changeStatus).hexColor
138
+ : colors.neutral[400];
139
+ };
140
+
141
+ const useResizeObserver = (
142
+ ref: RefObject<HTMLElement | null>,
143
+ handler: () => void,
144
+ ) => {
145
+ const size = useRef({
146
+ width: 0,
147
+ height: 0,
148
+ });
149
+
150
+ useEffect(() => {
151
+ const target = ref.current;
152
+ const handleResize = (entries: ResizeObserverEntry[]) => {
153
+ for (const entry of entries) {
154
+ const newWidth = entry.contentRect.width;
155
+ const newHeight = entry.contentRect.height;
156
+
157
+ if (
158
+ Math.abs(newHeight - size.current.height) > 10 ||
159
+ Math.abs(newWidth - size.current.width) > 10
160
+ ) {
161
+ if (
162
+ size.current.height > 0 &&
163
+ newHeight > 0 &&
164
+ size.current.width > 0 &&
165
+ newWidth > 0
166
+ ) {
167
+ handler();
168
+ }
169
+ }
170
+ size.current = {
171
+ width: newWidth,
172
+ height: newHeight,
173
+ };
174
+ }
175
+ };
176
+
177
+ const resizeObserver = new ResizeObserver(handleResize);
178
+
179
+ if (target) {
180
+ resizeObserver.observe(target);
181
+ }
182
+
183
+ return () => {
184
+ if (target) {
185
+ resizeObserver.unobserve(target);
186
+ }
187
+ };
188
+ }, [handler, ref]);
189
+ };
190
+
191
+ const useNavToCheck = () => {
192
+ const [, setLocation] = useAppLocation();
193
+ return useCallback(
194
+ (check: Check) => {
195
+ if (check.check_id) {
196
+ setLocation(`/checks/?id=${check.check_id}`);
197
+ }
198
+ },
199
+ [setLocation],
200
+ );
201
+ };
202
+
203
+ export function PrivateLineageView(
204
+ { interactive = false, ...props }: LineageViewProps,
205
+ ref: Ref<LineageViewRef>,
206
+ ) {
207
+ const { isDark } = useThemeColors();
208
+ const { apiClient } = useApiConfig();
209
+ const reactFlow = useReactFlow();
210
+ const refResize = useRef<HTMLDivElement>(null);
211
+ const { successToast, failToast } = useClipBoardToast();
212
+ const {
213
+ copyToClipboard,
214
+ ImageDownloadModal,
215
+ ref: refReactFlow,
216
+ } = useCopyToClipboard({
217
+ renderLibrary: "html-to-image",
218
+ imageType: "png",
219
+ shadowEffect: true,
220
+ backgroundColor: isDark ? colors.neutral[900] : colors.neutral[50],
221
+ ignoreElements: (element: Element) => {
222
+ try {
223
+ return element.classList.contains(IGNORE_SCREENSHOT_CLASS);
224
+ } catch {
225
+ if (element.className) {
226
+ return element.className.includes(IGNORE_SCREENSHOT_CLASS);
227
+ }
228
+ return false;
229
+ }
230
+ },
231
+ onSuccess: () => {
232
+ successToast("Copied the Lineage View as an image to clipboard");
233
+ },
234
+ onError: (error) => {
235
+ console.error("Error taking screenshot", error);
236
+ failToast("Failed to copy image to clipboard", error);
237
+ },
238
+ });
239
+ const [nodes, setNodes, onNodesChange] =
240
+ useNodesState<LineageGraphNodes>(initialNodes);
241
+ const [edges, setEdges, onEdgesChange] = useEdgesState<LineageGraphEdge>([]);
242
+
243
+ const {
244
+ lineageGraph,
245
+ retchLineageGraph,
246
+ isLoading,
247
+ error,
248
+ refetchRunsAggregated,
249
+ } = useLineageGraphContext();
250
+
251
+ const { featureToggles, singleEnv } = useRecceInstanceContext();
252
+ const { runId, showRunId, closeRunResult, runAction, isRunResultOpen } =
253
+ useRecceActionContext();
254
+ const { run } = useRun(runId);
255
+
256
+ const [viewOptions, setViewOptions] = useState<LineageDiffViewOptions>({
257
+ ...props.viewOptions,
258
+ });
259
+
260
+ // Helper to track lineage view render with node counts
261
+ const trackLineageRender = useCallback(
262
+ (
263
+ nodes: LineageGraphNodes[],
264
+ currentViewMode: string,
265
+ impactRadiusEnabled: boolean,
266
+ cllColumnActive: boolean,
267
+ rightSidebarOpen: boolean,
268
+ ) => {
269
+ const lineageGraphNodesOnly = nodes.filter(isLineageGraphNode);
270
+ const grouped = Object.groupBy(
271
+ lineageGraphNodesOnly,
272
+ (node) => node.data.changeStatus ?? "unchanged",
273
+ );
274
+ // Prefix status counts with "nodes_"
275
+ const statusCounts = Object.fromEntries(
276
+ Object.entries(grouped).map(([status, nodes]) => [
277
+ `nodes_${status}`,
278
+ nodes?.length ?? 0,
279
+ ]),
280
+ );
281
+ const trackingData = {
282
+ node_count: lineageGraphNodesOnly.length,
283
+ view_mode: currentViewMode,
284
+ impact_radius_enabled: impactRadiusEnabled,
285
+ right_sidebar_open: rightSidebarOpen,
286
+ ...statusCounts,
287
+ } as LineageViewRenderProps;
288
+ // Only include cll_column_active when a column is being viewed
289
+ if (cllColumnActive) {
290
+ trackingData.cll_column_active = true;
291
+ }
292
+ trackLineageViewRender(trackingData);
293
+ },
294
+ [],
295
+ );
296
+
297
+ const cllHistory = useRef<(CllInput | undefined)[]>([]).current;
298
+
299
+ const [cll, setCll] = useState<ColumnLineageData | undefined>(undefined);
300
+ const actionGetCll = useMutation({
301
+ mutationFn: (input: CllInput) => getCll(input, apiClient),
302
+ });
303
+ const [nodeColumnSetMap, setNodeColumSetMap] = useState<NodeColumnSetMap>({});
304
+
305
+ const findNodeByName = useCallback(
306
+ (name: string) => {
307
+ return nodes.filter(isLineageGraphNode).find((n) => n.data.name === name);
308
+ },
309
+ [nodes],
310
+ );
311
+
312
+ // Expose the function to the parent via the ref
313
+ useImperativeHandle(ref, () => ({
314
+ copyToClipboard,
315
+ }));
316
+
317
+ const isModelsChanged = useMemo(() => {
318
+ return !!(lineageGraph && lineageGraph.modifiedSet.length > 0);
319
+ }, [lineageGraph]);
320
+
321
+ /**
322
+ * View mode
323
+ * - all: show all nodes
324
+ * - changed_models: show only changed models
325
+ */
326
+ const viewMode = viewOptions.view_mode ?? "changed_models";
327
+ const filteredNodeIds: string[] = useMemo(() => {
328
+ return nodes
329
+ .filter((node) => node.type === "lineageGraphNode")
330
+ .map((node) => node.id);
331
+ }, [nodes]);
332
+ const filteredNodes = useMemo(() => {
333
+ if (!lineageGraph) {
334
+ return [];
335
+ }
336
+
337
+ return filteredNodeIds.map((nodeId) => lineageGraph.nodes[nodeId]);
338
+ }, [lineageGraph, filteredNodeIds]);
339
+
340
+ /**
341
+ * Focused node: the node that is currently focused. Show the NodeView when a node is focused
342
+ */
343
+ const [focusedNodeId, setFocusedNodeId] = useState<string>();
344
+ const focusedNode = focusedNodeId
345
+ ? lineageGraph?.nodes[focusedNodeId]
346
+ : undefined;
347
+
348
+ /**
349
+ * Select mode: the behavior of clicking on nodes
350
+ * - (undefined): no selection
351
+ * - selecting: selecting nodes
352
+ * - action_result: take action on selected nodes
353
+ */
354
+ const [selectMode, setSelectMode] = useState<"selecting" | "action_result">();
355
+ const [selectedNodeIds, setSelectedNodeIds] = useState<Set<string>>(
356
+ new Set(),
357
+ );
358
+ const selectedNodes = useMemo(() => {
359
+ if (!lineageGraph) {
360
+ return [];
361
+ }
362
+
363
+ const nodeIds = Array.from(selectedNodeIds);
364
+ return nodeIds.map((nodeId) => lineageGraph.nodes[nodeId]);
365
+ }, [lineageGraph, selectedNodeIds]);
366
+ const multiNodeAction = useMultiNodesAction(
367
+ selectedNodes.length > 0 ? selectedNodes : filteredNodes,
368
+ {
369
+ onActionStarted: () => {
370
+ setSelectMode("action_result");
371
+ },
372
+ onActionNodeUpdated: (updated: LineageGraphNode) => {
373
+ // trigger a re-render by updating the nodes
374
+ setNodes((nodes) =>
375
+ nodes.map((node) => {
376
+ if (node.id === updated.id) {
377
+ return {
378
+ ...node,
379
+ };
380
+ }
381
+ return node;
382
+ }),
383
+ );
384
+ },
385
+ onActionCompleted: () => {
386
+ return void 0;
387
+ },
388
+ },
389
+ );
390
+
391
+ /**
392
+ * Highlighted nodes: the nodes that are highlighted. The behavior of highlighting depends on the select mode
393
+ *
394
+ * - Default: nodes in the impact radius, or all nodes if the impact radius is not available
395
+ * - Focus: upstream/downstream nodes of the focused node
396
+ * - Multi-select: nodes in the impact radius, or all nodes if the impact radius is not available
397
+ * - Action Result: selected nodes
398
+ */
399
+ const highlighted = useMemo<Set<string>>(() => {
400
+ if (!lineageGraph) {
401
+ return new Set<string>();
402
+ }
403
+
404
+ let highlightedModels: Set<string> = new Set<string>();
405
+ if (cll) {
406
+ for (const [nodeId, node] of Object.entries(cll.current.nodes)) {
407
+ if (node.impacted !== false) {
408
+ highlightedModels.add(nodeId);
409
+ }
410
+ }
411
+ for (const columnId of Object.keys(cll.current.columns)) {
412
+ highlightedModels.add(columnId);
413
+ }
414
+ } else if (selectMode === "action_result") {
415
+ const nodeIds = Object.keys(multiNodeAction.actionState.actions);
416
+ highlightedModels = new Set(nodeIds);
417
+ } else if (focusedNode) {
418
+ highlightedModels = union(
419
+ selectUpstream(lineageGraph, [focusedNode.id]),
420
+ selectDownstream(lineageGraph, [focusedNode.id]),
421
+ );
422
+ } else if (isModelsChanged) {
423
+ highlightedModels = selectDownstream(
424
+ lineageGraph,
425
+ lineageGraph.modifiedSet,
426
+ );
427
+ } else {
428
+ highlightedModels = new Set(filteredNodeIds);
429
+ }
430
+
431
+ // Add columns in the highlighted models
432
+ return new Set<string>(highlightedModels);
433
+ }, [
434
+ lineageGraph,
435
+ cll,
436
+ selectMode,
437
+ focusedNode,
438
+ isModelsChanged,
439
+ multiNodeAction.actionState.actions,
440
+ filteredNodeIds,
441
+ ]);
442
+
443
+ const lineageViewContextMenu = useLineageViewContextMenu();
444
+
445
+ const closeContextMenu = () => {
446
+ lineageViewContextMenu.closeContextMenu();
447
+ };
448
+
449
+ // biome-ignore lint/correctness/useExhaustiveDependencies: Intentionally only run when lineageGraph changes (initial load/refetch).
450
+ useLayoutEffect(() => {
451
+ const t = async () => {
452
+ let filteredNodeIds: string[] | undefined = undefined;
453
+
454
+ if (!lineageGraph) {
455
+ return;
456
+ }
457
+
458
+ if (viewOptions.node_ids) {
459
+ filteredNodeIds = viewOptions.node_ids;
460
+ } else {
461
+ const packageName = lineageGraph.manifestMetadata.current?.project_name;
462
+ const viewMode =
463
+ viewOptions.view_mode ?? (isModelsChanged ? "changed_models" : "all");
464
+
465
+ const newViewOptions: LineageDiffViewOptions = {
466
+ view_mode: viewMode,
467
+ packages: packageName ? [packageName] : undefined,
468
+ ...props.viewOptions,
469
+ };
470
+
471
+ try {
472
+ const result = await select(
473
+ {
474
+ select: newViewOptions.select,
475
+ exclude: newViewOptions.exclude,
476
+ packages: newViewOptions.packages,
477
+ view_mode: newViewOptions.view_mode,
478
+ },
479
+ apiClient,
480
+ );
481
+ filteredNodeIds = result.nodes;
482
+ } catch (_) {
483
+ // fallback behavior
484
+ newViewOptions.view_mode = "all";
485
+ const result = await select(
486
+ {
487
+ select: newViewOptions.select,
488
+ exclude: newViewOptions.exclude,
489
+ packages: newViewOptions.packages,
490
+ view_mode: newViewOptions.view_mode,
491
+ },
492
+ apiClient,
493
+ );
494
+ filteredNodeIds = result.nodes;
495
+ }
496
+
497
+ setViewOptions(newViewOptions);
498
+ }
499
+
500
+ let cll: ColumnLineageData | undefined;
501
+ if (viewOptions.column_level_lineage) {
502
+ try {
503
+ cll = await actionGetCll.mutateAsync(
504
+ viewOptions.column_level_lineage,
505
+ );
506
+ } catch (e) {
507
+ if (e instanceof AxiosError) {
508
+ const e2 = e as AxiosError<{ detail?: string }>;
509
+ toaster.create({
510
+ title: "Column Level Lineage error",
511
+ description: e2.response?.data.detail ?? e.message,
512
+ type: "error",
513
+ closable: true,
514
+ });
515
+ return;
516
+ }
517
+ }
518
+ }
519
+
520
+ const [nodes, edges, nodeColumnSetMap] = toReactFlow(lineageGraph, {
521
+ selectedNodes: filteredNodeIds,
522
+ cll: cll,
523
+ });
524
+ layout(nodes, edges);
525
+ setNodes(nodes);
526
+ setEdges(edges);
527
+ setNodeColumSetMap(nodeColumnSetMap);
528
+ setCll(cll);
529
+
530
+ // TODO : code smell: vioates DRY. This really shouldn't hit both here and below
531
+
532
+ // Track lineage view render
533
+ trackLineageRender(
534
+ nodes,
535
+ viewOptions.view_mode ?? "changed_models",
536
+ viewOptions.column_level_lineage?.change_analysis ?? false,
537
+ !!viewOptions.column_level_lineage?.column,
538
+ !!focusedNodeId || !!run,
539
+ );
540
+ };
541
+
542
+ void t();
543
+ // Intentionally only run when lineageGraph changes (initial load/refetch).
544
+ // viewOptions changes are handled separately by handleViewOptionsChanged.
545
+ // Other dependencies (setNodes, setEdges, actionGetCll) are stable.
546
+ // eslint-disable-next-line react-hooks/exhaustive-deps
547
+ }, [lineageGraph]);
548
+
549
+ const onNodeViewClosed = () => {
550
+ setFocusedNodeId(undefined);
551
+ };
552
+
553
+ const centerNode = async (nodeId: string) => {
554
+ let node = nodes.find((n) => n.id === nodeId);
555
+ if (!node) {
556
+ return;
557
+ }
558
+
559
+ if (node.parentId) {
560
+ const parentId = node.parentId;
561
+ node = nodes.find((n) => n.id === parentId) ?? node;
562
+ }
563
+
564
+ if (node.measured != null) {
565
+ const { width, height } = node.measured;
566
+ if (width && height) {
567
+ const x = node.position.x + width / 2;
568
+ const y = node.position.y + height / 2;
569
+ const zoom = reactFlow.getZoom();
570
+
571
+ await reactFlow.setCenter(x, y, { zoom, duration: 200 });
572
+ }
573
+ }
574
+ };
575
+
576
+ const navToCheck = useNavToCheck();
577
+
578
+ useResizeObserver(refResize, async () => {
579
+ if (selectMode !== "selecting") {
580
+ if (!focusedNodeId) {
581
+ await reactFlow.fitView({ nodes, duration: 200 });
582
+ } else {
583
+ await centerNode(focusedNodeId);
584
+ }
585
+ }
586
+ });
587
+
588
+ const showColumnLevelLineage = async (
589
+ columnLevelLineage?: CllInput,
590
+ previous = false,
591
+ ) => {
592
+ const previousColumnLevelLineage = viewOptions.column_level_lineage;
593
+
594
+ await handleViewOptionsChanged(
595
+ {
596
+ ...viewOptions,
597
+ column_level_lineage: columnLevelLineage,
598
+ },
599
+ false,
600
+ );
601
+
602
+ if (!previous) {
603
+ cllHistory.push(previousColumnLevelLineage);
604
+ }
605
+ if (columnLevelLineage?.node_id) {
606
+ setFocusedNodeId(columnLevelLineage.node_id);
607
+ } else {
608
+ setFocusedNodeId(undefined);
609
+ }
610
+ };
611
+
612
+ const resetColumnLevelLineage = async (previous?: boolean) => {
613
+ if (previous) {
614
+ if (cllHistory.length === 0) {
615
+ return;
616
+ }
617
+ const previousCll = cllHistory.pop();
618
+ if (previousCll) {
619
+ await showColumnLevelLineage(previousCll, true);
620
+ } else {
621
+ await showColumnLevelLineage(undefined, true);
622
+ }
623
+ } else {
624
+ await showColumnLevelLineage(undefined, true);
625
+ }
626
+ };
627
+
628
+ const onColumnNodeClick = (
629
+ event: React.MouseEvent,
630
+ node: LineageGraphColumnNode,
631
+ ) => {
632
+ if (selectMode) {
633
+ return;
634
+ }
635
+
636
+ void showColumnLevelLineage({
637
+ node_id: node.data.node.id,
638
+ column: node.data.column,
639
+ });
640
+ };
641
+
642
+ const onNodeClick = (event: React.MouseEvent, node: Node) => {
643
+ if (!interactive) return;
644
+ if (!lineageGraph) {
645
+ return;
646
+ }
647
+
648
+ if (isLineageGraphColumnNode(node as LineageGraphNodes)) {
649
+ onColumnNodeClick(event, node as LineageGraphColumnNode);
650
+ return;
651
+ }
652
+
653
+ closeContextMenu();
654
+ if (!selectMode) {
655
+ setFocusedNodeId(node.id);
656
+ } else if (selectMode === "action_result") {
657
+ const action = multiNodeAction.actionState.actions[node.id];
658
+ if (action.run?.run_id) {
659
+ showRunId(action.run.run_id);
660
+ }
661
+ setFocusedNodeId(node.id);
662
+ } else {
663
+ const newSet = new Set(selectedNodeIds);
664
+ if (selectedNodeIds.has(node.id)) {
665
+ newSet.delete(node.id);
666
+ } else {
667
+ newSet.add(node.id);
668
+ }
669
+ setSelectedNodeIds(newSet);
670
+ if (newSet.size === 0) {
671
+ setSelectMode(undefined);
672
+ }
673
+ }
674
+ };
675
+
676
+ const refreshLayout = async (options: {
677
+ viewOptions?: LineageDiffViewOptions;
678
+ fitView?: boolean;
679
+ }) => {
680
+ let { viewOptions: newViewOptions = viewOptions } = options;
681
+ const { fitView } = options;
682
+
683
+ let selectedNodes: string[] | undefined = undefined;
684
+
685
+ if (!lineageGraph) {
686
+ return;
687
+ }
688
+
689
+ const reselect =
690
+ viewOptions.select !== newViewOptions.select ||
691
+ viewOptions.exclude !== newViewOptions.exclude ||
692
+ viewOptions.packages !== newViewOptions.packages ||
693
+ viewOptions.view_mode !== newViewOptions.view_mode;
694
+
695
+ if (reselect) {
696
+ try {
697
+ const result = await select(
698
+ {
699
+ select: newViewOptions.select,
700
+ exclude: newViewOptions.exclude,
701
+ packages: newViewOptions.packages,
702
+ view_mode: newViewOptions.view_mode,
703
+ },
704
+ apiClient,
705
+ );
706
+ // focus to unfocus the model or column node
707
+ newViewOptions = { ...newViewOptions, column_level_lineage: undefined };
708
+ selectedNodes = result.nodes;
709
+ } catch (e) {
710
+ if (e instanceof AxiosError) {
711
+ const e2 = e as AxiosError<{ detail?: string }>;
712
+ toaster.create({
713
+ title: "Select node error",
714
+ description: e2.response?.data.detail ?? e.message,
715
+ type: "error",
716
+ closable: true,
717
+ });
718
+ }
719
+ return;
720
+ }
721
+ setFocusedNodeId(undefined);
722
+ } else {
723
+ selectedNodes = nodes.map((n) => n.id);
724
+ }
725
+
726
+ let cll: ColumnLineageData | undefined;
727
+ if (newViewOptions.column_level_lineage) {
728
+ try {
729
+ cll = await actionGetCll.mutateAsync(
730
+ newViewOptions.column_level_lineage,
731
+ );
732
+ } catch (e) {
733
+ if (e instanceof AxiosError) {
734
+ const e2 = e as AxiosError<{ detail?: string }>;
735
+ toaster.create({
736
+ title: "Column Level Lineage error",
737
+ description: e2.response?.data.detail ?? e.message,
738
+ type: "error",
739
+ closable: true,
740
+ });
741
+ return;
742
+ }
743
+ }
744
+ }
745
+
746
+ const [newNodes, newEdges, newNodeColumnSetMap] = toReactFlow(
747
+ lineageGraph,
748
+ {
749
+ selectedNodes,
750
+ cll,
751
+ },
752
+ );
753
+ setNodes(newNodes);
754
+ setEdges(newEdges);
755
+ setNodeColumSetMap(newNodeColumnSetMap);
756
+ setCll(cll);
757
+
758
+ // Track lineage view render
759
+ trackLineageRender(
760
+ newNodes,
761
+ newViewOptions.view_mode ?? "changed_models",
762
+ newViewOptions.column_level_lineage?.change_analysis ?? false,
763
+ !!newViewOptions.column_level_lineage?.column,
764
+ !!focusedNodeId || !!run,
765
+ );
766
+
767
+ // Close the run result view if the run result node is not in the new nodes
768
+ if (
769
+ run &&
770
+ (isTopKDiffRun(run) ||
771
+ isProfileDiffRun(run) ||
772
+ isHistogramDiffRun(run) ||
773
+ isValueDiffRun(run) ||
774
+ isValueDiffDetailRun(run))
775
+ ) {
776
+ if (run.params?.model && !findNodeByName(run.params.model)) {
777
+ closeRunResult();
778
+ }
779
+ }
780
+
781
+ if (fitView) {
782
+ await new Promise((resolve) => setTimeout(resolve, 1));
783
+ (() => {
784
+ void reactFlow.fitView({ nodes: newNodes, duration: 200 });
785
+ })();
786
+ }
787
+ };
788
+
789
+ const handleViewOptionsChanged = async (
790
+ newViewOptions: LineageDiffViewOptions,
791
+ fitView = true,
792
+ ) => {
793
+ setViewOptions(newViewOptions);
794
+ await refreshLayout({
795
+ viewOptions: newViewOptions,
796
+ fitView,
797
+ });
798
+ };
799
+
800
+ const valueDiffAlertDialog = useValueDiffAlertDialog();
801
+
802
+ // biome-ignore lint/correctness/useExhaustiveDependencies: handleViewOptionsChanged and onNodeClick are intentionally omitted
803
+ useEffect(() => {
804
+ const runResultType = run?.type;
805
+ if (!interactive) {
806
+ // Skip the following logic if the view is not interactive
807
+ return;
808
+ }
809
+ if (!isRunResultOpen) {
810
+ // Skip the following logic if the run result is not open
811
+ return;
812
+ }
813
+ if (
814
+ !runResultType ||
815
+ ["query_diff", "query", "row_count"].includes(runResultType)
816
+ ) {
817
+ // Skip the following logic if the run result type is not related to a node
818
+ return;
819
+ }
820
+
821
+ if (!selectMode) {
822
+ // Skip the following logic if the select mode is not single
823
+ let selectedRunModel = undefined;
824
+ if (
825
+ isTopKDiffRun(run) ||
826
+ isProfileDiffRun(run) ||
827
+ isHistogramDiffRun(run) ||
828
+ isValueDiffRun(run) ||
829
+ isValueDiffDetailRun(run)
830
+ ) {
831
+ selectedRunModel = run.params?.model;
832
+ }
833
+
834
+ // Create a mock MouseEvent
835
+ const mockEvent = new MouseEvent("click", {
836
+ bubbles: true,
837
+ cancelable: true,
838
+ view: window,
839
+ }) as unknown as React.MouseEvent;
840
+
841
+ if (selectedRunModel) {
842
+ // If the run result is related to a node, select the node to show NodeView
843
+ const node = findNodeByName(selectedRunModel);
844
+ if (!node) {
845
+ // Cannot find the node in the current nodes, try to change the view mode to 'all'
846
+ void handleViewOptionsChanged({
847
+ ...viewOptions,
848
+ view_mode: "all",
849
+ });
850
+ } else if (isLineageGraphNode(node) && focusedNode !== node.data.data) {
851
+ // Only select the node if it is not already selected
852
+ onNodeClick(mockEvent, node);
853
+ }
854
+ } else {
855
+ // If the run result is not related to a node, close the NodeView
856
+ onNodeViewClosed();
857
+ }
858
+ }
859
+ // handleViewOptionsChanged and onNodeClick are intentionally omitted to prevent
860
+ // unnecessary re-runs. These functions are called conditionally within the effect
861
+ // and don't need to trigger the effect when they change.
862
+ // eslint-disable-next-line react-hooks/exhaustive-deps
863
+ }, [
864
+ run,
865
+ viewOptions,
866
+ isRunResultOpen,
867
+ selectMode,
868
+ findNodeByName,
869
+ focusedNode,
870
+ interactive,
871
+ ]);
872
+
873
+ const selectParentNodes = (nodeId: string, degree = 1000) => {
874
+ if (selectMode === "action_result" || lineageGraph === undefined) return;
875
+
876
+ if (!selectMode) {
877
+ setSelectMode("selecting");
878
+ multiNodeAction.reset();
879
+ if (viewOptions.column_level_lineage) {
880
+ void handleViewOptionsChanged({
881
+ ...viewOptions,
882
+ column_level_lineage: undefined,
883
+ });
884
+ }
885
+ }
886
+
887
+ const upstream = selectUpstream(lineageGraph, [nodeId], degree);
888
+ setSelectedNodeIds(union(selectedNodeIds, upstream));
889
+ };
890
+
891
+ const selectChildNodes = (nodeId: string, degree = 1000) => {
892
+ if (selectMode === "action_result" || lineageGraph === undefined) return;
893
+
894
+ if (!selectMode) {
895
+ setSelectMode("selecting");
896
+ multiNodeAction.reset();
897
+ if (viewOptions.column_level_lineage) {
898
+ void handleViewOptionsChanged({
899
+ ...viewOptions,
900
+ column_level_lineage: undefined,
901
+ });
902
+ }
903
+ }
904
+
905
+ const downstream = selectDownstream(lineageGraph, [nodeId], degree);
906
+ setSelectedNodeIds(union(selectedNodeIds, downstream));
907
+ };
908
+
909
+ const onNodeContextMenu = (
910
+ event: React.MouseEvent,
911
+ node: LineageGraphNodes,
912
+ ) => {
913
+ if (!interactive) {
914
+ return;
915
+ }
916
+ if (selectMode === "action_result") {
917
+ return;
918
+ }
919
+ // Only show context menu when selectMode is action
920
+ // Prevent native context menu from showing
921
+ event.preventDefault();
922
+ const reactFlowDiv = refReactFlow.current as unknown as HTMLDivElement;
923
+ const pane = reactFlowDiv.getBoundingClientRect();
924
+ const x = event.clientX - pane.left;
925
+ const y = event.clientY - pane.top + reactFlowDiv.offsetTop;
926
+ lineageViewContextMenu.showContextMenu(x, y, node);
927
+ };
928
+
929
+ const selectNode = (nodeId: string) => {
930
+ if (!selectMode) {
931
+ if (!lineageGraph) {
932
+ return;
933
+ }
934
+
935
+ setSelectedNodeIds(new Set([nodeId]));
936
+ setSelectMode("selecting");
937
+ setFocusedNodeId(undefined);
938
+ multiNodeAction.reset();
939
+ } else if (selectMode === "selecting") {
940
+ const newSelectedNodeIds = new Set(selectedNodeIds);
941
+ if (selectedNodeIds.has(nodeId)) {
942
+ newSelectedNodeIds.delete(nodeId);
943
+ } else {
944
+ newSelectedNodeIds.add(nodeId);
945
+ }
946
+
947
+ setSelectedNodeIds(newSelectedNodeIds);
948
+ if (newSelectedNodeIds.size === 0) {
949
+ setSelectMode(undefined);
950
+ }
951
+ }
952
+ };
953
+ const deselect = () => {
954
+ setSelectMode(undefined);
955
+ setSelectedNodeIds(new Set());
956
+ setFocusedNodeId(undefined);
957
+ closeRunResult();
958
+ refetchRunsAggregated?.();
959
+ };
960
+
961
+ const contextValue: LineageViewContextType = {
962
+ interactive,
963
+ nodes,
964
+ focusedNode,
965
+ selectedNodes,
966
+ viewOptions,
967
+ showContextMenu: onNodeContextMenu,
968
+ onViewOptionsChanged: handleViewOptionsChanged,
969
+ selectMode,
970
+ selectNode,
971
+ selectParentNodes,
972
+ selectChildNodes,
973
+ deselect,
974
+ isNodeHighlighted: (nodeId: string) => highlighted.has(nodeId),
975
+ isNodeSelected: (nodeId: string) => selectedNodeIds.has(nodeId),
976
+ isEdgeHighlighted: (source, target) => {
977
+ if (!cll) {
978
+ return highlighted.has(source) && highlighted.has(target);
979
+ } else {
980
+ if (!(source in cll.current.parent_map)) {
981
+ return false;
982
+ }
983
+ return target in cll.current.parent_map[source];
984
+ }
985
+ },
986
+ isNodeShowingChangeAnalysis: (nodeId: string) => {
987
+ if (!lineageGraph) {
988
+ return false;
989
+ }
990
+
991
+ const node =
992
+ nodeId in lineageGraph.nodes ? lineageGraph.nodes[nodeId] : undefined;
993
+
994
+ if (viewOptions.column_level_lineage?.change_analysis) {
995
+ const cll = viewOptions.column_level_lineage;
996
+
997
+ if (cll.node_id && !cll.column) {
998
+ return cll.node_id === nodeId && !!node?.data.changeStatus;
999
+ } else {
1000
+ return !!node?.data.changeStatus;
1001
+ }
1002
+ }
1003
+
1004
+ return false;
1005
+ },
1006
+ getNodeAction: (nodeId: string) => {
1007
+ return multiNodeAction.actionState.actions[nodeId];
1008
+ },
1009
+ getNodeColumnSet: (nodeId: string) => {
1010
+ if (!(nodeId in nodeColumnSetMap)) {
1011
+ return new Set<string>();
1012
+ }
1013
+
1014
+ return new Set(nodeColumnSetMap[nodeId]);
1015
+ },
1016
+ runRowCount: async () => {
1017
+ if (selectMode === "selecting") {
1018
+ await multiNodeAction.runRowCount();
1019
+ trackMultiNodesAction({ type: "row_count", selected: "multi" });
1020
+ } else if (focusedNode) {
1021
+ runAction(
1022
+ "row_count",
1023
+ { node_names: [focusedNode.data.name] },
1024
+ { showForm: false, showLast: false },
1025
+ );
1026
+ trackMultiNodesAction({ type: "row_count", selected: "single" });
1027
+ } else {
1028
+ runAction("row_count", {
1029
+ select: viewOptions.select,
1030
+ exclude: viewOptions.exclude,
1031
+ packages: viewOptions.packages,
1032
+ view_mode: viewOptions.view_mode,
1033
+ });
1034
+ trackMultiNodesAction({ type: "row_count", selected: "none" });
1035
+ }
1036
+ },
1037
+ runRowCountDiff: async () => {
1038
+ if (selectMode === "selecting") {
1039
+ await multiNodeAction.runRowCountDiff();
1040
+ trackMultiNodesAction({ type: "row_count_diff", selected: "multi" });
1041
+ } else if (focusedNode) {
1042
+ runAction(
1043
+ "row_count_diff",
1044
+ { node_names: [focusedNode.data.name] },
1045
+ { showForm: false, showLast: false },
1046
+ );
1047
+ trackMultiNodesAction({ type: "row_count_diff", selected: "single" });
1048
+ } else {
1049
+ runAction("row_count_diff", {
1050
+ select: viewOptions.select,
1051
+ exclude: viewOptions.exclude,
1052
+ packages: viewOptions.packages,
1053
+ view_mode: viewOptions.view_mode,
1054
+ });
1055
+ trackMultiNodesAction({ type: "row_count_diff", selected: "none" });
1056
+ }
1057
+ },
1058
+ runValueDiff: async () => {
1059
+ if (focusedNode) {
1060
+ runAction(
1061
+ "value_diff",
1062
+ {
1063
+ model: focusedNode.data.name,
1064
+ },
1065
+ { showForm: true, showLast: false },
1066
+ );
1067
+ trackMultiNodesAction({ type: "value_diff", selected: "single" });
1068
+ } else {
1069
+ const nodeCount =
1070
+ selectMode === "selecting"
1071
+ ? selectedNodes.length
1072
+ : filteredNodeIds.length;
1073
+ if (await valueDiffAlertDialog.confirm(nodeCount)) {
1074
+ await multiNodeAction.runValueDiff();
1075
+ trackMultiNodesAction({
1076
+ type: "value_diff",
1077
+ selected: selectMode === "selecting" ? "multi" : "none",
1078
+ });
1079
+ }
1080
+ }
1081
+ },
1082
+ addLineageDiffCheck: async () => {
1083
+ let check: Check | undefined = undefined;
1084
+ if (selectMode === "selecting") {
1085
+ check = await multiNodeAction.addLineageDiffCheck();
1086
+ deselect();
1087
+ trackMultiNodesAction({ type: "lineage_diff", selected: "multi" });
1088
+ } else if (!focusedNode) {
1089
+ check = await createLineageDiffCheck(viewOptions, apiClient);
1090
+ trackMultiNodesAction({ type: "lineage_diff", selected: "none" });
1091
+ }
1092
+
1093
+ if (check) {
1094
+ navToCheck(check);
1095
+ }
1096
+ },
1097
+ addSchemaDiffCheck: async () => {
1098
+ let check: Check | undefined = undefined;
1099
+
1100
+ if (selectMode === "selecting") {
1101
+ if (selectedNodes.length > 0) {
1102
+ check = await multiNodeAction.addSchemaDiffCheck();
1103
+ deselect();
1104
+ trackMultiNodesAction({ type: "schema_diff", selected: "multi" });
1105
+ }
1106
+ } else if (focusedNode) {
1107
+ check = await createSchemaDiffCheck(
1108
+ {
1109
+ node_id: focusedNode.id,
1110
+ },
1111
+ apiClient,
1112
+ );
1113
+ trackMultiNodesAction({ type: "schema_diff", selected: "single" });
1114
+ } else {
1115
+ check = await createSchemaDiffCheck(
1116
+ {
1117
+ select: viewOptions.select,
1118
+ exclude: viewOptions.exclude,
1119
+ packages: viewOptions.packages,
1120
+ view_mode: viewOptions.view_mode,
1121
+ },
1122
+ apiClient,
1123
+ );
1124
+ trackMultiNodesAction({ type: "schema_diff", selected: "none" });
1125
+ }
1126
+
1127
+ if (check) {
1128
+ navToCheck(check);
1129
+ }
1130
+ },
1131
+ cancel: multiNodeAction.cancel,
1132
+ actionState: multiNodeAction.actionState,
1133
+
1134
+ // Column Level Lineage
1135
+ centerNode,
1136
+ cll,
1137
+ showColumnLevelLineage,
1138
+ resetColumnLevelLineage,
1139
+ };
1140
+
1141
+ if (isLoading) {
1142
+ return (
1143
+ <Box
1144
+ sx={{
1145
+ width: "100%",
1146
+ height: "100%",
1147
+ display: "flex",
1148
+ alignItems: "center",
1149
+ justifyContent: "center",
1150
+ }}
1151
+ >
1152
+ <CircularProgress size={48} />
1153
+ </Box>
1154
+ );
1155
+ }
1156
+
1157
+ if (error) {
1158
+ return (
1159
+ <Box
1160
+ sx={{
1161
+ height: "100%",
1162
+ display: "flex",
1163
+ alignItems: "center",
1164
+ justifyContent: "center",
1165
+ }}
1166
+ >
1167
+ <Stack alignItems="center" spacing={1}>
1168
+ <Box>
1169
+ Failed to load lineage data. This could be because the server has
1170
+ been terminated or there is a network error.
1171
+ </Box>
1172
+ <Box>[Reason: {error}]</Box>
1173
+ <Button
1174
+ color="iochmara"
1175
+ variant="contained"
1176
+ onClick={() => {
1177
+ if (retchLineageGraph) {
1178
+ retchLineageGraph();
1179
+ }
1180
+ }}
1181
+ >
1182
+ Retry
1183
+ </Button>
1184
+ </Stack>
1185
+ </Box>
1186
+ );
1187
+ }
1188
+
1189
+ if (!lineageGraph || nodes == initialNodes) {
1190
+ return <></>;
1191
+ }
1192
+
1193
+ if (viewMode === "changed_models" && !lineageGraph.modifiedSet.length) {
1194
+ return (
1195
+ <Box
1196
+ sx={{
1197
+ height: "100%",
1198
+ display: "flex",
1199
+ alignItems: "center",
1200
+ justifyContent: "center",
1201
+ }}
1202
+ >
1203
+ <Stack alignItems="center" spacing={1}>
1204
+ <>No change detected</>
1205
+ <Button
1206
+ color="iochmara"
1207
+ variant="contained"
1208
+ onClick={async () => {
1209
+ await handleViewOptionsChanged({
1210
+ ...viewOptions,
1211
+ view_mode: "all",
1212
+ });
1213
+ }}
1214
+ >
1215
+ Show all nodes
1216
+ </Button>
1217
+ </Stack>
1218
+ </Box>
1219
+ );
1220
+ }
1221
+ return (
1222
+ <LineageViewContext.Provider value={contextValue}>
1223
+ <HSplit
1224
+ sizes={focusedNode ? [70, 30] : [100, 0]}
1225
+ minSize={focusedNode ? 400 : 0}
1226
+ gutterSize={focusedNode ? 5 : 0}
1227
+ style={{ height: "100%", width: "100%" }}
1228
+ >
1229
+ <Stack
1230
+ ref={refResize}
1231
+ divider={<Divider sx={{ borderColor: "grey.200" }} />}
1232
+ spacing={0}
1233
+ sx={{ contain: "strict", position: "relative" }}
1234
+ >
1235
+ {interactive && (
1236
+ <>
1237
+ <LineageViewTopBar />
1238
+ {featureToggles.mode === "metadata only" && (
1239
+ <SetupConnectionBanner />
1240
+ )}
1241
+ </>
1242
+ )}
1243
+ <ReactFlow
1244
+ proOptions={{
1245
+ hideAttribution: true,
1246
+ }}
1247
+ nodeTypes={nodeTypes}
1248
+ edgeTypes={edgeTypes}
1249
+ nodes={nodes}
1250
+ edges={edges}
1251
+ onNodesChange={onNodesChange}
1252
+ onEdgesChange={onEdgesChange}
1253
+ onNodeClick={onNodeClick}
1254
+ onNodeContextMenu={onNodeContextMenu}
1255
+ onClick={closeContextMenu}
1256
+ onInit={async () => {
1257
+ if (isModelsChanged) {
1258
+ await reactFlow.fitView();
1259
+ } else {
1260
+ const bounds = getNodesBounds(nodes, {});
1261
+ await reactFlow.setCenter(
1262
+ bounds.x + bounds.width / 2,
1263
+ bounds.y + bounds.height / 2,
1264
+ {
1265
+ zoom: 1,
1266
+ },
1267
+ );
1268
+ }
1269
+ }}
1270
+ maxZoom={1}
1271
+ minZoom={0.1}
1272
+ nodesDraggable={interactive}
1273
+ ref={refReactFlow as unknown as Ref<HTMLDivElement>}
1274
+ colorMode={isDark ? "dark" : "light"}
1275
+ >
1276
+ <Background
1277
+ id="lineage-bg"
1278
+ variant={BackgroundVariant.Dots}
1279
+ color={isDark ? colors.neutral[700] : colors.neutral[300]}
1280
+ gap={20}
1281
+ size={2}
1282
+ />
1283
+ <Controls
1284
+ showInteractive={false}
1285
+ position="top-right"
1286
+ className={IGNORE_SCREENSHOT_CLASS}
1287
+ style={{
1288
+ backgroundColor: isDark ? colors.neutral[700] : undefined,
1289
+ borderColor: isDark ? colors.neutral[600] : undefined,
1290
+ }}
1291
+ >
1292
+ <ControlButton
1293
+ title="copy image"
1294
+ onClick={async () => {
1295
+ await copyToClipboard();
1296
+ trackCopyToClipboard({
1297
+ type: viewMode,
1298
+ from: "lineage_view",
1299
+ });
1300
+ }}
1301
+ style={{
1302
+ backgroundColor: isDark ? colors.neutral[700] : undefined,
1303
+ color: isDark ? colors.neutral[200] : undefined,
1304
+ }}
1305
+ >
1306
+ <Box component={FiCopy} />
1307
+ </ControlButton>
1308
+ </Controls>
1309
+ <ImageDownloadModal />
1310
+ <Panel position="bottom-left">
1311
+ <Stack spacing="5px">
1312
+ {isModelsChanged && <ChangeStatusLegend />}
1313
+ {viewOptions.column_level_lineage && (
1314
+ <ColumnLevelLineageLegend />
1315
+ )}
1316
+ </Stack>
1317
+ </Panel>
1318
+ <Panel position="top-center">
1319
+ <LineageViewNotification
1320
+ notification={
1321
+ singleEnv ? <BaseEnvironmentSetupNotification /> : null
1322
+ }
1323
+ type={"info"}
1324
+ />
1325
+ </Panel>
1326
+ <Panel position="top-left">
1327
+ <Stack spacing="5px">
1328
+ <ColumnLevelLineageControl action={actionGetCll} />
1329
+ {nodes.length == 0 && (
1330
+ <Typography
1331
+ sx={{ fontSize: "1.25rem", color: "grey", opacity: 0.5 }}
1332
+ >
1333
+ No nodes
1334
+ </Typography>
1335
+ )}
1336
+ </Stack>
1337
+ </Panel>
1338
+ <MiniMap
1339
+ nodeColor={nodeColor}
1340
+ nodeStrokeWidth={3}
1341
+ zoomable
1342
+ pannable
1343
+ bgColor={isDark ? colors.neutral[800] : undefined}
1344
+ maskColor={
1345
+ isDark ? `${colors.neutral[900]}99` : `${colors.neutral[100]}99`
1346
+ }
1347
+ />
1348
+ {selectMode === "action_result" && (
1349
+ <Panel
1350
+ position="bottom-center"
1351
+ className={IGNORE_SCREENSHOT_CLASS}
1352
+ >
1353
+ <ActionControl
1354
+ onClose={() => {
1355
+ deselect();
1356
+ }}
1357
+ />
1358
+ </Panel>
1359
+ )}
1360
+ </ReactFlow>
1361
+ <LineageViewContextMenu {...lineageViewContextMenu.props} />
1362
+ </Stack>
1363
+ {focusedNode ? (
1364
+ <Box
1365
+ sx={{
1366
+ borderLeft: "solid 1px",
1367
+ borderColor: "divider",
1368
+ height: "100%",
1369
+ }}
1370
+ >
1371
+ <NodeView node={focusedNode} onCloseNode={onNodeViewClosed} />
1372
+ </Box>
1373
+ ) : (
1374
+ <Box></Box>
1375
+ )}
1376
+ </HSplit>
1377
+ {valueDiffAlertDialog.AlertDialog}
1378
+ </LineageViewContext.Provider>
1379
+ );
1380
+ }
1381
+
1382
+ export const LineageView = forwardRef<LineageViewRef, LineageViewProps>(
1383
+ PrivateLineageView,
1384
+ );