Skip to content

Commit a07a01d

Browse files
tjirabclaude
andcommitted
Feat: Add "Only Direct Neighbors" filter to VSCode lineage panel
Selecting a model in the VSCode lineage panel renders the full transitive lineage, which can be dense for hub models. This adds a new toggle to the existing settings menu that limits the view to the selected model plus its direct parents and direct children. The filter is implemented as a new withOnlyDirect boolean on LineageFlowContext, paired with a directNeighbors set memoized from the lineage map and mainNode. When enabled, getUpdatedNodes and getUpdatedEdges hide anything outside that set, leaving existing withConnected/withImpacted/withSecondary logic untouched. Refs #5811 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Bart Schuijt <schuijt.bart@gmail.com>
1 parent 4dac2b3 commit a07a01d

4 files changed

Lines changed: 82 additions & 6 deletions

File tree

vscode/react/src/components/graph/ModelLineage.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ function ModelColumnLineage(): JSX.Element {
209209
withConnected,
210210
withImpacted,
211211
withSecondary,
212+
withOnlyDirect,
213+
directNeighbors,
212214
hasBackground,
213215
activeEdges,
214216
connectedNodes,
@@ -217,6 +219,7 @@ function ModelColumnLineage(): JSX.Element {
217219
handleError,
218220
setActiveNodes,
219221
setWithColumns,
222+
setWithOnlyDirect,
220223
} = useLineageFlow()
221224

222225
const { setCenter } = useReactFlow()
@@ -252,6 +255,8 @@ function ModelColumnLineage(): JSX.Element {
252255
withConnected,
253256
withImpacted,
254257
withSecondary,
258+
withOnlyDirect,
259+
directNeighbors,
255260
)
256261
const newEdges = getUpdatedEdges(
257262
allEdges,
@@ -264,6 +269,8 @@ function ModelColumnLineage(): JSX.Element {
264269
withConnected,
265270
withImpacted,
266271
withSecondary,
272+
withOnlyDirect,
273+
directNeighbors,
267274
)
268275
const createLayout = createGraphLayout({
269276
nodesMap,
@@ -324,6 +331,8 @@ function ModelColumnLineage(): JSX.Element {
324331
withConnected,
325332
withImpacted,
326333
withSecondary,
334+
withOnlyDirect,
335+
directNeighbors,
327336
)
328337

329338
const newEdges = getUpdatedEdges(
@@ -337,6 +346,8 @@ function ModelColumnLineage(): JSX.Element {
337346
withConnected,
338347
withImpacted,
339348
withSecondary,
349+
withOnlyDirect,
350+
directNeighbors,
340351
)
341352

342353
setEdges(newEdges)
@@ -353,6 +364,8 @@ function ModelColumnLineage(): JSX.Element {
353364
withConnected,
354365
withImpacted,
355366
withSecondary,
367+
withOnlyDirect,
368+
directNeighbors,
356369
withColumns,
357370
mainNode,
358371
])
@@ -395,6 +408,8 @@ function ModelColumnLineage(): JSX.Element {
395408
<SettingsControl
396409
showColumns={withColumns}
397410
onWithColumnsChange={setWithColumns}
411+
onlyDirect={withOnlyDirect}
412+
onOnlyDirectChange={setWithOnlyDirect}
398413
/>
399414
</Controls>
400415
<Background

vscode/react/src/components/graph/SettingsControl.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@ import clsx from 'clsx'
66
interface SettingsControlProps {
77
showColumns: boolean
88
onWithColumnsChange: (value: boolean) => void
9+
onlyDirect: boolean
10+
onOnlyDirectChange: (value: boolean) => void
911
}
1012

1113
export function SettingsControl({
1214
showColumns,
1315
onWithColumnsChange,
16+
onlyDirect,
17+
onOnlyDirectChange,
1418
}: SettingsControlProps): JSX.Element {
19+
const itemClass = clsx(
20+
'group flex w-full items-center px-2 py-1 text-sm',
21+
'text-[var(--vscode-button-foreground)]',
22+
'hover:bg-[var(--vscode-button-background)] bg-[var(--vscode-button-hoverBackground)]',
23+
)
1524
return (
1625
<Menu
1726
as="div"
@@ -29,11 +38,7 @@ export function SettingsControl({
2938
<MenuItems className="absolute bottom-0 left-full ml-2 w-56 origin-bottom-left divide-y bg-theme shadow-lg focus:outline-none z-50">
3039
<MenuItem
3140
as="button"
32-
className={clsx(
33-
'group flex w-full items-center px-2 py-1 text-sm',
34-
'text-[var(--vscode-button-foreground)]',
35-
'hover:bg-[var(--vscode-button-background)] bg-[var(--vscode-button-hoverBackground)]',
36-
)}
41+
className={itemClass}
3742
onClick={() => onWithColumnsChange(!showColumns)}
3843
>
3944
<span className="flex-1 text-left">Show Columns</span>
@@ -44,6 +49,19 @@ export function SettingsControl({
4449
/>
4550
)}
4651
</MenuItem>
52+
<MenuItem
53+
as="button"
54+
className={itemClass}
55+
onClick={() => onOnlyDirectChange(!onlyDirect)}
56+
>
57+
<span className="flex-1 text-left">Only Direct Neighbors</span>
58+
{onlyDirect && (
59+
<CheckIcon
60+
className="h-4 w-4 text-primary-500"
61+
aria-hidden="true"
62+
/>
63+
)}
64+
</MenuItem>
4765
</MenuItems>
4866
</Menu>
4967
)

vscode/react/src/components/graph/context.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from 'react'
88
import { getNodeMap, hasActiveEdge, hasActiveEdgeConnector } from './help'
99
import { type Node } from 'reactflow'
10+
import { isNil } from '@/utils/index'
1011
import type { Lineage } from '@/domain/lineage'
1112
import type { ModelSQLMeshModel } from '@/domain/sqlmesh-model'
1213
import type { Column } from '@/domain/column'
@@ -43,6 +44,8 @@ interface LineageFlow {
4344
hasBackground: boolean
4445
withImpacted: boolean
4546
withSecondary: boolean
47+
withOnlyDirect: boolean
48+
directNeighbors: Set<ModelEncodedFQN>
4649
manuallySelectedColumn?: [ModelSQLMeshModel, Column]
4750
highlightedNodes: HighlightedNodes
4851
nodesMap: Record<ModelEncodedFQN, Node>
@@ -55,6 +58,7 @@ interface LineageFlow {
5558
setHasBackground: React.Dispatch<React.SetStateAction<boolean>>
5659
setWithImpacted: React.Dispatch<React.SetStateAction<boolean>>
5760
setWithSecondary: React.Dispatch<React.SetStateAction<boolean>>
61+
setWithOnlyDirect: React.Dispatch<React.SetStateAction<boolean>>
5862
setConnections: React.Dispatch<React.SetStateAction<Map<string, Connections>>>
5963
hasActiveEdge: (edge: [string | undefined, string | undefined]) => boolean
6064
addActiveEdges: (edges: Array<[string, string]>) => void
@@ -85,6 +89,8 @@ export const LineageFlowContext = createContext<LineageFlow>({
8589
withConnected: false,
8690
withImpacted: true,
8791
withSecondary: false,
92+
withOnlyDirect: false,
93+
directNeighbors: new Set(),
8894
hasBackground: true,
8995
mainNode: undefined,
9096
activeEdges: new Map(),
@@ -103,6 +109,7 @@ export const LineageFlowContext = createContext<LineageFlow>({
103109
setWithImpacted: () => false,
104110
setWithSecondary: () => false,
105111
setWithConnected: () => false,
112+
setWithOnlyDirect: () => false,
106113
hasActiveEdge: () => false,
107114
addActiveEdges: () => {},
108115
removeActiveEdges: () => {},
@@ -161,6 +168,7 @@ export default function LineageFlowProvider({
161168
const [hasBackground, setHasBackground] = useState(true)
162169
const [withImpacted, setWithImpacted] = useState(true)
163170
const [withSecondary, setWithSecondary] = useState(false)
171+
const [withOnlyDirect, setWithOnlyDirect] = useState(false)
164172

165173
const nodesMap = useMemo(
166174
() =>
@@ -264,6 +272,23 @@ export default function LineageFlowProvider({
264272
[nodesConnections],
265273
)
266274

275+
const directNeighbors = useMemo(() => {
276+
const set = new Set<ModelEncodedFQN>()
277+
if (isNil(mainNode)) return set
278+
set.add(mainNode)
279+
for (const parent of lineage[mainNode]?.models ?? []) {
280+
set.add(parent)
281+
}
282+
for (const [child, info] of Object.entries(lineage) as Array<
283+
[ModelEncodedFQN, Lineage]
284+
>) {
285+
if (info?.models?.includes(mainNode)) {
286+
set.add(child)
287+
}
288+
}
289+
return set
290+
}, [mainNode, lineage])
291+
267292
const selectedEdges = useMemo(
268293
() =>
269294
Array.from(selectedNodes)
@@ -292,6 +317,8 @@ export default function LineageFlowProvider({
292317
withConnected,
293318
withImpacted,
294319
withSecondary,
320+
withOnlyDirect,
321+
directNeighbors,
295322
showControls,
296323
hasBackground,
297324
nodesMap,
@@ -304,6 +331,7 @@ export default function LineageFlowProvider({
304331
setWithConnected,
305332
setWithImpacted,
306333
setWithSecondary,
334+
setWithOnlyDirect,
307335
setHasBackground,
308336
setSelectedNodes,
309337
setMainNode,

vscode/react/src/components/graph/help.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,8 @@ export function getUpdatedEdges(
562562
withConnected: boolean = false,
563563
withImpacted: boolean = false,
564564
withSecondary: boolean = false,
565+
withOnlyDirect: boolean = false,
566+
directNeighbors: Set<string> = new Set(),
565567
): Edge[] {
566568
const tempEdges = edges.map(edge => {
567569
const isActiveEdge = hasActiveEdge(activeEdges, [
@@ -593,10 +595,15 @@ export function getUpdatedEdges(
593595
hasEdge(selectedEdges, edge.id) &&
594596
activeNodes.has(edge.source) &&
595597
activeNodes.has(edge.target)
598+
const shouldHideNonDirect =
599+
withOnlyDirect &&
600+
(isFalse(directNeighbors.has(edge.source)) ||
601+
isFalse(directNeighbors.has(edge.target)))
596602

597603
if (
598604
isFalse(shouldHideImpacted) &&
599605
isFalse(shouldHideSecondary) &&
606+
isFalse(shouldHideNonDirect) &&
600607
(isFalse(hasSelections) || isVisibleEdge)
601608
) {
602609
edge.hidden = false
@@ -653,6 +660,8 @@ export function getUpdatedNodes(
653660
withConnected: boolean,
654661
withImpacted: boolean,
655662
withSecondary: boolean,
663+
withOnlyDirect: boolean = false,
664+
directNeighbors: Set<string> = new Set(),
656665
): Node[] {
657666
return nodes.map(node => {
658667
node.hidden = true
@@ -667,8 +676,14 @@ export function getUpdatedNodes(
667676
isFalse(withSecondary) && isFalse(hasSelections)
668677
const shouldHideSecondary = isSecondaryNode && withoutSecondaryNodes
669678
const shouldHideImpacted = isImpactedNode && withoutImpactedNodes
679+
const shouldHideNonDirect =
680+
withOnlyDirect && isFalse(directNeighbors.has(node.id))
670681

671-
if (isFalse(shouldHideImpacted) && isFalse(shouldHideSecondary)) {
682+
if (
683+
isFalse(shouldHideImpacted) &&
684+
isFalse(shouldHideSecondary) &&
685+
isFalse(shouldHideNonDirect)
686+
) {
672687
node.hidden = isFalse(isActiveNode)
673688
}
674689

0 commit comments

Comments
 (0)