Skip to content

Commit baf8329

Browse files
authored
Merge pull request #27 from seqeralabs/poll-files-tweaks
Poll files node: Add deleted files output and configurable 'all results' output
2 parents 1e21281 + 4045b78 commit baf8329

File tree

8 files changed

+528
-53
lines changed

8 files changed

+528
-53
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## [1.5.0] - 2025-12-18
4+
5+
- 💥 Breaking change: Poll files node
6+
- Added `pollIntervalSeconds` field to output payload alongside `nextPoll`
7+
- "Every poll" output port is now hidden by default, with a checkbox option to enable it
8+
- Added new "Deleted results" output that fires when files are removed from the Data Link
9+
10+
**Warning**: The change in the available ports on this node could affect your workflows.
11+
Please update with care - make sure that the correct output ports are still connected!
12+
313
## [1.4.1] - 2025-11-16
414

515
- Bugfix: Don't use absolute paths for internal API calls for config setup

docs/img/poll_files_node_edit.png

-21 KB
Loading

docs/seqera_nodes/poll_files.md

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Poll files
22

3-
**Periodically list a Seqera Data Explorer Data Link and emit messages when new objects appear.**
3+
**Periodically list a Seqera Data Explorer Data Link and emit messages when objects are added or deleted.**
44

5-
This node automatically monitors a Data Link for _changes_, making it perfect for event-driven workflows that trigger when new files are uploaded.
5+
This node automatically monitors a Data Link for _changes_, making it perfect for event-driven workflows that trigger when files are uploaded or removed.
66

77
!!! note
88

@@ -15,52 +15,69 @@ This node automatically monitors a Data Link for _changes_, making it perfect fo
1515

1616
## Configuration
1717

18-
!!! info
18+
This node works much like the [list files node](list_files.md), but instead of triggering when recieving a message input, it polls repeatedly and outputs events when it detects a change.
19+
20+
!!! warning
21+
22+
File addition / deletion is only detected across poll events.
23+
This means that if you delete a file and create a new one with the same name
24+
_inbetween_ two poll events, the change **will not be detected**.
1925

20-
This node works much like the [list files node](list_files.md), but instead of triggering when recieving a message input, it polls repeatedly and outputs events when it detects a change.
26+
If this kind of activity is likely, make sure that you set the polling frequency
27+
to be fast enough that the node will check the file listing between events
28+
_(poll -> delete file -> poll -> add file -> poll)_.
2129

2230
- **Seqera config**: Reference to the seqera-config node containing API credentials and default workspace settings.
2331
- **Node name**: Optional custom name for the node in the editor.
2432
- **Data Link name** (required): Display name of the Data Link. Supports autocomplete.
33+
- **Return type** (default **files**): `files`, `folders` or `all`.
34+
- **Poll frequency** (default **15 min**): Interval between polls.
35+
- **Every poll / Show output port** (default **off**): When enabled, adds an extra output that emits all files on every poll.
2536
- **Base path**: Path within the Data Link to start from.
2637
- **Prefix**: Prefix filter applied to both files and folders.
2738
- **Pattern**: Regular-expression filter applied to files after the prefix filter.
28-
- **Return type** (default **files**): `files`, `folders` or `all`.
2939
- **Max results** (default **100**): Maximum number of objects to return per poll.
3040
- **Depth** (default **0**): Folder recursion depth.
31-
- **Poll frequency** (default **15 min**): Interval between polls.
3241
- **Workspace ID**: Override the workspace ID from the Config node.
3342

3443
All properties work the same as the [list files](list_files.md) node, plus automatic polling.
3544

36-
## Outputs (two)
45+
## Outputs
46+
47+
By default, the node has two outputs:
48+
49+
1. **New results** – Emitted only when one or more _new_ objects are detected since the last poll.
50+
2. **Deleted results** – Emitted only when one or more objects are _deleted_ since the last poll.
3751

38-
The node has two outputs that fire at different times:
52+
When **Every poll / Show output port** is enabled, the node has three outputs:
3953

4054
1. **All results** – Emitted every poll with the full, filtered list of files.
4155
2. **New results** – Emitted only when one or more _new_ objects are detected since the last poll.
56+
3. **Deleted results** – Emitted only when one or more objects are _deleted_ since the last poll.
4257

43-
Both messages include the same properties:
58+
All outputs include the same properties:
4459

