mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
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:
@@ -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 ):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user