Skip to content

Commit d11f409

Browse files
authored
Merge pull request #21 from seqeralabs/poll-workflows
New poll-workflows node
2 parents 3b970fa + b6dc278 commit d11f409

File tree

10 files changed

+442
-33
lines changed

10 files changed

+442
-33
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Gives new Node-RED node types for your automation workflows, which are designed
1919

2020
- [Launch a workflow](https://seqeralabs.github.io/node-red-seqera/seqera_nodes/launch_workflow/)
2121
- [Monitor a workflow](https://seqeralabs.github.io/node-red-seqera/seqera_nodes/monitor_workflow/)
22+
- [Poll Workflows](https://seqeralabs.github.io/node-red-seqera/seqera_nodes/poll_workflows/)
2223
- [Add Dataset](https://seqeralabs.github.io/node-red-seqera/seqera_nodes/add_dataset/)
2324
- [List Files from Data Explorer](https://seqeralabs.github.io/node-red-seqera/seqera_nodes/list_files/)
2425
- [Poll Data Link Files](https://seqeralabs.github.io/node-red-seqera/seqera_nodes/poll_files/)

docs/img/poll_workflows_node.png

35.1 KB
Loading
87 KB
Loading

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Gives new Node-RED node types for your automation workflows, which are designed
1313

1414
- [Launch a workflow](seqera_nodes/launch_workflow.md)
1515
- [Monitor a workflow](seqera_nodes/monitor_workflow.md)
16+
- [Poll Workflows](seqera_nodes/poll_workflows.md)
1617
- [Add Dataset](seqera_nodes/add_dataset.md)
1718
- [List Files from Data Explorer](seqera_nodes/list_files.md)
1819
- [Poll Data Link Files](seqera_nodes/poll_files.md)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Poll workflows
2+
3+
**Periodically list workflow runs from Seqera Platform and emit messages when new workflows are detected.**
4+
5+
This node automatically monitors your workspace for _new workflow runs_. Trigger events when new pipelines are launched by other users or systems.
6+
7+
!!! note
8+
9+
The node starts polling as soon as the flow is deployed – it has **no message inputs**.
10+
11+
<figure markdown="span">
12+
![poll workflows node](../img/poll_workflows_node.png){ width=400}
13+
![poll workflows node edit panel](../img/poll_workflows_node_edit.png){ width=600}
14+
</figure>
15+
16+
## Configuration
17+
18+
- **Seqera config**: Reference to the seqera-config node containing API credentials and default workspace settings.
19+
- **Node name**: Optional custom name for the node in the editor.
20+
- **Poll frequency** (default **1 minute**): Interval between polls. Can be configured in seconds, minutes, hours, or days.
21+
- **Search**: Optional search filter for workflow names. Leave blank to include all workflows. Supports TypedInput (msg, flow, global, env, JSONata).
22+
- **Max results** (default **50**): Maximum number of workflow runs to fetch per poll. Supports TypedInput.
23+
- **Workspace ID**: Override the workspace ID from the Config node. Supports TypedInput.
24+
25+
## Outputs (two)
26+
27+
The node has two outputs that fire at different times:
28+
29+
1. **All results** – Emitted once per poll with the full, filtered list of workflows.
30+
2. **New workflows** – Emitted once **per new workflow** detected since the last poll. If 3 new workflows are found, this output fires 3 separate times.
31+
32+
### Output 1: All results
33+
34+
Emitted on every poll cycle with a single message:
35+
36+
- `msg.payload.workflows` – Array of workflow objects from the API (full workflow details).
37+
- `msg.payload.nextPoll` – ISO timestamp of the next scheduled poll.
38+
- `msg.workflowIds` – Convenience array of workflow ID strings.
39+
40+
### Output 2: New workflows
41+
42+
Emitted once per new workflow detected. Each message contains:
43+
44+
- `msg.payload.workflow` – Single workflow object from the API.
45+
- `msg.workflowId` – The workflow ID string.
46+
47+
!!! tip
48+
49+
Connect output 2 to the [monitor workflow](monitor_workflow.md) node and it will start monitoring the new workflow run without further configuration.
50+
51+
## How new workflows are detected
52+
53+
The node tracks seen workflow IDs in its context storage. On each poll:
54+
55+
1. Fetch the current list of workflows from Seqera Platform
56+
2. Compare workflow IDs against the list from the previous poll
57+
3. For each new workflow ID, emit a separate message on output 2
58+
4. Update the stored list for the next comparison
59+
60+
The comparison is based on the unique `workflow.id` field.
61+
62+
!!! info
63+
64+
The very first poll after the node is created sees everything as new and is handled as a special case. It does not output new results to avoid flooding downstream nodes on initialization.
65+
66+
## Required permissions
67+
68+
Minimum required role: **View**
69+
70+
See the [configuration documentation](configuration.md#required-token-permissions) for a full table of required permissions for all nodes.
71+
72+
## Example usage
73+
74+
### Monitor specific pipelines
75+
76+
1. Add a **poll-workflows** node to your flow
77+
2. Set **search** to filter by pipeline name (e.g., `nf-core/rnaseq`)
78+
3. Connect output 2 to a **workflow-monitor** node to track progress
79+
4. Chain additional automation based on workflow status
80+
81+
### Send Slack notifications for new runs
82+
83+
1. Add a **poll-workflows** node
84+
2. Connect output 2 to a function node that formats a Slack message:
85+
```javascript
86+
msg.payload = {
87+
text: `New workflow launched: ${msg.payload.workflow.workflow.runName} (${msg.workflowId})`,
88+
};
89+
return msg;
90+
```
91+
3. Connect to a Slack webhook node
92+
4. Deploy
93+
94+
### Comparison with Monitor Workflow node
95+
96+
- **Poll Workflows**: Discovers _new_ workflows automatically by polling the workspace
97+
- **Monitor Workflow**: Tracks the status of a _specific_ workflow you already know about
98+
99+
Use Poll Workflows when you want to react to workflows launched by others. Use Monitor Workflow when you want to track a workflow you just launched.
100+
101+
## See also
102+
103+
- [Monitor Workflow](monitor_workflow.md) – Track status of a specific workflow
104+
- [Launch Workflow](launch_workflow.md) – Launch a new workflow
105+
- [Poll Data Link Files](poll_files.md) – Monitor Data Links for new files

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ nav:
1515
- seqera_nodes/configuration.md
1616
- seqera_nodes/launch_workflow.md
1717
- seqera_nodes/monitor_workflow.md
18+
- seqera_nodes/poll_workflows.md
1819
- seqera_nodes/add_dataset.md
1920
- seqera_nodes/list_files.md
2021
- seqera_nodes/poll_files.md

nodes/datalink-poll.js

Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -31,40 +31,16 @@ module.exports = function (RED) {
3131
node.depthPropType = config.depthType;
3232
node.returnType = config.returnType || "files"; // files|folders|all
3333

34-
// Poll-specific property (legacy minutes value retained for backward compatibility)
35-
// This will be overridden by the new seconds-based duration parser below.
36-
37-
// Poll-specific property (seconds)
38-
const parseDurationToSeconds = (value) => {
39-
if (typeof value === "number") return value;
40-
if (typeof value !== "string") return NaN;
41-
const v = value.trim();
42-
let m;
43-
// DD-HH:MM:SS
44-
if ((m = v.match(/^(\d+)-(\d{1,2}):(\d{1,2}):(\d{1,2})$/))) {
45-
const [, dd, hh, mm, ss] = m.map(Number);
46-
return dd * 86400 + hh * 3600 + mm * 60 + ss;
47-
}
48-
// HH:MM:SS
49-
if ((m = v.match(/^(\d{1,2}):(\d{1,2}):(\d{1,2})$/))) {
50-
const [, hh, mm, ss] = m.map(Number);
51-
return hh * 3600 + mm * 60 + ss;
52-
}
53-
// MM:SS
54-
if ((m = v.match(/^(\d{1,2}):(\d{1,2})$/))) {
55-
const [, mm, ss] = m.map(Number);
56-
return mm * 60 + ss;
57-
}
58-
// SS
59-
if (/^\d+$/.test(v)) {
60-
return parseInt(v, 10);
61-
}
62-
return NaN;
34+
// Poll frequency configuration
35+
const unitMultipliers = {
36+
seconds: 1,
37+
minutes: 60,
38+
hours: 3600,
39+
days: 86400,
6340
};
64-
65-
// Poll-specific property (seconds)
66-
const pollSecs = parseDurationToSeconds(config.pollFrequency);
67-
node.pollFrequencySec = !pollSecs || Number.isNaN(pollSecs) ? 15 * 60 : pollSecs;
41+
const pollValue = parseInt(config.pollFrequency, 10) || 15;
42+
const pollUnits = config.pollUnits || "minutes";
43+
node.pollFrequencySec = pollValue * (unitMultipliers[pollUnits] || 60);
6844

6945
// Reference config node & defaults
7046
node.seqeraConfig = RED.nodes.getNode(config.seqera);

nodes/workflow-poll.html

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<script type="text/html" data-template-name="seqera-workflow-poll">
2+
<!-- Seqera config and node name -->
3+
<div class="form-row">
4+
<label for="node-input-seqera"><i class="icon-globe"></i> Seqera config</label>
5+
<input type="text" id="node-input-seqera" data-type="seqera-config" />
6+
</div>
7+
<div class="form-row">
8+
<label for="node-input-name"><i class="fa fa-tag"></i> Node Name</label>
9+
<input type="text" id="node-input-name" />
10+
</div>
11+
12+
<!-- Polling specific -->
13+
<div class="form-row">
14+
<label for="node-input-pollFrequency"><i class="fa fa-clock-o"></i> Poll frequency</label>
15+
<input type="text" id="node-input-pollFrequency" style="text-align:end; width:50px !important" />
16+
<select id="node-input-pollUnits" style="width:120px !important">
17+
<option value="seconds">Seconds</option>
18+
<option value="minutes">Minutes</option>
19+
<option value="hours">Hours</option>
20+
<option value="days">Days</option>
21+
</select>
22+
</div>
23+
24+
<!-- Workflow filter parameters -->
25+
<div class="form-row">
26+
<label for="node-input-search"><i class="fa fa-search"></i> Search</label>
27+
<input type="text" id="node-input-search" />
28+
</div>
29+
<div class="form-row">
30+
<label for="node-input-maxResults"><i class="fa fa-sort-numeric-asc"></i> Max results</label>
31+
<input type="text" id="node-input-maxResults" />
32+
</div>
33+
<div class="form-row">
34+
<label for="node-input-workspaceId"><i class="icon-tasks"></i> Workspace ID</label>
35+
<input type="text" id="node-input-workspaceId" />
36+
</div>
37+
</script>
38+
39+
<!-- prettier-ignore -->
40+
<script type="text/markdown" data-help-name="seqera-workflow-poll">
41+
Polls Seqera Platform for workflow runs at a fixed interval and emits messages when new workflows are detected.
42+
43+
### Inputs
44+
45+
This node has no inputs it starts polling automatically when the flow is deployed.
46+
47+
: pollFrequency (number) : Poll frequency (default `1 minute`). Can be configured in seconds, minutes, hours, or days.
48+
: search (string) : Optional search filter for workflow names. Leave blank to include all workflows.
49+
: maxResults (number) : Maximum number of workflow runs to fetch per poll (default 50).
50+
: workspaceId (string) : Override the workspace ID from the config node.
51+
52+
All input properties support msg._, flow._, global.\*, env, or JSONata expressions via the **typedInput**.
53+
54+
### Outputs
55+
56+
The node has two outputs that fire at different times:
57+
58+
1. **All results** Emitted every poll with the full list of workflows.
59+
2. **New workflows** Emitted once per new workflow detected since the previous poll.
60+
61+
**Output 1 (All results):**
62+
63+
: payload.workflows (array) : Full list of workflow objects from the API.
64+
: payload.nextPoll (string) : ISO timestamp of the next scheduled poll.
65+
: workflowIds (array) : Convenience array of workflow IDs (strings).
66+
67+
**Output 2 (New workflows - one message per workflow):**
68+
69+
: payload.workflow (object) : Single workflow object from the API.
70+
: workflowId (string) : The workflow ID.
71+
72+
If 3 new workflows are detected, output 2 will fire 3 separate times with 3 individual messages.
73+
74+
### Details
75+
76+
The node tracks seen workflow IDs in its context storage. On each poll:
77+
78+
1. Fetch the current list of workflows from Seqera Platform
79+
2. Compare against the list from the previous poll
80+
3. For each new workflow ID, emit a separate message on output 2
81+
4. Update the stored list for the next comparison
82+
83+
The comparison is based on the workflow ID. The very first poll after the node is created does not emit new results (it initializes the tracking state).
84+
85+
### Required permissions
86+
87+
Minimum required role: **View**
88+
89+
See the [configuration documentation](configuration.md#required-token-permissions) for a full table of required permissions for all nodes.
90+
91+
### Example usage
92+
93+
See the [Poll Workflows documentation](https://seqeralabs.github.io/node-red-seqera/seqera_nodes/poll_workflows/) for detailed examples.
94+
95+
### Notes
96+
97+
- The first poll after deployment/restart does **not** emit to the "New workflows" output
98+
- The tracking is reset on each Node-RED restart or flow redeployment
99+
- Very frequent polling (< 30 seconds) may impact API rate limits
100+
- Custom message properties are preserved in outputs (e.g., `msg._context`)
101+
</script>
102+
103+
<script type="text/javascript">
104+
RED.nodes.registerType("seqera-workflow-poll", {
105+
category: "seqera",
106+
color: "#A9A1C6",
107+
inputs: 0,
108+
outputs: 2,
109+
icon: "icons/pipeline.svg",
110+
align: "left",
111+
paletteLabel: "Poll wfs",
112+
label: function () {
113+
return this.name || "Poll workflows";
114+
},
115+
outputLabels: ["All workflows", "New workflows"],
116+
defaults: {
117+
name: { value: "" },
118+
seqera: { value: "", type: "seqera-config" },
119+
search: { value: "" },
120+
searchType: { value: "str" },
121+
maxResults: { value: "50" },
122+
maxResultsType: { value: "num" },
123+
workspaceId: { value: "" },
124+
workspaceIdType: { value: "str" },
125+
pollFrequency: { value: "1" },
126+
pollUnits: { value: "minutes" },
127+
},
128+
oneditprepare: function () {
129+
function ti(id, val, type, def = "str") {
130+
const types = ["str", "msg", "flow", "global", "env", "jsonata"];
131+
if (def === "num") types.splice(0, 1, "num");
132+
$(id).typedInput({ default: def, types });
133+
$(id).typedInput("value", val);
134+
$(id).typedInput("type", type);
135+
}
136+
137+
ti("#node-input-search", this.search || "", this.searchType || "str");
138+
ti("#node-input-maxResults", this.maxResults || "50", this.maxResultsType || "num", "num");
139+
ti("#node-input-workspaceId", this.workspaceId || "", this.workspaceIdType || "str");
140+
141+
// Poll frequency – simple number input with units
142+
$("#node-input-pollFrequency").val(this.pollFrequency || "1");
143+
$("#node-input-pollUnits").val(this.pollUnits || "minutes");
144+
},
145+
oneditsave: function () {
146+
function save(id, prop, propType) {
147+
this[prop] = $(id).typedInput("value");
148+
this[propType] = $(id).typedInput("type");
149+
}
150+
save.call(this, "#node-input-search", "search", "searchType");
151+
save.call(this, "#node-input-maxResults", "maxResults", "maxResultsType");
152+
save.call(this, "#node-input-workspaceId", "workspaceId", "workspaceIdType");
153+
154+
// Save poll frequency and units
155+
this.pollFrequency = $("#node-input-pollFrequency").val();
156+
this.pollUnits = $("#node-input-pollUnits").val();
157+
},
158+
});
159+
</script>

0 commit comments

Comments
 (0)