5 new changesets in galaxy-central: http://bitbucket.org/galaxy/galaxy-central/changeset/56fc50970fc3/ changeset: r5376:56fc50970fc3 user: dannon date: 2011-04-13 22:35:08 summary: Bugfix for workflow run not reloading history, icon for document stack. affected #: 2 files (803 bytes) Binary file static/images/documents-stack.png has changed --- a/templates/workflow/run_complete.mako Wed Apr 13 16:05:34 2011 -0400 +++ b/templates/workflow/run_complete.mako Wed Apr 13 16:35:08 2011 -0400 @@ -17,4 +17,10 @@ %endfor %endfor </div> -</div> \ No newline at end of file +</div> + +<script type="text/javascript"> + if ( parent.frames && parent.frames.galaxy_history ) { + parent.frames.galaxy_history.location.href="${h.url_for( controller='root', action='history' )}"; + } +</script> http://bitbucket.org/galaxy/galaxy-central/changeset/9a72df515de1/ changeset: r5377:9a72df515de1 user: dannon date: 2011-04-13 22:46:36 summary: Faded document stack icon. affected #: 1 file (587 bytes) Binary file static/images/documents-stack-faded.png has changed http://bitbucket.org/galaxy/galaxy-central/changeset/a946c7e60d1f/ changeset: r5378:a946c7e60d1f user: dannon date: 2011-04-14 01:05:45 summary: Merge. affected #: 1 file (1.8 KB) --- a/static/scripts/trackster.js Wed Apr 13 16:46:36 2011 -0400 +++ b/static/scripts/trackster.js Wed Apr 13 19:05:45 2011 -0400 @@ -741,7 +741,7 @@ this.high = Math.ceil(high); // 10^log10(range / DENSITY) Close approximation for browser window, assuming DENSITY = window width - this.resolution = Math.pow( 10, Math.ceil( Math.log( (this.high - this.low) / 200 ) / Math.LN10 ) ); + this.resolution = Math.pow( 10, Math.ceil( Math.log( (this.high - this.low) / DENSITY ) / Math.LN10 ) ); this.zoom_res = Math.pow( FEATURE_LEVELS, Math.max(0,Math.ceil( Math.log( this.resolution, FEATURE_LEVELS ) / Math.log(FEATURE_LEVELS) ))); // Overview @@ -1016,14 +1016,16 @@ return false; }, /** - * Returns true iff element is in [low, high]; range is inclusive. + * Returns true if (a) element's value is in [low, high] (range is inclusive) + * or (b) if value is non-numeric and hence unfilterable. */ keep: function(element) { if ( !this.applies_to( element ) ) { // No element to filter on. return true; } - return (element[this.index] >= this.low && element[this.index] <= this.high); + var val = parseInt(element[this.index]); + return (isNaN(val) || (val >= this.low && val <= this.high)); }, /** * Update filter's min and max values based on element's values. @@ -1306,11 +1308,22 @@ }); /** - * Tiles for TiledTracks. + * Tiles drawn by tracks. */ -var Tile = function(track, canvas) { - this.track = track; - this.canvas = canvas; +var Tile = function(index, resolution, canvas) { + // Wrap element in div for background + this.index = index; + this.resolution = resolution; + this.canvas = $("<div class='track-tile'/>").append(canvas); +}; + +var SummaryTreeTile = function(index, resolution, canvas, max_val) { + Tile.call(this, index, resolution, canvas); + this.max_val = max_val; +}; + +var FeatureTrackTile = function(index, resolution, canvas) { + Tile.call(this, index, resolution, canvas); }; /** @@ -1669,30 +1682,33 @@ // w_scale units are pixels per base. w_scale = width / range, resolution = this.view.resolution, - parent_element = $("<div style='position: relative;'></div>"); + parent_element = $("<div style='position: relative;'></div>"), + gen_key = function(width, w_scale, tile_index) { + return width + '_' + w_scale + '_' + tile_index; + }; if (!clear_after) { this.content_div.children().remove(); } this.content_div.append( parent_element ); this.max_height = 0; // Index of first tile that overlaps visible region var tile_index = Math.floor( low / resolution / DENSITY ); - // A set of setTimeout() ids used when drawing tiles. Each ID indicates - // a tile has been requested to be drawn or is being drawn. - var draw_tile_dict = {}; + // A list of tiles drawn/retrieved. + var drawn_tiles = []; + var tile_count = 0 while ( ( tile_index * DENSITY * resolution ) < high ) { // Check in cache - var key = width + '_' + w_scale + '_' + tile_index; + var key = gen_key(width, w_scale, tile_index); var cached = this.tile_cache.get(key); var tile_low = tile_index * DENSITY * this.view.resolution; var tile_high = tile_low + DENSITY * this.view.resolution; - // console.log(cached, this.tile_cache); if ( !force && cached ) { + drawn_tiles[drawn_tiles.length] = cached; this.show_tile(cached, parent_element, tile_low, w_scale); } else { - this.delayed_draw(force, key, tile_low, tile_high, tile_index, - resolution, parent_element, w_scale, draw_tile_dict); + this.delayed_draw(force, key, tile_index, resolution, parent_element, w_scale, drawn_tiles); } tile_index += 1; + tile_count++; } // @@ -1700,7 +1716,7 @@ // var track = this; var intervalId = setInterval(function() { - if (obj_length(draw_tile_dict) === 0) { + if (drawn_tiles.length === tile_count) { // All tiles have been drawn. clearInterval(intervalId); @@ -1725,6 +1741,29 @@ } // + // If mode is Histogram and tiles do not share max, redraw tiles as necessary using new max. + // + if (track.mode == "Histogram") { + // Get global max. + var global_max = -1; + for (var i = 0; i < drawn_tiles.length; i++) { + var cur_max = drawn_tiles[i].max_val; + if (cur_max > global_max) { + global_max = cur_max; + } + } + + for (var i = 0; i < drawn_tiles.length; i++) { + if (drawn_tiles[i].max_val !== global_max) { + var tile = drawn_tiles[i]; + tile.canvas.remove(); + track.delayed_draw(true, gen_key(width, w_scale, tile.index), tile.index, + tile.resolution, parent_element, w_scale, [], { max: global_max }); + } + } + } + + // // Update filter attributes, UI. // @@ -1755,6 +1794,19 @@ track.make_name_popup_menu(); } } + + // Store initial canvas in case we need to use it for overview + /* This is completely broken, just saves the first tile it sees + regardless of if it should be the overview + if (!track.initial_canvas && !window.G_vmlCanvasManager) { + track.initial_canvas = $(tile_element).clone(); + var src_ctx = tile_element.get(0).getContext("2d"); + var tgt_ctx = track.initial_canvas.get(0).getContext("2d"); + var data = src_ctx.getImageData(0, 0, src_ctx.canvas.width, src_ctx.canvas.height); + tgt_ctx.putImageData(data, 0, 0); + track.set_overview(); + } + */ } }, 50); @@ -1765,44 +1817,28 @@ this.child_tracks[i].draw(force, clear_after); } }, - delayed_draw: function(force, key, tile_low, tile_high, tile_index, resolution, parent_element, w_scale, draw_tile_dict) { - var track = this; - // Put a 50ms delay on drawing so that if the user scrolls fast, we don't load extra data + delayed_draw: function(force, key, tile_index, resolution, parent_element, w_scale, drawn_tiles, more_tile_data) { + var track = this, + tile_low = tile_index * DENSITY * resolution, + tile_high = tile_low + DENSITY * resolution; + + // Helper method. var draw_and_show_tile = function(id, result, resolution, tile_index, parent_element, w_scale, seq_data) { // DEBUG: this is still called too many times when moving slowly, // console.log( "draw_and_show_tile", resolution, tile_index, w_scale ); - returned_tile = track.draw_tile(result, resolution, tile_index, parent_element, w_scale, seq_data) - - // Wrap element in div for background - var wrapper_element = $("<div class='track-tile'>").prepend(returned_tile); - tile_element = wrapper_element; - - track.tile_cache.set(key, tile_element); - track.show_tile(tile_element, parent_element, tile_low, w_scale); - - // TODO: this should go in a post-draw function. - // Store initial canvas in case we need to use it for overview - /* This is completely broken, just saves the first tile it sees - regardless of if it should be the overview - if (!track.initial_canvas && !window.G_vmlCanvasManager) { - track.initial_canvas = $(tile_element).clone(); - var src_ctx = tile_element.get(0).getContext("2d"); - var tgt_ctx = track.initial_canvas.get(0).getContext("2d"); - var data = src_ctx.getImageData(0, 0, src_ctx.canvas.width, src_ctx.canvas.height); - tgt_ctx.putImageData(data, 0, 0); - track.set_overview(); - } - */ - - delete draw_tile_dict[id]; + var tile = track.draw_tile(result, resolution, tile_index, parent_element, w_scale, seq_data); + track.tile_cache.set(key, tile); + track.show_tile(tile, parent_element, tile_low, w_scale); + drawn_tiles[drawn_tiles.length] = tile; }; + // Put a 50ms delay on drawing so that if the user scrolls fast, we don't load extra data var id = setTimeout(function() { if (tile_low <= track.view.high && tile_high >= track.view.low) { // Show/draw tile: check cache for tile; if tile not in cache, draw it. - var tile_element = (force ? undefined : track.tile_cache.get(key)); - if (tile_element) { - track.show_tile(tile_element, parent_element, tile_low, w_scale); - delete draw_tile_dict[id]; + var tile = (force ? undefined : track.tile_cache.get(key)); + if (tile) { + track.show_tile(tile, parent_element, tile_low, w_scale); + drawn_tiles[drawn_tiles.length] = tile; } else { // @@ -1810,6 +1846,7 @@ // $.when(track.data_cache.get_data(view.chrom, tile_low, tile_high, track.mode, resolution, track.data_url_extra_params)).then(function(tile_data) { + extend(tile_data, more_tile_data); // If sequence data needed, get that and draw. Otherwise draw. if (view.reference_track && w_scale > view.canvas_manager.char_width_px) { $.when(view.reference_track.data_cache.get_data(view.chrom, tile_low, tile_high, @@ -1825,12 +1862,11 @@ } } }, 50); - draw_tile_dict[id] = true; }, /** * Show track tile and perform associated actions. */ - show_tile: function(tile_element, parent_element, tile_low, w_scale) { + show_tile: function(tile, parent_element, tile_low, w_scale) { // Readability. var track = this; @@ -1844,6 +1880,7 @@ if (this.left_offset) { left -= this.left_offset; } + var tile_element = tile.canvas; tile_element.css({ position: 'absolute', top: 0, left: left, height: '' }); // Setup and show tile element. @@ -1966,7 +2003,7 @@ var c_start = Math.round(c * w_scale); ctx.fillText(seq[c], c_start + track.left_offset, 10); } - return canvas; + return new Tile(tile_index, resolution, canvas); } this.content_div.css("height", "0px"); } @@ -2106,7 +2143,7 @@ this.prefs, this.mode ); painter.draw( ctx, width, height ); - return canvas; + return new Tile(tile_length, resolution, canvas); } }); @@ -2196,11 +2233,14 @@ * position. Return value is a dict with keys 'data', 'delta' (bin size) and 'max.' Data * is a two-item list; first item is bin start, second is bin's count. */ - get_summary_tree_data: function(data, low, high, bin_size) { - var num_bins = Math.floor((high - low)/bin_size), + get_summary_tree_data: function(data, low, high, num_bins) { + if (num_bins > high - low) { + num_bins = high - low; + } + var bin_size = Math.floor((high - low)/num_bins), bins = [], max_count = 0; - + /* // For debugging: for (var i = 0; i < data.length; i++) @@ -2335,8 +2375,7 @@ // Deal with left_offset by translating ctx.translate( left_offset, SUMMARY_TREE_TOP_PADDING ); painter.draw( ctx, width, required_height ); - // Canvas element is returned - return canvas; + return new SummaryTreeTile(tile_index, resolution, canvas, result.max); } // Drawing coverage histogram. This is different from summary tree because data can feature @@ -2359,13 +2398,15 @@ canvas.height = required_height + SUMMARY_TREE_TOP_PADDING; // Paint summary tree into canvas. var binned_data = this.get_summary_tree_data(result.data, tile_low, tile_high, 200); + if (result.max) { + binned_data.max = result.max; + } var painter = new painters.SummaryTreePainter(binned_data, tile_low, tile_high, this.prefs); var ctx = canvas.getContext("2d"); // Deal with left_offset by translating ctx.translate(left_offset, SUMMARY_TREE_TOP_PADDING); painter.draw(ctx, width, required_height); - // Canvas element is returned - return canvas; + return new SummaryTreeTile(tile_index, resolution, canvas, binned_data.max); } // Start dealing with row-by-row tracks @@ -2433,7 +2474,7 @@ // If there's no data, return. if (!result.data) { - return canvas; + return new Tile(tile_index, resolution, canvas); } } @@ -2444,7 +2485,7 @@ ctx.translate( left_offset, ERROR_PADDING ); painter.draw( ctx, width, required_height, slots ); - return canvas; + return new FeatureTrackTile(tile_index, resolution, canvas); } }); @@ -2558,7 +2599,7 @@ this.include_label = include_label; this.max_rows = max_rows; this.measureText = measureText; -} +}; /** * Slot a set of features, `this.slots` will be updated with slots by id, and @@ -2704,7 +2745,7 @@ // ---- Painters ---- var painters_module = function(require, exports){ - + /** * Draw a dashed line on a canvas using filled rectangles. This function is based on: * http://vetruvet.blogspot.com/2010/10/drawing-dashed-lines-on-html5-canvas.ht... @@ -2776,7 +2817,7 @@ Painter.call( this, data, view_start, view_end, prefs, mode ); } -SummaryTreePainter.prototype.default_prefis = { show_counts: false }; +SummaryTreePainter.prototype.default_prefs = { show_counts: false }; SummaryTreePainter.prototype.draw = function( ctx, width, height ) { @@ -2790,16 +2831,15 @@ // max rectangle is required_height. base_y = height; delta_x_px = Math.ceil(delta * w_scale); - ctx.save(); for (var i = 0, len = points.length; i < len; i++) { - var x = Math.floor( (points[i][0] - view_start) * w_scale ); var y = points[i][1]; if (!y) { continue; } - var y_px = y / max * height; + var y_px = y / max * height + if (y !== 0 && y_px < 1) { y_px = 1; } ctx.fillStyle = "black"; ctx.fillRect( x, base_y - y_px, delta_x_px, y_px ); @@ -2991,7 +3031,7 @@ } }); -// Contstants specific to feature tracks moved here (HACKING, these should +// Constants specific to feature tracks moved here (HACKING, these should // basically all be configuration options) var DENSE_TRACK_HEIGHT = 10, NO_DETAIL_TRACK_HEIGHT = 3, @@ -3034,8 +3074,7 @@ var feature_uid = feature[0], feature_start = feature[1], - // -1 b/c intervals are half-open. - feature_end = feature[2] - 1, + feature_end = feature[2], feature_name = feature[3], f_start = Math.floor( Math.max(0, (feature_start - tile_low) * w_scale) ), f_end = Math.ceil( Math.min(width, Math.max(0, (feature_end - tile_low) * w_scale)) ), @@ -3134,8 +3173,7 @@ for (var k = 0, k_len = feature_blocks.length; k < k_len; k++) { var block = feature_blocks[k], block_start = Math.floor( Math.max(0, (block[0] - tile_low) * w_scale) ), - // -1 b/c intervals are half-open. - block_end = Math.ceil( Math.min(width, Math.max((block[1] - 1 - tile_low) * w_scale)) ); + block_end = Math.ceil( Math.min(width, Math.max((block[1] - tile_low) * w_scale)) ); // Skip drawing if block not on tile. if (block_start > block_end) { continue; } @@ -3199,8 +3237,7 @@ var feature = data[i], feature_uid = feature[0], feature_start = feature[1], - // -1 b/c intervals are half-open. - feature_end = feature[2] - 1, + feature_end = feature[2], feature_name = feature[3], // All features need a start, end, and vertical center. f_start = Math.floor( Math.max(0, (feature_start - tile_low) * w_scale) ), http://bitbucket.org/galaxy/galaxy-central/changeset/1c5796258fb5/ changeset: r5379:1c5796258fb5 user: Martijn Vermaat date: 2011-04-14 01:04:40 summary: Run a workflow on a selection of multiple inputs, one invocation for each. - Makes input selections in the run workflow form support multiple selection. - If multiple inputs are selected, a workflow invocation is executed for each selected input. - Only of of the workflow inputs can have a multiple selection. affected #: 6 files (3.8 KB) --- a/lib/galaxy/tools/parameters/basic.py Wed Apr 13 16:46:36 2011 -0400 +++ b/lib/galaxy/tools/parameters/basic.py Thu Apr 14 01:04:40 2011 +0200 @@ -1421,6 +1421,14 @@ else: return "No dataset" + def validate( self, value, history=None ): + for validator in self.validators: + if value and self.multiple and isinstance(value, list): + for v in value: + validator.validate( v, history ) + else: + validator.validate( value, history ) + def get_dependencies( self ): """ Get the *names* of the other params this param depends on. --- a/lib/galaxy/web/controllers/workflow.py Wed Apr 13 16:46:36 2011 -0400 +++ b/lib/galaxy/web/controllers/workflow.py Thu Apr 14 01:04:40 2011 +0200 @@ -1262,96 +1262,117 @@ if kwargs: # If kwargs were provided, the states for each step should have # been POSTed - for step in workflow.steps: - step.upgrade_messages = {} - # Connections by input name - step.input_connections_by_name = \ - dict( ( conn.input_name, conn ) for conn in step.input_connections ) - # Extract just the arguments for this step by prefix - p = "%s|" % step.id - l = len(p) - step_args = dict( ( k[l:], v ) for ( k, v ) in kwargs.iteritems() if k.startswith( p ) ) - step_errors = None - if step.type == 'tool' or step.type is None: - module = module_factory.from_workflow_step( trans, step ) - # Fix any missing parameters - step.upgrade_messages = module.check_and_update_state() - if step.upgrade_messages: - has_upgrade_messages = True - # Any connected input needs to have value DummyDataset (these - # are not persisted so we need to do it every time) - module.add_dummy_datasets( connections=step.input_connections ) - # Get the tool - tool = module.tool - # Get the state - step.state = state = module.state - # Get old errors - old_errors = state.inputs.pop( "__errors__", {} ) - # Update the state - step_errors = tool.update_state( trans, tool.inputs, step.state.inputs, step_args, - update_only=True, old_errors=old_errors ) - else: - module = step.module = module_factory.from_workflow_step( trans, step ) - state = step.state = module.decode_runtime_state( trans, step_args.pop( "tool_state" ) ) - step_errors = module.update_runtime_state( trans, state, step_args ) - if step_errors: - errors[step.id] = state.inputs["__errors__"] = step_errors - if 'run_workflow' in kwargs and not errors: - new_history = None - if 'new_history' in kwargs: - if 'new_history_name' in kwargs and kwargs['new_history_name'] != '': - nh_name = kwargs['new_history_name'] + # Get the kwarg keys for data inputs + input_keys = filter(lambda a: a.endswith('|input'), kwargs) + # Example: prefixed='2|input' + # Check if one of them is a list + multiple_input_key = None + multiple_inputs = [None] + for input_key in input_keys: + if isinstance(kwargs[input_key], list): + multiple_input_key = input_key + multiple_inputs = kwargs[input_key] + # List to gather values for the template + invocations=[] + for input_number, single_input in enumerate(multiple_inputs): + # Example: single_input='1', single_input='2', etc... + # 'Fix' the kwargs, to have only the input for this iteration + if multiple_input_key: + kwargs[multiple_input_key] = single_input + for step in workflow.steps: + step.upgrade_messages = {} + # Connections by input name + step.input_connections_by_name = \ + dict( ( conn.input_name, conn ) for conn in step.input_connections ) + # Extract just the arguments for this step by prefix + p = "%s|" % step.id + l = len(p) + step_args = dict( ( k[l:], v ) for ( k, v ) in kwargs.iteritems() if k.startswith( p ) ) + step_errors = None + if step.type == 'tool' or step.type is None: + module = module_factory.from_workflow_step( trans, step ) + # Fix any missing parameters + step.upgrade_messages = module.check_and_update_state() + if step.upgrade_messages: + has_upgrade_messages = True + # Any connected input needs to have value DummyDataset (these + # are not persisted so we need to do it every time) + module.add_dummy_datasets( connections=step.input_connections ) + # Get the tool + tool = module.tool + # Get the state + step.state = state = module.state + # Get old errors + old_errors = state.inputs.pop( "__errors__", {} ) + # Update the state + step_errors = tool.update_state( trans, tool.inputs, step.state.inputs, step_args, + update_only=True, old_errors=old_errors ) else: - nh_name = "History from %s workflow" % workflow.name - new_history = trans.app.model.History( user=trans.user, name=nh_name ) - trans.sa_session.add( new_history ) - # Run each step, connecting outputs to inputs - workflow_invocation = model.WorkflowInvocation() - workflow_invocation.workflow = workflow - outputs = odict() - for i, step in enumerate( workflow.steps ): - # Execute module - job = None - if step.type == 'tool' or step.type is None: - tool = trans.app.toolbox.tools_by_id[ step.tool_id ] - input_values = step.state.inputs - # Connect up - def callback( input, value, prefixed_name, prefixed_label ): - if isinstance( input, DataToolParameter ): - if prefixed_name in step.input_connections_by_name: - conn = step.input_connections_by_name[ prefixed_name ] - return outputs[ conn.output_step.id ][ conn.output_name ] - visit_input_values( tool.inputs, step.state.inputs, callback ) - # Execute it - job, out_data = tool.execute( trans, step.state.inputs, history=new_history) - outputs[ step.id ] = out_data - # Create new PJA associations with the created job, to be run on completion. - # PJA Parameter Replacement (only applies to immediate actions-- rename specifically, for now) - # Pass along replacement dict with the execution of the PJA so we don't have to modify the object. - replacement_dict = {} - for k, v in kwargs.iteritems(): - if k.startswith('wf_parm|'): - replacement_dict[k[8:]] = v - for pja in step.post_job_actions: - if pja.action_type in ActionBox.immediate_actions: - ActionBox.execute(trans.app, trans.sa_session, pja, job, replacement_dict) - else: - job.add_post_job_action(pja) - else: - job, out_data = step.module.execute( trans, step.state ) - outputs[ step.id ] = out_data - # Record invocation - workflow_invocation_step = model.WorkflowInvocationStep() - workflow_invocation_step.workflow_invocation = workflow_invocation - workflow_invocation_step.workflow_step = step - workflow_invocation_step.job = job - # All jobs ran sucessfully, so we can save now - trans.sa_session.add( workflow_invocation ) - trans.sa_session.flush() - return trans.fill_template( "workflow/run_complete.mako", - workflow=stored, - outputs=outputs, - new_history=new_history ) + # Fix this for multiple inputs + module = step.module = module_factory.from_workflow_step( trans, step ) + state = step.state = module.decode_runtime_state( trans, step_args.pop( "tool_state" ) ) + step_errors = module.update_runtime_state( trans, state, step_args ) + if step_errors: + errors[step.id] = state.inputs["__errors__"] = step_errors + if 'run_workflow' in kwargs and not errors: + new_history = None + if 'new_history' in kwargs: + if 'new_history_name' in kwargs and kwargs['new_history_name'] != '': + nh_name = kwargs['new_history_name'] + else: + nh_name = "History from %s workflow" % workflow.name + if multiple_input_key: + nh_name = '%s %d' % (nh_name, input_number + 1) + new_history = trans.app.model.History( user=trans.user, name=nh_name ) + trans.sa_session.add( new_history ) + # Run each step, connecting outputs to inputs + workflow_invocation = model.WorkflowInvocation() + workflow_invocation.workflow = workflow + outputs = odict() + for i, step in enumerate( workflow.steps ): + # Execute module + job = None + if step.type == 'tool' or step.type is None: + tool = trans.app.toolbox.tools_by_id[ step.tool_id ] + input_values = step.state.inputs + # Connect up + def callback( input, value, prefixed_name, prefixed_label ): + if isinstance( input, DataToolParameter ): + if prefixed_name in step.input_connections_by_name: + conn = step.input_connections_by_name[ prefixed_name ] + return outputs[ conn.output_step.id ][ conn.output_name ] + visit_input_values( tool.inputs, step.state.inputs, callback ) + # Execute it + job, out_data = tool.execute( trans, step.state.inputs, history=new_history) + outputs[ step.id ] = out_data + # Create new PJA associations with the created job, to be run on completion. + # PJA Parameter Replacement (only applies to immediate actions-- rename specifically, for now) + # Pass along replacement dict with the execution of the PJA so we don't have to modify the object. + replacement_dict = {} + for k, v in kwargs.iteritems(): + if k.startswith('wf_parm|'): + replacement_dict[k[8:]] = v + for pja in step.post_job_actions: + if pja.action_type in ActionBox.immediate_actions: + ActionBox.execute(trans.app, trans.sa_session, pja, job, replacement_dict) + else: + job.add_post_job_action(pja) + else: + job, out_data = step.module.execute( trans, step.state ) + outputs[ step.id ] = out_data + # Record invocation + workflow_invocation_step = model.WorkflowInvocationStep() + workflow_invocation_step.workflow_invocation = workflow_invocation + workflow_invocation_step.workflow_step = step + workflow_invocation_step.job = job + # All jobs ran sucessfully, so we can save now + trans.sa_session.add( workflow_invocation ) + invocations.append({'outputs': outputs, + 'new_history': new_history}) + trans.sa_session.flush() + return trans.fill_template( "workflow/run_complete.mako", + workflow=stored, + invocations=invocations ) else: # Prepare each step missing_tools = [] @@ -1816,4 +1837,4 @@ cleanup( prefix, input.cases[current_case].inputs, group_values ) cleanup( "", inputs, values ) return associations - \ No newline at end of file + --- a/lib/galaxy/workflow/modules.py Wed Apr 13 16:46:36 2011 -0400 +++ b/lib/galaxy/workflow/modules.py Thu Apr 14 01:04:40 2011 +0200 @@ -131,7 +131,7 @@ def get_runtime_inputs( self, filter_set=['data'] ): label = self.state.get( "name", "Input Dataset" ) - return dict( input=DataToolParameter( None, Element( "param", name="input", label=label, type="data", format=', '.join(filter_set) ) ) ) + return dict( input=DataToolParameter( None, Element( "param", name="input", label=label, multiple=True, type="data", format=', '.join(filter_set) ) ) ) def get_runtime_state( self ): state = DefaultToolState() state.inputs = dict( input=None ) --- a/static/june_2007_style/blue/base.css Wed Apr 13 16:46:36 2011 -0400 +++ b/static/june_2007_style/blue/base.css Thu Apr 14 01:04:40 2011 +0200 @@ -170,3 +170,6 @@ .editable-text{cursor:pointer;} .editable-text:hover{cursor:text;border:dotted #999999 1px;} .text-and-autocomplete-select{background:url(fugue.png) no-repeat right -364px;} +.icon-button.multiinput{background:url(../images/documents-stack.png) no-repeat;cursor:pointer;float:none;display:inline-block;margin-left:10px;} +.icon-button.multiinput.disabled{background:url(../images/documents-stack-faded.png) no-repeat;cursor:auto;} +.worflow-invocation-complete{border:solid 1px #6A6;border-left-width:5px;margin:10px 0;} --- a/templates/workflow/run.mako Wed Apr 13 16:46:36 2011 -0400 +++ b/templates/workflow/run.mako Thu Apr 14 01:04:40 2011 +0200 @@ -2,7 +2,7 @@ <%def name="javascripts()"> ${parent.javascripts()} - ${h.js( "jquery.autocomplete" )} + ${h.js( "jquery.autocomplete", "jquery.tipsy" )} <script type="text/javascript"> $( function() { function show_tool_body(title){ @@ -20,6 +20,21 @@ show_tool_body(title); } } + function toggle_multiinput(select) { + if (select.attr('multiple')) { + $('.multiinput').removeClass('disabled'); + if (select.val()) { + select.val(select.val()[0]); + } else { + select.val($('option:last', select).val()); + } + select.removeAttr('multiple'); + } else { + $('.multiinput').addClass('disabled'); + $('.multiinput', select.parent().prev()).removeClass('disabled'); + select.attr('multiple', 'multiple'); + } + } $( "select[refresh_on_change='true']").change( function() { $( "#tool_form" ).submit(); }); @@ -41,6 +56,19 @@ $("#new_history_cbx").click(function(){ $("#new_history_input").toggle(this.checked); }); + $('select[name*="|input"]').removeAttr('multiple').each(function(i, s) { + var select = $(s); + select.parent().prev().append( + $('<span class="icon-button multiinput"></span>').click(function() { + if ($(this).hasClass('disabled')) return; + toggle_multiinput(select); + select.focus(); + }).attr('original-title', + 'Enable/disable selection of multiple input ' + + 'files. Each selected file will have an ' + + 'instance of the workflow.').tipsy({gravity:'s'}) + ); + }); }); </script></%def> --- a/templates/workflow/run_complete.mako Wed Apr 13 16:46:36 2011 -0400 +++ b/templates/workflow/run_complete.mako Thu Apr 14 01:04:40 2011 +0200 @@ -2,21 +2,25 @@ <div class="donemessagelarge"> Successfully ran workflow "${workflow.name}". The following datasets have been added to the queue: - %if new_history: - These datasets will appear in a new history: - <a target='galaxy_history' href="${h.url_for( controller='history', action='list', operation="Switch", id=trans.security.encode_id(new_history.id), use_panels=False, show_deleted=False )}"> - '${h.to_unicode(new_history.name)}'. - </a> - %endif - <div style="padding-left: 10px;"> - %for step_outputs in outputs.itervalues(): - %for data in step_outputs.itervalues(): - %if not new_history or data.history == new_history: - <p><strong>${data.hid}</strong>: ${data.name}</p> - %endif - %endfor - %endfor - </div> + %for invocation in invocations: + <div class="workflow-invocation-complete"> + %if invocation['new_history']: + <p>These datasets will appear in a new history: + <a target='galaxy_history' href="${h.url_for( controller='history', action='list', operation="Switch", id=trans.security.encode_id(invocation['new_history'].id), use_panels=False, show_deleted=False )}"> + '${h.to_unicode(invocation['new_history'].name)}'. + </a></p> + %endif + <div style="padding-left: 10px;"> + %for step_outputs in invocation['outputs'].itervalues(): + %for data in step_outputs.itervalues(): + %if not invocation['new_history'] or data.history == invocation['new_history']: + <p><strong>${data.hid}</strong>: ${data.name}</p> + %endif + %endfor + %endfor + </div> + </div> + %endfor </div><script type="text/javascript"> http://bitbucket.org/galaxy/galaxy-central/changeset/383258d637cb/ changeset: r5380:383258d637cb user: dannon date: 2011-04-14 01:07:22 summary: Merge. affected #: 6 files (3.8 KB) --- a/lib/galaxy/tools/parameters/basic.py Wed Apr 13 19:05:45 2011 -0400 +++ b/lib/galaxy/tools/parameters/basic.py Wed Apr 13 19:07:22 2011 -0400 @@ -1421,6 +1421,14 @@ else: return "No dataset" + def validate( self, value, history=None ): + for validator in self.validators: + if value and self.multiple and isinstance(value, list): + for v in value: + validator.validate( v, history ) + else: + validator.validate( value, history ) + def get_dependencies( self ): """ Get the *names* of the other params this param depends on. --- a/lib/galaxy/web/controllers/workflow.py Wed Apr 13 19:05:45 2011 -0400 +++ b/lib/galaxy/web/controllers/workflow.py Wed Apr 13 19:07:22 2011 -0400 @@ -1262,96 +1262,117 @@ if kwargs: # If kwargs were provided, the states for each step should have # been POSTed - for step in workflow.steps: - step.upgrade_messages = {} - # Connections by input name - step.input_connections_by_name = \ - dict( ( conn.input_name, conn ) for conn in step.input_connections ) - # Extract just the arguments for this step by prefix - p = "%s|" % step.id - l = len(p) - step_args = dict( ( k[l:], v ) for ( k, v ) in kwargs.iteritems() if k.startswith( p ) ) - step_errors = None - if step.type == 'tool' or step.type is None: - module = module_factory.from_workflow_step( trans, step ) - # Fix any missing parameters - step.upgrade_messages = module.check_and_update_state() - if step.upgrade_messages: - has_upgrade_messages = True - # Any connected input needs to have value DummyDataset (these - # are not persisted so we need to do it every time) - module.add_dummy_datasets( connections=step.input_connections ) - # Get the tool - tool = module.tool - # Get the state - step.state = state = module.state - # Get old errors - old_errors = state.inputs.pop( "__errors__", {} ) - # Update the state - step_errors = tool.update_state( trans, tool.inputs, step.state.inputs, step_args, - update_only=True, old_errors=old_errors ) - else: - module = step.module = module_factory.from_workflow_step( trans, step ) - state = step.state = module.decode_runtime_state( trans, step_args.pop( "tool_state" ) ) - step_errors = module.update_runtime_state( trans, state, step_args ) - if step_errors: - errors[step.id] = state.inputs["__errors__"] = step_errors - if 'run_workflow' in kwargs and not errors: - new_history = None - if 'new_history' in kwargs: - if 'new_history_name' in kwargs and kwargs['new_history_name'] != '': - nh_name = kwargs['new_history_name'] + # Get the kwarg keys for data inputs + input_keys = filter(lambda a: a.endswith('|input'), kwargs) + # Example: prefixed='2|input' + # Check if one of them is a list + multiple_input_key = None + multiple_inputs = [None] + for input_key in input_keys: + if isinstance(kwargs[input_key], list): + multiple_input_key = input_key + multiple_inputs = kwargs[input_key] + # List to gather values for the template + invocations=[] + for input_number, single_input in enumerate(multiple_inputs): + # Example: single_input='1', single_input='2', etc... + # 'Fix' the kwargs, to have only the input for this iteration + if multiple_input_key: + kwargs[multiple_input_key] = single_input + for step in workflow.steps: + step.upgrade_messages = {} + # Connections by input name + step.input_connections_by_name = \ + dict( ( conn.input_name, conn ) for conn in step.input_connections ) + # Extract just the arguments for this step by prefix + p = "%s|" % step.id + l = len(p) + step_args = dict( ( k[l:], v ) for ( k, v ) in kwargs.iteritems() if k.startswith( p ) ) + step_errors = None + if step.type == 'tool' or step.type is None: + module = module_factory.from_workflow_step( trans, step ) + # Fix any missing parameters + step.upgrade_messages = module.check_and_update_state() + if step.upgrade_messages: + has_upgrade_messages = True + # Any connected input needs to have value DummyDataset (these + # are not persisted so we need to do it every time) + module.add_dummy_datasets( connections=step.input_connections ) + # Get the tool + tool = module.tool + # Get the state + step.state = state = module.state + # Get old errors + old_errors = state.inputs.pop( "__errors__", {} ) + # Update the state + step_errors = tool.update_state( trans, tool.inputs, step.state.inputs, step_args, + update_only=True, old_errors=old_errors ) else: - nh_name = "History from %s workflow" % workflow.name - new_history = trans.app.model.History( user=trans.user, name=nh_name ) - trans.sa_session.add( new_history ) - # Run each step, connecting outputs to inputs - workflow_invocation = model.WorkflowInvocation() - workflow_invocation.workflow = workflow - outputs = odict() - for i, step in enumerate( workflow.steps ): - # Execute module - job = None - if step.type == 'tool' or step.type is None: - tool = trans.app.toolbox.tools_by_id[ step.tool_id ] - input_values = step.state.inputs - # Connect up - def callback( input, value, prefixed_name, prefixed_label ): - if isinstance( input, DataToolParameter ): - if prefixed_name in step.input_connections_by_name: - conn = step.input_connections_by_name[ prefixed_name ] - return outputs[ conn.output_step.id ][ conn.output_name ] - visit_input_values( tool.inputs, step.state.inputs, callback ) - # Execute it - job, out_data = tool.execute( trans, step.state.inputs, history=new_history) - outputs[ step.id ] = out_data - # Create new PJA associations with the created job, to be run on completion. - # PJA Parameter Replacement (only applies to immediate actions-- rename specifically, for now) - # Pass along replacement dict with the execution of the PJA so we don't have to modify the object. - replacement_dict = {} - for k, v in kwargs.iteritems(): - if k.startswith('wf_parm|'): - replacement_dict[k[8:]] = v - for pja in step.post_job_actions: - if pja.action_type in ActionBox.immediate_actions: - ActionBox.execute(trans.app, trans.sa_session, pja, job, replacement_dict) - else: - job.add_post_job_action(pja) - else: - job, out_data = step.module.execute( trans, step.state ) - outputs[ step.id ] = out_data - # Record invocation - workflow_invocation_step = model.WorkflowInvocationStep() - workflow_invocation_step.workflow_invocation = workflow_invocation - workflow_invocation_step.workflow_step = step - workflow_invocation_step.job = job - # All jobs ran sucessfully, so we can save now - trans.sa_session.add( workflow_invocation ) - trans.sa_session.flush() - return trans.fill_template( "workflow/run_complete.mako", - workflow=stored, - outputs=outputs, - new_history=new_history ) + # Fix this for multiple inputs + module = step.module = module_factory.from_workflow_step( trans, step ) + state = step.state = module.decode_runtime_state( trans, step_args.pop( "tool_state" ) ) + step_errors = module.update_runtime_state( trans, state, step_args ) + if step_errors: + errors[step.id] = state.inputs["__errors__"] = step_errors + if 'run_workflow' in kwargs and not errors: + new_history = None + if 'new_history' in kwargs: + if 'new_history_name' in kwargs and kwargs['new_history_name'] != '': + nh_name = kwargs['new_history_name'] + else: + nh_name = "History from %s workflow" % workflow.name + if multiple_input_key: + nh_name = '%s %d' % (nh_name, input_number + 1) + new_history = trans.app.model.History( user=trans.user, name=nh_name ) + trans.sa_session.add( new_history ) + # Run each step, connecting outputs to inputs + workflow_invocation = model.WorkflowInvocation() + workflow_invocation.workflow = workflow + outputs = odict() + for i, step in enumerate( workflow.steps ): + # Execute module + job = None + if step.type == 'tool' or step.type is None: + tool = trans.app.toolbox.tools_by_id[ step.tool_id ] + input_values = step.state.inputs + # Connect up + def callback( input, value, prefixed_name, prefixed_label ): + if isinstance( input, DataToolParameter ): + if prefixed_name in step.input_connections_by_name: + conn = step.input_connections_by_name[ prefixed_name ] + return outputs[ conn.output_step.id ][ conn.output_name ] + visit_input_values( tool.inputs, step.state.inputs, callback ) + # Execute it + job, out_data = tool.execute( trans, step.state.inputs, history=new_history) + outputs[ step.id ] = out_data + # Create new PJA associations with the created job, to be run on completion. + # PJA Parameter Replacement (only applies to immediate actions-- rename specifically, for now) + # Pass along replacement dict with the execution of the PJA so we don't have to modify the object. + replacement_dict = {} + for k, v in kwargs.iteritems(): + if k.startswith('wf_parm|'): + replacement_dict[k[8:]] = v + for pja in step.post_job_actions: + if pja.action_type in ActionBox.immediate_actions: + ActionBox.execute(trans.app, trans.sa_session, pja, job, replacement_dict) + else: + job.add_post_job_action(pja) + else: + job, out_data = step.module.execute( trans, step.state ) + outputs[ step.id ] = out_data + # Record invocation + workflow_invocation_step = model.WorkflowInvocationStep() + workflow_invocation_step.workflow_invocation = workflow_invocation + workflow_invocation_step.workflow_step = step + workflow_invocation_step.job = job + # All jobs ran sucessfully, so we can save now + trans.sa_session.add( workflow_invocation ) + invocations.append({'outputs': outputs, + 'new_history': new_history}) + trans.sa_session.flush() + return trans.fill_template( "workflow/run_complete.mako", + workflow=stored, + invocations=invocations ) else: # Prepare each step missing_tools = [] @@ -1816,4 +1837,4 @@ cleanup( prefix, input.cases[current_case].inputs, group_values ) cleanup( "", inputs, values ) return associations - \ No newline at end of file + --- a/lib/galaxy/workflow/modules.py Wed Apr 13 19:05:45 2011 -0400 +++ b/lib/galaxy/workflow/modules.py Wed Apr 13 19:07:22 2011 -0400 @@ -131,7 +131,7 @@ def get_runtime_inputs( self, filter_set=['data'] ): label = self.state.get( "name", "Input Dataset" ) - return dict( input=DataToolParameter( None, Element( "param", name="input", label=label, type="data", format=', '.join(filter_set) ) ) ) + return dict( input=DataToolParameter( None, Element( "param", name="input", label=label, multiple=True, type="data", format=', '.join(filter_set) ) ) ) def get_runtime_state( self ): state = DefaultToolState() state.inputs = dict( input=None ) --- a/static/june_2007_style/blue/base.css Wed Apr 13 19:05:45 2011 -0400 +++ b/static/june_2007_style/blue/base.css Wed Apr 13 19:07:22 2011 -0400 @@ -170,3 +170,6 @@ .editable-text{cursor:pointer;} .editable-text:hover{cursor:text;border:dotted #999999 1px;} .text-and-autocomplete-select{background:url(fugue.png) no-repeat right -364px;} +.icon-button.multiinput{background:url(../images/documents-stack.png) no-repeat;cursor:pointer;float:none;display:inline-block;margin-left:10px;} +.icon-button.multiinput.disabled{background:url(../images/documents-stack-faded.png) no-repeat;cursor:auto;} +.worflow-invocation-complete{border:solid 1px #6A6;border-left-width:5px;margin:10px 0;} --- a/templates/workflow/run.mako Wed Apr 13 19:05:45 2011 -0400 +++ b/templates/workflow/run.mako Wed Apr 13 19:07:22 2011 -0400 @@ -2,7 +2,7 @@ <%def name="javascripts()"> ${parent.javascripts()} - ${h.js( "jquery.autocomplete" )} + ${h.js( "jquery.autocomplete", "jquery.tipsy" )} <script type="text/javascript"> $( function() { function show_tool_body(title){ @@ -20,6 +20,21 @@ show_tool_body(title); } } + function toggle_multiinput(select) { + if (select.attr('multiple')) { + $('.multiinput').removeClass('disabled'); + if (select.val()) { + select.val(select.val()[0]); + } else { + select.val($('option:last', select).val()); + } + select.removeAttr('multiple'); + } else { + $('.multiinput').addClass('disabled'); + $('.multiinput', select.parent().prev()).removeClass('disabled'); + select.attr('multiple', 'multiple'); + } + } $( "select[refresh_on_change='true']").change( function() { $( "#tool_form" ).submit(); }); @@ -41,6 +56,19 @@ $("#new_history_cbx").click(function(){ $("#new_history_input").toggle(this.checked); }); + $('select[name*="|input"]').removeAttr('multiple').each(function(i, s) { + var select = $(s); + select.parent().prev().append( + $('<span class="icon-button multiinput"></span>').click(function() { + if ($(this).hasClass('disabled')) return; + toggle_multiinput(select); + select.focus(); + }).attr('original-title', + 'Enable/disable selection of multiple input ' + + 'files. Each selected file will have an ' + + 'instance of the workflow.').tipsy({gravity:'s'}) + ); + }); }); </script></%def> --- a/templates/workflow/run_complete.mako Wed Apr 13 19:05:45 2011 -0400 +++ b/templates/workflow/run_complete.mako Wed Apr 13 19:07:22 2011 -0400 @@ -2,21 +2,25 @@ <div class="donemessagelarge"> Successfully ran workflow "${workflow.name}". The following datasets have been added to the queue: - %if new_history: - These datasets will appear in a new history: - <a target='galaxy_history' href="${h.url_for( controller='history', action='list', operation="Switch", id=trans.security.encode_id(new_history.id), use_panels=False, show_deleted=False )}"> - '${h.to_unicode(new_history.name)}'. - </a> - %endif - <div style="padding-left: 10px;"> - %for step_outputs in outputs.itervalues(): - %for data in step_outputs.itervalues(): - %if not new_history or data.history == new_history: - <p><strong>${data.hid}</strong>: ${data.name}</p> - %endif - %endfor - %endfor - </div> + %for invocation in invocations: + <div class="workflow-invocation-complete"> + %if invocation['new_history']: + <p>These datasets will appear in a new history: + <a target='galaxy_history' href="${h.url_for( controller='history', action='list', operation="Switch", id=trans.security.encode_id(invocation['new_history'].id), use_panels=False, show_deleted=False )}"> + '${h.to_unicode(invocation['new_history'].name)}'. + </a></p> + %endif + <div style="padding-left: 10px;"> + %for step_outputs in invocation['outputs'].itervalues(): + %for data in step_outputs.itervalues(): + %if not invocation['new_history'] or data.history == invocation['new_history']: + <p><strong>${data.hid}</strong>: ${data.name}</p> + %endif + %endfor + %endfor + </div> + </div> + %endfor </div><script type="text/javascript"> 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.