This commit is contained in:
Dannon Baker
2014-08-26 10:48:35 -04:00
10 changed files with 216 additions and 105 deletions
+1 -1
View File
@@ -18,4 +18,4 @@ a4113cc1cb5eaa68091c9a73375f00555b66dd11 release_2013.01.13
81fbe25bd02edcd53065e8e4476dd1dfb5a72cf2 latest_2013.11.04
2a756ca2cb1826db7796018e77d12e2dd7b67603 latest_2014.02.10
ca45b78adb4152fc6e7395514d46eba6b7d0b838 release_2014.08.11
7e4d21621ce12e13ebbdf9fd3259df58c3ef124c latest_2014.08.11
7e22f35798522100ff03e1fdd4eced962b292360 latest_2014.08.11
@@ -24,7 +24,7 @@ from galaxy.web.base.controller import BaseUIController, SharableMixin, UsesStor
from galaxy.web.framework import form
from galaxy.web.framework.helpers import grids, time_ago
from galaxy.web.framework.helpers import to_unicode
from galaxy.workflow.modules import module_factory
from galaxy.workflow.modules import module_factory, is_tool_module_type
from galaxy.workflow.run import invoke
from galaxy.workflow.run import WorkflowRunConfig
from galaxy.workflow.extract import summarize
@@ -836,8 +836,8 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix
steps_by_external_id = {}
errors = []
for key, step_dict in data['steps'].iteritems():
is_input = step_dict[ 'type' ] in [ 'data_input', 'data_collection_input' ]
if not is_input and step_dict['tool_id'] not in trans.app.toolbox.tools_by_id:
is_tool = is_tool_module_type( step_dict[ 'type' ] )
if is_tool and step_dict['tool_id'] not in trans.app.toolbox.tools_by_id:
errors.append("Step %s requires tool '%s'." % (step_dict['id'], step_dict['tool_id']))
if errors:
return dict( name=workflow.name,
+22 -1
View File
@@ -71,9 +71,18 @@ class WorkflowModule( object ):
## ---- Configuration time -----------------------------------------------
def get_state( self ):
""" Return a serializable representation of the persistable state of
the step - for tools it DefaultToolState.encode returns a string and
for inputs a json description is dumped out.
"""
return None
def get_errors( self ):
""" It seems like this is effectively just used as boolean - some places
in the tool shed self.errors is set to boolean, other places 'unavailable',
likewise in Galaxy it stores a list containing a string with an unrecognized
tool id error message.
"""
return None
def get_data_inputs( self ):
@@ -292,6 +301,9 @@ class ToolModule( WorkflowModule ):
@classmethod
def new( Class, trans, tool_id=None ):
module = Class( trans, tool_id )
if module.tool is None:
error_message = "Attempted to create new workflow module for invalid tool_id, no tool with id - %s." % tool_id
raise Exception( error_message )
module.state = module.tool.new_state( trans, all_pages=True )
return module
@@ -536,5 +548,14 @@ class WorkflowModuleFactory( object ):
type = step.type
return self.module_types[type].from_workflow_step( trans, step )
module_types = dict( data_input=InputDataModule, data_collection_input=InputDataCollectionModule, tool=ToolModule )
def is_tool_module_type( module_type ):
return not module_type or module_type == "tool"
module_types = dict(
data_input=InputDataModule,
data_collection_input=InputDataCollectionModule,
tool=ToolModule,
)
module_factory = WorkflowModuleFactory( module_types )
+6 -4
View File
@@ -71,7 +71,7 @@ class WorkflowInvoker( object ):
self.trans.sa_session.add( workflow_invocation )
# Not flushing in here, because web controller may create multiple
# invokations.
# invocations.
return self.outputs
def _invoke_step( self, step ):
@@ -211,10 +211,9 @@ class WorkflowInvoker( object ):
"""
replacement = None
if prefixed_name in step.input_connections_by_name:
outputs = self.outputs
connection = step.input_connections_by_name[ prefixed_name ]
if input.multiple:
replacement = [ outputs[ c.output_step.id ][ c.output_name ] for c in connection ]
replacement = [ self._replacement_for_connection( c ) for c in connection ]
# If replacement is just one dataset collection, replace tool
# input with dataset collection - tool framework will extract
# datasets properly.
@@ -222,9 +221,12 @@ class WorkflowInvoker( object ):
if isinstance( replacement[ 0 ], model.HistoryDatasetCollectionAssociation ):
replacement = replacement[ 0 ]
else:
replacement = outputs[ connection[ 0 ].output_step.id ][ connection[ 0 ].output_name ]
replacement = self._replacement_for_connection( connection[ 0 ] )
return replacement
def _replacement_for_connection( self, connection ):
return self.outputs[ connection.output_step.id ][ connection.output_name ]
def _populate_state( self ):
# Build the state for each step
for step in self.workflow.steps:
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
define(["libs/underscore"],function(b){var e=b.extend;var c=2,a=5;var d=function(i,h,f,g){this.slots={};this.start_end_dct={};this.w_scale=i;this.mode=h;this.include_label=(h==="Pack");this.max_rows=f;this.measureText=g};e(d.prototype,{slot_features:function(m){var p=this.w_scale,h=this.start_end_dct,x=[],z=[],n=0,y=this.max_rows;for(var v=0,w=m.length;v<w;v++){var k=m[v],o=k[0];if(this.slots[o]!==undefined){n=Math.max(n,this.slots[o]);z.push(this.slots[o])}else{x.push(v)}}var q=function(E,F){for(var D=0;D<=y;D++){var B=false,G=h[D];if(G!==undefined){for(var A=0,C=G.length;A<C;A++){var i=G[A];if(F>i[0]&&E<i[1]){B=true;break}}}if(!B){return D}}return -1};for(var v=0,w=x.length;v<w;v++){var k=m[x[v]],o=k[0],t=k[1],f=k[2],r=k[3],g=Math.floor(t*p),l=Math.ceil(f*p),u=this.measureText(r).width,j;if(r!==undefined&&this.include_label){u+=(c+a);if(g-u>=0){g-=u;j="left"}else{l+=u;j="right"}}var s=q(g,l);if(s>=0){if(h[s]===undefined){h[s]=[]}h[s].push([g,l]);this.slots[o]=s;n=Math.max(n,s)}}return n+1}});return{FeatureSlotter:d}});
define(["libs/underscore"],function(c){var f=c.extend;var d=2,b=5;var a=function(h,g){this.slot=h;this.feature=g};var e=function(j,i,g,h){this.slots={};this.start_end_dct={};this.w_scale=j;this.mode=i;this.include_label=(i==="Pack");this.max_rows=g;this.measureText=h};f(e.prototype,{_get_draw_coords:function(j){var h=Math.floor(j[1]*this.w_scale),i=Math.ceil(j[2]*this.w_scale),g=j[3],k;if(g!==undefined&&this.include_label){var l=this.measureText(g).width+(d+b);if(h-l>=0){h-=l;k="left"}else{i+=l;k="right"}}return[h,i]},_find_slot:function(j){var o=j[0],n=j[1];for(var l=0;l<=this.max_rows;l++){var p=false,m=this.start_end_dct[l];if(m!==undefined){for(var g=0,h=m.length;g<h;g++){var i=m[g];if(n>i[0]&&o<i[1]){p=true;break}}}if(!p){return l}}return -1},slot_features:function(h){var q=this.start_end_dct,v=[],m=0,x,l;for(var o=0,t=h.length;o<t;o++){x=h[o];l=x[0];var g=this.slots[l];if(g){if(x[1]<g.feature[1]||g.feature[2]<x[2]){console.log(x[3],g.slot,this._find_slot(this._get_draw_coords(x)));var s=this._get_draw_coords(g.feature),p=this._get_draw_coords(x),j=this.start_end_dct[g.slot];for(var n=0;n<j.length;n++){var w=j[n];if(w[0]===s[0]&&w[1]===s[1]){j[n]=p}}}m=Math.max(m,this.slots[l].slot)}else{v.push(o)}}for(var o=0,t=v.length;o<t;o++){x=h[v[o]];l=x[0];var r=this._get_draw_coords(x);var u=this._find_slot(r);if(u>=0){if(q[u]===undefined){q[u]=[]}q[u].push(r);this.slots[l]=new a(u,x);m=Math.max(m,u)}}return m+1}});return{FeatureSlotter:e}});
+1 -1
View File
@@ -396,7 +396,7 @@ _.extend(FeaturePainter.prototype, {
feature_end = feature[2],
// Slot valid only if features are slotted and this feature is slotted;
// feature may not be due to lack of space.
slot = (slots && slots[feature_uid] !== undefined ? slots[feature_uid] : null);
slot = (slots && slots[feature_uid] !== undefined ? slots[feature_uid].slot : null);
// Draw feature if (a) mode is dense or feature is slotted (as it must be for all non-dense modes) and
// (b) there's overlap between the feature and drawing region.
+131 -93
View File
@@ -6,6 +6,14 @@ var extend = _.extend;
var LABEL_SPACING = 2,
PACK_SPACING = 5;
/**
* Hold slotting information for a feature.
*/
var SlottedInfo = function(slot, feature) {
this.slot = slot;
this.feature = feature;
};
/**
* FeatureSlotter determines slots in which to draw features for vertical
* packing.
@@ -28,118 +36,148 @@ var FeatureSlotter = function (w_scale, mode, max_rows, measureText) {
* the largest slot required for the passed set of features is returned
*/
extend( FeatureSlotter.prototype, {
slot_features: function( features ) {
var w_scale = this.w_scale,
start_end_dct = this.start_end_dct,
undone = [],
slotted = [],
highest_slot = 0,
max_rows = this.max_rows;
// If feature already exists in slots (from previously seen tiles), use the same slot,
// otherwise if not seen, add to "undone" list for slot calculation.
// TODO: Should calculate zoom tile index, which will improve performance
// by only having to look at a smaller subset
// if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
for (var i = 0, len = features.length; i < len; i++) {
var feature = features[i],
feature_uid = feature[0];
if (this.slots[feature_uid] !== undefined) {
highest_slot = Math.max(highest_slot, this.slots[feature_uid]);
slotted.push(this.slots[feature_uid]);
/**
* Get drawing coordinate for a feature.
*/
_get_draw_coords: function(feature) {
// Get initial draw coordinates using w_scale.
var draw_start = Math.floor(feature[1] * this.w_scale),
draw_end = Math.ceil(feature[2] * this.w_scale),
f_name = feature[3],
text_align;
// Update start, end drawing locations to include feature name.
// Try to put the name on the left, if not, put on right.
if (f_name !== undefined && this.include_label ) {
// Add gap for label spacing and extra pack space padding
// TODO: Fix constants
var text_len = this.measureText(f_name).width + (LABEL_SPACING + PACK_SPACING);
if (draw_start - text_len >= 0) {
draw_start -= text_len;
text_align = "left";
} else {
undone.push(i);
draw_end += text_len;
text_align = "right";
}
}
// Slot unslotted features.
// Find the first slot such that current feature doesn't overlap any other features in that slot.
// Returns -1 if no slot was found.
var find_slot = function(f_start, f_end) {
// TODO: Fix constants
for (var slot_num = 0; slot_num <= max_rows; slot_num++) {
var has_overlap = false,
slot = start_end_dct[slot_num];
if (slot !== undefined) {
// Iterate through features already in slot to see if current feature will fit.
for (var k = 0, k_len = slot.length; k < k_len; k++) {
var s_e = slot[k];
if (f_end > s_e[0] && f_start < s_e[1]) {
// There is overlap
has_overlap = true;
break;
/*
if (slot_num < 0) {
TODO: this is not yet working --
console.log(feature_uid, "looking for slot with text on the right");
// Slot not found. If text was on left, try on right and see
// if slot can be found.
// TODO: are there any checks we need to do to ensure that text
// will fit on tile?
if (text_align === "left") {
draw_start -= text_len;
draw_end -= text_len;
text_align = "right";
slot_num = find_slot(draw_start, draw_end);
}
if (slot_num >= 0) {
console.log(feature_uid, "found slot with text on the right");
}
}
*/
return [draw_start, draw_end];
},
/**
* Find the first slot such that current feature doesn't overlap any other features in that slot.
* Returns -1 if no slot was found.
*/
_find_slot: function(draw_coords) {
// TODO: Use a data structure for faster searching of available slots.
var draw_start = draw_coords[0],
draw_end = draw_coords[1];
for (var slot_num = 0; slot_num <= this.max_rows; slot_num++) {
var has_overlap = false,
slot = this.start_end_dct[slot_num];
if (slot !== undefined) {
// Iterate through features already in slot to see if current feature will fit.
for (var k = 0, k_len = slot.length; k < k_len; k++) {
var s_e = slot[k];
if (draw_end > s_e[0] && draw_start < s_e[1]) {
// There is overlap
has_overlap = true;
break;
}
}
}
if (!has_overlap) {
return slot_num;
}
}
return -1;
},
/**
* Slot features.
*/
slot_features: function( features ) {
var start_end_dct = this.start_end_dct,
undone = [],
highest_slot = 0,
feature,
feature_uid;
// Loop through features to (a) find those that are not yet slotted and (b) update
// those that are slotted if new information is availabe. For (a), features already
// slotted (based on slotting from other tiles) will retain their current slot.
for (var i = 0, len = features.length; i < len; i++) {
feature = features[i];
feature_uid = feature[0];
var slotted_info = this.slots[feature_uid];
// Separate and handle slotted vs. unslotted features.
if (slotted_info) {
// Feature is slotted; if feature now has larger start/end coordinates,
// update drawing coordinates.
if (feature[1] < slotted_info.feature[1] || slotted_info.feature[2] < feature[2]) {
console.log(feature[3], slotted_info.slot, this._find_slot(this._get_draw_coords(feature)));
// Feature has changed (e.g. a single read now has its pair), so recalculate its
// drawing coordinates.
var old_draw_coords = this._get_draw_coords(slotted_info.feature),
new_draw_coords = this._get_draw_coords(feature),
slotted_coords = this.start_end_dct[slotted_info.slot];
for (var k = 0; k < slotted_coords.length; k++) {
var dc = slotted_coords[k];
if (dc[0] === old_draw_coords[0] && dc[1] === old_draw_coords[1]) {
// Replace old drawing coordinates with new ones.
slotted_coords[k] = new_draw_coords;
}
}
}
if (!has_overlap) {
return slot_num;
}
highest_slot = Math.max(highest_slot, this.slots[feature_uid].slot);
}
else {
undone.push(i);
}
return -1;
};
}
// Slot unslotted features.
// Do slotting.
for (var i = 0, len = undone.length; i < len; i++) {
var feature = features[undone[i]],
feature_uid = feature[0],
feature_start = feature[1],
feature_end = feature[2],
feature_name = feature[3],
// Where to start, end drawing on screen.
f_start = Math.floor( feature_start * w_scale ),
f_end = Math.ceil( feature_end * w_scale ),
text_len = this.measureText(feature_name).width,
text_align;
// Update start, end drawing locations to include feature name.
// Try to put the name on the left, if not, put on right.
if (feature_name !== undefined && this.include_label ) {
// Add gap for label spacing and extra pack space padding
// TODO: Fix constants
text_len += (LABEL_SPACING + PACK_SPACING);
if (f_start - text_len >= 0) {
f_start -= text_len;
text_align = "left";
} else {
f_end += text_len;
text_align = "right";
}
}
feature = features[undone[i]];
feature_uid = feature[0];
var draw_coords = this._get_draw_coords(feature);
// Find slot.
var slot_num = find_slot(f_start, f_end);
var slot_num = this._find_slot(draw_coords);
/*
if (slot_num < 0) {
TODO: this is not yet working --
console.log(feature_uid, "looking for slot with text on the right");
// Slot not found. If text was on left, try on right and see
// if slot can be found.
// TODO: are there any checks we need to do to ensure that text
// will fit on tile?
if (text_align === "left") {
f_start -= text_len;
f_end -= text_len;
text_align = "right";
slot_num = find_slot(f_start, f_end);
}
if (slot_num >= 0) {
console.log(feature_uid, "found slot with text on the right");
}
}
*/
// Do slotting.
if (slot_num >= 0) {
// Add current feature to slot.
if (start_end_dct[slot_num] === undefined) {
start_end_dct[slot_num] = [];
}
start_end_dct[slot_num].push([f_start, f_end]);
this.slots[feature_uid] = slot_num;
start_end_dct[slot_num].push(draw_coords);
this.slots[feature_uid] = new SlottedInfo(slot_num, feature);
highest_slot = Math.max(highest_slot, slot_num);
}
}
+39
View File
@@ -0,0 +1,39 @@
from galaxy import eggs
eggs.require( "mock" )
import mock
from galaxy.workflow import modules
from .workflow_support import MockTrans
def test_input_has_no_errors():
trans = MockTrans()
input_step_module = modules.module_factory.new( trans, 'data_input' )
assert not input_step_module.get_errors()
def test_valid_new_tool_has_no_errors():
trans = MockTrans()
mock_tool = mock.Mock()
trans.app.toolbox.tools[ "cat1" ] = mock_tool
tool_module = modules.module_factory.new( trans, 'tool', tool_id="cat1" )
assert not tool_module.get_errors()
def test_missing_tool_has_errors():
trans = MockTrans()
tool_dict = { "type": "tool", "tool_id": "cat1" }
tool_module = modules.module_factory.from_dict( trans, tool_dict )
assert tool_module.get_errors()
def test_cannot_create_tool_modules_for_missing_tools():
trans = MockTrans()
exception = False
try:
modules.module_factory.new( trans, 'tool', tool_id="cat1" )
except Exception:
exception = True
assert exception
+11
View File
@@ -17,3 +17,14 @@ class TestApp( object ):
"sqlite:///:memory:",
create_tables=True
)
self.toolbox = TestToolbox()
class TestToolbox( object ):
def __init__( self ):
self.tools = {}
def get_tool( self, tool_id ):
# Real tool box returns None of missing tool also
return self.tools.get( tool_id, None )