4560
- `msg.payload.files` – Array of file objects from the API.
4661
- `msg.payload.resourceType`, `msg.payload.resourceRef`, `msg.payload.provider` – Data Link metadata.
4762
- `msg.files` – Convenience array of fully-qualified object names (strings).
4863
- `msg.payload.nextPoll` (only on **All results** output) – ISO timestamp of the next scheduled poll.
64+
- `msg.payload.pollIntervalSeconds` (only on **All results** output) – Poll interval duration in seconds.
4965

50-
## How new files are detected
66+
## How changes are detected
5167

5268
The node tracks seen files in its context storage. On each poll:
5369

5470
1. Fetch the current list of files from the Data Link
5571
2. Compare against the list from the previous poll
56-
3. If new files are found, emit them on output 2
57-
4. Update the stored list for the next comparison
72+
3. If new files are found, emit them on the "New results" output
73+
4. If files are missing (deleted), emit them on the "Deleted results" output
74+
5. Update the stored list for the next comparison
5875

59-
The comparison is based on the full file path. Files that are deleted and re-uploaded will be detected as "new".
76+
The comparison is based on the full file path. Files that are deleted and re-uploaded will be detected as "deleted" then "new" on subsequent polls.
6077

6178
!!! info
6279

63-
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.
80+
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 or deleted results.
6481

6582
## Required permissions
6683

@@ -74,7 +91,7 @@ See the [configuration documentation](configuration.md#required-token-permission
7491

7592
1. Add a **poll-files** node and configure the Data Link
7693
2. Set **pollFrequency** to your desired interval (e.g., `5:00` for 5 minutes)
77-
3. Connect output 2 (New results) to a **workflow-launch** node
94+
3. Connect the output (New results) to a **workflow-launch** node
7895
4. Configure the launch node to use the file paths from `msg.files`
7996
5. Deploy
8097

@@ -83,12 +100,12 @@ Now every time a new file appears in the Data Link, a workflow will automaticall
83100
### Trigger only on specific file types
84101

85102
1. Set **pattern**: `.*\.bam$` to only detect BAM files
86-
2. Connect output 2 to your processing logic
103+
2. Connect the output to your processing logic
87104
3. The node will only emit when new BAM files appear
88105

89106
## Notes
90107

91-
- The first poll after deployment/restart does **not** emit to the "New results" output (it initializes the tracking state)
108+
- The first poll after deployment/restart does **not** emit to the "New results" or "Deleted results" outputs (it initializes the tracking state)
92109
- The tracking is reset on each Node-RED restart or flow redeployment
93110
- Very frequent polling (< 30 seconds) may impact API rate limits
94111
- Custom message properties are preserved in outputs (e.g., `msg._context`)

examples/02 - Launch on file upload.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"returnType": "files",
3232
"x": 100,
3333
"y": 100,
34-
"wires": [[], ["2b7682159053d63c"]]
34+
"wires": [["2b7682159053d63c"], []]
3535
},
3636
{
3737
"id": "2b7682159053d63c",
@@ -127,5 +127,13 @@
127127
"x": 570,
128128
"y": 200,
129129
"wires": [[], [], []]
130+
},
131+
{
132+
"id": "2801ba6dc68b2335",
133+
"type": "global-config",
134+
"env": [],
135+
"modules": {
136+
"@seqera/node-red-seqera": "1.4.1"
137+
}
130138
}
131139
]

nodes/datalink-poll.html

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,20 @@
99
<input type="text" id="node-input-name" />
1010
</div>
1111

