Skip to content

Commit be4bdba

Browse files
committed
feat: Pack Flex Nodes into Subgraphs
1 parent 744ff16 commit be4bdba

6 files changed

Lines changed: 132 additions & 5 deletions

File tree

src/components/shared/ReactFlow/FlowCanvas/Subgraphs/create/NodeListItem.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { InlineStack } from "@/components/ui/layout";
77
import { Paragraph } from "@/components/ui/typography";
88
import { cn } from "@/lib/utils";
99

10+
import { isFlexNode } from "../../types";
1011
import { getNodeTypeColor } from "./utils";
1112

1213
interface NodeListItemProps {
@@ -34,16 +35,31 @@ export function NodeListItem({
3435
}
3536
};
3637

38+
const isFlexType = isFlexNode(node);
39+
40+
const displayValue = isFlexType
41+
? `Sticky Note: ${node.data.properties.title.length > 0 ? node.data.properties.title : node.id}`
42+
: node.id;
43+
3744
return (
3845
<li key={node.id} className="flex justify-between items-center">
3946
<InlineStack
4047
gap="3"
4148
className={cn({ "opacity-50": isExcluded })}
4249
wrap="nowrap"
4350
>
44-
<Badge variant="dot" className={cn(getNodeTypeColor(node.type))} />
51+
<Badge
52+
variant="dot"
53+
className={cn(getNodeTypeColor(node.type))}
54+
style={{
55+
backgroundColor: isFlexType
56+
? node.data.properties.color
57+
: undefined,
58+
filter: isFlexType ? "brightness(0.8)" : undefined,
59+
}}
60+
/>
4561
<Paragraph font="mono" size="xs">
46-
{node.id}
62+
{displayValue}
4763
</Paragraph>
4864
{isOrphaned && (
4965
<InlineStack gap="1" wrap="nowrap">

src/components/shared/ReactFlow/FlowCanvas/Subgraphs/create/checkForOrphanedNodes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export const checkForOrphanedNodes = (
9898
});
9999

100100
const orphanedNodes = selectedNodes.filter(
101-
(node) => !connectedNodeIds.has(node.id),
101+
(node) => !connectedNodeIds.has(node.id) && node.type !== "flex",
102102
);
103103

104104
return orphanedNodes;

src/components/shared/ReactFlow/FlowCanvas/Subgraphs/create/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export const canGroupNodes = (nodes: Node[]): GroupingValidation => {
2727

2828
export const getNodeTypeColor = (nodeType: string | undefined): string => {
2929
switch (nodeType) {
30+
case "flex":
31+
return "bg-yellow-300";
3032
case "input":
3133
return "bg-blue-500";
3234
case "output":

src/utils/nodes/createSubgraphFromNodes.ts

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

3+
import {
4+
getFlexNodeAnnotations,
5+
serializeFlexNodes,
6+
} from "@/components/shared/ReactFlow/FlowCanvas/FlexNode/interface";
7+
import type { FlexNodeData } from "@/components/shared/ReactFlow/FlowCanvas/FlexNode/types";
38
import {
49
type Bounds,
510
calculateNodesCenter,
@@ -23,7 +28,10 @@ import {
2328
isTaskOutputArgument,
2429
} from "@/utils/componentSpec";
2530

26-
import { EDITOR_POSITION_ANNOTATION } from "../annotations";
31+
import {
32+
EDITOR_POSITION_ANNOTATION,
33+
FLEX_NODES_ANNOTATION,
34+
} from "../annotations";
2735
import { extractPositionFromAnnotations } from "../annotations";
2836
import { generateDigest } from "../componentStore";
2937
import { getUniqueName, getUniqueTaskName } from "../unique";
@@ -67,12 +75,14 @@ export const createSubgraphFromNodes = async (
6775
const taskNodes = selectedNodes.filter((node) => node.type === "task");
6876
const inputNodes = selectedNodes.filter((node) => node.type === "input");
6977
const outputNodes = selectedNodes.filter((node) => node.type === "output");
78+
const flexNodes = selectedNodes.filter((node) => node.type === "flex");
7079

7180
const subgraphTasks: Record<string, TaskSpec> = {};
7281
const subgraphInputs: InputSpec[] = [];
7382
const subgraphArguments: Record<string, ArgumentType> = {};
7483
const subgraphOutputs: OutputSpec[] = [];
7584
const subgraphOutputValues: Record<string, TaskOutputArgument> = {};
85+
const subgraphAnnotations: Record<string, string> = {};
7686

7787
const bounds = getNodesBounds(selectedNodes);
7888

@@ -145,6 +155,13 @@ export const createSubgraphFromNodes = async (
145155
currentGraphSpec,
146156
);
147157

158+
processSelectedFlexNodes(
159+
flexNodes,
160+
bounds,
161+
subgraphAnnotations,
162+
currentComponentSpec,
163+
);
164+
148165
// Create the replacement task that represents the subgraph
149166
const subgraphPosition = calculateNodesCenter(selectedNodes);
150167

@@ -159,6 +176,7 @@ export const createSubgraphFromNodes = async (
159176
subgraphOutputValues,
160177
subgraphPosition,
161178
subgraphArguments,
179+
subgraphAnnotations,
162180
);
163181

164182
const text = await getComponentText(subgraphTask.componentRef);
@@ -179,6 +197,7 @@ const createSubgraphTask = async (
179197
outputValues: Record<string, TaskOutputArgument>,
180198
position: XYPosition,
181199
args: Record<string, ArgumentType>,
200+
annotations: Record<string, string> = {},
182201
) => {
183202
let author: string = "Unknown";
184203
try {
@@ -205,6 +224,7 @@ const createSubgraphTask = async (
205224
sdk: "https://cloud-pipelines.net/pipeline-editor/",
206225
"editor.flow-direction": "left-to-right",
207226
author,
227+
...annotations,
208228
},
209229
},
210230
};
@@ -309,6 +329,38 @@ const processSelectedOutputNodes = (
309329
});
310330
};
311331

332+
const processSelectedFlexNodes = (
333+
flexNodes: Node[],
334+
bounds: Bounds,
335+
annotations: Record<string, string>,
336+
currentComponentSpec: ComponentSpec,
337+
): void => {
338+
const existingFlexNodes = getFlexNodeAnnotations(currentComponentSpec);
339+
const newFlexNodes: FlexNodeData[] = [];
340+
341+
flexNodes.forEach((node) => {
342+
const flexNodeId = node.id;
343+
344+
const existingFlexNode = existingFlexNodes.find(
345+
(flex) => flex.id === flexNodeId,
346+
);
347+
348+
if (existingFlexNode) {
349+
const newFlexNode = { ...existingFlexNode };
350+
const normalizedPosition = normalizeNodePosition(node, bounds);
351+
newFlexNode.position = normalizedPosition;
352+
353+
newFlexNodes.push(newFlexNode);
354+
}
355+
});
356+
357+
if (newFlexNodes.length === 0) {
358+
return;
359+
}
360+
361+
annotations[FLEX_NODES_ANNOTATION] = serializeFlexNodes(newFlexNodes);
362+
};
363+
312364
const processTaskInputConnections = (
313365
taskSpec: TaskSpec,
314366
taskPosition: XYPosition,

src/utils/nodes/unpacking/helpers.ts

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

3+
import {
4+
getFlexNodeAnnotations,
5+
serializeFlexNodes,
6+
} from "@/components/shared/ReactFlow/FlowCanvas/FlexNode/interface";
37
import addTask from "@/components/shared/ReactFlow/FlowCanvas/utils/addTask";
48
import { setGraphOutputValue } from "@/components/shared/ReactFlow/FlowCanvas/utils/setGraphOutputValue";
59
import { setTaskArgument } from "@/components/shared/ReactFlow/FlowCanvas/utils/setTaskArgument";
6-
import { extractPositionFromAnnotations } from "@/utils/annotations";
10+
import {
11+
extractPositionFromAnnotations,
12+
FLEX_NODES_ANNOTATION,
13+
} from "@/utils/annotations";
714
import {
815
type ArgumentType,
916
type ComponentSpec,
@@ -23,6 +30,47 @@ import {
2330
normalizeNodePositionInGroup,
2431
} from "@/utils/graphUtils";
2532

33+
export const unpackFlexNodes = (
34+
containerSpec: ComponentSpec,
35+
containerPosition: XYPosition,
36+
componentSpec: ComponentSpec,
37+
): ComponentSpec => {
38+
const updatedSpec = componentSpec;
39+
40+
const flexNodes = getFlexNodeAnnotations(containerSpec);
41+
42+
const containerCenter = calculateSpecCenter(containerSpec);
43+
44+
const newFlexNodes = flexNodes.map((flexNode) => {
45+
const position = normalizeNodePositionInGroup(
46+
{
47+
x: flexNode.position.x,
48+
y: flexNode.position.y,
49+
},
50+
containerPosition,
51+
containerCenter,
52+
);
53+
54+
return {
55+
...flexNode,
56+
position,
57+
};
58+
});
59+
60+
if (!updatedSpec.metadata) {
61+
updatedSpec.metadata = {};
62+
}
63+
64+
if (!updatedSpec.metadata.annotations) {
65+
updatedSpec.metadata.annotations = {};
66+
}
67+
68+
updatedSpec.metadata.annotations[FLEX_NODES_ANNOTATION] =
69+
serializeFlexNodes(newFlexNodes);
70+
71+
return updatedSpec;
72+
};
73+
2674
export const unpackInputs = (
2775
containerSpec: ComponentSpec,
2876
containerPosition: XYPosition,

src/utils/nodes/unpacking/unpackSubgraph.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
reconnectDownstreamOutputs,
1212
reconnectDownstreamTasks,
1313
reconnectUpstreamInputsAndTasks,
14+
unpackFlexNodes,
1415
unpackInputs,
1516
unpackOutputs,
1617
unpackTasks,
@@ -46,6 +47,14 @@ export const unpackSubgraph = (
4647

4748
let updatedComponentSpec = componentSpec;
4849

50+
// Unpack flex Nodes
51+
const specAfterFlexNodes = unpackFlexNodes(
52+
subgraphSpec,
53+
subgraphPosition,
54+
updatedComponentSpec,
55+
);
56+
updatedComponentSpec = specAfterFlexNodes;
57+
4958
// Unpack inputs
5059
const { spec: specAfterInputs, inputNameMap } = unpackInputs(
5160
subgraphSpec,

0 commit comments

Comments
 (0)