Support for sharing workflows (as well as renaming). A shared workflow is

read only for the recipient, however they can always run the latest version of
the workflow. They can also 'clone' the workflow to create their own editable
copy of the current version.
This commit is contained in:
James Taylor
2008-07-14 01:56:50 +00:00
parent 0071b7389e
commit 6906f5a048
7 changed files with 219 additions and 13 deletions
+5
View File
@@ -538,6 +538,11 @@ class WorkflowStepConnection( object ):
self.input_step_id = None
self.input_name = None
class StoredWorkflowUserShareAssociation( object ):
def __init__( self ):
self.stored_workflow = None
self.user = None
## ---- Utility methods -------------------------------------------------------
def directory_hash_id( id ):
+12
View File
@@ -223,6 +223,12 @@ WorkflowStepConnection.table = Table( "workflow_step_connection", metadata,
Column( "input_name", String)
)
StoredWorkflowUserShareAssociation.table = Table( "stored_workflow_user_share_connection", metadata,
Column( "id", Integer, primary_key=True ),
Column( "stored_workflow_id", Integer, ForeignKey( "stored_workflow.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True )
)
# With the tables defined we can define the mappers and setup the
# relationships between the model objects.
@@ -327,6 +333,12 @@ assign_mapper( context, StoredWorkflow, StoredWorkflow.table,
primaryjoin=( StoredWorkflow.table.c.latest_workflow_id == Workflow.table.c.id ) )
) )
assign_mapper( context, StoredWorkflowUserShareAssociation, StoredWorkflowUserShareAssociation.table,
properties=dict( user=relation( User, backref='workflows_shared_by_others' ),
stored_workflow=relation( StoredWorkflow )
) )
def db_next_hid( self ):
"""
Override __next_hid to generate from the database in a concurrency
+1 -1
View File
@@ -6,7 +6,7 @@ import os, time, logging
# Pieces of Galaxy to make global in every controller
from galaxy import config, tools, web, model, util
from galaxy.web import error
from galaxy.web import error, url_for
from Cheetah.Template import Template
+87 -7
View File
@@ -22,7 +22,82 @@ class WorkflowController( BaseController ):
"""
user = trans.get_user()
workflows = trans.sa_session.query( model.StoredWorkflow ).filter_by( user=user, deleted=False ).all()
return trans.fill_template( "workflow/index.mako", workflows = workflows )
shared_by_others = trans.sa_session \
.query( model.StoredWorkflowUserShareAssociation ) \
.filter_by( user=user ) \
.filter( model.StoredWorkflow.c.deleted == False ) \
.all()
return trans.fill_template( "workflow/index.mako",
workflows = workflows,
shared_by_others = shared_by_others )
@web.expose
@web.require_login( "use Galaxy workflows" )
def share( self, trans, id, email="" ):
msg = mtype = None
# Load workflow from database
stored = get_stored_workflow( trans, id )
if email:
other = model.User.get_by( email=email )
if not other:
mtype = "error"
msg = ( "User '%s' does not exist" % email )
elif trans.sa_session.query( model.StoredWorkflowUserShareAssociation ) \
.filter_by( user=other, stored_workflow=stored ).count() > 0:
mtype = "error"
msg = ( "Workflow already shared with '%s'" % email )
else:
share = model.StoredWorkflowUserShareAssociation()
share.stored_workflow = stored
share.user = other
session = trans.sa_session
session.save( share )
session.flush()
## trans.template_context['message'] = "Workflow '%s' shared with user '%s'" % ( stored.name, other.email )
## return trans.send_redirect( url_for( controller='workflow', action='index' ) )
return trans.show_message( "Workflow '%s' shared with user '%s'" % ( stored.name, other.email ) )
return trans.fill_template( "workflow/share.mako",
message = msg,
messagetype = mtype,
stored=stored,
email=email )
@web.expose
@web.require_login( "use Galaxy workflows" )
def rename( self, trans, id, new_name=None ):
stored = get_stored_workflow( trans, id )
if new_name is not None:
stored.name = new_name
trans.sa_session.flush()
return trans.response.send_redirect( url_for( controller='workflow', action='index' ) )
else:
return trans.fill_template( "workflow/rename.mako", stored=stored )
@web.expose
@web.require_login( "use Galaxy workflows" )
def clone( self, trans, id ):
stored = get_stored_workflow( trans, id, check_ownership=False )
user = trans.get_user()
if stored.user == user:
owner = True
else:
if trans.sa_session.query( model.StoredWorkflowUserShareAssociation ) \
.filter_by( user=user, stored_workflow=stored ).count() == 0:
error( "Workflow is not owned by or shared with current user" )
owner = False
new_stored = model.StoredWorkflow()
new_stored.name = "Clone of '%s'" % stored.name
new_stored.latest_workflow = stored.latest_workflow
if not owner:
new_stored.name += "shared by '%s'" % stored.user.email
new_stored.user = user
# Persist
session = trans.sa_session
session.save( new_stored )
session.flush()
# Display the management page
return trans.response.send_redirect( url_for( controller='workflow', action='index' ) )
@web.expose
@web.require_login( "create workflows" )
@@ -47,7 +122,7 @@ class WorkflowController( BaseController ):
session.save( stored_workflow )
session.flush()
# Display the management page
return self.index( trans )
return trans.response.send_redirect( url_for( controller='workflow', action='index' ) )
@web.expose
def delete( self, trans, id=None ):
@@ -60,7 +135,7 @@ class WorkflowController( BaseController ):
stored.deleted = True
stored.flush()
# Display the management page
return self.index( trans )
return trans.response.send_redirect( url_for( controller='workflow', action='index' ) )
@web.expose
@web.require_login( "edit workflows" )
@@ -348,7 +423,12 @@ class WorkflowController( BaseController ):
@web.expose
def run( self, trans, id, **kwargs ):
stored = get_stored_workflow( trans, id )
stored = get_stored_workflow( trans, id, check_ownership=False )
user = trans.get_user()
if stored.user != user:
if trans.sa_session.query( model.StoredWorkflowUserShareAssociation ) \
.filter_by( user=user, stored_workflow=stored ).count() == 0:
error( "Workflow is not owned by or shared with current user" )
# Get the latest revision
workflow = stored.latest_workflow
# It is possible for a workflow to have 0 steps
@@ -392,7 +472,7 @@ class WorkflowController( BaseController ):
step.input_connections_by_name = dict( ( conn.input_name, conn ) for conn in step.input_connections )
if not errors:
# Run each step, connecting outputs to inputs
outputs = {}
outputs = odict()
for step in workflow.steps:
if step.type == 'tool' or step.type is None:
tool = trans.app.toolbox.tools_by_id[ step.tool_id ]
@@ -453,7 +533,7 @@ class WorkflowController( BaseController ):
## ---- Utility methods -------------------------------------------------------
def get_stored_workflow( trans, id ):
def get_stored_workflow( trans, id, check_ownership=True ):
"""
Get a StoredWorkflow from the database by id, verifying ownership.
"""
@@ -466,7 +546,7 @@ def get_stored_workflow( trans, id ):
user = trans.get_user()
if not user:
error( "Must be logged in to use workflows" )
if not( stored.user == user ):
if check_ownership and not( stored.user == user ):
error( "Workflow is not owned by current user" )
# Looks good
return stored
+42 -5
View File
@@ -47,26 +47,63 @@
</div>
</div>
<h2>Stored workflows</h2>
<h2>Your workflows</h2>
%if workflows:
<table class="colored" border="0" cellspacing="0" cellpadding="0" width="100%">
<tr class="header"><td>Name</td><td># of Steps<td>Last Updated</td><td>Actions</td></tr>
<tr class="header">
<th>Name</th>
<th># of Steps</th>
## <th>Last Updated</th>
<th>Actions</th>
</tr>
%for workflow in workflows:
<tr>
<td>${workflow.name}</td>
<td>${len(workflow.latest_workflow.steps)}
<td>${str(workflow.update_time)[:19]}</td>
<td>${len(workflow.latest_workflow.steps)}</td>
## <td>${str(workflow.update_time)[:19]}</td>
<td>
<a href="${h.url_for( action='run', id=trans.security.encode_id(workflow.id) )}">run</a>
| <a href="${h.url_for( action='editor', id=trans.security.encode_id(workflow.id) )}" target="_parent">edit</a>
| <a href="${h.url_for( action='rename', id=trans.security.encode_id(workflow.id) )}">rename</a>
| <a href="${h.url_for( action='share', id=trans.security.encode_id(workflow.id) )}">share</a>
| <a href="${h.url_for( action='delete', id=trans.security.encode_id(workflow.id) )}">delete</a>
</td>
</tr>
%endfor
</table>
%else:
You have no stored workflows.
You have no workflows.
%endif
<h2>Workflows shared with you by others</h2>
%if shared_by_others:
<table class="colored" border="0" cellspacing="0" cellpadding="0" width="100%">
<tr class="header">
<th>Name</th>
<th>Owner</th>
<th># of Steps</th>
<th>Actions</th>
</tr>
%for association in shared_by_others:
<% workflow = association.stored_workflow %>
<tr>
<td>${workflow.name}</td>
<td>${workflow.user.email}</td>
<td>${len(workflow.latest_workflow.steps)}</td>
<td>
<a href="${h.url_for( action='run', id=trans.security.encode_id(workflow.id) )}">run</a>
<a href="${h.url_for( action='clone', id=trans.security.encode_id(workflow.id) )}">clone</a>
</td>
</tr>
%endfor
</table>
%else:
No workflows have been shared with you.
%endif
+36
View File
@@ -0,0 +1,36 @@
<%inherit file="/base.mako"/>
%if message:
<%
if messagetype is UNDEFINED:
mt = "done"
else:
mt = messagetype
%>
<p />
<div class="${mt}message">
${message}
</div>
<p />
%endif
<div class="toolForm">
<div class="toolFormTitle">Rename workflow '${stored.name}'</div>
<div class="toolFormBody">
<form action="${h.url_for( action='rename', id=trans.security.encode_id(stored.id) )}" method="POST">
<div class="form-row">
<label>
New name
</label>
<div style="float: left; width: 250px; margin-right: 10px;">
<input type="text" name="new_name" value="${stored.name}" size="40">
</div>
<div style="clear: both"></div>
</div>
<div class="form-row">
<input type="submit" value="Rename"></input>
</div>
</form>
</div>
</div>
</div>
+36
View File
@@ -0,0 +1,36 @@
<%inherit file="/base.mako"/>
%if message:
<%
if messagetype is UNDEFINED:
mt = "done"
else:
mt = messagetype
%>
<p />
<div class="${mt}message">
${message}
</div>
<p />
%endif
<div class="toolForm">
<div class="toolFormTitle">Share workflow '${stored.name}'</div>
<div class="toolFormBody">
<form action="${h.url_for( action='share', id=trans.security.encode_id(stored.id) )}" method="POST">
<div class="form-row">
<label>
Email address of user to share with
</label>
<div style="float: left; width: 250px; margin-right: 10px;">
<input type="text" name="email" value="${email}" size="40">
</div>
<div style="clear: both"></div>
</div>
<div class="form-row">
<input type="submit" value="Share"></input>
</div>
</form>
</div>
</div>
</div>