From 15ce210c2fa79d2221a3f3d8183ed825b1f86db7 Mon Sep 17 00:00:00 2001 From: guerler Date: Thu, 23 Apr 2026 20:55:35 +0300 Subject: [PATCH 1/4] Adjust tool form conversion utility --- client/src/components/Form/utilities.js | 21 ++++++++++++++------ client/src/components/Form/utilities.test.js | 12 +++++------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/client/src/components/Form/utilities.js b/client/src/components/Form/utilities.js index 97d4927a62a..abb75556260 100644 --- a/client/src/components/Form/utilities.js +++ b/client/src/components/Form/utilities.js @@ -194,8 +194,11 @@ function _convertValue(node, value) { return _convertDataValue(value, node.multiple); } if (node.type === "data_column") { - if (value === null || value === undefined || value === "") { - return value; + if (value === undefined) { + return undefined; + } + if (value === null || value === "") { + return null; } if (Array.isArray(value)) { return value.map((v) => (typeof v === "string" ? parseInt(v, 10) : v)); @@ -203,14 +206,20 @@ function _convertValue(node, value) { return typeof value === "string" ? parseInt(value, 10) : value; } if (node.type === "integer") { - if (value === null || value === undefined || value === "") { - return value; + if (value === undefined) { + return undefined; + } + if (value === null || value === "") { + return null; } return typeof value === "string" ? parseInt(value, 10) : value; } if (node.type === "float") { - if (value === null || value === undefined || value === "") { - return value; + if (value === undefined) { + return undefined; + } + if (value === null || value === "") { + return null; } return typeof value === "string" ? parseFloat(value) : value; } diff --git a/client/src/components/Form/utilities.test.js b/client/src/components/Form/utilities.test.js index 6e496a8a758..fb1fedb8b64 100644 --- a/client/src/components/Form/utilities.test.js +++ b/client/src/components/Form/utilities.test.js @@ -726,10 +726,10 @@ describe("form component utilities", () => { expect(buildNestedState(inputs, formData)).toEqual({ col: [1, 2, 5] }); }); - it("should preserve data_column null/empty/undefined values", () => { + it("should convert cleared data_column values to null but keep undefined as-is", () => { const inputs = [{ name: "col", type: "data_column" }]; expect(buildNestedState(inputs, { col: null })).toEqual({ col: null }); - expect(buildNestedState(inputs, { col: "" })).toEqual({ col: "" }); + expect(buildNestedState(inputs, { col: "" })).toEqual({ col: null }); expect(buildNestedState(inputs, { col: undefined })).toEqual({ col: undefined }); }); @@ -744,10 +744,10 @@ describe("form component utilities", () => { expect(buildNestedState(inputs, { num: "42" })).toEqual({ num: 42 }); }); - it("should preserve integer null/empty/undefined values", () => { + it("should convert cleared integer values to null but keep undefined as-is", () => { const inputs = [{ name: "num", type: "integer" }]; expect(buildNestedState(inputs, { num: null })).toEqual({ num: null }); - expect(buildNestedState(inputs, { num: "" })).toEqual({ num: "" }); + expect(buildNestedState(inputs, { num: "" })).toEqual({ num: null }); expect(buildNestedState(inputs, { num: undefined })).toEqual({ num: undefined }); }); @@ -761,10 +761,10 @@ describe("form component utilities", () => { expect(buildNestedState(inputs, { val: "3.14" })).toEqual({ val: 3.14 }); }); - it("should preserve float null/empty/undefined values", () => { + it("should convert cleared float values to null but keep undefined as-is", () => { const inputs = [{ name: "val", type: "float" }]; expect(buildNestedState(inputs, { val: null })).toEqual({ val: null }); - expect(buildNestedState(inputs, { val: "" })).toEqual({ val: "" }); + expect(buildNestedState(inputs, { val: "" })).toEqual({ val: null }); expect(buildNestedState(inputs, { val: undefined })).toEqual({ val: undefined }); }); From da8b7d645e5d60c1c67d31e7d70e867a40b7e3cd Mon Sep 17 00:00:00 2001 From: guerler Date: Thu, 23 Apr 2026 20:59:04 +0300 Subject: [PATCH 2/4] Handle None for optional data params in encode_callback --- lib/galaxy/tool_util/parameters/convert.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/galaxy/tool_util/parameters/convert.py b/lib/galaxy/tool_util/parameters/convert.py index c6bffded3af..3979c148630 100644 --- a/lib/galaxy/tool_util/parameters/convert.py +++ b/lib/galaxy/tool_util/parameters/convert.py @@ -481,12 +481,16 @@ def _encode_callback_for(encode_id: EncodeFunctionT) -> Callback: def encode_callback(parameter: ToolParameterT, value: Any): if isinstance(parameter, DataParameterModel): + if value is None: + return VISITOR_NO_REPLACEMENT if parameter.multiple and isinstance(value, list): return list(map(encode_element, value)) else: assert isinstance(value, dict), str(value) return encode_element(value) elif isinstance(parameter, DataCollectionParameterModel): + if value is None: + return VISITOR_NO_REPLACEMENT assert isinstance(value, dict), str(value) return encode_element(value) else: From c97d3dec333efd1f4f8f0203452634ae39c3e271 Mon Sep 17 00:00:00 2001 From: guerler Date: Thu, 23 Apr 2026 21:13:56 +0300 Subject: [PATCH 3/4] Fix tool name handling --- lib/galaxy/celery/tasks.py | 8 +++++++- lib/galaxy/schema/tasks.py | 1 + lib/galaxy/tools/__init__.py | 8 ++++++-- lib/galaxy/webapps/galaxy/services/jobs.py | 1 + 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/celery/tasks.py b/lib/galaxy/celery/tasks.py index 6725a5588af..b630fdce26a 100644 --- a/lib/galaxy/celery/tasks.py +++ b/lib/galaxy/celery/tasks.py @@ -92,9 +92,14 @@ def cached_create_tool_from_representation( raw_tool_source: str, tool_source_class: TOOL_SOURCE_CLASS, tool_dir: Optional[str] = None, + tool_id: Optional[str] = None, ): return create_tool_from_representation( - app=app, raw_tool_source=raw_tool_source, tool_dir=tool_dir, tool_source_class=tool_source_class + app=app, + raw_tool_source=raw_tool_source, + tool_dir=tool_dir, + tool_source_class=tool_source_class, + guid=tool_id, ) @@ -441,6 +446,7 @@ def queue_jobs(request: QueueJobs, app: MinimalManagerApp, job_submitter: JobSub raw_tool_source=raw_tool_source, tool_dir=request.tool_source.tool_dir, tool_source_class=tool_source_class, + tool_id=request.tool_source.tool_id, ) job_submitter.queue_jobs( diff --git a/lib/galaxy/schema/tasks.py b/lib/galaxy/schema/tasks.py index 6ee1c2d433e..55a46447ba4 100644 --- a/lib/galaxy/schema/tasks.py +++ b/lib/galaxy/schema/tasks.py @@ -182,6 +182,7 @@ class ToolSource(Model): raw_tool_source: str tool_dir: Optional[str] = None tool_source_class: TOOL_SOURCE_CLASS = "XmlToolSource" + tool_id: Optional[str] = None class QueueJobs(Model): diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index acbb0e1bcad..9d302636883 100644 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -469,10 +469,14 @@ def create_tool_from_source(app, tool_source: ToolSource, config_file: Optional[ def create_tool_from_representation( - app, raw_tool_source: str, tool_dir: Optional[StrPath] = None, tool_source_class="XmlToolSource" + app, + raw_tool_source: str, + tool_dir: Optional[StrPath] = None, + tool_source_class="XmlToolSource", + guid: Optional[str] = None, ) -> "Tool": tool_source = get_tool_source(tool_source_class=tool_source_class, raw_tool_source=raw_tool_source) - return create_tool_from_source(app, tool_source=tool_source, tool_dir=tool_dir) + return create_tool_from_source(app, tool_source=tool_source, tool_dir=tool_dir, guid=guid) class NullToolTagManager(AbstractToolTagManager): diff --git a/lib/galaxy/webapps/galaxy/services/jobs.py b/lib/galaxy/webapps/galaxy/services/jobs.py index 65bca15b884..fac6ea9ecf3 100644 --- a/lib/galaxy/webapps/galaxy/services/jobs.py +++ b/lib/galaxy/webapps/galaxy/services/jobs.py @@ -282,6 +282,7 @@ class JobsService(ServiceBase): raw_tool_source=tool_source_model.source, tool_dir=tool.tool_dir, tool_source_class=tool_source_model.source_class, + tool_id=tool.id, ) task_request = QueueJobs( user=trans.async_request_user, From 6fcc21ff053b5bec0ddc3164f76987acbf8c7d75 Mon Sep 17 00:00:00 2001 From: guerler Date: Thu, 23 Apr 2026 21:35:39 +0300 Subject: [PATCH 4/4] Add tests --- .../tools/test_tool_serialization_roundtrip.py | 17 +++++++++++++++++ test/unit/tool_util/test_parameter_convert.py | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/test/unit/app/tools/test_tool_serialization_roundtrip.py b/test/unit/app/tools/test_tool_serialization_roundtrip.py index a706cbbc422..5f8c7ce67d9 100644 --- a/test/unit/app/tools/test_tool_serialization_roundtrip.py +++ b/test/unit/app/tools/test_tool_serialization_roundtrip.py @@ -36,6 +36,23 @@ def test_repopulate_after_serialization_yaml(): ) +def test_repopulate_applies_guid(): + tool = simple_constructs_tool() + raw_tool_source, tool_source_class = tool.to_raw_tool_source() + guid = "toolshed.example.com/repos/owner/repo/simple_constructs_y/1.0" + + app = mock_app_for_tool_support() + rebuilt = create_tool_from_representation( + app, + raw_tool_source, + tool.tool_dir, + tool_source_class, + guid=guid, + ) + assert rebuilt.id == guid + assert rebuilt.old_id == "simple_constructs_y" + + def simple_constructs_tool() -> Tool: tool_path = functional_test_tool_path("simple_constructs.yml") tool_source = functional_test_tool_source("simple_constructs_y") diff --git a/test/unit/tool_util/test_parameter_convert.py b/test/unit/tool_util/test_parameter_convert.py index 91084376540..b3b289df34c 100644 --- a/test/unit/tool_util/test_parameter_convert.py +++ b/test/unit/tool_util/test_parameter_convert.py @@ -121,6 +121,14 @@ def test_multi_data(): assert encoded_state.input_state["parameter"][1]["id"] == EXAMPLE_ID_2_ENCODED +def test_encode_optional_data_collection_none(): + tool_source = tool_source_for("parameters/gx_data_collection_optional") + bundle = input_models_for_tool_source(tool_source) + internal_state = RequestInternalToolState({"parameter": None}) + encoded_state = encode(internal_state, bundle, _fake_encode) + assert encoded_state.input_state["parameter"] is None + + def test_landing_encode_data(): tool_source = tool_source_for("parameters/gx_data") bundle = input_models_for_tool_source(tool_source)