fix(workflows): resume saving after metadata confirmation (#345)

This commit is contained in:
Zeyu Shao (邵泽宇) authored and GitHub committed 2026-10-08 11:08:46 +08:00
1 parent 8e00147d06
commit aadcdcbcfc
2 files changed
+122 -3

No files matched your search

+114
View File
@@ -0,0 +1,114 @@
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const test = require('node:test');
const assert = require('node:assert/strict');
const source = fs.readFileSync(path.join(__dirname, 'workflows.js'), 'utf8');
function createEditor() {
const elements = {
'workflow-id': { value: '' },
'workflow-name': { value: '' },
'workflow-description': { value: '' },
'workflow-enabled': { checked: true },
'workflow-canvas': {}
};
const nodes = [
{ id: 'start', type: 'start', config: {} },
{ id: 'output', type: 'output', config: { output_key: 'result' } }
].map(data => ({ id: () => data.id, data: key => data[key], position: () => ({ x: 0, y: 0 }) }));
const state = { calls: [], notifications: [], workflows: [], modalOpen: false };
const context = {
document: { getElementById: id => elements[id] || null, querySelector: () => null, addEventListener() {} },
cytoscape: () => ({
nodes: () => nodes,
edges: () => [{ id: () => 'edge', source: () => nodes[0], target: () => nodes[1], data: () => '' }],
on() {}, resize() {}
}),
openAppModal: () => { state.modalOpen = true; },
closeAppModal: () => { state.modalOpen = false; },
showNotification: (message, type) => state.notifications.push({ message, type }),
apiFetch: async (url, options = {}) => {
const body = options.body ? JSON.parse(options.body) : undefined;
state.calls.push({ url, method: options.method || 'GET', body });
if (url === '/api/workflows/validate') {
if (state.validationGate) await state.validationGate;
return { ok: true };
}
if (options.method === 'POST' || options.method === 'PUT') {
const workflow = { ...body, graph_json: JSON.stringify(body.graph), version: 1 };
state.workflows = [workflow];
return { ok: true, json: async () => ({ workflow }) };
}
if (url.startsWith('/api/workflows?')) return { ok: true, json: async () => ({ workflows: state.workflows }) };
throw new Error('Unexpected API: ' + url);
}
};
context.window = context;
vm.createContext(context);
vm.runInContext(source, context, { filename: 'workflows.js' });
const fillMeta = () => {
elements['workflow-id'].value = 'save-regression';
elements['workflow-name'].value = 'Save regression';
};
const saves = () => state.calls.filter(call => call.url === '/api/workflows' && call.method === 'POST');
return { context, state, elements, fillMeta, saves };
}
test('Save resumes after metadata confirmation and submits the graph once', async () => {
const editor = createEditor();
await editor.context.saveWorkflowDraft();
assert.equal(editor.state.modalOpen, true);
assert.equal(editor.state.calls.length, 0);
editor.fillMeta();
let finishValidation;
editor.state.validationGate = new Promise(resolve => { finishValidation = resolve; });
const saving = editor.context.applyWorkflowMetaModal();
await editor.context.applyWorkflowMetaModal();
assert.equal(editor.state.calls.filter(call => call.url === '/api/workflows/validate').length, 1);
assert.equal(editor.saves().length, 0);
finishValidation();
await saving;
assert.equal(editor.saves().length, 1);
assert.equal(editor.saves()[0].body.name, 'Save regression');
assert.deepEqual(editor.saves()[0].body.graph.nodes.map(node => node.id), ['start', 'output']);
assert.equal(editor.state.modalOpen, false);
assert.equal(editor.state.notifications.at(-1).message, 'workflows.saved');
assert.ok(editor.state.calls.some(call => call.url.startsWith('/api/workflows?')));
});
test('Cancel clears a pending save; reopening metadata only edits the draft', async () => {
const editor = createEditor();
await editor.context.saveWorkflowDraft();
editor.context.closeWorkflowMetaModal();
editor.fillMeta();
await editor.context.applyWorkflowMetaModal();
assert.equal(editor.state.calls.length, 0);
await editor.context.saveWorkflowDraft();
assert.equal(editor.saves().length, 1);
const callCount = editor.state.calls.length;
editor.context.openWorkflowMetaModal();
await editor.context.applyWorkflowMetaModal();
assert.equal(editor.state.calls.length, callCount);
});
test('Opening metadata normally replaces pending save intent', async () => {
const editor = createEditor();
await editor.context.saveWorkflowDraft();
editor.context.openWorkflowMetaModal();
editor.fillMeta();
await editor.context.applyWorkflowMetaModal();
assert.equal(editor.state.calls.length, 0);
});
test('Invalid metadata keeps the pending save until corrected', async () => {
const editor = createEditor();
await editor.context.saveWorkflowDraft();
await editor.context.applyWorkflowMetaModal();
assert.equal(editor.state.modalOpen, true);
assert.equal(editor.state.calls.length, 0);
editor.fillMeta();
await editor.context.applyWorkflowMetaModal();
assert.equal(editor.saves().length, 1);
});
+8 -3
View File
@@ -15,6 +15,7 @@
let workflows = [];
let currentWorkflowId = '';
let workflowMetaSavePending = false;
let cy = null;
let nodeSeq = 1;
let edgeSeq = 1;
@@ -1993,7 +1994,8 @@
if (wf) fillWorkflowForm(wf);
};
window.openWorkflowMetaModal = function () {
window.openWorkflowMetaModal = function (options) {
workflowMetaSavePending = !!(options && options.saveOnApply);
const nameEl = document.getElementById('workflow-name');
const idEl = document.getElementById('workflow-id');
if (currentWorkflowId) {
@@ -2009,12 +2011,13 @@
};
window.closeWorkflowMetaModal = function () {
workflowMetaSavePending = false;
if (typeof closeAppModal === 'function') {
closeAppModal('workflow-meta-modal');
}
};
window.applyWorkflowMetaModal = function () {
window.applyWorkflowMetaModal = async function () {
const meta = readWorkflowMetaFromForm();
if (!meta.id || !meta.name) {
if (typeof showNotification === 'function') {
@@ -2024,7 +2027,9 @@
}
updateWorkflowCanvasTitle();
renderWorkflowList();
const shouldSave = workflowMetaSavePending;
closeWorkflowMetaModal();
if (shouldSave) await saveWorkflowDraft();
};
window.editWorkflowFromList = function (id) {
@@ -2232,7 +2237,7 @@
if (typeof showNotification === 'function') {
showNotification(_t('workflows.idNameRequired'), 'error');
}
openWorkflowMetaModal();
openWorkflowMetaModal({ saveOnApply: true });
return;
}
const graph = elementsToGraph();