fix(wizard): use real EA2 flow/defines collections for steps

EA2's apply-batch doc collections are restricted to ['data','flow','rule',
'version','view']; there is no 'ea2_node'. Steps are themselves 'flow'
documents (kind='definition') linked to the parent flow via the 'defines'
edge collection with role='next' (or 'presentation').

handleStructureSave now does two batches:
1) create N step flows, capture server-assigned keys
2) wire parent->step1->...->stepN with create_edge on 'defines'
This commit is contained in:
2026-06-14 12:23:22 +04:00
parent 7b4f76c1de
commit b7145c325b
2 changed files with 37 additions and 26 deletions
+19 -12
View File
@@ -120,30 +120,37 @@ export const wizardApi = {
return api.startTransaction(process_definition_id, business_subject, signal); return api.startTransaction(process_definition_id, business_subject, signal);
}, },
/** Helpers so call sites don't have to know the discriminated-union shape by heart. */
ops: { ops: {
createNode(flowKey: string, node: { _key: string; display_name: string; dispatch_kind: string; agent_capability?: string }): CreateOp { createStep(node: { display_name: string; dispatch_kind: string; agent_capability?: string; description?: string }): CreateOp {
const { _key, ...rest } = node; const name = node.display_name.toLowerCase().replace(/[^a-z0-9]+/g, "_").slice(0, 40) || "step";
return { return {
op: "create", op: "create",
coll: "ea2_node", coll: "flow",
data: { _key, flow_key: flowKey, ...rest }, data: {
kind: "definition",
name,
display_name: node.display_name,
status: "draft",
description: node.description || node.display_name,
source_context: "EA2_WIZARD_STEP",
dispatch_kind: node.dispatch_kind,
...(node.agent_capability ? { agent_capability: node.agent_capability } : {}),
},
}; };
}, },
createEdge(_flowKey: string, fromKey: string, toKey: string, role = "next"): CreateEdgeOp { createStepEdge(parentFlowKey: string, childFlowKey: string): CreateEdgeOp {
// EA2 server rejects `data` on create_edge despite the OpenAPI schema marking it optional.
return { return {
op: "create_edge", op: "create_edge",
edge_coll: "ea2_edge", edge_coll: "defines",
from: `ea2_node/${fromKey}`, from: `flow/${parentFlowKey}`,
to: `ea2_node/${toKey}`, to: `flow/${childFlowKey}`,
role, role: "next",
}; };
}, },
publishFlow(flowKey: string): UpdateOp { publishFlow(flowKey: string): UpdateOp {
return { return {
op: "update", op: "update",
coll: "ea2_flow", coll: "flow",
key: flowKey, key: flowKey,
data: { status: "published" }, data: { status: "published" },
}; };
+14 -10
View File
@@ -103,23 +103,27 @@ export default function Wizard() {
if (!draft.flowKey || !actor) return; if (!draft.flowKey || !actor) return;
setWorking(true); setWorking(true);
try { try {
const ops: BatchOperation[] = [ const createOps: BatchOperation[] = draft.nodes.map(n =>
...draft.nodes.map(n => wizardApi.ops.createStep({
wizardApi.ops.createNode(draft.flowKey!, {
_key: n._key,
display_name: n.display_name, display_name: n.display_name,
dispatch_kind: n.dispatch_kind, dispatch_kind: n.dispatch_kind,
agent_capability: n.agent_capability, agent_capability: n.agent_capability,
}) })
), );
...draft.nodes.slice(0, -1).map((n, i) => const createRes = await wizardApi.applyBatch(draft.flowKey, createOps, actor);
wizardApi.ops.createEdge(draft.flowKey!, n._key, draft.nodes[i + 1]._key, "next") const createdKeys: string[] = (createRes?.result?.ops || []).map((o: any) => o.key);
const nodesWithKeys = draft.nodes.map((n, i) => ({ ...n, _key: createdKeys[i] || n._key }));
const linkOps: BatchOperation[] = [
wizardApi.ops.createStepEdge(draft.flowKey!, nodesWithKeys[0]._key),
...nodesWithKeys.slice(0, -1).map((n, i) =>
wizardApi.ops.createStepEdge(n._key, nodesWithKeys[i + 1]._key)
), ),
]; ];
if (linkOps.length) await wizardApi.applyBatch(draft.flowKey, linkOps, actor);
await wizardApi.applyBatch(draft.flowKey, ops, actor); updateDraft({ step: "Generate", nodes: nodesWithKeys });
updateDraft({ step: "Generate" }); pushToast("ok", `Saved ${createdKeys.length} steps to EA2`);
pushToast("ok", "Structure saved");
} catch (err: any) { } catch (err: any) {
pushToast("err", `Save failed: ${err.message}`); pushToast("err", `Save failed: ${err.message}`);
} finally { } finally {