Skip to content

Commit 581c5ae

Browse files
authored
Feat: Display external table in lineage (#857)
1 parent b3bcb5d commit 581c5ae

File tree

6 files changed

+243
-243
lines changed

6 files changed

+243
-243
lines changed

web/client/.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module.exports = {
1818
'react/jsx-uses-react': OFF,
1919
'react/react-in-jsx-scope': OFF,
2020
'no-use-before-define': OFF,
21+
'@typescript-eslint/no-non-null-assertion': OFF,
2122
'@typescript-eslint/no-use-before-define': [
2223
ERROR,
2324
{

web/client/src/context/editor.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { type LineageColumn } from '@api/client'
12
import { uid } from '@utils/index'
23
import { create } from 'zustand'
34
import useLocalStorage from '~/hooks/useLocalStorage'
@@ -11,13 +12,7 @@ export interface Dialect {
1112

1213
export interface Lineage {
1314
models: string[]
14-
columns?: Record<
15-
string,
16-
{
17-
source?: string
18-
models?: Record<string, string[]>
19-
}
20-
>
15+
columns?: Record<string, LineageColumn>
2116
}
2217

2318
interface EditorStore {

web/client/src/library/components/graph/Graph.tsx

Lines changed: 69 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,17 @@ import {
2828
createGraphLayout,
2929
toNodeOrEdgeId,
3030
type GraphNodeData,
31-
mergeLineage,
31+
mergeLineageWithColumns,
32+
hasNoModels,
33+
mergeConnections,
3234
} from './help'
3335
import {
3436
debounceAsync,
3537
debounceSync,
3638
isArrayEmpty,
3739
isArrayNotEmpty,
3840
isFalse,
41+
isNil,
3942
isTrue,
4043
} from '../../../utils'
4144
import { EnumSize, EnumVariant } from '~/types/enum'
@@ -45,6 +48,7 @@ import {
4548
type Column,
4649
columnLineageApiLineageModelNameColumnNameGet,
4750
type ColumnLineageApiLineageModelNameColumnNameGet200,
51+
type LineageColumn,
4852
} from '@api/client'
4953
import Loading from '@components/loading/Loading'
5054
import Spinner from '@components/logo/Spinner'
@@ -57,7 +61,6 @@ import { useLineageFlow } from './context'
5761
import { useQueryClient } from '@tanstack/react-query'
5862
import Input from '@components/input/Input'
5963
import { type ResponseWithDetail } from '@api/instance'
60-
import { Divider } from '@components/divider/Divider'
6164

6265
const ModelColumnDisplay = memo(function ModelColumnDisplay({
6366
columnName,
@@ -108,7 +111,10 @@ const ModelNodeHandles = memo(function ModelNodeHandles({
108111
const updateNodeInternals = useUpdateNodeInternals()
109112

110113
useEffect(() => {
111-
updateNodeInternals(nodeId)
114+
// TODO: This is a hack to fix the issue where the handles are not rendered yet
115+
setTimeout(() => {
116+
updateNodeInternals(nodeId)
117+
}, 100)
112118
}, [hasLeft, hasRight])
113119

114120
return (
@@ -140,7 +146,7 @@ const ModelNodeHandles = memo(function ModelNodeHandles({
140146
className={clsx(
141147
'w-2 h-2 rounded-full !bg-secondary-500 dark:!bg-primary-500',
142148
)}
143-
></Handle>
149+
/>
144150
)}
145151
</div>
146152
)
@@ -291,27 +297,7 @@ const ModelColumn = memo(function ModelColumn({
291297

292298
debouncedGetColumnLineage(column.name)
293299
.then(data => {
294-
const models =
295-
data as ColumnLineageApiLineageModelNameColumnNameGet200
296-
297-
setIsEmpty(() => {
298-
for (const modelName in models) {
299-
const model = models[modelName]
300-
301-
if (model == null) continue
302-
303-
for (const columnName in model) {
304-
const lineage = model[columnName]
305-
306-
if (lineage == null) continue
307-
308-
return Object.keys(lineage.models ?? {}).length === 0
309-
}
310-
}
311-
312-
return false
313-
})
314-
300+
setIsEmpty(hasNoModels())
315301
updateColumnLineage(data)
316302
})
317303
.catch(error => {
@@ -397,7 +383,6 @@ const ModelColumns = memo(function ModelColumns({
397383
const queryClient = useQueryClient()
398384

399385
const {
400-
models,
401386
connections,
402387
isActiveColumn,
403388
setConnections,
@@ -456,75 +441,16 @@ const ModelColumns = memo(function ModelColumns({
456441

457442
const updateColumnLineage = useCallback(
458443
function updateColumnLineage(
459-
columnLineage: ColumnLineageApiLineageModelNameColumnNameGet200,
444+
columnLineage: Record<string, Record<string, LineageColumn>> = {},
460445
): void {
461-
setLineage(lineage => mergeLineage(models, lineage, columnLineage))
462-
setConnections(connections => {
463-
for (const modelName in columnLineage) {
464-
const model = columnLineage[modelName]
465-
466-
if (model == null) continue
467-
468-
for (const columnName in model) {
469-
const column = model[columnName]
470-
471-
if (column?.models == null) continue
472-
473-
const connectionSource = connections.get(
474-
toNodeOrEdgeId(modelName, columnName),
475-
) ?? {
476-
left: [],
477-
right: [],
478-
}
479-
480-
Object.entries(column.models).forEach(([id, columns]) => {
481-
columns.forEach(column => {
482-
const connectionTarget = connections.get(
483-
toNodeOrEdgeId(id, column),
484-
) ?? {
485-
left: [],
486-
right: [],
487-
}
488-
489-
connectionTarget.right = Array.from(
490-
new Set(
491-
connectionTarget.right.concat(
492-
toNodeOrEdgeId(modelName, columnName),
493-
),
494-
),
495-
)
496-
connectionSource.left = Array.from(
497-
new Set(
498-
connectionSource.left.concat(toNodeOrEdgeId(id, column)),
499-
),
500-
)
501-
502-
connections.set(toNodeOrEdgeId(id, column), connectionTarget)
503-
connections.set(
504-
toNodeOrEdgeId(modelName, columnName),
505-
connectionSource,
506-
)
507-
})
508-
})
509-
510-
const modelColumnConnectionsLeft = (
511-
connections.get(toNodeOrEdgeId(modelName, columnName))?.left ?? []
512-
).map(id => toNodeOrEdgeId('right', id))
513-
const modelColumnConnectionsRight = (
514-
connections.get(toNodeOrEdgeId(modelName, columnName))?.right ??
515-
[]
516-
).map(id => toNodeOrEdgeId('left', id))
517-
518-
addActiveEdges(
519-
modelColumnConnectionsLeft.concat(modelColumnConnectionsRight),
520-
)
521-
}
522-
}
523-
524-
return new Map(connections)
525-
})
446+
setLineage(lineage =>
447+
mergeLineageWithColumns(structuredClone(lineage), columnLineage),
448+
)
449+
setConnections(connections =>
450+
mergeConnections(columnLineage, connections, addActiveEdges),
451+
)
526452
},
527-
[models, addActiveEdges, setConnections],
453+
[addActiveEdges, setConnections],
528454
)
529455

530456
const isSelectManually = useCallback(
@@ -644,7 +570,6 @@ const ModelColumns = memo(function ModelColumns({
644570
/>
645571
))}
646572
</div>
647-
<Divider className="border-primary-500" />
648573
{columns.length > limit && (
649574
<div className="py-2 flex justify-center bg-theme-lighter">
650575
<Button
@@ -710,27 +635,23 @@ function ModelColumnLineage({
710635
'border-4 border-brand-500': [model.name],
711636
}
712637

713-
void load()
714-
715-
async function load(): Promise<void> {
716-
const nodesAndEdges = getNodesAndEdges({
717-
lineage,
718-
highlightedNodes: highlightedNodes ?? highlightedNodesDefault,
719-
models,
720-
nodes,
721-
edges,
722-
model,
723-
withColumns,
724-
})
638+
const nodesAndEdges = getNodesAndEdges({
639+
lineage,
640+
highlightedNodes: highlightedNodes ?? highlightedNodesDefault,
641+
models,
642+
nodes,
643+
edges,
644+
model,
645+
withColumns,
646+
})
725647

726-
void createGraphLayout(nodesAndEdges).then(layout => {
727-
setNodes(layout.nodes)
728-
setEdges(toggleEdge(layout.edges))
729-
setIsBuildingLayout(
730-
isArrayEmpty(layout.nodes) || isArrayEmpty(layout.edges),
731-
)
732-
})
733-
}
648+
void createGraphLayout(nodesAndEdges).then(layout => {
649+
setNodes(layout.nodes)
650+
setEdges(toggleEdge(layout.edges))
651+
setIsBuildingLayout(
652+
isArrayEmpty(layout.nodes) || isArrayEmpty(layout.edges),
653+
)
654+
})
734655
}, [model.name, models, highlightedNodes, lineage])
735656

736657
useEffect(() => {
@@ -777,16 +698,30 @@ function ModelNode({
777698
sourcePosition,
778699
targetPosition,
779700
}: NodeProps & { data: GraphNodeData }): JSX.Element {
780-
const { models, withColumns, handleClickModel } = useLineageFlow()
701+
const {
702+
models,
703+
withColumns,
704+
handleClickModel,
705+
lineage = {},
706+
} = useLineageFlow()
781707

782708
const { model, columns } = useMemo(() => {
783709
const model = models.get(id)
710+
const columns = model?.columns ?? []
711+
712+
Object.keys(lineage[id]?.columns ?? {}).forEach((column: string) => {
713+
const found = columns.find(({ name }) => name === column)
714+
715+
if (isNil(found)) {
716+
columns.push({ name: column, type: 'UNKNOWN' })
717+
}
718+
})
784719

785720
return {
786721
model,
787-
columns: model?.columns ?? [],
722+
columns,
788723
}
789-
}, [id, models])
724+
}, [id, models, lineage])
790725

791726
const handleClick = useCallback(
792727
(e: MouseEvent) => {
@@ -802,12 +737,15 @@ function ModelNode({
802737
)
803738
const splat = data.highlightedNodes?.['*']
804739
const isInteractive = isTrue(data.isInteractive) && handleClickModel != null
740+
const isTable = data.type === 'table'
741+
const showColumns = withColumns && isArrayNotEmpty(columns)
805742

806743
return (
807744
<div
808745
className={clsx(
809-
'text-xs font-semibold text-secondary-500 dark:text-primary-100 rounded-xl shadow-lg relative z-1',
746+
'text-xs font-semibold rounded-xl shadow-lg relative z-1',
810747
highlighted == null ? splat : highlighted,
748+
isTable ? '' : 'text-secondary-500 dark:text-primary-100',
811749
)}
812750
>
813751
<div className="drag-handle">
@@ -816,24 +754,32 @@ function ModelNode({
816754
type={model?.type}
817755
label={data.label}
818756
className={clsx(
819-
'bg-secondary-100 dark:bg-primary-900 py-2',
820-
withColumns ? 'rounded-t-lg' : 'rounded-lg',
757+
'py-2',
758+
showColumns ? 'rounded-t-lg' : 'rounded-lg',
759+
isTable ? 'bg-neutral-600' : 'bg-secondary-100 dark:bg-primary-900',
821760
)}
822761
hasLeft={sourcePosition === Position.Left}
823762
hasRight={targetPosition === Position.Right}
824763
handleClick={isInteractive ? handleClick : undefined}
825764
/>
826765
</div>
827-
{withColumns && isArrayNotEmpty(columns) && (
766+
{showColumns && isArrayNotEmpty(columns) && (
828767
<>
829768
<ModelColumns
830769
className="max-h-[15rem]"
831770
nodeId={id}
832771
columns={columns}
833-
disabled={model?.type === 'python'}
772+
disabled={model?.type === 'python' || data.type !== 'model'}
834773
withHandles={true}
835774
/>
836-
<div className="rounded-b-lg bg-secondary-100 dark:bg-primary-900 py-1"></div>
775+
<div
776+
className={clsx(
777+
'rounded-b-lg py-1',
778+
isTable
779+
? 'bg-neutral-600'
780+
: 'bg-secondary-100 dark:bg-primary-900',
781+
)}
782+
></div>
837783
</>
838784
)}
839785
</div>

web/client/src/library/components/graph/ModelLineage.tsx

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import { memo, useCallback, useEffect } from 'react'
44
import { ModelColumnLineage } from './Graph'
55
import { type ModelSQLMeshModel } from '@models/sqlmesh-model'
66
import { useLineageFlow } from './context'
7-
import { type Lineage } from '@context/editor'
8-
import { mergeLineage } from './help'
7+
import { mergeLineageWithModels } from './help'
98

109
const ModelLineage = memo(function ModelLineage({
1110
model,
@@ -18,7 +17,7 @@ const ModelLineage = memo(function ModelLineage({
1817
highlightedNodes?: Record<string, string[]>
1918
className?: string
2019
}): JSX.Element {
21-
const { clearActiveEdges, models, lineage, setLineage } = useLineageFlow()
20+
const { clearActiveEdges, setLineage } = useLineageFlow()
2221

2322
const { data: dataLineage, refetch: getModelLineage } = useApiModelLineage(
2423
model.name,
@@ -37,21 +36,9 @@ const ModelLineage = memo(function ModelLineage({
3736
if (dataLineage == null) {
3837
setLineage(undefined)
3938
} else {
40-
const lineageModels = Object.keys(dataLineage).reduce(
41-
(acc: Record<string, Lineage>, key) => {
42-
if (models.has(key)) {
43-
acc[key] = {
44-
models: (dataLineage[key] ?? []).filter(name => models.has(name)),
45-
columns: lineage?.[key]?.columns ?? undefined,
46-
}
47-
}
48-
49-
return acc
50-
},
51-
{},
39+
setLineage(lineage =>
40+
mergeLineageWithModels(structuredClone(lineage), dataLineage),
5241
)
53-
54-
setLineage(mergeLineage(models, lineageModels))
5542
}
5643
}, [dataLineage])
5744

0 commit comments

Comments
 (0)