diff --git a/lib/galaxy/managers/context.py b/lib/galaxy/managers/context.py
index 24c29567a80b..4d55fcffbadb 100644
--- a/lib/galaxy/managers/context.py
+++ b/lib/galaxy/managers/context.py
@@ -39,10 +39,13 @@
import string
from json import dumps
from typing import (
+ Any,
Callable,
cast,
+ Dict,
List,
Optional,
+ Tuple,
)
from sqlalchemy import select
@@ -204,6 +207,13 @@ class ProvidesUserContext(ProvidesAppContext):
galaxy_session: Optional[GalaxySession] = None
_tag_handler: Optional[GalaxyTagHandlerSession] = None
+ _short_term_cache: Dict[Tuple[str, ...], Any]
+
+ def set_cache_value(self, args: Tuple[str, ...], value: Any):
+ self._short_term_cache[args] = value
+
+ def get_cache_value(self, args: Tuple[str, ...], default: Any = None) -> Any:
+ return self._short_term_cache.get(args, default)
@property
def tag_handler(self):
diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py
index d1f50b945c28..1fae939dd5d1 100644
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -3670,6 +3670,17 @@ def element_is_valid(element: model.DatasetCollectionElement):
return False
+class FilterNullTool(FilterDatasetsTool):
+ tool_type = "filter_null"
+ require_dataset_ok = True
+
+ @staticmethod
+ def element_is_valid(element: model.DatasetCollectionElement):
+ element_object = element.element_object
+ assert isinstance(element_object, model.DatasetInstance)
+ return element_object.extension == "expression.json" and element_object.blurb == "skipped"
+
+
class FlattenTool(DatabaseOperationTool):
tool_type = "flatten_collection"
require_terminal_states = False
diff --git a/lib/galaxy/tools/parameters/dynamic_options.py b/lib/galaxy/tools/parameters/dynamic_options.py
index b8cd20dab37d..e354c93eabb5 100644
--- a/lib/galaxy/tools/parameters/dynamic_options.py
+++ b/lib/galaxy/tools/parameters/dynamic_options.py
@@ -286,14 +286,12 @@ def get_dependency_name(self):
return self.ref_name
def filter_options(self, options, trans, other_values):
- if trans is not None and trans.workflow_building_mode:
- return []
ref = other_values.get(self.ref_name, None)
- if ref is None:
+ if ref is None or is_runtime_value(ref):
ref = []
# - for HDCAs the list of contained HDAs is extracted
- # - single values are transformed in a single eleent list
+ # - single values are transformed in a single element list
# - remaining cases are already lists (select and data parameters with multiple=true)
if isinstance(ref, HistoryDatasetCollectionAssociation):
ref = ref.to_hda_representative(multiple=True)
@@ -835,6 +833,9 @@ def get_field_by_name_for_value(self, field_name, value, trans, other_values):
return rval
def get_options(self, trans, other_values):
+
+ rval = []
+
def to_triple(values):
if len(values) == 2:
return [str(values[0]), str(values[1]), False]
@@ -877,8 +878,7 @@ def to_triple(values):
data = []
# We only support the very specific ["name", "value", "selected"] format for now.
- return [to_triple(d) for d in data]
- rval = []
+ rval = [to_triple(d) for d in data]
if (
self.file_fields is not None
or self.tool_data_table is not None
diff --git a/lib/galaxy/webapps/base/webapp.py b/lib/galaxy/webapps/base/webapp.py
index 822e5b7ec236..75a14f04e301 100644
--- a/lib/galaxy/webapps/base/webapp.py
+++ b/lib/galaxy/webapps/base/webapp.py
@@ -14,6 +14,7 @@
Any,
Dict,
Optional,
+ Tuple,
)
from urllib.parse import urlparse
@@ -325,6 +326,7 @@ def __init__(
self.galaxy_session = None
self.error_message = None
self.host = self.request.host
+ self._short_term_cache: Dict[Tuple[str, ...], Any] = {}
# set any cross origin resource sharing headers if configured to do so
self.set_cors_headers()
diff --git a/lib/galaxy/work/context.py b/lib/galaxy/work/context.py
index 8e4fc74afc21..ec1146adc102 100644
--- a/lib/galaxy/work/context.py
+++ b/lib/galaxy/work/context.py
@@ -49,12 +49,6 @@ def __init__(
self.workflow_building_mode = workflow_building_mode
self.galaxy_session = galaxy_session
- def set_cache_value(self, args: Tuple[str, ...], value: Any):
- self._short_term_cache[args] = value
-
- def get_cache_value(self, args: Tuple[str, ...], default: Any = None) -> Any:
- return self._short_term_cache.get(args, default)
-
@property
def app(self):
return self._app
diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py
index a5fadafeb28a..2cdef868d4ee 100644
--- a/lib/galaxy/workflow/modules.py
+++ b/lib/galaxy/workflow/modules.py
@@ -1414,7 +1414,7 @@ def restrict_options(self, step, connections: Iterable[WorkflowStepConnection],
def callback(input, prefixed_name, context, **kwargs):
if prefixed_name == connection.input_name and hasattr(input, "get_options"): # noqa: B023
- static_options.append(input.get_options(self.trans, {}))
+ static_options.append(input.get_options(self.trans, context))
visit_input_values(tool_inputs, module.state.inputs, callback)
elif isinstance(module, SubWorkflowModule):
diff --git a/lib/galaxy_test/api/test_workflow_build_module.py b/lib/galaxy_test/api/test_workflow_build_module.py
new file mode 100644
index 000000000000..546f6192e397
--- /dev/null
+++ b/lib/galaxy_test/api/test_workflow_build_module.py
@@ -0,0 +1,19 @@
+from galaxy_test.base.populators import (
+ skip_without_tool,
+ WorkflowPopulator,
+)
+from ._framework import ApiTestCase
+
+
+class TestBuildWorkflowModule(ApiTestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.workflow_populator = WorkflowPopulator(self.galaxy_interactor)
+
+ @skip_without_tool("select_from_url")
+ def test_build_module_filter_dynamic_select(self):
+ # Verify that filtering on parameters that depend on parameter and validators works
+ # fine in workflow building mode.
+ module = self.workflow_populator.build_module(step_type="tool", content_id="select_from_url")
+ assert not module["errors"], module["errors"]
diff --git a/lib/galaxy_test/base/populators.py b/lib/galaxy_test/base/populators.py
index 014880ebd1e6..bd5feaa01592 100644
--- a/lib/galaxy_test/base/populators.py
+++ b/lib/galaxy_test/base/populators.py
@@ -2298,6 +2298,12 @@ def import_tool(self, tool) -> Dict[str, Any]:
assert upload_response.status_code == 200, upload_response
return upload_response.json()
+ def build_module(self, step_type: str, content_id: Optional[str] = None, inputs: Optional[Dict[str, Any]] = None):
+ payload = {"inputs": inputs or {}, "type": step_type, "content_id": content_id}
+ response = self._post("workflows/build_module", data=payload, json=True)
+ assert response.status_code == 200, response
+ return response.json()
+
def _import_tool_response(self, tool) -> Response:
using_requirement("admin")
tool_str = json.dumps(tool, indent=4)
diff --git a/test/functional/tools/select_from_url.xml b/test/functional/tools/select_from_url.xml
index 60a712e74631..488055677e85 100644
--- a/test/functional/tools/select_from_url.xml
+++ b/test/functional/tools/select_from_url.xml
@@ -10,6 +10,13 @@ echo '$url_param_value_header_and_body' > '$param_value_header_and_body'
+
+
+
+
+
+
+