12+
<!-- Data link parameters (same as list node) -->
13+
<div class="form-row">
14+
<label for="node-input-dataLinkName"><i class="fa fa-link"></i> Data link</label>
15+
<input type="text" id="node-input-dataLinkName" />
16+
</div>
17+
<div class="form-row">
18+
<label for="node-input-returnType"><i class="fa fa-filter"></i> Return type</label>
19+
<select id="node-input-returnType">
20+
<option value="files">Files only</option>
21+
<option value="folders">Folders only</option>
22+
<option value="all">Everything</option>
23+
</select>
24+
</div>
25+
1226
<!-- Polling specific -->
1327
<div class="form-row">
1428
<label for="node-input-pollFrequency"><i class="fa fa-clock-o"></i> Poll frequency</label>
@@ -20,20 +34,23 @@
2034
<option value="days">Days</option>
2135
</select>
2236
</div>
23-
24-
<!-- Data link parameters (same as list node) -->
2537
<div class="form-row">
26-
<label for="node-input-dataLinkName"><i class="fa fa-link"></i> Data link name</label>
27-
<input type="text" id="node-input-dataLinkName" />
28-
</div>
29-
<div class="form-row">
30-
<label for="node-input-returnType"><i class="fa fa-filter"></i> Return type</label>
31-
<select id="node-input-returnType">
32-
<option value="files">Files only</option>
33-
<option value="folders">Folders only</option>
34-
<option value="all">Everything</option>
35-
</select>
38+
<label for="node-input-outputAllPolls">
39+
<i class="fa fa-refresh" aria-hidden="true"></i>
40+
Every poll
41+
</label>
42+
<label for="node-input-outputAllPolls" style="width:70%">
43+
<input
44+
type="checkbox"
45+
id="node-input-outputAllPolls"
46+
style="display:inline-block; width:22px; vertical-align:top;"
47+
autocomplete="off"
48+
/>
49+
Show output port
50+
</label>
3651
</div>
52+
53+
<!-- More specifics -->
3754
<div class="form-row">
3855
<label for="node-input-basePath"><i class="fa fa-folder-open-o"></i> Base path</label>
3956
<input type="text" id="node-input-basePath" />
@@ -66,30 +83,37 @@
6683

6784
### Inputs
6885

69-
: pollFrequency (string) : Poll frequency (default `15 minutes`). Can be configured in seconds, minutes, hours, or days.
7086
: dataLinkName (string) : The name of the data explorer link.
87+
: returnType (string) : Select whether to return files, folders or everything.
88+
: pollFrequency (string) : Poll frequency (default `15 minutes`). Can be configured in seconds, minutes, hours, or days.
89+
: outputAllPolls (boolean) : Show the "every poll" output port. Emits an output file listing on every poll event, irrespective of changes (disabled by default).
7190
: basePath (string) : Path within the data link to start browsing. Leave blank for the root.
7291
: prefix (string) : Optional prefix filter for results (applies to folders and files)
7392
: pattern (string) : Optional regex pattern filter for results (applies to files only)
74-
: returnType (string) : Select whether to return files, folders or everything.
7593
: maxResults (number) : Maximum number of results to return (default 100).
94+
: depth (number) : Folder recursion depth (default 0).
7695
: workspaceId (string) : Override the workspace ID from the config node.
7796

78-
All inputs support msg._, flow._, global.\*, env, or JSONata expressions via the **typedInput**.
97+
All inputs support `msg`, `flow`, `global`, `env`, or JSONata expressions via the **typedInput**.
7998

8099
### Outputs
81100

82-
The node has two outputs:
101+
The node has two or three outputs depending on configuration:
83102

84-
1. All results on every poll.
85-
2. New objects since the previous poll (nothing sent if no new objects).
103+
1. **All results** (optional, disabled by default) - Emitted every poll with the full list of files.
104+
2. **New results** - Emitted only when new objects are detected since the previous poll.
105+
3. **Deleted results** - Emitted only when objects are deleted since the previous poll.
86106

87-
Both outputs have the following properties:
107+
All outputs have the following properties:
88108

89-
: payload (array) : Fle information aggregated from the API (array of objects).
109+
: payload (array) : File information aggregated from the API (array of objects).
90110
: files (array) : File names (array of strings).
91111

92-
All typed-input fields are identical to the _List files_ node with the addition of **poll frequency**.
112+
The "All results" output also includes:
113+
114+
: payload.nextPoll (string) : ISO timestamp of the next scheduled poll.
115+
: payload.pollIntervalSeconds (number) : Poll interval duration in seconds.
116+
93117
</script>
94118

