3 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/6485ce612ca8/ Changeset: 6485ce612ca8 User: dannon Date: 2015-01-13 22:09:52+00:00 Summary: Remove echo of exception from message. It is still logged. The message as-is tells the user to contact an admin if they don't get an email. Affected #: 1 file diff -r a74b0112995cd2ae396d0916a6e845b7709f9002 -r 6485ce612ca842464d6290e6cae33bb16265e6e8 lib/galaxy/webapps/galaxy/controllers/user.py --- a/lib/galaxy/webapps/galaxy/controllers/user.py +++ b/lib/galaxy/webapps/galaxy/controllers/user.py @@ -1167,8 +1167,6 @@ trans.sa_session.flush() trans.log_event( "User reset password: %s" % email ) except Exception, e: - status = 'error' - message = 'Failed to reset password: %s' % str( e ) log.exception( 'Unable to reset password.' ) return trans.fill_template( '/user/reset_password.mako', message=message, https://bitbucket.org/galaxy/galaxy-central/commits/f73ddd949744/ Changeset: f73ddd949744 User: dannon Date: 2015-01-14 14:44:33+00:00 Summary: Admins should have to re-authenticate to change password via this mechanism as well. Affected #: 1 file diff -r 6485ce612ca842464d6290e6cae33bb16265e6e8 -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 templates/user/change_password.mako --- a/templates/user/change_password.mako +++ b/templates/user/change_password.mako @@ -10,7 +10,7 @@ <div class="toolFormTitle">Change Password</div> %if token: <input type="hidden" name="token" value="${token|h}"/> - %elif not is_admin: + %else: <div class="form-row"><label>Current password:</label><input type="password" name="current" value="" size="40"/> https://bitbucket.org/galaxy/galaxy-central/commits/292aa159ad5b/ Changeset: 292aa159ad5b User: dannon Date: 2015-01-14 14:44:56+00:00 Summary: Merge. Affected #: 34 files diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 .hgtags --- a/.hgtags +++ b/.hgtags @@ -8,16 +8,18 @@ 5e605ed6069fe4c5ca9875e95e91b2713499e8ca release_2014.02.10 9e53251b0b7e93b9563008a2b112f2e815a04bbc release_2014.04.14 7e257c7b10badb65772b1528cb61d58175a42e47 release_2014.06.02 -7a4d321c0e38fa263ea83d29a35a608c3181fcba latest_2014.06.02 -9661b9d5d5b330483ae3ad2236410e0efaa7c500 latest_2014.04.14 -6b0bd93038a843b1585155f0d63f0eea2459c70b latest_2013.01.13 -3e62060b14b9afc46f8e0ec02e1a4500d77db9e1 latest_2013.02.08 -425009b3ff4d8b67d2812253b221f3c4f4a8d1e3 latest_2013.04.01 -9713d86392ef985ffcdc39ff0c8ddf51a1f9ce47 latest_2013.06.03 -9ed84cd208e07e8985ec917cb025fcbbb09edcfb latest_2013.08.12 -81fbe25bd02edcd53065e8e4476dd1dfb5a72cf2 latest_2013.11.04 -2a756ca2cb1826db7796018e77d12e2dd7b67603 latest_2014.02.10 +4145417a6e1c13f82a3de365aadef0fb3ed7ab14 latest_2014.06.02 +8f9dcac033694e4cabcf5daae5cca1cfefbe967f latest_2014.04.14 +9c323aad4ffdd65a3deb06a4a36f6b2c5115a60f latest_2013.01.13 +b986c184be88947b5d1d90be7f36cfd2627dd938 latest_2013.02.08 +dec9431d66b837a208e2f060d90afd913c721227 latest_2013.04.01 +19e56e66b0b344c6e2afa4541f6988e4fdb9af29 latest_2013.06.03 +cee903b8b3eee9145627ee89742555dac581791e latest_2013.08.12 +7d5aa19a166cba9039e15f338a1e3fc924c43d3a latest_2013.11.04 +0c000cc2f9c05bf4c1c2bc3a10215014fd64e696 latest_2014.02.10 ca45b78adb4152fc6e7395514d46eba6b7d0b838 release_2014.08.11 -548ab24667d6206780237bd807f7d857a484c461 latest_2014.08.11 +8150024c0e6fc5aef3033cf8aaa574896f6b5d0d latest_2014.08.11 2092948937ac30ef82f71463a235c66d34987088 release_2014.10.06 -5834b1066462dd219f67c1c3cbd77c78e7cf3a6c latest_2014.10.06 +c437b28348a9345db8433e5b4f0e05ec8fb6c38a latest_2014.10.06 +2e8dd2949dd3eee0f56f9a3a5ebf1b2baca24aee release_2015.01.13 +2e8dd2949dd3eee0f56f9a3a5ebf1b2baca24aee latest_2015.01.13 diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 client/galaxy/scripts/mvc/tools/tools-form-base.js --- a/client/galaxy/scripts/mvc/tools/tools-form-base.js +++ b/client/galaxy/scripts/mvc/tools/tools-form-base.js @@ -111,7 +111,7 @@ this.tree.finalize(); // show errors - if (!this.workflow && options.errors) { + if (options.errors) { var error_messages = this.tree.matchResponse(options.errors); for (var input_id in error_messages) { this.highlight(input_id, error_messages[input_id], true); @@ -131,6 +131,9 @@ this.element_list[i].reset(); } }); + + // refresh + this.trigger('refresh'); }, /** Renders the UI elements required for the form @@ -145,7 +148,7 @@ // button for version selection var requirements_button = new Ui.ButtonIcon({ icon : 'fa-info-circle', - title : 'Requirements', + title : (!this.workflow && 'Requirements') || null, tooltip : 'Display tool requirements', onclick : function() { if (!this.visible) { @@ -170,7 +173,7 @@ // button for version selection var versions_button = new Ui.ButtonMenu({ icon : 'fa-cubes', - title : 'Versions', + title : (!this.workflow && 'Versions') || null, tooltip : 'Select another tool version' }); if (options.versions && options.versions.length > 1) { @@ -200,7 +203,7 @@ // button menu var menu_button = new Ui.ButtonMenu({ icon : 'fa-caret-down', - title : 'Options', + title : (!this.workflow && 'Options') || null, tooltip : 'View available options' }); diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 client/galaxy/scripts/mvc/tools/tools-form-workflow.js --- a/client/galaxy/scripts/mvc/tools/tools-form-workflow.js +++ b/client/galaxy/scripts/mvc/tools/tools-form-workflow.js @@ -9,6 +9,7 @@ initialize: function(options) { // link with node representation in workflow module this.node = workflow.active_node; + if (!this.node) { console.debug('FAILED - tools-form-workflow:initialize() - Node not found in workflow.'); return; @@ -18,6 +19,22 @@ this.workflow = true; this.options = options; + // declare fields as optional + Utils.deepeach(options.inputs, function(item) { + if (item.type) { + item.optional = (['data', 'data_hidden', 'hidden', 'drill_down']).indexOf(item.type) == -1; + } + }); + + // declare conditional fields as not optional + Utils.deepeach(options.inputs, function(item) { + if (item.type) { + if (item.type == 'conditional') { + item.test_param.optional = false; + } + } + }); + // load extension var self = this; Utils.get({ @@ -38,6 +55,7 @@ inputs[Utils.uuid()] = { label : 'Edit Step Attributes', type : 'section', + expand : this.node.annotation, inputs : [{ label : 'Annotation / Notes', name : 'annotation', @@ -160,7 +178,10 @@ type : 'boolean', value : 'false', ignore : 'false', - help : 'This action will send an email notifying you when the job is done.' + help : 'This action will send an email notifying you when the job is done.', + payload : { + 'host' : window.location.host + } },{ action : 'DeleteIntermediatesAction', label : 'Delete non-outputs', @@ -173,31 +194,51 @@ // visit input nodes and enrich by name/value pairs from server data var self = this; - function visit (inputs) { - for (var i in inputs) { - var input = inputs[i]; + function visit (head, head_list) { + head_list = head_list || []; + head_list.push(head); + for (var i in head.inputs) { + var input = head.inputs[i]; if (input.action) { + // construct identifier as expected by backend input.name = 'pja__' + output_id + '__' + input.action; if (input.argument) { input.name += '__' + input.argument; } + + // modify names of payload arguments + if (input.payload) { + for (var p_id in input.payload) { + var p = input.payload[p_id]; + input.payload[input.name + '__' + p_id] = p; + delete p; + } + } + + // access/verify existence of value var d = self.post_job_actions[input.action + output_id]; if (d) { + // mark as expanded + for (var j in head_list) { + head_list[j].expand = true; + } + + // update input field value if (input.argument) { input.value = d.action_arguments && d.action_arguments[input.argument] || input.value; } else { input.value = 'true'; } } - } else { - if (input.inputs) { - visit(input.inputs); - } + } + // continue with sub section + if (input.inputs) { + visit(input, head_list.slice(0)); } } } - visit(input_config.inputs); - + visit(input_config); + // return final configuration return input_config; }, @@ -205,6 +246,15 @@ /** Builds a new model through api call and recreates the entire form */ _buildModel: function() { + Galaxy.modal.show({ + title : 'Coming soon...', + body : 'This feature has not been implemented yet.', + buttons : { + 'Close' : function() { + Galaxy.modal.hide(); + } + } + }); }, /** Request a new model for an already created tool form and updates the form inputs @@ -231,7 +281,7 @@ data : current_state, success : function(data) { // update node in workflow module - self.node.update_field_data(data, true); + self.node.update_field_data(data); // process completed self.deferred.done(process_id); diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 client/galaxy/scripts/mvc/tools/tools-input.js --- a/client/galaxy/scripts/mvc/tools/tools-input.js +++ b/client/galaxy/scripts/mvc/tools/tools-input.js @@ -12,6 +12,7 @@ // link field this.field = options.field; + this.defaultvalue = options.defaultvalue; // set element this.setElement(this._template(options)); @@ -25,8 +26,13 @@ // add field element this.$field.prepend(this.field.$el); - // start with enabled optional fields + // decide wether to expand or collapse optional fields this.field.skip = false; + var v = this.field.value && this.field.value(); + this.field.skip = Boolean(options.optional && + ((this.field.validate && !this.field.validate()) || !v || + (v == this.defaultvalue) || (Number(v) == Number(this.defaultvalue)) || + (JSON.stringify(v) == JSON.stringify(this.defaultvalue)))); // refresh view this._refresh(); @@ -63,11 +69,12 @@ if (!this.field.skip) { this.$field.fadeIn('fast'); this.$title_optional.html('Disable'); - this.app.trigger('refresh'); } else { this.$field.hide(); this.$title_optional.html('Enable'); + this.field.value && this.field.value(this.defaultvalue); } + this.app.trigger('refresh'); }, /** Main Template @@ -82,7 +89,7 @@ // is optional if (options.optional) { - tmp += 'Optional: ' + options.label + + tmp += options.label + '<span> [<span class="ui-table-form-title-optional"/>]</span>'; } else { tmp += options.label; diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 client/galaxy/scripts/mvc/tools/tools-section.js --- a/client/galaxy/scripts/mvc/tools/tools-section.js +++ b/client/galaxy/scripts/mvc/tools/tools-section.js @@ -232,9 +232,9 @@ // create input field wrapper var input_element = new InputElement(this.app, { - label : input_def.title, - help : input_def.help, - field : repeat + label : input_def.title, + help : input_def.help, + field : repeat }); // displays as grouped subsection @@ -292,6 +292,11 @@ } }); + // show sub section if requested + if (input_def.expand) { + portlet.$header.trigger('click'); + } + // create table row this.table.add(portlet.$el); @@ -313,10 +318,11 @@ // create input field wrapper var input_element = new InputElement(this.app, { - label : input_def.label, - optional : input_def.optional, - help : input_def.help, - field : field + label : input_def.label, + defaultvalue : input_def.defaultvalue, + optional : input_def.optional, + help : input_def.help, + field : field }); // add to element list diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 client/galaxy/scripts/mvc/tools/tools-template.js --- a/client/galaxy/scripts/mvc/tools/tools-template.js +++ b/client/galaxy/scripts/mvc/tools/tools-template.js @@ -4,7 +4,7 @@ // tool form templates return { help: function(content) { - return '<div class="toolHelp">' + + return '<div class="toolHelp" style="overflow: auto;">' + '<div class="toolHelpBody">' + content + '</div>' + diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 client/galaxy/scripts/mvc/tools/tools-tree.js --- a/client/galaxy/scripts/mvc/tools/tools-tree.js +++ b/client/galaxy/scripts/mvc/tools/tools-tree.js @@ -122,13 +122,21 @@ value = patch[input.type](value); } - // handle default value - if (!field.skip) { - if (field.validate && !field.validate(value)) { + // handle simple value + if (!field.skip || self.app.workflow) { + if (field.validate && !field.validate()) { value = null; } - if (input.ignore === undefined || (value && input.ignore != value)) { + if (input.ignore === undefined || (value !== null && input.ignore != value)) { + // add value to submission add (job_input_id, input.id, value); + + // add payload to submission + if (input.payload) { + for (var p_id in input.payload) { + add (p_id, input.id, input.payload[p_id]); + } + } } } } diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 client/galaxy/scripts/utils/utils.js --- a/client/galaxy/scripts/utils/utils.js +++ b/client/galaxy/scripts/utils/utils.js @@ -6,6 +6,18 @@ // dependencies define(["libs/underscore"], function(_) { +/** Traverse through json +*/ +function deepeach(dict, callback) { + for (var i in dict) { + var d = dict[i]; + if (d && typeof(d) == "object") { + callback(d); + deepeach(d, callback); + } + } +} + /** * Sanitize/escape a string * @param{String} content - Content to be sanitized @@ -248,7 +260,8 @@ request: request, sanitize: sanitize, textify: textify, - validate: validate + validate: validate, + deepeach: deepeach }; }); diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 lib/galaxy/datatypes/metadata.py --- a/lib/galaxy/datatypes/metadata.py +++ b/lib/galaxy/datatypes/metadata.py @@ -20,6 +20,7 @@ import galaxy.model from galaxy.util import listify +from galaxy.util.object_wrapper import sanitize_lists_to_string from galaxy.util import stringify_dictionary_keys from galaxy.util import string_as_bool from galaxy.util import in_directory @@ -232,6 +233,9 @@ def to_string( self, value ): return str( value ) + def to_safe_string( self, value ): + return sanitize_lists_to_string( self.to_string( value ) ) + def make_copy( self, value, target_context = None, source_context = None ): return copy.deepcopy( value ) @@ -480,6 +484,10 @@ def to_string( self, value ): return json.dumps( value ) + def to_safe_string( self, value ): + # We do not sanitize json dicts + return json.safe_dumps( value ) + class PythonObjectParameter( MetadataParameter ): @@ -510,6 +518,10 @@ return str( self.spec.no_value ) return value.file_name + def to_safe_string( self, value ): + # We do not sanitize file names + return self.to_string( value ) + def get_html_field( self, value=None, context=None, other_values=None, **kwd ): context = context or {} other_values = other_values or {} diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 lib/galaxy/tools/__init__.py --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -12,8 +12,10 @@ import threading import types import urllib +import copy -from galaxy import eggs +from galaxy import eggs, util + eggs.require( "MarkupSafe" ) # MarkupSafe must load before mako eggs.require( "Mako" ) eggs.require( "elementtree" ) @@ -2150,9 +2152,9 @@ return None # ensures that input dictionary is jsonifiable - def sanitize(dict): + def sanitize(dict, key='value'): # get current value - value = dict['value'] if 'value' in dict else None + value = dict[key] if key in dict else None # jsonify by type if dict['type'] in ['data']: @@ -2167,15 +2169,8 @@ value = jsonify(value) # update and return - dict['value'] = value - return dict + dict[key] = value - # initialize state using default parameters - def initialize_state(trans, inputs, state, context=None): - context = ExpressionContext(state, context) - for input in inputs.itervalues(): - state[input.name] = input.get_initial_value(trans, context) - # check the current state of a value and update it if necessary def check_state(trans, input, default_value, context): value = default_value @@ -2195,6 +2190,7 @@ def populate_state(trans, inputs, state, errors, incoming, prefix="", context=None ): context = ExpressionContext(state, context) for input in inputs.itervalues(): + state[input.name] = input.get_initial_value(trans, context) key = prefix + input.name if input.type == 'repeat': group_state = state[input.name] @@ -2207,7 +2203,6 @@ if rep_index < input.max: new_state = {} new_state['__index__'] = rep_index - initialize_state(trans, input.inputs, new_state, context) group_state.append(new_state) populate_state(trans, input.inputs, new_state, errors, incoming, prefix=rep_name + "|", context=context) rep_index += 1 @@ -2223,7 +2218,6 @@ try: current_case = input.get_current_case(value, trans) group_state = state[input.name] = {} - initialize_state(trans, input.cases[current_case].inputs, group_state, context) populate_state( trans, input.cases[current_case].inputs, group_state, errors, incoming, prefix=group_prefix, context=context) group_state['__current_case__'] = current_case except Exception, e: @@ -2272,13 +2266,17 @@ # identify name input_name = tool_dict.get('name') if input_name: + # backup default value + tool_dict['defaultvalue'] = input.get_initial_value(trans, other_values) + # update input value from tool state if input_name in state_inputs: tool_dict['value'] = state_inputs[input_name] - - # sanitize if value exists - tool_dict = sanitize(tool_dict) - + + # sanitize values + sanitize(tool_dict, 'value') + sanitize(tool_dict, 'defaultvalue') + # backup final input dictionary group_inputs[input_index] = tool_dict @@ -2303,7 +2301,6 @@ # initialize and populate tool state state_inputs = {} state_errors = {} - initialize_state(trans, self.inputs, state_inputs) populate_state(trans, self.inputs, state_inputs, state_errors, params.__dict__) # create basic tool model diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 lib/galaxy/tools/evaluation.py --- a/lib/galaxy/tools/evaluation.py +++ b/lib/galaxy/tools/evaluation.py @@ -2,10 +2,12 @@ import tempfile from galaxy import model +from galaxy.util.object_wrapper import wrap_with_safe_string from galaxy.util.bunch import Bunch from galaxy.util.none_like import NoneDataset from galaxy.util.template import fill_template from galaxy.tools.wrappers import ( + ToolParameterValueWrapper, DatasetFilenameWrapper, DatasetListWrapper, DatasetCollectionWrapper, @@ -114,6 +116,9 @@ self.__populate_input_dataset_wrappers(param_dict, input_datasets, input_dataset_paths) self.__populate_output_dataset_wrappers(param_dict, output_datasets, output_paths, job_working_directory) self.__populate_unstructured_path_rewrites(param_dict) + # Call param dict sanitizer, before non-job params are added, as we don't want to sanitize filenames. + self.__sanitize_param_dict( param_dict ) + # Parameters added after this line are not sanitized self.__populate_non_job_params(param_dict) # Return the dictionary of parameters @@ -334,6 +339,24 @@ #the paths rewritten. self.__walk_inputs( self.tool.inputs, param_dict, rewrite_unstructured_paths ) + def __sanitize_param_dict( self, param_dict ): + """ + Sanitize all values that will be substituted on the command line, with the exception of ToolParameterValueWrappers, + which already have their own specific sanitization rules and also exclude special-cased named values. + We will only examine the first level for values to skip; the wrapping function will recurse as necessary. + + Note: this method follows the style of the similar populate calls, in that param_dict is modified in-place. + """ + # chromInfo is a filename, do not sanitize it. + skip = [ 'chromInfo' ] + if not self.tool or not self.tool.options or self.tool.options.sanitize: + for key, value in param_dict.items(): + if key not in skip: + # Remove key so that new wrapped object will occupy key slot + del param_dict[key] + # And replace with new wrapped key + param_dict[ wrap_with_safe_string( key, no_wrap_classes=ToolParameterValueWrapper ) ] = wrap_with_safe_string( value, no_wrap_classes=ToolParameterValueWrapper ) + def build( self ): """ Build runtime description of job to execute, evaluate command and diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 lib/galaxy/tools/wrappers.py --- a/lib/galaxy/tools/wrappers.py +++ b/lib/galaxy/tools/wrappers.py @@ -2,6 +2,7 @@ from galaxy import exceptions from galaxy.util.none_like import NoneDataset from galaxy.util import odict +from galaxy.util.object_wrapper import wrap_with_safe_string from logging import getLogger log = getLogger( __name__ ) @@ -162,10 +163,13 @@ if name in self.metadata.spec: if rval is None: rval = self.metadata.spec[name].no_value - rval = self.metadata.spec[name].param.to_string( rval ) + rval = self.metadata.spec[ name ].param.to_safe_string( rval ) # Store this value, so we don't need to recalculate if needed # again setattr( self, name, rval ) + else: + #escape string value of non-defined metadata value + rval = wrap_with_safe_string( rval ) return rval def __nonzero__( self ): @@ -190,9 +194,13 @@ ext = tool.inputs[name].extensions[0] except: ext = 'data' - self.dataset = NoneDataset( datatypes_registry=datatypes_registry, ext=ext ) + self.dataset = wrap_with_safe_string( NoneDataset( datatypes_registry=datatypes_registry, ext=ext ), no_wrap_classes=ToolParameterValueWrapper ) else: - self.dataset = dataset + # Tool wrappers should not normally be accessing .dataset directly, + # so we will wrap it and keep the original around for file paths + # Should we name this .value to maintain consistency with most other ToolParameterValueWrapper? + self.unsanitized = dataset + self.dataset = wrap_with_safe_string( dataset, no_wrap_classes=ToolParameterValueWrapper ) self.metadata = self.MetadataWrapper( dataset.metadata ) self.datatypes_registry = datatypes_registry self.false_path = getattr( dataset_path, "false_path", None ) @@ -210,7 +218,7 @@ if self.false_path is not None: return self.false_path else: - return self.dataset.file_name + return self.unsanitized.file_name def __getattr__( self, key ): if self.false_path is not None and key == 'file_name': @@ -230,7 +238,7 @@ # object store to find the static location of this # directory. try: - return self.dataset.extra_files_path + return self.unsanitized.extra_files_path except exceptions.ObjectNotFound: # NestedObjectstore raises an error here # instead of just returning a non-existent diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 lib/galaxy/util/__init__.py --- a/lib/galaxy/util/__init__.py +++ b/lib/galaxy/util/__init__.py @@ -360,46 +360,57 @@ '#': '__pd__'} -def restore_text(text): +def restore_text( text, character_map=mapped_chars ): """Restores sanitized text""" if not text: return text - for key, value in mapped_chars.items(): + for key, value in character_map.items(): text = text.replace(value, key) return text -def sanitize_text(text): +def sanitize_text( text, valid_characters=valid_chars, character_map=mapped_chars, invalid_character='X' ): """ Restricts the characters that are allowed in text; accepts both strings - and lists of strings. + and lists of strings; non-string entities will be cast to strings. """ - if isinstance( text, basestring ): - return _sanitize_text_helper(text) - elif isinstance( text, list ): - return [ _sanitize_text_helper(t) for t in text ] + if isinstance( text, list ): + return map( lambda x: sanitize_text( x, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character ), text ) + if not isinstance( text, basestring ): + text = smart_str( text ) + return _sanitize_text_helper( text, valid_characters=valid_characters, character_map=character_map ) - -def _sanitize_text_helper(text): +def _sanitize_text_helper( text, valid_characters=valid_chars, character_map=mapped_chars, invalid_character='X' ): """Restricts the characters that are allowed in a string""" out = [] for c in text: - if c in valid_chars: + if c in valid_characters: out.append(c) - elif c in mapped_chars: - out.append(mapped_chars[c]) + elif c in character_map: + out.append( character_map[c] ) else: - out.append('X') # makes debugging easier + out.append( invalid_character ) # makes debugging easier return ''.join(out) -def sanitize_param(value): +def sanitize_lists_to_string( values, valid_characters=valid_chars, character_map=mapped_chars, invalid_character='X' ): + if isinstance( values, list ): + rval = [] + for value in values: + rval.append( sanitize_lists_to_string( value, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character ) ) + values = ",".join( rval ) + else: + values = sanitize_text( values, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character ) + return values + + +def sanitize_param( value, valid_characters=valid_chars, character_map=mapped_chars, invalid_character='X' ): """Clean incoming parameters (strings or lists)""" if isinstance( value, basestring ): - return sanitize_text(value) + return sanitize_text( value, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character ) elif isinstance( value, list ): - return map(sanitize_text, value) + return map( lambda x: sanitize_text( x, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character ), value ) else: raise Exception('Unknown parameter type (%s)' % ( type( value ) )) diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 lib/galaxy/util/dbkeys.py --- a/lib/galaxy/util/dbkeys.py +++ b/lib/galaxy/util/dbkeys.py @@ -4,6 +4,7 @@ #dbkeys read from disk using builds.txt from galaxy.util import read_dbnames from galaxy.util.json import loads +from galaxy.util.object_wrapper import sanitize_lists_to_string import os.path @@ -84,6 +85,7 @@ # use configured server len path if not chrom_info: # Default to built-in build. - chrom_info = os.path.join( self._static_chrom_info_path, "%s.len" % dbkey ) + # Since we are using an unverified dbkey, we will sanitize the dbkey before use + chrom_info = os.path.join( self._static_chrom_info_path, "%s.len" % sanitize_lists_to_string( dbkey ) ) chrom_info = os.path.abspath( chrom_info ) return ( chrom_info, db_dataset ) diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 lib/galaxy/util/object_wrapper.py --- /dev/null +++ b/lib/galaxy/util/object_wrapper.py @@ -0,0 +1,436 @@ +""" +Classes for wrapping Objects and Sanitizing string output. +""" + +import inspect +import copy_reg +import logging +import string +from numbers import Number +from types import ( NoneType, NotImplementedType, EllipsisType, FunctionType, MethodType, GeneratorType, CodeType, + BuiltinFunctionType, BuiltinMethodType, ModuleType, XRangeType, SliceType, TracebackType, FrameType, + BufferType, DictProxyType, GetSetDescriptorType, MemberDescriptorType ) +from UserDict import UserDict + +from galaxy.util import sanitize_lists_to_string as _sanitize_lists_to_string + +log = logging.getLogger( __name__ ) + +# Define different behaviors for different types, see also: https://docs.python.org/2/library/types.html + +# Known Callable types +__CALLABLE_TYPES__ = ( FunctionType, MethodType, GeneratorType, CodeType, BuiltinFunctionType, BuiltinMethodType, ) + +# Always wrap these types without attempting to subclass +__WRAP_NO_SUBCLASS__ = ( ModuleType, XRangeType, SliceType, BufferType, TracebackType, FrameType, DictProxyType, + GetSetDescriptorType, MemberDescriptorType ) + __CALLABLE_TYPES__ + +# Don't wrap or sanitize. +__DONT_SANITIZE_TYPES__ = ( Number, bool, NoneType, NotImplementedType, EllipsisType, bytearray, ) + +# Don't wrap, but do sanitize. +__DONT_WRAP_TYPES__ = tuple() #( basestring, ) so that we can get the unsanitized string, we will now wrap basestring instances + +# Wrap contents, but not the container +__WRAP_SEQUENCES__ = ( tuple, list, ) +__WRAP_SETS__ = ( set, frozenset, ) +__WRAP_MAPPINGS__ = ( dict, UserDict, ) + + +# Define the set of characters that are not sanitized, and define a set of mappings for those that are. +# characters that are valid +VALID_CHARACTERS = set( string.letters + string.digits + " -=_.()/+*^,:?!@" ) + +# characters that are allowed but need to be escaped +CHARACTER_MAP = { '>': '__gt__', + '<': '__lt__', + "'": '__sq__', + '"': '__dq__', + '[': '__ob__', + ']': '__cb__', + '{': '__oc__', + '}': '__cc__', + '\n': '__cn__', + '\r': '__cr__', + '\t': '__tc__', + '#': '__pd__'} + +INVALID_CHARACTER = "X" + +def sanitize_lists_to_string( values, valid_characters=VALID_CHARACTERS, character_map=CHARACTER_MAP, invalid_character=INVALID_CHARACTER ): + return _sanitize_lists_to_string( values, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character ) + + +def wrap_with_safe_string( value, no_wrap_classes = None ): + """ + Recursively wrap values that should be wrapped. + """ + + def __do_wrap( value ): + if isinstance( value, SafeStringWrapper ): + # Only ever wrap one-layer + return value + if callable( value ): + safe_class = CallableSafeStringWrapper + else: + safe_class = SafeStringWrapper + if isinstance( value, no_wrap_classes ): + return value + if isinstance( value, __DONT_WRAP_TYPES__ ): + return sanitize_lists_to_string( value, valid_characters=VALID_CHARACTERS, character_map=CHARACTER_MAP ) + if isinstance( value, __WRAP_NO_SUBCLASS__ ): + return safe_class( value, safe_string_wrapper_function = __do_wrap ) + for this_type in __WRAP_SEQUENCES__ + __WRAP_SETS__: + if isinstance( value, this_type ): + return this_type( map( __do_wrap, value ) ) + for this_type in __WRAP_MAPPINGS__: + if isinstance( value, this_type ): + # Wrap both key and value + return this_type( map( lambda x: ( __do_wrap( x[0] ), __do_wrap( x[1] ) ), value.items() ) ) + # Create a dynamic class that joins SafeStringWrapper with the object being wrapped. + # This allows e.g. isinstance to continue to work. + try: + wrapped_class_name = value.__name__ + wrapped_class = value + except: + wrapped_class_name = value.__class__.__name__ + wrapped_class = value.__class__ + value_mod = inspect.getmodule( value ) + if value_mod: + wrapped_class_name = "%s.%s" % ( value_mod.__name__, wrapped_class_name ) + wrapped_class_name = "SafeStringWrapper(%s:%s)" % ( wrapped_class_name, ",".join( sorted( map( str, no_wrap_classes ) ) ) ) + do_wrap_func_name = "__do_wrap_%s" % ( wrapped_class_name ) + do_wrap_func = __do_wrap + global_dict = globals() + if wrapped_class_name in global_dict: + # Check to see if we have created a wrapper for this class yet, if so, reuse + wrapped_class = global_dict.get( wrapped_class_name ) + do_wrap_func = global_dict.get( do_wrap_func_name, __do_wrap ) + else: + try: + wrapped_class = type( wrapped_class_name, ( safe_class, wrapped_class, ), {} ) + except TypeError, e: + # Fail-safe for when a class cannot be dynamically subclassed. + log.warning( "Unable to create dynamic subclass for %s, %s: %s", type( value), value, e ) + wrapped_class = type( wrapped_class_name, ( safe_class, ), {} ) + if wrapped_class not in ( SafeStringWrapper, CallableSafeStringWrapper ): + # Save this wrapper for reuse and pickling/copying + global_dict[ wrapped_class_name ] = wrapped_class + do_wrap_func.__name__ = do_wrap_func_name + global_dict[ do_wrap_func_name ] = do_wrap_func + def pickle_safe_object( safe_object ): + return ( wrapped_class, ( safe_object.unsanitized, do_wrap_func, ) ) + # Set pickle and copy properties + copy_reg.pickle( wrapped_class, pickle_safe_object, do_wrap_func ) + return wrapped_class( value, safe_string_wrapper_function = do_wrap_func ) + # Determine classes not to wrap + if no_wrap_classes: + if not isinstance( no_wrap_classes, ( tuple, list ) ): + no_wrap_classes = [ no_wrap_classes ] + no_wrap_classes = list( no_wrap_classes ) + list( __DONT_SANITIZE_TYPES__ ) + [ SafeStringWrapper ] + else: + no_wrap_classes = list( __DONT_SANITIZE_TYPES__ ) + [ SafeStringWrapper ] + no_wrap_classes = tuple( set( sorted( no_wrap_classes, key=str ) ) ) + return __do_wrap( value ) + + +# N.B. refer to e.g. https://docs.python.org/2/reference/datamodel.html for information on Python's Data Model. + + +class SafeStringWrapper( object ): + """ + Class that wraps and sanitizes any provided value's attributes + that will attempt to be cast into a string. + + Attempts to mimic behavior of original class, including operands. + + To ensure proper handling of e.g. subclass checks, the *wrap_with_safe_string()* + method should be used. + + This wrapping occurs in a recursive/parasitic fashion, as all called attributes of + the originally wrapped object will also be wrapped and sanitized, unless the attribute + is of a type found in __DONT_SANITIZE_TYPES__ + __DONT_WRAP_TYPES__, where e.g. ~(strings + will still be sanitized, but not wrapped), and e.g. integers will have neither. + """ + __UNSANITIZED_ATTRIBUTE_NAME__ = 'unsanitized' + __NO_WRAP_NAMES__ = [ '__safe_string_wrapper_function__', __UNSANITIZED_ATTRIBUTE_NAME__] + + + def __new__( cls, *arg, **kwd ): + # We need to define a __new__ since, we are subclassing from e.g. immutable str, which internally sets data + # that will be used when other + this (this + other is handled by __add__) + safe_string_wrapper_function = kwd.get( 'safe_string_wrapper_function', None) or wrap_with_safe_string + try: + return super( SafeStringWrapper, cls ).__new__( cls, sanitize_lists_to_string( arg[0], valid_characters=VALID_CHARACTERS, character_map=CHARACTER_MAP ) ) + except Exception, e: + log.warning( "Could not provide an argument to %s.__new__: %s; will try without arguments.", cls, e ) + return super( SafeStringWrapper, cls ).__new__( cls ) + + def __init__( self, value, safe_string_wrapper_function = wrap_with_safe_string ): + self.unsanitized = value + self.__safe_string_wrapper_function__ = safe_string_wrapper_function + + def __str__( self ): + return sanitize_lists_to_string( self.unsanitized, valid_characters=VALID_CHARACTERS, character_map=CHARACTER_MAP ) + + def __repr__( self ): + return "%s object at %x on: %s" % ( sanitize_lists_to_string( self.__class__.__name__, valid_characters=VALID_CHARACTERS, character_map=CHARACTER_MAP ), id( self ), sanitize_lists_to_string( repr( self.unsanitized ), valid_characters=VALID_CHARACTERS, character_map=CHARACTER_MAP ) ) + + def __lt__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.unsanitized < other + + def __le__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.unsanitized <= other + + def __eq__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.unsanitized == other + + def __ne__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.unsanitized != other + + def __gt__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.unsanitized > other + + def __ge__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.unsanitized >= other + + def __lt__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.unsanitized < other + + def __cmp__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return cmp( self.unsanitized, other ) + + # Do not implement __rcmp__, python 2.2 < 2.6 + + def __hash__( self ): + return hash( self.unsanitized ) + + def __nonzero__( self ): + return bool( self.unsanitized ) + + # Do not implement __unicode__, we will rely on __str__ + + def __getattr__( self, name ): + if name in SafeStringWrapper.__NO_WRAP_NAMES__: + #FIXME: is this ever reached? + return object.__getattr__( self, name ) + return self.__safe_string_wrapper_function__( getattr( self.unsanitized, name ) ) + + def __setattr__( self, name, value ): + if name in SafeStringWrapper.__NO_WRAP_NAMES__: + return object.__setattr__( self, name, value ) + return setattr( self.unsanitized, name, value ) + + def __delattr__( self, name ): + if name in SafeStringWrapper.__NO_WRAP_NAMES__: + return object.__delattr__( self, name ) + return delattr( self.unsanitized, name ) + + def __getattribute__( self, name ): + if name in SafeStringWrapper.__NO_WRAP_NAMES__: + return object.__getattribute__( self, name ) + return self.__safe_string_wrapper_function__( getattr( object.__getattribute__( self, 'unsanitized' ), name ) ) + + # Skip Descriptors + + # Skip __slots__ + + # Don't need __metaclass__, we'll use the helper function to handle with subclassing for e.g. isinstance() + + # Revisit: + # __instancecheck__ + # __subclasscheck__ + # We are using a helper class to create dynamic subclasses to handle class checks + + # We address __call__ as needed based upon unsanitized, through the use of a CallableSafeStringWrapper class + + def __len__( self ): + original_value = self.unsanitized + while isinstance( original_value, SafeStringWrapper ): + original_value = self.unsanitized + return len( self.unsanitized ) + + def __getitem__( self, key ): + return self.__safe_string_wrapper_function__( self.unsanitized[ key ] ) + + def __setitem__( self, key, value ): + while isinstance( value, SafeStringWrapper ): + value = value.unsanitized + self.unsanitized[ key ] = value + + def __delitem__( self, key ): + del self.unsanitized[ key ] + + def __iter__( self ): + return iter( map( self.__safe_string_wrapper_function__, iter( self.unsanitized ) ) ) + + # Do not implement __reversed__ + + def __contains__( self, item ): + # FIXME: Do we need to consider if item is/isn't or does/doesn't contain SafeStringWrapper? + # When considering e.g. nested lists/dicts/etc, this gets complicated + while isinstance( item, SafeStringWrapper ): + item = item.unsanitized + return item in self.unsanitized + + # Not sure that we need these slice methods, but will provide anyway + def __getslice__( self, i, j ): + return self.__safe_string_wrapper_function__( self.unsanitized[ i:j ] ) + + def __setslice__( self, i, j, value ): + self.unsanitized[ i:j ] = value + + def __delslice__( self, i, j ): + del self.unsanitized[ i:j ] + + def __add__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( self.unsanitized + other ) + + def __sub__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( self.unsanitized - other ) + + def __mul__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( self.unsanitized * other ) + + def __floordiv__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( self.unsanitized // other ) + + def __mod__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( self.unsanitized % other ) + + def __divmod__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( divmod( self.unsanitized, other ) ) + + def __pow__( self, *other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( pow( self.unsanitized, *other ) ) + + def __lshift__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( self.unsanitized << other ) + + def __rshift__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( self.unsanitized >> other ) + + def __and__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( self.unsanitized & other ) + + def __xor__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( self.unsanitized ^ other ) + + def __or__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( self.unsanitized | other ) + + def __div__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( self.unsanitized / other ) + + def __truediv__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( self.unsanitized / other ) + + # The only reflected operand that we will define is __rpow__, due to coercion rules complications as per docs + def __rpow__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return self.__safe_string_wrapper_function__( pow( other, self.unsanitized ) ) + + # Do not implement in-place operands + + def __neg__( self ): + return __safe_string_wrapper_function__( -self.unsanitized ) + + def __pos__( self ): + return __safe_string_wrapper_function__( +self.unsanitized ) + + def __abs__( self ): + return __safe_string_wrapper_function__( abs( self.unsanitized ) ) + + def __invert__( self ): + return __safe_string_wrapper_function__( ~self.unsanitized ) + + def __complex__( self ): + return __safe_string_wrapper_function__( complex( self.unsanitized ) ) + + def __int__( self ): + return int( self.unsanitized ) + + def __float__( self ): + return float( self.unsanitized ) + + def __oct__( self ): + return oct( self.unsanitized ) + + def __hex__( self ): + return hex( self.unsanitized ) + + def __index__( self ): + return self.unsanitized.index() + + def __coerce__( self, other ): + while isinstance( other, SafeStringWrapper ): + other = other.unsanitized + return coerce( self.unsanitized, other ) + + def __enter__( self ): + return self.unsanitized.__enter__() + + def __exit__( self, *args ): + return self.unsanitized.__exit__( *args ) + +class CallableSafeStringWrapper( SafeStringWrapper ): + + def __call__( self, *args, **kwds ): + return self.__safe_string_wrapper_function__( self.unsanitized( *args, **kwds ) ) + + +# Enable pickling/deepcopy +def pickle_SafeStringWrapper( safe_object ): + args = ( safe_object.unsanitized, ) + cls = SafeStringWrapper + if isinstance( safe_object, CallableSafeStringWrapper ): + cls = CallableSafeStringWrapper + return ( cls, args ) +copy_reg.pickle( SafeStringWrapper, pickle_SafeStringWrapper, wrap_with_safe_string ) +copy_reg.pickle( CallableSafeStringWrapper, pickle_SafeStringWrapper, wrap_with_safe_string ) + diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 lib/galaxy/webapps/galaxy/controllers/tool_runner.py --- a/lib/galaxy/webapps/galaxy/controllers/tool_runner.py +++ b/lib/galaxy/webapps/galaxy/controllers/tool_runner.py @@ -80,14 +80,28 @@ message=message, status=status, redirect=redirect ) ) - params = galaxy.util.Params( kwd, sanitize = False ) #Sanitize parameters when substituting into command line via input wrappers - #do param translation here, used by datasource tools - if tool.input_translator: - tool.input_translator.translate( params ) + + def _validated_params_for( kwd ): + params = galaxy.util.Params( kwd, sanitize=False ) # Sanitize parameters when substituting into command line via input wrappers + #do param translation here, used by datasource tools + if tool.input_translator: + tool.input_translator.translate( params ) + return params + + params = _validated_params_for( kwd ) # We may be visiting Galaxy for the first time ( e.g., sending data from UCSC ), # so make sure to create a new history if we've never had one before. history = tool.get_default_history_by_trans( trans, create=True ) - template, vars = tool.handle_input( trans, params.__dict__ ) + try: + template, vars = tool.handle_input( trans, params.__dict__ ) + except KeyError: + # This error indicates (or at least can indicate) there was a + # problem with the stored tool_state - it is incompatible with + # this variant of the tool - possibly because the tool changed + # or because the tool version changed. + del kwd[ "tool_state" ] + params = _validated_params_for( kwd ) + template, vars = tool.handle_input( trans, params.__dict__ ) if len( params ) > 0: trans.log_event( "Tool params: %s" % ( str( params ) ), tool_id=tool_id ) add_frame = AddFrameData() diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/mvc/tools/tools-form-base.js --- a/static/scripts/mvc/tools/tools-form-base.js +++ b/static/scripts/mvc/tools/tools-form-base.js @@ -111,7 +111,7 @@ this.tree.finalize(); // show errors - if (!this.workflow && options.errors) { + if (options.errors) { var error_messages = this.tree.matchResponse(options.errors); for (var input_id in error_messages) { this.highlight(input_id, error_messages[input_id], true); @@ -131,6 +131,9 @@ this.element_list[i].reset(); } }); + + // refresh + this.trigger('refresh'); }, /** Renders the UI elements required for the form @@ -145,7 +148,7 @@ // button for version selection var requirements_button = new Ui.ButtonIcon({ icon : 'fa-info-circle', - title : 'Requirements', + title : (!this.workflow && 'Requirements') || null, tooltip : 'Display tool requirements', onclick : function() { if (!this.visible) { @@ -170,7 +173,7 @@ // button for version selection var versions_button = new Ui.ButtonMenu({ icon : 'fa-cubes', - title : 'Versions', + title : (!this.workflow && 'Versions') || null, tooltip : 'Select another tool version' }); if (options.versions && options.versions.length > 1) { @@ -200,7 +203,7 @@ // button menu var menu_button = new Ui.ButtonMenu({ icon : 'fa-caret-down', - title : 'Options', + title : (!this.workflow && 'Options') || null, tooltip : 'View available options' }); diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/mvc/tools/tools-form-workflow.js --- a/static/scripts/mvc/tools/tools-form-workflow.js +++ b/static/scripts/mvc/tools/tools-form-workflow.js @@ -9,6 +9,7 @@ initialize: function(options) { // link with node representation in workflow module this.node = workflow.active_node; + if (!this.node) { console.debug('FAILED - tools-form-workflow:initialize() - Node not found in workflow.'); return; @@ -18,6 +19,22 @@ this.workflow = true; this.options = options; + // declare fields as optional + Utils.deepeach(options.inputs, function(item) { + if (item.type) { + item.optional = (['data', 'data_hidden', 'hidden', 'drill_down']).indexOf(item.type) == -1; + } + }); + + // declare conditional fields as not optional + Utils.deepeach(options.inputs, function(item) { + if (item.type) { + if (item.type == 'conditional') { + item.test_param.optional = false; + } + } + }); + // load extension var self = this; Utils.get({ @@ -38,6 +55,7 @@ inputs[Utils.uuid()] = { label : 'Edit Step Attributes', type : 'section', + expand : this.node.annotation, inputs : [{ label : 'Annotation / Notes', name : 'annotation', @@ -160,7 +178,10 @@ type : 'boolean', value : 'false', ignore : 'false', - help : 'This action will send an email notifying you when the job is done.' + help : 'This action will send an email notifying you when the job is done.', + payload : { + 'host' : window.location.host + } },{ action : 'DeleteIntermediatesAction', label : 'Delete non-outputs', @@ -173,31 +194,51 @@ // visit input nodes and enrich by name/value pairs from server data var self = this; - function visit (inputs) { - for (var i in inputs) { - var input = inputs[i]; + function visit (head, head_list) { + head_list = head_list || []; + head_list.push(head); + for (var i in head.inputs) { + var input = head.inputs[i]; if (input.action) { + // construct identifier as expected by backend input.name = 'pja__' + output_id + '__' + input.action; if (input.argument) { input.name += '__' + input.argument; } + + // modify names of payload arguments + if (input.payload) { + for (var p_id in input.payload) { + var p = input.payload[p_id]; + input.payload[input.name + '__' + p_id] = p; + delete p; + } + } + + // access/verify existence of value var d = self.post_job_actions[input.action + output_id]; if (d) { + // mark as expanded + for (var j in head_list) { + head_list[j].expand = true; + } + + // update input field value if (input.argument) { input.value = d.action_arguments && d.action_arguments[input.argument] || input.value; } else { input.value = 'true'; } } - } else { - if (input.inputs) { - visit(input.inputs); - } + } + // continue with sub section + if (input.inputs) { + visit(input, head_list.slice(0)); } } } - visit(input_config.inputs); - + visit(input_config); + // return final configuration return input_config; }, @@ -205,6 +246,15 @@ /** Builds a new model through api call and recreates the entire form */ _buildModel: function() { + Galaxy.modal.show({ + title : 'Coming soon...', + body : 'This feature has not been implemented yet.', + buttons : { + 'Close' : function() { + Galaxy.modal.hide(); + } + } + }); }, /** Request a new model for an already created tool form and updates the form inputs @@ -231,7 +281,7 @@ data : current_state, success : function(data) { // update node in workflow module - self.node.update_field_data(data, true); + self.node.update_field_data(data); // process completed self.deferred.done(process_id); diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/mvc/tools/tools-input.js --- a/static/scripts/mvc/tools/tools-input.js +++ b/static/scripts/mvc/tools/tools-input.js @@ -12,6 +12,7 @@ // link field this.field = options.field; + this.defaultvalue = options.defaultvalue; // set element this.setElement(this._template(options)); @@ -25,8 +26,13 @@ // add field element this.$field.prepend(this.field.$el); - // start with enabled optional fields + // decide wether to expand or collapse optional fields this.field.skip = false; + var v = this.field.value && this.field.value(); + this.field.skip = Boolean(options.optional && + ((this.field.validate && !this.field.validate()) || !v || + (v == this.defaultvalue) || (Number(v) == Number(this.defaultvalue)) || + (JSON.stringify(v) == JSON.stringify(this.defaultvalue)))); // refresh view this._refresh(); @@ -63,11 +69,12 @@ if (!this.field.skip) { this.$field.fadeIn('fast'); this.$title_optional.html('Disable'); - this.app.trigger('refresh'); } else { this.$field.hide(); this.$title_optional.html('Enable'); + this.field.value && this.field.value(this.defaultvalue); } + this.app.trigger('refresh'); }, /** Main Template @@ -82,7 +89,7 @@ // is optional if (options.optional) { - tmp += 'Optional: ' + options.label + + tmp += options.label + '<span> [<span class="ui-table-form-title-optional"/>]</span>'; } else { tmp += options.label; diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/mvc/tools/tools-section.js --- a/static/scripts/mvc/tools/tools-section.js +++ b/static/scripts/mvc/tools/tools-section.js @@ -232,9 +232,9 @@ // create input field wrapper var input_element = new InputElement(this.app, { - label : input_def.title, - help : input_def.help, - field : repeat + label : input_def.title, + help : input_def.help, + field : repeat }); // displays as grouped subsection @@ -292,6 +292,11 @@ } }); + // show sub section if requested + if (input_def.expand) { + portlet.$header.trigger('click'); + } + // create table row this.table.add(portlet.$el); @@ -313,10 +318,11 @@ // create input field wrapper var input_element = new InputElement(this.app, { - label : input_def.label, - optional : input_def.optional, - help : input_def.help, - field : field + label : input_def.label, + defaultvalue : input_def.defaultvalue, + optional : input_def.optional, + help : input_def.help, + field : field }); // add to element list diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/mvc/tools/tools-template.js --- a/static/scripts/mvc/tools/tools-template.js +++ b/static/scripts/mvc/tools/tools-template.js @@ -4,7 +4,7 @@ // tool form templates return { help: function(content) { - return '<div class="toolHelp">' + + return '<div class="toolHelp" style="overflow: auto;">' + '<div class="toolHelpBody">' + content + '</div>' + diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/mvc/tools/tools-tree.js --- a/static/scripts/mvc/tools/tools-tree.js +++ b/static/scripts/mvc/tools/tools-tree.js @@ -122,13 +122,21 @@ value = patch[input.type](value); } - // handle default value - if (!field.skip) { - if (field.validate && !field.validate(value)) { + // handle simple value + if (!field.skip || self.app.workflow) { + if (field.validate && !field.validate()) { value = null; } - if (input.ignore === undefined || (value && input.ignore != value)) { + if (input.ignore === undefined || (value !== null && input.ignore != value)) { + // add value to submission add (job_input_id, input.id, value); + + // add payload to submission + if (input.payload) { + for (var p_id in input.payload) { + add (p_id, input.id, input.payload[p_id]); + } + } } } } diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/packed/mvc/tools/tools-form-base.js --- a/static/scripts/packed/mvc/tools/tools-form-base.js +++ b/static/scripts/packed/mvc/tools/tools-form-base.js @@ -1,1 +1,1 @@ -define(["utils/utils","utils/deferred","mvc/ui/ui-portlet","mvc/ui/ui-misc","mvc/citation/citation-model","mvc/citation/citation-view","mvc/tools","mvc/tools/tools-template","mvc/tools/tools-content","mvc/tools/tools-section","mvc/tools/tools-tree"],function(g,h,f,k,i,a,d,c,e,j,b){return Backbone.View.extend({initialize:function(l){console.debug(l);var m=parent.Galaxy;if(m&&m.modal){this.modal=m.modal}else{this.modal=new k.Modal.View()}if(m&&m.currUser){this.is_admin=m.currUser.get("is_admin")}else{this.is_admin=false}this.options=l;this.container=this.options.container||"body";this.deferred=new h();this.setElement("<div/>");$(this.container).append(this.$el);this._buildForm()},reciept:function(l){$(this.container).empty();$(this.container).append(l)},highlight:function(m,n,l){var o=this.element_list[m];if(o){o.error(n||"Please verify this parameter.");if(!l){$(this.container).animate({scrollTop:o.$el.offset().top-20},500)}}},_buildForm:function(){var l=this;this.off("refresh");this.off("reset");this.field_list={};this.input_list={};this.element_list={};this.tree=new b(this);this.content=new e(this);var n=this.options;this._renderForm(n);this.tree.finalize();if(!this.workflow&&n.errors){var o=this.tree.matchResponse(n.errors);for(var m in o){this.highlight(m,o[m],true)}}this.on("refresh",function(){l.deferred.reset();l.deferred.execute(function(){l._updateModel()})});this.on("reset",function(){for(var p in this.element_list){this.element_list[p].reset()}})},_renderForm:function(u){var t=this;this.message=new k.Message();var q=new k.ButtonIcon({icon:"fa-info-circle",title:"Requirements",tooltip:"Display tool requirements",onclick:function(){if(!this.visible){this.visible=true;t.message.update({persistent:true,message:c.requirements(u),status:"info"})}else{this.visible=false;t.message.update({message:""})}}});if(!u.requirements||u.requirements.length==0){q.$el.hide()}var m=new k.ButtonMenu({icon:"fa-cubes",title:"Versions",tooltip:"Select another tool version"});if(u.versions&&u.versions.length>1){for(var o in u.versions){var r=u.versions[o];if(r!=u.version){m.addMenu({title:"Switch to "+r,version:r,icon:"fa-cube",onclick:function(){u.id=u.id.replace(u.version,this.version);u.version=this.version;t.deferred.reset();t.deferred.execute(function(){t._buildModel()})}})}}}else{m.$el.hide()}var p=new k.ButtonMenu({icon:"fa-caret-down",title:"Options",tooltip:"View available options"});if(u.biostar_url){p.addMenu({icon:"fa-question-circle",title:"Question?",tooltip:"Ask a question about this tool (Biostar)",onclick:function(){window.open(u.biostar_url+"/p/new/post/")}});p.addMenu({icon:"fa-search",title:"Search",tooltip:"Search help for this tool (Biostar)",onclick:function(){window.open(u.biostar_url+"/t/"+u.id+"/")}})}p.addMenu({icon:"fa-share",title:"Share",tooltip:"Share this tool",onclick:function(){prompt("Copy to clipboard: Ctrl+C, Enter",window.location.origin+galaxy_config.root+"root?tool_id="+u.id)}});if(this.is_admin){p.addMenu({icon:"fa-download",title:"Download",tooltip:"Download this tool",onclick:function(){window.location.href=galaxy_config.root+"api/tools/"+u.id+"/download"}})}this.section=new j.View(t,{inputs:u.inputs,cls:"ui-table-plain"});if(this.incompatible){this.$el.hide();$("#tool-form-classic").show();return}this.portlet=new f.View({icon:"fa-wrench",title:"<b>"+u.name+"</b> "+u.description+" (Galaxy Tool Version "+u.version+")",cls:"ui-portlet-slim",operations:{requirements:q,menu:p,versions:m},buttons:this.buttons});if(this.options.workflow){this.portlet.$content.css("padding","0px")}this.portlet.append(this.message.$el,true);this.portlet.append(this.section.$el);this.$el.empty();this.$el.append(this.portlet.$el);if(u.help!=""){this.$el.append(c.help(u.help))}if(u.citations){var s=$("<div/>");var l=new i.ToolCitationCollection();l.tool_id=u.id;var n=new a.CitationListView({el:s,collection:l});n.render();l.fetch();this.$el.append(s)}if(u.message){this.message.update({persistent:true,status:"warning",message:u.message})}}})}); \ No newline at end of file +define(["utils/utils","utils/deferred","mvc/ui/ui-portlet","mvc/ui/ui-misc","mvc/citation/citation-model","mvc/citation/citation-view","mvc/tools","mvc/tools/tools-template","mvc/tools/tools-content","mvc/tools/tools-section","mvc/tools/tools-tree"],function(g,h,f,k,i,a,d,c,e,j,b){return Backbone.View.extend({initialize:function(l){console.debug(l);var m=parent.Galaxy;if(m&&m.modal){this.modal=m.modal}else{this.modal=new k.Modal.View()}if(m&&m.currUser){this.is_admin=m.currUser.get("is_admin")}else{this.is_admin=false}this.options=l;this.container=this.options.container||"body";this.deferred=new h();this.setElement("<div/>");$(this.container).append(this.$el);this._buildForm()},reciept:function(l){$(this.container).empty();$(this.container).append(l)},highlight:function(m,n,l){var o=this.element_list[m];if(o){o.error(n||"Please verify this parameter.");if(!l){$(this.container).animate({scrollTop:o.$el.offset().top-20},500)}}},_buildForm:function(){var l=this;this.off("refresh");this.off("reset");this.field_list={};this.input_list={};this.element_list={};this.tree=new b(this);this.content=new e(this);var n=this.options;this._renderForm(n);this.tree.finalize();if(n.errors){var o=this.tree.matchResponse(n.errors);for(var m in o){this.highlight(m,o[m],true)}}this.on("refresh",function(){l.deferred.reset();l.deferred.execute(function(){l._updateModel()})});this.on("reset",function(){for(var p in this.element_list){this.element_list[p].reset()}});this.trigger("refresh")},_renderForm:function(u){var t=this;this.message=new k.Message();var q=new k.ButtonIcon({icon:"fa-info-circle",title:(!this.workflow&&"Requirements")||null,tooltip:"Display tool requirements",onclick:function(){if(!this.visible){this.visible=true;t.message.update({persistent:true,message:c.requirements(u),status:"info"})}else{this.visible=false;t.message.update({message:""})}}});if(!u.requirements||u.requirements.length==0){q.$el.hide()}var m=new k.ButtonMenu({icon:"fa-cubes",title:(!this.workflow&&"Versions")||null,tooltip:"Select another tool version"});if(u.versions&&u.versions.length>1){for(var o in u.versions){var r=u.versions[o];if(r!=u.version){m.addMenu({title:"Switch to "+r,version:r,icon:"fa-cube",onclick:function(){u.id=u.id.replace(u.version,this.version);u.version=this.version;t.deferred.reset();t.deferred.execute(function(){t._buildModel()})}})}}}else{m.$el.hide()}var p=new k.ButtonMenu({icon:"fa-caret-down",title:(!this.workflow&&"Options")||null,tooltip:"View available options"});if(u.biostar_url){p.addMenu({icon:"fa-question-circle",title:"Question?",tooltip:"Ask a question about this tool (Biostar)",onclick:function(){window.open(u.biostar_url+"/p/new/post/")}});p.addMenu({icon:"fa-search",title:"Search",tooltip:"Search help for this tool (Biostar)",onclick:function(){window.open(u.biostar_url+"/t/"+u.id+"/")}})}p.addMenu({icon:"fa-share",title:"Share",tooltip:"Share this tool",onclick:function(){prompt("Copy to clipboard: Ctrl+C, Enter",window.location.origin+galaxy_config.root+"root?tool_id="+u.id)}});if(this.is_admin){p.addMenu({icon:"fa-download",title:"Download",tooltip:"Download this tool",onclick:function(){window.location.href=galaxy_config.root+"api/tools/"+u.id+"/download"}})}this.section=new j.View(t,{inputs:u.inputs,cls:"ui-table-plain"});if(this.incompatible){this.$el.hide();$("#tool-form-classic").show();return}this.portlet=new f.View({icon:"fa-wrench",title:"<b>"+u.name+"</b> "+u.description+" (Galaxy Tool Version "+u.version+")",cls:"ui-portlet-slim",operations:{requirements:q,menu:p,versions:m},buttons:this.buttons});if(this.options.workflow){this.portlet.$content.css("padding","0px")}this.portlet.append(this.message.$el,true);this.portlet.append(this.section.$el);this.$el.empty();this.$el.append(this.portlet.$el);if(u.help!=""){this.$el.append(c.help(u.help))}if(u.citations){var s=$("<div/>");var l=new i.ToolCitationCollection();l.tool_id=u.id;var n=new a.CitationListView({el:s,collection:l});n.render();l.fetch();this.$el.append(s)}if(u.message){this.message.update({persistent:true,status:"warning",message:u.message})}}})}); \ No newline at end of file diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/packed/mvc/tools/tools-form-workflow.js --- a/static/scripts/packed/mvc/tools/tools-form-workflow.js +++ b/static/scripts/packed/mvc/tools/tools-form-workflow.js @@ -1,1 +1,1 @@ -define(["utils/utils","mvc/tools/tools-form-base"],function(b,a){var c=a.extend({initialize:function(e){this.node=workflow.active_node;if(!this.node){console.debug("FAILED - tools-form-workflow:initialize() - Node not found in workflow.");return}this.workflow=true;this.options=e;var d=this;b.get({url:galaxy_config.root+"api/datatypes",cache:true,success:function(f){d.datatypes=f;d._makeSections(e.inputs);a.prototype.initialize.call(d,e)}})},_makeSections:function(d){d[b.uuid()]={label:"Edit Step Attributes",type:"section",inputs:[{label:"Annotation / Notes",name:"annotation",type:"text",area:true,help:"Add an annotation or notes to this step; annotations are available when a workflow is viewed.",value:this.node.annotation}]};this.post_job_actions=this.node.post_job_actions;for(var e in this.node.output_terminals){d[b.uuid()]=this._makeSection(e)}},_makeSection:function(h){var g=[];for(key in this.datatypes){g.push({0:this.datatypes[key],1:this.datatypes[key]})}g.sort(function(j,i){return j.label>i.label?1:j.label<i.label?-1:0});g.unshift({0:"Sequences",1:"Sequences"});g.unshift({0:"Roadmaps",1:"Roadmaps"});g.unshift({0:"Leave unchanged",1:"None"});var f={label:"Edit Step Action: '"+h+"'",type:"section",inputs:[{action:"RenameDatasetAction",argument:"newname",label:"Rename dataset",type:"text",value:"",ignore:"",help:'This action will rename the result dataset. Click <a href="https://wiki.galaxyproject.org/Learn/AdvancedWorkflow/Variables">here</a> for more information.'},{action:"ChangeDatatypeAction",argument:"newtype",label:"Change datatype",type:"select",ignore:"None",options:g,help:"This action will change the datatype of the output to the indicated value."},{action:"TagDatasetAction",argument:"tags",label:"Tags",type:"text",value:"",ignore:"",help:"This action will set tags for the dataset."},{label:"Assign columns",type:"section",inputs:[{action:"ColumnSetAction",argument:"chromCol",label:"Chrom column",type:"text",value:"",ignore:""},{action:"ColumnSetAction",argument:"startCol",label:"Start column",type:"text",value:"",ignore:""},{action:"ColumnSetAction",argument:"endCol",label:"End column",type:"text",value:"",ignore:""},{action:"ColumnSetAction",argument:"strandCol",label:"Strand column",type:"text",value:"",ignore:""},{action:"ColumnSetAction",argument:"nameCol",label:"Name column",type:"text",value:"",ignore:""}],help:"This action will set column assignments in the output dataset. Blank fields are ignored."},{action:"EmailAction",label:"Email notification",type:"boolean",value:"false",ignore:"false",help:"This action will send an email notifying you when the job is done."},{action:"DeleteIntermediatesAction",label:"Delete non-outputs",type:"boolean",value:"false",ignore:"false",help:"All non-output steps of this workflow will have datasets deleted if they are no longer being used as job inputs when the job this action is attached to is finished. You *must* be using workflow outputs (the snowflake) in your workflow for this to have any effect."}]};var d=this;function e(j){for(var l in j){var k=j[l];if(k.action){k.name="pja__"+h+"__"+k.action;if(k.argument){k.name+="__"+k.argument}var m=d.post_job_actions[k.action+h];if(m){if(k.argument){k.value=m.action_arguments&&m.action_arguments[k.argument]||k.value}else{k.value="true"}}}else{if(k.inputs){e(k.inputs)}}}}e(f.inputs);return f},_buildModel:function(){},_updateModel:function(){var d=this;var e=this.tree.finalize();console.debug("tools-form-workflow::_refreshForm() - Refreshing states.");console.debug(e);var g=this.deferred.register();var f=galaxy_config.root+"workflow/editor_form_post?tool_id="+this.options.id;b.request({type:"GET",url:f,data:e,success:function(h){d.node.update_field_data(h,true);d.deferred.done(g);console.debug("tools-form::_refreshForm() - States refreshed.");console.debug(h)},error:function(h){d.deferred.done(g);console.debug("tools-form::_refreshForm() - Refresh request failed.");console.debug(h)}})}});return{View:c}}); \ No newline at end of file +define(["utils/utils","mvc/tools/tools-form-base"],function(b,a){var c=a.extend({initialize:function(e){this.node=workflow.active_node;if(!this.node){console.debug("FAILED - tools-form-workflow:initialize() - Node not found in workflow.");return}this.workflow=true;this.options=e;b.deepeach(e.inputs,function(f){if(f.type){f.optional=(["data","data_hidden","hidden","drill_down"]).indexOf(f.type)==-1}});b.deepeach(e.inputs,function(f){if(f.type){if(f.type=="conditional"){f.test_param.optional=false}}});var d=this;b.get({url:galaxy_config.root+"api/datatypes",cache:true,success:function(f){d.datatypes=f;d._makeSections(e.inputs);a.prototype.initialize.call(d,e)}})},_makeSections:function(d){d[b.uuid()]={label:"Edit Step Attributes",type:"section",expand:this.node.annotation,inputs:[{label:"Annotation / Notes",name:"annotation",type:"text",area:true,help:"Add an annotation or notes to this step; annotations are available when a workflow is viewed.",value:this.node.annotation}]};this.post_job_actions=this.node.post_job_actions;for(var e in this.node.output_terminals){d[b.uuid()]=this._makeSection(e)}},_makeSection:function(h){var g=[];for(key in this.datatypes){g.push({0:this.datatypes[key],1:this.datatypes[key]})}g.sort(function(j,i){return j.label>i.label?1:j.label<i.label?-1:0});g.unshift({0:"Sequences",1:"Sequences"});g.unshift({0:"Roadmaps",1:"Roadmaps"});g.unshift({0:"Leave unchanged",1:"None"});var f={label:"Edit Step Action: '"+h+"'",type:"section",inputs:[{action:"RenameDatasetAction",argument:"newname",label:"Rename dataset",type:"text",value:"",ignore:"",help:'This action will rename the result dataset. Click <a href="https://wiki.galaxyproject.org/Learn/AdvancedWorkflow/Variables">here</a> for more information.'},{action:"ChangeDatatypeAction",argument:"newtype",label:"Change datatype",type:"select",ignore:"None",options:g,help:"This action will change the datatype of the output to the indicated value."},{action:"TagDatasetAction",argument:"tags",label:"Tags",type:"text",value:"",ignore:"",help:"This action will set tags for the dataset."},{label:"Assign columns",type:"section",inputs:[{action:"ColumnSetAction",argument:"chromCol",label:"Chrom column",type:"text",value:"",ignore:""},{action:"ColumnSetAction",argument:"startCol",label:"Start column",type:"text",value:"",ignore:""},{action:"ColumnSetAction",argument:"endCol",label:"End column",type:"text",value:"",ignore:""},{action:"ColumnSetAction",argument:"strandCol",label:"Strand column",type:"text",value:"",ignore:""},{action:"ColumnSetAction",argument:"nameCol",label:"Name column",type:"text",value:"",ignore:""}],help:"This action will set column assignments in the output dataset. Blank fields are ignored."},{action:"EmailAction",label:"Email notification",type:"boolean",value:"false",ignore:"false",help:"This action will send an email notifying you when the job is done.",payload:{host:window.location.host}},{action:"DeleteIntermediatesAction",label:"Delete non-outputs",type:"boolean",value:"false",ignore:"false",help:"All non-output steps of this workflow will have datasets deleted if they are no longer being used as job inputs when the job this action is attached to is finished. You *must* be using workflow outputs (the snowflake) in your workflow for this to have any effect."}]};var d=this;function e(n,o){o=o||[];o.push(n);for(var m in n.inputs){var k=n.inputs[m];if(k.action){k.name="pja__"+h+"__"+k.action;if(k.argument){k.name+="__"+k.argument}if(k.payload){for(var s in k.payload){var q=k.payload[s];k.payload[k.name+"__"+s]=q;delete q}}var r=d.post_job_actions[k.action+h];if(r){for(var l in o){o[l].expand=true}if(k.argument){k.value=r.action_arguments&&r.action_arguments[k.argument]||k.value}else{k.value="true"}}}if(k.inputs){e(k,o.slice(0))}}}e(f);return f},_buildModel:function(){Galaxy.modal.show({title:"Coming soon...",body:"This feature has not been implemented yet.",buttons:{Close:function(){Galaxy.modal.hide()}}})},_updateModel:function(){var d=this;var e=this.tree.finalize();console.debug("tools-form-workflow::_refreshForm() - Refreshing states.");console.debug(e);var g=this.deferred.register();var f=galaxy_config.root+"workflow/editor_form_post?tool_id="+this.options.id;b.request({type:"GET",url:f,data:e,success:function(h){d.node.update_field_data(h);d.deferred.done(g);console.debug("tools-form::_refreshForm() - States refreshed.");console.debug(h)},error:function(h){d.deferred.done(g);console.debug("tools-form::_refreshForm() - Refresh request failed.");console.debug(h)}})}});return{View:c}}); \ No newline at end of file diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/packed/mvc/tools/tools-input.js --- a/static/scripts/packed/mvc/tools/tools-input.js +++ b/static/scripts/packed/mvc/tools/tools-input.js @@ -1,1 +1,1 @@ -define([],function(){return Backbone.View.extend({initialize:function(c,b){this.app=c;this.field=b.field;this.setElement(this._template(b));this.$field=this.$el.find(".ui-table-form-field");this.$title_optional=this.$el.find(".ui-table-form-title-optional");this.$error_text=this.$el.find(".ui-table-form-error-text");this.$error=this.$el.find(".ui-table-form-error");this.$field.prepend(this.field.$el);this.field.skip=false;this._refresh();var a=this;this.$title_optional.on("click",function(){a.field.skip=!a.field.skip;a._refresh()})},error:function(a){this.$error_text.html(a);this.$error.fadeIn();this.$el.addClass("ui-error")},reset:function(){this.$error.hide();this.$el.removeClass("ui-error")},_refresh:function(){if(!this.field.skip){this.$field.fadeIn("fast");this.$title_optional.html("Disable");this.app.trigger("refresh")}else{this.$field.hide();this.$title_optional.html("Enable")}},_template:function(a){var b='<div class="ui-table-form-element"><div class="ui-table-form-error ui-error"><span class="fa fa-arrow-down"/><span class="ui-table-form-error-text"/></div><div class="ui-table-form-title-strong">';if(a.optional){b+="Optional: "+a.label+'<span> [<span class="ui-table-form-title-optional"/>]</span>'}else{b+=a.label}b+='</div><div class="ui-table-form-field">';if(a.help){b+='<div class="ui-table-form-info">'+a.help+"</div>"}b+="</div></div>";return b}})}); \ No newline at end of file +define([],function(){return Backbone.View.extend({initialize:function(d,c){this.app=d;this.field=c.field;this.defaultvalue=c.defaultvalue;this.setElement(this._template(c));this.$field=this.$el.find(".ui-table-form-field");this.$title_optional=this.$el.find(".ui-table-form-title-optional");this.$error_text=this.$el.find(".ui-table-form-error-text");this.$error=this.$el.find(".ui-table-form-error");this.$field.prepend(this.field.$el);this.field.skip=false;var b=this.field.value&&this.field.value();this.field.skip=Boolean(c.optional&&((this.field.validate&&!this.field.validate())||!b||(b==this.defaultvalue)||(Number(b)==Number(this.defaultvalue))||(JSON.stringify(b)==JSON.stringify(this.defaultvalue))));this._refresh();var a=this;this.$title_optional.on("click",function(){a.field.skip=!a.field.skip;a._refresh()})},error:function(a){this.$error_text.html(a);this.$error.fadeIn();this.$el.addClass("ui-error")},reset:function(){this.$error.hide();this.$el.removeClass("ui-error")},_refresh:function(){if(!this.field.skip){this.$field.fadeIn("fast");this.$title_optional.html("Disable")}else{this.$field.hide();this.$title_optional.html("Enable");this.field.value&&this.field.value(this.defaultvalue)}this.app.trigger("refresh")},_template:function(a){var b='<div class="ui-table-form-element"><div class="ui-table-form-error ui-error"><span class="fa fa-arrow-down"/><span class="ui-table-form-error-text"/></div><div class="ui-table-form-title-strong">';if(a.optional){b+=a.label+'<span> [<span class="ui-table-form-title-optional"/>]</span>'}else{b+=a.label}b+='</div><div class="ui-table-form-field">';if(a.help){b+='<div class="ui-table-form-info">'+a.help+"</div>"}b+="</div></div>";return b}})}); \ No newline at end of file diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/packed/mvc/tools/tools-section.js --- a/static/scripts/packed/mvc/tools/tools-section.js +++ b/static/scripts/packed/mvc/tools/tools-section.js @@ -1,1 +1,1 @@ -define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-misc","mvc/ui/ui-portlet","mvc/tools/tools-repeat","mvc/tools/tools-select-content","mvc/tools/tools-input"],function(e,b,h,d,c,a,f){var g=Backbone.View.extend({initialize:function(j,i){this.app=j;this.inputs=i.inputs;i.cls_tr="section-row";this.table=new b.View(i);this.setElement(this.table.$el);this.render()},render:function(){this.table.delAll();for(var j in this.inputs){this.add(this.inputs[j])}},add:function(k){var j=this;var i=jQuery.extend(true,{},k);i.id=k.id=e.uuid();this.app.input_list[i.id]=i;var l=i.type;switch(l){case"conditional":this._addConditional(i);break;case"repeat":this._addRepeat(i);break;case"section":this._addSection(i);break;default:this._addRow(i)}},_addConditional:function(j){var k=this;j.test_param.id=j.id;var n=this._addRow(j.test_param);n.options.onchange=function(w){var v=k.app.tree.matchCase(j,w);for(var u in j.cases){var q=j.cases[u];var t=j.id+"-section-"+u;var p=k.table.get(t);var s=false;for(var r in q.inputs){if(!q.inputs[r].hidden){s=true;break}}if(u==v&&s){p.fadeIn("fast")}else{p.hide()}}k.app.trigger("refresh")};for(var m in j.cases){var l=j.id+"-section-"+m;var o=new g(this.app,{inputs:j.cases[m].inputs,cls:"ui-table-plain"});o.$el.addClass("ui-table-form-section");this.table.add(o.$el);this.table.append(l)}n.trigger("change")},_addRepeat:function(p){var s=this;var q=0;function n(i,u){var t=p.id+"-section-"+(q++);var v=null;if(u){v=function(){l.del(t);l.retitle(p.title);s.app.trigger("refresh")}}var w=new g(s.app,{inputs:i,cls:"ui-table-plain"});l.add({id:t,title:p.title,$el:w.$el,ondel:v});l.retitle(p.title)}var l=new c.View({title_new:p.title,max:p.max,onnew:function(){n(p.inputs,true);s.app.trigger("refresh")}});var j=p.min;var r=_.size(p.cache);for(var m=0;m<Math.max(r,j);m++){var o=null;if(m<r){o=p.cache[m]}else{o=p.inputs}n(o,m>=j)}var k=new f(this.app,{label:p.title,help:p.help,field:l});k.$el.addClass("ui-table-form-section");this.table.add(k.$el);this.table.append(p.id)},_addSection:function(i){var j=this;var n=new g(j.app,{inputs:i.inputs,cls:"ui-table-plain"});var m=new h.ButtonIcon({icon:"fa-eye-slash",tooltip:"Show/hide section",cls:"ui-button-icon-plain"});var l=new d.View({title:i.label,cls:"ui-portlet-section",operations:{button_visible:m}});l.append(n.$el);var k=false;l.$content.hide();l.$header.css("cursor","pointer");l.$header.on("click",function(){if(k){k=false;l.$content.hide();m.setIcon("fa-eye-slash")}else{k=true;l.$content.fadeIn("fast");m.setIcon("fa-eye")}});this.table.add(l.$el);this.table.append(i.id)},_addRow:function(i){var l=i.id;var j=this._createField(i);this.app.field_list[l]=j;var k=new f(this.app,{label:i.label,optional:i.optional,help:i.help,field:j});this.app.element_list[l]=k;this.table.add(k.$el);this.table.append(l);if(i.hidden){this.table.get(l).hide()}return j},_createField:function(i){var j=null;switch(i.type){case"text":j=this._fieldText(i);break;case"select":j=this._fieldSelect(i);break;case"data":j=this._fieldData(i);break;case"data_collection":j=this._fieldData(i);break;case"data_column":i.error_text="Missing columns in referenced dataset.";j=this._fieldSelect(i);break;case"hidden":j=this._fieldHidden(i);break;case"hidden_data":j=this._fieldHidden(i);break;case"integer":j=this._fieldSlider(i);break;case"float":j=this._fieldSlider(i);break;case"boolean":j=this._fieldBoolean(i);break;case"genomebuild":i.searchable=true;j=this._fieldSelect(i);break;case"drill_down":j=this._fieldDrilldown(i);break;case"baseurl":j=this._fieldHidden(i);break;default:this.app.incompatible=true;if(i.options){j=this._fieldSelect(i)}else{j=this._fieldText(i)}console.debug("tools-form::_addRow() : Auto matched field type ("+i.type+").")}if(i.value!==undefined){j.value(i.value)}return j},_fieldData:function(i){if(this.app.workflow){var k=e.textify(i.extensions.toString());i.info="Data input '"+i.name+"' ("+k+")";return this._fieldHidden(i)}var j=this;return new a.View(this.app,{id:"field-"+i.id,extensions:i.extensions,multiple:i.multiple,type:i.type,data:i.options,onchange:function(){j.app.trigger("refresh")}})},_fieldSelect:function(j){if(this.app.workflow&&j.is_dynamic){if(!e.validate(j.value)){j.value=""}return this._fieldText(j)}var l=[];for(var m in j.options){var n=j.options[m];l.push({label:n[0],value:n[1]})}var o=h.Select;switch(j.display){case"checkboxes":o=h.Checkbox;break;case"radio":o=h.Radio;break}var k=this;return new o.View({id:"field-"+j.id,data:l,error_text:j.error_text||"No options available",multiple:j.multiple,searchable:j.searchable,onchange:function(){k.app.trigger("refresh")}})},_fieldDrilldown:function(i){var j=this;return new h.Drilldown.View({id:"field-"+i.id,data:i.options,display:i.display,onchange:function(){j.app.trigger("refresh")}})},_fieldText:function(i){var j=this;return new h.Input({id:"field-"+i.id,area:i.area,onchange:function(){j.app.trigger("refresh")}})},_fieldSlider:function(i){var j=this;return new h.Slider.View({id:"field-"+i.id,precise:i.type=="float",min:i.min,max:i.max,onchange:function(){j.app.trigger("refresh")}})},_fieldHidden:function(i){return new h.Hidden({id:"field-"+i.id,info:i.info})},_fieldBoolean:function(i){var j=this;return new h.RadioButton.View({id:"field-"+i.id,data:[{label:"Yes",value:"true"},{label:"No",value:"false"}],onchange:function(){j.app.trigger("refresh")}})}});return{View:g}}); \ No newline at end of file +define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-misc","mvc/ui/ui-portlet","mvc/tools/tools-repeat","mvc/tools/tools-select-content","mvc/tools/tools-input"],function(e,b,h,d,c,a,f){var g=Backbone.View.extend({initialize:function(j,i){this.app=j;this.inputs=i.inputs;i.cls_tr="section-row";this.table=new b.View(i);this.setElement(this.table.$el);this.render()},render:function(){this.table.delAll();for(var j in this.inputs){this.add(this.inputs[j])}},add:function(k){var j=this;var i=jQuery.extend(true,{},k);i.id=k.id=e.uuid();this.app.input_list[i.id]=i;var l=i.type;switch(l){case"conditional":this._addConditional(i);break;case"repeat":this._addRepeat(i);break;case"section":this._addSection(i);break;default:this._addRow(i)}},_addConditional:function(j){var k=this;j.test_param.id=j.id;var n=this._addRow(j.test_param);n.options.onchange=function(w){var v=k.app.tree.matchCase(j,w);for(var u in j.cases){var q=j.cases[u];var t=j.id+"-section-"+u;var p=k.table.get(t);var s=false;for(var r in q.inputs){if(!q.inputs[r].hidden){s=true;break}}if(u==v&&s){p.fadeIn("fast")}else{p.hide()}}k.app.trigger("refresh")};for(var m in j.cases){var l=j.id+"-section-"+m;var o=new g(this.app,{inputs:j.cases[m].inputs,cls:"ui-table-plain"});o.$el.addClass("ui-table-form-section");this.table.add(o.$el);this.table.append(l)}n.trigger("change")},_addRepeat:function(p){var s=this;var q=0;function n(i,u){var t=p.id+"-section-"+(q++);var v=null;if(u){v=function(){l.del(t);l.retitle(p.title);s.app.trigger("refresh")}}var w=new g(s.app,{inputs:i,cls:"ui-table-plain"});l.add({id:t,title:p.title,$el:w.$el,ondel:v});l.retitle(p.title)}var l=new c.View({title_new:p.title,max:p.max,onnew:function(){n(p.inputs,true);s.app.trigger("refresh")}});var j=p.min;var r=_.size(p.cache);for(var m=0;m<Math.max(r,j);m++){var o=null;if(m<r){o=p.cache[m]}else{o=p.inputs}n(o,m>=j)}var k=new f(this.app,{label:p.title,help:p.help,field:l});k.$el.addClass("ui-table-form-section");this.table.add(k.$el);this.table.append(p.id)},_addSection:function(i){var j=this;var n=new g(j.app,{inputs:i.inputs,cls:"ui-table-plain"});var m=new h.ButtonIcon({icon:"fa-eye-slash",tooltip:"Show/hide section",cls:"ui-button-icon-plain"});var l=new d.View({title:i.label,cls:"ui-portlet-section",operations:{button_visible:m}});l.append(n.$el);var k=false;l.$content.hide();l.$header.css("cursor","pointer");l.$header.on("click",function(){if(k){k=false;l.$content.hide();m.setIcon("fa-eye-slash")}else{k=true;l.$content.fadeIn("fast");m.setIcon("fa-eye")}});if(i.expand){l.$header.trigger("click")}this.table.add(l.$el);this.table.append(i.id)},_addRow:function(i){var l=i.id;var j=this._createField(i);this.app.field_list[l]=j;var k=new f(this.app,{label:i.label,defaultvalue:i.defaultvalue,optional:i.optional,help:i.help,field:j});this.app.element_list[l]=k;this.table.add(k.$el);this.table.append(l);if(i.hidden){this.table.get(l).hide()}return j},_createField:function(i){var j=null;switch(i.type){case"text":j=this._fieldText(i);break;case"select":j=this._fieldSelect(i);break;case"data":j=this._fieldData(i);break;case"data_collection":j=this._fieldData(i);break;case"data_column":i.error_text="Missing columns in referenced dataset.";j=this._fieldSelect(i);break;case"hidden":j=this._fieldHidden(i);break;case"hidden_data":j=this._fieldHidden(i);break;case"integer":j=this._fieldSlider(i);break;case"float":j=this._fieldSlider(i);break;case"boolean":j=this._fieldBoolean(i);break;case"genomebuild":i.searchable=true;j=this._fieldSelect(i);break;case"drill_down":j=this._fieldDrilldown(i);break;case"baseurl":j=this._fieldHidden(i);break;default:this.app.incompatible=true;if(i.options){j=this._fieldSelect(i)}else{j=this._fieldText(i)}console.debug("tools-form::_addRow() : Auto matched field type ("+i.type+").")}if(i.value!==undefined){j.value(i.value)}return j},_fieldData:function(i){if(this.app.workflow){var k=e.textify(i.extensions.toString());i.info="Data input '"+i.name+"' ("+k+")";return this._fieldHidden(i)}var j=this;return new a.View(this.app,{id:"field-"+i.id,extensions:i.extensions,multiple:i.multiple,type:i.type,data:i.options,onchange:function(){j.app.trigger("refresh")}})},_fieldSelect:function(j){if(this.app.workflow&&j.is_dynamic){if(!e.validate(j.value)){j.value=""}return this._fieldText(j)}var l=[];for(var m in j.options){var n=j.options[m];l.push({label:n[0],value:n[1]})}var o=h.Select;switch(j.display){case"checkboxes":o=h.Checkbox;break;case"radio":o=h.Radio;break}var k=this;return new o.View({id:"field-"+j.id,data:l,error_text:j.error_text||"No options available",multiple:j.multiple,searchable:j.searchable,onchange:function(){k.app.trigger("refresh")}})},_fieldDrilldown:function(i){var j=this;return new h.Drilldown.View({id:"field-"+i.id,data:i.options,display:i.display,onchange:function(){j.app.trigger("refresh")}})},_fieldText:function(i){var j=this;return new h.Input({id:"field-"+i.id,area:i.area,onchange:function(){j.app.trigger("refresh")}})},_fieldSlider:function(i){var j=this;return new h.Slider.View({id:"field-"+i.id,precise:i.type=="float",min:i.min,max:i.max,onchange:function(){j.app.trigger("refresh")}})},_fieldHidden:function(i){return new h.Hidden({id:"field-"+i.id,info:i.info})},_fieldBoolean:function(i){var j=this;return new h.RadioButton.View({id:"field-"+i.id,data:[{label:"Yes",value:"true"},{label:"No",value:"false"}],onchange:function(){j.app.trigger("refresh")}})}});return{View:g}}); \ No newline at end of file diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/packed/mvc/tools/tools-template.js --- a/static/scripts/packed/mvc/tools/tools-template.js +++ b/static/scripts/packed/mvc/tools/tools-template.js @@ -1,1 +1,1 @@ -define([],function(){return{help:function(a){return'<div class="toolHelp"><div class="toolHelpBody">'+a+"</div></div>"},success:function(c){if(!c.jobs||!c.jobs.length){console.debug("tools-template::success() - Failed jobs.");return}var a=c.jobs.length;var d="";if(a==1){d="1 job has"}else{d=a+" jobs have"}var b='<div class="donemessagelarge"><p>'+d+" been successfully added to the queue - resulting in the following datasets:</p>";for(var e in c.outputs){b+='<p style="padding: 10px 20px;"><b>'+(parseInt(e)+1)+": "+c.outputs[e].name+"</b></p>"}b+="<p>You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from 'running' to 'finished' if completed successfully or 'error' if problems were encountered.</p></div>";return b},error:function(a){return'<div><p>The server could not complete the request. Please contact the Galaxy Team if this error persists.</p><textarea class="ui-textarea" disabled style="color: black;" rows="6">'+JSON.stringify(a,undefined,4)+"</textarea></div>"},batchMode:function(){return'<div class="ui-table-form-info"><i class="fa fa-sitemap" style="font-size: 1.2em; padding: 2px 5px;"/>This is a batch mode input field. A separate job will be triggered for each dataset.</div>'},requirements:function(a){var d="This tool requires ";for(var b in a.requirements){var c=a.requirements[b];d+=c.name;if(c.version){d+=" (Version "+c.version+")"}if(b<a.requirements.length-2){d+=", "}if(b==a.requirements.length-2){d+=" and "}}return d+'. Click <a target="_blank" href="https://wiki.galaxyproject.org/Tools/Requirements">here</a> for more information.'}}}); \ No newline at end of file +define([],function(){return{help:function(a){return'<div class="toolHelp" style="overflow: auto;"><div class="toolHelpBody">'+a+"</div></div>"},success:function(c){if(!c.jobs||!c.jobs.length){console.debug("tools-template::success() - Failed jobs.");return}var a=c.jobs.length;var d="";if(a==1){d="1 job has"}else{d=a+" jobs have"}var b='<div class="donemessagelarge"><p>'+d+" been successfully added to the queue - resulting in the following datasets:</p>";for(var e in c.outputs){b+='<p style="padding: 10px 20px;"><b>'+(parseInt(e)+1)+": "+c.outputs[e].name+"</b></p>"}b+="<p>You can check the status of queued jobs and view the resulting data by refreshing the History pane. When the job has been run the status will change from 'running' to 'finished' if completed successfully or 'error' if problems were encountered.</p></div>";return b},error:function(a){return'<div><p>The server could not complete the request. Please contact the Galaxy Team if this error persists.</p><textarea class="ui-textarea" disabled style="color: black;" rows="6">'+JSON.stringify(a,undefined,4)+"</textarea></div>"},batchMode:function(){return'<div class="ui-table-form-info"><i class="fa fa-sitemap" style="font-size: 1.2em; padding: 2px 5px;"/>This is a batch mode input field. A separate job will be triggered for each dataset.</div>'},requirements:function(a){var d="This tool requires ";for(var b in a.requirements){var c=a.requirements[b];d+=c.name;if(c.version){d+=" (Version "+c.version+")"}if(b<a.requirements.length-2){d+=", "}if(b==a.requirements.length-2){d+=" and "}}return d+'. Click <a target="_blank" href="https://wiki.galaxyproject.org/Tools/Requirements">here</a> for more information.'}}}); \ No newline at end of file diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/packed/mvc/tools/tools-tree.js --- a/static/scripts/packed/mvc/tools/tools-tree.js +++ b/static/scripts/packed/mvc/tools/tools-tree.js @@ -1,1 +1,1 @@ -define(["utils/utils"],function(a){return Backbone.Model.extend({initialize:function(b){this.app=b},finalize:function(g){var b=this;this.map_dict={};if(!this.app.section){return{}}g=g||{};var f={};var e={};this._iterate(this.app.section.$el,e);function d(j,i,h){f[j]=h;b.map_dict[j]=i}function c(o,r){for(var m in r){var k=r[m];if(k.input){var t=k.input;var n=o;if(o!=""){n+="|"}n+=t.name;switch(t.type){case"repeat":var j="section-";var w=[];var q=null;for(var v in k){var p=v.indexOf(j);if(p!=-1){p+=j.length;w.push(parseInt(v.substr(p)));if(!q){q=v.substr(0,p)}}}w.sort(function(x,i){return x-i});var m=0;for(var l in w){c(n+"_"+m++,k[q+w[l]])}break;case"conditional":var u=b.app.field_list[t.id].value();if(g[t.test_param.type]){u=g[t.test_param.type](u)}d(n+"|"+t.test_param.name,t.id,u);var h=b.matchCase(t,u);if(h!=-1){c(n,r[t.id+"-section-"+h])}break;case"section":c("",k);break;default:var s=b.app.field_list[t.id];if(s&&s.value){var u=s.value();if(g[t.type]){u=g[t.type](u)}if(!s.skip){if(s.validate&&!s.validate(u)){u=null}if(t.ignore===undefined||(u&&t.ignore!=u)){d(n,t.id,u)}}}}}}}c("",e);return f},match:function(b){return this.map_dict&&this.map_dict[b]},matchCase:function(b,d){if(b.test_param.type=="boolean"){if(d=="true"){d=b.test_param.truevalue||"true"}else{d=b.test_param.falsevalue||"false"}}for(var c in b.cases){if(b.cases[c].value==d){return c}}return -1},matchModel:function(d,f){var b={};var c=this;function e(g,p){for(var m in p){var k=p[m];var n=k.name;if(g!=""){n=g+"|"+n}switch(k.type){case"repeat":for(var l in k.cache){e(n+"_"+l,k.cache[l])}break;case"conditional":var q=k.test_param&&k.test_param.value;var h=c.matchCase(k,q);if(h!=-1){e(n,k.cases[h].inputs)}break;default:var o=c.map_dict[n];if(o){f(o,k)}}}}e("",d.inputs);return b},matchResponse:function(d){var b={};var c=this;function e(l,j){if(typeof j==="string"){var g=c.map_dict[l];if(g){b[g]=j}}else{for(var h in j){var f=h;if(l!==""){var k="|";if(j instanceof Array){k="_"}f=l+k+f}e(f,j[h])}}}e("",d);return b},_iterate:function(d,e){var b=this;var c=$(d).children();c.each(function(){var h=this;var g=$(h).attr("id");if($(h).hasClass("section-row")){e[g]={};var f=b.app.input_list[g];if(f){e[g]={input:f}}b._iterate(h,e[g])}else{b._iterate(h,e)}})}})}); \ No newline at end of file +define(["utils/utils"],function(a){return Backbone.Model.extend({initialize:function(b){this.app=b},finalize:function(g){var b=this;this.map_dict={};if(!this.app.section){return{}}g=g||{};var f={};var e={};this._iterate(this.app.section.$el,e);function d(j,i,h){f[j]=h;b.map_dict[j]=i}function c(p,s){for(var n in s){var k=s[n];if(k.input){var u=k.input;var o=p;if(p!=""){o+="|"}o+=u.name;switch(u.type){case"repeat":var j="section-";var x=[];var r=null;for(var w in k){var q=w.indexOf(j);if(q!=-1){q+=j.length;x.push(parseInt(w.substr(q)));if(!r){r=w.substr(0,q)}}}x.sort(function(y,i){return y-i});var n=0;for(var l in x){c(o+"_"+n++,k[r+x[l]])}break;case"conditional":var v=b.app.field_list[u.id].value();if(g[u.test_param.type]){v=g[u.test_param.type](v)}d(o+"|"+u.test_param.name,u.id,v);var h=b.matchCase(u,v);if(h!=-1){c(o,s[u.id+"-section-"+h])}break;case"section":c("",k);break;default:var t=b.app.field_list[u.id];if(t&&t.value){var v=t.value();if(g[u.type]){v=g[u.type](v)}if(!t.skip||b.app.workflow){if(t.validate&&!t.validate()){v=null}if(u.ignore===undefined||(v!==null&&u.ignore!=v)){d(o,u.id,v);if(u.payload){for(var m in u.payload){d(m,u.id,u.payload[m])}}}}}}}}}c("",e);return f},match:function(b){return this.map_dict&&this.map_dict[b]},matchCase:function(b,d){if(b.test_param.type=="boolean"){if(d=="true"){d=b.test_param.truevalue||"true"}else{d=b.test_param.falsevalue||"false"}}for(var c in b.cases){if(b.cases[c].value==d){return c}}return -1},matchModel:function(d,f){var b={};var c=this;function e(g,p){for(var m in p){var k=p[m];var n=k.name;if(g!=""){n=g+"|"+n}switch(k.type){case"repeat":for(var l in k.cache){e(n+"_"+l,k.cache[l])}break;case"conditional":var q=k.test_param&&k.test_param.value;var h=c.matchCase(k,q);if(h!=-1){e(n,k.cases[h].inputs)}break;default:var o=c.map_dict[n];if(o){f(o,k)}}}}e("",d.inputs);return b},matchResponse:function(d){var b={};var c=this;function e(l,j){if(typeof j==="string"){var g=c.map_dict[l];if(g){b[g]=j}}else{for(var h in j){var f=h;if(l!==""){var k="|";if(j instanceof Array){k="_"}f=l+k+f}e(f,j[h])}}}e("",d);return b},_iterate:function(d,e){var b=this;var c=$(d).children();c.each(function(){var h=this;var g=$(h).attr("id");if($(h).hasClass("section-row")){e[g]={};var f=b.app.input_list[g];if(f){e[g]={input:f}}b._iterate(h,e[g])}else{b._iterate(h,e)}})}})}); \ No newline at end of file diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/packed/utils/utils.js --- a/static/scripts/packed/utils/utils.js +++ b/static/scripts/packed/utils/utils.js @@ -1,1 +1,1 @@ -define(["libs/underscore"],function(m){function d(n){return $("<div/>").text(n).html()}function l(o){if(!(o instanceof Array)){o=[o]}for(var n in o){if(["None",null,"null",undefined,"undefined"].indexOf(o[n])>-1){return false}}return true}function h(n){var n=n.toString();if(n){n=n.replace(/,/g,", ");var o=n.lastIndexOf(", ");if(o!=-1){n=n.substr(0,o)+" or "+n.substr(o+1)}return n}return""}function e(n){top.__utils__get__=top.__utils__get__||{};if(n.cache&&top.__utils__get__[n.url]){n.success&&n.success(top.__utils__get__[n.url]);console.debug("utils.js::get() - Fetching from cache ["+n.url+"].")}else{i({url:n.url,data:n.data,success:function(o){top.__utils__get__[n.url]=o;n.success&&n.success(o)},error:function(o){n.error&&n.error(o)}})}}function i(o){var n={contentType:"application/json",type:o.type||"GET",data:o.data||{},url:o.url};if(n.type=="GET"||n.type=="DELETE"){if(n.url.indexOf("?")==-1){n.url+="?"}else{n.url+="&"}n.url=n.url+$.param(n.data);n.data=null}else{n.dataType="json";n.url=n.url;n.data=JSON.stringify(n.data)}$.ajax(n).done(function(p){if(typeof p==="string"){try{p=p.replace("Infinity,",'"Infinity",');p=jQuery.parseJSON(p)}catch(q){console.debug(q)}}o.success&&o.success(p)}).fail(function(q){var p=null;try{p=jQuery.parseJSON(q.responseText)}catch(r){p=q.responseText}o.error&&o.error(p,q)})}function j(q,n){var o=$('<div class="'+q+'"></div>');o.appendTo(":eq(0)");var p=o.css(n);o.remove();return p}function g(n){if(!$('link[href^="'+n+'"]').length){$('<link href="'+galaxy_config.root+n+'" rel="stylesheet">').appendTo("head")}}function k(n,o){if(n){return m.defaults(n,o)}else{return o}}function b(o,q){var p="";if(o>=100000000000){o=o/100000000000;p="TB"}else{if(o>=100000000){o=o/100000000;p="GB"}else{if(o>=100000){o=o/100000;p="MB"}else{if(o>=100){o=o/100;p="KB"}else{if(o>0){o=o*10;p="b"}else{return"<strong>-</strong>"}}}}}var n=(Math.round(o)/10);if(q){return n+" "+p}else{return"<strong>"+n+"</strong> "+p}}function a(){return"x"+Math.random().toString(36).substring(2,9)}function c(n){var o=$("<p></p>");o.append(n);return o}function f(){var p=new Date();var n=(p.getHours()<10?"0":"")+p.getHours();var o=(p.getMinutes()<10?"0":"")+p.getMinutes();var q=p.getDate()+"/"+(p.getMonth()+1)+"/"+p.getFullYear()+", "+n+":"+o;return q}return{cssLoadFile:g,cssGetAttribute:j,get:e,merge:k,bytesToString:b,uuid:a,time:f,wrap:c,request:i,sanitize:d,textify:h,validate:l}}); \ No newline at end of file +define(["libs/underscore"],function(m){function n(r,q){for(var o in r){var p=r[o];if(p&&typeof(p)=="object"){q(p);n(p,q)}}}function d(o){return $("<div/>").text(o).html()}function l(p){if(!(p instanceof Array)){p=[p]}for(var o in p){if(["None",null,"null",undefined,"undefined"].indexOf(p[o])>-1){return false}}return true}function h(o){var o=o.toString();if(o){o=o.replace(/,/g,", ");var p=o.lastIndexOf(", ");if(p!=-1){o=o.substr(0,p)+" or "+o.substr(p+1)}return o}return""}function e(o){top.__utils__get__=top.__utils__get__||{};if(o.cache&&top.__utils__get__[o.url]){o.success&&o.success(top.__utils__get__[o.url]);console.debug("utils.js::get() - Fetching from cache ["+o.url+"].")}else{i({url:o.url,data:o.data,success:function(p){top.__utils__get__[o.url]=p;o.success&&o.success(p)},error:function(p){o.error&&o.error(p)}})}}function i(p){var o={contentType:"application/json",type:p.type||"GET",data:p.data||{},url:p.url};if(o.type=="GET"||o.type=="DELETE"){if(o.url.indexOf("?")==-1){o.url+="?"}else{o.url+="&"}o.url=o.url+$.param(o.data);o.data=null}else{o.dataType="json";o.url=o.url;o.data=JSON.stringify(o.data)}$.ajax(o).done(function(q){if(typeof q==="string"){try{q=q.replace("Infinity,",'"Infinity",');q=jQuery.parseJSON(q)}catch(r){console.debug(r)}}p.success&&p.success(q)}).fail(function(r){var q=null;try{q=jQuery.parseJSON(r.responseText)}catch(s){q=r.responseText}p.error&&p.error(q,r)})}function j(r,o){var p=$('<div class="'+r+'"></div>');p.appendTo(":eq(0)");var q=p.css(o);p.remove();return q}function g(o){if(!$('link[href^="'+o+'"]').length){$('<link href="'+galaxy_config.root+o+'" rel="stylesheet">').appendTo("head")}}function k(o,p){if(o){return m.defaults(o,p)}else{return p}}function b(p,r){var q="";if(p>=100000000000){p=p/100000000000;q="TB"}else{if(p>=100000000){p=p/100000000;q="GB"}else{if(p>=100000){p=p/100000;q="MB"}else{if(p>=100){p=p/100;q="KB"}else{if(p>0){p=p*10;q="b"}else{return"<strong>-</strong>"}}}}}var o=(Math.round(p)/10);if(r){return o+" "+q}else{return"<strong>"+o+"</strong> "+q}}function a(){return"x"+Math.random().toString(36).substring(2,9)}function c(o){var p=$("<p></p>");p.append(o);return p}function f(){var q=new Date();var o=(q.getHours()<10?"0":"")+q.getHours();var p=(q.getMinutes()<10?"0":"")+q.getMinutes();var r=q.getDate()+"/"+(q.getMonth()+1)+"/"+q.getFullYear()+", "+o+":"+p;return r}return{cssLoadFile:g,cssGetAttribute:j,get:e,merge:k,bytesToString:b,uuid:a,time:f,wrap:c,request:i,sanitize:d,textify:h,validate:l,deepeach:n}}); \ No newline at end of file diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/scripts/utils/utils.js --- a/static/scripts/utils/utils.js +++ b/static/scripts/utils/utils.js @@ -6,6 +6,18 @@ // dependencies define(["libs/underscore"], function(_) { +/** Traverse through json +*/ +function deepeach(dict, callback) { + for (var i in dict) { + var d = dict[i]; + if (d && typeof(d) == "object") { + callback(d); + deepeach(d, callback); + } + } +} + /** * Sanitize/escape a string * @param{String} content - Content to be sanitized @@ -248,7 +260,8 @@ request: request, sanitize: sanitize, textify: textify, - validate: validate + validate: validate, + deepeach: deepeach }; }); diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/style/blue/base.css --- a/static/style/blue/base.css +++ b/static/style/blue/base.css @@ -1412,7 +1412,7 @@ .ui-button-icon-plain{border:none !important;background:none !important;height:inherit !important;width:inherit !important;padding-right:3px !important} .ui-tabs .ui-tabs-add{font-size:0.8em;margin-right:5px} .ui-tabs .ui-tabs-delete{font-size:0.8em;margin-left:5px;cursor:pointer} -.ui-portlet,.ui-portlet-slim,.ui-portlet-repeat,.ui-portlet-section,.ui-portlet-slim,.ui-portlet-repeat,.ui-portlet-section{border:solid #d6b161 1px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;position:relative;clear:both;width:auto;height:100%}.ui-portlet .portlet-header,.ui-portlet-slim .portlet-header{background:#ebd9b2;border-bottom:solid #d6b161 1px;padding:2px 8px;overflow:visible;float:right;width:100%}.ui-portlet .portlet-header .portlet-title,.ui-portlet-slim .portlet-header .portlet-title{display:inline;vertical-align:middle}.ui-portlet .portlet-header .portlet-title .portlet-title-text,.ui-portlet-slim .portlet-header .portlet-title .portlet-title-text{vertical-align:middle} +.ui-portlet,.ui-portlet-slim,.ui-portlet-repeat,.ui-portlet-section,.ui-portlet-slim,.ui-portlet-repeat,.ui-portlet-section{border:solid #d6b161 1px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;position:relative;clear:both;width:auto;height:100%}.ui-portlet .portlet-header,.ui-portlet-slim .portlet-header{background:#ebd9b2;border-bottom:solid #d6b161 1px;padding:2px 8px;overflow:visible;float:right;width:100%}.ui-portlet .portlet-header .portlet-title,.ui-portlet-slim .portlet-header .portlet-title{display:inline;vertical-align:middle}.ui-portlet .portlet-header .portlet-title .portlet-title-text,.ui-portlet-slim .portlet-header .portlet-title .portlet-title-text{vertical-align:middle;line-height:20px} .ui-portlet .portlet-header .portlet-title .icon,.ui-portlet-slim .portlet-header .portlet-title .icon{font-size:1.2em;vertical-align:middle} .ui-portlet .portlet-buttons,.ui-portlet-slim .portlet-buttons{height:50px;padding:10px} .ui-portlet .portlet-content,.ui-portlet-slim .portlet-content{height:inherit;padding:10px;clear:both}.ui-portlet .portlet-content .content,.ui-portlet-slim .portlet-content .content{padding:10px;height:100%;width:100%}.ui-portlet .portlet-content .content .buttons,.ui-portlet-slim .portlet-content .content .buttons{height:50px;padding:10px} @@ -1765,7 +1765,7 @@ div.toolFormDisabled{border-color:#bfbfbf} div.toolHelp{margin-top:15px;padding:5px} div.toolHelpBody{width:100%} -.toolForm.toolFormInCanvas{border:solid #d6b161 1px;background:#fff}.toolForm.toolFormInCanvas.toolForm-active{border:solid blue 3px;margin:4px} +.toolForm.toolFormInCanvas{border:solid #d6b161 1px;background:#fff}.toolForm.toolFormInCanvas.toolForm-active{border:solid #5f6990 3px;margin:4px} .toolForm.toolFormInCanvas .toolFormTitle{font-size:12px;line-height:1.428571429} div.form,div.toolForm{border:solid #d6b161 1px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px} div.form-title,div.toolFormTitle{padding:5px 10px;background:#ebd9b2;border-bottom:solid #d6b161 1px} diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/style/src/less/base.less --- a/static/style/src/less/base.less +++ b/static/style/src/less/base.less @@ -586,7 +586,7 @@ border: solid @form-border 1px; background: @white; &.toolForm-active { - border: solid blue 3px; + border: solid @brand-primary 3px; margin: 4px; } .toolFormTitle { diff -r f73ddd949744e9f79ca84cecfbfbee4eb923daf4 -r 292aa159ad5b429393c7bd9855bfe28d4eb39cd3 static/style/src/less/ui.less --- a/static/style/src/less/ui.less +++ b/static/style/src/less/ui.less @@ -168,6 +168,7 @@ vertical-align: middle; .portlet-title-text { vertical-align: middle; + line-height: 20px; } .icon { font-size: 1.2em; Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.