Merge branch 'release_17.01' into dev

Conflicts:
	lib/galaxy/managers/workflows.py

Discarded all changes made to
lib/galaxy/managers/workflows.py
in https://github.com/galaxyproject/galaxy/pull/3681
This commit is contained in:
Nicola Soranzo
2017-03-01 11:32:59 +00:00
5 changed files with 307 additions and 14 deletions
+6 -4
View File
@@ -29,15 +29,17 @@ Highlights
Thanks to `@abretaud <https://github.com/abretaud>`__, `@ashvark <https://github.com/ashvark>`__, `@jvolkening <https://github.com/jvolkening>`__, and `@mvdbeek <https://github.com/mvdbeek>`__.
Implemented in `Pull Request 3145`_, `PullRequest 3510`_ and `PullRequest 3514`_.
`Github <https://github.com/galaxyproject/galaxy>`__
===========================================================
Get Galaxy
==========
New Galaxy repository
The code lives at `Github <https://github.com/galaxyproject/galaxy>`__ and you should have `Git <https://git-scm.com/>`__ to obtain it.
To get a new Galaxy repository run:
.. code-block:: shell
$ git clone -b release_17.01 https://github.com/galaxyproject/galaxy.git
Update of existing Galaxy repository
To update an existing Galaxy repository run:
.. code-block:: shell
$ git checkout release_17.01 && git pull --ff-only origin release_17.01
+42 -1
View File
@@ -9,6 +9,7 @@ import time
import galaxy.queues
from galaxy import util
from galaxy.model.util import pgcalc
from kombu import Connection
from kombu.mixins import ConsumerMixin
@@ -18,7 +19,31 @@ logging.getLogger('kombu').setLevel(logging.WARNING)
log = logging.getLogger(__name__)
def send_local_control_task(app, task, kwargs={}):
"""
This sends a message to the process-local control worker, which is useful
for one-time asynchronous tasks like recalculating user disk usage.
"""
log.info("Queuing async task %s." % task)
payload = {'task': task,
'kwargs': kwargs}
try:
c = Connection(app.config.amqp_internal_connection)
with producers[c].acquire(block=True) as producer:
producer.publish(payload,
exchange=galaxy.queues.galaxy_exchange,
declare=[galaxy.queues.galaxy_exchange] + [galaxy.queues.control_queue_from_config(app.config)],
routing_key='control')
except Exception:
log.exception("Error queueing async task: %s." % payload)
def send_control_task(app, task, noop_self=False, kwargs={}):
"""
This sends a control task out to all processes, useful for things like
reloading a data table, which needs to happen individually in all
processes.
"""
log.info("Sending %s control task." % task)
payload = {'task': task,
'kwargs': kwargs}
@@ -112,6 +137,21 @@ def reload_sanitize_whitelist(app):
app.config.reload_sanitize_whitelist()
def recalculate_user_disk_usage(app, **kwargs):
user_id = kwargs.get('user_id', None)
sa_session = app.model.context
if user_id:
user = sa_session.query( app.model.User ).get( app.security.decode_id( user_id ) )
if user:
if sa_session.get_bind().dialect.name not in ( 'postgres', 'postgresql' ):
new = user.calculate_disk_usage()
else:
new = pgcalc(sa_session, user.id)
user.set_disk_usage(new)
sa_session.add(user)
sa_session.flush()
def reload_tool_data_tables(app, **kwargs):
params = util.Params(kwargs)
log.debug("Executing tool data table reload for %s" % params.get('table_names', 'all tables'))
@@ -135,7 +175,8 @@ control_message_to_task = { 'create_panel_section': create_panel_section,
'reload_display_application': reload_display_application,
'reload_tool_data_tables': reload_tool_data_tables,
'admin_job_lock': admin_job_lock,
'reload_sanitize_whitelist': reload_sanitize_whitelist}
'reload_sanitize_whitelist': reload_sanitize_whitelist,
'recalculate_user_disk_usage': recalculate_user_disk_usage}
class GalaxyQueueWorker(ConsumerMixin, threading.Thread):
+7 -7
View File
@@ -49,8 +49,9 @@ class ToolBoxSearch( object ):
self.build_index( index_help )
def build_index( self, index_help=True ):
# Works around https://bitbucket.org/mchaput/whoosh/issues/391/race-conditions-with-temp-storage
"""Prepare search index for tools loaded in toolbox."""
RamStorage.temp_storage = _temp_storage
# Works around https://bitbucket.org/mchaput/whoosh/issues/391/race-conditions-with-temp-storage
self.storage = RamStorage()
self.index = self.storage.create_index( self.schema )
writer = self.index.writer()
@@ -66,16 +67,15 @@ class ToolBoxSearch( object ):
"section": to_unicode( tool.get_panel_section()[1] if len( tool.get_panel_section() ) == 2 else '' ),
"help": to_unicode( "" )
}
# Hyphens are wildcards in Whoosh causing bad things
if tool.name.find( '-' ) != -1:
# Hyphens are wildcards in Whoosh causing bad things
add_doc_kwds['name'] = (' ').join( [ token.text for token in self.rex( to_unicode( tool.name ) ) ] )
else:
add_doc_kwds['name'] = to_unicode( tool.name )
# We do not want to search Tool Shed or version parts
# of the long ids
if id.find( '/' ) != -1:
slash_indexes = [ m.start() for m in re.finditer( '/', id ) ]
id_stub = id[ ( slash_indexes[1] + 1 ): slash_indexes[4] ]
if tool.guid:
# Create a stub consisting of owner, repo, and tool from guid
slash_indexes = [ m.start() for m in re.finditer( '/', tool.guid ) ]
id_stub = tool.guid[ ( slash_indexes[1] + 1 ): slash_indexes[4] ]
add_doc_kwds['stub'] = (' ').join( [ token.text for token in self.rex( to_unicode( id_stub ) ) ] )
else:
add_doc_kwds['stub'] = to_unicode( id )
+212 -2
View File
@@ -17,6 +17,7 @@ from galaxy import model
from galaxy import util
from galaxy import web
from galaxy.exceptions import ObjectInvalid
from galaxy.queue_worker import send_local_control_task
from galaxy.security.validate_user_input import (transform_publicname,
validate_email,
validate_password,
@@ -624,9 +625,12 @@ class User( BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Creat
refresh_frames = [ 'masthead', 'history', 'tools' ]
else:
refresh_frames = [ 'masthead', 'history' ]
# Recalculate user disk usage.
if trans.user:
trans.user.calculate_disk_usage()
# Queue a quota recalculation (async) task -- this takes a
# while sometimes, so we don't want to block on logout.
send_local_control_task( trans.app,
'recalculate_user_disk_usage',
{'user_id': trans.security.encode_id(trans.user.id)} )
# Since logging an event requires a session, we'll log prior to ending the session
trans.log_event( "User logged out" )
else:
@@ -1190,3 +1194,209 @@ class User( BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Creat
message=message,
display_top=kwd.get('redirect_home', False)
)
@web.expose
@web.require_admin
def edit_info( self, trans, cntrller, **kwd ):
"""
TEMPORARY ENDPOINT - added back to support admin-level user info
editing prior to adminjs. This is code that was prematurely removed
from the user controller when the user-side editing functionality was
replaced.
The method manage_user_info, which follows this, should also be removed
at that time.
Edit user information = username, email or password.
"""
params = util.Params( kwd )
is_admin = cntrller == 'admin' and trans.user_is_admin()
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
user_id = params.get( 'user_id', None )
if user_id and is_admin:
user = trans.sa_session.query( trans.app.model.User ).get( trans.security.decode_id( user_id ) )
elif user_id and ( not trans.user or trans.user.id != trans.security.decode_id( user_id ) ):
message = 'Invalid user id'
status = 'error'
user = None
else:
user = trans.user
if user and params.get( 'login_info_button', False ):
# Editing email and username
email = util.restore_text( params.get( 'email', '' ) )
username = util.restore_text( params.get( 'username', '' ) ).lower()
# Validate the new values for email and username
message = validate_email( trans, email, user )
if not message and username:
message = validate_publicname( trans, username, user )
if message:
status = 'error'
else:
if ( user.email != email ):
# The user's private role name must match the user's login ( email )
private_role = trans.app.security_agent.get_private_user_role( user )
private_role.name = email
private_role.description = 'Private role for ' + email
# Change the email itself
user.email = email
trans.sa_session.add_all( ( user, private_role ) )
trans.sa_session.flush()
if trans.webapp.name == 'galaxy' and trans.app.config.user_activation_on:
user.active = False
trans.sa_session.add( user )
trans.sa_session.flush()
is_activation_sent = self.send_verification_email( trans, user.email, user.username )
if is_activation_sent:
message = 'The login information has been updated with the changes.<br>Verification email has been sent to your new email address. Please verify it by clicking the activation link in the email.<br>Please check your spam/trash folder in case you cannot find the message.'
else:
message = 'Unable to send activation email, please contact your local Galaxy administrator.'
if trans.app.config.error_email_to is not None:
message += ' Contact: %s' % trans.app.config.error_email_to
if ( user.username != username ):
user.username = username
trans.sa_session.add( user )
trans.sa_session.flush()
message = 'The login information has been updated with the changes.'
elif user and params.get( 'edit_user_info_button', False ):
# Edit user information - webapp MUST BE 'galaxy'
user_type_fd_id = params.get( 'user_type_fd_id', 'none' )
if user_type_fd_id not in [ 'none' ]:
user_type_form_definition = trans.sa_session.query( trans.app.model.FormDefinition ).get( trans.security.decode_id( user_type_fd_id ) )
elif user.values:
user_type_form_definition = user.values.form_definition
else:
# User was created before any of the user_info forms were created
user_type_form_definition = None
if user_type_form_definition:
values = self.get_form_values( trans, user, user_type_form_definition, **kwd )
else:
values = {}
flush_needed = False
if user.values:
# Editing the user info of an existing user with existing user info
user.values.content = values
trans.sa_session.add( user.values )
flush_needed = True
elif values:
form_values = trans.model.FormValues( user_type_form_definition, values )
trans.sa_session.add( form_values )
user.values = form_values
flush_needed = True
if flush_needed:
trans.sa_session.add( user )
trans.sa_session.flush()
message = "The user information has been updated with the changes."
if user and trans.webapp.name == 'galaxy' and is_admin:
kwd[ 'user_id' ] = trans.security.encode_id( user.id )
kwd[ 'id' ] = user_id
if message:
kwd[ 'message' ] = util.sanitize_text( message )
if status:
kwd[ 'status' ] = status
return trans.response.send_redirect( web.url_for( controller='user',
action='manage_user_info',
cntrller=cntrller,
**kwd ) )
@web.expose
@web.require_admin
def manage_user_info( self, trans, cntrller, **kwd ):
'''TEMPORARY ENDPOINT - added back to support admin-level user info
editing prior to adminjs. This is code that was prematurely removed
from the user controller when the user-side editing functionality was
replaced.
When this is removed, templates/webapps/galaxy/user/manage_info.mako
should go as well.
Manage a user's login, password, public username, type,
addresses, etc.'''
def __get_user_type_form_definition( trans, user=None, **kwd ):
params = util.Params( kwd )
if user and user.values:
user_type_fd_id = trans.security.encode_id( user.values.form_definition.id )
else:
user_type_fd_id = params.get( 'user_type_fd_id', 'none' )
if user_type_fd_id not in [ 'none' ]:
user_type_form_definition = trans.sa_session.query( trans.app.model.FormDefinition ).get( trans.security.decode_id( user_type_fd_id ) )
else:
user_type_form_definition = None
return user_type_form_definition
def __get_widgets( trans, user_type_form_definition, user=None, **kwd ):
widgets = []
if user_type_form_definition:
if user:
if user.values:
widgets = user_type_form_definition.get_widgets( user=user,
contents=user.values.content,
**kwd )
else:
widgets = user_type_form_definition.get_widgets( None, contents={}, **kwd )
else:
widgets = user_type_form_definition.get_widgets( None, contents={}, **kwd )
return widgets
def __build_user_type_fd_id_select_field( trans, selected_value ):
from galaxy.web.form_builder import build_select_field
# Get all the user information forms
user_info_forms = self.get_all_forms( trans,
filter=dict( deleted=False ),
form_type=trans.model.FormDefinition.types.USER_INFO )
return build_select_field( trans,
objs=user_info_forms,
label_attr='name',
select_field_name='user_type_fd_id',
initial_value='none',
selected_value=selected_value,
refresh_on_change=True )
params = util.Params( kwd )
user_id = params.get( 'id', None )
if user_id:
user = trans.sa_session.query( trans.app.model.User ).get( trans.security.decode_id( user_id ) )
else:
user = trans.user
if not user:
raise AssertionError("The user id (%s) is not valid" % str( user_id ))
email = util.restore_text( params.get( 'email', user.email ) )
username = util.restore_text( params.get( 'username', '' ) )
if not username:
username = user.username
message = escape( util.restore_text( params.get( 'message', '' ) ) )
status = params.get( 'status', 'done' )
user_type_form_definition = __get_user_type_form_definition( trans, user=user, **kwd )
user_type_fd_id = params.get( 'user_type_fd_id', 'none' )
if user_type_fd_id == 'none' and user_type_form_definition is not None:
user_type_fd_id = trans.security.encode_id( user_type_form_definition.id )
user_type_fd_id_select_field = __build_user_type_fd_id_select_field( trans, selected_value=user_type_fd_id )
widgets = __get_widgets( trans, user_type_form_definition, user=user, **kwd )
# user's addresses
show_filter = util.restore_text( params.get( 'show_filter', 'Active' ) )
if show_filter == 'All':
addresses = [address for address in user.addresses]
elif show_filter == 'Deleted':
addresses = [address for address in user.addresses if address.deleted]
else:
addresses = [address for address in user.addresses if not address.deleted]
user_info_forms = self.get_all_forms( trans,
filter=dict( deleted=False ),
form_type=trans.app.model.FormDefinition.types.USER_INFO )
return trans.fill_template( '/webapps/galaxy/user/manage_info.mako',
cntrller=cntrller,
user=user,
email=email,
is_admin=True,
username=username,
user_type_fd_id_select_field=user_type_fd_id_select_field,
user_info_forms=user_info_forms,
user_type_form_definition=user_type_form_definition,
user_type_fd_id=user_type_fd_id,
widgets=widgets,
addresses=addresses,
show_filter=show_filter,
message=message,
status=status )
@@ -0,0 +1,40 @@
<%inherit file="/base.mako"/>
<%namespace file="/user/info.mako" import="render_user_info" />
<%namespace file="/message.mako" import="render_msg" />
%if message:
${render_msg( message, status )}
%endif
${render_user_info()}
%if user.values or user_info_forms:
<p></p>
<div class="toolForm">
<form name="user_info" id="user_info" action="${h.url_for( controller='user', action='edit_info', cntrller=cntrller, user_id=trans.security.encode_id( user.id ) )}" method="post" >
<div class="toolFormTitle">User information</div>
%if user_type_fd_id_select_field and len( user_type_fd_id_select_field.options ) >= 1:
<div class="form-row">
<label>User type:</label>
${user_type_fd_id_select_field.get_html()}
</div>
%else:
<input type="hidden" name="user_type_fd_id" value="${trans.security.encode_id( user_type_fd_id )}"/>
%endif
%for field in widgets:
<div class="form-row">
<label>${field['label']}:</label>
${field['widget'].get_html()}
<div class="toolParamHelp" style="clear: both;">
${field['helptext']}
</div>
<div style="clear: both"></div>
</div>
%endfor
<div class="form-row">
<input type="submit" name="edit_user_info_button" value="Save"/>
</div>
</form>
</div>
<p></p>
%endif