95119
<script type="text/javascript">
@@ -104,7 +128,14 @@
104128
label: function () {
105129
return this.name || "Poll files";
106130
},
107-
outputLabels: ["All objects", "Only new objects"],
131+
outputLabels: function (index) {
132+
if (this.outputAllPolls) {
133+
// 3 outputs: All, New, Deleted
134+
return ["All objects", "New objects", "Deleted objects"][index];
135+
}
136+
// 2 outputs: New, Deleted
137+
return ["New objects", "Deleted objects"][index];
138+
},
108139
defaults: {
109140
name: { value: "" },
110141
seqera: { value: "", type: "seqera-config" },
@@ -128,6 +159,7 @@
128159
pollFrequency: { value: "15" },
129160
pollUnits: { value: "minutes" },
130161
returnType: { value: "files" },
162+
outputAllPolls: { value: false },
131163
},
132164
oneditprepare: function () {
133165
function ti(id, val, type, def = "str") {
@@ -149,6 +181,9 @@
149181
$("#node-input-pollFrequency").val(this.pollFrequency || "15");
150182
$("#node-input-pollUnits").val(this.pollUnits || "minutes");
151183

184+
// Output all polls checkbox
185+
$("#node-input-outputAllPolls").prop("checked", this.outputAllPolls || false);
186+
152187
$("#node-input-returnType").val(this.returnType || "files");
153188

154189
// Add auto-complete for datalink name when type is "str"
@@ -260,6 +295,10 @@
260295
this.pollFrequency = $("#node-input-pollFrequency").val();
261296
this.pollUnits = $("#node-input-pollUnits").val();
262297

298+
// Save output all polls checkbox and update outputs count
299+
this.outputAllPolls = $("#node-input-outputAllPolls").prop("checked");
300+
this.outputs = this.outputAllPolls ? 3 : 2;
301+
263302
this.returnType = $("#node-input-returnType").val();
264303
},
265304
});

nodes/datalink-poll.js

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ module.exports = function (RED) {
3030
node.depthProp = config.depth;
3131
node.depthPropType = config.depthType;
3232
node.returnType = config.returnType || "files"; // files|folders|all
33+
node.outputAllPolls = config.outputAllPolls || false;
3334

3435
// Poll frequency configuration
3536
const unitMultipliers = {
@@ -74,11 +75,15 @@ module.exports = function (RED) {
7475
resourceRef: result.resourceRef,
7576
provider: result.provider,
7677
nextPoll: new Date(Date.now() + node.pollFrequencySec * 1000).toISOString(),
78+
pollIntervalSeconds: node.pollFrequencySec,
7779
},
7880
files: result.files.map((it) => `${result.resourceRef}/${it}`),
7981
};
8082

81-
// Second output: only new items since previous poll
83+
// Build set of current names for comparison
84+
const currentNamesSet = new Set(result.items.map((it) => it.name));
85+
86+
// New items since previous poll
8287
let msgNew = null;
8388
if (previousNamesSet) {
8489
const newItems = result.items.filter((it) => !previousNamesSet.has(it.name));
@@ -95,11 +100,36 @@ module.exports = function (RED) {
95100
}
96101
}
97102

103+
// Deleted items since previous poll
104+
let msgDeleted = null;
105+
if (previousNamesSet) {
106+
const deletedNames = [...previousNamesSet].filter((name) => !currentNamesSet.has(name));
107+
if (deletedNames.length) {
108+
msgDeleted = {
109+
payload: {
110+
files: deletedNames.map((name) => ({ name })),
111+
resourceType: result.resourceType,
112+
resourceRef: result.resourceRef,
113+
provider: result.provider,
114+
},
115+
files: deletedNames.map((name) => `${result.resourceRef}/${name}`),
116+
};
117+
}
118+
}
119+
98120
// Update cache
99-
previousNamesSet = new Set(result.items.map((it) => it.name));
121+
previousNamesSet = currentNamesSet;
100122

101123
node.status({ fill: "green", shape: "dot", text: `${result.items.length} items: ${formatDateTime()}` });
102-
node.send([msgAll, msgNew]);
124+
125+
// Send to outputs based on configuration
126+
if (node.outputAllPolls) {
127+
// Three outputs: [All results, New results, Deleted results]
128+
node.send([msgAll, msgNew, msgDeleted]);
129+
} else {
130+
// Two outputs: [New results, Deleted results]
131+
node.send([msgNew, msgDeleted]);
132+
}
103133
} catch (err) {
104134
node.error(`Seqera datalink poll failed: ${err.message}`);
105135
node.status({ fill: "red", shape: "dot", text: `error: ${formatDateTime()}` });

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@seqera/node-red-seqera",
3-
"version": "1.4.1",
3+
"version": "1.5.0",
44
"description": "Node-RED nodes for interacting with the Seqera Platform API",
55
"author": "Phil Ewels <phil.ewels@seqera.io>",
66
"license": "Apache-2.0",

0 commit comments

Comments
 (0)