From 5d2455f6a909a328d42d1062a488ccd37b576c6c Mon Sep 17 00:00:00 2001 From: Matthias Bernt Date: Mon, 1 Jun 2026 15:03:46 +0200 Subject: [PATCH 001/256] add test covering a collection input --- test/unit/app/tools/test_actions.py | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/unit/app/tools/test_actions.py b/test/unit/app/tools/test_actions.py index de6efdca29d..d6ec06b4412 100644 --- a/test/unit/app/tools/test_actions.py +++ b/test/unit/app/tools/test_actions.py @@ -51,6 +51,18 @@ TWO_OUTPUTS = """ """ +# Tool with a multiple="true" data parameter – used to test on_string handling for collections. +MULTIPLE_DATA_TOOL = """ + cat "$param1" < $out1 + + + + + + + +""" + def test_on_text_for_numeric_ids(): def assert_on_text_is(expected, hids): @@ -83,6 +95,27 @@ def test_on_text_for_dataset_and_collections(): class TestDefaultToolAction(TestCase, tools_support.UsesTools): + def test_on_text_multiple_true_collection(self): + # Create a collection with three datasets + hdca = model.HistoryDatasetCollectionAssociation() + hdca.id = 999 + hdca.hid = 55 + collection = model.DatasetCollection() + hdca.collection = collection + # add three datasets to the collection + hda1 = self.__add_dataset() + hda2 = self.__add_dataset() + hda3 = self.__add_dataset() + model.DatasetCollectionElement(collection=collection, element=hda1) + model.DatasetCollectionElement(collection=collection, element=hda2) + model.DatasetCollectionElement(collection=collection, element=hda3) + collection.collection_type = "list" + self.history.dataset_collections.append(hdca) + # incoming param with the collection + incoming = {"param1": hdca} + job, output = self._simple_execute(contents=MULTIPLE_DATA_TOOL, incoming=incoming) + assert output["out1"].name == f"Test Tool on collection {hdca.hid}" + def setUp(self): self.setup_app() history = model.History() From 4ed0118ebdb66c87f2c1aa80176bee90f30574cb Mon Sep 17 00:00:00 2001 From: Matthias Bernt Date: Mon, 1 Jun 2026 15:04:16 +0200 Subject: [PATCH 002/256] fix --- lib/galaxy/tools/actions/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/galaxy/tools/actions/__init__.py b/lib/galaxy/tools/actions/__init__.py index 50429437766..3088a070043 100644 --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -941,8 +941,12 @@ class DefaultToolAction(ToolAction): for input_name in inp_data: data = inp_data[input_name] + # Skip collection inputs and any suffixed dataset names derived from them (e.g., "datasets1", "datasets2") if input_name in inp_dataset_collections: continue + # Also skip if the name starts with a collection input key and the remainder is a numeric suffix + if any(input_name.startswith(key) and input_name[len(key) :].isdigit() for key in inp_dataset_collections): + continue if getattr(data, "hid", None): input_hids.append(data.hid) return on_text_for_dataset_and_collections(dataset_hids=input_hids, collection_hids=collection_hids) From 9960452f0f0b22dcb019e5623ecf7cfb3157c3f6 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Wed, 3 Jun 2026 09:44:19 +0200 Subject: [PATCH 003/256] Add utility for workflow file validation --- .../Workflow/Import/workflowValidation.ts | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 client/src/components/Workflow/Import/workflowValidation.ts diff --git a/client/src/components/Workflow/Import/workflowValidation.ts b/client/src/components/Workflow/Import/workflowValidation.ts new file mode 100644 index 00000000000..19b4f69b4ae --- /dev/null +++ b/client/src/components/Workflow/Import/workflowValidation.ts @@ -0,0 +1,69 @@ +/** Accepted workflow file extensions */ +export const WORKFLOW_FILE_EXTENSIONS = [".ga", ".gxwf.yml", ".gxwf.yaml", ".yml", ".yaml"] as const; + +/** Human-readable list of accepted formats for file inputs */ +export const WORKFLOW_FILE_ACCEPT = ".ga, .yml, .yaml"; + +/** + * Check if a filename has a valid workflow file extension. + * Supports .ga (Galaxy Archive), .yml/.yaml (Galaxy Workflow Format), and .gxwf.yml/.gxwf.yaml + */ +export function hasWorkflowFileExtension(fileName: string): boolean { + const lowerName = fileName.toLowerCase(); + return WORKFLOW_FILE_EXTENSIONS.some((ext) => lowerName.endsWith(ext)); +} + +/** + * Validate a workflow file by checking its extension and optionally its content. + * + * For .ga files (JSON format), checks for `"a_galaxy_workflow": "true"`. + * For .yml/.yaml files (YAML format), checks for `class: GalaxyWorkflow` or `class: GalaxyWorkflow`. + * + * Content validation reads only the first 1KB of the file for efficiency. + */ +export async function validateWorkflowFile( + file: File, + options: { checkContent?: boolean } = {}, +): Promise<{ valid: boolean; error?: string }> { + if (!hasWorkflowFileExtension(file.name)) { + return { valid: false, error: `Invalid file type. Accepted formats: ${WORKFLOW_FILE_ACCEPT}` }; + } + + if (options.checkContent) { + const lowerName = file.name.toLowerCase(); + + if (lowerName.endsWith(".ga")) { + const hasMarker = await readFileMarker(file, '"a_galaxy_workflow"'); + if (!hasMarker) { + return { + valid: false, + error: "File does not appear to be a Galaxy workflow (.ga). Missing workflow marker.", + }; + } + } else if (lowerName.endsWith(".yml") || lowerName.endsWith(".yaml")) { + const hasMarker = await readFileMarker(file, "class:"); + if (!hasMarker) { + return { + valid: false, + error: "File does not appear to be a Galaxy workflow (.yml/.yaml). Missing 'class:' marker.", + }; + } + } + } + + return { valid: true }; +} + +/** + * Read the first 1KB of a file and check if it contains the given marker string. + * This is efficient and avoids loading the entire file into memory. + */ +async function readFileMarker(file: File, marker: string): Promise { + try { + const slice = file.slice(0, 1024); + const text = await slice.text(); + return text.includes(marker); + } catch { + return false; + } +} From b2f79e1084f4eea9b56a33e1d619c382251380d4 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Wed, 3 Jun 2026 09:44:20 +0200 Subject: [PATCH 004/256] Improve file import usability with drag-and-drop and validation --- .../components/Workflow/Import/FromFile.vue | 171 +++++++++++++----- .../Workflow/Import/FromFileOrUrl.test.ts | 2 +- 2 files changed, 126 insertions(+), 47 deletions(-) diff --git a/client/src/components/Workflow/Import/FromFile.vue b/client/src/components/Workflow/Import/FromFile.vue index d5fe4ffa9a5..607f9e9b246 100644 --- a/client/src/components/Workflow/Import/FromFile.vue +++ b/client/src/components/Workflow/Import/FromFile.vue @@ -1,44 +1,40 @@ + + diff --git a/client/src/components/Workflow/Import/FromFileOrUrl.test.ts b/client/src/components/Workflow/Import/FromFileOrUrl.test.ts index f9c759b83be..2bfdaf7f468 100644 --- a/client/src/components/Workflow/Import/FromFileOrUrl.test.ts +++ b/client/src/components/Workflow/Import/FromFileOrUrl.test.ts @@ -72,6 +72,6 @@ describe("FromUrl", () => { describe("FromFile", () => { it("can mount the component", async () => { const wrapper = mount(FromFile as object, { localVue }); - expect(wrapper.find("#workflow-import-button").exists()).toBe(true); + expect(wrapper.find("form").exists()).toBe(true); }); }); From 16f4744e92e9f6f77c52cb0f39e64080f28e5de0 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Wed, 3 Jun 2026 09:44:20 +0200 Subject: [PATCH 005/256] Streamline import wizard navigation and UI --- .../components/Workflow/WorkflowImport.vue | 93 ++++++++++++++++--- 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/client/src/components/Workflow/WorkflowImport.vue b/client/src/components/Workflow/WorkflowImport.vue index 3c8d1239899..8d3e149be18 100644 --- a/client/src/components/Workflow/WorkflowImport.vue +++ b/client/src/components/Workflow/WorkflowImport.vue @@ -1,10 +1,14 @@ diff --git a/client/src/components/Workflow/Import/TrsIdImport.vue b/client/src/components/Workflow/Import/TrsIdImport.vue index f6fb5d3f00a..3c33eed25ed 100644 --- a/client/src/components/Workflow/Import/TrsIdImport.vue +++ b/client/src/components/Workflow/Import/TrsIdImport.vue @@ -134,8 +134,6 @@ defineExpose({ triggerImport });