diff --git a/client/galaxy/scripts/mvc/form/form-parameters.js b/client/galaxy/scripts/mvc/form/form-parameters.js index bda898a9322..5c7a9abd89e 100644 --- a/client/galaxy/scripts/mvc/form/form-parameters.js +++ b/client/galaxy/scripts/mvc/form/form-parameters.js @@ -84,23 +84,10 @@ export default Backbone.Model.extend({ } // identify display type - var SelectClass = Ui.Select; - switch (input_def.display) { - case "checkboxes": - SelectClass = Ui.Checkbox; - break; - case "radio": - SelectClass = Ui.Radio; - break; - case "radiobutton": - SelectClass = Ui.RadioButton; - break; - } - - // create select field - return new SelectClass.View({ + return new Ui.TextSelect({ id: `field-${input_def.id}`, data: data, + display: input_def.display, error_text: input_def.error_text || "No options available", readonly: input_def.readonly, multiple: input_def.multiple, diff --git a/client/galaxy/scripts/mvc/ui/ui-misc.js b/client/galaxy/scripts/mvc/ui/ui-misc.js index c5ee45392b8..4ed8892847b 100644 --- a/client/galaxy/scripts/mvc/ui/ui-misc.js +++ b/client/galaxy/scripts/mvc/ui/ui-misc.js @@ -184,6 +184,35 @@ export var Hidden = Backbone.View.extend({ } }); +/** Creates an input element which switches between select and text field */ +export var TextSelect = Backbone.View.extend({ + initialize: function(options) { + this.text = new Input(options); + var classes = { + "checkboxes": Checkbox, + "radio": Radio, + "radiobutton": RadioButton + } + var SelectClass = classes[options.display] || Select; + this.select = new SelectClass.View(options); + this.setElement($("
").append(this.select.$el) + .append(this.text.$el)); + this.update(options.data); + }, + value: function(new_val) { + var element = this.textmode ? this.text : this.select; + return element.value(new_val); + }, + update: function(options) { + var v = this.value(); + this.textmode = !$.isArray(options) || options.length === 0; + this.text.$el[this.textmode ? "show" : "hide"](); + this.select.$el[this.textmode ? "hide" : "show"](); + this.select.update(options); + this.value(v); + } +}); + /** Creates a upload element input field */ export var Upload = Backbone.View.extend({ initialize: function(options) { @@ -266,6 +295,7 @@ export default { Checkbox: Options.Checkbox, Radio: Options.Radio, Select: Select, + TextSelect: TextSelect, Hidden: Hidden, Slider: Slider, Drilldown: Drilldown diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py index 7265d48a344..1e727537855 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -43,8 +43,6 @@ workflow_building_modes = Bunch(DISABLED=False, ENABLED=True, USE_HISTORY=1) WORKFLOW_PARAMETER_REGULAR_EXPRESSION = re.compile('''\$\{.+?\}''') -MAX_DEFAULT_COLUMNS = 999 - def contains_workflow_parameter(value, search=False): if not isinstance(value, string_types): @@ -57,7 +55,17 @@ def contains_workflow_parameter(value, search=False): def is_runtime_value(value): - return isinstance(value, RuntimeValue) or (isinstance(value, dict) and value.get('__class__') == 'RuntimeValue') + return isinstance(value, RuntimeValue) or (isinstance(value, dict) + and value.get("__class__") == "RuntimeValue") + + +def has_runtime_datasets(trans, value): + for v in util.listify(value): + if isinstance(v, trans.app.model.HistoryDatasetAssociation) and \ + ((hasattr(v, "state") and v.state != galaxy.model.Dataset.states.OK) or + hasattr(v, "implicit_conversion")): + return True + return False def parse_dynamic_options(param, input_source): @@ -848,10 +856,12 @@ class SelectToolParameter(ToolParameter): legal_values = self.get_legal_values(trans, other_values) workflow_building_mode = trans.workflow_building_mode for context_value in other_values.values(): - if is_runtime_value(context_value): + if is_runtime_value(context_value) or has_runtime_datasets(trans, context_value): workflow_building_mode = workflow_building_modes.ENABLED break - if len(list(legal_values)) == 0 and workflow_building_mode: + if not legal_values: + if not workflow_building_mode: + raise ValueError("Parameter %s requires a value, but has no legal values defined." % self.name) if self.multiple: # While it is generally allowed that a select value can be '', # we do not allow this to be the case in a dynamically @@ -866,10 +876,10 @@ class SelectToolParameter(ToolParameter): # use \r\n to separate lines. value = value.split() return value - if (not legal_values or value is None) and self.optional: - return None - if not legal_values: - raise ValueError("Parameter %s requires a value, but has no legal values defined." % self.name) + elif value is None: + if self.optional: + return None + raise ValueError("An invalid option was selected for %s, please verify." % (self.name)) if isinstance(value, list): if not self.multiple: raise ValueError("Multiple values provided but parameter %s is not expecting multiple values." % self.name) @@ -914,7 +924,7 @@ class SelectToolParameter(ToolParameter): def get_initial_value(self, trans, other_values): options = list(self.get_options(trans, other_values)) - if len(options) == 0 and trans.workflow_building_mode: + if not options: return None value = [optval for _, optval, selected in options if selected] if len(value) == 0: @@ -1114,7 +1124,7 @@ class ColumnListParameter(SelectToolParameter): dataset (if found). """ # Get the value of the associated data reference (a dataset) - dataset = other_values.get(self.data_ref, None) + dataset = other_values.get(self.data_ref) # Check if a dataset is selected if not dataset: return [] @@ -1123,16 +1133,14 @@ class ColumnListParameter(SelectToolParameter): # Use representative dataset if a dataset collection is parsed if isinstance(dataset, trans.app.model.HistoryDatasetCollectionAssociation): dataset = dataset.to_hda_representative() - # Columns can only be identified if metadata is available - if not hasattr(dataset, 'metadata') or not hasattr(dataset.metadata, 'columns'): + # Columns can only be identified if the dataset is ready and metadata is available + if not hasattr(dataset, 'metadata') or \ + not hasattr(dataset.metadata, 'columns') or \ + not dataset.metadata.columns: return [] # Build up possible columns for this dataset this_column_list = [] - # Valid column-based datasets contain at least 1 column if that column has not been - # specified we prepopulate the selector assuming that the datasets is not ready yet. - if dataset.metadata.columns is None: - this_column_list = [str(i) for i in range(1, MAX_DEFAULT_COLUMNS + 1)] - elif self.numerical: + if self.numerical: # If numerical was requested, filter columns based on metadata for i, col in enumerate(dataset.metadata.column_types): if col == 'int' or col == 'float': @@ -1319,24 +1327,24 @@ class DrillDownSelectToolParameter(SelectToolParameter): def from_json(self, value, trans, other_values={}): legal_values = self.get_legal_values(trans, other_values) - if len(list(legal_values)) == 0 and trans.workflow_building_mode: + if not legal_values: + if not trans.workflow_building_mode: + raise ValueError("Parameter %s requires a value, but has no legal values defined." % self.name) if self.multiple: if value == '': # No option selected value = None else: value = value.split("\n") return value - if not value and not self.optional: + elif value is None: + if self.optional: + return None raise ValueError("An invalid option was selected for %s, please verify." % (self.name)) - if not value: - return None if not isinstance(value, list): value = [value] if len(value) > 1 and not self.multiple: raise ValueError("Multiple values provided but parameter %s is not expecting multiple values." % self.name) rval = [] - if not legal_values: - raise ValueError("Parameter %s requires a value, but has no legal values defined." % self.name) for val in value: if val not in legal_values: raise ValueError("An invalid option was selected for %s, %r, please verify" % (self.name, val)) @@ -1391,7 +1399,7 @@ class DrillDownSelectToolParameter(SelectToolParameter): recurse_options(initial_values, option['options']) # More working around dynamic options for workflow options = self.get_options(trans=trans, other_values=other_values) - if len(list(options)) == 0 and trans.workflow_building_mode: + if not options: return None initial_values = [] recurse_options(initial_values, options) @@ -1685,16 +1693,18 @@ class DataToolParameter(BaseDataToolParameter): rval = value else: rval = trans.sa_session.query(trans.app.model.HistoryDatasetAssociation).get(value) - if isinstance(rval, list): - values = rval - else: - values = [rval] + values = util.listify(rval) + dataset_matcher = DatasetMatcher(trans, self, None, other_values) for v in values: if v: if v.deleted: raise ValueError("The previously selected dataset has been deleted.") - if hasattr(v, "dataset") and v.dataset.state in [galaxy.model.Dataset.states.ERROR, galaxy.model.Dataset.states.DISCARDED]: + elif hasattr(v, "dataset") and v.dataset.state in [galaxy.model.Dataset.states.ERROR, galaxy.model.Dataset.states.DISCARDED]: raise ValueError("The previously selected dataset has entered an unusable state") + elif hasattr(v, "dataset"): + match = dataset_matcher.hda_match(v, check_security=False) + if match and match.implicit_conversion: + v.implicit_conversion = True if not self.multiple: if len(values) > 1: raise ValueError("More than one dataset supplied to single input dataset parameter.") diff --git a/test/api/test_tools.py b/test/api/test_tools.py index e1546a3f9d9..5d516122a58 100644 --- a/test/api/test_tools.py +++ b/test/api/test_tools.py @@ -466,7 +466,12 @@ class ToolsTestCase(api.ApiTestCase): 'col': "' ; echo 'moo", } response = self._run("column_param", history_id, inputs) - assert response.status_code != 200 + # This needs to either fail at submit time or at job prepare time, but we have + # to make sure the job doesn't run. + if response.status_code == 200: + job = response.json()["jobs"][0] + final_job_state = self.dataset_populator.wait_for_job(job["id"]) + assert final_job_state == "error" @skip_without_tool("collection_paired_test") def test_collection_parameter(self):