Merge pull request #1251 from jmchilton/workflow_labels_2

Workflow Editor UI for Step Labels
This commit is contained in:
Martin Cech
2015-12-08 12:04:26 -05:00
23 changed files with 325 additions and 163 deletions
@@ -1,4 +1,8 @@
define(['mvc/workflow/workflow-connector'], function( Connector ) {
define([
'mvc/workflow/workflow-connector',
'libs/toastr'
],
function( Connector, Toastr ) {
function Workflow( app, canvas_container ) {
this.app = app;
this.canvas_container = canvas_container;
@@ -7,10 +11,49 @@ define(['mvc/workflow/workflow-connector'], function( Connector ) {
this.name = null;
this.has_changes = false;
this.active_form_has_changes = false;
this.nodeLabels = {}; // TODO: track and enforce output labels also
}
$.extend( Workflow.prototype, {
create_node: function ( type, title_text, tool_id ) {
var node = this.app.prebuildNode( type, title_text, tool_id );
canLabelNodeWith: function( label ) {
if( label ) {
return ! (label in this.nodeLabels);
} else {
// empty labels are non-exclusive, so allow this one.
return true;
}
},
registerNodeLabel: function( label ) {
if( label ) {
this.nodeLabels[label] = true;
}
},
unregisterNodeLabel: function( label ) {
if( label ) {
delete this.nodeLabels[label];
}
},
updateNodeLabel: function( fromLabel, toLabel ) {
if( fromLabel ) {
this.unregisterNodeLabel( fromLabel );
}
if( ! this.canLabelNodeWith( toLabel ) ) {
Toastr.warning("Workflow contains duplicate node labels " + toLabel + ". This must be fixed before it can be saved.");
}
if( toLabel ) {
this.registerNodeLabel( toLabel );
}
},
attemptUpdateNodeLabel: function( node, label ) {
if( this.canLabelNodeWith( label ) ) {
node.setLabel( label );
return true;
} else {
return false;
}
},
create_node: function ( type, title_text, content_id ) {
var node = this.app.prebuildNode( type, title_text, content_id );
this.add_node( node );
this.fit_canvas_to_nodes();
this.app.canvas_manager.draw_overview();
@@ -131,7 +174,7 @@ define(['mvc/workflow/workflow-connector'], function( Connector ) {
var node_data = {
id : node.id,
type : node.type,
tool_id : node.tool_id,
tool_id : node.content_id,
tool_state : node.tool_state,
tool_errors : node.tool_errors,
input_connections : input_connections,
@@ -383,4 +426,4 @@ define(['mvc/workflow/workflow-connector'], function( Connector ) {
}
});
return Workflow;
});
});
@@ -124,6 +124,10 @@ define(['mvc/workflow/workflow-view-node'], function( NodeView ) {
// Remove active class
$(element).removeClass( "toolForm-active" );
},
setLabel: function(label) {
this.app.workflow.updateNodeLabel(this.label, label);
this.label = label || null;
},
init_field_data : function ( data ) {
if ( data.type ) {
this.type = data.type;
@@ -135,7 +139,7 @@ define(['mvc/workflow/workflow-view-node'], function( NodeView ) {
this.tooltip = data.tooltip ? data.tooltip : "";
this.annotation = data.annotation;
this.post_job_actions = data.post_job_actions ? data.post_job_actions : {};
this.label = data.label;
this.setLabel(data.label);
this.uuid = data.uuid;
this.workflow_outputs = data.workflow_outputs ? data.workflow_outputs : [];
@@ -165,6 +169,8 @@ define(['mvc/workflow/workflow-view-node'], function( NodeView ) {
this.form_html = data.form_html;
this.tool_errors = data.tool_errors;
this.annotation = data['annotation'];
this.setLabel(data.label);
if( "post_job_actions" in data ) {
// Won't be present in response for data inputs
var pja_in = $.parseJSON(data.post_job_actions);
@@ -184,6 +190,7 @@ define(['mvc/workflow/workflow-view-node'], function( NodeView ) {
unusedView.el.terminal.destroy();
} );
nodeView.terminalViews = newTerminalViews;
node.nodeView.render();
// In general workflow editor assumes tool outputs don't change in # or
// type (not really valid right?) but adding special logic here for
// data collection input parameters that can have their collection
@@ -11,6 +11,14 @@ define(['mvc/workflow/workflow-view-terminals', 'mvc/workflow/workflow-view-data
},
render: function() {
var label = this.node.label;
var title;
if(label) {
title = label;
} else {
title = this.node.name;
}
this.$el.find(".nodeTitle").text(title);
this.renderToolErrors();
this.$el.css( "width", Math.min(250, Math.max( this.$el.width(), this.output_width )));
},
@@ -7,7 +7,9 @@ define([
'mvc/workflow/workflow-node',
'mvc/tool/tools-form-workflow',
'utils/async-save-text',
], function( Utils, Globals, Workflow, WorkflowCanvas, Node, ToolsForm, async_save_text ){
'libs/toastr',
'ui/editable-text'
], function( Utils, Globals, Workflow, WorkflowCanvas, Node, ToolsForm, async_save_text, Toastr ){
// Reset tool search to start state.
function reset_tool_search( initValue ) {
@@ -44,6 +46,21 @@ function reset_tool_search( initValue ) {
}
}
NODE_ICONS = {
'tool': 'fa-wrench',
'data_input': 'fa-file-o',
'data_collection_input': 'fa-folder-o',
'pause': 'fa-pause'
}
add_node_icon = function($to_el, nodeType) {
var iconStyle = NODE_ICONS[nodeType];
if(iconStyle) {
var $icon = $('<i class="icon fa">&nbsp;</i>').addClass(iconStyle);
$to_el.before($icon);
}
}
// Really a shell of a real backbone view, but refactoring in the right
// direction I think.
@@ -61,7 +78,33 @@ EditorFormView = Backbone.View.extend({
this.setElement($el);
if (node && node.id != 'no-node') {
$el.find('.toolForm:first').after(this._genericStepAttributesTemplate( node ));
$el.find('table:first').after(this._genericStepAttributesTemplate( node ));
var nodeType = node.type;
add_node_icon($el.find('.portlet-title-text'), nodeType);
var $titleText = $el.find(".portlet-title-text");
$titleText.data('last-value', $titleText.text());
$titleText.make_text_editable({
on_finish: function( newLabel ){
var lastValue = $titleText.data("last-value");
if( newLabel == lastValue ) {
return;
}
var workflow = workflowView.workflow;
if( workflow.attemptUpdateNodeLabel( node, newLabel ) ) {
$el.find("input[name='label']").val(newLabel);
$titleText.data("last-value", newLabel);
$el.find('form').submit();
if(newLabel == "") {
// If label unset restore default name as title.
$titleText.text(node.name);
}
} else {
Toastr.warning("Step label " + newLabel + " already exists, cannot update label.");
$titleText.text(lastValue);
}
}
});
($el.find( 'form' ).length > 0) && $el.find( 'form' ).ajaxForm( {
type: 'POST',
dataType: 'json',
@@ -525,12 +568,10 @@ EditorFormView = Backbone.View.extend({
cc.css( { left: left, top: top } );
},
// Add a new step to the workflow by tool id
add_node_for_tool: function ( id, title ) {
node = this.workflow.create_node( 'tool', title, id );
_moduleInitAjax: function(node, request_data) {
$.ajax( {
url: this.urls.get_new_module_info,
data: { type: "tool", tool_id: id, "_": "true" },
data: request_data,
global: false,
dataType: "json",
success: function( data ) {
@@ -546,23 +587,21 @@ EditorFormView = Backbone.View.extend({
});
},
// Add a new step to the workflow by tool id
add_node_for_tool: function ( id, title ) {
node = this.workflow.create_node( 'tool', title, id );
this._moduleInitAjax(node, { type: "tool", content_id: id, "_": "true" });
},
// Add a new step to the workflow by tool id
add_node_for_subworkflow: function ( id, title ) {
node = this.workflow.create_node( 'subworkflow', title, id );
this._moduleInitAjax(node, { type: "subworkflow", subworkflow_id: id, "_": "true" });
},
add_node_for_module: function ( type, title ) {
node = this.workflow.create_node( type, title );
$.ajax( {
url: this.urls.get_new_module_info,
data: { type: type, "_": "true" },
dataType: "json",
success: function( data ) {
node.init_field_data( data );
},
error: function( x, e ) {
var m = "error loading field data"
if ( x.status == 0 ) {
m += ", server unavailable"
}
node.error( m );
}
});
this._moduleInitAjax(node, { type: type, "_": "true" });
},
// This function preloads how to display known pja's.
@@ -704,22 +743,27 @@ EditorFormView = Backbone.View.extend({
return ( this.type_to_type[child] ) && ( parent in this.type_to_type[child] );
},
prebuildNode: function ( type, title_text, tool_id ) {
$newNodeElement: function(type, title_text) {
var $f = $("<div class='toolForm toolFormInCanvas'></div>");
var $title = $("<div class='toolFormTitle unselectable'><span class='nodeTitle'>" + title_text + "</div></div>" );
add_node_icon($title.find('.nodeTitle'), type);
$f.append( $title );
$f.css( "left", $(window).scrollLeft() + 20 );
$f.css( "top", $(window).scrollTop() + 20 );
var $b = $("<div class='toolFormBody'></div>");
$f.append($b);
return $f
},
prebuildNode: function ( type, title_text, content_id ) {
var self = this;
var f = $("<div class='toolForm toolFormInCanvas'></div>");
var node = new Node( this, { element: f } );
var $f = this.$newNodeElement( type, title_text );
var node = new Node( this, { element: $f } );
node.type = type;
if ( type == 'tool' ) {
node.tool_id = tool_id;
}
var title = $("<div class='toolFormTitle unselectable'>" + title_text + "</div>" );
f.append( title );
f.css( "left", $(window).scrollLeft() + 20 ); f.css( "top", $(window).scrollTop() + 20 );
var b = $("<div class='toolFormBody'></div>");
node.content_id = content_id;
var tmp = "<div><img height='16' align='middle' src='" + Galaxy.root + "static/images/loading_small_white_bg.gif'/> loading tool info...</div>";
b.append( tmp );
$f.find(".toolFormBody").append(tmp);
node.form_html = tmp;
f.append( b );
// Fix width to computed width
// Now add floats
var buttons = $("<div class='buttons' style='float: right;'></div>");
@@ -727,17 +771,17 @@ EditorFormView = Backbone.View.extend({
node.destroy();
}));
// Place inside container
f.appendTo( "#canvas-container" );
$f.appendTo( "#canvas-container" );
// Position in container
var o = $("#canvas-container").position();
var p = $("#canvas-container").parent();
var width = f.width();
var height = f.height();
f.css( { left: ( - o.left ) + ( p.width() / 2 ) - ( width / 2 ), top: ( - o.top ) + ( p.height() / 2 ) - ( height / 2 ) } );
buttons.prependTo( title );
var width = $f.width();
var height = $f.height();
$f.css( { left: ( - o.left ) + ( p.width() / 2 ) - ( width / 2 ), top: ( - o.top ) + ( p.height() / 2 ) - ( height / 2 ) } );
buttons.prependTo( $f.find(".toolFormTitle" ) );
width += ( buttons.width() + 10 );
f.css( "width", width );
$(f).bind( "dragstart", function() {
$f.css( "width", width );
$f.bind( "dragstart", function() {
self.workflow.activate_node( node );
}).bind( "dragend", function() {
self.workflow.node_changed( this );
+11 -5
View File
@@ -112,6 +112,16 @@ def get_class( class_name ):
return item_class
def decode_id(app, id):
try:
# note: use str - occasionally a fully numeric id will be placed in post body and parsed as int via JSON
# resulting in error for valid id
return app.security.decode_id( str( id ) )
except ( ValueError, TypeError ):
msg = "Malformed id ( %s ) specified, unable to decode" % ( str( id ) )
raise exceptions.MalformedId( msg, id=str( id ) )
def get_object( trans, id, class_name, check_ownership=False, check_accessible=False, deleted=None ):
"""
Convenience method to get a model object with the specified checks. This is
@@ -119,11 +129,7 @@ def get_object( trans, id, class_name, check_ownership=False, check_accessible=F
controller mixin code - however whenever possible the managers for a
particular model should be used to load objects.
"""
try:
decoded_id = trans.security.decode_id( id )
except:
raise exceptions.MessageException( "Malformed %s id ( %s ) specified, unable to decode"
% ( class_name, str( id ) ), type='error' )
decoded_id = decode_id(trans.app, id)
try:
item_class = get_class( class_name )
assert item_class is not None
+47 -7
View File
@@ -2,12 +2,18 @@ from __future__ import absolute_import
from collections import namedtuple
import json
import uuid
from sqlalchemy import and_
from galaxy import model
from galaxy import util
from galaxy import exceptions
from galaxy.model.item_attrs import UsesAnnotations
from galaxy.workflow import modules
from .base import decode_id
# For WorkflowContentManager
from galaxy.util.sanitize_html import sanitize_html
from galaxy.workflow.steps import attach_ordered_steps
@@ -26,6 +32,38 @@ class WorkflowsManager( object ):
def __init__( self, app ):
self.app = app
def get_stored_workflow( self, trans, workflow_id ):
""" Use a supplied ID (UUID or encoded stored workflow ID) to find
a workflow.
"""
if util.is_uuid(workflow_id):
# see if they have passed in the UUID for a workflow that is attached to a stored workflow
workflow_uuid = uuid.UUID(workflow_id)
stored_workflow = trans.sa_session.query(trans.app.model.StoredWorkflow).filter( and_(
trans.app.model.StoredWorkflow.latest_workflow_id == trans.app.model.Workflow.id,
trans.app.model.Workflow.uuid == workflow_uuid
)).first()
if stored_workflow is None:
raise exceptions.ObjectNotFound( "Workflow not found: %s" % workflow_id )
else:
workflow_id = decode_id( self.app, workflow_id )
query = trans.sa_session.query( trans.app.model.StoredWorkflow )
stored_workflow = query.get( workflow_id )
if stored_workflow is None:
raise exceptions.ObjectNotFound( "No such workflow found." )
return stored_workflow
def get_stored_accessible_workflow( self, trans, workflow_id ):
stored_workflow = self.get_stored_workflow( trans, workflow_id )
# check to see if user has permissions to selected workflow
if stored_workflow.user != trans.user and not trans.user_is_admin():
if trans.sa_session.query(trans.app.model.StoredWorkflowUserShareAssociation).filter_by(user=trans.user, stored_workflow=stored_workflow).count() == 0:
message = "Workflow is not owned by or shared with current user"
raise exceptions.ItemAccessibilityException( message )
return stored_workflow
def check_security( self, trans, has_workflow, check_ownership=True, check_accessible=True):
""" check accessibility or ownership of workflows, storedworkflows, and
workflowinvocations. Throw an exception or returns True if user has
@@ -352,7 +390,7 @@ class WorkflowContentsManager(UsesAnnotations):
step_dict = {
'id': step.order_index,
'type': module.type,
'tool_id': module.get_tool_id(),
'tool_id': module.get_content_id(),
'name': module.get_name(),
'tool_state': module.get_state(),
'tooltip': module.get_tooltip( static_path=url_for( '/static' ) ),
@@ -395,11 +433,13 @@ class WorkflowContentsManager(UsesAnnotations):
action_arguments=pja.action_arguments
)
step_dict['post_job_actions'] = pja_dict
# workflow outputs
outputs = []
for output in step.workflow_outputs:
outputs.append({"output_name": output.output_name})
step_dict['workflow_outputs'] = outputs
# workflow outputs
outputs = []
for output in step.workflow_outputs:
outputs.append({"output_name": output.output_name})
step_dict['workflow_outputs'] = outputs
# Encode input connections as dictionary
input_conn_dict = {}
for conn in input_connections:
@@ -453,7 +493,7 @@ class WorkflowContentsManager(UsesAnnotations):
step_dict = {
'id': step.order_index,
'type': module.type,
'tool_id': module.get_tool_id(),
'tool_id': module.get_content_id(),
'tool_version': step.tool_version,
'name': module.get_name(),
'tool_state': module.get_state( secure=False ),
+1 -8
View File
@@ -11,7 +11,6 @@ from paste.httpexceptions import HTTPBadRequest, HTTPInternalServerError
from paste.httpexceptions import HTTPNotImplemented, HTTPRequestRangeNotSatisfiable
from galaxy.exceptions import ItemAccessibilityException, ItemDeletionException, ItemOwnershipException
from galaxy.exceptions import MessageException
from galaxy import exceptions
from galaxy import web
from galaxy import model
@@ -94,13 +93,7 @@ class BaseController( object ):
# ---- parsing query params
def decode_id( self, id ):
try:
# note: use str - occasionally a fully numeric id will be placed in post body and parsed as int via JSON
# resulting in error for valid id
return self.app.security.decode_id( str( id ) )
except ( ValueError, TypeError ):
msg = "Malformed id ( %s ) specified, unable to decode" % ( str( id ) )
raise exceptions.MalformedId( msg, id=str( id ) )
return managers_base.decode_id( self.app, id )
def encode_all_ids( self, trans, rval, recursive=False ):
"""
+3 -27
View File
@@ -4,11 +4,10 @@ API operations for Workflows
from __future__ import absolute_import
import uuid
import logging
import copy
import urllib
from sqlalchemy import and_, desc, false, or_, true
from sqlalchemy import desc, false, or_, true
from galaxy import exceptions, util
from galaxy.model.item_attrs import UsesAnnotations
from galaxy.managers import histories
@@ -570,33 +569,10 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn
)
def __get_stored_accessible_workflow( self, trans, workflow_id ):
stored_workflow = self.__get_stored_workflow( trans, workflow_id )
# check to see if user has permissions to selected workflow
if stored_workflow.user != trans.user and not trans.user_is_admin():
if trans.sa_session.query(trans.app.model.StoredWorkflowUserShareAssociation).filter_by(user=trans.user, stored_workflow=stored_workflow).count() == 0:
message = "Workflow is not owned by or shared with current user"
raise exceptions.ItemAccessibilityException( message )
return stored_workflow
return self.workflow_manager.get_stored_accessible_workflow( trans, workflow_id )
def __get_stored_workflow( self, trans, workflow_id ):
if util.is_uuid(workflow_id):
# see if they have passed in the UUID for a workflow that is attached to a stored workflow
workflow_uuid = uuid.UUID(workflow_id)
stored_workflow = trans.sa_session.query(trans.app.model.StoredWorkflow).filter( and_(
trans.app.model.StoredWorkflow.latest_workflow_id == trans.app.model.Workflow.id,
trans.app.model.Workflow.uuid == workflow_uuid
)).first()
if stored_workflow is None:
raise exceptions.ObjectNotFound( "Workflow not found: %s" % workflow_id )
else:
workflow_id = self.decode_id( workflow_id )
query = trans.sa_session.query( trans.app.model.StoredWorkflow )
stored_workflow = query.get( workflow_id )
if stored_workflow is None:
raise exceptions.ObjectNotFound( "No such workflow found." )
return stored_workflow
return self.workflow_manager.get_stored_workflow( trans, workflow_id )
def __encode_invocation( self, trans, invocation, view="element", step_details=False ):
return self.encode_all_ids(
@@ -613,7 +613,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix
return trans.fill_template( "workflow/editor.mako", stored=stored, annotation=self.get_item_annotation_str( trans.sa_session, trans.user, stored ) )
@web.json
def editor_form_post( self, trans, type='tool', tool_id=None, annotation=None, **incoming ):
def editor_form_post( self, trans, type='tool', tool_id=None, annotation=None, label=None, **incoming ):
"""
Accepts a tool state and incoming values, and generates a new tool
form and some additional information, packed into a json dictionary.
@@ -625,12 +625,14 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix
module = module_factory.from_dict( trans, {
'type': type,
'tool_id': tool_id,
'tool_state': tool_state
'tool_state': tool_state,
'label': label or None,
} )
# update module state
module.update_state( incoming )
if type == 'tool':
return {
'label': module.label,
'tool_state': module.get_state(),
'data_inputs': module.get_data_inputs(),
'data_outputs': module.get_data_outputs(),
@@ -641,6 +643,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix
}
else:
return {
'label': module.label,
'tool_state': module.get_state(),
'data_inputs': module.get_data_inputs(),
'data_outputs': module.get_data_outputs(),
@@ -664,7 +667,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix
return {
'type': module.type,
'name': module.get_name(),
'tool_id': module.get_tool_id(),
'tool_id': module.get_content_id(),
'tool_state': module.get_state(),
'tool_model': tool_model,
'tooltip': module.get_tooltip( static_path=url_for( '/static' ) ),
+19 -9
View File
@@ -42,7 +42,7 @@ class WorkflowModule( object ):
# ---- Creating modules from various representations ---------------------
@classmethod
def new( Class, trans, tool_id=None ):
def new( Class, trans, content_id=None ):
"""
Create a new instance of the module with default state
"""
@@ -73,7 +73,10 @@ class WorkflowModule( object ):
def get_name( self ):
return self.name
def get_tool_id( self ):
def get_content_id( self ):
""" If this component has an identifier external to the step (such
as a tool or another workflow) return the identifier for that content.
"""
return None
def get_tooltip( self, static_path='' ):
@@ -194,9 +197,10 @@ class WorkflowModule( object ):
class SimpleWorkflowModule( WorkflowModule ):
@classmethod
def new( Class, trans, tool_id=None ):
def new( Class, trans, content_id=None ):
module = Class( trans )
module.state = Class.default_state()
module.label = None
return module
@classmethod
@@ -204,12 +208,14 @@ class SimpleWorkflowModule( WorkflowModule ):
module = Class( trans )
state = loads( d["tool_state"] )
module.recover_state( state )
module.label = d.get("label", None) or None
return module
@classmethod
def from_workflow_step( Class, trans, step ):
module = Class( trans )
module.recover_state( step.tool_inputs )
module.label = step.label
return module
@classmethod
@@ -502,12 +508,13 @@ class ToolModule( WorkflowModule ):
self.errors[ tool_id ] = 'Tool unavailable'
@classmethod
def new( Class, trans, tool_id=None ):
module = Class( trans, tool_id )
def new( Class, trans, content_id=None ):
module = Class( trans, content_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
error_message = "Attempted to create new workflow module for invalid tool_id, no tool with id - %s." % content_id
raise Exception( error_message )
module.state = module.tool.new_state( trans )
module.label = None
return module
@classmethod
@@ -516,6 +523,7 @@ class ToolModule( WorkflowModule ):
tool_version = str( d.get( 'tool_version', None ) )
module = Class( trans, tool_id, tool_version=tool_version )
module.state = galaxy.tools.DefaultToolState()
module.label = d.get("label", None) or None
if module.tool is not None:
message = ""
if tool_id != module.tool_id:
@@ -560,6 +568,7 @@ class ToolModule( WorkflowModule ):
module.recover_state( step.tool_inputs )
module.errors = step.tool_errors
module.workflow_outputs = step.workflow_outputs
module.label = step.label or None
pjadict = {}
for pja in step.post_job_actions:
pjadict[pja.action_type] = pja
@@ -623,7 +632,7 @@ class ToolModule( WorkflowModule ):
return self.tool.name
return 'unavailable'
def get_tool_id( self ):
def get_content_id( self ):
return self.tool_id
def get_tool_version( self ):
@@ -735,6 +744,7 @@ class ToolModule( WorkflowModule ):
# set. We also need to make sure all datasets have a dummy value
# for dependencies to see
self.label = incoming.get("label", None) or None
self.post_job_actions = ActionBox.handle_incoming(incoming)
make_runtime_key = incoming.get( 'make_runtime', None )
@@ -951,13 +961,13 @@ class WorkflowModuleFactory( object ):
def __init__( self, module_types ):
self.module_types = module_types
def new( self, trans, type, tool_id=None ):
def new( self, trans, type, content_id=None ):
"""
Return module for type and (optional) tool_id intialized with
new / default state.
"""
assert type in self.module_types
return self.module_types[type].new( trans, tool_id )
return self.module_types[type].new( trans, content_id )
def from_dict( self, trans, d, **kwargs ):
"""
+2 -2
View File
@@ -235,8 +235,8 @@ class WorkflowProgress( object ):
if not hasattr( step, 'module' ):
self.module_injector.inject( step )
if step_id not in step_states:
template = "Workflow invocation [%s] has no step state for step id [%s]. States are [%s]."
message = template % (self.workflow_invocation.id, step_id, step_states)
template = "Workflow invocation [%s] has no step state for step id [%s]. States ids are %s."
message = template % (self.workflow_invocation.id, step_id, step_states.keys())
raise Exception(message)
runtime_state = step_states[ step_id ].value
step.state = step.module.recover_runtime_state( runtime_state )
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
{"version":3,"file":"workflow-view-node.js","sources":["../../../src/mvc/workflow/workflow-view-node.js"],"names":["define","TerminalViews","DataViews","Backbone","View","extend","initialize","options","this","node","output_width","Math","max","$el","width","tool_body","find","remove","newInputsDiv","appendTo","terminalViews","outputTerminlViews","render","renderToolErrors","css","min","tool_errors","addClass","removeClass","$","updateMaxWidth","newWidth","addRule","append","addDataInput","input","body","skipResize","terminalView","name","terminalViewClass","input_type","InputCollectionTerminalView","InputTerminalView","el","terminal","destroy","update","destroyInvalidConnections","terminalElement","inputView","DataInputView","nodeView","ib","prepend","terminalElements","addDataOutput","output","OutputCollectionTerminalView","OutputTerminalView","outputView","DataOutputView","updateDataOutput","outputTerminal","output_terminals"],"mappings":"AAAAA,QAAQ,uCAAwC,mCAAoC,SAAUC,EAAeC,GACzG,MAAOC,UAASC,KAAKC,QACjBC,WAAY,SAAUC,GAClBC,KAAKC,KAAOF,EAAQE,KACpBD,KAAKE,aAAeC,KAAKC,IAAI,IAAKJ,KAAKK,IAAIC,SAC3CN,KAAKO,UAAYP,KAAKK,IAAIG,KAAM,iBAChCR,KAAKO,UAAUC,KAAM,OAAQC,SAC7BT,KAAKU,eAAeC,SAAUX,KAAKO,WACnCP,KAAKY,iBACLZ,KAAKa,uBAGTC,OAAQ,WACJd,KAAKe,mBACLf,KAAKK,IAAIW,IAAK,QAASb,KAAKc,IAAI,IAAKd,KAAKC,IAAKJ,KAAKK,IAAIC,QAASN,KAAKE,iBAG1Ea,iBAAkB,WACTf,KAAKC,KAAKiB,YACXlB,KAAKK,IAAIc,SAAU,mBAEnBnB,KAAKK,IAAIe,YAAa,oBAI9BV,aAAc,WACV,MAAOW,GAAE,+BAGbC,eAAgB,SAAUC,GACtBvB,KAAKE,aAAeC,KAAKC,IAAKJ,KAAKE,aAAcqB,IAGrDC,QAAS,WACLxB,KAAKO,UAAUkB,OAAQJ,EAAG,8BAG9BK,aAAc,SAAUC,EAAOC,GAC3B,GAAIC,IAAa,CACXD,KACFA,EAAO5B,KAAKqB,EAAG,WAGfQ,GAAa,EAEjB,IAAIC,GAAe9B,KAAKY,cAAee,EAAMI,MACzCC,EAA0C,sBAApBL,EAAMM,WAAuCxC,EAAcyC,4BAA8BzC,EAAc0C,iBAKjI,KAJIL,GAAoBA,YAAwBE,KAC5CF,EAAaM,GAAGC,SAASC,UACzBR,EAAe,MAEbA,EAKC,CACH,GAAIO,GAAWP,EAAaM,GAAGC,QAC/BA,GAASE,OAAQZ,GACjBU,EAASG,gCAPTV,GAAe,GAAIE,IACf/B,KAAMD,KAAKC,KACX0B,MAAOA,GAOf3B,MAAKY,cAAee,EAAMI,MAASD,CACnC,IAAIW,GAAkBX,EAAaM,GAC/BM,EAAY,GAAIhD,GAAUiD,eAC1BF,gBAAiBA,EACjBd,MAAOA,EACPiB,SAAU5C,KACV6B,WAAYA,IAEZgB,EAAKH,EAAUrC,GAEnB,OADAuB,GAAKH,OAAQoB,EAAGC,QAAShB,EAAaiB,qBAC/BjB,GAGXkB,cAAe,SAAUC,GACrB,GAAIjB,GAAsBiB,EAAkB,WAAIxD,EAAcyD,6BAA+BzD,EAAc0D,mBACvGrB,EAAe,GAAIE,IACnB/B,KAAMD,KAAKC,KACXgD,OAAQA,IAERG,EAAa,GAAI1D,GAAU2D,gBAC3BJ,OAAUA,EACVR,gBAAmBX,EAAaM,GAChCQ,SAAY5C,MAEhBA,MAAKO,UAAUkB,OAAQ2B,EAAW/C,IAAIoB,OAAQK,EAAaiB,sBAG/DO,iBAAkB,SAAUL,GACxB,GAAIM,GAAiBvD,KAAKC,KAAKuD,iBAAkBP,EAAOlB,KACxDwB,GAAehB,OAAQU"}
{"version":3,"file":"workflow-view-node.js","sources":["../../../src/mvc/workflow/workflow-view-node.js"],"names":["define","TerminalViews","DataViews","Backbone","View","extend","initialize","options","this","node","output_width","Math","max","$el","width","tool_body","find","remove","newInputsDiv","appendTo","terminalViews","outputTerminlViews","render","title","label","name","text","renderToolErrors","css","min","tool_errors","addClass","removeClass","$","updateMaxWidth","newWidth","addRule","append","addDataInput","input","body","skipResize","terminalView","terminalViewClass","input_type","InputCollectionTerminalView","InputTerminalView","el","terminal","destroy","update","destroyInvalidConnections","terminalElement","inputView","DataInputView","nodeView","ib","prepend","terminalElements","addDataOutput","output","OutputCollectionTerminalView","OutputTerminalView","outputView","DataOutputView","updateDataOutput","outputTerminal","output_terminals"],"mappings":"AAAAA,QAAQ,uCAAwC,mCAAoC,SAAUC,EAAeC,GACzG,MAAOC,UAASC,KAAKC,QACjBC,WAAY,SAAUC,GAClBC,KAAKC,KAAOF,EAAQE,KACpBD,KAAKE,aAAeC,KAAKC,IAAI,IAAKJ,KAAKK,IAAIC,SAC3CN,KAAKO,UAAYP,KAAKK,IAAIG,KAAM,iBAChCR,KAAKO,UAAUC,KAAM,OAAQC,SAC7BT,KAAKU,eAAeC,SAAUX,KAAKO,WACnCP,KAAKY,iBACLZ,KAAKa,uBAGTC,OAAQ,WACJ,GACIC,GADAC,EAAQhB,KAAKC,KAAKe,KAGlBD,GADDC,EACSA,EAEAhB,KAAKC,KAAKgB,KAEtBjB,KAAKK,IAAIG,KAAK,cAAcU,KAAKH,GACjCf,KAAKmB,mBACLnB,KAAKK,IAAIe,IAAK,QAASjB,KAAKkB,IAAI,IAAKlB,KAAKC,IAAKJ,KAAKK,IAAIC,QAASN,KAAKE,iBAG1EiB,iBAAkB,WACTnB,KAAKC,KAAKqB,YACXtB,KAAKK,IAAIkB,SAAU,mBAEnBvB,KAAKK,IAAImB,YAAa,oBAI9Bd,aAAc,WACV,MAAOe,GAAE,+BAGbC,eAAgB,SAAUC,GACtB3B,KAAKE,aAAeC,KAAKC,IAAKJ,KAAKE,aAAcyB,IAGrDC,QAAS,WACL5B,KAAKO,UAAUsB,OAAQJ,EAAG,8BAG9BK,aAAc,SAAUC,EAAOC,GAC3B,GAAIC,IAAa,CACXD,KACFA,EAAOhC,KAAKyB,EAAG,WAGfQ,GAAa,EAEjB,IAAIC,GAAelC,KAAKY,cAAemB,EAAMd,MACzCkB,EAA0C,sBAApBJ,EAAMK,WAAuC3C,EAAc4C,4BAA8B5C,EAAc6C,iBAKjI,KAJIJ,GAAoBA,YAAwBC,KAC5CD,EAAaK,GAAGC,SAASC,UACzBP,EAAe,MAEbA,EAKC,CACH,GAAIM,GAAWN,EAAaK,GAAGC,QAC/BA,GAASE,OAAQX,GACjBS,EAASG,gCAPTT,GAAe,GAAIC,IACflC,KAAMD,KAAKC,KACX8B,MAAOA,GAOf/B,MAAKY,cAAemB,EAAMd,MAASiB,CACnC,IAAIU,GAAkBV,EAAaK,GAC/BM,EAAY,GAAInD,GAAUoD,eAC1BF,gBAAiBA,EACjBb,MAAOA,EACPgB,SAAU/C,KACViC,WAAYA,IAEZe,EAAKH,EAAUxC,GAEnB,OADA2B,GAAKH,OAAQmB,EAAGC,QAASf,EAAagB,qBAC/BhB,GAGXiB,cAAe,SAAUC,GACrB,GAAIjB,GAAsBiB,EAAkB,WAAI3D,EAAc4D,6BAA+B5D,EAAc6D,mBACvGpB,EAAe,GAAIC,IACnBlC,KAAMD,KAAKC,KACXmD,OAAQA,IAERG,EAAa,GAAI7D,GAAU8D,gBAC3BJ,OAAUA,EACVR,gBAAmBV,EAAaK,GAChCQ,SAAY/C,MAEhBA,MAAKO,UAAUsB,OAAQ0B,EAAWlD,IAAIwB,OAAQK,EAAagB,sBAG/DO,iBAAkB,SAAUL,GACxB,GAAIM,GAAiB1D,KAAKC,KAAK0D,iBAAkBP,EAAOnC,KACxDyC,GAAehB,OAAQU"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,2 +1,2 @@
define(["mvc/workflow/workflow-view-node"],function(a){var b=Backbone.Model.extend({initialize:function(a,b){this.app=a,this.element=b.element,this.input_terminals={},this.output_terminals={},this.tool_errors={},this.workflow_outputs=[]},getWorkflowOutput:function(a){return _.findWhere(this.workflow_outputs,{output_name:a})},isWorkflowOutput:function(a){return void 0!=this.getWorkflowOutput(a)},removeWorkflowOutput:function(a){for(;this.isWorkflowOutput(a);)this.workflow_outputs.splice(this.getWorkflowOutput(a),1)},addWorkflowOutput:function(a){this.isWorkflowOutput(a)||this.workflow_outputs.push({output_name:a})},connectedOutputTerminals:function(){return this._connectedTerminals(this.output_terminals)},_connectedTerminals:function(a){var b=[];return $.each(a,function(a,c){c.connectors.length>0&&b.push(c)}),b},hasConnectedOutputTerminals:function(){var a=this.output_terminals;for(var b in a)if(a[b].connectors.length>0)return!0;return!1},connectedMappedInputTerminals:function(){return this._connectedMappedTerminals(this.input_terminals)},hasConnectedMappedInputTerminals:function(){var a=this.input_terminals;for(var b in a){var c=a[b];if(c.connectors.length>0&&c.isMappedOver())return!0}return!1},_connectedMappedTerminals:function(a){var b=[];return $.each(a,function(a,c){var d=c.mapOver();d.isCollection&&c.connectors.length>0&&b.push(c)}),b},mappedInputTerminals:function(){return this._mappedTerminals(this.input_terminals)},_mappedTerminals:function(a){var b=[];return $.each(a,function(a,c){var d=c.mapOver();d.isCollection&&b.push(c)}),b},hasMappedOverInputTerminals:function(){var a=!1;return _.each(this.input_terminals,function(b){var c=b.mapOver();c.isCollection&&(a=!0)}),a},redraw:function(){$.each(this.input_terminals,function(a,b){b.redraw()}),$.each(this.output_terminals,function(a,b){b.redraw()})},destroy:function(){$.each(this.input_terminals,function(a,b){b.destroy()}),$.each(this.output_terminals,function(a,b){b.destroy()}),this.app.workflow.remove_node(this),$(this.element).remove()},make_active:function(){$(this.element).addClass("toolForm-active")},make_inactive:function(){var a=this.element.get(0);!function(b){b.removeChild(a),b.appendChild(a)}(a.parentNode),$(a).removeClass("toolForm-active")},init_field_data:function(b){b.type&&(this.type=b.type),this.name=b.name,this.form_html=b.form_html,this.tool_state=b.tool_state,this.tool_errors=b.tool_errors,this.tooltip=b.tooltip?b.tooltip:"",this.annotation=b.annotation,this.post_job_actions=b.post_job_actions?b.post_job_actions:{},this.label=b.label,this.uuid=b.uuid,this.workflow_outputs=b.workflow_outputs?b.workflow_outputs:[];var c=this,d=new a({el:this.element[0],node:c});c.nodeView=d,$.each(b.data_inputs,function(a,b){d.addDataInput(b)}),b.data_inputs.length>0&&b.data_outputs.length>0&&d.addRule(),$.each(b.data_outputs,function(a,b){d.addDataOutput(b)}),d.render(),this.app.workflow.node_changed(this,!0)},update_field_data:function(a){var b=this;if(nodeView=b.nodeView,this.tool_state=a.tool_state,this.form_html=a.form_html,this.tool_errors=a.tool_errors,this.annotation=a.annotation,"post_job_actions"in a){var c=$.parseJSON(a.post_job_actions);this.post_job_actions=c?c:{}}b.nodeView.renderToolErrors();var d=nodeView.$("div.inputs"),e=nodeView.newInputsDiv(),f={};_.each(a.data_inputs,function(a){var c=b.nodeView.addDataInput(a,e);f[a.name]=c}),_.each(_.difference(_.values(nodeView.terminalViews),_.values(f)),function(a){a.el.terminal.destroy()}),nodeView.terminalViews=f,1==a.data_outputs.length&&"collection_type"in a.data_outputs[0]&&nodeView.updateDataOutput(a.data_outputs[0]),d.replaceWith(e),this.markChanged(),this.redraw()},error:function(a){var b=$(this.element).find(".toolFormBody");b.find("div").remove();var c="<div style='color: red; text-style: italic;'>"+a+"</div>";this.form_html=c,b.html(c),this.app.workflow.node_changed(this)},markChanged:function(){this.app.workflow.node_changed(this)}});return b});
define(["mvc/workflow/workflow-view-node"],function(a){var b=Backbone.Model.extend({initialize:function(a,b){this.app=a,this.element=b.element,this.input_terminals={},this.output_terminals={},this.tool_errors={},this.workflow_outputs=[]},getWorkflowOutput:function(a){return _.findWhere(this.workflow_outputs,{output_name:a})},isWorkflowOutput:function(a){return void 0!=this.getWorkflowOutput(a)},removeWorkflowOutput:function(a){for(;this.isWorkflowOutput(a);)this.workflow_outputs.splice(this.getWorkflowOutput(a),1)},addWorkflowOutput:function(a){this.isWorkflowOutput(a)||this.workflow_outputs.push({output_name:a})},connectedOutputTerminals:function(){return this._connectedTerminals(this.output_terminals)},_connectedTerminals:function(a){var b=[];return $.each(a,function(a,c){c.connectors.length>0&&b.push(c)}),b},hasConnectedOutputTerminals:function(){var a=this.output_terminals;for(var b in a)if(a[b].connectors.length>0)return!0;return!1},connectedMappedInputTerminals:function(){return this._connectedMappedTerminals(this.input_terminals)},hasConnectedMappedInputTerminals:function(){var a=this.input_terminals;for(var b in a){var c=a[b];if(c.connectors.length>0&&c.isMappedOver())return!0}return!1},_connectedMappedTerminals:function(a){var b=[];return $.each(a,function(a,c){var d=c.mapOver();d.isCollection&&c.connectors.length>0&&b.push(c)}),b},mappedInputTerminals:function(){return this._mappedTerminals(this.input_terminals)},_mappedTerminals:function(a){var b=[];return $.each(a,function(a,c){var d=c.mapOver();d.isCollection&&b.push(c)}),b},hasMappedOverInputTerminals:function(){var a=!1;return _.each(this.input_terminals,function(b){var c=b.mapOver();c.isCollection&&(a=!0)}),a},redraw:function(){$.each(this.input_terminals,function(a,b){b.redraw()}),$.each(this.output_terminals,function(a,b){b.redraw()})},destroy:function(){$.each(this.input_terminals,function(a,b){b.destroy()}),$.each(this.output_terminals,function(a,b){b.destroy()}),this.app.workflow.remove_node(this),$(this.element).remove()},make_active:function(){$(this.element).addClass("toolForm-active")},make_inactive:function(){var a=this.element.get(0);!function(b){b.removeChild(a),b.appendChild(a)}(a.parentNode),$(a).removeClass("toolForm-active")},setLabel:function(a){this.app.workflow.updateNodeLabel(this.label,a),this.label=a||null},init_field_data:function(b){b.type&&(this.type=b.type),this.name=b.name,this.form_html=b.form_html,this.tool_state=b.tool_state,this.tool_errors=b.tool_errors,this.tooltip=b.tooltip?b.tooltip:"",this.annotation=b.annotation,this.post_job_actions=b.post_job_actions?b.post_job_actions:{},this.setLabel(b.label),this.uuid=b.uuid,this.workflow_outputs=b.workflow_outputs?b.workflow_outputs:[];var c=this,d=new a({el:this.element[0],node:c});c.nodeView=d,$.each(b.data_inputs,function(a,b){d.addDataInput(b)}),b.data_inputs.length>0&&b.data_outputs.length>0&&d.addRule(),$.each(b.data_outputs,function(a,b){d.addDataOutput(b)}),d.render(),this.app.workflow.node_changed(this,!0)},update_field_data:function(a){var b=this;if(nodeView=b.nodeView,this.tool_state=a.tool_state,this.form_html=a.form_html,this.tool_errors=a.tool_errors,this.annotation=a.annotation,this.setLabel(a.label),"post_job_actions"in a){var c=$.parseJSON(a.post_job_actions);this.post_job_actions=c?c:{}}b.nodeView.renderToolErrors();var d=nodeView.$("div.inputs"),e=nodeView.newInputsDiv(),f={};_.each(a.data_inputs,function(a){var c=b.nodeView.addDataInput(a,e);f[a.name]=c}),_.each(_.difference(_.values(nodeView.terminalViews),_.values(f)),function(a){a.el.terminal.destroy()}),nodeView.terminalViews=f,b.nodeView.render(),1==a.data_outputs.length&&"collection_type"in a.data_outputs[0]&&nodeView.updateDataOutput(a.data_outputs[0]),d.replaceWith(e),this.markChanged(),this.redraw()},error:function(a){var b=$(this.element).find(".toolFormBody");b.find("div").remove();var c="<div style='color: red; text-style: italic;'>"+a+"</div>";this.form_html=c,b.html(c),this.app.workflow.node_changed(this)},markChanged:function(){this.app.workflow.node_changed(this)}});return b});
//# sourceMappingURL=../../../maps/mvc/workflow/workflow-node.js.map
@@ -1,2 +1,2 @@
define(["mvc/workflow/workflow-view-terminals","mvc/workflow/workflow-view-data"],function(a,b){return Backbone.View.extend({initialize:function(a){this.node=a.node,this.output_width=Math.max(150,this.$el.width()),this.tool_body=this.$el.find(".toolFormBody"),this.tool_body.find("div").remove(),this.newInputsDiv().appendTo(this.tool_body),this.terminalViews={},this.outputTerminlViews={}},render:function(){this.renderToolErrors(),this.$el.css("width",Math.min(250,Math.max(this.$el.width(),this.output_width)))},renderToolErrors:function(){this.node.tool_errors?this.$el.addClass("tool-node-error"):this.$el.removeClass("tool-node-error")},newInputsDiv:function(){return $("<div class='inputs'></div>")},updateMaxWidth:function(a){this.output_width=Math.max(this.output_width,a)},addRule:function(){this.tool_body.append($("<div class='rule'></div>"))},addDataInput:function(c,d){var e=!0;d||(d=this.$(".inputs"),e=!1);var f=this.terminalViews[c.name],g="dataset_collection"==c.input_type?a.InputCollectionTerminalView:a.InputTerminalView;if(!f||f instanceof g||(f.el.terminal.destroy(),f=null),f){var h=f.el.terminal;h.update(c),h.destroyInvalidConnections()}else f=new g({node:this.node,input:c});this.terminalViews[c.name]=f;var i=f.el,j=new b.DataInputView({terminalElement:i,input:c,nodeView:this,skipResize:e}),k=j.$el;return d.append(k.prepend(f.terminalElements())),f},addDataOutput:function(c){var d=c.collection?a.OutputCollectionTerminalView:a.OutputTerminalView,e=new d({node:this.node,output:c}),f=new b.DataOutputView({output:c,terminalElement:e.el,nodeView:this});this.tool_body.append(f.$el.append(e.terminalElements()))},updateDataOutput:function(a){var b=this.node.output_terminals[a.name];b.update(a)}})});
define(["mvc/workflow/workflow-view-terminals","mvc/workflow/workflow-view-data"],function(a,b){return Backbone.View.extend({initialize:function(a){this.node=a.node,this.output_width=Math.max(150,this.$el.width()),this.tool_body=this.$el.find(".toolFormBody"),this.tool_body.find("div").remove(),this.newInputsDiv().appendTo(this.tool_body),this.terminalViews={},this.outputTerminlViews={}},render:function(){var a,b=this.node.label;a=b?b:this.node.name,this.$el.find(".nodeTitle").text(a),this.renderToolErrors(),this.$el.css("width",Math.min(250,Math.max(this.$el.width(),this.output_width)))},renderToolErrors:function(){this.node.tool_errors?this.$el.addClass("tool-node-error"):this.$el.removeClass("tool-node-error")},newInputsDiv:function(){return $("<div class='inputs'></div>")},updateMaxWidth:function(a){this.output_width=Math.max(this.output_width,a)},addRule:function(){this.tool_body.append($("<div class='rule'></div>"))},addDataInput:function(c,d){var e=!0;d||(d=this.$(".inputs"),e=!1);var f=this.terminalViews[c.name],g="dataset_collection"==c.input_type?a.InputCollectionTerminalView:a.InputTerminalView;if(!f||f instanceof g||(f.el.terminal.destroy(),f=null),f){var h=f.el.terminal;h.update(c),h.destroyInvalidConnections()}else f=new g({node:this.node,input:c});this.terminalViews[c.name]=f;var i=f.el,j=new b.DataInputView({terminalElement:i,input:c,nodeView:this,skipResize:e}),k=j.$el;return d.append(k.prepend(f.terminalElements())),f},addDataOutput:function(c){var d=c.collection?a.OutputCollectionTerminalView:a.OutputTerminalView,e=new d({node:this.node,output:c}),f=new b.DataOutputView({output:c,terminalElement:e.el,nodeView:this});this.tool_body.append(f.$el.append(e.terminalElements()))},updateDataOutput:function(a){var b=this.node.output_terminals[a.name];b.update(a)}})});
//# sourceMappingURL=../../../maps/mvc/workflow/workflow-view-node.js.map
File diff suppressed because one or more lines are too long
@@ -1,12 +1,31 @@
<form name="${form.name}" action="${h.url_for( controller='workflow', action='editor_form_post' )}" method="post">
<div class="toolForm">
<div class="toolFormTitle">${form.title}</div>
<div class="toolFormBody">
<%
from xml.sax.saxutils import escape
label = module.label
if label is not None:
title = label
else:
title = form.title
%>
<div class="ui-portlet-narrow">
<div class="portlet-header">
<div class="portlet-title">
<span class="portlet-title-text"><b>${title}</b></span>
</div>
</div>
<div class="portlet-content">
<div class="content">
<div class="ui-margin-top"></div>
<div>
<input type="hidden" name="type" value="${module.type}" />
<input type="hidden" name="label" value="${escape(label or '')}" />
<table class="ui-table-plain">
<thead></thead>
<tbody>
%if form.inputs:
%for input in form.inputs:
<%
cls = "form-row"
cls = "section-row"
if input.error:
cls += " form-row-error"
extra_attributes = ""
@@ -16,38 +35,37 @@
if input.type:
type_attribute = "type=\"input.type\""
%>
<div class="${cls}">
<label>
<tr class="${cls}"><td><div class="ui-table-form-element">
<div class="ui-table-form-title">
${input.label}:
</label>
<div style="float: left; width: 250px; margin-right: 10px;">
</div>
<div class="ui-table-form-field" style="display: block;">
<input ${type_attribute} name="${input.name | h}" value="${input.value | h}" size="30" ${extra_attributes}>
%if hasattr( input, "body_html" ):
${input.body_html()}
</input>
%endif
</div>
%if input.error:
<div style="float: left; color: red; font-weight: bold; padding-top: 1px; padding-bottom: 3px;">
<div style="width: 300px;"><img style="vertical-align: middle;" src="${h.url_for('/static/style/error_small.png')}">&nbsp;<span style="vertical-align: middle;">${input.error}</span></div>
</div>
%endif
%if input.error:
<div style="float: left; color: red; font-weight: bold; padding-top: 1px; padding-bottom: 3px;">
<div style="width: 300px;"><img style="vertical-align: middle;" src="${h.url_for('/static/style/error_small.png')}">&nbsp;<span style="vertical-align: middle;">${input.error}</span></div>
</div>
%endif
%if input.help:
<div class="toolParamHelp" style="clear: both;">
${input.help}
</div>
%endif
%if input.help:
<div class="ui-table-form-info">
${input.help}
</div>
%endif
</div><!-- ui-table-form-field -->
<div style="clear: both"></div>
</div>
</div></td></tr>
%endfor
%else:
<div class="form-row"><i>No options</i></div>
%endif
</tbody>
</table>
</div>
</div> <!-- content -->
</div><!-- portlet-content -->
</div>
</form>
+5 -14
View File
@@ -91,7 +91,7 @@ def _python_to_workflow(as_python, conversion_context):
as_python["steps"] = steps_as_dict
steps = steps_as_dict
for i, step in steps.iteritems():
for step in steps.itervalues():
step_type = step.get("type", None)
if "run" in step:
if step_type is not None:
@@ -113,7 +113,7 @@ def _python_to_workflow(as_python, conversion_context):
run_to_step_function(conversion_context, step, run_action)
del step["run"]
for i, step in steps.iteritems():
for step in steps.itervalues():
step_type = step.get("type", "tool")
step_type = STEP_TYPE_ALIASES.get(step_type, step_type)
if step_type not in STEP_TYPES:
@@ -148,14 +148,14 @@ def convert_inputs_to_steps(inputs, steps):
if "label" not in input_def and "id" not in input_def:
raise Exception("Input must define a label.")
raw_label = __rip_value(input_def, "label")
raw_id = __rip_value(input_def, "id")
raw_label = input_def.pop("label", None)
raw_id = input_def.pop("id", None)
label = raw_label or raw_id
if not label:
raise Exception("Input label must not be empty.")
input_type = __rip_value(input_def, "type", "data")
input_type = input_def.pop("type", "data")
if input_type in ["File", "data", "data_input"]:
step_type = "data_input"
elif input_type in ["collection", "data_collection", "data_collection_input"]:
@@ -380,15 +380,6 @@ def __join_prefix(prefix, key):
return new_key
def __rip_value(input_def, key, default=None):
if key in input_def:
value = input_def[key]
del input_def[key]
else:
value = default
return value
def __init_connect_dict(step):
if "connect" not in step:
step["connect"] = {}
+31 -8
View File
@@ -2,6 +2,7 @@
define([
"sinon-qunit",
"test-app",
'utils/utils',
"mvc/workflow/workflow-view",
"mvc/workflow/workflow-node",
"mvc/workflow/workflow-view-node",
@@ -11,6 +12,7 @@ define([
], function(
sinon,
testApp,
Utils,
App,
Node,
NodeView,
@@ -296,8 +298,8 @@ define([
setup: function() {
this.input_terminal = { destroy: sinon.spy(), redraw: sinon.spy() };
this.output_terminal = { destroy: sinon.spy(), redraw: sinon.spy() };
this.element = $("<div><div class='toolFormBody'></div></div>");
this.app = create_app();
this.element = this.app.$newNodeElement( "tool", "newnode" );
this.node = new Node( this.app, { element: this.element } );
this.node.input_terminals.i1 = this.input_terminal;
this.node.output_terminals.o1 = this.output_terminal;
@@ -311,22 +313,24 @@ define([
f();
ok( node_changed_spy.calledWith( node ) );
},
init_field_data_simple: function() {
var data = {
init_field_data_simple: function(option_overrides) {
var data = Utils.merge(option_overrides, {
data_inputs: [ {name: "input1", extensions: [ "data" ] } ],
data_outputs: [ {name: "output1", extensions: [ "data" ] } ],
};
label: null,
});
this.node.init_field_data( data );
},
update_field_data_with_new_input: function() {
var new_data = {
update_field_data_with_new_input: function(option_overrides) {
var new_data = Utils.merge(option_overrides, {
data_inputs: [
{ name: "input1", extensions: [ "data" ] },
{ name: "extra_0|input1", extensions: [ "data" ] },
],
data_outputs: [ {name: "output1", extensions: [ "data" ] } ],
post_job_actions: "{}"
};
post_job_actions: "{}",
label: "New Label"
});
this.node.update_field_data( new_data );
}
} );
@@ -368,6 +372,7 @@ define([
tooltip: "tool tooltip",
annotation: "tool annotation",
workflow_outputs: [ {"output_name": "out1"} ],
label: "Cat that data.",
};
node.init_field_data( data );
equal( node.type, "tool" );
@@ -376,6 +381,7 @@ define([
equal( node.tool_state, "ok" );
equal( node.tooltip, "tool tooltip" );
equal( node.annotation, "tool annotation" );
equal( node.label, "Cat that data." );
deepEqual( node.post_job_actions, {} );
deepEqual( node.workflow_outputs, [ {"output_name": "out1"} ] );
} );
@@ -394,9 +400,26 @@ define([
equal( test.$( ".input-terminal" ).length, 1 );
equal( test.$( ".rule" ).length, 1 );
equal( test.$( ".toolFormBody" ).children().length, 3 );
equal( test.$( ".nodeTitle" ).text(), "newnode" );
ok( test.$( ".toolFormTitle" ).find("i").hasClass("fa-wrench") );
} );
} );
test( "node title behavior", function() {
var test = this;
this.expect_workflow_node_changed( function( ) {
// Node created with name newnode
equal( test.$( ".nodeTitle" ).text(), "newnode" );
// init_field_data_simple doesn't change label, so it should
// remain original name.
test.init_field_data_simple();
equal( test.$( ".nodeTitle" ).text(), "newnode" );
// Despite awkward name, update does change the label...
test.update_field_data_with_new_input();
equal( test.$( ".nodeTitle" ).text(), "New Label" );
});
});
test( "update_field_data updated data inputs and outputs", function() {
var test = this;
this.expect_workflow_node_changed( function( ) {
+2 -2
View File
@@ -19,7 +19,7 @@ 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" )
tool_module = modules.module_factory.new( trans, "tool", content_id="cat1" )
assert not tool_module.get_errors()
@@ -194,7 +194,7 @@ def test_cannot_create_tool_modules_for_missing_tools():
trans = MockTrans()
exception = False
try:
modules.module_factory.new( trans, "tool", tool_id="cat1" )
modules.module_factory.new( trans, "tool", content_id="cat1" )
except Exception:
exception = True
assert exception