Skip to content

Commit d646580

Browse files
authored
check integrations before running (#75)
* step handler * step logging * better dx * better dx * workflow complete * check integrations before running * add check for db too * adds guard
1 parent f0e8f82 commit d646580

File tree

3 files changed

+234
-149
lines changed

3 files changed

+234
-149
lines changed

app/workflows/[workflowId]/page.tsx

Lines changed: 8 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,15 @@ import {
1616
edgesAtom,
1717
hasSidebarBeenShownAtom,
1818
hasUnsavedChangesAtom,
19-
isExecutingAtom,
2019
isGeneratingAtom,
2120
isPanelAnimatingAtom,
2221
isSavingAtom,
2322
isSidebarCollapsedAtom,
2423
nodesAtom,
25-
propertiesPanelActiveTabAtom,
2624
rightPanelWidthAtom,
2725
selectedExecutionIdAtom,
2826
selectedNodeAtom,
27+
triggerExecuteAtom,
2928
updateNodeDataAtom,
3029
type WorkflowNode,
3130
workflowNotFoundAtom,
@@ -40,14 +39,11 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
4039
const searchParams = useSearchParams();
4140
const isMobile = useIsMobile();
4241
const [isGenerating, setIsGenerating] = useAtom(isGeneratingAtom);
43-
const [isExecuting, setIsExecuting] = useAtom(isExecutingAtom);
4442
const [_isSaving, setIsSaving] = useAtom(isSavingAtom);
4543
const [nodes] = useAtom(nodesAtom);
4644
const [edges] = useAtom(edgesAtom);
4745
const [currentWorkflowId] = useAtom(currentWorkflowIdAtom);
48-
const [selectedExecutionId, setSelectedExecutionId] = useAtom(
49-
selectedExecutionIdAtom
50-
);
46+
const [selectedExecutionId] = useAtom(selectedExecutionIdAtom);
5147
const setNodes = useSetAtom(nodesAtom);
5248
const setEdges = useSetAtom(edgesAtom);
5349
const setCurrentWorkflowId = useSetAtom(currentWorkflowIdAtom);
@@ -56,7 +52,7 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
5652
const setSelectedNodeId = useSetAtom(selectedNodeAtom);
5753
const setHasUnsavedChanges = useSetAtom(hasUnsavedChangesAtom);
5854
const [workflowNotFound, setWorkflowNotFound] = useAtom(workflowNotFoundAtom);
59-
const setActiveTab = useSetAtom(propertiesPanelActiveTabAtom);
55+
const setTriggerExecute = useSetAtom(triggerExecuteAtom);
6056
const setRightPanelWidth = useSetAtom(rightPanelWidthAtom);
6157
const setIsPanelAnimating = useSetAtom(isPanelAnimatingAtom);
6258
const [hasSidebarBeenShown, setHasSidebarBeenShown] = useAtom(
@@ -372,144 +368,6 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
372368
setHasUnsavedChanges,
373369
]);
374370

375-
// Helper to update node statuses
376-
const updateAllNodeStatuses = useCallback(
377-
(status: "idle" | "error" | "success") => {
378-
for (const node of nodes) {
379-
updateNodeData({ id: node.id, data: { status } });
380-
}
381-
},
382-
[nodes, updateNodeData]
383-
);
384-
385-
// Helper to poll execution status
386-
const pollExecutionStatus = useCallback(
387-
async (executionId: string) => {
388-
try {
389-
const statusData = await api.workflow.getExecutionStatus(executionId);
390-
391-
// Update node statuses based on the execution logs
392-
for (const nodeStatus of statusData.nodeStatuses) {
393-
updateNodeData({
394-
id: nodeStatus.nodeId,
395-
data: {
396-
status: nodeStatus.status as
397-
| "idle"
398-
| "running"
399-
| "success"
400-
| "error",
401-
},
402-
});
403-
}
404-
405-
// Return status and whether execution is complete
406-
return {
407-
isComplete: statusData.status !== "running",
408-
status: statusData.status,
409-
};
410-
} catch (error) {
411-
console.error("Failed to poll execution status:", error);
412-
return { isComplete: false, status: "running" };
413-
}
414-
},
415-
[updateNodeData]
416-
);
417-
418-
const handleRun = useCallback(async () => {
419-
if (
420-
isExecuting ||
421-
nodes.length === 0 ||
422-
isGenerating ||
423-
!currentWorkflowId
424-
) {
425-
return;
426-
}
427-
428-
// Switch to Runs tab when starting a test run
429-
setActiveTab("runs");
430-
431-
// Deselect all nodes and edges
432-
setNodes(nodes.map((node) => ({ ...node, selected: false })));
433-
setEdges(edges.map((edge) => ({ ...edge, selected: false })));
434-
setSelectedNodeId(null);
435-
436-
setIsExecuting(true);
437-
438-
// Set all nodes to idle first
439-
updateAllNodeStatuses("idle");
440-
441-
// Immediately set trigger nodes to running for instant visual feedback
442-
for (const node of nodes) {
443-
if (node.data.type === "trigger") {
444-
updateNodeData({ id: node.id, data: { status: "running" } });
445-
}
446-
}
447-
448-
try {
449-
// Start the execution via API
450-
const response = await fetch(
451-
`/api/workflow/${currentWorkflowId}/execute`,
452-
{
453-
method: "POST",
454-
headers: {
455-
"Content-Type": "application/json",
456-
},
457-
body: JSON.stringify({ input: {} }),
458-
}
459-
);
460-
461-
if (!response.ok) {
462-
throw new Error("Failed to execute workflow");
463-
}
464-
465-
const result = await response.json();
466-
467-
// Select the new execution
468-
setSelectedExecutionId(result.executionId);
469-
470-
// Poll for execution status updates
471-
const pollInterval = setInterval(async () => {
472-
const { isComplete } = await pollExecutionStatus(result.executionId);
473-
474-
if (isComplete) {
475-
if (executionPollingIntervalRef.current) {
476-
clearInterval(executionPollingIntervalRef.current);
477-
executionPollingIntervalRef.current = null;
478-
}
479-
480-
setIsExecuting(false);
481-
482-
// Don't reset node statuses - let them show the final state
483-
// The user can click another run or deselect to reset
484-
}
485-
}, 500); // Poll every 500ms
486-
487-
executionPollingIntervalRef.current = pollInterval;
488-
} catch (error) {
489-
console.error("Failed to execute workflow:", error);
490-
toast.error(
491-
error instanceof Error ? error.message : "Failed to execute workflow"
492-
);
493-
updateAllNodeStatuses("error");
494-
setIsExecuting(false);
495-
}
496-
}, [
497-
isExecuting,
498-
nodes,
499-
edges,
500-
isGenerating,
501-
currentWorkflowId,
502-
setIsExecuting,
503-
updateAllNodeStatuses,
504-
pollExecutionStatus,
505-
updateNodeData,
506-
setActiveTab,
507-
setNodes,
508-
setEdges,
509-
setSelectedNodeId,
510-
setSelectedExecutionId,
511-
]);
512-
513371
// Helper to check if target is an input element
514372
const isInputElement = useCallback(
515373
(target: HTMLElement) =>
@@ -538,19 +396,22 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
538396
);
539397

540398
// Helper to handle run shortcut
399+
// Uses triggerExecuteAtom to share the same execute flow as the Run button
400+
// This ensures keyboard shortcut goes through the same checks (e.g., missing integrations)
541401
const handleRunShortcut = useCallback(
542402
(e: KeyboardEvent, target: HTMLElement) => {
543403
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
544404
if (!(isInputElement(target) || isInMonacoEditor(target))) {
545405
e.preventDefault();
546406
e.stopPropagation();
547-
handleRun();
407+
// Trigger execute via atom - the toolbar will handle it
408+
setTriggerExecute(true);
548409
}
549410
return true;
550411
}
551412
return false;
552413
},
553-
[handleRun, isInputElement, isInMonacoEditor]
414+
[setTriggerExecute, isInputElement, isInMonacoEditor]
554415
);
555416

556417
useEffect(() => {

0 commit comments

Comments
 (0)