Skip to content

Commit cbb961e

Browse files
Enhance output 1 to fire for all status updates
- Output 1 now fires for ALL statuses (submitted, running, succeeded, failed, cancelled) to enable comprehensive status tracking - Output 2 still fires only on success (for flow control) - Output 3 still fires only on failure/cancel (for flow control) - Updated HTML help documentation to explain new behavior - Added tests verifying both outputs fire for terminal states This change allows users to connect a single wire to output 1 for tracking all workflow state changes, while still using outputs 2/3 for flow control purposes. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 78454af commit cbb961e

File tree

3 files changed

+211
-10
lines changed

3 files changed

+211
-10
lines changed

nodes/workflow-monitor.html

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,20 @@
4848

4949
### Outputs
5050

51-
1. Status updates (active workflows)
52-
: payload (object) : The workflow details from the API, sent on every status poll while workflow is active.
51+
1. All status updates
52+
: payload (object) : The workflow details from the API, sent on **every** status poll including terminal states. Use this output for tracking/monitoring all workflow state changes.
5353
: workflowId (string) : The ID of the workflow.
5454

5555
2. Success
56-
: payload (object) : The workflow details from the API, sent once when the workflow completes successfully.
56+
: payload (object) : The workflow details from the API, sent once when the workflow completes successfully. Use this output for flow control to trigger downstream success actions.
5757
: workflowId (string) : The ID of the workflow.
5858

5959
3. Error
60-
: payload (object) : The workflow details from the API, sent once when the workflow fails or is cancelled.
60+
: payload (object) : The workflow details from the API, sent once when the workflow fails or is cancelled. Use this output for flow control to trigger downstream error handling.
6161
: workflowId (string) : The ID of the workflow.
6262

63+
**Note:** When a workflow succeeds, both outputs 1 and 2 fire. When a workflow fails/cancels, both outputs 1 and 3 fire. This allows output 1 to be used for comprehensive status tracking while outputs 2 and 3 can be used for flow control.
64+
6365
### Details
6466

6567
This node queries the Seqera Platform API for the current status of a workflow run. When *Keep polling status* is enabled (default) the node will keep polling at the configured interval until the workflow reaches a terminal state (succeeded, failed, cancelled, or unknown).

nodes/workflow-monitor.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,16 +122,17 @@ module.exports = function (RED) {
122122
};
123123

124124
// Decide which output to send to
125-
// Output 1: Active (submitted, running)
126-
// Output 2: Succeeded
127-
// Output 3: Failed/Cancelled/Unknown
125+
// Output 1: All status updates (always fires for tracking/monitoring)
126+
// Output 2: Succeeded (terminal state, for flow control)
127+
// Output 3: Failed/Cancelled/Unknown (terminal state, for flow control)
128128
if (/^(submitted|running)$/.test(statusLower)) {
129129
send([outMsg, null, null]);
130130
} else if (/^(succeeded)$/.test(statusLower)) {
131-
send([null, outMsg, null]);
131+
// Send to both output 1 (status tracking) and output 2 (success flow)
132+
send([outMsg, outMsg, null]);
132133
} else {
133-
// failed, cancelled, unknown
134-
send([null, null, outMsg]);
134+
// failed, cancelled, unknown - send to both output 1 (status tracking) and output 3 (failure flow)
135+
send([outMsg, null, outMsg]);
135136
}
136137

137138
// If keepPolling disabled OR workflow reached a final state, stop polling THIS workflow

