diff --git a/client/src/components/Workflow/Editor/Index.vue b/client/src/components/Workflow/Editor/Index.vue index 6025c523050c..71983b2421d0 100644 --- a/client/src/components/Workflow/Editor/Index.vue +++ b/client/src/components/Workflow/Editor/Index.vue @@ -355,6 +355,15 @@ export default { } const tags = ref([]); + + watch( + () => props.workflowTags, + (newTags) => { + tags.value = [...newTags]; + }, + { immediate: true } + ); + const setTagsHandler = new SetValueActionHandler( undoRedoStore, (value) => (tags.value = structuredClone(value)), @@ -792,12 +801,9 @@ export default { this.report.markdown = markdown; }, onRun() { - const runUrl = `/workflows/run?id=${this.id}${ - this.version !== undefined ? `&version=${this.version}` : "" - }`; - this.onNavigate(runUrl); + this.onNavigate(`/workflows/run?id=${this.id}`, false, false, true); }, - async onNavigate(url, forceSave = false, ignoreChanges = false) { + async onNavigate(url, forceSave = false, ignoreChanges = false, appendVersion = false) { if (this.isNewTempWorkflow) { await this.onCreate(); } else if (this.hasChanges && !forceSave && !ignoreChanges) { @@ -810,6 +816,9 @@ export default { await this.onSave(); } + if (appendVersion && this.version !== undefined) { + url += `&version=${this.version}`; + } this.hasChanges = false; await nextTick(); this.$router.push(url); diff --git a/client/src/components/Workflow/Editor/SaveChangesModal.vue b/client/src/components/Workflow/Editor/SaveChangesModal.vue index 9a2b5bbda2d0..921fa76a4b9e 100644 --- a/client/src/components/Workflow/Editor/SaveChangesModal.vue +++ b/client/src/components/Workflow/Editor/SaveChangesModal.vue @@ -24,7 +24,7 @@ const busy = ref(false); const emit = defineEmits<{ /** Proceed with or without saving the changes */ - (e: "on-proceed", url: string, forceSave: boolean, ignoreChanges: boolean): void; + (e: "on-proceed", url: string, forceSave: boolean, ignoreChanges: boolean, appendVersion: boolean): void; /** Update the nav URL prop */ (e: "update:nav-url", url: string): void; /** Update the show modal boolean prop */ @@ -49,13 +49,13 @@ function closeModal() { function dontSave() { busy.value = true; - emit("on-proceed", props.navUrl, false, true); + emit("on-proceed", props.navUrl, false, true, true); } function saveChanges() { busy.value = true; closeModal(); - emit("on-proceed", props.navUrl, true, false); + emit("on-proceed", props.navUrl, true, false, true); } diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationStep.vue b/client/src/components/WorkflowInvocationState/WorkflowInvocationStep.vue index 4678418e6f5d..243c44d428d1 100644 --- a/client/src/components/WorkflowInvocationState/WorkflowInvocationStep.vue +++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationStep.vue @@ -74,7 +74,7 @@ + :parameters="[getParamInput(stepDetails)]" /> ({ invocationId: route.params.invocationId, isFullPage: true, - fromPanel: route.query.from_panel, + fromPanel: Boolean(route.query.from_panel), }), }, { diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 07a2eadd6f40..d2d9b0fcfba2 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -8922,7 +8922,7 @@ def to_dict(self, view="collection", value_mapper=None, step_details=False, lega for input_step_parameter in self.input_step_parameters: label = input_step_parameter.workflow_step.label if not label: - continue + label = f"{input_step_parameter.workflow_step.order_index + 1}: Unnamed parameter" input_parameters[label] = { "parameter_value": input_step_parameter.parameter_value, "label": label, diff --git a/lib/galaxy/model/store/discover.py b/lib/galaxy/model/store/discover.py index 212515d230fc..2fb7f386be7e 100644 --- a/lib/galaxy/model/store/discover.py +++ b/lib/galaxy/model/store/discover.py @@ -38,6 +38,10 @@ from galaxy.util.hash_util import HASH_NAME_MAP if TYPE_CHECKING: + from galaxy.job_execution.output_collect import ( + DatasetCollector, + ToolMetadataDatasetCollector, + ) from galaxy.model.store import ModelExportStore log = logging.getLogger(__name__) @@ -50,7 +54,7 @@ class MaxDiscoveredFilesExceededError(ValueError): pass -CollectorT = Any # TODO: setup an interface for these file collectors data classes. +CollectorT = Union["DatasetCollector", "ToolMetadataDatasetCollector"] class ModelPersistenceContext(metaclass=abc.ABCMeta): @@ -1058,19 +1062,21 @@ def name(self): return self.as_dict.get("name") @property - def dbkey(self): - return self.as_dict.get("dbkey", getattr(self.collector, "default_dbkey", "?")) + def dbkey(self) -> str: + return self.as_dict.get("dbkey", self.collector and self.collector.default_dbkey or "?") @property - def ext(self): - return self.as_dict.get("ext", getattr(self.collector, "default_ext", "data")) + def ext(self) -> str: + return self.as_dict.get("ext", self.collector and self.collector.default_ext or "data") @property - def visible(self): + def visible(self) -> bool: try: return self.as_dict["visible"].lower() == "visible" except KeyError: - return getattr(self.collector, "default_visible", True) + if self.collector and self.collector.default_visible is not None: + return self.collector.default_visible + return True @property def link_data(self): diff --git a/lib/galaxy/tool_util/parser/output_collection_def.py b/lib/galaxy/tool_util/parser/output_collection_def.py index 1e80ba1ea950..baf61c520cdb 100644 --- a/lib/galaxy/tool_util/parser/output_collection_def.py +++ b/lib/galaxy/tool_util/parser/output_collection_def.py @@ -34,7 +34,13 @@ def dataset_collector_descriptions_from_elem(elem, legacy=True): if num_discover_dataset_blocks == 0 and legacy: collectors = [DEFAULT_DATASET_COLLECTOR_DESCRIPTION] else: - collectors = [dataset_collection_description(**e.attrib) for e in primary_dataset_elems] + default_format = elem.attrib.get("format") + collectors = [] + for e in primary_dataset_elems: + description_attributes = e.attrib + if default_format and "format" not in description_attributes and "ext" not in description_attributes: + description_attributes["format"] = default_format + collectors.append(dataset_collection_description(**description_attributes)) return _validate_collectors(collectors) diff --git a/lib/galaxy/tool_util/xsd/galaxy.xsd b/lib/galaxy/tool_util/xsd/galaxy.xsd index 39d07f0cc8b2..73558250251f 100644 --- a/lib/galaxy/tool_util/xsd/galaxy.xsd +++ b/lib/galaxy/tool_util/xsd/galaxy.xsd @@ -5641,11 +5641,13 @@ The default is ``galaxy.json``. - The short name for the output datatype. -The valid values for format can be found in + +(e.g. ``format="pdf"`` or ``format="fastqsanger"``). For collections this is the default +format for all included elements. Note that the format specified here is ignored for +discovered data sets on Galaxy versions prior to 24.0 and should be specified using the ```` tag set. + ]]> diff --git a/test/functional/tools/discover_default_ext.xml b/test/functional/tools/discover_default_ext.xml new file mode 100644 index 000000000000..7f7c7d83cdd8 --- /dev/null +++ b/test/functional/tools/discover_default_ext.xml @@ -0,0 +1,52 @@ + + 1.txt; +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/sample_tool_conf.xml b/test/functional/tools/sample_tool_conf.xml index b58a2ab1f64f..45eee3cc16c9 100644 --- a/test/functional/tools/sample_tool_conf.xml +++ b/test/functional/tools/sample_tool_conf.xml @@ -208,6 +208,7 @@ + diff --git a/test/unit/app/tools/test_collect_primary_datasets.py b/test/unit/app/tools/test_collect_primary_datasets.py index 16f3c6aae13e..fb612853b4db 100644 --- a/test/unit/app/tools/test_collect_primary_datasets.py +++ b/test/unit/app/tools/test_collect_primary_datasets.py @@ -128,6 +128,20 @@ def test_collect_multiple_recurse_dict(self): created_hda_3 = datasets[DEFAULT_TOOL_OUTPUT]["test3"] assert_created_with_path(self.app.object_store, created_hda_3.dataset, path3) + def test_collect_collection_default_format(self): + self._replace_output_collectors( + """ + + """ + ) + self._setup_extra_file(subdir="subdir_for_name_discovery", filename="test1") + self._setup_extra_file(subdir="subdir_for_name_discovery", filename="test2") + + datasets = self._collect() + assert DEFAULT_TOOL_OUTPUT in datasets + for dataset in datasets[DEFAULT_TOOL_OUTPUT].values(): + assert dataset.ext == "abcdef" + def test_collect_sorted_reverse(self): self._replace_output_collectors( """ diff --git a/test/unit/app/tools/test_parameter_validation.py b/test/unit/app/tools/test_parameter_validation.py index 26a994335e9c..a7798927eb65 100644 --- a/test/unit/app/tools/test_parameter_validation.py +++ b/test/unit/app/tools/test_parameter_validation.py @@ -219,11 +219,13 @@ def test_InRangeValidator(self): ) p.validate(10) with self.assertRaisesRegex( - ValueError, r"Parameter blah: Value \('15'\) must not fulfill float\('10'\) < float\(value\) <= float\('20'\)" + ValueError, + r"Parameter blah: Value \('15'\) must not fulfill float\('10'\) < float\(value\) <= float\('20'\)", ): p.validate(15) with self.assertRaisesRegex( - ValueError, r"Parameter blah: Value \('20'\) must not fulfill float\('10'\) < float\(value\) <= float\('20'\)" + ValueError, + r"Parameter blah: Value \('20'\) must not fulfill float\('10'\) < float\(value\) <= float\('20'\)", ): p.validate(20) p.validate(21)