From 0ea1d2a4adbed2a7edc51b13d5e00746f0f06086 Mon Sep 17 00:00:00 2001 From: Ben King <9087625+benfdking@users.noreply.github.com> Date: Fri, 4 Jul 2025 16:04:09 +0100 Subject: [PATCH] chore(vscode): continued typing of nodes --- .../src/components/graph/ModelColumns.tsx | 48 ++++++++----------- .../react/src/components/graph/ModelNode.tsx | 30 +++++------- .../graph/ModelNodeHeaderHandles.tsx | 17 +++---- .../react/src/components/graph/constants.ts | 16 +++++++ vscode/react/src/components/graph/context.tsx | 30 ++++++------ vscode/react/src/components/graph/help.ts | 46 +++++------------- vscode/react/src/components/graph/types.ts | 32 +++++++++---- 7 files changed, 107 insertions(+), 112 deletions(-) create mode 100644 vscode/react/src/components/graph/constants.ts diff --git a/vscode/react/src/components/graph/ModelColumns.tsx b/vscode/react/src/components/graph/ModelColumns.tsx index 19f382467a..e0e180de51 100644 --- a/vscode/react/src/components/graph/ModelColumns.tsx +++ b/vscode/react/src/components/graph/ModelColumns.tsx @@ -10,7 +10,7 @@ import { isNotNil, truncate, } from '@/utils/index' -import { EnumSide, toID, type Side } from './types' +import { toID, type PartialColumnHandleId, type Side } from './types' import { NoSymbolIcon } from '@heroicons/react/24/solid' import { ClockIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline' import clsx from 'clsx' @@ -38,7 +38,7 @@ export function ModelColumns({ columns, disabled, className, - limit = 5, + limit, withHandles = false, withDescription = true, maxHeight = '50vh', @@ -47,7 +47,7 @@ export function ModelColumns({ columns: Column[] disabled?: boolean className?: string - limit?: number + limit: number withHandles?: boolean withDescription?: boolean maxHeight?: string @@ -125,7 +125,7 @@ export function ModelColumns({ } const isSelectManually = useCallback( - function isSelectManually(columnName: string): boolean { + function isSelectManually(columnName: ColumnName): boolean { if (isNil(manuallySelectedColumn)) return false const [selectedModel, selectedColumn] = manuallySelectedColumn @@ -138,12 +138,10 @@ export function ModelColumns({ ) const removeEdges = useCallback( - function removeEdges(columnId: string): void { + function removeEdges(columnId: PartialColumnHandleId): void { const visited = new Set() - removeActiveEdges( - walk(columnId, EnumSide.Left).concat(walk(columnId, EnumSide.Right)), - ) + removeActiveEdges(walk(columnId, 'left').concat(walk(columnId, 'right'))) if (connections.size === 0 && isNotNil(lineageCache)) { setLineage(lineageCache) @@ -164,12 +162,12 @@ export function ModelColumns({ return edges .map(edge => [ - side === EnumSide.Left - ? [toID(EnumSide.Left, id), toID(EnumSide.Right, edge)] - : [toID(EnumSide.Left, edge), toID(EnumSide.Right, id)], + side === 'left' + ? [toID('left', id), toID('right', edge)] + : [toID('left', edge), toID('right', id)], ].concat(walk(edge, side)), ) - .flat() as Array<[string, string]> + .flat() as Array<[PartialColumnHandleId, PartialColumnHandleId]> } }, [removeActiveEdges, connections], @@ -324,8 +322,8 @@ function ModelColumn({ withHandles = false, withDescription = true, }: { - id: string - nodeId: string + id: PartialColumnHandleId + nodeId: ModelEncodedFQN column: Column disabled?: boolean isActive?: boolean @@ -337,7 +335,7 @@ function ModelColumn({ updateColumnLineage: ( lineage: ColumnLineageApiLineageModelNameColumnNameGet200, ) => void - removeEdges: (columnId: string) => void + removeEdges: (columnId: PartialColumnHandleId) => void selectManually?: React.Dispatch< React.SetStateAction< [ModelSQLMeshModel, Column] | undefined @@ -454,8 +452,8 @@ function ColumnHandles({ children, className, }: { - nodeId: string - id: string + nodeId: ModelEncodedFQN + id: PartialColumnHandleId children: React.ReactNode className?: string hasLeft?: boolean @@ -482,7 +480,7 @@ function ColumnHandles({ {hasLeft && (
{disabled && ( @@ -542,7 +534,7 @@ function ColumnDisplay({ className="w-3 h-3 mr-2" /> )} - {truncate(decodedColumnName, 50, 20)} + {truncate(columnName, 50, 20)} m.fqn === decodedId) const modelColumns = model?.columns?.map(fromAPIColumn) ?? [] - Object.keys(lineage[decodedId]?.columns ?? {}).forEach((column: string) => { - const found = modelColumns.find(({ name }: any) => { - try { - return name === decodeURI(column) - } catch { - return name === column - } - }) - + toKeys(lineage[decodedId]?.columns ?? {}).forEach(column => { + const found = modelColumns.find(({ name }) => name === column) if (isNil(found)) { modelColumns.push( fromAPIColumn({ name: column, type: EnumColumnType.UNKNOWN }), ) } }) - - modelColumns.forEach((column: any) => { + return modelColumns.map(column => { let columnType = column.type ?? EnumColumnType.UNKNOWN - if (columnType.startsWith(EnumColumnType.STRUCT)) { columnType = EnumColumnType.STRUCT } - - column.type = columnType + return { + ...column, + type: columnType, + } }) - - return modelColumns }, [id, models, lineage]) const highlightedNodeModels = useMemo( @@ -136,7 +129,7 @@ export default function ModelNode({ // Ensure nodeData.type is a valid LineageNodeModelType const nodeType: LineageNodeModelType = Object.values( EnumLineageNodeModelType, - ).includes(nodeData.type as any) + ).includes(nodeData.type) ? (nodeData.type as LineageNodeModelType) : EnumLineageNodeModelType.unknown @@ -222,12 +215,13 @@ export default function ModelNode({ ? undefined : handleSelect } - count={hasHighlightedNodes ? undefined : columns.length} + numberOfColumns={columns.length} /> {showColumns && ( {truncate(decodeURI(label), 50, 20)} - {isNotNil(count) && ( + {isNotNil(numberOfColumns) && ( - {count} + {numberOfColumns} )} @@ -97,7 +98,7 @@ export function ModelNodeHeaderHandles({ {hasRight && ( export type ActiveEdges = Map> export type ActiveNodes = Set -export type SelectedNodes = Set +export type SelectedNodes = Set export type HighlightedNodes = Record interface LineageFlow { lineage: Record lineageCache?: Record mainNode?: ModelEncodedFQN - connectedNodes: Set + connectedNodes: Set activeEdges: ActiveEdges activeNodes: ActiveNodes selectedNodes: SelectedNodes - selectedEdges: any[] + selectedEdges: ConnectedNode[] models: Record unknownModels: Set connections: Map @@ -44,7 +45,7 @@ interface LineageFlow { withSecondary: boolean manuallySelectedColumn?: [ModelSQLMeshModel, Column] highlightedNodes: HighlightedNodes - nodesMap: Record + nodesMap: Record setHighlightedNodes: React.Dispatch> setActiveNodes: React.Dispatch> setWithConnected: React.Dispatch> @@ -64,7 +65,7 @@ interface LineageFlow { setLineageCache: React.Dispatch< React.SetStateAction | undefined> > - handleClickModel?: (modelName: string) => void + handleClickModel?: (modelName: ModelEncodedFQN) => void handleError?: (error: any) => void setManuallySelectedColumn: React.Dispatch< React.SetStateAction<[ModelSQLMeshModel, Column] | undefined> @@ -130,7 +131,7 @@ export default function LineageFlowProvider({ models, }: { children: React.ReactNode - handleClickModel?: (modelName: string) => void + handleClickModel?: (modelName: ModelEncodedFQN) => void handleError?: (error: any) => void showColumns?: boolean showConnected?: boolean @@ -142,9 +143,9 @@ export default function LineageFlowProvider({ const [lineageCache, setLineageCache] = useState< Record | undefined >(undefined) - const [nodesConnections, setNodeConnections] = useState>( - {}, - ) + const [nodesConnections, setNodeConnections] = useState< + Record + >({}) const [withColumns, setWithColumns] = useState(showColumns) const [mainNode, setMainNode] = useState() const [manuallySelectedColumn, setManuallySelectedColumn] = @@ -248,9 +249,8 @@ export default function LineageFlowProvider({ modelName: ModelEncodedFQN, columnName: ColumnName, ): boolean { - const leftConnector = [EnumSide.Left, modelName, columnName].join('__') - const rightConnector = [EnumSide.Right, modelName, columnName].join('__') - + const leftConnector = toID('left', modelName, columnName) + const rightConnector = toID('right', modelName, columnName) return ( hasActiveEdgeConnector(activeEdges, leftConnector) || hasActiveEdgeConnector(activeEdges, rightConnector) @@ -259,8 +259,8 @@ export default function LineageFlowProvider({ [checkActiveEdge, activeEdges], ) - const connectedNodes: Set = useMemo( - () => new Set(Object.keys(nodesConnections)), + const connectedNodes = useMemo( + () => new Set(toKeys(nodesConnections)), [nodesConnections], ) diff --git a/vscode/react/src/components/graph/help.ts b/vscode/react/src/components/graph/help.ts index a09c88fe5a..317d449807 100644 --- a/vscode/react/src/components/graph/help.ts +++ b/vscode/react/src/components/graph/help.ts @@ -9,7 +9,7 @@ import { import { type LineageColumn } from '@/api/client' import { Position, type Edge, type Node, type XYPosition } from 'reactflow' import { type ActiveEdges, type Connections } from './context' -import { EnumSide, toID, toKeys } from './types' +import { toID, toKeys } from './types' import { EnumLineageNodeModelType, type LineageNodeModelType, @@ -19,23 +19,12 @@ import type { ConnectedNode } from '@/workers/lineage' import { encode, type ModelEncodedFQN, type ModelURI } from '@/domain/models' import type { Column, ColumnName } from '@/domain/column' import type { ModelSQLMeshModel } from '@/domain/sqlmesh-model' - -/** - * Space between nodes. - */ -const NODE_BALANCE_SPACE = 64 -/** - * Height of a column line. - */ -const COLUMN_LINE_HEIGHT = 24 -/** - * Assumed width of a character. - */ -const CHAR_WIDTH = 8 -/** - * Maximum number of columns that can be visible in a node. - */ -const MAX_VISIBLE_COLUMNS = 5 +import { + CHAR_WIDTH, + COLUMN_LINE_HEIGHT, + MAX_VISIBLE_COLUMNS, + NODE_BALANCE_SPACE, +} from './constants' export interface GraphNodeData { label: string @@ -125,17 +114,8 @@ export function getEdges( if (isNil(sourceColumns)) continue for (const sourceColumnName of sourceColumns) { - const sourceHandler = toID( - EnumSide.Right, - sourceModelName, - sourceColumnName, - ) - const targetHandler = toID( - EnumSide.Left, - targetModelName, - targetColumnName, - ) - + const sourceHandler = toID('right', sourceModelName, sourceColumnName) + const targetHandler = toID('left', targetModelName, targetColumnName) outputEdges.push( createGraphEdge( sourceModelName, @@ -460,14 +440,14 @@ export function mergeConnections( // And right bucket contains references to all targets (left handlers) connectionsModelSource.left.forEach(id => { activeEdges.push([ - toID(EnumSide.Left, modelColumnIdSource), - toID(EnumSide.Right, id), + toID('left', modelColumnIdSource), + toID('right', id), ]) }) connectionsModelSource.right.forEach(id => { activeEdges.push([ - toID(EnumSide.Left, id), - toID(EnumSide.Right, modelColumnIdSource), + toID('left', id), + toID('right', modelColumnIdSource), ]) }) }) diff --git a/vscode/react/src/components/graph/types.ts b/vscode/react/src/components/graph/types.ts index 831251e2d7..fde31e3084 100644 --- a/vscode/react/src/components/graph/types.ts +++ b/vscode/react/src/components/graph/types.ts @@ -1,17 +1,21 @@ import type { ColumnName } from '@/domain/column' import type { ModelEncodedFQN } from '@/domain/models' +import type { Branded } from '@bus/brand' -export const EnumSide = { - Left: 'left', - Right: 'right', -} as const - -export type Side = (typeof EnumSide)[keyof typeof EnumSide] +export type Side = 'left' | 'right' export type NodeId = string export type EdgeId = string +/** + * Partial column handle id that isn't complete yet as it's missing the left/right side + * definition. + */ +export type PartialColumnHandleId = Branded +export type ColumnHandleId = Branded +export type ModelHandleId = Branded + /** * Converts a list of strings to a single string with a double underscore * Outlines with types, the type of ids that can be created. @@ -19,15 +23,23 @@ export type EdgeId = string * @returns */ export function toID( - leftOrRight: 'left' | 'right', + leftOrRight: Side, modelName: ModelEncodedFQN, columnName: ColumnName, ): NodeId -export function toID(source: NodeId, target: NodeId): NodeId export function toID( - leftOrRight: 'left' | 'right', modelName: ModelEncodedFQN, -): NodeId + columnName: ColumnName, +): PartialColumnHandleId +export function toID( + leftOrRight: Side, + partialColumnHandleId: PartialColumnHandleId, +): ColumnHandleId +export function toID( + leftOrRight: Side, + modelName: ModelEncodedFQN, +): ModelHandleId +export function toID(source: NodeId, target: NodeId): NodeId export function toID( source: NodeId, target: NodeId,