Skip to content

Commit

Permalink
Prompt graph serialization errors (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
rsamf authored Jul 20, 2024
1 parent efbf612 commit 4090f51
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 36 deletions.
42 changes: 36 additions & 6 deletions web/src/components/ContextMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import { keyRecursively, uniqueIdFrom } from '../utils';
import { Graph } from '../graph';
import { useRunState } from '../hooks/RunState';
import { useAPI } from '../hooks/API';
import { useNotification } from '../hooks/Notification';
import { SerializationErrorMessages } from './Errors.tsx';

export function NodeContextMenu({ nodeId, top, left, ...props }) {
const reactFlowInstance = useReactFlow();
const node = useMemo(() => reactFlowInstance.getNode(nodeId), [nodeId]);
const [runState, runStateShouldChange] = useRunState();
const API = useAPI();
const updateNodeInternals = useUpdateNodeInternals();
const notification = useNotification();

const NODE_OPTIONS = useMemo(() => [
{
Expand All @@ -21,7 +24,16 @@ export function NodeContextMenu({ nodeId, top, left, ...props }) {
const { getNodes, getEdges } = reactFlowInstance;
const nodes = getNodes();
const edges = getEdges();
const [graph, resources] = await Graph.serializeForAPI(nodes, edges);
const [[graph, resources], errors] = await Graph.serializeForAPI(nodes, edges);
if (errors.length > 0) {
notification.error({
key: 'invalid-graph',
message: 'Invalid Graph',
description: <SerializationErrorMessages errors={errors} />,
duration: 3,
})
return;
}
API.run(graph, resources, node.id);
runStateShouldChange();
}
Expand All @@ -33,7 +45,16 @@ export function NodeContextMenu({ nodeId, top, left, ...props }) {
const { getNodes, getEdges } = reactFlowInstance;
const nodes = getNodes();
const edges = getEdges();
const [graph, resources] = await Graph.serializeForAPI(nodes, edges);
const [[graph, resources], errors] = await Graph.serializeForAPI(nodes, edges);
if (errors.length > 0) {
notification.error({
key: 'invalid-graph',
message: 'Invalid Graph',
description: <SerializationErrorMessages errors={errors} />,
duration: 3,
})
return;
}
API.step(graph, resources, node.id);
runStateShouldChange();
}
Expand All @@ -45,7 +66,16 @@ export function NodeContextMenu({ nodeId, top, left, ...props }) {
const { getNodes, getEdges } = reactFlowInstance;
const nodes = getNodes();
const edges = getEdges();
const [graph, resources] = await Graph.serializeForAPI(nodes, edges);
const [[graph, resources], errors] = await Graph.serializeForAPI(nodes, edges);
if (errors.length > 0) {
notification.error({
key: 'invalid-graph',
message: 'Invalid Graph',
description: <SerializationErrorMessages errors={errors} />,
duration: 3,
})
return;
}
API.clear(graph, resources, node.id);
}
},
Expand All @@ -58,7 +88,7 @@ export function NodeContextMenu({ nodeId, top, left, ...props }) {
x: node.position.x + 50,
y: node.position.y + 50,
};

addNodes({
...node,
selected: false,
Expand Down Expand Up @@ -98,7 +128,7 @@ export function NodeContextMenu({ nodeId, top, left, ...props }) {
}
}
], [node, reactFlowInstance, runState, runStateShouldChange, API]);

const GROUP_OPTIONS = useMemo(() => {
const addExport = (isInput, exp) => {
const currentExports = isInput ? node.data.exports.inputs : node.data.exports.outputs;
Expand Down Expand Up @@ -236,7 +266,7 @@ export function NodeContextMenu({ nodeId, top, left, ...props }) {
}
}
], [node, reactFlowInstance]);

const getOptions = (nodeType) => {
if (nodeType === 'group') {
return GROUP_OPTIONS;
Expand Down
17 changes: 17 additions & 0 deletions web/src/components/Errors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { SerializationError } from '../graph';
import React, { Space, Typography } from 'antd';
const { Text } = Typography;

export function SerializationErrorMessages({ errors }: { errors: SerializationError[] }) {
return (
<Space direction="vertical">
{errors.map((error, i) => (
<div key={i}>
<Text>{error.type} on node </Text>
<Text strong>{error.node}</Text>
{error.pin && <span><Text>, at pin </Text><Text strong>{error.pin}</Text></span>}
</div>
))}
</Space>
);
}
35 changes: 27 additions & 8 deletions web/src/components/Flow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import ReactFlow, {
useNodes,
useEdges
} from 'reactflow';
import { Button, Flex, Typography, notification, theme } from 'antd';
import { Button, Flex, theme } from 'antd';
import { ClearOutlined, CaretRightOutlined, PauseOutlined } from '@ant-design/icons';
import { Graph } from '../graph';
import AddNode from './AddNode';
Expand All @@ -23,9 +23,9 @@ import { useRunState } from '../hooks/RunState';
import { GraphStore } from '../graphstore.ts';
import { NodeConfig } from './NodeConfig.tsx';
import { Subflow } from './Nodes/Subflow.tsx';
import { useFilename } from '../hooks/Filename.ts';
import { Monitor } from './Monitor.tsx';
const { Text } = Typography;
import { useNotificationInitializer, useNotification } from '../hooks/Notification';
import { SerializationErrorMessages } from './Errors.tsx';
const { useToken } = theme;
const makeDroppable = (e) => e.preventDefault();
const onLoadGraph = async (filename, API) => {
Expand All @@ -48,8 +48,8 @@ export default function Flow({ filename }) {
const [paneMenu, setPaneMenu] = useState(null);
const [runState, _] = useRunState();
const graphStore = useRef(null);
const [notificationCtrl, notificationCtxt] = useNotificationInitializer();

const [notificationCtrl, notificationCtxt] = notification.useNotification({ maxCount: 1 });
// Coalesce
const [isAddNodeActive, setIsAddNodeActive] = useState(false);
const [eventMousePos, setEventMousePos] = useState({ x: 0, y: 0 });
Expand Down Expand Up @@ -367,22 +367,41 @@ function ControlRow() {
const API = useAPI();
const nodes = useNodes();
const edges = useEdges();
const notification = useNotification();

const run = useCallback(async () => {
const [graph, resources] = await Graph.serializeForAPI(nodes, edges);
const [[graph, resources], errors] = await Graph.serializeForAPI(nodes, edges);
if (errors.length > 0) {
notification.error({
key: 'invalid-graph',
message: 'Invalid Graph',
description: <SerializationErrorMessages errors={errors}/>,
duration: 3,
})
return;
}
API.runAll(graph, resources);
runStateShouldChange();
}, [API, nodes, edges]);
}, [API, nodes, edges, notification]);

const pause = useCallback(() => {
API.pause();
runStateShouldChange();
}, [API]);

const clear = useCallback(async () => {
const [graph, resources] = await Graph.serializeForAPI(nodes, edges);
const [[graph, resources], errors] = await Graph.serializeForAPI(nodes, edges);
if (errors.length > 0) {
notification.error({
key: 'invalid-graph',
message: 'Invalid Graph',
description: <SerializationErrorMessages errors={errors}/>,
duration: 3,
})
return;
}
API.clearAll(graph, resources);
}, [API, nodes, edges]);
}, [API, nodes, edges, notification]);

return (
<div className="control-row">
Expand Down
17 changes: 14 additions & 3 deletions web/src/components/Nodes/Node.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { useAPI, useAPINodeMessage } from '../../hooks/API';
import { useFilename } from '../../hooks/Filename';
import { recordCountBadgeStyle, nodeBorderStyle, inputHandleStyle, outputHandleStyle } from '../../styles';
import { getMergedLogs, keyRecursively, mediaUrl } from '../../utils';
const { Text } = Typography;
import { useNotification } from '../../hooks/Notification';
import { SerializationErrorMessages } from '../Errors';
const { Panel } = Collapse;
const { useToken } = theme;

Expand All @@ -29,6 +30,7 @@ export function WorkflowStep({ id, data, selected }) {
const edges = useEdges();
const { token } = useToken();
const { getNode } = useReactFlow();
const notification = useNotification();

const API = useAPI();

Expand Down Expand Up @@ -76,10 +78,19 @@ export function WorkflowStep({ id, data, selected }) {
if (!API) {
return;
}
const [graph, resources] = await Graph.serializeForAPI(nodes, edges);
const [[graph, resources], errors] = await Graph.serializeForAPI(nodes, edges);
if (errors.length > 0) {
notification.error({
key: 'invalid-graph',
message: 'Invalid Graph',
description: <SerializationErrorMessages errors={errors}/>,
duration: 3,
})
return;
}
API.run(graph, resources, id);
runStateShouldChange();
}, [nodes, edges, API]);
}, [nodes, edges, API, notification]);

const borderStyle = useMemo(() => nodeBorderStyle(token, errored, selected, parentSelected), [token, errored, selected, parentSelected]);
const badgeIndicatorStyle = useMemo(() => recordCountBadgeStyle(token), [token]);
Expand Down
18 changes: 15 additions & 3 deletions web/src/components/Nodes/Subflow.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useMemo, useState, useCallback } from 'react';
import { Card, Typography, Flex, Button, Badge, theme } from 'antd';
import { Card, Typography, Flex, Button, Badge, theme, notification } from 'antd';
import { CaretRightOutlined } from '@ant-design/icons';
import { useAPI, useAPIMessage } from '../../hooks/API';
import { useRunState } from '../../hooks/RunState';
Expand All @@ -8,6 +8,8 @@ import { Graph } from '../../graph';
import { nodeBorderStyle, recordCountBadgeStyle, inputHandleStyle, outputHandleStyle } from '../../styles';
import { useFilename } from '../../hooks/Filename';
import { getGlobalRunningFile } from '../../hooks/RunState';
import { useNotification } from '../../hooks/Notification';
import { SerializationErrorMessages } from '../Errors.tsx';
const { Text } = Typography;
const { useToken } = theme;

Expand All @@ -26,6 +28,7 @@ export function Subflow({ id, data, selected }) {
const { getNode, getNodes, getEdges } = useReactFlow();
const API = useAPI();
const filename = useFilename();
const notification = useNotification();

const updateRecordCount = useCallback((node, values) => {
setRecordCount({
Expand Down Expand Up @@ -111,10 +114,19 @@ export function Subflow({ id, data, selected }) {
}
const nodes = getNodes();
const edges = getEdges();
const [graph, resources] = await Graph.serializeForAPI(nodes, edges);
const [[graph, resources], errors] = await Graph.serializeForAPI(nodes, edges);
if (errors.length > 0) {
notification.error({
key: 'invalid-graph',
message: 'Invalid Graph',
description: <SerializationErrorMessages errors={errors}/>,
duration: 3,
})
return;
}
API.run(graph, resources, id);
runStateShouldChange();
}, [API, id]);
}, [API, id, notification]);

return (
<div style={borderStyle}>
Expand Down
1 change: 1 addition & 0 deletions web/src/components/Nodes/node.css
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ textarea.code {
.workflow-node .handles .outputs {
display: flex;
flex-direction: column;
align-items: flex-end;
}

.workflow-node .handles .outputs .output {
Expand Down
Loading

0 comments on commit 4090f51

Please sign in to comment.