feat(editor): visible feedback for Test run — status pill + View link

Before, the Test run button fired and returned an execution id but there was
no on-screen confirmation, so users assumed nothing happened. Adds an inline
status bar under the toolbar that shows 'Running…', then either 'Started
execution <id> · View execution →' (auto-clears in 6s) or 'Test run failed:
<msg>' (auto-clears in 8s).
This commit is contained in:
Claude
2026-08-28 19:53:34 +00:00
parent c705f58531
commit 5d04c0a2d4

View File

@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { Link, useParams } from "react-router-dom";
import type { Edge, Node } from "@xyflow/react";
import { useNodeTypes, useWorkflow } from "@/api/hooks";
@@ -82,6 +82,19 @@ export default function EditorPage() {
() => apiFetch<void>(`/api/workflows/${id}/deactivate`, { method: "POST" }),
{ onSuccess: () => id && invalidate(qk.workflow(id)) },
);
const [testStatus, setTestStatus] = useState<
| { kind: "idle" }
| { kind: "running" }
| { kind: "success"; executionId: string }
| { kind: "error"; message: string }
>({ kind: "idle" });
const clearTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => () => { if (clearTimer.current) clearTimeout(clearTimer.current); }, []);
function scheduleClear(ms: number) {
if (clearTimer.current) clearTimeout(clearTimer.current);
clearTimer.current = setTimeout(() => setTestStatus({ kind: "idle" }), ms);
}
const testRun = useApiMutation(
() => {
const triggers = nodes.filter((n) =>
@@ -93,6 +106,17 @@ export default function EditorPage() {
{ method: "POST", body: { startFromNodeId: startFrom, triggerData: {} } },
);
},
{
onMutate: () => setTestStatus({ kind: "running" }),
onSuccess: (res) => {
setTestStatus({ kind: "success", executionId: res.executionId });
scheduleClear(6000);
},
onError: (err) => {
setTestStatus({ kind: "error", message: err instanceof Error ? err.message : String(err) });
scheduleClear(8000);
},
},
);
if (wf.isLoading || !wf.data) {
@@ -144,6 +168,33 @@ export default function EditorPage() {
)}
</div>
</div>
{testStatus.kind !== "idle" && (
<div
role="status"
className={
"px-4 py-2 text-sm border-b flex items-center gap-2 " +
(testStatus.kind === "success"
? "bg-emerald-950/50 border-emerald-900 text-emerald-200"
: testStatus.kind === "error"
? "bg-red-950/50 border-red-900 text-red-200"
: "bg-slate-900 border-slate-800 text-slate-300")
}
>
{testStatus.kind === "running" && <>Running test execution…</>}
{testStatus.kind === "success" && (
<>
Started execution <code className="font-mono text-xs">{testStatus.executionId}</code>
<Link
to={`/workflows/${id}/executions`}
className="ml-auto underline hover:text-emerald-100"
>
View execution →
</Link>
</>
)}
{testStatus.kind === "error" && <>Test run failed: {testStatus.message}</>}
</div>
)}
<div className="flex-1 flex">
<NodePalette />
<div className="flex-1">