feat(server): enhance workflow step execution and JSONata integration

- Refactored step execution logic to introduce runCompiledStep for improved clarity and functionality.
- Added support for "set" steps, allowing context manipulation using JSONata expressions.
- Implemented evaluateJsonata and isJsonataTruthy functions for evaluating conditions and expressions.
- Updated workflow parsing to handle new step types and validation rules.
- Enhanced logging and error handling for skipped steps and invalid configurations.
- Modified UI components to reflect new step types and statuses, including "skipped".
This commit is contained in:
2026-08-14 21:13:40 +07:00
parent fdb1c0a8a6
commit d4381d2943
8 changed files with 248 additions and 39 deletions
+3 -1
View File
@@ -13,7 +13,9 @@ export function StatusBadge({ status }) {
? "badge-error"
: status === "running"
? "badge-warning"
: "badge-ghost";
: status === "skipped"
? "badge-info"
: "badge-ghost";
return <span className={`badge badge-sm ${cls}`}>{status ?? "—"}</span>;
}
+31 -4
View File
@@ -1,12 +1,26 @@
function parseStep(step) {
if (typeof step === "string") {
return { script: step, id: null, needs: null };
return { kind: "script", script: step, id: null, needs: null, when: null, as: null };
}
if (step?.set) {
const as = typeof step.set?.as === "string" && step.set.as ? step.set.as : "set";
return {
kind: "set",
script: null,
as,
id: typeof step.id === "string" && step.id ? step.id : null,
needs: null,
when: typeof step.when === "string" && step.when ? step.when : null,
};
}
if (step?.script) {
return {
kind: "script",
script: step.script,
as: null,
id: typeof step.id === "string" && step.id ? step.id : null,
needs: step.needs ?? null,
when: typeof step.when === "string" && step.when ? step.when : null,
};
}
return null;
@@ -38,6 +52,15 @@ function triggerLabel(t) {
return `${method} ${path}`.trim();
}
function stepLabel(s) {
if (s.kind === "set") {
const base = s.id ? `${s.id}: set ${s.as}` : `set ${s.as}`;
return s.when ? `${base} when: ${s.when}` : base;
}
const base = s.id ? `${s.id}: ${s.script}` : s.script;
return s.when ? `${base} when: ${s.when}` : base;
}
export function workflowToFlowchart(parsed) {
/** @type {Record<string, string>} */
const scriptIds = {};
@@ -72,9 +95,13 @@ export function workflowToFlowchart(parsed) {
}
for (const s of parsedSteps) {
scriptIds[s.mermaidId] = s.script;
const label = s.id ? `${s.id}: ${s.script}` : s.script;
lines.push(` ${s.mermaidId}["${mermaidLabel(label)}"]`);
const label = mermaidLabel(stepLabel(s));
if (s.kind === "set") {
lines.push(` ${s.mermaidId}(["${label}"])`);
} else {
scriptIds[s.mermaidId] = s.script;
lines.push(` ${s.mermaidId}["${label}"]`);
}
}
if (dagMode) {
+18 -3
View File
@@ -4,6 +4,17 @@ import { useRun } from "../api/hooks.js";
import { LogViewer } from "../components/LogViewer.jsx";
import { formatTime, StatusBadge } from "../lib/format.jsx";
function stepLabel(s) {
if (s.script === "set") {
return s.config?.as ? `set:${s.config.as}` : "set";
}
return s.script;
}
function isEditableScript(s) {
return Boolean(s.script) && s.script !== "set";
}
export function EventDetailPage() {
const { id } = useParams();
const { data: run, isLoading, error } = useRun(id);
@@ -67,9 +78,13 @@ export function EventDetailPage() {
<tr key={s.id}>
<td>{s.step_index}</td>
<td className="font-mono">
<Link className="link" to={`/scripts/${encodeURIComponent(s.script)}/edit`}>
{s.script}
</Link>
{isEditableScript(s) ? (
<Link className="link" to={`/scripts/${encodeURIComponent(s.script)}/edit`}>
{stepLabel(s)}
</Link>
) : (
stepLabel(s)
)}
</td>
<td>
<StatusBadge status={s.status} />
+1
View File
@@ -38,6 +38,7 @@ export function EventsPage() {
<option value="running">running</option>
<option value="success">success</option>
<option value="failed">failed</option>
<option value="skipped">skipped</option>
</select>
</div>
{isLoading ? (