Skip to content

Commit ca27f1c

Browse files
committed
Stricter Typing on Nodes
1 parent 62e8606 commit ca27f1c

21 files changed

+166
-145
lines changed

src/components/shared/ReactFlow/FlowCanvas/FlowCanvas.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ const FlowCanvas = ({
306306
const nodeData = useMemo(
307307
() => ({
308308
connectable: !readOnly && !!nodesConnectable,
309-
readOnly,
309+
readOnly: !!readOnly,
310310
nodeCallbacks,
311311
}),
312312
[readOnly, nodesConnectable, nodeCallbacks],

src/components/shared/ReactFlow/FlowCanvas/GhostNode/GhostNode.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { memo, useMemo } from "react";
33

44
import { cn } from "@/lib/utils";
55
import { TaskNodeProvider } from "@/providers/TaskNodeProvider";
6-
import type { TaskNodeData } from "@/types/taskNode";
6+
import type { TaskNodeData } from "@/types/nodes";
77
import type { ComponentReference } from "@/utils/componentSpec";
88
import { generateTaskSpec } from "@/utils/nodes/generateTaskSpec";
9+
import { createEmptyTaskCallbacks } from "@/utils/nodes/taskCallbackUtils";
910

1011
import { TaskNodeCard } from "../TaskNode/TaskNodeCard";
1112

@@ -74,5 +75,9 @@ const generateGhostTaskNodeData = (
7475
taskSpec,
7576
taskId: ghostTaskId,
7677
isGhost: true,
78+
readOnly: true,
79+
highlighted: false,
80+
connectable: false,
81+
callbacks: createEmptyTaskCallbacks(),
7782
};
7883
};

src/components/shared/ReactFlow/FlowCanvas/GhostNode/HintNode.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { type NodeProps, useReactFlow } from "@xyflow/react";
22
import { memo, type PropsWithChildren, useMemo } from "react";
33

44
import { cn } from "@/lib/utils";
5-
import type { HintNodeData } from "@/types/hintNode";
5+
import type { HintNodeData } from "@/types/nodes";
66

77
const HintNode = ({ data }: NodeProps) => {
88
const { getZoom } = useReactFlow();

src/components/shared/ReactFlow/FlowCanvas/IONode/IONode.tsx

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,12 @@ import { Paragraph } from "@/components/ui/typography";
1010
import { cn } from "@/lib/utils";
1111
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
1212
import { useContextPanel } from "@/providers/ContextPanelProvider";
13+
import type { IONodeData } from "@/types/nodes";
14+
import type { InputSpec, TypeSpecType } from "@/utils/componentSpec";
1315

1416
interface IONodeProps {
1517
type: "input" | "output";
16-
data: {
17-
label: string;
18-
value?: string;
19-
default?: string;
20-
type?: string;
21-
readOnly?: boolean;
22-
};
18+
data: IONodeData;
2319
selected: boolean;
2420
deletable: boolean;
2521
}
@@ -28,10 +24,12 @@ const IONode = ({ type, data, selected = false }: IONodeProps) => {
2824
const { graphSpec, componentSpec } = useComponentSpec();
2925
const { setContent, clearContent } = useContextPanel();
3026

27+
const { spec, readOnly } = data;
28+
3129
const isInput = type === "input";
3230
const isOutput = type === "output";
3331

34-
const readOnly = !!data.readOnly;
32+
const inputSpec = isInput ? (spec as InputSpec) : null;
3533

3634
const handleType = isInput ? "source" : "target";
3735
const handlePosition = isInput ? Position.Right : Position.Left;
@@ -44,13 +42,13 @@ const IONode = ({ type, data, selected = false }: IONodeProps) => {
4442
const borderColor = selected ? selectedColor : defaultColor;
4543

4644
const input = useMemo(
47-
() => componentSpec.inputs?.find((input) => input.name === data.label),
48-
[componentSpec.inputs, data.label],
45+
() => componentSpec.inputs?.find((input) => input.name === spec.name),
46+
[componentSpec.inputs, spec.name],
4947
);
5048

5149
const output = useMemo(
52-
() => componentSpec.outputs?.find((output) => output.name === data.label),
53-
[componentSpec.outputs, data.label],
50+
() => componentSpec.outputs?.find((output) => output.name === spec.name),
51+
[componentSpec.outputs, spec.name],
5452
);
5553

5654
useEffect(() => {
@@ -88,7 +86,7 @@ const IONode = ({ type, data, selected = false }: IONodeProps) => {
8886
};
8987
}, [input, output, selected, readOnly]);
9088

91-
const connectedOutput = getOutputConnectedDetails(graphSpec, data.label);
89+
const connectedOutput = getOutputConnectedDetails(graphSpec, spec.name);
9290
const outputConnectedValue = connectedOutput.outputName;
9391
const outputConnectedType = connectedOutput.outputType;
9492
const outputConnectedTaskId = connectedOutput.taskId;
@@ -98,30 +96,28 @@ const IONode = ({ type, data, selected = false }: IONodeProps) => {
9896

9997
const handleClassName = isInput ? "translate-x-1.5" : "-translate-x-1.5";
10098

101-
const hasDataValue = !!data.value;
102-
const hasDataDefault = !!data.default;
103-
104-
const inputValue = hasDataValue
105-
? data.value
106-
: hasDataDefault
107-
? data.default
99+
const inputValue = inputSpec?.value
100+
? inputSpec?.value
101+
: inputSpec?.default
102+
? inputSpec?.default
108103
: null;
109-
110104
const outputValue = outputConnectedValue ?? null;
111-
112105
const value = isInput ? inputValue : outputValue;
113106

107+
const inputType = getTypeDisplayString(inputSpec?.type);
108+
const outputType = getTypeDisplayString(outputConnectedType);
109+
const typeValue = isInput ? inputType : outputType;
110+
114111
return (
115112
<Card className={cn("border-2 max-w-[300px] p-0", borderColor)}>
116113
<CardHeader className="px-2 py-2.5">
117-
<CardTitle className="break-words">{data.label}</CardTitle>
114+
<CardTitle className="break-words">{spec.name}</CardTitle>
118115
</CardHeader>
119116
<CardContent className="p-2 max-w-[250px]">
120117
<BlockStack gap="2">
121118
{/* type */}
122119
<Paragraph size="xs" font="mono" className="truncate text-slate-700">
123-
<span className="font-bold">Type:</span>{" "}
124-
{outputConnectedType ?? data.type ?? "Any"}
120+
<span className="font-bold">Type:</span> {typeValue}
125121
</Paragraph>
126122

127123
{!!outputConnectedTaskId && (
@@ -161,3 +157,13 @@ const IONode = ({ type, data, selected = false }: IONodeProps) => {
161157
};
162158

163159
export default memo(IONode);
160+
161+
const getTypeDisplayString = (type: TypeSpecType | undefined): string => {
162+
if (!type) return "Any";
163+
164+
if (typeof type === "string") {
165+
return type;
166+
}
167+
168+
return JSON.stringify(type);
169+
};

src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNode.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { memo, useMemo } from "react";
44
import type { ContainerExecutionStatus } from "@/api/types.gen";
55
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
66
import { TaskNodeProvider } from "@/providers/TaskNodeProvider";
7-
import type { TaskNodeData } from "@/types/taskNode";
7+
import type { TaskNodeData } from "@/types/nodes";
88

99
import { StatusIndicator } from "./StatusIndicator";
1010
import { TaskNodeCard } from "./TaskNodeCard";

src/components/shared/ReactFlow/FlowCanvas/utils/addTask.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import type { XYPosition } from "@xyflow/react";
22

3-
import type { TaskType } from "@/types/taskNode";
4-
import type {
5-
ComponentSpec,
6-
GraphSpec,
7-
InputSpec,
8-
OutputSpec,
9-
TaskSpec,
3+
import type { TaskType } from "@/types/nodes";
4+
import {
5+
type ComponentSpec,
6+
type GraphSpec,
7+
type InputSpec,
8+
isGraphImplementation,
9+
type OutputSpec,
10+
type TaskSpec,
1011
} from "@/utils/componentSpec";
1112
import { deepClone } from "@/utils/deepClone";
1213
import {
@@ -23,7 +24,7 @@ const addTask = (
2324
): ComponentSpec => {
2425
const newComponentSpec = deepClone(componentSpec);
2526

26-
if (!("graph" in newComponentSpec.implementation)) {
27+
if (!isGraphImplementation(newComponentSpec.implementation)) {
2728
console.error("Implementation does not contain a graph.");
2829
return newComponentSpec;
2930
}

src/components/shared/ReactFlow/FlowCanvas/utils/duplicateNodes.test.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Node } from "@xyflow/react";
22
import { describe, expect, it, vi } from "vitest";
33

4-
import type { NodeCallbacks, TaskNodeData } from "@/types/taskNode";
4+
import type { TaskNodeData } from "@/types/nodes";
55
import type {
66
ComponentSpec,
77
InputSpec,
@@ -85,14 +85,6 @@ const createMockTaskNodeCallbacks = () => ({
8585
onUpgrade: vi.fn(),
8686
});
8787

88-
const createMockNodeCallbacks = (): NodeCallbacks => ({
89-
setArguments: vi.fn(),
90-
setAnnotations: vi.fn(),
91-
onDelete: vi.fn(),
92-
onDuplicate: vi.fn(),
93-
onUpgrade: vi.fn(),
94-
});
95-
9688
const createMockTaskNode = (
9789
taskId: string,
9890
taskSpec: TaskSpec,
@@ -106,8 +98,10 @@ const createMockTaskNode = (
10698
taskId,
10799
label: "Test Task",
108100
highlighted: false,
101+
readOnly: false,
102+
isGhost: false,
103+
connectable: true,
109104
callbacks: createMockTaskNodeCallbacks(),
110-
nodeCallbacks: createMockNodeCallbacks(),
111105
},
112106
selected: false,
113107
dragging: false,

src/components/shared/ReactFlow/FlowCanvas/utils/duplicateNodes.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type Node, type XYPosition } from "@xyflow/react";
22

3-
import type { TaskNodeData } from "@/types/taskNode";
3+
import type { IONodeData, NodeData, TaskNodeData } from "@/types/nodes";
44
import {
55
type ComponentSpec,
66
type GraphInputArgument,
@@ -23,6 +23,7 @@ import {
2323
taskIdToNodeId,
2424
} from "@/utils/nodes/nodeIdUtils";
2525
import { setPositionInAnnotations } from "@/utils/nodes/setPositionInAnnotations";
26+
import { convertTaskCallbacksToNodeCallbacks } from "@/utils/nodes/taskCallbackUtils";
2627
import {
2728
getUniqueInputName,
2829
getUniqueOutputName,
@@ -277,17 +278,19 @@ export const duplicateNodes = (
277278
return null;
278279
}
279280

280-
const originalNodeData = originalNode.data as TaskNodeData;
281-
282281
if (originalNode.type === "task") {
283282
const newTaskId = nodeIdToTaskId(newNodeId);
284283

285284
const newTaskSpec = updatedGraphSpec.tasks[newTaskId];
286285

287-
const newNode = createTaskNode(
288-
[newTaskId, newTaskSpec],
289-
originalNodeData,
290-
);
286+
const taskData = originalNode.data as TaskNodeData;
287+
const nodeData: NodeData = {
288+
readOnly: taskData.readOnly,
289+
connectable: taskData.connectable,
290+
callbacks: convertTaskCallbacksToNodeCallbacks(taskData.callbacks),
291+
};
292+
293+
const newNode = createTaskNode([newTaskId, newTaskSpec], nodeData);
291294

292295
newNode.id = newNodeId;
293296
newNode.selected = false;
@@ -313,7 +316,12 @@ export const duplicateNodes = (
313316
return null;
314317
}
315318

316-
const newNode = createInputNode(newInputSpec, originalNodeData);
319+
const inputData = originalNode.data as IONodeData;
320+
const nodeData: NodeData = {
321+
readOnly: inputData.readOnly,
322+
};
323+
324+
const newNode = createInputNode(newInputSpec, nodeData);
317325

318326
newNode.id = newNodeId;
319327
newNode.selected = false;
@@ -339,7 +347,12 @@ export const duplicateNodes = (
339347
return null;
340348
}
341349

342-
const newNode = createOutputNode(newOutputSpec, originalNodeData);
350+
const outputData = originalNode.data as IONodeData;
351+
const nodeData: NodeData = {
352+
readOnly: outputData.readOnly,
353+
};
354+
355+
const newNode = createOutputNode(newOutputSpec, nodeData);
343356

344357
newNode.id = newNodeId;
345358

src/components/shared/ReactFlow/FlowCanvas/utils/getTaskFromEvent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { DragEvent } from "react";
22

3-
import type { TaskType } from "@/types/taskNode";
3+
import type { TaskType } from "@/types/nodes";
44
import type { TaskSpec } from "@/utils/componentSpec";
55

66
export const getTaskFromEvent = (event: DragEvent) => {

src/hooks/useHintNode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useMemo } from "react";
44

55
import { useBetaFlagValue } from "@/components/shared/Settings/useBetaFlags";
66
import { useComponentLibrary } from "@/providers/ComponentLibraryProvider";
7-
import type { HintNodeData } from "@/types/hintNode";
7+
import type { HintNodeData } from "@/types/nodes";
88

99
const HINT_NODE_ID = "hint-node";
1010

0 commit comments

Comments
 (0)