@seqera/node-red-seqera
Version:
Node-RED nodes for interacting with the Seqera Platform API
321 lines (277 loc) • 13.9 kB
HTML
<script type="text/html" data-template-name="seqera-workflow-launch">
<div class="form-row">
<label for="node-input-seqera"><i class="icon-globe"></i> Seqera config</label>
<input type="text" id="node-input-seqera" data-type="seqera-config" />
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Node name</label>
<input type="text" id="node-input-name" />
</div>
<div class="form-row">
<label for="node-input-launchpadName">
<i class="fa fa-rocket"></i> <abbr title="Launchpad pipeline name">Launchpad</abbr>
</label>
<input type="text" id="node-input-launchpadName" />
</div>
<div class="form-row">
<label for="node-input-runName"><i class="fa fa-play-circle"></i> Run name</label>
<input type="text" id="node-input-runName" />
</div>
<div class="form-row">
<label for="node-input-resumeWorkflowId">
<i class="fa fa-history"></i>
<abbr title="Workflow ID from previous run to resume">Resume from</abbr>
</label>
<input type="text" id="node-input-resumeWorkflowId" />
</div>
<div class="form-row">
<label style="width: auto; margin-right: 10px;"><i class="fa fa-list"></i> Parameters</label>
<ol id="node-input-params-container"></ol>
</div>
<div class="form-row">
<label for="node-input-paramsKey"><i class="fa fa-code"></i> Params JSON</label>
<input type="text" id="node-input-paramsKey" />
</div>
<div class="form-row">
<label for="node-input-workspaceId">
<i class="icon-tasks"></i> <abbr title="Override Seqera config: workspace ID">Workspace ID</abbr>
</label>
<input type="text" id="node-input-workspaceId" />
</div>
<div class="form-row">
<label for="node-input-sourceWorkspaceId">
<i class="icon-tasks"></i>
<abbr title="Override Seqera config: Source workspace ID (if a shared workflow and different to workspaceId)">
Source WS ID
</abbr>
</label>
<input type="text" id="node-input-sourceWorkspaceId" />
</div>
</script>
<!-- prettier-ignore -->
<script type="text/markdown" data-help-name="seqera-workflow-launch">
Launches a workflow using the Seqera Platform API.
### Inputs
: Seqera config (seqera-config) : Reference to the seqera-config node containing API credentials and default workspace settings.
: Node name (string) : Optional custom name for the node in the editor.
: Launchpad (string) : The human-readable name of a pipeline in the launchpad to launch. Supports autocomplete.
: Run name (string) : Custom name for the workflow run (optional, defaults to auto-generated name).
: Resume from (string) : Workflow ID from a previous run to resume (optional). Can be extracted from workflow monitor output using `msg.workflowId`.
: Parameters (array) : Individual parameter key-value pairs added via the editable list. Each parameter can be configured with a name and a value of any type (string, number, boolean, JSON, etc.). These take highest precedence when merging.
: Params JSON (object) : A JSON object containing multiple parameters to merge with the launchpad's default parameters. Merged before individual parameters.
: Workspace ID (string) : Override the workspace ID from the Seqera config node (optional).
: Source WS ID (string) : The source workspace ID, required if launching a shared workflow from a different workspace than the target workspace (optional).
### Outputs
1. Standard output
: payload (object) : The launch response from the API.
: workflowId (string) : The ID of the launched workflow.
### Details
This node launches a workflow using either:
1. A launchpad name - the node will automatically look up the launchpad configuration
2. A full launch request body provided in `msg.body`
Workflow parameters can be provided in two ways:
1. **Params JSON** - A complete JSON object containing multiple parameters. This can be a JSON literal, or can be set to read from a message property (e.g., `msg.params`)
2. **Additional params** - Individual key-value pairs added via the editable list
When both are provided, they are merged together with individual params taking highest precedence. All parameters are then merged with the default parameters from the launchpad.
#### Resuming Failed Workflows
To resume a failed workflow, provide the **workflow ID** from the previous run (available in `msg.workflowId` from the workflow monitor output). The node will automatically:
1. Fetch the workflow's session ID and commit ID
2. Enable Nextflow resume functionality
3. Use the same pipeline revision as the original run
The resumed workflow must use the same work directory as the original run, so ensure your compute environment has access to the cached results.
### References
- [Seqera Platform API docs](https://docs.seqera.io/platform/latest/api) - information about launching workflows
- [Seqera Resume docs](https://docs.seqera.io/platform-cloud/launch/cache-resume#resume-a-workflow-run) - information about resume functionality
</script>
<script type="text/javascript">
RED.nodes.registerType("seqera-workflow-launch", {
category: "seqera",
color: "#A9A1C6",
inputs: 1,
outputs: 1,
icon: "icons/pipeline.svg",
align: "left",
paletteLabel: "Launch wf",
label: function () {
return this.name || "Launch workflow";
},
defaults: {
name: { value: "" },
seqera: { value: "", type: "seqera-config" },
launchpadName: { value: "", required: true },
launchpadNameType: { value: "str" },
paramsKey: { value: "{}" },
paramsKeyType: { value: "json" },
params: { value: [] },
runName: { value: "" },
runNameType: { value: "str" },
resumeWorkflowId: { value: "" },
resumeWorkflowIdType: { value: "str" },
workspaceId: { value: "" },
workspaceIdType: { value: "str" },
sourceWorkspaceId: { value: "" },
sourceWorkspaceIdType: { value: "str" },
},
oneditprepare: function () {
function ti(id, val, type) {
$(id).typedInput({ default: "str", types: ["str", "msg", "flow", "global", "env", "jsonata"] });
$(id).typedInput("value", val);
$(id).typedInput("type", type);
}
// Special case for params - default to json type
$("#node-input-paramsKey").typedInput({
default: "json",
types: ["json", "msg", "flow", "global", "str", "env", "jsonata"],
});
$("#node-input-paramsKey").typedInput("value", this.paramsKey || "{}");
$("#node-input-paramsKey").typedInput("type", this.paramsKeyType || "json");
ti("#node-input-launchpadName", this.launchpadName || "", this.launchpadNameType || "str");
ti("#node-input-runName", this.runName || "", this.runNameType || "str");
// Initialize resumeWorkflowId typedInput (supports msg for extracting from workflow monitor output)
$("#node-input-resumeWorkflowId").typedInput({
default: "str",
types: ["str", "msg", "flow", "global", "env", "jsonata"],
});
$("#node-input-resumeWorkflowId").typedInput("value", this.resumeWorkflowId || "");
$("#node-input-resumeWorkflowId").typedInput("type", this.resumeWorkflowIdType || "str");
ti("#node-input-workspaceId", this.workspaceId || "", this.workspaceIdType || "str");
ti("#node-input-sourceWorkspaceId", this.sourceWorkspaceId || "", this.sourceWorkspaceIdType || "str");
// Add auto-complete for launchpad name when type is "str"
const setupAutoComplete = () => {
const launchpadInput = $("#node-input-launchpadName");
const currentType = launchpadInput.typedInput("type");
// Ensure our custom CSS is added only once
if (!document.getElementById("seqera-autocomplete-style")) {
const style = document.createElement("style");
style.id = "seqera-autocomplete-style";
style.innerHTML = `.red-ui-autoComplete li a { display:block; padding:4px 8px; width:100%; box-sizing:border-box; }`;
document.head.appendChild(style);
}
// Get the actual visible input element from the typedInput widget
const actualInput = launchpadInput.parent().find(".red-ui-typedInput-input");
// Remove existing auto-complete if any
if (actualInput.data("autoCompleteEnabled")) {
actualInput.autoComplete("destroy");
actualInput.data("autoCompleteEnabled", false);
}
if (currentType === "str" && actualInput.length > 0) {
// Add a small delay to ensure typedInput is fully initialized
setTimeout(() => {
actualInput.autoComplete({
search: function (value, done) {
// Only search if we have some input
if (!value || value.length < 1) {
done([]);
return;
}
// Make API call to get pipeline names
const nodeId =
(RED.editor.edit_node && RED.editor.edit_node.id) ||
(RED.editor.edit_stack && RED.editor.edit_stack.length > 0 && RED.editor.edit_stack[0].id) ||
"temp-" + Date.now();
const seqeraConfigId = $("#node-input-seqera").val();
if (!seqeraConfigId) {
done([]);
return;
}
const params = new URLSearchParams({
search: value,
seqeraConfig: seqeraConfigId,
});
// Add workspace ID override if configured
const workspaceIdOverride = $("#node-input-workspaceId").typedInput("value");
const workspaceIdType = $("#node-input-workspaceId").typedInput("type");
if (workspaceIdType === "str" && workspaceIdOverride && workspaceIdOverride.trim()) {
params.append("workspaceId", workspaceIdOverride);
}
$.ajax({
url: `admin/seqera/pipelines/${nodeId}?${params.toString()}`,
method: "GET",
success: function (data) {
// data is already in the correct format: [{value, label}]
done(data || []);
},
error: function () {
done([]);
},
});
},
});
actualInput.data("autoCompleteEnabled", true);
}, 100);
}
};
// Set up auto-complete initially
setupAutoComplete();
// Re-setup auto-complete when type changes
$("#node-input-launchpadName").on("change", function (event, type) {
if (type) {
// Add delay to avoid conflicts with ongoing autocomplete operations
setTimeout(setupAutoComplete, 200);
}
});
// Re-setup auto-complete when Seqera config changes
$("#node-input-seqera").on("change", function () {
setupAutoComplete();
});
// Initialize params editableList
$("#node-input-params-container").editableList({
height: "auto",
addItem: function (container, index, param) {
const row = $("<div/>").css({ display: "flex", alignItems: "center", gap: "5px" }).appendTo(container);
// Parameter name input (string only)
const nameLabel = $('<span style="margin-left: 5px; min-width: 40px;">Name:</span>').appendTo(row);
const nameInput = $('<input type="text" style="width: 150px;">')
.val(param.name || "")
.appendTo(row);
// Parameter value input (typed)
const valueLabel = $('<span style="margin-left: 10px; min-width: 40px;">Value:</span>').appendTo(row);
const valueInput = $('<input type="text" style="flex: 1; min-width: 200px;">').appendTo(row);
valueInput.typedInput({
default: "str",
types: ["str", "msg", "flow", "global", "env", "jsonata", "json", "bool", "num"],
});
valueInput.typedInput("value", param.value || "");
valueInput.typedInput("type", param.valueType || "str");
// Store references in container data
container.data("nameInput", nameInput);
container.data("valueInput", valueInput);
},
removable: true,
sortable: true,
});
// Populate existing params
if (this.params && Array.isArray(this.params)) {
this.params.forEach((param) => {
$("#node-input-params-container").editableList("addItem", param);
});
}
},
oneditsave: function () {
function save(id, prop, propType) {
this[prop] = $(id).typedInput("value");
this[propType] = $(id).typedInput("type");
}
save.call(this, "#node-input-launchpadName", "launchpadName", "launchpadNameType");
save.call(this, "#node-input-paramsKey", "paramsKey", "paramsKeyType");
save.call(this, "#node-input-runName", "runName", "runNameType");
save.call(this, "#node-input-resumeWorkflowId", "resumeWorkflowId", "resumeWorkflowIdType");
save.call(this, "#node-input-workspaceId", "workspaceId", "workspaceIdType");
save.call(this, "#node-input-sourceWorkspaceId", "sourceWorkspaceId", "sourceWorkspaceIdType");
// Save params array from editableList
const params = [];
$("#node-input-params-container")
.editableList("items")
.each(function () {
const name = $(this).data("nameInput").val().trim();
const value = $(this).data("valueInput").typedInput("value");
const valueType = $(this).data("valueInput").typedInput("type");
if (name) {
// Only save if name is not empty
params.push({ name, value, valueType });
}
});
this.params = params;
},
});
</script>