test/workflow-monitor_spec.js

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,204 @@ describe("seqera-workflow-monitor Node", function () {
414414
monitorNode.receive({ workflowId: "wf-123" });
415415
});
416416
});
417+
418+
it("should send to BOTH output 1 and output 2 when status is succeeded", function (done) {
419+
const flow = [
420+
createConfigNode(),
421+
{
422+
id: "monitor1",
423+
type: "seqera-workflow-monitor",
424+
name: "Test Monitor",
425+
seqera: "config-node-1",
426+
workflowId: "workflowId",
427+
workflowIdType: "msg",
428+
keepPolling: false,
429+
wires: [["helper1"], ["helper2"], ["helper3"]],
430+
},
431+
{ id: "helper1", type: "helper" },
432+
{ id: "helper2", type: "helper" },
433+
{ id: "helper3", type: "helper" },
434+
];
435+
436+
nock(DEFAULT_BASE_URL)
437+
.get("/workflow/wf-123")
438+
.query(true)
439+
.reply(200, createWorkflowResponse({ status: "succeeded" }));
440+
441+
helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () {
442+
const monitorNode = helper.getNode("monitor1");
443+
const helper1 = helper.getNode("helper1");
444+
const helper2 = helper.getNode("helper2");
445+
const helper3 = helper.getNode("helper3");
446+
447+
let output1Received = false;
448+
let output2Received = false;
449+
450+
const checkDone = () => {
451+
if (output1Received && output2Received) {
452+
done();
453+
}
454+
};
455+
456+
helper1.on("input", function (msg) {
457+
try {
458+
expect(msg.payload.workflow.status).to.equal("succeeded");
459+
output1Received = true;
460+
checkDone();
461+
} catch (err) {
462+
done(err);
463+
}
464+
});
465+
466+
helper2.on("input", function (msg) {
467+
try {
468+
expect(msg.payload.workflow.status).to.equal("succeeded");
469+
output2Received = true;
470+
checkDone();
471+
} catch (err) {
472+
done(err);
473+
}
474+
});
475+
476+
helper3.on("input", function () {
477+
done(new Error("Should not receive on output 3 for succeeded status"));
478+
});
479+
480+
monitorNode.receive({ workflowId: "wf-123" });
481+
});
482+
});
483+
484+
it("should send to BOTH output 1 and output 3 when status is failed", function (done) {
485+
const flow = [
486+
createConfigNode(),
487+
{
488+
id: "monitor1",
489+
type: "seqera-workflow-monitor",
490+
name: "Test Monitor",
491+
seqera: "config-node-1",
492+
workflowId: "workflowId",
493+
workflowIdType: "msg",
494+
keepPolling: false,
495+
wires: [["helper1"], ["helper2"], ["helper3"]],
496+
},
497+
{ id: "helper1", type: "helper" },
498+
{ id: "helper2", type: "helper" },
499+
{ id: "helper3", type: "helper" },
500+
];
501+
502+
nock(DEFAULT_BASE_URL)
503+
.get("/workflow/wf-123")
504+
.query(true)
505+
.reply(200, createWorkflowResponse({ status: "failed" }));
506+
507+
helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () {
508+
const monitorNode = helper.getNode("monitor1");
509+
const helper1 = helper.getNode("helper1");
510+
const helper2 = helper.getNode("helper2");
511+
const helper3 = helper.getNode("helper3");
512+
513+
let output1Received = false;
514+
let output3Received = false;
515+
516+
const checkDone = () => {
517+
if (output1Received && output3Received) {
518+
done();
519+
}
520+
};
521+
522+
helper1.on("input", function (msg) {
523+
try {
524+
expect(msg.payload.workflow.status).to.equal("failed");
525+
output1Received = true;
526+
checkDone();
527+
} catch (err) {
528+
done(err);
529+
}
530+
});
531+
532+
helper2.on("input", function () {
533+
done(new Error("Should not receive on output 2 for failed status"));
534+
});
535+
536+
helper3.on("input", function (msg) {
537+
try {
538+
expect(msg.payload.workflow.status).to.equal("failed");
539+
output3Received = true;
540+
checkDone();
541+
} catch (err) {
542+
done(err);
543+
}
544+
});
545+
546+
monitorNode.receive({ workflowId: "wf-123" });
547+
});
548+
});
549+
550+
it("should send to BOTH output 1 and output 3 when status is cancelled", function (done) {
551+
const flow = [
552+
createConfigNode(),
553+
{
554+
id: "monitor1",
555+
type: "seqera-workflow-monitor",
556+
name: "Test Monitor",
557+
seqera: "config-node-1",
558+
workflowId: "workflowId",
559+
workflowIdType: "msg",
560+
keepPolling: false,
561+
wires: [["helper1"], ["helper2"], ["helper3"]],
562+
},
563+
{ id: "helper1", type: "helper" },
564+
{ id: "helper2", type: "helper" },
565+
{ id: "helper3", type: "helper" },
566+
];
567+
568+
nock(DEFAULT_BASE_URL)
569+
.get("/workflow/wf-123")
570+
.query(true)
571+
.reply(200, createWorkflowResponse({ status: "cancelled" }));
572+
573+
helper.load([configNode, workflowMonitorNode], flow, createCredentials(), function () {
574+
const monitorNode = helper.getNode("monitor1");
575+
const helper1 = helper.getNode("helper1");
576+
const helper2 = helper.getNode("helper2");
577+
const helper3 = helper.getNode("helper3");
578+
579+
let output1Received = false;
580+
let output3Received = false;
581+
582+
const checkDone = () => {
583+
if (output1Received && output3Received) {
584+
done();
585+
}
586+
};
587+
588+
helper1.on("input", function (msg) {
589+
try {
590+
expect(msg.payload.workflow.status).to.equal("cancelled");
591+
output1Received = true;
592+
checkDone();
593+
} catch (err) {
594+
done(err);
595+
}
596+
});
597+
598+
helper2.on("input", function () {
599+
done(new Error("Should not receive on output 2 for cancelled status"));
600+
});
601+
602+
helper3.on("input", function (msg) {
603+
try {
604+
expect(msg.payload.workflow.status).to.equal("cancelled");
605+
output3Received = true;
606+
checkDone();
607+
} catch (err) {
608+
done(err);
609+
}
610+
});
611+
612+
monitorNode.receive({ workflowId: "wf-123" });
613+
});
614+
});
417615
});
418616

419617
describe("polling behavior", function () {

0 commit comments

Comments
 (0)