galaxy-commits
Threads by month
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
May 2014
- 1 participants
- 242 discussions
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/f09f01f2b21e/
Changeset: f09f01f2b21e
User: martenson
Date: 2014-05-07 22:48:34
Summary: pep8
Affected #: 2 files
diff -r d283202d915e6f3fc5f80330a1e85d3a32a16049 -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 lib/galaxy/exceptions/__init__.py
--- a/lib/galaxy/exceptions/__init__.py
+++ b/lib/galaxy/exceptions/__init__.py
@@ -37,6 +37,7 @@
# Please keep the exceptions ordered by status code
+
class ActionInputError( MessageException ):
status_code = 400
err_code = error_codes.USER_REQUEST_INVALID_PARAMETER
@@ -44,76 +45,94 @@
def __init__( self, err_msg, type="error" ):
super( ActionInputError, self ).__init__( err_msg, type )
+
class DuplicatedSlugException( MessageException ):
status_code = 400
err_code = error_codes.USER_SLUG_DUPLICATE
+
class ObjectAttributeInvalidException( MessageException ):
status_code = 400
err_code = error_codes.USER_OBJECT_ATTRIBUTE_INVALID
+
class ObjectAttributeMissingException( MessageException ):
status_code = 400
err_code = error_codes.USER_OBJECT_ATTRIBUTE_MISSING
+
class MalformedId( MessageException ):
status_code = 400
err_code = error_codes.MALFORMED_ID
+
class UnknownContentsType( MessageException ):
status_code = 400
err_code = error_codes.UNKNOWN_CONTENTS_TYPE
+
class RequestParameterMissingException( MessageException ):
status_code = 400
err_code = error_codes.USER_REQUEST_MISSING_PARAMETER
+
class ToolMetaParameterException( MessageException ):
status_code = 400
err_code = error_codes.USER_TOOL_META_PARAMETER_PROBLEM
+
class RequestParameterInvalidException( MessageException ):
status_code = 400
err_code = error_codes.USER_REQUEST_INVALID_PARAMETER
+
class AuthenticationRequired( MessageException ):
status_code = 403
#TODO: as 401 and send WWW-Authenticate: ???
err_code = error_codes.USER_NO_API_KEY
+
class ItemAccessibilityException( MessageException ):
status_code = 403
err_code = error_codes.USER_CANNOT_ACCESS_ITEM
+
class ItemOwnershipException( MessageException ):
status_code = 403
err_code = error_codes.USER_DOES_NOT_OWN_ITEM
+
class ConfigDoesNotAllowException( MessageException ):
status_code = 403
err_code = error_codes.CONFIG_DOES_NOT_ALLOW
+
class InsufficientPermissionsException( MessageException ):
status_code = 403
err_code = error_codes.INSUFFICIENT_PERMISSIONS
+
class ObjectNotFound( MessageException ):
""" Accessed object was not found """
status_code = 404
err_code = error_codes.USER_OBJECT_NOT_FOUND
+
class Conflict( MessageException ):
status_code = 409
err_code = error_codes.CONFLICT
+
class InconsistentDatabase ( MessageException ):
status_code = 500
err_code = error_codes.INCONSISTENT_DATABASE
+
class InternalServerError ( MessageException ):
status_code = 500
err_code = error_codes.INTERNAL_SERVER_ERROR
+
class NotImplemented ( MessageException ):
status_code = 501
err_code = error_codes.NOT_IMPLEMENTED
diff -r d283202d915e6f3fc5f80330a1e85d3a32a16049 -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 lib/galaxy/webapps/galaxy/api/libraries.py
--- a/lib/galaxy/webapps/galaxy/api/libraries.py
+++ b/lib/galaxy/webapps/galaxy/api/libraries.py
@@ -2,7 +2,6 @@
API operations on a data library.
"""
from galaxy import util
-from galaxy import web
from galaxy import exceptions
from galaxy.web import _future_expose_api as expose_api
from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous
@@ -12,6 +11,7 @@
import logging
log = logging.getLogger( __name__ )
+
class LibrariesController( BaseAPIController ):
@expose_api_anonymous
@@ -33,7 +33,8 @@
query = trans.sa_session.query( trans.app.model.Library )
deleted = kwd.get( 'deleted', 'missing' )
try:
- if not trans.user_is_admin(): # non-admins can't see deleted libraries
+ if not trans.user_is_admin():
+ # non-admins can't see deleted libraries
deleted = False
else:
deleted = util.asbool( deleted )
@@ -48,16 +49,15 @@
current_user_role_ids = [ role.id for role in trans.get_current_user_roles() ]
library_access_action = trans.app.security_agent.permitted_actions.LIBRARY_ACCESS.action
restricted_library_ids = [ lp.library_id for lp in ( trans.sa_session.query( trans.model.LibraryPermissions )
- .filter( trans.model.LibraryPermissions.table.c.action == library_access_action )
- .distinct() ) ]
+ .filter( trans.model.LibraryPermissions.table.c.action == library_access_action )
+ .distinct() ) ]
accessible_restricted_library_ids = [ lp.library_id for lp in ( trans.sa_session.query( trans.model.LibraryPermissions )
- .filter( and_( trans.model.LibraryPermissions.table.c.action == library_access_action,
- trans.model.LibraryPermissions.table.c.role_id.in_( current_user_role_ids ) ) ) ) ]
- query = query.filter( or_( not_( trans.model.Library.table.c.id.in_( restricted_library_ids ) ),
- trans.model.Library.table.c.id.in_( accessible_restricted_library_ids ) ) )
+ .filter( and_( trans.model.LibraryPermissions.table.c.action == library_access_action,
+ trans.model.LibraryPermissions.table.c.role_id.in_( current_user_role_ids ) ) ) ) ]
+ query = query.filter( or_( not_( trans.model.Library.table.c.id.in_( restricted_library_ids ) ), trans.model.Library.table.c.id.in_( accessible_restricted_library_ids ) ) )
libraries = []
for library in query:
- item = library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } )
+ item = library.to_dict( view='element', value_mapper={ 'id': trans.security.encode_id, 'root_folder_id': trans.security.encode_id } )
if trans.app.security_agent.library_is_public( library, contents=False ):
item[ 'public' ] = True
current_user_roles = trans.get_current_user_roles()
@@ -106,7 +106,7 @@
library = None
if not library or not ( trans.user_is_admin() or trans.app.security_agent.can_access_library( trans.get_current_user_roles(), library ) ):
raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id )
- return library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } )
+ return library.to_dict( view='element', value_mapper={ 'id': trans.security.encode_id, 'root_folder_id': trans.security.encode_id } )
@expose_api
def create( self, trans, payload, **kwd ):
@@ -144,7 +144,7 @@
trans.sa_session.add_all( ( library, root_folder ) )
trans.sa_session.flush()
- item = library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } )
+ item = library.to_dict( view='element', value_mapper={ 'id': trans.security.encode_id, 'root_folder_id': trans.security.encode_id } )
item['can_user_add'] = True
item['can_user_modify'] = True
item['can_user_manage'] = True
@@ -204,7 +204,7 @@
raise exceptions.RequestParameterMissingException( "You did not specify any payload." )
trans.sa_session.add( library )
trans.sa_session.flush()
- return library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } )
+ return library.to_dict( view='element', value_mapper={ 'id': trans.security.encode_id, 'root_folder_id': trans.security.encode_id } )
@expose_api
def delete( self, trans, id, **kwd ):
@@ -249,5 +249,4 @@
trans.sa_session.add( library )
trans.sa_session.flush()
- return library.to_dict( view='element', value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } )
-
+ return library.to_dict( view='element', value_mapper={ 'id': trans.security.encode_id, 'root_folder_id': trans.security.encode_id } )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0b087515ad77/
Changeset: 0b087515ad77
User: jmchilton
Date: 2014-05-07 22:39:03
Summary: Add framework test tool that has both data and data_collection param - with test case.
Tool test is mildly useful to verify this works for single execution - but more useful as basis for future changesets testing mixed collection and subcollection mapping tool executions.
Affected #: 3 files
diff -r 4bf3a182df988e5c17e5f6080900130636ecf00f -r 0b087515ad774b62d69a35af6d738b70bf006a37 test-data/simple_lines_both.txt
--- /dev/null
+++ b/test-data/simple_lines_both.txt
@@ -0,0 +1,2 @@
+This is a line of text.
+This is a different line of text.
\ No newline at end of file
diff -r 4bf3a182df988e5c17e5f6080900130636ecf00f -r 0b087515ad774b62d69a35af6d738b70bf006a37 test/functional/tools/collection_mixed_param.xml
--- /dev/null
+++ b/test/functional/tools/collection_mixed_param.xml
@@ -0,0 +1,24 @@
+<tool id="collection_mixed_param" name="collection_mixed_param" version="0.1.0">
+ <command>
+ cat #for $f in $f1# ${f} #end for# $f2 >> $out1;
+ </command>
+ <inputs>
+ <param name="f1" type="data_collection" collection_type="paired" />
+ <param name="f2" type="data" format="txt" />
+ </inputs>
+ <outputs>
+ <data format="txt" name="out1" />
+ </outputs>
+ <tests>
+ <test>
+ <param name="f1">
+ <collection type="paired">
+ <element name="left" value="simple_line.txt" />
+ <element name="right" value="simple_line_alternative.txt" />
+ </collection>
+ </param>
+ <param name="f2" value="simple_lines_both.txt" />
+ <output name="out1" file="simple_lines_interleaved.txt"/>
+ </test>
+ </tests>
+</tool>
diff -r 4bf3a182df988e5c17e5f6080900130636ecf00f -r 0b087515ad774b62d69a35af6d738b70bf006a37 test/functional/tools/samples_tool_conf.xml
--- a/test/functional/tools/samples_tool_conf.xml
+++ b/test/functional/tools/samples_tool_conf.xml
@@ -20,4 +20,5 @@
<tool file="multi_data_param.xml" /><tool file="collection_paired_test.xml" /><tool file="collection_nested_test.xml" />
+ <tool file="collection_mixed_param.xml" /></toolbox>
\ No newline at end of file
https://bitbucket.org/galaxy/galaxy-central/commits/c28fa21b8f8c/
Changeset: c28fa21b8f8c
User: jmchilton
Date: 2014-05-07 22:39:03
Summary: Bugfix: Don't require matching identifiers to match up collections.
I changed this in some other places in the code before committing but this part stayed broken. Collections will match on order, type, and number of elements (pushing the problem to UI) - element identifiers can be different but will be preserved when possible.
Affected #: 1 file
diff -r 0b087515ad774b62d69a35af6d738b70bf006a37 -r c28fa21b8f8ca56068f58c084b9f23fd048e7eea lib/galaxy/dataset_collections/structure.py
--- a/lib/galaxy/dataset_collections/structure.py
+++ b/lib/galaxy/dataset_collections/structure.py
@@ -63,9 +63,6 @@
return False
for my_child, other_child in zip( self.children, other_structure.children ):
- if my_child[ 0 ] != other_child[ 0 ]: # Different identifiers, TODO: generalize
- return False
-
# At least one is nested collection...
if my_child[ 1 ].is_leaf != other_child[ 1 ].is_leaf:
return False
https://bitbucket.org/galaxy/galaxy-central/commits/d283202d915e/
Changeset: d283202d915e
User: jmchilton
Date: 2014-05-07 22:39:03
Summary: Bugfix: Rework collection matching logic so it properly matches combined collection/subcollection mapping.
With functional test to verify it works end-to-end with tool execution and unit tests to verify different kinds of collection combinations.
Affected #: 7 files
diff -r c28fa21b8f8ca56068f58c084b9f23fd048e7eea -r d283202d915e6f3fc5f80330a1e85d3a32a16049 lib/galaxy/dataset_collections/matching.py
--- a/lib/galaxy/dataset_collections/matching.py
+++ b/lib/galaxy/dataset_collections/matching.py
@@ -61,7 +61,7 @@
matching_collections = MatchingCollections()
for input_key, to_match in collections_to_match.iteritems():
hdca = to_match.hdca
- subcollection_type = to_match = to_match.subcollection_type
+ subcollection_type = to_match.subcollection_type
collection_type_description = collection_type_descriptions.for_collection_type( hdca.collection.collection_type )
matching_collections.__attempt_add_to_match( input_key, hdca, collection_type_description, subcollection_type )
diff -r c28fa21b8f8ca56068f58c084b9f23fd048e7eea -r d283202d915e6f3fc5f80330a1e85d3a32a16049 lib/galaxy/dataset_collections/structure.py
--- a/lib/galaxy/dataset_collections/structure.py
+++ b/lib/galaxy/dataset_collections/structure.py
@@ -18,19 +18,15 @@
class Tree( object ):
- def __init__( self, dataset_collection, collection_type_description, leaf_subcollection_type ):
+ def __init__( self, dataset_collection, collection_type_description ):
self.collection_type_description = collection_type_description
- self.leaf_subcollection_type = leaf_subcollection_type # collection_type to trim tree at...
children = []
for element in dataset_collection.elements:
- child_collection = element.child_collection
- if child_collection:
+ if collection_type_description.has_subcollections():
+ child_collection = element.child_collection
subcollection_type_description = collection_type_description.subcollection_type_description() # Type description of children
- if subcollection_type_description.can_match_type( leaf_subcollection_type ):
- children.append( ( element.element_identifier, leaf ) )
- else:
- children.append( ( element.element_identifier, Tree( child_collection, collection_type_description=subcollection_type_description, leaf_subcollection_type=leaf_subcollection_type ) ) )
- elif element.hda:
+ children.append( ( element.element_identifier, Tree( child_collection, collection_type_description=subcollection_type_description ) ) )
+ else:
children.append( ( element.element_identifier, leaf ) )
self.children = children
@@ -56,7 +52,6 @@
def can_match( self, other_structure ):
if not self.collection_type_description.can_match_type( other_structure.collection_type_description ):
- # TODO: generalize
return False
if len( self.children ) != len( other_structure.children ):
@@ -99,4 +94,7 @@
def get_structure( dataset_collection_instance, collection_type_description, leaf_subcollection_type=None ):
- return Tree( dataset_collection_instance.collection, collection_type_description, leaf_subcollection_type=leaf_subcollection_type )
+ if leaf_subcollection_type:
+ collection_type_description = collection_type_description.effective_collection_type_description( leaf_subcollection_type )
+
+ return Tree( dataset_collection_instance.collection, collection_type_description )
diff -r c28fa21b8f8ca56068f58c084b9f23fd048e7eea -r d283202d915e6f3fc5f80330a1e85d3a32a16049 lib/galaxy/dataset_collections/type_description.py
--- a/lib/galaxy/dataset_collections/type_description.py
+++ b/lib/galaxy/dataset_collections/type_description.py
@@ -15,9 +15,9 @@
""" Abstraction over dataset collection type that ties together string
reprentation in database/model with type registry.
-
- >>> nested_type_description = CollectionTypeDescription( "list:paired", None )
- >>> paired_type_description = CollectionTypeDescription( "paired", None )
+ >>> factory = CollectionTypeDescriptionFactory( None )
+ >>> nested_type_description = factory.for_collection_type( "list:paired" )
+ >>> paired_type_description = factory.for_collection_type( "paired" )
>>> nested_type_description.has_subcollections_of_type( "list" )
False
>>> nested_type_description.has_subcollections_of_type( "list:paired" )
@@ -34,6 +34,10 @@
'paired'
>>> nested_type_description.rank_collection_type()
'list'
+ >>> nested_type_description.effective_collection_type( paired_type_description )
+ 'list'
+ >>> nested_type_description.effective_collection_type_description( paired_type_description ).collection_type
+ 'list'
"""
def __init__( self, collection_type, collection_type_description_factory ):
@@ -41,6 +45,19 @@
self.collection_type_description_factory = collection_type_description_factory
self.__has_subcollections = self.collection_type.find( ":" ) > 0
+ def effective_collection_type_description( self, subcollection_type ):
+ effective_collection_type = self.effective_collection_type( subcollection_type )
+ return self.collection_type_description_factory.for_collection_type( effective_collection_type )
+
+ def effective_collection_type( self, subcollection_type ):
+ if hasattr( subcollection_type, 'collection_type' ):
+ subcollection_type = subcollection_type.collection_type
+
+ if not self.has_subcollections_of_type( subcollection_type ):
+ raise ValueError( "Cannot compute effective subcollection type of %s over %s" % ( subcollection_type, self ) )
+
+ return self.collection_type[ :-( len( subcollection_type ) + 1 ) ]
+
def has_subcollections_of_type( self, other_collection_type ):
""" Take in another type (either flat string or another
CollectionTypeDescription) and determine if this collection contains
diff -r c28fa21b8f8ca56068f58c084b9f23fd048e7eea -r d283202d915e6f3fc5f80330a1e85d3a32a16049 test/api/helpers.py
--- a/test/api/helpers.py
+++ b/test/api/helpers.py
@@ -291,12 +291,10 @@
return element_identifiers
def list_identifiers( self, history_id, contents=None ):
- hda1, hda2, hda3 = self.__datasets( history_id, count=3, contents=contents )
- element_identifiers = [
- dict( name="data1", src="hda", id=hda1[ "id" ] ),
- dict( name="data2", src="hda", id=hda2[ "id" ] ),
- dict( name="data3", src="hda", id=hda3[ "id" ] ),
- ]
+ count = 3 if not contents else len( contents )
+ hdas = self.__datasets( history_id, count=count, contents=contents )
+ hda_to_identifier = lambda ( i, hda ): dict( name="data%d" % ( i + 1 ), src="hda", id=hda[ "id" ] )
+ element_identifiers = map( hda_to_identifier, enumerate( hdas ) )
return element_identifiers
def __create( self, payload ):
diff -r c28fa21b8f8ca56068f58c084b9f23fd048e7eea -r d283202d915e6f3fc5f80330a1e85d3a32a16049 test/api/test_tools.py
--- a/test/api/test_tools.py
+++ b/test/api/test_tools.py
@@ -279,6 +279,27 @@
assert output1_content.strip() == "123\n456", output1_content
assert output2_content.strip() == "789\n0ab", output2_content
+ @skip_without_tool( "collection_mixed_param" )
+ def test_combined_mapping_and_subcollection_mapping( self ):
+ history_id = self.dataset_populator.new_history()
+ nested_list_id = self.__build_nested_list( history_id )
+ create_response = self.dataset_collection_populator.create_list_in_history( history_id, contents=["xxx", "yyy"] )
+ list_id = create_response.json()[ "id" ]
+ inputs = {
+ "f1|__collection_multirun__": "%s|paired" % nested_list_id,
+ "f2|__collection_multirun__": list_id,
+ }
+ self.dataset_populator.wait_for_history( history_id, assert_ok=True )
+ outputs = self._run_and_get_outputs( "collection_mixed_param", history_id, inputs )
+ assert len( outputs ), 2
+ self.dataset_populator.wait_for_history( history_id, assert_ok=True )
+ output1 = outputs[ 0 ]
+ output2 = outputs[ 1 ]
+ output1_content = self._get_content( history_id, dataset=output1 )
+ output2_content = self._get_content( history_id, dataset=output2 )
+ assert output1_content.strip() == "123\n456\nxxx", output1_content
+ assert output2_content.strip() == "789\n0ab\nyyy", output2_content
+
def _cat1_outputs( self, history_id, inputs ):
return self._run_outputs( self._run_cat1( history_id, inputs ) )
diff -r c28fa21b8f8ca56068f58c084b9f23fd048e7eea -r d283202d915e6f3fc5f80330a1e85d3a32a16049 test/unit/dataset_collections/test_matching.py
--- /dev/null
+++ b/test/unit/dataset_collections/test_matching.py
@@ -0,0 +1,141 @@
+from galaxy.dataset_collections import (
+ type_description,
+ registry,
+ matching,
+)
+
+TYPE_REGISTRY = registry.DatasetCollectionTypesRegistry( None )
+TYPE_DESCRIPTION_FACTORY = type_description.CollectionTypeDescriptionFactory( TYPE_REGISTRY )
+
+
+def test_pairs_match():
+ assert_can_match( pair_instance(), pair_instance() )
+
+
+def test_lists_of_same_cardinality_match():
+ assert_can_match( list_instance(), list_instance() )
+
+
+def test_nested_lists_match():
+ nested_list = list_instance(
+ elements=[
+ pair_element("data1"),
+ pair_element("data2"),
+ pair_element("data3"),
+ ],
+ collection_type="list:paired",
+ )
+ assert_can_match( nested_list, nested_list )
+
+
+def test_different_types_cannot_match():
+ assert_cannot_match( list_instance(), pair_instance() )
+ assert_cannot_match( pair_instance(), list_instance() )
+
+
+def test_lists_of_different_cardinality_do_not_match():
+ list_1 = list_instance( ids=[ "data1", "data2" ] )
+ list_2 = list_instance( ids=[ "data1", "data2", "data3" ] )
+ assert_cannot_match( list_1, list_2 )
+ assert_cannot_match( list_2, list_1 )
+
+
+def test_valid_collection_subcollection_matching():
+ flat_list = list_instance( ids=[ "data1", "data2", "data3" ] )
+ nested_list = list_instance(
+ elements=[
+ pair_element("data11"),
+ pair_element("data21"),
+ pair_element("data31"),
+ ],
+ collection_type="list:paired",
+ )
+ assert_cannot_match( flat_list, nested_list )
+ assert_cannot_match( nested_list, flat_list )
+ assert_can_match( ( nested_list, "paired" ), flat_list )
+
+
+def assert_can_match( *items ):
+ to_match = build_collections_to_match( *items )
+ matching.MatchingCollections.for_collections( to_match, TYPE_DESCRIPTION_FACTORY )
+
+
+def assert_cannot_match( *items ):
+ to_match = build_collections_to_match( *items )
+ threw_exception = False
+ try:
+ matching.MatchingCollections.for_collections( to_match, TYPE_DESCRIPTION_FACTORY )
+ except Exception:
+ threw_exception = True
+ assert threw_exception
+
+
+def build_collections_to_match( *items ):
+ to_match = matching.CollectionsToMatch()
+
+ for i, item in enumerate( items ):
+ if isinstance( item, tuple ):
+ collection_instance, subcollection_type = item
+ else:
+ collection_instance, subcollection_type = item, None
+ to_match.add( "input_%d" % i, collection_instance, subcollection_type )
+ return to_match
+
+
+def pair_element( element_identifier ):
+ return collection_element( element_identifier, pair_instance().collection )
+
+
+def pair_instance( ):
+ paired_collection_instance = collection_instance( collection_type="paired", elements=[
+ hda_element( "left" ),
+ hda_element( "right" ),
+ ] )
+ return paired_collection_instance
+
+
+def list_instance( collection_type="list", elements=None, ids=None ):
+ if not elements:
+ if ids is None:
+ ids = [ "data1", "data2" ]
+ elements = map(hda_element, ids)
+ list_collection_instance = collection_instance(
+ collection_type=collection_type,
+ elements=elements
+ )
+ return list_collection_instance
+
+
+class MockCollectionInstance( object ):
+
+ def __init__( self, collection_type, elements ):
+ self.collection = MockCollection( collection_type, elements )
+
+
+class MockCollection( object ):
+
+ def __init__( self, collection_type, elements ):
+ self.collection_type = collection_type
+ self.elements = elements
+
+
+class MockCollectionElement( object ):
+
+ def __init__( self, element_identifier, collection ):
+ self.element_identifier = element_identifier
+ self.child_collection = collection
+ self.hda = None
+
+
+class MockHDAElement( object ):
+
+ def __init__( self, element_identifier ):
+ self.element_identifier = element_identifier
+ self.child_collection = False
+ self.hda = object()
+
+
+collection_instance = MockCollectionInstance
+collection = MockCollection
+collection_element = MockCollectionElement
+hda_element = MockHDAElement
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/4bf3a182df98/
Changeset: 4bf3a182df98
User: martenson
Date: 2014-05-07 21:31:03
Summary: pep8 fixes
Affected #: 1 file
diff -r bc1d006b0e66209e26f107ff6b48bfece3777eec -r 4bf3a182df988e5c17e5f6080900130636ecf00f lib/galaxy/util/streamball.py
--- a/lib/galaxy/util/streamball.py
+++ b/lib/galaxy/util/streamball.py
@@ -2,11 +2,13 @@
A simple wrapper for writing tarballs as a stream.
"""
import os
-import logging, tarfile
+import logging
+import tarfile
from galaxy.exceptions import ObjectNotFound
log = logging.getLogger( __name__ )
+
class StreamBall( object ):
def __init__( self, mode, members=None ):
self.members = members
@@ -15,16 +17,19 @@
self.mode = mode
self.wsgi_status = None
self.wsgi_headeritems = None
+
def add( self, file, relpath, check_file=False):
- if check_file and len(file)>0:
+ if check_file and len(file) > 0:
if not os.path.isfile(file):
raise ObjectNotFound
else:
self.members[file] = relpath
else:
self.members[file] = relpath
+
def stream( self, environ, start_response ):
response_write = start_response( self.wsgi_status, self.wsgi_headeritems )
+
class tarfileobj:
def write( self, *args, **kwargs ):
response_write( *args, **kwargs )
@@ -34,10 +39,12 @@
tf.close()
return []
+
class ZipBall(object):
def __init__(self, tmpf, tmpd):
self._tmpf = tmpf
self._tmpd = tmpd
+
def stream(self, environ, start_response):
response_write = start_response( self.wsgi_status, self.wsgi_headeritems )
tmpfh = open( self._tmpf )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: martenson: hunted down bug in streamball implementation that prevented adding files to archive if the filecheck was on and it passed
by commits-noreply@bitbucket.org 07 May '14
by commits-noreply@bitbucket.org 07 May '14
07 May '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/bc1d006b0e66/
Changeset: bc1d006b0e66
User: martenson
Date: 2014-05-07 21:27:37
Summary: hunted down bug in streamball implementation that prevented adding files to archive if the filecheck was on and it passed
Affected #: 1 file
diff -r f7baaf52e94c57cc1a3f7297c191a3e19826293d -r bc1d006b0e66209e26f107ff6b48bfece3777eec lib/galaxy/util/streamball.py
--- a/lib/galaxy/util/streamball.py
+++ b/lib/galaxy/util/streamball.py
@@ -19,6 +19,8 @@
if check_file and len(file)>0:
if not os.path.isfile(file):
raise ObjectNotFound
+ else:
+ self.members[file] = relpath
else:
self.members[file] = relpath
def stream( self, environ, start_response ):
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: martenson: packed versions of John's JS code from dataset collections
by commits-noreply@bitbucket.org 07 May '14
by commits-noreply@bitbucket.org 07 May '14
07 May '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/f7baaf52e94c/
Changeset: f7baaf52e94c
User: martenson
Date: 2014-05-07 20:29:04
Summary: packed versions of John's JS code from dataset collections
Affected #: 12 files
diff -r 40054ac6f07bce479affd6b39772640e9321e57b -r f7baaf52e94c57cc1a3f7297c191a3e19826293d static/scripts/packed/galaxy.base.js
--- a/static/scripts/packed/galaxy.base.js
+++ b/static/scripts/packed/galaxy.base.js
@@ -1,1 +1,1 @@
-(function(){var b=0;var c=["ms","moz","webkit","o"];for(var a=0;a<c.length&&!window.requestAnimationFrame;++a){window.requestAnimationFrame=window[c[a]+"RequestAnimationFrame"];window.cancelRequestAnimationFrame=window[c[a]+"CancelRequestAnimationFrame"]}if(!window.requestAnimationFrame){window.requestAnimationFrame=function(h,e){var d=new Date().getTime();var f=Math.max(0,16-(d-b));var g=window.setTimeout(function(){h(d+f)},f);b=d+f;return g}}if(!window.cancelAnimationFrame){window.cancelAnimationFrame=function(d){clearTimeout(d)}}}());if(!Array.indexOf){Array.prototype.indexOf=function(c){for(var b=0,a=this.length;b<a;b++){if(this[b]==c){return b}}return -1}}function obj_length(c){if(c.length!==undefined){return c.length}var b=0;for(var a in c){b++}return b}$.fn.makeAbsolute=function(a){return this.each(function(){var b=$(this);var c=b.position();b.css({position:"absolute",marginLeft:0,marginTop:0,top:c.top,left:c.left,right:$(window).width()-(c.left+b.width())});if(a){b.remove().appendTo("body")}})};function make_popupmenu(b,c){var a=(b.data("menu_options"));b.data("menu_options",c);if(a){return}b.bind("click.show_popup",function(d){$(".popmenu-wrapper").remove();setTimeout(function(){var g=$("<ul class='dropdown-menu' id='"+b.attr("id")+"-menu'></ul>");var f=b.data("menu_options");if(obj_length(f)<=0){$("<li>No Options.</li>").appendTo(g)}$.each(f,function(j,i){if(i){var l=i.action||i;g.append($("<li></li>").append($("<a>").attr("href",i.url).html(j).click(l)))}else{g.append($("<li></li>").addClass("head").append($("<a href='#'></a>").html(j)))}});var h=$("<div class='popmenu-wrapper' style='position: absolute;left: 0; top: -1000;'></div>").append(g).appendTo("body");var e=d.pageX-h.width()/2;e=Math.min(e,$(document).scrollLeft()+$(window).width()-$(h).width()-5);e=Math.max(e,$(document).scrollLeft()+5);h.css({top:d.pageY,left:e})},10);setTimeout(function(){var f=function(h){$(h).bind("click.close_popup",function(){$(".popmenu-wrapper").remove();h.unbind("click.close_popup")})};f($(window.document));f($(window.top.document));for(var e=window.top.frames.length;e--;){var g=$(window.top.frames[e].document);f(g)}},50);return false})}function make_popup_menus(a){a=a||document;$(a).find("div[popupmenu]").each(function(){var b={};var d=$(this);d.find("a").each(function(){var g=$(this),i=g.get(0),e=i.getAttribute("confirm"),f=i.getAttribute("href"),h=i.getAttribute("target");if(!f){b[g.text()]=null}else{b[g.text()]={url:f,action:function(){if(!e||confirm(e)){if(h){window.open(f,h);return false}else{g.click()}}}}}});var c=$(a).find("#"+d.attr("popupmenu"));c.find("a").bind("click",function(f){f.stopPropagation();return true});make_popupmenu(c,b);c.addClass("popup");d.remove()})}function naturalSort(j,h){var p=/(-?[0-9\.]+)/g,k=j.toString().toLowerCase()||"",g=h.toString().toLowerCase()||"",l=String.fromCharCode(0),n=k.replace(p,l+"$1"+l).split(l),e=g.replace(p,l+"$1"+l).split(l),d=(new Date(k)).getTime(),o=d?(new Date(g)).getTime():null;if(o){if(d<o){return -1}else{if(d>o){return 1}}}var m,f;for(var i=0,c=Math.max(n.length,e.length);i<c;i++){m=parseFloat(n[i])||n[i];f=parseFloat(e[i])||e[i];if(m<f){return -1}else{if(m>f){return 1}}}return 0}$.fn.refresh_select2=function(){var b=$(this);var a={width:"resolve",closeOnSelect:!b.is("[MULTIPLE]")};return b.select2(a)};function replace_big_select_inputs(a,c,b){if(!jQuery.fn.select2){return}if(a===undefined){a=20}if(c===undefined){c=3000}b=b||$("select");b.each(function(){var e=$(this);var d=e.find("option").length;if((d<a)||(d>c)){return}if(e.hasClass("no-autocomplete")){return}e.refresh_select2()})}$.fn.make_text_editable=function(g){var d=("num_cols" in g?g.num_cols:30),c=("num_rows" in g?g.num_rows:4),e=("use_textarea" in g?g.use_textarea:false),b=("on_finish" in g?g.on_finish:null),f=("help_text" in g?g.help_text:null);var a=$(this);a.addClass("editable-text").click(function(l){if($(this).children(":input").length>0){return}a.removeClass("editable-text");var i=function(m){a.find(":input").remove();if(m!==""){a.text(m)}else{a.html("<br>")}a.addClass("editable-text");if(b){b(m)}};var h=("cur_text" in g?g.cur_text:a.text()),k,j;if(e){k=$("<textarea/>").attr({rows:c,cols:d}).text($.trim(h)).keyup(function(m){if(m.keyCode===27){i(h)}});j=$("<button/>").text("Done").click(function(){i(k.val());return false})}else{k=$("<input type='text'/>").attr({value:$.trim(h),size:d}).blur(function(){i(h)}).keyup(function(m){if(m.keyCode===27){$(this).trigger("blur")}else{if(m.keyCode===13){i($(this).val())}}m.stopPropagation()})}a.text("");a.append(k);if(j){a.append(j)}k.focus();k.select();l.stopPropagation()});if(f){a.attr("title",f).tooltip()}return a};function async_save_text(d,f,e,a,c,h,i,g,b){if(c===undefined){c=30}if(i===undefined){i=4}$("#"+d).click(function(){if($("#renaming-active").length>0){return}var l=$("#"+f),k=l.text(),j;if(h){j=$("<textarea></textarea>").attr({rows:i,cols:c}).text($.trim(k))}else{j=$("<input type='text'></input>").attr({value:$.trim(k),size:c})}j.attr("id","renaming-active");j.blur(function(){$(this).remove();l.show();if(b){b(j)}});j.keyup(function(n){if(n.keyCode===27){$(this).trigger("blur")}else{if(n.keyCode===13){var m={};m[a]=$(this).val();$(this).trigger("blur");$.ajax({url:e,data:m,error:function(){alert("Text editing for elt "+f+" failed")},success:function(o){if(o!==""){l.text(o)}else{l.html("<em>None</em>")}if(b){b(j)}}})}}});if(g){g(j)}l.hide();j.insertAfter(l);j.focus();j.select();return})}function init_history_items(d,a,c){var b=function(){try{var e=$.jStorage.get("history_expand_state");if(e){for(var g in e){$("#"+g+" div.historyItemBody").show()}}}catch(f){$.jStorage.deleteKey("history_expand_state")}if($.browser.mozilla){$("div.historyItemBody").each(function(){if(!$(this).is(":visible")){$(this).find("pre.peek").css("overflow","hidden")}})}d.each(function(){var j=this.id,h=$(this).children("div.historyItemBody"),i=h.find("pre.peek");$(this).find(".historyItemTitleBar > .historyItemTitle").wrap("<a href='javascript:void(0);'></a>").click(function(){var k;if(h.is(":visible")){if($.browser.mozilla){i.css("overflow","hidden")}h.slideUp("fast");if(!c){k=$.jStorage.get("history_expand_state");if(k){delete k[j];$.jStorage.set("history_expand_state",k)}}}else{h.slideDown("fast",function(){if($.browser.mozilla){i.css("overflow","auto")}});if(!c){k=$.jStorage.get("history_expand_state");if(!k){k={}}k[j]=true;$.jStorage.set("history_expand_state",k)}}return false})});$("#top-links > a.toggle").click(function(){var h=$.jStorage.get("history_expand_state");if(!h){h={}}$("div.historyItemBody:visible").each(function(){if($.browser.mozilla){$(this).find("pre.peek").css("overflow","hidden")}$(this).slideUp("fast");if(h){delete h[$(this).parent().attr("id")]}});$.jStorage.set("history_expand_state",h)}).show()};b()}function commatize(b){b+="";var a=/(\d+)(\d{3})/;while(a.test(b)){b=b.replace(a,"$1,$2")}return b}function reset_tool_search(a){var c=$("#galaxy_tools").contents();if(c.length===0){c=$(document)}$(this).removeClass("search_active");c.find(".toolTitle").removeClass("search_match");c.find(".toolSectionBody").hide();c.find(".toolTitle").show();c.find(".toolPanelLabel").show();c.find(".toolSectionWrapper").each(function(){if($(this).attr("id")!=="recently_used_wrapper"){$(this).show()}else{if($(this).hasClass("user_pref_visible")){$(this).show()}}});c.find("#search-no-results").hide();c.find("#search-spinner").hide();if(a){var b=c.find("#tool-search-query");b.val("search tools")}}var GalaxyAsync=function(a){this.url_dict={};this.log_action=(a===undefined?false:a)};GalaxyAsync.prototype.set_func_url=function(a,b){this.url_dict[a]=b};GalaxyAsync.prototype.set_user_pref=function(a,b){var c=this.url_dict[arguments.callee];if(c===undefined){return false}$.ajax({url:c,data:{pref_name:a,pref_value:b},error:function(){return false},success:function(){return true}})};GalaxyAsync.prototype.log_user_action=function(c,b,d){if(!this.log_action){return}var a=this.url_dict[arguments.callee];if(a===undefined){return false}$.ajax({url:a,data:{action:c,context:b,params:d},error:function(){return false},success:function(){return true}})};function init_refresh_on_change(){$("select[refresh_on_change='true']").off("change").change(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");$(document).trigger("convert_to_values");a.get(0).form.submit()});$(":checkbox[refresh_on_change='true']").off("click").click(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");a.get(0).form.submit()});$("a[confirm]").off("click").click(function(){return confirm($(this).attr("confirm"))})}$(document).ready(function(){init_refresh_on_change();if($.fn.tooltip){$(".unified-panel-header [title]").tooltip({placement:"bottom"});$("[title]").tooltip()}make_popup_menus();replace_big_select_inputs(20,1500);$("a").click(function(){var b=$(this);var c=(parent.frames&&parent.frames.galaxy_main);if((b.attr("target")=="galaxy_main")&&(!c)){var a=b.attr("href");if(a.indexOf("?")==-1){a+="?"}else{a+="&"}a+="use_panels=True";b.attr("href",a);b.attr("target","_self")}return b})});
\ No newline at end of file
+(function(){var b=0;var c=["ms","moz","webkit","o"];for(var a=0;a<c.length&&!window.requestAnimationFrame;++a){window.requestAnimationFrame=window[c[a]+"RequestAnimationFrame"];window.cancelRequestAnimationFrame=window[c[a]+"CancelRequestAnimationFrame"]}if(!window.requestAnimationFrame){window.requestAnimationFrame=function(h,e){var d=new Date().getTime();var f=Math.max(0,16-(d-b));var g=window.setTimeout(function(){h(d+f)},f);b=d+f;return g}}if(!window.cancelAnimationFrame){window.cancelAnimationFrame=function(d){clearTimeout(d)}}}());if(!Array.indexOf){Array.prototype.indexOf=function(c){for(var b=0,a=this.length;b<a;b++){if(this[b]==c){return b}}return -1}}function obj_length(c){if(c.length!==undefined){return c.length}var b=0;for(var a in c){b++}return b}$.fn.makeAbsolute=function(a){return this.each(function(){var b=$(this);var c=b.position();b.css({position:"absolute",marginLeft:0,marginTop:0,top:c.top,left:c.left,right:$(window).width()-(c.left+b.width())});if(a){b.remove().appendTo("body")}})};function make_popupmenu(b,c){var a=(b.data("menu_options"));b.data("menu_options",c);if(a){return}b.bind("click.show_popup",function(d){$(".popmenu-wrapper").remove();setTimeout(function(){var g=$("<ul class='dropdown-menu' id='"+b.attr("id")+"-menu'></ul>");var f=b.data("menu_options");if(obj_length(f)<=0){$("<li>No Options.</li>").appendTo(g)}$.each(f,function(j,i){if(i){var l=i.action||i;g.append($("<li></li>").append($("<a>").attr("href",i.url).html(j).click(l)))}else{g.append($("<li></li>").addClass("head").append($("<a href='#'></a>").html(j)))}});var h=$("<div class='popmenu-wrapper' style='position: absolute;left: 0; top: -1000;'></div>").append(g).appendTo("body");var e=d.pageX-h.width()/2;e=Math.min(e,$(document).scrollLeft()+$(window).width()-$(h).width()-5);e=Math.max(e,$(document).scrollLeft()+5);h.css({top:d.pageY,left:e})},10);setTimeout(function(){var f=function(h){$(h).bind("click.close_popup",function(){$(".popmenu-wrapper").remove();h.unbind("click.close_popup")})};f($(window.document));f($(window.top.document));for(var e=window.top.frames.length;e--;){var g=$(window.top.frames[e].document);f(g)}},50);return false})}function make_popup_menus(a){a=a||document;$(a).find("div[popupmenu]").each(function(){var b={};var d=$(this);d.find("a").each(function(){var g=$(this),i=g.get(0),e=i.getAttribute("confirm"),f=i.getAttribute("href"),h=i.getAttribute("target");if(!f){b[g.text()]=null}else{b[g.text()]={url:f,action:function(){if(!e||confirm(e)){if(h){window.open(f,h);return false}else{g.click()}}}}}});var c=$(a).find("#"+d.attr("popupmenu"));c.find("a").bind("click",function(f){f.stopPropagation();return true});make_popupmenu(c,b);c.addClass("popup");d.remove()})}function naturalSort(j,h){var p=/(-?[0-9\.]+)/g,k=j.toString().toLowerCase()||"",g=h.toString().toLowerCase()||"",l=String.fromCharCode(0),n=k.replace(p,l+"$1"+l).split(l),e=g.replace(p,l+"$1"+l).split(l),d=(new Date(k)).getTime(),o=d?(new Date(g)).getTime():null;if(o){if(d<o){return -1}else{if(d>o){return 1}}}var m,f;for(var i=0,c=Math.max(n.length,e.length);i<c;i++){m=parseFloat(n[i])||n[i];f=parseFloat(e[i])||e[i];if(m<f){return -1}else{if(m>f){return 1}}}return 0}$.fn.refresh_select2=function(){var b=$(this);var a={width:"resolve",closeOnSelect:!b.is("[MULTIPLE]")};return b.select2(a)};function replace_big_select_inputs(a,c,b){if(!jQuery.fn.select2){return}if(a===undefined){a=20}if(c===undefined){c=3000}b=b||$("select");b.each(function(){var e=$(this).not("[multiple]");var d=e.find("option").length;if((d<a)||(d>c)){return}if(e.hasClass("no-autocomplete")){return}e.refresh_select2()})}$.fn.make_text_editable=function(g){var d=("num_cols" in g?g.num_cols:30),c=("num_rows" in g?g.num_rows:4),e=("use_textarea" in g?g.use_textarea:false),b=("on_finish" in g?g.on_finish:null),f=("help_text" in g?g.help_text:null);var a=$(this);a.addClass("editable-text").click(function(l){if($(this).children(":input").length>0){return}a.removeClass("editable-text");var i=function(m){a.find(":input").remove();if(m!==""){a.text(m)}else{a.html("<br>")}a.addClass("editable-text");if(b){b(m)}};var h=("cur_text" in g?g.cur_text:a.text()),k,j;if(e){k=$("<textarea/>").attr({rows:c,cols:d}).text($.trim(h)).keyup(function(m){if(m.keyCode===27){i(h)}});j=$("<button/>").text("Done").click(function(){i(k.val());return false})}else{k=$("<input type='text'/>").attr({value:$.trim(h),size:d}).blur(function(){i(h)}).keyup(function(m){if(m.keyCode===27){$(this).trigger("blur")}else{if(m.keyCode===13){i($(this).val())}}m.stopPropagation()})}a.text("");a.append(k);if(j){a.append(j)}k.focus();k.select();l.stopPropagation()});if(f){a.attr("title",f).tooltip()}return a};function async_save_text(d,f,e,a,c,h,i,g,b){if(c===undefined){c=30}if(i===undefined){i=4}$("#"+d).click(function(){if($("#renaming-active").length>0){return}var l=$("#"+f),k=l.text(),j;if(h){j=$("<textarea></textarea>").attr({rows:i,cols:c}).text($.trim(k))}else{j=$("<input type='text'></input>").attr({value:$.trim(k),size:c})}j.attr("id","renaming-active");j.blur(function(){$(this).remove();l.show();if(b){b(j)}});j.keyup(function(n){if(n.keyCode===27){$(this).trigger("blur")}else{if(n.keyCode===13){var m={};m[a]=$(this).val();$(this).trigger("blur");$.ajax({url:e,data:m,error:function(){alert("Text editing for elt "+f+" failed")},success:function(o){if(o!==""){l.text(o)}else{l.html("<em>None</em>")}if(b){b(j)}}})}}});if(g){g(j)}l.hide();j.insertAfter(l);j.focus();j.select();return})}function init_history_items(d,a,c){var b=function(){try{var e=$.jStorage.get("history_expand_state");if(e){for(var g in e){$("#"+g+" div.historyItemBody").show()}}}catch(f){$.jStorage.deleteKey("history_expand_state")}if($.browser.mozilla){$("div.historyItemBody").each(function(){if(!$(this).is(":visible")){$(this).find("pre.peek").css("overflow","hidden")}})}d.each(function(){var j=this.id,h=$(this).children("div.historyItemBody"),i=h.find("pre.peek");$(this).find(".historyItemTitleBar > .historyItemTitle").wrap("<a href='javascript:void(0);'></a>").click(function(){var k;if(h.is(":visible")){if($.browser.mozilla){i.css("overflow","hidden")}h.slideUp("fast");if(!c){k=$.jStorage.get("history_expand_state");if(k){delete k[j];$.jStorage.set("history_expand_state",k)}}}else{h.slideDown("fast",function(){if($.browser.mozilla){i.css("overflow","auto")}});if(!c){k=$.jStorage.get("history_expand_state");if(!k){k={}}k[j]=true;$.jStorage.set("history_expand_state",k)}}return false})});$("#top-links > a.toggle").click(function(){var h=$.jStorage.get("history_expand_state");if(!h){h={}}$("div.historyItemBody:visible").each(function(){if($.browser.mozilla){$(this).find("pre.peek").css("overflow","hidden")}$(this).slideUp("fast");if(h){delete h[$(this).parent().attr("id")]}});$.jStorage.set("history_expand_state",h)}).show()};b()}function commatize(b){b+="";var a=/(\d+)(\d{3})/;while(a.test(b)){b=b.replace(a,"$1,$2")}return b}function reset_tool_search(a){var c=$("#galaxy_tools").contents();if(c.length===0){c=$(document)}$(this).removeClass("search_active");c.find(".toolTitle").removeClass("search_match");c.find(".toolSectionBody").hide();c.find(".toolTitle").show();c.find(".toolPanelLabel").show();c.find(".toolSectionWrapper").each(function(){if($(this).attr("id")!=="recently_used_wrapper"){$(this).show()}else{if($(this).hasClass("user_pref_visible")){$(this).show()}}});c.find("#search-no-results").hide();c.find("#search-spinner").hide();if(a){var b=c.find("#tool-search-query");b.val("search tools")}}var GalaxyAsync=function(a){this.url_dict={};this.log_action=(a===undefined?false:a)};GalaxyAsync.prototype.set_func_url=function(a,b){this.url_dict[a]=b};GalaxyAsync.prototype.set_user_pref=function(a,b){var c=this.url_dict[arguments.callee];if(c===undefined){return false}$.ajax({url:c,data:{pref_name:a,pref_value:b},error:function(){return false},success:function(){return true}})};GalaxyAsync.prototype.log_user_action=function(c,b,d){if(!this.log_action){return}var a=this.url_dict[arguments.callee];if(a===undefined){return false}$.ajax({url:a,data:{action:c,context:b,params:d},error:function(){return false},success:function(){return true}})};function init_refresh_on_change(){$("select[refresh_on_change='true']").off("change").change(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");$(document).trigger("convert_to_values");a.get(0).form.submit()});$(":checkbox[refresh_on_change='true']").off("click").click(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");a.get(0).form.submit()});$("a[confirm]").off("click").click(function(){return confirm($(this).attr("confirm"))})}$(document).ready(function(){init_refresh_on_change();if($.fn.tooltip){$(".unified-panel-header [title]").tooltip({placement:"bottom"});$("[title]").tooltip()}make_popup_menus();replace_big_select_inputs(20,1500);$("a").click(function(){var b=$(this);var c=(parent.frames&&parent.frames.galaxy_main);if((b.attr("target")=="galaxy_main")&&(!c)){var a=b.attr("href");if(a.indexOf("?")==-1){a+="?"}else{a+="&"}a+="use_panels=True";b.attr("href",a);b.attr("target","_self")}return b})});
\ No newline at end of file
diff -r 40054ac6f07bce479affd6b39772640e9321e57b -r f7baaf52e94c57cc1a3f7297c191a3e19826293d static/scripts/packed/galaxy.tools.js
--- a/static/scripts/packed/galaxy.tools.js
+++ b/static/scripts/packed/galaxy.tools.js
@@ -1,1 +1,1 @@
-define(["mvc/tools"],function(b){var a=function(d,c){$("input[name='"+d+"'][type='checkbox']").attr("checked",!!c)};$("div.checkUncheckAllPlaceholder").each(function(){var c=$(this).attr("checkbox_name");select_link=$("<a class='action-button'></a>").text("Select All").click(function(){a(c,true)});unselect_link=$("<a class='action-button'></a>").text("Unselect All").click(function(){a(c,false)});$(this).append(select_link).append(" ").append(unselect_link)})});
\ No newline at end of file
+define(["libs/underscore","mvc/tools"],function(c,b){var a=function(g,f){$("input[name='"+g+"'][type='checkbox']").attr("checked",!!f)};$("div.checkUncheckAllPlaceholder").each(function(){var f=$(this).attr("checkbox_name");select_link=$("<a class='action-button'></a>").text("Select All").click(function(){a(f,true)});unselect_link=$("<a class='action-button'></a>").text("Unselect All").click(function(){a(f,false)});$(this).append(select_link).append(" ").append(unselect_link)});var e={select_single:{icon_class:"fa-file-o",select_by:"Run tool on single input",allow_remap:true},select_multiple:{icon_class:"fa-files-o",select_by:"Run tool in parallel across multiple datasets",allow_remap:false,min_option_count:2},select_collection:{icon_class:"fa-folder-o",select_by:"Run tool in parallel across dataset collection",allow_remap:false},multiselect_single:{icon_class:"fa-list-alt",select_by:"Run tool over multiple datasets",allow_remap:true},multiselect_collection:{icon_class:"fa-folder-o",select_by:"Run tool over dataset collection",allow_remap:false,},select_single_collection:{icon_class:"fa-file-o",select_by:"Run tool on single collection",allow_remap:true},select_map_over_collections:{icon_class:"fa-folder-o",select_by:"Map tool over compontents of nested collection",allow_remap:false,}};var d=Backbone.View.extend({initialize:function(k){var g=k.default_option;var l=null;var j=k.switch_options;this.switchOptions=j;this.prefix=k.prefix;var i=this.$el;var f=this;var h=0;c.each(this.switchOptions,function(q,s){var m=c.size(q.options);var p=e[s];var o=h++;var r=false;if(g==s){l=o}else{if(m<(p.min_option_count||1)){r=true}}if(!r){var n=$('<i class="fa '+p.icon_class+'" style="padding-left: 5px; padding-right: 2px;"></i>').click(function(){f.enableSelectBy(o,s)}).attr("title",p.select_by);f.formRow().find("label").append(n)}});if(l!=null){f.enableSelectBy(l,g)}},formRow:function(){return this.$el.closest(".form-row")},render:function(){},enableSelectBy:function(k,j){var h=e[j];if(h.allow_remap){$("div#remap-row").css("display","none")}else{$("div#remap-row").css("display","inherit")}this.formRow().find("i").each(function(l,m){if(l==k){$(m).css("color","black")}else{$(m).css("color","Gray")}});var g=this.$("select");var f=this.switchOptions[j];g.attr("name",this.prefix+f.name);g.attr("multiple",f.multiple);var i=this.$(".select2-container").length>0;g.html("");c.each(f.options,function(m){var o=m[0];var n=m[1];var l=m[2];g.append($("<option />",{text:o,val:n,selected:l}))});if(i){g.select2()}}});return{SwitchSelectView:d}});
\ No newline at end of file
diff -r 40054ac6f07bce479affd6b39772640e9321e57b -r f7baaf52e94c57cc1a3f7297c191a3e19826293d static/scripts/packed/galaxy.workflow_editor.canvas.js
--- a/static/scripts/packed/galaxy.workflow_editor.canvas.js
+++ b/static/scripts/packed/galaxy.workflow_editor.canvas.js
@@ -1,1 +1,1 @@
-var Terminal=Backbone.Model.extend({initialize:function(a){this.element=a.element;this.connectors=[]},connect:function(a){this.connectors.push(a);if(this.node){this.node.markChanged()}},disconnect:function(a){this.connectors.splice($.inArray(a,this.connectors),1);if(this.node){this.node.markChanged()}},redraw:function(){$.each(this.connectors,function(a,b){b.redraw()})},destroy:function(){$.each(this.connectors.slice(),function(a,b){b.destroy()})}});var OutputTerminal=Terminal.extend({initialize:function(a){Terminal.prototype.initialize.call(this,a);this.datatypes=a.datatypes}});var InputTerminal=Terminal.extend({initialize:function(a){Terminal.prototype.initialize.call(this,a);this.update(a.input)},update:function(a){this.datatypes=a.extensions;this.multiple=a.multiple},canAccept:function(a){if(this._inputFilled()){return false}else{return this.attachable(a)}},_inputFilled:function(){return !(this.connectors.length<1||this.multiple)},attachable:function(a){for(var c in this.datatypes){var f=new Array();f=f.concat(a.datatypes);if(a.node.post_job_actions){for(var d in a.node.post_job_actions){var g=a.node.post_job_actions[d];if(g.action_type=="ChangeDatatypeAction"&&(g.output_name==""||g.output_name==a.name)&&g.action_arguments){f.push(g.action_arguments.newtype)}}}for(var b in f){if(f[b]=="input"||issubtype(f[b],this.datatypes[c])){return true}}}return false}});function Connector(b,a){this.canvas=null;this.dragging=false;this.inner_color="#FFFFFF";this.outer_color="#D8B365";if(b&&a){this.connect(b,a)}}$.extend(Connector.prototype,{connect:function(b,a){this.handle1=b;if(this.handle1){this.handle1.connect(this)}this.handle2=a;if(this.handle2){this.handle2.connect(this)}},destroy:function(){if(this.handle1){this.handle1.disconnect(this)}if(this.handle2){this.handle2.disconnect(this)}$(this.canvas).remove()},redraw:function(){var d=$("#canvas-container");if(!this.canvas){this.canvas=document.createElement("canvas");if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(this.canvas)}d.append($(this.canvas));if(this.dragging){this.canvas.style.zIndex="300"}}var n=function(c){return $(c).offset().left-d.offset().left};var i=function(c){return $(c).offset().top-d.offset().top};if(!this.handle1||!this.handle2){return}var h=n(this.handle1.element)+5;var g=i(this.handle1.element)+5;var p=n(this.handle2.element)+5;var m=i(this.handle2.element)+5;var f=100;var k=Math.min(h,p);var a=Math.max(h,p);var j=Math.min(g,m);var t=Math.max(g,m);var b=Math.min(Math.max(Math.abs(t-j)/2,100),300);var o=k-f;var s=j-f;var q=a-k+2*f;var l=t-j+2*f;this.canvas.style.left=o+"px";this.canvas.style.top=s+"px";this.canvas.setAttribute("width",q);this.canvas.setAttribute("height",l);h-=o;g-=s;p-=o;m-=s;var r=this.canvas.getContext("2d");r.lineCap="round";r.strokeStyle=this.outer_color;r.lineWidth=7;r.beginPath();r.moveTo(h,g);r.bezierCurveTo(h+b,g,p-b,m,p,m);r.stroke();r.strokeStyle=this.inner_color;r.lineWidth=5;r.beginPath();r.moveTo(h,g);r.bezierCurveTo(h+b,g,p-b,m,p,m);r.stroke()}});var Node=Backbone.Model.extend({initialize:function(a){this.element=a.element;this.input_terminals={};this.output_terminals={};this.tool_errors={}},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()});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){if(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.workflow_outputs=b.workflow_outputs?b.workflow_outputs:[];var a=this;var c=new NodeView({el:this.element[0],node:a,});a.nodeView=c;$.each(b.data_inputs,function(f,d){c.addDataInput(d)});if((b.data_inputs.length>0)&&(b.data_outputs.length>0)){c.addRule()}$.each(b.data_outputs,function(f,d){c.addDataOutput(d)});c.render();workflow.node_changed(this)},update_field_data:function(d){var c=this;nodeView=c.nodeView;this.tool_state=d.tool_state;this.form_html=d.form_html;this.tool_errors=d.tool_errors;this.annotation=d.annotation;var f=$.parseJSON(d.post_job_actions);this.post_job_actions=f?f:{};c.nodeView.renderToolErrors();var g=nodeView.$("div.inputs");var a=nodeView.newInputsDiv();var b={};_.each(d.data_inputs,function(h){var i=c.nodeView.addDataInput(h,a);b[h.name]=i});_.each(_.difference(_.values(nodeView.terminalViews),_.values(b)),function(h){h.el.terminal.destroy()});nodeView.terminalViews=b;g.replaceWith(a);this.markChanged();this.redraw()},error:function(d){var a=$(this.element).find(".toolFormBody");a.find("div").remove();var c="<div style='color: red; text-style: italic;'>"+d+"</div>";this.form_html=c;a.html(c);workflow.node_changed(this)},markChanged:function(){workflow.node_changed(this)}});function Workflow(a){this.canvas_container=a;this.id_counter=0;this.nodes={};this.name=null;this.has_changes=false;this.active_form_has_changes=false}$.extend(Workflow.prototype,{add_node:function(a){a.id=this.id_counter;a.element.attr("id","wf-node-step-"+a.id);this.id_counter++;this.nodes[a.id]=a;this.has_changes=true;a.workflow=this},remove_node:function(a){if(this.active_node==a){this.clear_active_node()}delete this.nodes[a.id];this.has_changes=true},remove_all:function(){wf=this;$.each(this.nodes,function(b,a){a.destroy();wf.remove_node(a)})},rectify_workflow_outputs:function(){var b=false;var a=false;$.each(this.nodes,function(c,d){if(d.workflow_outputs&&d.workflow_outputs.length>0){b=true}$.each(d.post_job_actions,function(g,f){if(f.action_type==="HideDatasetAction"){a=true}})});if(b!==false||a!==false){$.each(this.nodes,function(c,g){if(g.type==="tool"){var f=false;if(g.post_job_actions==null){g.post_job_actions={};f=true}var d=[];$.each(g.post_job_actions,function(i,h){if(h.action_type=="HideDatasetAction"){d.push(i)}});if(d.length>0){$.each(d,function(h,j){f=true;delete g.post_job_actions[j]})}if(b){$.each(g.output_terminals,function(i,j){var h=true;$.each(g.workflow_outputs,function(l,m){if(j.name===m){h=false}});if(h===true){f=true;var k={action_type:"HideDatasetAction",output_name:j.name,action_arguments:{}};g.post_job_actions["HideDatasetAction"+j.name]=null;g.post_job_actions["HideDatasetAction"+j.name]=k}})}if(workflow.active_node==g&&f===true){workflow.reload_active_node()}}})}},to_simple:function(){var a={};$.each(this.nodes,function(c,f){var g={};$.each(f.input_terminals,function(i,j){g[j.name]=null;var h=[];$.each(j.connectors,function(k,l){h[k]={id:l.handle1.node.id,output_name:l.handle1.name};g[j.name]=h})});var b={};if(f.post_job_actions){$.each(f.post_job_actions,function(j,h){var k={action_type:h.action_type,output_name:h.output_name,action_arguments:h.action_arguments};b[h.action_type+h.output_name]=null;b[h.action_type+h.output_name]=k})}if(!f.workflow_outputs){f.workflow_outputs=[]}var d={id:f.id,type:f.type,tool_id:f.tool_id,tool_state:f.tool_state,tool_errors:f.tool_errors,input_connections:g,position:$(f.element).position(),annotation:f.annotation,post_job_actions:f.post_job_actions,workflow_outputs:f.workflow_outputs};a[f.id]=d});return{steps:a}},from_simple:function(b){wf=this;var c=0;wf.name=b.name;var a=false;$.each(b.steps,function(g,f){var d=prebuild_node(f.type,f.name,f.tool_id);d.init_field_data(f);if(f.position){d.element.css({top:f.position.top,left:f.position.left})}d.id=f.id;wf.nodes[d.id]=d;c=Math.max(c,parseInt(g));if(!a&&d.type==="tool"){if(d.workflow_outputs.length>0){a=true}else{$.each(d.post_job_actions,function(i,h){if(h.action_type==="HideDatasetAction"){a=true}})}}});wf.id_counter=c+1;$.each(b.steps,function(g,f){var d=wf.nodes[g];$.each(f.input_connections,function(i,h){if(h){if(!$.isArray(h)){h=[h]}$.each(h,function(k,j){var m=wf.nodes[j.id];var n=new Connector();n.connect(m.output_terminals[j.output_name],d.input_terminals[i]);n.redraw()})}});if(a&&d.type==="tool"){$.each(d.output_terminals,function(h,i){if(d.post_job_actions["HideDatasetAction"+i.name]===undefined){d.workflow_outputs.push(i.name);callout=$(d.element).find(".callout."+i.name);callout.find("img").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small.png");workflow.has_changes=true}})}})},check_changes_in_active_form:function(){if(this.active_form_has_changes){this.has_changes=true;$("#right-content").find("form").submit();this.active_form_has_changes=false}},reload_active_node:function(){if(this.active_node){var a=this.active_node;this.clear_active_node();this.activate_node(a)}},clear_active_node:function(){if(this.active_node){this.active_node.make_inactive();this.active_node=null}parent.show_form_for_tool("<div>No node selected</div>")},activate_node:function(a){if(this.active_node!=a){this.check_changes_in_active_form();this.clear_active_node();parent.show_form_for_tool(a.form_html+a.tooltip,a);a.make_active();this.active_node=a}},node_changed:function(a){this.has_changes=true;if(this.active_node==a){this.check_changes_in_active_form();parent.show_form_for_tool(a.form_html+a.tooltip,a)}},layout:function(){this.check_changes_in_active_form();this.has_changes=true;var i={};var b={};$.each(this.nodes,function(l,k){if(i[l]===undefined){i[l]=0}if(b[l]===undefined){b[l]=[]}});$.each(this.nodes,function(l,k){$.each(k.input_terminals,function(m,n){$.each(n.connectors,function(p,q){var o=q.handle1.node;i[k.id]+=1;b[o.id].push(k.id)})})});node_ids_by_level=[];while(true){level_parents=[];for(var a in i){if(i[a]==0){level_parents.push(a)}}if(level_parents.length==0){break}node_ids_by_level.push(level_parents);for(var f in level_parents){var j=level_parents[f];delete i[j];for(var g in b[j]){i[b[j][g]]-=1}}}if(i.length){return}var d=this.nodes;var h=80;v_pad=30;var c=h;$.each(node_ids_by_level,function(k,l){l.sort(function(p,o){return $(d[p].element).position().top-$(d[o].element).position().top});var m=0;var n=v_pad;$.each(l,function(o,r){var q=d[r];var p=$(q.element);$(p).css({top:n,left:c});m=Math.max(m,$(p).width());n+=$(p).height()+v_pad});c+=m+h});$.each(d,function(k,l){l.redraw()})},bounds_for_all_nodes:function(){var d=Infinity,b=-Infinity,c=Infinity,a=-Infinity,f;$.each(this.nodes,function(h,g){e=$(g.element);f=e.position();d=Math.min(d,f.left);b=Math.max(b,f.left+e.width());c=Math.min(c,f.top);a=Math.max(a,f.top+e.width())});return{xmin:d,xmax:b,ymin:c,ymax:a}},fit_canvas_to_nodes:function(){var a=this.bounds_for_all_nodes();var f=this.canvas_container.position();var i=this.canvas_container.parent();var d=fix_delta(a.xmin,100);var h=fix_delta(a.ymin,100);d=Math.max(d,f.left);h=Math.max(h,f.top);var c=f.left-d;var g=f.top-h;var b=round_up(a.xmax+100,100)+d;var j=round_up(a.ymax+100,100)+h;b=Math.max(b,-c+i.width());j=Math.max(j,-g+i.height());this.canvas_container.css({left:c,top:g,width:b,height:j});this.canvas_container.children().each(function(){var k=$(this).position();$(this).css("left",k.left+d);$(this).css("top",k.top+h)})}});function fix_delta(a,b){if(a<b||a>3*b){new_pos=(Math.ceil(((a%b))/b)+1)*b;return(-(a-new_pos))}return 0}function round_up(a,b){return Math.ceil(a/b)*b}function prebuild_node(l,j,r){var i=$("<div class='toolForm toolFormInCanvas'></div>");var g=new Node({element:i});g.type=l;if(l=="tool"){g.tool_id=r}var n=$("<div class='toolFormTitle unselectable'>"+j+"</div>");i.append(n);i.css("left",$(window).scrollLeft()+20);i.css("top",$(window).scrollTop()+20);var m=$("<div class='toolFormBody'></div>");var h="<div><img height='16' align='middle' src='"+galaxy_config.root+"static/images/loading_small_white_bg.gif'/> loading tool info...</div>";m.append(h);g.form_html=h;i.append(m);var k=$("<div class='buttons' style='float: right;'></div>");k.append($("<div>").addClass("fa-icon-button fa fa-times").click(function(b){g.destroy()}));i.appendTo("#canvas-container");var d=$("#canvas-container").position();var c=$("#canvas-container").parent();var a=i.width();var q=i.height();i.css({left:(-d.left)+(c.width()/2)-(a/2),top:(-d.top)+(c.height()/2)-(q/2)});k.prependTo(n);a+=(k.width()+10);i.css("width",a);$(i).bind("dragstart",function(){workflow.activate_node(g)}).bind("dragend",function(){workflow.node_changed(this);workflow.fit_canvas_to_nodes();canvas_manager.draw_overview()}).bind("dragclickonly",function(){workflow.activate_node(g)}).bind("drag",function(o,p){var f=$(this).offsetParent().offset(),b=p.offsetX-f.left,s=p.offsetY-f.top;$(this).css({left:b,top:s});$(this).find(".terminal").each(function(){this.terminal.redraw()})});return g}function add_node(b,d,a){var c=prebuild_node(b,d,a);workflow.add_node(c);workflow.fit_canvas_to_nodes();canvas_manager.draw_overview();workflow.activate_node(c);return c}var ext_to_type=null;var type_to_type=null;function issubtype(b,a){b=ext_to_type[b];a=ext_to_type[a];return(type_to_type[b])&&(a in type_to_type[b])}function populate_datatype_info(a){ext_to_type=a.ext_to_class_name;type_to_type=a.class_to_classes}var NodeView=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={}},render:function(){this.renderToolErrors();this.$el.css("width",Math.min(250,Math.max(this.$el.width(),this.output_width)))},renderToolErrors:function(){if(this.node.tool_errors){this.$el.addClass("tool-node-error")}else{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(d,a){var f=true;if(!a){a=this.$(".inputs");f=false}var h=this.terminalViews[d.name];if(!h){h=new InputTerminalView({node:this.node,input:d})}else{var g=h.el.terminal;g.update(d);_.each(g.connectors,function(j){if(j.handle1&&!g.attachable(j.handle1)){j.destroy()}})}this.terminalViews[d.name]=h;var i=h.el;var c=new DataInputView({terminalElement:i,input:d,nodeView:this,skipResize:f});var b=c.$el;var i=c.terminalElement;a.append(b.prepend(i));return h},addDataOutput:function(a){var c=new OutputTerminalView({node:this.node,output:a});var b=new DataOutputView({output:a,terminalElement:c.el,nodeView:this,});this.tool_body.append(b.$el.append(b.terminalElement))}});var DataInputView=Backbone.View.extend({className:"form-row dataRow input-data-row",initialize:function(a){this.input=a.input;this.nodeView=a.nodeView;this.terminalElement=a.terminalElement;this.$el.attr("name",this.input.name).html(this.input.label);if(!a.skipResize){this.$el.css({position:"absolute",left:-1000,top:-1000,display:"none"});$("body").append(this.el);this.nodeView.updateMaxWidth(this.$el.outerWidth());this.$el.css({position:"",left:"",top:"",display:""});this.$el.remove()}},});var OutputCalloutView=Backbone.View.extend({tagName:"div",initialize:function(b){this.label=b.label;this.node=b.node;this.output=b.output;var a=this;this.$el.attr("class","callout "+this.label).css({display:"none"}).append($("<div class='buttons'></div>").append($("<img/>").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small-outline.png").click(function(){if($.inArray(a.output.name,a.node.workflow_outputs)!=-1){a.node.workflow_outputs.splice($.inArray(a.output.name,a.node.workflow_outputs),1);a.$("img").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small-outline.png")}else{a.node.workflow_outputs.push(a.output.name);a.$("img").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small.png")}workflow.has_changes=true;canvas_manager.draw_overview()}))).tooltip({delay:500,title:"Mark dataset as a workflow output. All unmarked datasets will be hidden."});this.$el.css({top:"50%",margin:"-8px 0px 0px 0px",right:8});this.$el.show();this.resetImage()},resetImage:function(){if($.inArray(this.output.name,this.node.workflow_outputs)===-1){this.$("img").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small-outline.png")}else{this.$("img").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small.png")}},hoverImage:function(){this.$("img").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small-yellow.png")}});var DataOutputView=Backbone.View.extend({className:"form-row dataRow",initialize:function(c){this.output=c.output;this.terminalElement=c.terminalElement;this.nodeView=c.nodeView;var a=this.output;var b=a.name;var d=this.nodeView.node;if(a.extensions.indexOf("input")<0){b=b+" ("+a.extensions.join(", ")+")"}this.$el.html(b);if(d.type=="tool"){var f=new OutputCalloutView({label:b,output:a,node:d,});this.$el.append(f.el);this.$el.hover(function(){f.hoverImage()},function(){f.resetImage()})}this.$el.css({position:"absolute",left:-1000,top:-1000,display:"none"});$("body").append(this.el);this.nodeView.updateMaxWidth(this.$el.outerWidth()+17);this.$el.css({position:"",left:"",top:"",display:""}).detach()}});var InputTerminalView=Backbone.View.extend({className:"terminal input-terminal",initialize:function(c){var f=c.node;var a=c.input;var b=a.name;var d=this.el.terminal=new InputTerminal({element:this.el,input:a});d.node=f;d.name=b;f.input_terminals[b]=d},events:{dropinit:"onDropInit",dropstart:"onDropStart",dropend:"onDropEnd",drop:"onDrop",hover:"onHover",},onDropInit:function(b,c){var a=this.el.terminal;return $(c.drag).hasClass("output-terminal")&&a.canAccept(c.drag.terminal)},onDropStart:function(a,b){if(b.proxy.terminal){b.proxy.terminal.connectors[0].inner_color="#BBFFBB"}},onDropEnd:function(a,b){if(b.proxy.terminal){b.proxy.terminal.connectors[0].inner_color="#FFFFFF"}},onDrop:function(b,c){var a=this.el.terminal;new Connector(c.drag.terminal,a).redraw()},onHover:function(){var c=this.el;var b=c.terminal;if(b.connectors.length>0){var a=$("<div class='callout'></div>").css({display:"none"}).appendTo("body").append($("<div class='button'></div>").append($("<div/>").addClass("fa-icon-button fa fa-times").click(function(){$.each(b.connectors,function(f,d){if(d){d.destroy()}});a.remove()}))).bind("mouseleave",function(){$(this).remove()});a.css({top:$(c).offset().top-2,left:$(c).offset().left-a.width(),"padding-right":$(c).width()}).show()}},});var OutputTerminalView=Backbone.View.extend({className:"terminal output-terminal",initialize:function(c){var i=c.node;var a=c.output;var b=a.name;var h=a.extensions;var g=this.el;var f=g;var d=g.terminal=new OutputTerminal({element:g,datatypes:h});d.node=i;d.name=b;i.output_terminals[b]=d},events:{drag:"onDrag",dragstart:"onDragStart",dragend:"onDragEnd",},onDrag:function(b,c){var a=function(){var f=$(c.proxy).offsetParent().offset(),d=c.offsetX-f.left,g=c.offsetY-f.top;$(c.proxy).css({left:d,top:g});c.proxy.terminal.redraw();canvas_manager.update_viewport_overlay()};a();$("#canvas-container").get(0).scroll_panel.test(b,a)},onDragStart:function(b,f){$(f.available).addClass("input-terminal-active");workflow.check_changes_in_active_form();var a=$('<div class="drag-terminal" style="position: absolute;"></div>').appendTo("#canvas-container").get(0);a.terminal=new OutputTerminal({element:a});var g=new Connector();g.dragging=true;g.connect(this.el.terminal,a.terminal);return a},onDragEnd:function(a,b){b.proxy.terminal.connectors[0].destroy();$(b.proxy).remove();$(b.available).removeClass("input-terminal-active");$("#canvas-container").get(0).scroll_panel.stop()}});function ScrollPanel(a){this.panel=a}$.extend(ScrollPanel.prototype,{test:function(v,d){clearTimeout(this.timeout);var k=v.pageX,j=v.pageY,l=$(this.panel),c=l.position(),b=l.width(),i=l.height(),w=l.parent(),s=w.width(),a=w.height(),r=w.offset(),p=r.left,m=r.top,A=p+w.width(),u=m+w.height(),B=-(b-(s/2)),z=-(i-(a/2)),g=(s/2),f=(a/2),h=false,q=5,o=23;if(k-q<p){if(c.left<g){var n=Math.min(o,g-c.left);l.css("left",c.left+n);h=true}}else{if(k+q>A){if(c.left>B){var n=Math.min(o,c.left-B);l.css("left",c.left-n);h=true}}else{if(j-q<m){if(c.top<f){var n=Math.min(o,f-c.top);l.css("top",c.top+n);h=true}}else{if(j+q>u){if(c.top>z){var n=Math.min(o,c.top-B);l.css("top",(c.top-n)+"px");h=true}}}}}if(h){d();var l=this;this.timeout=setTimeout(function(){l.test(v,d)},50)}},stop:function(b,a){clearTimeout(this.timeout)}});function CanvasManager(b,a){this.cv=b;this.cc=this.cv.find("#canvas-container");this.oc=a.find("#overview-canvas");this.ov=a.find("#overview-viewport");this.init_drag()}$.extend(CanvasManager.prototype,{init_drag:function(){var b=this;var a=function(f,g){f=Math.min(f,b.cv.width()/2);f=Math.max(f,-b.cc.width()+b.cv.width()/2);g=Math.min(g,b.cv.height()/2);g=Math.max(g,-b.cc.height()+b.cv.height()/2);b.cc.css({left:f,top:g});b.update_viewport_overlay()};this.cc.each(function(){this.scroll_panel=new ScrollPanel(this)});var d,c;this.cv.bind("dragstart",function(){var g=$(this).offset();var f=b.cc.position();c=f.top-g.top;d=f.left-g.left}).bind("drag",function(f,g){a(g.offsetX+d,g.offsetY+c)}).bind("dragend",function(){workflow.fit_canvas_to_nodes();b.draw_overview()});this.ov.bind("drag",function(k,l){var h=b.cc.width(),n=b.cc.height(),m=b.oc.width(),j=b.oc.height(),f=$(this).offsetParent().offset(),i=l.offsetX-f.left,g=l.offsetY-f.top;a(-(i/m*h),-(g/j*n))}).bind("dragend",function(){workflow.fit_canvas_to_nodes();b.draw_overview()});$("#overview-border").bind("drag",function(g,i){var j=$(this).offsetParent();var h=j.offset();var f=Math.max(j.width()-(i.offsetX-h.left),j.height()-(i.offsetY-h.top));$(this).css({width:f,height:f});b.draw_overview()});$("#overview-border div").bind("drag",function(){})},update_viewport_overlay:function(){var b=this.cc,f=this.cv,a=this.oc,c=this.ov,d=b.width(),j=b.height(),i=a.width(),g=a.height(),h=b.position();c.css({left:-(h.left/d*i),top:-(h.top/j*g),width:(f.width()/d*i)-2,height:(f.height()/j*g)-2})},draw_overview:function(){var j=$("#overview-canvas"),m=j.parent().parent().width(),i=j.get(0).getContext("2d"),d=$("#canvas-container").width(),l=$("#canvas-container").height();var g,a,k,f;var h=this.cv.width();var b=this.cv.height();if(d<h&&l<b){k=d/h*m;f=(m-k)/2;g=l/b*m;a=(m-g)/2}else{if(d<l){a=0;g=m;k=Math.ceil(g*d/l);f=(m-k)/2}else{k=m;f=0;g=Math.ceil(k*l/d);a=(m-g)/2}}j.parent().css({left:f,top:a,width:k,height:g});j.attr("width",k);j.attr("height",g);$.each(workflow.nodes,function(t,q){i.fillStyle="#D2C099";i.strokeStyle="#D8B365";i.lineWidth=1;var s=$(q.element),n=s.position(),c=n.left/d*k,r=n.top/l*g,o=s.width()/d*k,p=s.height()/l*g;if(q.tool_errors){i.fillStyle="#FFCCCC";i.strokeStyle="#AA6666"}else{if(q.workflow_outputs!=undefined&&q.workflow_outputs.length>0){i.fillStyle="#E8A92D";i.strokeStyle="#E8A92D"}}i.fillRect(c,r,o,p);i.strokeRect(c,r,o,p)});this.update_viewport_overlay()}});
\ No newline at end of file
+function CollectionTypeDescription(a){this.collectionType=a;this.isCollection=true;this.rank=a.split(":").length}$.extend(CollectionTypeDescription.prototype,{append:function(a){if(a===NULL_COLLECTION_TYPE_DESCRIPTION){return this}if(a===ANY_COLLECTION_TYPE_DESCRIPTION){return otherCollectionType}return new CollectionTypeDescription(this.collectionType+":"+a.collectionType)},canMatch:function(a){if(a===NULL_COLLECTION_TYPE_DESCRIPTION){return false}if(a===ANY_COLLECTION_TYPE_DESCRIPTION){return true}return a.collectionType==this.collectionType},canMapOver:function(b){if(b===NULL_COLLECTION_TYPE_DESCRIPTION){return false}if(b===ANY_COLLECTION_TYPE_DESCRIPTION){return false}if(this.rank<=b.rank){return false}var a=b.collectionType;return this._endsWith(this.collectionType,a)},effectiveMapOver:function(a){var c=a.collectionType;var b=this.collectionType.substring(0,this.collectionType.length-c.length-1);return new CollectionTypeDescription(b)},equal:function(a){return a.collectionType==this.collectionType},toString:function(){return"CollectionType["+this.collectionType+"]"},_endsWith:function(b,a){return b.indexOf(a,b.length-a.length)!==-1}});NULL_COLLECTION_TYPE_DESCRIPTION={isCollection:false,canMatch:function(a){return false},canMapOver:function(a){return false},toString:function(){return"NullCollectionType[]"},append:function(a){return a},equal:function(a){return a===this}};ANY_COLLECTION_TYPE_DESCRIPTION={isCollection:true,canMatch:function(a){return NULL_COLLECTION_TYPE_DESCRIPTION!==a},canMapOver:function(a){return false},toString:function(){return"AnyCollectionType[]"},append:function(a){throw"Cannot append to ANY_COLLECTION_TYPE_DESCRIPTION"},equal:function(a){return a===this}};var TerminalMapping=Backbone.Model.extend({initialize:function(a){this.mapOver=a.mapOver||NULL_COLLECTION_TYPE_DESCRIPTION;this.terminal=a.terminal;this.terminal.terminalMapping=this},disableMapOver:function(){this.setMapOver(NULL_COLLECTION_TYPE_DESCRIPTION)},setMapOver:function(a){this.mapOver=a;this.trigger("change")}});var TerminalMappingView=Backbone.View.extend({tagName:"div",className:"fa-icon-button fa fa-folder-o",initialize:function(b){var a="Run tool in parallel over collection";this.$el.tooltip({delay:500,title:a});this.model.bind("change",_.bind(this.render,this))},render:function(){if(this.model.mapOver.isCollection){this.$el.show()}else{this.$el.hide()}},});var InputTerminalMappingView=TerminalMappingView.extend({events:{click:"onClick",mouseenter:"onMouseEnter",mouseleave:"onMouseLeave",},onMouseEnter:function(b){var a=this.model;if(!a.terminal.connected()&&a.mapOver.isCollection){this.$el.color("red")}},onMouseLeave:function(a){this.$el.color("blue")},onClick:function(b){var a=this.model;if(!a.terminal.connected()&&a.mapOver.isCollection){a.terminal.resetMapping()}},});var InputTerminalMapping=TerminalMapping;var InputCollectionTerminalMapping=TerminalMapping;var OutputTerminalMapping=TerminalMapping;var OutputTerminalMappingView=TerminalMappingView;var InputCollectionTerminalMappingView=InputTerminalMappingView;var OutputCollectionTerminalMapping=TerminalMapping;var OutputCollectionTerminalMappingView=TerminalMappingView;var Terminal=Backbone.Model.extend({initialize:function(a){this.element=a.element;this.connectors=[]},connect:function(a){this.connectors.push(a);if(this.node){this.node.markChanged()}},disconnect:function(a){this.connectors.splice($.inArray(a,this.connectors),1);if(this.node){this.node.markChanged();this.resetMappingIfNeeded()}},redraw:function(){$.each(this.connectors,function(a,b){b.redraw()})},destroy:function(){$.each(this.connectors.slice(),function(a,b){b.destroy()})},destroyInvalidConnections:function(){_.each(this.connectors,function(a){a.destroyIfInvalid()})},setMapOver:function(a){if(this.multiple){return}if(!this.mapOver().equal(a)){this.terminalMapping.setMapOver(a);_.each(this.node.output_terminals,function(b){b.setMapOver(a)})}},mapOver:function(){if(!this.terminalMapping){return NULL_COLLECTION_TYPE_DESCRIPTION}else{return this.terminalMapping.mapOver}},isMappedOver:function(){return this.terminalMapping&&this.terminalMapping.mapOver.isCollection},resetMapping:function(){this.terminalMapping.disableMapOver()},resetMappingIfNeeded:function(){},});var OutputTerminal=Terminal.extend({initialize:function(a){Terminal.prototype.initialize.call(this,a);this.datatypes=a.datatypes},resetMappingIfNeeded:function(){if(!this.node.hasMappedOverInputTerminals()){this.resetMapping()}},resetMapping:function(){this.terminalMapping.disableMapOver();_.each(this.connectors,function(a){var b=a.handle2;if(b){b.resetMappingIfNeeded();a.destroyIfInvalid()}})}});var BaseInputTerminal=Terminal.extend({initialize:function(a){Terminal.prototype.initialize.call(this,a);this.update(a.input)},canAccept:function(a){if(this._inputFilled()){return false}else{return this.attachable(a)}},resetMappingIfNeeded:function(){var b=this.mapOver();if(!b.isCollection){return}var a=this.node.hasConnectedMappedInputTerminals()||(!this.node.hasConnectedOutputTerminals());if(a){this.resetMapping()}},resetMapping:function(){this.terminalMapping.disableMapOver();if(!this.node.hasMappedOverInputTerminals()){_.each(this.node.output_terminals,function(a){a.resetMapping()})}},connected:function(){return this.connectors.length!==0},_inputFilled:function(){var a;if(!this.connected()){a=false}else{if(this.multiple){if(!this.connected()){a=false}else{var b=this.connectors[0].handle1;if(b===null){a=false}else{if(b.isDataCollectionInput||b.isMappedOver()||b.datatypes.indexOf("input_collection")>0){a=true}else{a=false}}}}else{a=true}}return a},_mappingConstraints:function(){if(!this.node){return[]}var b=this.mapOver();if(b.isCollection){return[b]}var a=[];if(!this.node.hasConnectedOutputTerminals()){_.each(this.node.connectedMappedInputTerminals(),function(c){a.push(c.mapOver())})}else{a.push(_.first(_.values(this.node.output_terminals)).mapOver())}return a},_producesAcceptableDatatype:function(a){for(var c in this.datatypes){var f=new Array();f=f.concat(a.datatypes);if(a.node.post_job_actions){for(var d in a.node.post_job_actions){var g=a.node.post_job_actions[d];if(g.action_type=="ChangeDatatypeAction"&&(g.output_name==""||g.output_name==a.name)&&g.action_arguments){f.push(g.action_arguments.newtype)}}}for(var b in f){var h=f[b];if(h=="input"||h=="input_collection"||issubtype(f[b],this.datatypes[c])){return true}}}return false},_otherCollectionType:function(a){var c=NULL_COLLECTION_TYPE_DESCRIPTION;if(a.isDataCollectionInput){c=a.collectionType}else{var b=a.mapOver();if(b.isCollection){c=b}}return c},});var InputTerminal=BaseInputTerminal.extend({update:function(a){this.datatypes=a.extensions;this.multiple=a.multiple;this.collection=false},connect:function(a){BaseInputTerminal.prototype.connect.call(this,a);var b=a.handle1;if(!b){return}var c=this._otherCollectionType(b);if(c.isCollection){this.setMapOver(c)}},attachable:function(b){var d=this._otherCollectionType(b);var a=this.mapOver();if(d.isCollection){if(a.isCollection&&a.canMatch(d)){return this._producesAcceptableDatatype(b)}else{var c=this._mappingConstraints();if(c.every(_.bind(d.canMatch,d))){return this._producesAcceptableDatatype(b)}else{return false}}}else{if(a.isCollection){return false}}return this._producesAcceptableDatatype(b)}});var InputCollectionTerminal=BaseInputTerminal.extend({update:function(a){this.multiple=false;this.collection=true;this.datatypes=a.extensions;if(a.collection_type){this.collectionType=new CollectionTypeDescription(a.collection_type)}else{this.collectionType=ANY_COLLECTION_TYPE_DESCRIPTION}},connect:function(b){BaseInputTerminal.prototype.connect.call(this,b);var a=b.handle1;if(!a){return}var c=this._effectiveMapOver(a);this.setMapOver(c)},_effectiveMapOver:function(a){var b=this.collectionType;var c=this._otherCollectionType(a);if(!b.canMatch(c)){return c.effectiveMapOver(b)}else{return NULL_COLLECTION_TYPE_DESCRIPTION}},_effectiveCollectionType:function(){var b=this.collectionType;var a=this.mapOver();return a.append(b)},attachable:function(b){var g=this._otherCollectionType(b);if(g.isCollection){var f=this._effectiveCollectionType();var a=this.mapOver();if(f.canMatch(g)){return this._producesAcceptableDatatype(b)}else{if(a.isCollection){return false}else{if(g.canMapOver(this.collectionType)){var d=this._effectiveMapOver(b);if(!d.isCollection){return false}var c=this._mappingConstraints();if(c.every(d.canMatch)){return this._producesAcceptableDatatype(b)}}}}}return false}});var OutputCollectionTerminal=Terminal.extend({initialize:function(a){Terminal.prototype.initialize.call(this,a);this.datatypes=a.datatypes;this.collectionType=new CollectionTypeDescription(a.collection_type);this.isDataCollectionInput=true},update:function(a){var b=new CollectionTypeDescription(a.collection_type);if(b.collectionType!=this.collectionType.collectionType){_.each(this.connectors,function(c){c.destroy()})}this.collectionType=b}});function Connector(b,a){this.canvas=null;this.dragging=false;this.inner_color="#FFFFFF";this.outer_color="#D8B365";if(b&&a){this.connect(b,a)}}$.extend(Connector.prototype,{connect:function(b,a){this.handle1=b;if(this.handle1){this.handle1.connect(this)}this.handle2=a;if(this.handle2){this.handle2.connect(this)}},destroy:function(){if(this.handle1){this.handle1.disconnect(this)}if(this.handle2){this.handle2.disconnect(this)}$(this.canvas).remove()},destroyIfInvalid:function(){if(this.handle1&&this.handle2&&!this.handle2.attachable(this.handle1)){this.destroy()}},redraw:function(){var f=$("#canvas-container");if(!this.canvas){this.canvas=document.createElement("canvas");if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(this.canvas)}f.append($(this.canvas));if(this.dragging){this.canvas.style.zIndex="300"}}var v=function(c){return $(c).offset().left-f.offset().left};var p=function(c){return $(c).offset().top-f.offset().top};if(!this.handle1||!this.handle2){return}var o=v(this.handle1.element)+5;var n=p(this.handle1.element)+5;var x=v(this.handle2.element)+5;var u=p(this.handle2.element)+5;var k=100;var r=Math.min(o,x);var a=Math.max(o,x);var q=Math.min(n,u);var B=Math.max(n,u);var d=Math.min(Math.max(Math.abs(B-q)/2,100),300);var w=r-k;var A=q-k;var y=a-r+2*k;var s=B-q+2*k;this.canvas.style.left=w+"px";this.canvas.style.top=A+"px";this.canvas.setAttribute("width",y);this.canvas.setAttribute("height",s);o-=w;n-=A;x-=w;u-=A;var z=this.canvas.getContext("2d"),h=null,l=null;var g=1;if(this.handle1&&this.handle1.isMappedOver()){var h=[-6,-3,0,3,6];g=5}else{var h=[0]}if(this.handle2&&this.handle2.isMappedOver()){var l=[-6,-3,0,3,6];g=5}else{var l=[0]}var b=this;for(var t=0;t<g;t++){var m=5,j=7;if(h.length>1||l.length>1){m=1;j=3}b.draw_outlined_curve(o,n,x,u,d,m,j,h[t%h.length],l[t%l.length])}},draw_outlined_curve:function(j,i,l,k,a,b,f,g,d){var g=g||0;var d=d||0;var h=this.canvas.getContext("2d");h.lineCap="round";h.strokeStyle=this.outer_color;h.lineWidth=f;h.beginPath();h.moveTo(j,i+g);h.bezierCurveTo(j+a,i+g,l-a,k+d,l,k+d);h.stroke();h.strokeStyle=this.inner_color;h.lineWidth=b;h.beginPath();h.moveTo(j,i+g);h.bezierCurveTo(j+a,i+g,l-a,k+d,l,k+d);h.stroke()}});var Node=Backbone.Model.extend({initialize:function(a){this.element=a.element;this.input_terminals={};this.output_terminals={};this.tool_errors={}},connectedOutputTerminals:function(){var a=[];$.each(this.output_terminals,function(b,c){if(c.connectors.length>0){a.push(c)}});return a},hasConnectedOutputTerminals:function(){var a=this.output_terminals;for(var b in a){if(a[b].connectors.length>0){return true}}return false},connectedMappedInputTerminals:function(){return this._connectedMappedTerminals(this.input_terminals)},hasConnectedMappedInputTerminals:function(){var c=this.input_terminals;for(var b in c){var a=c[b];if(a.connectors.length>0&&a.isMappedOver()){return true}}return false},_connectedMappedTerminals:function(b){var a=[];$.each(b,function(c,d){var f=d.mapOver();if(f.isCollection){if(d.connectors.length>0){a.push(d)}}});return a},hasMappedOverInputTerminals:function(){var a=false;_.each(this.input_terminals,function(b){var c=b.mapOver();if(c.isCollection){a=true}});return a},forceDisconnectOutputs:function(){_.each(this.output_terminals,function(a){a.disconnectAll()})},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()});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){if(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.workflow_outputs=b.workflow_outputs?b.workflow_outputs:[];var a=this;var c=new NodeView({el:this.element[0],node:a,});a.nodeView=c;$.each(b.data_inputs,function(f,d){c.addDataInput(d)});if((b.data_inputs.length>0)&&(b.data_outputs.length>0)){c.addRule()}$.each(b.data_outputs,function(f,d){c.addDataOutput(d)});c.render();workflow.node_changed(this)},update_field_data:function(d){var c=this;nodeView=c.nodeView;this.tool_state=d.tool_state;this.form_html=d.form_html;this.tool_errors=d.tool_errors;this.annotation=d.annotation;if("post_job_actions" in d){var f=$.parseJSON(d.post_job_actions);this.post_job_actions=f?f:{}}c.nodeView.renderToolErrors();var g=nodeView.$("div.inputs");var a=nodeView.newInputsDiv();var b={};_.each(d.data_inputs,function(h){var i=c.nodeView.addDataInput(h,a);b[h.name]=i});_.each(_.difference(_.values(nodeView.terminalViews),_.values(b)),function(h){h.el.terminal.destroy()});nodeView.terminalViews=b;if(d.data_outputs.length==1&&"collection_type" in d.data_outputs[0]){nodeView.updateDataOutput(d.data_outputs[0])}g.replaceWith(a);this.markChanged();this.redraw()},error:function(d){var a=$(this.element).find(".toolFormBody");a.find("div").remove();var c="<div style='color: red; text-style: italic;'>"+d+"</div>";this.form_html=c;a.html(c);workflow.node_changed(this)},markChanged:function(){workflow.node_changed(this)}});function Workflow(a){this.canvas_container=a;this.id_counter=0;this.nodes={};this.name=null;this.has_changes=false;this.active_form_has_changes=false}$.extend(Workflow.prototype,{add_node:function(a){a.id=this.id_counter;a.element.attr("id","wf-node-step-"+a.id);this.id_counter++;this.nodes[a.id]=a;this.has_changes=true;a.workflow=this},remove_node:function(a){if(this.active_node==a){this.clear_active_node()}delete this.nodes[a.id];this.has_changes=true},remove_all:function(){wf=this;$.each(this.nodes,function(b,a){a.destroy();wf.remove_node(a)})},rectify_workflow_outputs:function(){var b=false;var a=false;$.each(this.nodes,function(c,d){if(d.workflow_outputs&&d.workflow_outputs.length>0){b=true}$.each(d.post_job_actions,function(g,f){if(f.action_type==="HideDatasetAction"){a=true}})});if(b!==false||a!==false){$.each(this.nodes,function(c,g){if(g.type==="tool"){var f=false;if(g.post_job_actions==null){g.post_job_actions={};f=true}var d=[];$.each(g.post_job_actions,function(i,h){if(h.action_type=="HideDatasetAction"){d.push(i)}});if(d.length>0){$.each(d,function(h,j){f=true;delete g.post_job_actions[j]})}if(b){$.each(g.output_terminals,function(i,j){var h=true;$.each(g.workflow_outputs,function(l,m){if(j.name===m){h=false}});if(h===true){f=true;var k={action_type:"HideDatasetAction",output_name:j.name,action_arguments:{}};g.post_job_actions["HideDatasetAction"+j.name]=null;g.post_job_actions["HideDatasetAction"+j.name]=k}})}if(workflow.active_node==g&&f===true){workflow.reload_active_node()}}})}},to_simple:function(){var a={};$.each(this.nodes,function(c,f){var g={};$.each(f.input_terminals,function(i,j){g[j.name]=null;var h=[];$.each(j.connectors,function(k,l){h[k]={id:l.handle1.node.id,output_name:l.handle1.name};g[j.name]=h})});var b={};if(f.post_job_actions){$.each(f.post_job_actions,function(j,h){var k={action_type:h.action_type,output_name:h.output_name,action_arguments:h.action_arguments};b[h.action_type+h.output_name]=null;b[h.action_type+h.output_name]=k})}if(!f.workflow_outputs){f.workflow_outputs=[]}var d={id:f.id,type:f.type,tool_id:f.tool_id,tool_state:f.tool_state,tool_errors:f.tool_errors,input_connections:g,position:$(f.element).position(),annotation:f.annotation,post_job_actions:f.post_job_actions,workflow_outputs:f.workflow_outputs};a[f.id]=d});return{steps:a}},from_simple:function(b){wf=this;var c=0;wf.name=b.name;var a=false;$.each(b.steps,function(g,f){var d=prebuild_node(f.type,f.name,f.tool_id);d.init_field_data(f);if(f.position){d.element.css({top:f.position.top,left:f.position.left})}d.id=f.id;wf.nodes[d.id]=d;c=Math.max(c,parseInt(g));if(!a&&d.type==="tool"){if(d.workflow_outputs.length>0){a=true}else{$.each(d.post_job_actions,function(i,h){if(h.action_type==="HideDatasetAction"){a=true}})}}});wf.id_counter=c+1;$.each(b.steps,function(g,f){var d=wf.nodes[g];$.each(f.input_connections,function(i,h){if(h){if(!$.isArray(h)){h=[h]}$.each(h,function(k,j){var m=wf.nodes[j.id];var n=new Connector();n.connect(m.output_terminals[j.output_name],d.input_terminals[i]);n.redraw()})}});if(a&&d.type==="tool"){$.each(d.output_terminals,function(h,i){if(d.post_job_actions["HideDatasetAction"+i.name]===undefined){d.workflow_outputs.push(i.name);callout=$(d.element).find(".callout."+i.name);callout.find("img").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small.png");workflow.has_changes=true}})}})},check_changes_in_active_form:function(){if(this.active_form_has_changes){this.has_changes=true;$("#right-content").find("form").submit();this.active_form_has_changes=false}},reload_active_node:function(){if(this.active_node){var a=this.active_node;this.clear_active_node();this.activate_node(a)}},clear_active_node:function(){if(this.active_node){this.active_node.make_inactive();this.active_node=null}parent.show_form_for_tool("<div>No node selected</div>")},activate_node:function(a){if(this.active_node!=a){this.check_changes_in_active_form();this.clear_active_node();parent.show_form_for_tool(a.form_html+a.tooltip,a);a.make_active();this.active_node=a}},node_changed:function(a){this.has_changes=true;if(this.active_node==a){this.check_changes_in_active_form();parent.show_form_for_tool(a.form_html+a.tooltip,a)}},layout:function(){this.check_changes_in_active_form();this.has_changes=true;var i={};var b={};$.each(this.nodes,function(l,k){if(i[l]===undefined){i[l]=0}if(b[l]===undefined){b[l]=[]}});$.each(this.nodes,function(l,k){$.each(k.input_terminals,function(m,n){$.each(n.connectors,function(p,q){var o=q.handle1.node;i[k.id]+=1;b[o.id].push(k.id)})})});node_ids_by_level=[];while(true){level_parents=[];for(var a in i){if(i[a]==0){level_parents.push(a)}}if(level_parents.length==0){break}node_ids_by_level.push(level_parents);for(var f in level_parents){var j=level_parents[f];delete i[j];for(var g in b[j]){i[b[j][g]]-=1}}}if(i.length){return}var d=this.nodes;var h=80;v_pad=30;var c=h;$.each(node_ids_by_level,function(k,l){l.sort(function(p,o){return $(d[p].element).position().top-$(d[o].element).position().top});var m=0;var n=v_pad;$.each(l,function(o,r){var q=d[r];var p=$(q.element);$(p).css({top:n,left:c});m=Math.max(m,$(p).width());n+=$(p).height()+v_pad});c+=m+h});$.each(d,function(k,l){l.redraw()})},bounds_for_all_nodes:function(){var d=Infinity,b=-Infinity,c=Infinity,a=-Infinity,f;$.each(this.nodes,function(h,g){e=$(g.element);f=e.position();d=Math.min(d,f.left);b=Math.max(b,f.left+e.width());c=Math.min(c,f.top);a=Math.max(a,f.top+e.width())});return{xmin:d,xmax:b,ymin:c,ymax:a}},fit_canvas_to_nodes:function(){var a=this.bounds_for_all_nodes();var f=this.canvas_container.position();var i=this.canvas_container.parent();var d=fix_delta(a.xmin,100);var h=fix_delta(a.ymin,100);d=Math.max(d,f.left);h=Math.max(h,f.top);var c=f.left-d;var g=f.top-h;var b=round_up(a.xmax+100,100)+d;var j=round_up(a.ymax+100,100)+h;b=Math.max(b,-c+i.width());j=Math.max(j,-g+i.height());this.canvas_container.css({left:c,top:g,width:b,height:j});this.canvas_container.children().each(function(){var k=$(this).position();$(this).css("left",k.left+d);$(this).css("top",k.top+h)})}});function fix_delta(a,b){if(a<b||a>3*b){new_pos=(Math.ceil(((a%b))/b)+1)*b;return(-(a-new_pos))}return 0}function round_up(a,b){return Math.ceil(a/b)*b}function prebuild_node(l,j,r){var i=$("<div class='toolForm toolFormInCanvas'></div>");var g=new Node({element:i});g.type=l;if(l=="tool"){g.tool_id=r}var n=$("<div class='toolFormTitle unselectable'>"+j+"</div>");i.append(n);i.css("left",$(window).scrollLeft()+20);i.css("top",$(window).scrollTop()+20);var m=$("<div class='toolFormBody'></div>");var h="<div><img height='16' align='middle' src='"+galaxy_config.root+"static/images/loading_small_white_bg.gif'/> loading tool info...</div>";m.append(h);g.form_html=h;i.append(m);var k=$("<div class='buttons' style='float: right;'></div>");k.append($("<div>").addClass("fa-icon-button fa fa-times").click(function(b){g.destroy()}));i.appendTo("#canvas-container");var d=$("#canvas-container").position();var c=$("#canvas-container").parent();var a=i.width();var q=i.height();i.css({left:(-d.left)+(c.width()/2)-(a/2),top:(-d.top)+(c.height()/2)-(q/2)});k.prependTo(n);a+=(k.width()+10);i.css("width",a);$(i).bind("dragstart",function(){workflow.activate_node(g)}).bind("dragend",function(){workflow.node_changed(this);workflow.fit_canvas_to_nodes();canvas_manager.draw_overview()}).bind("dragclickonly",function(){workflow.activate_node(g)}).bind("drag",function(o,p){var f=$(this).offsetParent().offset(),b=p.offsetX-f.left,s=p.offsetY-f.top;$(this).css({left:b,top:s});$(this).find(".terminal").each(function(){this.terminal.redraw()})});return g}function add_node(b,d,a){var c=prebuild_node(b,d,a);workflow.add_node(c);workflow.fit_canvas_to_nodes();canvas_manager.draw_overview();workflow.activate_node(c);return c}var ext_to_type=null;var type_to_type=null;function issubtype(b,a){b=ext_to_type[b];a=ext_to_type[a];return(type_to_type[b])&&(a in type_to_type[b])}function populate_datatype_info(a){ext_to_type=a.ext_to_class_name;type_to_type=a.class_to_classes}var NodeView=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(){if(this.node.tool_errors){this.$el.addClass("tool-node-error")}else{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(i,d){var j=true;if(!d){d=this.$(".inputs");j=false}var f=this.terminalViews[i.name];if(!f){var h=(i.input_type=="dataset_collection")?InputCollectionTerminalView:InputTerminalView;f=new h({node:this.node,input:i})}else{var g=f.el.terminal;g.update(i);g.destroyInvalidConnections()}this.terminalViews[i.name]=f;var c=f.el;var b=new DataInputView({terminalElement:c,input:i,nodeView:this,skipResize:j});var a=b.$el;d.append(a.prepend(f.terminalElements()));return f},addDataOutput:function(a){var d=(a.collection_type)?OutputCollectionTerminalView:OutputTerminalView;var c=new d({node:this.node,output:a});var b=new DataOutputView({output:a,terminalElement:c.el,nodeView:this,});this.tool_body.append(b.$el.append(c.terminalElements()))},updateDataOutput:function(b){var a=this.node.output_terminals[b.name];a.update(b)}});var DataInputView=Backbone.View.extend({className:"form-row dataRow input-data-row",initialize:function(a){this.input=a.input;this.nodeView=a.nodeView;this.terminalElement=a.terminalElement;this.$el.attr("name",this.input.name).html(this.input.label);if(!a.skipResize){this.$el.css({position:"absolute",left:-1000,top:-1000,display:"none"});$("body").append(this.el);this.nodeView.updateMaxWidth(this.$el.outerWidth());this.$el.css({position:"",left:"",top:"",display:""});this.$el.remove()}},});var OutputCalloutView=Backbone.View.extend({tagName:"div",initialize:function(b){this.label=b.label;this.node=b.node;this.output=b.output;var a=this;this.$el.attr("class","callout "+this.label).css({display:"none"}).append($("<div class='buttons'></div>").append($("<img/>").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small-outline.png").click(function(){if($.inArray(a.output.name,a.node.workflow_outputs)!=-1){a.node.workflow_outputs.splice($.inArray(a.output.name,a.node.workflow_outputs),1);a.$("img").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small-outline.png")}else{a.node.workflow_outputs.push(a.output.name);a.$("img").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small.png")}workflow.has_changes=true;canvas_manager.draw_overview()}))).tooltip({delay:500,title:"Mark dataset as a workflow output. All unmarked datasets will be hidden."});this.$el.css({top:"50%",margin:"-8px 0px 0px 0px",right:8});this.$el.show();this.resetImage()},resetImage:function(){if($.inArray(this.output.name,this.node.workflow_outputs)===-1){this.$("img").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small-outline.png")}else{this.$("img").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small.png")}},hoverImage:function(){this.$("img").attr("src",galaxy_config.root+"static/images/fugue/asterisk-small-yellow.png")}});var DataOutputView=Backbone.View.extend({className:"form-row dataRow",initialize:function(c){this.output=c.output;this.terminalElement=c.terminalElement;this.nodeView=c.nodeView;var a=this.output;var b=a.name;var f=this.nodeView.node;var d=a.extensions.indexOf("input")>=0||a.extensions.indexOf("input_collection")>=0;if(!d){b=b+" ("+a.extensions.join(", ")+")"}this.$el.html(b);if(f.type=="tool"){var g=new OutputCalloutView({label:b,output:a,node:f,});this.$el.append(g.el);this.$el.hover(function(){g.hoverImage()},function(){g.resetImage()})}this.$el.css({position:"absolute",left:-1000,top:-1000,display:"none"});$("body").append(this.el);this.nodeView.updateMaxWidth(this.$el.outerWidth()+17);this.$el.css({position:"",left:"",top:"",display:""}).detach()}});var TerminalView=Backbone.View.extend({setupMappingView:function(b){var c=new this.terminalMappingClass({terminal:b});var a=new this.terminalMappingViewClass({model:c});a.render();b.terminalMappingView=a;this.terminalMappingView=a},terminalElements:function(){if(this.terminalMappingView){return[this.terminalMappingView.el,this.el]}else{return[this.el]}}});var BaseInputTerminalView=TerminalView.extend({className:"terminal input-terminal",initialize:function(c){var f=c.node;var a=c.input;var b=a.name;var d=this.terminalForInput(a);if(!d.multiple){this.setupMappingView(d)}this.el.terminal=d;d.node=f;d.name=b;f.input_terminals[b]=d},events:{dropinit:"onDropInit",dropstart:"onDropStart",dropend:"onDropEnd",drop:"onDrop",hover:"onHover",},onDropInit:function(b,c){var a=this.el.terminal;return $(c.drag).hasClass("output-terminal")&&a.canAccept(c.drag.terminal)},onDropStart:function(a,b){if(b.proxy.terminal){b.proxy.terminal.connectors[0].inner_color="#BBFFBB"}},onDropEnd:function(a,b){if(b.proxy.terminal){b.proxy.terminal.connectors[0].inner_color="#FFFFFF"}},onDrop:function(b,c){var a=this.el.terminal;new Connector(c.drag.terminal,a).redraw()},onHover:function(){var c=this.el;var b=c.terminal;if(b.connectors.length>0){var a=$("<div class='callout'></div>").css({display:"none"}).appendTo("body").append($("<div class='button'></div>").append($("<div/>").addClass("fa-icon-button fa fa-times").click(function(){$.each(b.connectors,function(f,d){if(d){d.destroy()}});a.remove()}))).bind("mouseleave",function(){$(this).remove()});a.css({top:$(c).offset().top-2,left:$(c).offset().left-a.width(),"padding-right":$(c).width()}).show()}},});var InputTerminalView=BaseInputTerminalView.extend({terminalMappingClass:InputTerminalMapping,terminalMappingViewClass:InputTerminalMappingView,terminalForInput:function(a){return new InputTerminal({element:this.el,input:a})},});var InputCollectionTerminalView=BaseInputTerminalView.extend({terminalMappingClass:InputCollectionTerminalMapping,terminalMappingViewClass:InputCollectionTerminalMappingView,terminalForInput:function(a){return new InputCollectionTerminal({element:this.el,input:a})},});var BaseOutputTerminalView=TerminalView.extend({className:"terminal output-terminal",initialize:function(c){var f=c.node;var a=c.output;var b=a.name;var d=this.terminalForOutput(a);this.setupMappingView(d);this.el.terminal=d;d.node=f;d.name=b;f.output_terminals[b]=d},events:{drag:"onDrag",dragstart:"onDragStart",dragend:"onDragEnd",},onDrag:function(b,c){var a=function(){var f=$(c.proxy).offsetParent().offset(),d=c.offsetX-f.left,g=c.offsetY-f.top;$(c.proxy).css({left:d,top:g});c.proxy.terminal.redraw();canvas_manager.update_viewport_overlay()};a();$("#canvas-container").get(0).scroll_panel.test(b,a)},onDragStart:function(b,f){$(f.available).addClass("input-terminal-active");workflow.check_changes_in_active_form();var a=$('<div class="drag-terminal" style="position: absolute;"></div>').appendTo("#canvas-container").get(0);a.terminal=new OutputTerminal({element:a});var g=new Connector();g.dragging=true;g.connect(this.el.terminal,a.terminal);return a},onDragEnd:function(a,b){b.proxy.terminal.connectors[0].destroy();$(b.proxy).remove();$(b.available).removeClass("input-terminal-active");$("#canvas-container").get(0).scroll_panel.stop()}});var OutputTerminalView=BaseOutputTerminalView.extend({terminalMappingClass:OutputTerminalMapping,terminalMappingViewClass:OutputTerminalMappingView,terminalForOutput:function(a){var c=a.extensions;var b=new OutputTerminal({element:this.el,datatypes:c});return b},});var OutputCollectionTerminalView=BaseOutputTerminalView.extend({terminalMappingClass:OutputCollectionTerminalMapping,terminalMappingViewClass:OutputCollectionTerminalMappingView,terminalForOutput:function(a){var c=a.collection_type;var b=new OutputCollectionTerminal({element:this.el,collection_type:c,datatypes:a.extensions});return b},});function ScrollPanel(a){this.panel=a}$.extend(ScrollPanel.prototype,{test:function(v,d){clearTimeout(this.timeout);var k=v.pageX,j=v.pageY,l=$(this.panel),c=l.position(),b=l.width(),i=l.height(),w=l.parent(),s=w.width(),a=w.height(),r=w.offset(),p=r.left,m=r.top,A=p+w.width(),u=m+w.height(),B=-(b-(s/2)),z=-(i-(a/2)),g=(s/2),f=(a/2),h=false,q=5,o=23;if(k-q<p){if(c.left<g){var n=Math.min(o,g-c.left);l.css("left",c.left+n);h=true}}else{if(k+q>A){if(c.left>B){var n=Math.min(o,c.left-B);l.css("left",c.left-n);h=true}}else{if(j-q<m){if(c.top<f){var n=Math.min(o,f-c.top);l.css("top",c.top+n);h=true}}else{if(j+q>u){if(c.top>z){var n=Math.min(o,c.top-B);l.css("top",(c.top-n)+"px");h=true}}}}}if(h){d();var l=this;this.timeout=setTimeout(function(){l.test(v,d)},50)}},stop:function(b,a){clearTimeout(this.timeout)}});function CanvasManager(b,a){this.cv=b;this.cc=this.cv.find("#canvas-container");this.oc=a.find("#overview-canvas");this.ov=a.find("#overview-viewport");this.init_drag()}$.extend(CanvasManager.prototype,{init_drag:function(){var b=this;var a=function(f,g){f=Math.min(f,b.cv.width()/2);f=Math.max(f,-b.cc.width()+b.cv.width()/2);g=Math.min(g,b.cv.height()/2);g=Math.max(g,-b.cc.height()+b.cv.height()/2);b.cc.css({left:f,top:g});b.update_viewport_overlay()};this.cc.each(function(){this.scroll_panel=new ScrollPanel(this)});var d,c;this.cv.bind("dragstart",function(){var g=$(this).offset();var f=b.cc.position();c=f.top-g.top;d=f.left-g.left}).bind("drag",function(f,g){a(g.offsetX+d,g.offsetY+c)}).bind("dragend",function(){workflow.fit_canvas_to_nodes();b.draw_overview()});this.ov.bind("drag",function(k,l){var h=b.cc.width(),n=b.cc.height(),m=b.oc.width(),j=b.oc.height(),f=$(this).offsetParent().offset(),i=l.offsetX-f.left,g=l.offsetY-f.top;a(-(i/m*h),-(g/j*n))}).bind("dragend",function(){workflow.fit_canvas_to_nodes();b.draw_overview()});$("#overview-border").bind("drag",function(g,i){var j=$(this).offsetParent();var h=j.offset();var f=Math.max(j.width()-(i.offsetX-h.left),j.height()-(i.offsetY-h.top));$(this).css({width:f,height:f});b.draw_overview()});$("#overview-border div").bind("drag",function(){})},update_viewport_overlay:function(){var b=this.cc,f=this.cv,a=this.oc,c=this.ov,d=b.width(),j=b.height(),i=a.width(),g=a.height(),h=b.position();c.css({left:-(h.left/d*i),top:-(h.top/j*g),width:(f.width()/d*i)-2,height:(f.height()/j*g)-2})},draw_overview:function(){var j=$("#overview-canvas"),m=j.parent().parent().width(),i=j.get(0).getContext("2d"),d=$("#canvas-container").width(),l=$("#canvas-container").height();var g,a,k,f;var h=this.cv.width();var b=this.cv.height();if(d<h&&l<b){k=d/h*m;f=(m-k)/2;g=l/b*m;a=(m-g)/2}else{if(d<l){a=0;g=m;k=Math.ceil(g*d/l);f=(m-k)/2}else{k=m;f=0;g=Math.ceil(k*l/d);a=(m-g)/2}}j.parent().css({left:f,top:a,width:k,height:g});j.attr("width",k);j.attr("height",g);$.each(workflow.nodes,function(t,q){i.fillStyle="#D2C099";i.strokeStyle="#D8B365";i.lineWidth=1;var s=$(q.element),n=s.position(),c=n.left/d*k,r=n.top/l*g,o=s.width()/d*k,p=s.height()/l*g;if(q.tool_errors){i.fillStyle="#FFCCCC";i.strokeStyle="#AA6666"}else{if(q.workflow_outputs!=undefined&&q.workflow_outputs.length>0){i.fillStyle="#E8A92D";i.strokeStyle="#E8A92D"}}i.fillRect(c,r,o,p);i.strokeRect(c,r,o,p)});this.update_viewport_overlay()}});
\ No newline at end of file
diff -r 40054ac6f07bce479affd6b39772640e9321e57b -r f7baaf52e94c57cc1a3f7297c191a3e19826293d static/scripts/packed/galaxy.workflows.js
--- a/static/scripts/packed/galaxy.workflows.js
+++ b/static/scripts/packed/galaxy.workflows.js
@@ -1,1 +1,1 @@
-$(function(){if(window.lt_ie_7){show_modal("Browser not supported","Sorry, the workflow editor is not supported for IE6 and below.");return}$("#tool-search-query").click(function(){$(this).focus();$(this).select()}).keyup(function(){$(this).css("font-style","normal");if(this.value.length<3){reset_tool_search(false)}else{if(this.value!=this.lastValue){$(this).addClass("search_active");var g=this.value+"*";if(this.timer){clearTimeout(this.timer)}$("#search-spinner").show();this.timer=setTimeout(function(){$.get(tool_search_url,{query:g},function(i){$("#search-no-results").hide();$(".toolSectionWrapper").hide();$(".toolSectionWrapper").find(".toolTitle").hide();if(i.length!=0){var h=$.map(i,function(k,j){return"link-"+k});$(h).each(function(j,k){$("[id='"+k+"']").parent().addClass("search_match");$("[id='"+k+"']").parent().show().parent().parent().show().parent().show()});$(".toolPanelLabel").each(function(){var l=$(this);var k=l.next();var j=true;while(k.length!==0&&k.hasClass("toolTitle")){if(k.is(":visible")){j=false;break}else{k=k.next()}}if(j){l.hide()}})}else{$("#search-no-results").show()}$("#search-spinner").hide()},"json")},200)}}this.lastValue=this.value});canvas_manager=new CanvasManager($("#canvas-viewport"),$("#overview"));reset();$.ajax({url:get_datatypes_url,dataType:"json",cache:false,success:function(g){populate_datatype_info(g);$.ajax({url:load_workflow_url,data:{id:workflow_id,_:"true"},dataType:"json",cache:false,success:function(h){reset();workflow.from_simple(h);workflow.has_changes=false;workflow.fit_canvas_to_nodes();scroll_to_nodes();canvas_manager.draw_overview();upgrade_message="";$.each(h.upgrade_messages,function(j,i){upgrade_message+=("<li>Step "+(parseInt(j,10)+1)+": "+workflow.nodes[j].name+"<ul>");$.each(i,function(k,l){upgrade_message+="<li>"+l+"</li>"});upgrade_message+="</ul></li>"});if(upgrade_message){show_modal("Workflow loaded with changes","Problems were encountered loading this workflow (possibly a result of tool upgrades). Please review the following parameters and then save.<ul>"+upgrade_message+"</ul>",{Continue:hide_modal})}else{hide_modal()}show_workflow_parameters()},beforeSubmit:function(h){show_message("Loading workflow","progress")}})}});$(document).ajaxStart(function(){active_ajax_call=true;$(document).bind("ajaxStop.global",function(){active_ajax_call=false})});$(document).ajaxError(function(i,g){var h=g.responseText||g.statusText||"Could not connect to server";show_modal("Server error",h,{"Ignore error":hide_modal});return false});make_popupmenu($("#workflow-options-button"),{Save:save_current_workflow,Run:function(){window.location=run_workflow_url},"Edit Attributes":c,"Auto Re-layout":f,Close:close_editor});function b(){workflow.clear_active_node();$(".right-content").hide();var j="";for(var h in workflow.nodes){var i=workflow.nodes[h];if(i.type=="tool"){j+="<div class='toolForm' style='margin-bottom:5px;'><div class='toolFormTitle'>Step "+i.id+" - "+i.name+"</div>";for(var k in i.output_terminals){var g=i.output_terminals[k];if($.inArray(g.name,i.workflow_outputs)!=-1){j+="<p>"+g.name+"<input type='checkbox' name='"+i.id+"|"+g.name+"' checked /></p>"}else{j+="<p>"+g.name+"<input type='checkbox' name='"+i.id+"|"+g.name+"' /></p>"}}j+="</div>"}}$("#output-fill-area").html(j);$("#output-fill-area input").bind("click",function(){var n=this.name.split("|")[0];var l=this.name.split("|")[1];if(this.checked){if($.inArray(l,workflow.nodes[n].workflow_outputs)==-1){workflow.nodes[n].workflow_outputs.push(l)}}else{while($.inArray(l,workflow.nodes[n].workflow_outputs)!=-1){var m=$.inArray(l,workflow.nodes[n].workflow_outputs);workflow.nodes[n].workflow_outputs=workflow.nodes[n].workflow_outputs.slice(0,m).concat(workflow.nodes[n].workflow_outputs.slice(m+1))}}workflow.has_changes=true});$("#workflow-output-area").show()}function f(){workflow.layout();workflow.fit_canvas_to_nodes();scroll_to_nodes();canvas_manager.draw_overview()}function c(){workflow.clear_active_node();$(".right-content").hide();$("#edit-attributes").show()}overview_size=$.jStorage.get("overview-size");if(overview_size!==undefined){$("#overview-border").css({width:overview_size,height:overview_size})}if($.jStorage.get("overview-off")){d()}else{e()}$("#overview-border").bind("dragend",function(h,j){var k=$(this).offsetParent();var i=k.offset();var g=Math.max(k.width()-(j.offsetX-i.left),k.height()-(j.offsetY-i.top));$.jStorage.set("overview-size",g+"px")});function e(){$.jStorage.set("overview-off",false);$("#overview-border").css("right","0px");$("#close-viewport").css("background-position","0px 0px")}function d(){$.jStorage.set("overview-off",true);$("#overview-border").css("right","20000px");$("#close-viewport").css("background-position","12px 0px")}$("#close-viewport").click(function(){if($("#overview-border").css("right")==="0px"){d()}else{e()}});window.onbeforeunload=function(){if(workflow&&workflow.has_changes){return"There are unsaved changes to your workflow which will be lost."}};$("div.toolSectionBody").hide();$("div.toolSectionTitle > span").wrap("<a href='#'></a>");var a=null;$("div.toolSectionTitle").each(function(){var g=$(this).next("div.toolSectionBody");$(this).click(function(){if(g.is(":hidden")){if(a){a.slideUp("fast")}a=g;g.slideDown("fast")}else{g.slideUp("fast");a=null}})});async_save_text("workflow-name","workflow-name",rename_async_url,"new_name");$("#workflow-tag").click(function(){$(".tag-area").click();return false});async_save_text("workflow-annotation","workflow-annotation",annotate_async_url,"new_annotation",25,true,4)});function reset(){if(workflow){workflow.remove_all()}workflow=new Workflow($("#canvas-container"))}function scroll_to_nodes(){var a=$("#canvas-viewport");var d=$("#canvas-container");var c,b;if(d.width()<a.width()){b=(a.width()-d.width())/2}else{b=0}if(d.height()<a.height()){c=(a.height()-d.height())/2}else{c=0}d.css({left:b,top:c})}function add_node_for_tool(b,a){node=add_node("tool",a,b);$.ajax({url:get_new_module_info_url,data:{type:"tool",tool_id:b,_:"true"},global:false,dataType:"json",success:function(c){node.init_field_data(c)},error:function(d,f){var c="error loading field data";if(d.status===0){c+=", server unavailable"}node.error(c)}})}function add_node_for_module(a,b){node=add_node(a,b);$.ajax({url:get_new_module_info_url,data:{type:a,_:"true"},dataType:"json",success:function(c){node.init_field_data(c)},error:function(d,f){var c="error loading field data";if(d.status==0){c+=", server unavailable"}node.error(c)}})}function display_pja(b,a){$("#pja_container").append(get_pja_form(b));$("#pja_container>.toolForm:last>.toolFormTitle>.buttons").click(function(){action_to_rem=$(this).closest(".toolForm",".action_tag").children(".action_tag:first").text();$(this).closest(".toolForm").remove();delete workflow.active_node.post_job_actions[action_to_rem];workflow.active_form_has_changes=true})}function display_pja_list(){return pja_list}function display_file_list(b){addlist="<select id='node_data_list' name='node_data_list'>";for(var a in b.output_terminals){addlist+="<option value='"+a+"'>"+a+"</option>"}addlist+="</select>";return addlist}function new_pja(d,b,a){if(a.post_job_actions===undefined){a.post_job_actions={}}if(a.post_job_actions[d+b]===undefined){var c={};c.action_type=d;c.output_name=b;a.post_job_actions[d+b]=null;a.post_job_actions[d+b]=c;display_pja(c,a);workflow.active_form_has_changes=true;return true}else{return false}}function show_workflow_parameters(){var c=/\$\{.+?\}/g;var b=[];var f=$("#workflow-parameters-container");var e=$("#workflow-parameters-box");var a="";var d=[];$.each(workflow.nodes,function(g,h){var i=h.form_html.match(c);if(i){d=d.concat(i)}if(h.post_job_actions){$.each(h.post_job_actions,function(j,l){if(l.action_arguments){$.each(l.action_arguments,function(m,n){var o=n.match(c);if(o){d=d.concat(o)}})}});if(d){$.each(d,function(j,l){if($.inArray(l,b)===-1){b.push(l)}})}}});if(b&&b.length!==0){$.each(b,function(g,h){a+="<div>"+h.substring(2,h.length-1)+"</div>"});f.html(a);e.show()}else{f.html(a);e.hide()}}function show_form_for_tool(c,b){$(".right-content").hide();$("#right-content").show().html(c);if(b){$("#right-content").find(".toolForm:first").after("<p><div class='metadataForm'><div class='metadataFormTitle'>Edit Step Attributes</div><div class='form-row'><label>Annotation / Notes:</label><div style='margin-right: 10px;'><textarea name='annotation' rows='3' style='width: 100%'>"+b.annotation+"</textarea><div class='toolParamHelp'>Add an annotation or notes to this step; annotations are available when a workflow is viewed.</div></div></div></div>")}if(b&&b.type=="tool"){pjastr="<p><div class='metadataForm'><div class='metadataFormTitle'>Edit Step Actions</div><div class='form-row'> "+display_pja_list()+" <br/> "+display_file_list(b)+" <div class='action-button' style='border:1px solid black;display:inline;' id='add_pja'>Create</div></div><div class='form-row'><div style='margin-right: 10px;'><span id='pja_container'></span>";pjastr+="<div class='toolParamHelp'>Add actions to this step; actions are applied when this workflow step completes.</div></div></div></div>";$("#right-content").find(".toolForm").after(pjastr);for(var a in b.post_job_actions){if(a!="undefined"){display_pja(b.post_job_actions[a],b)}}$("#add_pja").click(function(){new_pja($("#new_pja_list").val(),$("#node_data_list").val(),b)})}$("#right-content").find("form").ajaxForm({type:"POST",dataType:"json",success:function(d){workflow.active_form_has_changes=false;b.update_field_data(d);show_workflow_parameters()},beforeSubmit:function(d){d.push({name:"tool_state",value:b.tool_state});d.push({name:"_",value:"true"})}}).each(function(){var d=this;$(this).find("select[refresh_on_change='true']").change(function(){$(d).submit()});$(this).find(".popupmenu").each(function(){var g=$(this).parents("div.form-row").attr("id");var e=$('<a class="popup-arrow" id="popup-arrow-for-'+g+'">▼</a>');var f={};$(this).find("button").each(function(){var h=$(this).attr("name");var i=$(this).attr("value");f[$(this).text()]=function(){$(d).append("<input type='hidden' name='"+h+"' value='"+i+"' />").submit()}});e.insertAfter(this);$(this).remove();make_popupmenu(e,f)});$(this).find("input,textarea,select").each(function(){$(this).bind("focus click",function(){workflow.active_form_has_changes=true})})})}var close_editor=function(){workflow.check_changes_in_active_form();if(workflow&&workflow.has_changes){do_close=function(){window.onbeforeunload=undefined;window.document.location=workflow_index_url};show_modal("Close workflow editor","There are unsaved changes to your workflow which will be lost.",{Cancel:hide_modal,"Save Changes":function(){save_current_workflow(null,do_close)}},{"Don't Save":do_close})}else{window.document.location=workflow_index_url}};var save_current_workflow=function(b,c){show_message("Saving workflow","progress");workflow.check_changes_in_active_form();if(!workflow.has_changes){hide_modal();if(c){c()}return}workflow.rectify_workflow_outputs();var a=function(d){$.ajax({url:save_workflow_url,type:"POST",data:{id:workflow_id,workflow_data:function(){return JSON.stringify(workflow.to_simple())},_:"true"},dataType:"json",success:function(g){var e=$("<div></div>").text(g.message);if(g.errors){e.addClass("warningmark");var f=$("<ul/>");$.each(g.errors,function(j,h){$("<li></li>").text(h).appendTo(f)});e.append(f)}else{e.addClass("donemark")}workflow.name=g.name;workflow.has_changes=false;workflow.stored=true;show_workflow_parameters();if(g.errors){show_modal("Saving workflow",e,{Ok:hide_modal})}else{if(d){d()}hide_modal()}}})};if(active_ajax_call){$(document).bind("ajaxStop.save_workflow",function(){$(document).unbind("ajaxStop.save_workflow");a();$(document).unbind("ajaxStop.save_workflow");active_ajax_call=false})}else{a(c)}};
\ No newline at end of file
+$(function(){if(window.lt_ie_7){show_modal("Browser not supported","Sorry, the workflow editor is not supported for IE6 and below.");return}$("#tool-search-query").click(function(){$(this).focus();$(this).select()}).keyup(function(){$(this).css("font-style","normal");if(this.value.length<3){reset_tool_search(false)}else{if(this.value!=this.lastValue){$(this).addClass("search_active");var g=this.value+"*";if(this.timer){clearTimeout(this.timer)}$("#search-spinner").show();this.timer=setTimeout(function(){$.get(tool_search_url,{query:g},function(i){$("#search-no-results").hide();$(".toolSectionWrapper").hide();$(".toolSectionWrapper").find(".toolTitle").hide();if(i.length!=0){var h=$.map(i,function(k,j){return"link-"+k});$(h).each(function(j,k){$("[id='"+k+"']").parent().addClass("search_match");$("[id='"+k+"']").parent().show().parent().parent().show().parent().show()});$(".toolPanelLabel").each(function(){var l=$(this);var k=l.next();var j=true;while(k.length!==0&&k.hasClass("toolTitle")){if(k.is(":visible")){j=false;break}else{k=k.next()}}if(j){l.hide()}})}else{$("#search-no-results").show()}$("#search-spinner").hide()},"json")},200)}}this.lastValue=this.value});canvas_manager=new CanvasManager($("#canvas-viewport"),$("#overview"));reset();$.ajax({url:get_datatypes_url,dataType:"json",cache:false,success:function(g){populate_datatype_info(g);$.ajax({url:load_workflow_url,data:{id:workflow_id,_:"true"},dataType:"json",cache:false,success:function(h){reset();workflow.from_simple(h);workflow.has_changes=false;workflow.fit_canvas_to_nodes();scroll_to_nodes();canvas_manager.draw_overview();upgrade_message="";$.each(h.upgrade_messages,function(j,i){upgrade_message+=("<li>Step "+(parseInt(j,10)+1)+": "+workflow.nodes[j].name+"<ul>");$.each(i,function(k,l){upgrade_message+="<li>"+l+"</li>"});upgrade_message+="</ul></li>"});if(upgrade_message){show_modal("Workflow loaded with changes","Problems were encountered loading this workflow (possibly a result of tool upgrades). Please review the following parameters and then save.<ul>"+upgrade_message+"</ul>",{Continue:hide_modal})}else{hide_modal()}show_workflow_parameters()},beforeSubmit:function(h){show_message("Loading workflow","progress")}})}});$(document).ajaxStart(function(){active_ajax_call=true;$(document).bind("ajaxStop.global",function(){active_ajax_call=false})});$(document).ajaxError(function(i,g){var h=g.responseText||g.statusText||"Could not connect to server";show_modal("Server error",h,{"Ignore error":hide_modal});return false});make_popupmenu($("#workflow-options-button"),{Save:save_current_workflow,Run:function(){window.location=run_workflow_url},"Edit Attributes":c,"Auto Re-layout":f,Close:close_editor});function b(){workflow.clear_active_node();$(".right-content").hide();var j="";for(var h in workflow.nodes){var i=workflow.nodes[h];if(i.type=="tool"){j+="<div class='toolForm' style='margin-bottom:5px;'><div class='toolFormTitle'>Step "+i.id+" - "+i.name+"</div>";for(var k in i.output_terminals){var g=i.output_terminals[k];if($.inArray(g.name,i.workflow_outputs)!=-1){j+="<p>"+g.name+"<input type='checkbox' name='"+i.id+"|"+g.name+"' checked /></p>"}else{j+="<p>"+g.name+"<input type='checkbox' name='"+i.id+"|"+g.name+"' /></p>"}}j+="</div>"}}$("#output-fill-area").html(j);$("#output-fill-area input").bind("click",function(){var n=this.name.split("|")[0];var l=this.name.split("|")[1];if(this.checked){if($.inArray(l,workflow.nodes[n].workflow_outputs)==-1){workflow.nodes[n].workflow_outputs.push(l)}}else{while($.inArray(l,workflow.nodes[n].workflow_outputs)!=-1){var m=$.inArray(l,workflow.nodes[n].workflow_outputs);workflow.nodes[n].workflow_outputs=workflow.nodes[n].workflow_outputs.slice(0,m).concat(workflow.nodes[n].workflow_outputs.slice(m+1))}}workflow.has_changes=true});$("#workflow-output-area").show()}function f(){workflow.layout();workflow.fit_canvas_to_nodes();scroll_to_nodes();canvas_manager.draw_overview()}function c(){workflow.clear_active_node();$(".right-content").hide();$("#edit-attributes").show()}overview_size=$.jStorage.get("overview-size");if(overview_size!==undefined){$("#overview-border").css({width:overview_size,height:overview_size})}if($.jStorage.get("overview-off")){d()}else{e()}$("#overview-border").bind("dragend",function(h,j){var k=$(this).offsetParent();var i=k.offset();var g=Math.max(k.width()-(j.offsetX-i.left),k.height()-(j.offsetY-i.top));$.jStorage.set("overview-size",g+"px")});function e(){$.jStorage.set("overview-off",false);$("#overview-border").css("right","0px");$("#close-viewport").css("background-position","0px 0px")}function d(){$.jStorage.set("overview-off",true);$("#overview-border").css("right","20000px");$("#close-viewport").css("background-position","12px 0px")}$("#close-viewport").click(function(){if($("#overview-border").css("right")==="0px"){d()}else{e()}});window.onbeforeunload=function(){if(workflow&&workflow.has_changes){return"There are unsaved changes to your workflow which will be lost."}};$("div.toolSectionBody").hide();$("div.toolSectionTitle > span").wrap("<a href='#'></a>");var a=null;$("div.toolSectionTitle").each(function(){var g=$(this).next("div.toolSectionBody");$(this).click(function(){if(g.is(":hidden")){if(a){a.slideUp("fast")}a=g;g.slideDown("fast")}else{g.slideUp("fast");a=null}})});async_save_text("workflow-name","workflow-name",rename_async_url,"new_name");$("#workflow-tag").click(function(){$(".tag-area").click();return false});async_save_text("workflow-annotation","workflow-annotation",annotate_async_url,"new_annotation",25,true,4)});function reset(){if(workflow){workflow.remove_all()}workflow=new Workflow($("#canvas-container"))}function scroll_to_nodes(){var a=$("#canvas-viewport");var d=$("#canvas-container");var c,b;if(d.width()<a.width()){b=(a.width()-d.width())/2}else{b=0}if(d.height()<a.height()){c=(a.height()-d.height())/2}else{c=0}d.css({left:b,top:c})}function add_node_for_tool(b,a){node=add_node("tool",a,b);$.ajax({url:get_new_module_info_url,data:{type:"tool",tool_id:b,_:"true"},global:false,dataType:"json",success:function(c){node.init_field_data(c)},error:function(d,f){var c="error loading field data";if(d.status===0){c+=", server unavailable"}node.error(c)}})}function add_node_for_module(a,b){node=add_node(a,b);$.ajax({url:get_new_module_info_url,data:{type:a,_:"true"},dataType:"json",success:function(c){node.init_field_data(c)},error:function(d,f){var c="error loading field data";if(d.status==0){c+=", server unavailable"}node.error(c)}})}function display_pja(b,a){$("#pja_container").append(get_pja_form(b));$("#pja_container>.toolForm:last>.toolFormTitle>.buttons").click(function(){action_to_rem=$(this).closest(".toolForm",".action_tag").children(".action_tag:first").text();$(this).closest(".toolForm").remove();delete workflow.active_node.post_job_actions[action_to_rem];workflow.active_form_has_changes=true})}function display_pja_list(){return pja_list}function display_file_list(b){addlist="<select id='node_data_list' name='node_data_list'>";for(var a in b.output_terminals){addlist+="<option value='"+a+"'>"+a+"</option>"}addlist+="</select>";return addlist}function new_pja(d,b,a){if(a.post_job_actions===undefined){a.post_job_actions={}}if(a.post_job_actions[d+b]===undefined){var c={};c.action_type=d;c.output_name=b;a.post_job_actions[d+b]=null;a.post_job_actions[d+b]=c;display_pja(c,a);workflow.active_form_has_changes=true;return true}else{return false}}function show_workflow_parameters(){var c=/\$\{.+?\}/g;var b=[];var f=$("#workflow-parameters-container");var e=$("#workflow-parameters-box");var a="";var d=[];$.each(workflow.nodes,function(g,h){var i=h.form_html.match(c);if(i){d=d.concat(i)}if(h.post_job_actions){$.each(h.post_job_actions,function(j,l){if(l.action_arguments){$.each(l.action_arguments,function(m,n){var o=n.match(c);if(o){d=d.concat(o)}})}});if(d){$.each(d,function(j,l){if($.inArray(l,b)===-1){b.push(l)}})}}});if(b&&b.length!==0){$.each(b,function(g,h){a+="<div>"+h.substring(2,h.length-1)+"</div>"});f.html(a);e.show()}else{f.html(a);e.hide()}}function show_form_for_tool(c,b){$(".right-content").hide();$("#right-content").show().html(c);if(b){$("#right-content").find(".toolForm:first").after("<p><div class='metadataForm'><div class='metadataFormTitle'>Edit Step Attributes</div><div class='form-row'><label>Annotation / Notes:</label><div style='margin-right: 10px;'><textarea name='annotation' rows='3' style='width: 100%'>"+b.annotation+"</textarea><div class='toolParamHelp'>Add an annotation or notes to this step; annotations are available when a workflow is viewed.</div></div></div></div>")}if(b&&b.type=="tool"){pjastr="<p><div class='metadataForm'><div class='metadataFormTitle'>Edit Step Actions</div><div class='form-row'> "+display_pja_list()+" <br/> "+display_file_list(b)+" <div class='action-button' style='border:1px solid black;display:inline;' id='add_pja'>Create</div></div><div class='form-row'><div style='margin-right: 10px;'><span id='pja_container'></span>";pjastr+="<div class='toolParamHelp'>Add actions to this step; actions are applied when this workflow step completes.</div></div></div></div>";$("#right-content").find(".toolForm").after(pjastr);for(var a in b.post_job_actions){if(a!="undefined"){display_pja(b.post_job_actions[a],b)}}$("#add_pja").click(function(){new_pja($("#new_pja_list").val(),$("#node_data_list").val(),b)})}$("#right-content").find("form").ajaxForm({type:"POST",dataType:"json",success:function(d){workflow.active_form_has_changes=false;b.update_field_data(d);show_workflow_parameters()},beforeSubmit:function(d){d.push({name:"tool_state",value:b.tool_state});d.push({name:"_",value:"true"})}}).each(function(){var d=this;$(this).find("select[refresh_on_change='true']").change(function(){$(d).submit()});$(this).find("input[refresh_on_change='true']").change(function(){$(d).submit()});$(this).find(".popupmenu").each(function(){var g=$(this).parents("div.form-row").attr("id");var e=$('<a class="popup-arrow" id="popup-arrow-for-'+g+'">▼</a>');var f={};$(this).find("button").each(function(){var h=$(this).attr("name");var i=$(this).attr("value");f[$(this).text()]=function(){$(d).append("<input type='hidden' name='"+h+"' value='"+i+"' />").submit()}});e.insertAfter(this);$(this).remove();make_popupmenu(e,f)});$(this).find("input,textarea,select").each(function(){$(this).bind("focus click",function(){workflow.active_form_has_changes=true})})})}var close_editor=function(){workflow.check_changes_in_active_form();if(workflow&&workflow.has_changes){do_close=function(){window.onbeforeunload=undefined;window.document.location=workflow_index_url};show_modal("Close workflow editor","There are unsaved changes to your workflow which will be lost.",{Cancel:hide_modal,"Save Changes":function(){save_current_workflow(null,do_close)}},{"Don't Save":do_close})}else{window.document.location=workflow_index_url}};var save_current_workflow=function(b,c){show_message("Saving workflow","progress");workflow.check_changes_in_active_form();if(!workflow.has_changes){hide_modal();if(c){c()}return}workflow.rectify_workflow_outputs();var a=function(d){$.ajax({url:save_workflow_url,type:"POST",data:{id:workflow_id,workflow_data:function(){return JSON.stringify(workflow.to_simple())},_:"true"},dataType:"json",success:function(g){var e=$("<div></div>").text(g.message);if(g.errors){e.addClass("warningmark");var f=$("<ul/>");$.each(g.errors,function(j,h){$("<li></li>").text(h).appendTo(f)});e.append(f)}else{e.addClass("donemark")}workflow.name=g.name;workflow.has_changes=false;workflow.stored=true;show_workflow_parameters();if(g.errors){show_modal("Saving workflow",e,{Ok:hide_modal})}else{if(d){d()}hide_modal()}}})};if(active_ajax_call){$(document).bind("ajaxStop.save_workflow",function(){$(document).unbind("ajaxStop.save_workflow");a();$(document).unbind("ajaxStop.save_workflow");active_ajax_call=false})}else{a(c)}};
\ No newline at end of file
diff -r 40054ac6f07bce479affd6b39772640e9321e57b -r f7baaf52e94c57cc1a3f7297c191a3e19826293d static/scripts/packed/mvc/collection/dataset-collection-base.js
--- /dev/null
+++ b/static/scripts/packed/mvc/collection/dataset-collection-base.js
@@ -0,0 +1,1 @@
+define(["mvc/dataset/hda-model","mvc/dataset/hda-base"],function(b,a){var c=a.HistoryContentBaseView.extend({className:"dataset hda history-panel-hda",id:function(){return"hdca-"+this.model.get("id")},initialize:function(d){if(d.logger){this.logger=this.model.logger=d.logger}this.log(this+".initialize:",d);this.selectable=d.selectable||false;this.selected=d.selected||false;this.expanded=d.expanded||false},render:function(e){var d=this._buildNewRender();this._queueNewRender(d,e);return this},templateSkeleton:function(){return['<div class="dataset hda">',' <div class="dataset-warnings">',"<% if ( deleted ) { %>",' <div class="dataset-deleted-msg warningmessagesmall"><strong>'," This dataset has been deleted."," </div>","<% } %>","<% if ( ! visible ) { %>",' <div class="dataset-hidden-msg warningmessagesmall"><strong>'," This dataset has been hidden."," </div>","<% } %>"," </div>",' <div class="dataset-selector"><span class="fa fa-2x fa-square-o"></span></div>',' <div class="dataset-primary-actions"></div>',' <div class="dataset-title-bar clear" tabindex="0">',' <span class="dataset-state-icon state-icon"></span>',' <div class="dataset-title">',' <span class="hda-hid"><%= hid %></span>',' <span class="dataset-name"><%= name %></span>'," </div>"," </div>",' <div class="dataset-body"></div>',"</div>",].join("")},templateBody:function(){return['<div class="dataset-body">',' <div class="dataset-summary">'," A dataset collection."," </div>",].join("")},_buildNewRender:function(){var d=$(_.template(this.templateSkeleton(),this.model.toJSON()));d.find(".dataset-primary-actions").append(this._render_titleButtons());d.children(".dataset-body").replaceWith(this._render_body());this._setUpBehaviors(d);return d},_render_titleButtons:function(){return[]},_render_body:function(){var e=$('<div>Error: unknown state "'+this.model.get("state")+'".</div>'),d=this["_render_body_"+this.model.get("state")];if(_.isFunction(d)){e=d.call(this)}this._setUpBehaviors(e);if(this.expanded){e.show()}return e},_setUpBehaviors:function(d){d=d||this.$el;make_popup_menus(d);d.find("[title]").tooltip({placement:"bottom"})},events:{"click .dataset-title-bar":"toggleBodyVisibility","keydown .dataset-title-bar":"toggleBodyVisibility","click .dataset-selector":"toggleSelect"},expandBody:function(){var d=this;function e(){d.$el.children(".dataset-body").replaceWith(d._render_body());d.$el.children(".dataset-body").slideDown(d.fxSpeed,function(){d.expanded=true;d.trigger("body-expanded",d.model.get("id"))})}e()},collapseBody:function(){var d=this;this.$el.children(".dataset-body").slideUp(d.fxSpeed,function(){d.expanded=false;d.trigger("body-collapsed",d.model.get("id"))})},_render_body_ok:function(){var d=this,e=$(_.template(this.templateBody(),this.model.toJSON()));if(this.model.get("deleted")){return e}return e}});return{DatasetCollectionBaseView:c}});
\ No newline at end of file
diff -r 40054ac6f07bce479affd6b39772640e9321e57b -r f7baaf52e94c57cc1a3f7297c191a3e19826293d static/scripts/packed/mvc/collection/dataset-collection-edit.js
--- /dev/null
+++ b/static/scripts/packed/mvc/collection/dataset-collection-edit.js
@@ -0,0 +1,1 @@
+define(["mvc/dataset/hda-model","mvc/collection/dataset-collection-base",],function(b,c){var a=c.DatasetCollectionBaseView.extend({initialize:function(d){c.DatasetCollectionBaseView.prototype.initialize.call(this,d)},_render_titleButtons:function(){return c.DatasetCollectionBaseView.prototype._render_titleButtons.call(this).concat([this._render_deleteButton()])},_render_deleteButton:function(){if((this.model.get("state")===b.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===b.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var d=this,e={title:_l("Delete"),classes:"dataset-delete",onclick:function(){d.$el.find(".icon-btn.dataset-delete").trigger("mouseout");d.model["delete"]()}};if(this.model.get("deleted")){e={title:_l("Dataset collection is already deleted"),disabled:true}}e.faIcon="fa-times";return faIconButton(e)},});return{DatasetCollectionEditView:a}});
\ No newline at end of file
diff -r 40054ac6f07bce479affd6b39772640e9321e57b -r f7baaf52e94c57cc1a3f7297c191a3e19826293d static/scripts/packed/mvc/dataset/hda-base.js
--- a/static/scripts/packed/mvc/dataset/hda-base.js
+++ b/static/scripts/packed/mvc/dataset/hda-base.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-model","mvc/base-mvc","utils/localization"],function(e,b,d){var c=Backbone.View.extend(b.LoggableMixin).extend({tagName:"div",className:"dataset hda history-panel-hda",id:function(){return"hda-"+this.model.get("id")},fxSpeed:"fast",initialize:function(g){if(g.logger){this.logger=this.model.logger=g.logger}this.log(this+".initialize:",g);this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton];this.linkTarget=g.linkTarget||"_blank";this.selectable=g.selectable||false;this.selected=g.selected||false;this.expanded=g.expanded||false;this.draggable=g.draggable||false;this._setUpListeners()},_setUpListeners:function(){this.model.on("change",function(h,g){if(this.model.changedAttributes().state&&this.model.inReadyState()&&this.expanded&&!this.model.hasDetails()){this.model.fetch()}else{this.render()}},this)},render:function(i){i=(i===undefined)?(true):(i);var g=this;this.$el.find("[title]").tooltip("destroy");this.urls=this.model.urls();var h=this._buildNewRender();if(i){$(g).queue(function(j){this.$el.fadeOut(g.fxSpeed,j)})}$(g).queue(function(j){this.$el.empty().attr("class",g.className).addClass("state-"+g.model.get("state")).append(h.children());if(this.selectable){this.showSelector(0)}j()});if(i){$(g).queue(function(j){this.$el.fadeIn(g.fxSpeed,j)})}$(g).queue(function(j){this.trigger("rendered",g);if(this.model.inReadyState()){this.trigger("rendered:ready",g)}if(this.draggable){this.draggableOn()}j()});return this},_buildNewRender:function(){var g=$(c.templates.skeleton(this.model.toJSON()));g.find(".dataset-primary-actions").append(this._render_titleButtons());g.children(".dataset-body").replaceWith(this._render_body());this._setUpBehaviors(g);return g},_setUpBehaviors:function(g){g=g||this.$el;make_popup_menus(g);g.find("[title]").tooltip({placement:"bottom"})},_render_titleButtons:function(){return[this._render_displayButton()]},_render_displayButton:function(){if((this.model.get("state")===e.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(this.model.get("state")===e.HistoryDatasetAssociation.STATES.DISCARDED)||(!this.model.get("accessible"))){return null}var h={target:this.linkTarget,classes:"dataset-display"};if(this.model.get("purged")){h.disabled=true;h.title=d("Cannot display datasets removed from disk")}else{if(this.model.get("state")===e.HistoryDatasetAssociation.STATES.UPLOAD){h.disabled=true;h.title=d("This dataset must finish uploading before it can be viewed")}else{if(this.model.get("state")===e.HistoryDatasetAssociation.STATES.NEW){h.disabled=true;h.title=d("This dataset is not yet viewable")}else{h.title=d("View data");h.href=this.urls.display;var g=this;h.onclick=function(i){if(Galaxy.frame&&Galaxy.frame.active){Galaxy.frame.add({title:"Data Viewer: "+g.model.get("name"),type:"url",content:g.urls.display});i.preventDefault()}}}}}h.faIcon="fa-eye";return faIconButton(h)},_render_downloadButton:function(){if(this.model.get("purged")||!this.model.hasData()){return null}var h=this.urls,i=this.model.get("meta_files");if(_.isEmpty(i)){return $(['<a href="'+h.download+'" title="'+d("Download")+'" ','class="icon-btn dataset-download-btn">','<span class="fa fa-floppy-o"></span>',"</a>"].join(""))}var j="dataset-"+this.model.get("id")+"-popup",g=['<div popupmenu="'+j+'">','<a href="'+h.download+'">',d("Download dataset"),"</a>","<a>"+d("Additional files")+"</a>",_.map(i,function(k){return['<a class="action-button" href="',h.meta_download+k.file_type,'">',d("Download")," ",k.file_type,"</a>"].join("")}).join("\n"),"</div>",'<div class="icon-btn-group">','<a href="'+h.download+'" title="'+d("Download")+'" ','class="icon-btn dataset-download-btn">','<span class="fa fa-floppy-o"></span>','</a><a class="icon-btn popup" id="'+j+'">','<span class="fa fa-caret-down"></span>',"</a>","</div>"].join("\n");return $(g)},_render_showParamsButton:function(){return faIconButton({title:d("View details"),classes:"dataset-params-btn",href:this.urls.show_params,target:this.linkTarget,faIcon:"fa-info-circle"})},_render_body:function(){var h=$('<div>Error: unknown dataset state "'+this.model.get("state")+'".</div>'),g=this["_render_body_"+this.model.get("state")];if(_.isFunction(g)){h=g.call(this)}this._setUpBehaviors(h);if(this.expanded){h.show()}return h},_render_stateBodyHelper:function(g,j){j=j||[];var h=this,i=$(c.templates.body(_.extend(this.model.toJSON(),{body:g})));i.find(".dataset-actions .left").append(_.map(j,function(k){return k.call(h)}));return i},_render_body_new:function(){return this._render_stateBodyHelper("<div>"+d("This is a new dataset and not all of its data are available yet")+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_noPermission:function(){return this._render_stateBodyHelper("<div>"+d("You do not have permission to view this dataset")+"</div>")},_render_body_discarded:function(){return this._render_stateBodyHelper("<div>"+d("The job creating this dataset was cancelled before completion")+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_queued:function(){return this._render_stateBodyHelper("<div>"+d("This job is waiting to run")+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_upload:function(){return this._render_stateBodyHelper("<div>"+d("This dataset is currently uploading")+"</div>")},_render_body_setting_metadata:function(){return this._render_stateBodyHelper("<div>"+d("Metadata is being auto-detected")+"</div>")},_render_body_running:function(){return this._render_stateBodyHelper("<div>"+d("This job is currently running")+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_paused:function(){return this._render_stateBodyHelper("<div>"+d('This job is paused. Use the "Resume Paused Jobs" in the history menu to resume')+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_error:function(){var g=['<span class="help-text">',d("An error occurred with this dataset"),":</span>",'<div class="job-error-text">',$.trim(this.model.get("misc_info")),"</div>"].join("");if(!this.model.get("purged")){g="<div>"+this.model.get("misc_blurb")+"</div>"+g}return this._render_stateBodyHelper(g,[this._render_downloadButton].concat(this.defaultPrimaryActionButtonRenderers))},_render_body_empty:function(){return this._render_stateBodyHelper("<div>"+d("No data")+": <i>"+this.model.get("misc_blurb")+"</i></div>",this.defaultPrimaryActionButtonRenderers)},_render_body_failed_metadata:function(){var g=$('<div class="warningmessagesmall"></div>').append($("<strong/>").text(d("An error occurred setting the metadata for this dataset"))),h=this._render_body_ok();h.prepend(g);return h},_render_body_ok:function(){var g=this,i=$(c.templates.body(this.model.toJSON())),h=[this._render_downloadButton].concat(this.defaultPrimaryActionButtonRenderers);i.find(".dataset-actions .left").append(_.map(h,function(j){return j.call(g)}));if(this.model.isDeletedOrPurged()){return i}return i},events:{"click .dataset-title-bar":"toggleBodyVisibility","keydown .dataset-title-bar":"toggleBodyVisibility","click .dataset-selector":"toggleSelect"},toggleBodyVisibility:function(j,h){var g=32,i=13;if(j&&(j.type==="keydown")&&!(j.keyCode===g||j.keyCode===i)){return true}var k=this.$el.find(".dataset-body");h=(h===undefined)?(!k.is(":visible")):(h);if(h){this.expandBody()}else{this.collapseBody()}return false},expandBody:function(){var g=this;function h(){g.$el.children(".dataset-body").replaceWith(g._render_body());g.$el.children(".dataset-body").slideDown(g.fxSpeed,function(){g.expanded=true;g.trigger("body-expanded",g.model.get("id"))})}if(this.model.inReadyState()&&!this.model.hasDetails()){this.model.fetch({silent:true}).always(function(i){g.urls=g.model.urls();h()})}else{h()}},collapseBody:function(){var g=this;this.$el.children(".dataset-body").slideUp(g.fxSpeed,function(){g.expanded=false;g.trigger("body-collapsed",g.model.get("id"))})},showSelector:function(){if(this.selected){this.select(null,true)}this.selectable=true;this.trigger("selectable",true,this);this.$(".dataset-primary-actions").hide();this.$(".dataset-selector").show()},hideSelector:function(){this.selectable=false;this.trigger("selectable",false,this);this.$(".dataset-selector").hide();this.$(".dataset-primary-actions").show()},toggleSelector:function(){if(!this.$el.find(".dataset-selector").is(":visible")){this.showSelector()}else{this.hideSelector()}},select:function(g){this.$el.find(".dataset-selector span").removeClass("fa-square-o").addClass("fa-check-square-o");if(!this.selected){this.trigger("selected",this);this.selected=true}return false},deselect:function(g){this.$el.find(".dataset-selector span").removeClass("fa-check-square-o").addClass("fa-square-o");if(this.selected){this.trigger("de-selected",this);this.selected=false}return false},toggleSelect:function(g){if(this.selected){this.deselect(g)}else{this.select(g)}},draggableOn:function(){this.draggable=true;this.dragStartHandler=_.bind(this._dragStartHandler,this);this.dragEndHandler=_.bind(this._dragEndHandler,this);var g=this.$el.find(".dataset-title-bar").attr("draggable",true).get(0);g.addEventListener("dragstart",this.dragStartHandler,false);g.addEventListener("dragend",this.dragEndHandler,false)},draggableOff:function(){this.draggable=false;var g=this.$el.find(".dataset-title-bar").attr("draggable",false).get(0);g.removeEventListener("dragstart",this.dragStartHandler,false);g.removeEventListener("dragend",this.dragEndHandler,false)},toggleDraggable:function(){if(this.draggable){this.draggableOff()}else{this.draggableOn()}},_dragStartHandler:function(g){this.trigger("dragstart",this);g.dataTransfer.effectAllowed="move";g.dataTransfer.setData("text",JSON.stringify(this.model.toJSON()));return false},_dragEndHandler:function(g){this.trigger("dragend",this);return false},remove:function(h){var g=this;this.$el.fadeOut(g.fxSpeed,function(){g.$el.remove();g.off();if(h){h()}})},toString:function(){var g=(this.model)?(this.model+""):("(no model)");return"HDABaseView("+g+")"}});var a=['<div class="dataset hda">','<div class="dataset-warnings">',"<% if( hda.error ){ %>",'<div class="errormessagesmall">',d("There was an error getting the data for this dataset"),":<%- hda.error %>","</div>","<% } %>","<% if( hda.deleted ){ %>","<% if( hda.purged ){ %>",'<div class="dataset-purged-msg warningmessagesmall"><strong>',d("This dataset has been deleted and removed from disk")+".","</strong></div>","<% } else { %>",'<div class="dataset-deleted-msg warningmessagesmall"><strong>',d("This dataset has been deleted")+".","</strong></div>","<% } %>","<% } %>","<% if( !hda.visible ){ %>",'<div class="dataset-hidden-msg warningmessagesmall"><strong>',d("This dataset has been hidden")+".","</strong></div>","<% } %>","</div>",'<div class="dataset-selector">','<span class="fa fa-2x fa-square-o"></span>',"</div>",'<div class="dataset-primary-actions"></div>','<div class="dataset-title-bar clear" tabindex="0">','<span class="dataset-state-icon state-icon"></span>','<div class="dataset-title">','<span class="hda-hid"><%- hda.hid %></span> ','<span class="dataset-name"><%- hda.name %></span>',"</div>","</div>",'<div class="dataset-body"></div>',"</div>"].join("");var f=['<div class="dataset-body">',"<% if( hda.body ){ %>",'<div class="dataset-summary">',"<%= hda.body %>","</div>",'<div class="dataset-actions clear">','<div class="left"></div>','<div class="right"></div>',"</div>","<% } else { %>",'<div class="dataset-summary">',"<% if( hda.misc_blurb ){ %>",'<div class="dataset-blurb">','<span class="value"><%- hda.misc_blurb %></span>',"</div>","<% } %>","<% if( hda.data_type ){ %>",'<div class="dataset-datatype">','<label class="prompt">',d("format"),"</label>",'<span class="value"><%- hda.data_type %></span>',"</div>","<% } %>","<% if( hda.metadata_dbkey ){ %>",'<div class="dataset-dbkey">','<label class="prompt">',d("database"),"</label>",'<span class="value">',"<%- hda.metadata_dbkey %>","</span>","</div>","<% } %>","<% if( hda.misc_info ){ %>",'<div class="dataset-info">','<span class="value"><%- hda.misc_info %></span>',"</div>","<% } %>","</div>",'<div class="dataset-actions clear">','<div class="left"></div>','<div class="right"></div>',"</div>","<% if( !hda.deleted ){ %>",'<div class="tags-display"></div>','<div class="annotation-display"></div>','<div class="dataset-display-applications">',"<% _.each( hda.display_apps, function( app ){ %>",'<div class="display-application">','<span class="display-application-location"><%- app.label %></span> ','<span class="display-application-links">',"<% _.each( app.links, function( link ){ %>",'<a target="<%= link.target %>" href="<%= link.href %>">',"<% print( _l( link.text ) ); %>","</a> ","<% }); %>","</span>","</div>","<% }); %>","<% _.each( hda.display_types, function( app ){ %>",'<div class="display-application">','<span class="display-application-location"><%- app.label %></span> ','<span class="display-application-links">',"<% _.each( app.links, function( link ){ %>",'<a target="<%= link.target %>" href="<%= link.href %>">',"<% print( _l( link.text ) ); %>","</a> ","<% }); %>","</span>","</div>","<% }); %>","</div>",'<div class="dataset-peek">',"<% if( hda.peek ){ %>",'<pre class="peek"><%= hda.peek %></pre>',"<% } %>","</div>","<% } %>","<% } %>","</div>"].join("");c.templates={skeleton:function(g){return _.template(a,g,{variable:"hda"})},body:function(g){return _.template(f,g,{variable:"hda"})}};return{HDABaseView:c}});
\ No newline at end of file
+define(["mvc/dataset/hda-model","mvc/base-mvc","utils/localization"],function(e,b,d){var g=Backbone.View.extend(b.LoggableMixin).extend({tagName:"div",fxSpeed:"fast",_queueNewRender:function(i,j){j=(j===undefined)?(true):(j);var h=this;if(j){$(h).queue(function(k){this.$el.fadeOut(h.fxSpeed,k)})}$(h).queue(function(k){this.$el.empty().attr("class",h.className).addClass("state-"+h.model.get("state")).append(i.children());if(this.selectable){this.showSelector(0)}k()});if(j){$(h).queue(function(k){this.$el.fadeIn(h.fxSpeed,k)})}$(h).queue(function(k){this.trigger("rendered",h);if(this.model.inReadyState()){this.trigger("rendered:ready",h)}if(this.draggable){this.draggableOn()}k()})},toggleBodyVisibility:function(k,i){var h=32,j=13;if(k&&(k.type==="keydown")&&!(k.keyCode===h||k.keyCode===j)){return true}var l=this.$el.find(".dataset-body");i=(i===undefined)?(!l.is(":visible")):(i);if(i){this.expandBody()}else{this.collapseBody()}return false},showSelector:function(){if(this.selected){this.select(null,true)}this.selectable=true;this.trigger("selectable",true,this);this.$(".dataset-primary-actions").hide();this.$(".dataset-selector").show()},hideSelector:function(){this.selectable=false;this.trigger("selectable",false,this);this.$(".dataset-selector").hide();this.$(".dataset-primary-actions").show()},toggleSelector:function(){if(!this.$el.find(".dataset-selector").is(":visible")){this.showSelector()}else{this.hideSelector()}},select:function(h){this.$el.find(".dataset-selector span").removeClass("fa-square-o").addClass("fa-check-square-o");if(!this.selected){this.trigger("selected",this);this.selected=true}return false},deselect:function(h){this.$el.find(".dataset-selector span").removeClass("fa-check-square-o").addClass("fa-square-o");if(this.selected){this.trigger("de-selected",this);this.selected=false}return false},toggleSelect:function(h){if(this.selected){this.deselect(h)}else{this.select(h)}},});var c=g.extend({className:"dataset hda history-panel-hda",id:function(){return"hda-"+this.model.get("id")},initialize:function(h){if(h.logger){this.logger=this.model.logger=h.logger}this.log(this+".initialize:",h);this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton];this.linkTarget=h.linkTarget||"_blank";this.selectable=h.selectable||false;this.selected=h.selected||false;this.expanded=h.expanded||false;this.draggable=h.draggable||false;this._setUpListeners()},_setUpListeners:function(){this.model.on("change",function(i,h){if(this.model.changedAttributes().state&&this.model.inReadyState()&&this.expanded&&!this.model.hasDetails()){this.model.fetch()}else{this.render()}},this)},render:function(i){this.$el.find("[title]").tooltip("destroy");this.urls=this.model.urls();var h=this._buildNewRender();this._queueNewRender(h,i);return this},_buildNewRender:function(){var h=$(c.templates.skeleton(this.model.toJSON()));h.find(".dataset-primary-actions").append(this._render_titleButtons());h.children(".dataset-body").replaceWith(this._render_body());this._setUpBehaviors(h);return h},_setUpBehaviors:function(h){h=h||this.$el;make_popup_menus(h);h.find("[title]").tooltip({placement:"bottom"})},_render_titleButtons:function(){return[this._render_displayButton()]},_render_displayButton:function(){if((this.model.get("state")===e.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(this.model.get("state")===e.HistoryDatasetAssociation.STATES.DISCARDED)||(!this.model.get("accessible"))){return null}var i={target:this.linkTarget,classes:"dataset-display"};if(this.model.get("purged")){i.disabled=true;i.title=d("Cannot display datasets removed from disk")}else{if(this.model.get("state")===e.HistoryDatasetAssociation.STATES.UPLOAD){i.disabled=true;i.title=d("This dataset must finish uploading before it can be viewed")}else{if(this.model.get("state")===e.HistoryDatasetAssociation.STATES.NEW){i.disabled=true;i.title=d("This dataset is not yet viewable")}else{i.title=d("View data");i.href=this.urls.display;var h=this;i.onclick=function(j){if(Galaxy.frame&&Galaxy.frame.active){Galaxy.frame.add({title:"Data Viewer: "+h.model.get("name"),type:"url",content:h.urls.display});j.preventDefault()}}}}}i.faIcon="fa-eye";return faIconButton(i)},_render_downloadButton:function(){if(this.model.get("purged")||!this.model.hasData()){return null}var i=this.urls,j=this.model.get("meta_files");if(_.isEmpty(j)){return $(['<a href="'+i.download+'" title="'+d("Download")+'" ','class="icon-btn dataset-download-btn">','<span class="fa fa-floppy-o"></span>',"</a>"].join(""))}var k="dataset-"+this.model.get("id")+"-popup",h=['<div popupmenu="'+k+'">','<a href="'+i.download+'">',d("Download dataset"),"</a>","<a>"+d("Additional files")+"</a>",_.map(j,function(l){return['<a class="action-button" href="',i.meta_download+l.file_type,'">',d("Download")," ",l.file_type,"</a>"].join("")}).join("\n"),"</div>",'<div class="icon-btn-group">','<a href="'+i.download+'" title="'+d("Download")+'" ','class="icon-btn dataset-download-btn">','<span class="fa fa-floppy-o"></span>','</a><a class="icon-btn popup" id="'+k+'">','<span class="fa fa-caret-down"></span>',"</a>","</div>"].join("\n");return $(h)},_render_showParamsButton:function(){return faIconButton({title:d("View details"),classes:"dataset-params-btn",href:this.urls.show_params,target:this.linkTarget,faIcon:"fa-info-circle"})},_render_body:function(){var i=$('<div>Error: unknown dataset state "'+this.model.get("state")+'".</div>'),h=this["_render_body_"+this.model.get("state")];if(_.isFunction(h)){i=h.call(this)}this._setUpBehaviors(i);if(this.expanded){i.show()}return i},_render_stateBodyHelper:function(h,k){k=k||[];var i=this,j=$(c.templates.body(_.extend(this.model.toJSON(),{body:h})));j.find(".dataset-actions .left").append(_.map(k,function(l){return l.call(i)}));return j},_render_body_new:function(){return this._render_stateBodyHelper("<div>"+d("This is a new dataset and not all of its data are available yet")+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_noPermission:function(){return this._render_stateBodyHelper("<div>"+d("You do not have permission to view this dataset")+"</div>")},_render_body_discarded:function(){return this._render_stateBodyHelper("<div>"+d("The job creating this dataset was cancelled before completion")+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_queued:function(){return this._render_stateBodyHelper("<div>"+d("This job is waiting to run")+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_upload:function(){return this._render_stateBodyHelper("<div>"+d("This dataset is currently uploading")+"</div>")},_render_body_setting_metadata:function(){return this._render_stateBodyHelper("<div>"+d("Metadata is being auto-detected")+"</div>")},_render_body_running:function(){return this._render_stateBodyHelper("<div>"+d("This job is currently running")+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_paused:function(){return this._render_stateBodyHelper("<div>"+d('This job is paused. Use the "Resume Paused Jobs" in the history menu to resume')+"</div>",this.defaultPrimaryActionButtonRenderers)},_render_body_error:function(){var h=['<span class="help-text">',d("An error occurred with this dataset"),":</span>",'<div class="job-error-text">',$.trim(this.model.get("misc_info")),"</div>"].join("");if(!this.model.get("purged")){h="<div>"+this.model.get("misc_blurb")+"</div>"+h}return this._render_stateBodyHelper(h,[this._render_downloadButton].concat(this.defaultPrimaryActionButtonRenderers))},_render_body_empty:function(){return this._render_stateBodyHelper("<div>"+d("No data")+": <i>"+this.model.get("misc_blurb")+"</i></div>",this.defaultPrimaryActionButtonRenderers)},_render_body_failed_metadata:function(){var h=$('<div class="warningmessagesmall"></div>').append($("<strong/>").text(d("An error occurred setting the metadata for this dataset"))),i=this._render_body_ok();i.prepend(h);return i},_render_body_ok:function(){var h=this,j=$(c.templates.body(this.model.toJSON())),i=[this._render_downloadButton].concat(this.defaultPrimaryActionButtonRenderers);j.find(".dataset-actions .left").append(_.map(i,function(k){return k.call(h)}));if(this.model.isDeletedOrPurged()){return j}return j},events:{"click .dataset-title-bar":"toggleBodyVisibility","keydown .dataset-title-bar":"toggleBodyVisibility","click .dataset-selector":"toggleSelect"},expandBody:function(){var h=this;function i(){h.$el.children(".dataset-body").replaceWith(h._render_body());h.$el.children(".dataset-body").slideDown(h.fxSpeed,function(){h.expanded=true;h.trigger("body-expanded",h.model.get("id"))})}if(this.model.inReadyState()&&!this.model.hasDetails()){this.model.fetch({silent:true}).always(function(j){h.urls=h.model.urls();i()})}else{i()}},collapseBody:function(){var h=this;this.$el.children(".dataset-body").slideUp(h.fxSpeed,function(){h.expanded=false;h.trigger("body-collapsed",h.model.get("id"))})},draggableOn:function(){this.draggable=true;this.dragStartHandler=_.bind(this._dragStartHandler,this);this.dragEndHandler=_.bind(this._dragEndHandler,this);var h=this.$el.find(".dataset-title-bar").attr("draggable",true).get(0);h.addEventListener("dragstart",this.dragStartHandler,false);h.addEventListener("dragend",this.dragEndHandler,false)},draggableOff:function(){this.draggable=false;var h=this.$el.find(".dataset-title-bar").attr("draggable",false).get(0);h.removeEventListener("dragstart",this.dragStartHandler,false);h.removeEventListener("dragend",this.dragEndHandler,false)},toggleDraggable:function(){if(this.draggable){this.draggableOff()}else{this.draggableOn()}},_dragStartHandler:function(h){this.trigger("dragstart",this);h.dataTransfer.effectAllowed="move";h.dataTransfer.setData("text",JSON.stringify(this.model.toJSON()));return false},_dragEndHandler:function(h){this.trigger("dragend",this);return false},remove:function(i){var h=this;this.$el.fadeOut(h.fxSpeed,function(){h.$el.remove();h.off();if(i){i()}})},toString:function(){var h=(this.model)?(this.model+""):("(no model)");return"HDABaseView("+h+")"}});var a=['<div class="dataset hda">','<div class="dataset-warnings">',"<% if( hda.error ){ %>",'<div class="errormessagesmall">',d("There was an error getting the data for this dataset"),":<%- hda.error %>","</div>","<% } %>","<% if( hda.deleted ){ %>","<% if( hda.purged ){ %>",'<div class="dataset-purged-msg warningmessagesmall"><strong>',d("This dataset has been deleted and removed from disk")+".","</strong></div>","<% } else { %>",'<div class="dataset-deleted-msg warningmessagesmall"><strong>',d("This dataset has been deleted")+".","</strong></div>","<% } %>","<% } %>","<% if( !hda.visible ){ %>",'<div class="dataset-hidden-msg warningmessagesmall"><strong>',d("This dataset has been hidden")+".","</strong></div>","<% } %>","</div>",'<div class="dataset-selector">','<span class="fa fa-2x fa-square-o"></span>',"</div>",'<div class="dataset-primary-actions"></div>','<div class="dataset-title-bar clear" tabindex="0">','<span class="dataset-state-icon state-icon"></span>','<div class="dataset-title">','<span class="hda-hid"><%- hda.hid %></span> ','<span class="dataset-name"><%- hda.name %></span>',"</div>","</div>",'<div class="dataset-body"></div>',"</div>"].join("");var f=['<div class="dataset-body">',"<% if( hda.body ){ %>",'<div class="dataset-summary">',"<%= hda.body %>","</div>",'<div class="dataset-actions clear">','<div class="left"></div>','<div class="right"></div>',"</div>","<% } else { %>",'<div class="dataset-summary">',"<% if( hda.misc_blurb ){ %>",'<div class="dataset-blurb">','<span class="value"><%- hda.misc_blurb %></span>',"</div>","<% } %>","<% if( hda.data_type ){ %>",'<div class="dataset-datatype">','<label class="prompt">',d("format"),"</label>",'<span class="value"><%- hda.data_type %></span>',"</div>","<% } %>","<% if( hda.metadata_dbkey ){ %>",'<div class="dataset-dbkey">','<label class="prompt">',d("database"),"</label>",'<span class="value">',"<%- hda.metadata_dbkey %>","</span>","</div>","<% } %>","<% if( hda.misc_info ){ %>",'<div class="dataset-info">','<span class="value"><%- hda.misc_info %></span>',"</div>","<% } %>","</div>",'<div class="dataset-actions clear">','<div class="left"></div>','<div class="right"></div>',"</div>","<% if( !hda.deleted ){ %>",'<div class="tags-display"></div>','<div class="annotation-display"></div>','<div class="dataset-display-applications">',"<% _.each( hda.display_apps, function( app ){ %>",'<div class="display-application">','<span class="display-application-location"><%- app.label %></span> ','<span class="display-application-links">',"<% _.each( app.links, function( link ){ %>",'<a target="<%= link.target %>" href="<%= link.href %>">',"<% print( _l( link.text ) ); %>","</a> ","<% }); %>","</span>","</div>","<% }); %>","<% _.each( hda.display_types, function( app ){ %>",'<div class="display-application">','<span class="display-application-location"><%- app.label %></span> ','<span class="display-application-links">',"<% _.each( app.links, function( link ){ %>",'<a target="<%= link.target %>" href="<%= link.href %>">',"<% print( _l( link.text ) ); %>","</a> ","<% }); %>","</span>","</div>","<% }); %>","</div>",'<div class="dataset-peek">',"<% if( hda.peek ){ %>",'<pre class="peek"><%= hda.peek %></pre>',"<% } %>","</div>","<% } %>","<% } %>","</div>"].join("");c.templates={skeleton:function(h){return _.template(a,h,{variable:"hda"})},body:function(h){return _.template(f,h,{variable:"hda"})}};return{HistoryContentBaseView:g,HDABaseView:c}});
\ No newline at end of file
diff -r 40054ac6f07bce479affd6b39772640e9321e57b -r f7baaf52e94c57cc1a3f7297c191a3e19826293d static/scripts/packed/mvc/dataset/hda-model.js
--- a/static/scripts/packed/mvc/dataset/hda-model.js
+++ b/static/scripts/packed/mvc/dataset/hda-model.js
@@ -1,1 +1,1 @@
-define(["mvc/base-mvc","utils/localization"],function(d,b){var i=Backbone.Model.extend(d.LoggableMixin).extend({defaults:{history_id:null,model_class:"HistoryDatasetAssociation",hid:0,id:null,name:"(unnamed dataset)",state:"new",deleted:false,visible:true,accessible:true,purged:false,data_type:"",file_size:0,file_ext:"",meta_files:[],misc_blurb:"",misc_info:"",tags:[],annotation:""},urlRoot:galaxy_config.root+"api/histories/",url:function(){return this.urlRoot+this.get("history_id")+"/contents/"+this.get("id")},urls:function(){var k=this.get("id");if(!k){return{}}var j={purge:galaxy_config.root+"datasets/"+k+"/purge_async",display:galaxy_config.root+"datasets/"+k+"/display/?preview=True",edit:galaxy_config.root+"datasets/"+k+"/edit",download:galaxy_config.root+"datasets/"+k+"/display?to_ext="+this.get("file_ext"),report_error:galaxy_config.root+"dataset/errors?id="+k,rerun:galaxy_config.root+"tool_runner/rerun?id="+k,show_params:galaxy_config.root+"datasets/"+k+"/show_params",visualization:galaxy_config.root+"visualization",annotation:{get:galaxy_config.root+"dataset/get_annotation_async?id="+k,set:galaxy_config.root+"dataset/annotate_async?id="+k},meta_download:galaxy_config.root+"dataset/get_metadata_file?hda_id="+k+"&metadata_name="};return j},initialize:function(j){this.log(this+".initialize",this.attributes);this.log("\tparent history_id: "+this.get("history_id"));if(!this.get("accessible")){this.set("state",i.STATES.NOT_VIEWABLE)}this._setUpListeners()},_setUpListeners:function(){this.on("change:state",function(k,j){this.log(this+" has changed state:",k,j);if(this.inReadyState()){this.trigger("state:ready",k,j,this.previous("state"))}})},isDeletedOrPurged:function(){return(this.get("deleted")||this.get("purged"))},isVisible:function(k,l){var j=true;if((!k)&&(this.get("deleted")||this.get("purged"))){j=false}if((!l)&&(!this.get("visible"))){j=false}return j},hidden:function(){return !this.get("visible")},inReadyState:function(){var j=_.contains(i.READY_STATES,this.get("state"));return(this.isDeletedOrPurged()||j)},hasDetails:function(){return _.has(this.attributes,"genome_build")},hasData:function(){return(this.get("file_size")>0)},"delete":function e(j){if(this.get("deleted")){return jQuery.when()}return this.save({deleted:true},j)},undelete:function h(j){if(!this.get("deleted")||this.get("purged")){return jQuery.when()}return this.save({deleted:false},j)},hide:function c(j){if(!this.get("visible")){return jQuery.when()}return this.save({visible:false},j)},unhide:function g(j){if(this.get("visible")){return jQuery.when()}return this.save({visible:true},j)},purge:function f(j){if(this.get("purged")){return jQuery.when()}j=j||{};j.url=galaxy_config.root+"datasets/"+this.get("id")+"/purge_async";var k=this,l=jQuery.ajax(j);l.done(function(o,m,n){k.set({deleted:true,purged:true})});l.fail(function(q,m,p){var n=b("Unable to purge dataset");var o=("Removal of datasets by users is not allowed in this Galaxy instance");if(q.responseJSON&&q.responseJSON.error){n=q.responseJSON.error}else{if(q.responseText.indexOf(o)!==-1){n=o}}q.responseText=n;k.trigger("error",k,q,j,b(n),{error:n})});return l},searchAttributes:["name","file_ext","genome_build","misc_blurb","misc_info","annotation","tags"],searchAliases:{title:"name",format:"file_ext",database:"genome_build",blurb:"misc_blurb",description:"misc_blurb",info:"misc_info",tag:"tags"},searchAttribute:function(l,j){var k=this.get(l);if(!j||(k===undefined||k===null)){return false}if(_.isArray(k)){return this._searchArrayAttribute(k,j)}return(k.toString().toLowerCase().indexOf(j.toLowerCase())!==-1)},_searchArrayAttribute:function(k,j){j=j.toLowerCase();return _.any(k,function(l){return(l.toString().toLowerCase().indexOf(j.toLowerCase())!==-1)})},search:function(j){var k=this;return _.filter(this.searchAttributes,function(l){return k.searchAttribute(l,j)})},matches:function(k){var m="=",j=k.split(m);if(j.length>=2){var l=j[0];l=this.searchAliases[l]||l;return this.searchAttribute(l,j[1])}return !!this.search(k).length},matchesAll:function(k){var j=this;k=k.match(/(".*"|\w*=".*"|\S*)/g).filter(function(l){return !!l});return _.all(k,function(l){l=l.replace(/"/g,"");return j.matches(l)})},toString:function(){var j=this.get("id")||"";if(this.get("name")){j=this.get("hid")+' :"'+this.get("name")+'",'+j}return"HDA("+j+")"}});i.STATES={UPLOAD:"upload",QUEUED:"queued",RUNNING:"running",SETTING_METADATA:"setting_metadata",NEW:"new",EMPTY:"empty",OK:"ok",PAUSED:"paused",FAILED_METADATA:"failed_metadata",NOT_VIEWABLE:"noPermission",DISCARDED:"discarded",ERROR:"error"};i.READY_STATES=[i.STATES.OK,i.STATES.EMPTY,i.STATES.PAUSED,i.STATES.FAILED_METADATA,i.STATES.NOT_VIEWABLE,i.STATES.DISCARDED,i.STATES.ERROR];i.NOT_READY_STATES=[i.STATES.UPLOAD,i.STATES.QUEUED,i.STATES.RUNNING,i.STATES.SETTING_METADATA,i.STATES.NEW];var a=Backbone.Collection.extend(d.LoggableMixin).extend({model:i,urlRoot:galaxy_config.root+"api/histories",url:function(){return this.urlRoot+"/"+this.historyId+"/contents"},initialize:function(k,j){j=j||{};this.historyId=j.historyId},ids:function(){return this.map(function(j){return j.id})},notReady:function(){return this.filter(function(j){return !j.inReadyState()})},running:function(){var j=[];this.each(function(k){if(!k.inReadyState()){j.push(k.get("id"))}});return j},getByHid:function(j){return _.first(this.filter(function(k){return k.get("hid")===j}))},getVisible:function(j,m,l){l=l||[];var k=new a(this.filter(function(n){return n.isVisible(j,m)}));_.each(l,function(n){if(!_.isFunction(n)){return}k=new a(k.filter(n))});return k},haveDetails:function(){return this.all(function(j){return j.hasDetails()})},fetchAllDetails:function(k){k=k||{};var j={details:"all"};k.data=(k.data)?(_.extend(k.data,j)):(j);return this.fetch(k)},ajaxQueue:function(m,l){var k=jQuery.Deferred(),j=this.length,o=[];if(!j){k.resolve([]);return k}var n=this.chain().reverse().map(function(q,p){return function(){var r=m.call(q,l);r.done(function(s){k.notify({curr:p,total:j,response:s,model:q})});r.always(function(s){o.push(s);if(n.length){n.shift()()}else{k.resolve(o)}})}}).value();n.shift()();return k},matches:function(j){return this.filter(function(k){return k.matches(j)})},set:function(l,j){var k=this;l=_.map(l,function(n){var o=k.get(n.id);if(!o){return n}var m=o.toJSON();_.extend(m,n);return m});Backbone.Collection.prototype.set.call(this,l,j)},toString:function(){return(["HDACollection(",[this.historyId,this.length].join(),")"].join(""))}});return{HistoryDatasetAssociation:i,HDACollection:a}});
\ No newline at end of file
+define(["mvc/base-mvc","utils/localization"],function(e,c){var a=Backbone.Model.extend(e.LoggableMixin).extend({urlRoot:galaxy_config.root+"api/histories/",url:function(){return this.urlRoot+this.get("history_id")+"/contents/"+this.get("history_content_type")+"s/"+this.get("id")},hidden:function(){return !this.get("visible")},"delete":function f(m){if(this.get("deleted")){return jQuery.when()}return this.save({deleted:true},m)},undelete:function i(m){if(!this.get("deleted")||this.get("purged")){return jQuery.when()}return this.save({deleted:false},m)},hide:function d(m){if(!this.get("visible")){return jQuery.when()}return this.save({visible:false},m)},unhide:function h(m){if(this.get("visible")){return jQuery.when()}return this.save({visible:true},m)},isVisible:function(n,o){var m=true;if((!n)&&(this.get("deleted")||this.get("purged"))){m=false}if((!o)&&(!this.get("visible"))){m=false}return m},searchAttribute:function(o,m){var n=this.get(o);if(!m||(n===undefined||n===null)){return false}if(_.isArray(n)){return this._searchArrayAttribute(n,m)}return(n.toString().toLowerCase().indexOf(m.toLowerCase())!==-1)},_searchArrayAttribute:function(n,m){m=m.toLowerCase();return _.any(n,function(o){return(o.toString().toLowerCase().indexOf(m.toLowerCase())!==-1)})},search:function(m){var n=this;return _.filter(this.searchAttributes,function(o){return n.searchAttribute(o,m)})},matches:function(n){var p="=",m=n.split(p);if(m.length>=2){var o=m[0];o=this.searchAliases[o]||o;return this.searchAttribute(o,m[1])}return !!this.search(n).length},matchesAll:function(n){var m=this;n=n.match(/(".*"|\w*=".*"|\S*)/g).filter(function(o){return !!o});return _.all(n,function(o){o=o.replace(/"/g,"");return m.matches(o)})}});var l=a.extend({defaults:{history_id:null,model_class:"HistoryDatasetAssociation",history_content_type:"dataset",hid:0,id:null,name:"(unnamed dataset)",state:"new",deleted:false,visible:true,accessible:true,purged:false,data_type:"",file_size:0,file_ext:"",meta_files:[],misc_blurb:"",misc_info:"",tags:[],annotation:""},urls:function(){var n=this.get("id");if(!n){return{}}var m={purge:galaxy_config.root+"datasets/"+n+"/purge_async",display:galaxy_config.root+"datasets/"+n+"/display/?preview=True",edit:galaxy_config.root+"datasets/"+n+"/edit",download:galaxy_config.root+"datasets/"+n+"/display?to_ext="+this.get("file_ext"),report_error:galaxy_config.root+"dataset/errors?id="+n,rerun:galaxy_config.root+"tool_runner/rerun?id="+n,show_params:galaxy_config.root+"datasets/"+n+"/show_params",visualization:galaxy_config.root+"visualization",annotation:{get:galaxy_config.root+"dataset/get_annotation_async?id="+n,set:galaxy_config.root+"dataset/annotate_async?id="+n},meta_download:galaxy_config.root+"dataset/get_metadata_file?hda_id="+n+"&metadata_name="};return m},initialize:function(m){this.log(this+".initialize",this.attributes);this.log("\tparent history_id: "+this.get("history_id"));if(!this.get("accessible")){this.set("state",l.STATES.NOT_VIEWABLE)}this._setUpListeners()},_setUpListeners:function(){this.on("change:state",function(n,m){this.log(this+" has changed state:",n,m);if(this.inReadyState()){this.trigger("state:ready",n,m,this.previous("state"))}})},isDeletedOrPurged:function(){return(this.get("deleted")||this.get("purged"))},inReadyState:function(){var m=_.contains(l.READY_STATES,this.get("state"));return(this.isDeletedOrPurged()||m)},hasDetails:function(){return _.has(this.attributes,"genome_build")},hasData:function(){return(this.get("file_size")>0)},"delete":function f(m){if(this.get("deleted")){return jQuery.when()}return this.save({deleted:true},m)},undelete:function i(m){if(!this.get("deleted")||this.get("purged")){return jQuery.when()}return this.save({deleted:false},m)},hide:function d(m){if(!this.get("visible")){return jQuery.when()}return this.save({visible:false},m)},unhide:function h(m){if(this.get("visible")){return jQuery.when()}return this.save({visible:true},m)},purge:function g(m){if(this.get("purged")){return jQuery.when()}m=m||{};m.url=galaxy_config.root+"datasets/"+this.get("id")+"/purge_async";var n=this,o=jQuery.ajax(m);o.done(function(r,p,q){n.set({deleted:true,purged:true})});o.fail(function(t,p,s){var q=c("Unable to purge dataset");var r=("Removal of datasets by users is not allowed in this Galaxy instance");if(t.responseJSON&&t.responseJSON.error){q=t.responseJSON.error}else{if(t.responseText.indexOf(r)!==-1){q=r}}t.responseText=q;n.trigger("error",n,t,m,c(q),{error:q})});return o},searchAttributes:["name","file_ext","genome_build","misc_blurb","misc_info","annotation","tags"],searchAliases:{title:"name",format:"file_ext",database:"genome_build",blurb:"misc_blurb",description:"misc_blurb",info:"misc_info",tag:"tags"},toString:function(){var m=this.get("id")||"";if(this.get("name")){m=this.get("hid")+' :"'+this.get("name")+'",'+m}return"HDA("+m+")"}});l.STATES={UPLOAD:"upload",QUEUED:"queued",RUNNING:"running",SETTING_METADATA:"setting_metadata",NEW:"new",EMPTY:"empty",OK:"ok",PAUSED:"paused",FAILED_METADATA:"failed_metadata",NOT_VIEWABLE:"noPermission",DISCARDED:"discarded",ERROR:"error"};l.READY_STATES=[l.STATES.OK,l.STATES.EMPTY,l.STATES.PAUSED,l.STATES.FAILED_METADATA,l.STATES.NOT_VIEWABLE,l.STATES.DISCARDED,l.STATES.ERROR];l.NOT_READY_STATES=[l.STATES.UPLOAD,l.STATES.QUEUED,l.STATES.RUNNING,l.STATES.SETTING_METADATA,l.STATES.NEW];var b=Backbone.Collection.extend(e.LoggableMixin).extend({model:function(n,m){if(n.history_content_type=="dataset"){return new l(n,m)}else{if(n.history_content_type=="dataset_collection"){return new j(n,m)}else{}}},urlRoot:galaxy_config.root+"api/histories",url:function(){return this.urlRoot+"/"+this.historyId+"/contents"},initialize:function(n,m){m=m||{};this.historyId=m.historyId},ids:function(){return this.map(function(m){return m.id})},notReady:function(){return this.filter(function(m){return !m.inReadyState()})},running:function(){var m=[];this.each(function(n){if(!n.inReadyState()){m.push(n.get("id"))}});return m},getByHid:function(m){return _.first(this.filter(function(n){return n.get("hid")===m}))},getVisible:function(m,p,o){o=o||[];var n=new b(this.filter(function(q){return q.isVisible(m,p)}));_.each(o,function(q){if(!_.isFunction(q)){return}n=new b(n.filter(q))});return n},haveDetails:function(){return this.all(function(m){return m.hasDetails()})},fetchAllDetails:function(n){n=n||{};var m={details:"all"};n.data=(n.data)?(_.extend(n.data,m)):(m);return this.fetch(n)},ajaxQueue:function(p,o){var n=jQuery.Deferred(),m=this.length,r=[];if(!m){n.resolve([]);return n}var q=this.chain().reverse().map(function(t,s){return function(){var u=p.call(t,o);u.done(function(v){n.notify({curr:s,total:m,response:v,model:t})});u.always(function(v){r.push(v);if(q.length){q.shift()()}else{n.resolve(r)}})}}).value();q.shift()();return n},matches:function(m){return this.filter(function(n){return n.matches(m)})},set:function(o,m){var n=this;o=_.map(o,function(q){var r=n.get(q.id);if(!r){return q}var p=r.toJSON();_.extend(p,q);return p});Backbone.Collection.prototype.set.call(this,o,m)},promoteToHistoryDatasetCollection:function k(r,p,n){n=n||{};n.url=this.url();n.type="POST";var t=p;var q=[],m=null;if(p=="list"){this.chain().each(function(w){var u=w.attributes.name;var x=w.id;var v=w.attributes.history_content_type;if(v=="dataset"){if(t!="list"){console.log("Invalid collection type")}q.push({name:u,src:"hda",id:x})}else{if(t=="list"){t="list:"+w.attributes.collection_type}else{if(t!="list:"+w.attributes.collection_type){console.log("Invalid collection type")}}q.push({name:u,src:"hdca",id:x})}});m="New Dataset List"}else{if(p=="paired"){var o=this.ids();if(o.length!=2){}q.push({name:"left",src:"hda",id:o[0]});q.push({name:"right",src:"hda",id:o[1]});m="New Dataset Pair"}}n.data={type:"dataset_collection",name:m,collection_type:t,element_identifiers:JSON.stringify(q),};var s=jQuery.ajax(n);s.done(function(w,u,v){r.refresh()});s.fail(function(w,u,v){if(w.responseJSON&&w.responseJSON.error){error=w.responseJSON.error}else{error=w.responseJSON}w.responseText=error});return s},toString:function(){return(["HDACollection(",[this.historyId,this.length].join(),")"].join(""))}});var j=a.extend({defaults:{history_id:null,model_class:"HistoryDatasetCollectionAssociation",history_content_type:"dataset_collection",hid:0,id:null,name:"(unnamed dataset collection)",state:"ok",accessible:true,deleted:false,visible:true,purged:false,tags:[],annotation:""},urls:function(){},inReadyState:function(){return true},searchAttributes:["name"],searchAliases:{title:"name"},});return{HistoryDatasetAssociation:l,HDACollection:b}});
\ No newline at end of file
diff -r 40054ac6f07bce479affd6b39772640e9321e57b -r f7baaf52e94c57cc1a3f7297c191a3e19826293d static/scripts/packed/mvc/history/annotated-history-panel.js
--- a/static/scripts/packed/mvc/history/annotated-history-panel.js
+++ b/static/scripts/packed/mvc/history/annotated-history-panel.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-model","mvc/dataset/hda-base","mvc/history/readonly-history-panel","utils/localization"],function(d,a,b,c){var e=b.ReadOnlyHistoryPanel.extend({className:"annotated-history-panel",HDAViewClass:a.HDABaseView,renderModel:function(){this.$el.addClass(this.className);var h=b.ReadOnlyHistoryPanel.prototype.renderModel.call(this),f=this.$datasetsList(h),g=$("<table/>").addClass("datasets-list datasets-table");g.append(f.children());f.replaceWith(g);h.find(".history-subtitle").after(this.renderHistoryAnnotation());h.find(".history-search-btn").hide();h.find(".history-controls").after(h.find(".history-search-controls").show());return h},renderHistoryAnnotation:function(){var f=this.model.get("annotation");if(!f){return null}return $(['<div class="history-annotation">',f,"</div>"].join(""))},renderHdas:function(g){g=g||this.$el;var f=b.ReadOnlyHistoryPanel.prototype.renderHdas.call(this,g);this.$datasetsList(g).prepend($("<tr/>").addClass("headers").append([$("<th/>").text(c("Dataset")),$("<th/>").text(c("Annotation"))]));return f},attachHdaView:function(i,g){g=g||this.$el;var j=_.find(i.el.classList,function(k){return(/^state\-/).test(k)}),f=i.model.get("annotation")||"",h=$("<tr/>").addClass("dataset-row").append([$("<td/>").addClass("dataset-container").append(i.$el).addClass(j?j.replace("-","-color-"):""),$("<td/>").addClass("additional-info").text(f)]);this.$datasetsList(g).append(h)},events:_.extend(_.clone(b.ReadOnlyHistoryPanel.prototype.events),{"click tr":function(f){$(f.currentTarget).find(".dataset-title-bar").click()},"click .icon-btn":function(f){f.stopPropagation()}}),toString:function(){return"AnnotatedHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{AnnotatedHistoryPanel:e}});
\ No newline at end of file
+define(["mvc/dataset/hda-model","mvc/dataset/hda-base","mvc/history/readonly-history-panel","utils/localization"],function(d,a,b,c){var e=b.ReadOnlyHistoryPanel.extend({className:"annotated-history-panel",HDAViewClass:a.HDABaseView,renderModel:function(){this.$el.addClass(this.className);var h=b.ReadOnlyHistoryPanel.prototype.renderModel.call(this),f=this.$datasetsList(h),g=$("<table/>").addClass("datasets-list datasets-table");g.append(f.children());f.replaceWith(g);h.find(".history-subtitle").after(this.renderHistoryAnnotation());h.find(".history-search-btn").hide();h.find(".history-controls").after(h.find(".history-search-controls").show());return h},renderHistoryAnnotation:function(){var f=this.model.get("annotation");if(!f){return null}return $(['<div class="history-annotation">',f,"</div>"].join(""))},renderHdas:function(g){g=g||this.$el;var f=b.ReadOnlyHistoryPanel.prototype.renderHdas.call(this,g);this.$datasetsList(g).prepend($("<tr/>").addClass("headers").append([$("<th/>").text(c("Dataset")),$("<th/>").text(c("Annotation"))]));return f},attachContentView:function(i,g){g=g||this.$el;var j=_.find(i.el.classList,function(k){return(/^state\-/).test(k)}),f=i.model.get("annotation")||"",h=$("<tr/>").addClass("dataset-row").append([$("<td/>").addClass("dataset-container").append(i.$el).addClass(j?j.replace("-","-color-"):""),$("<td/>").addClass("additional-info").text(f)]);this.$datasetsList(g).append(h)},events:_.extend(_.clone(b.ReadOnlyHistoryPanel.prototype.events),{"click tr":function(f){$(f.currentTarget).find(".dataset-title-bar").click()},"click .icon-btn":function(f){f.stopPropagation()}}),toString:function(){return"AnnotatedHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{AnnotatedHistoryPanel:e}});
\ No newline at end of file
diff -r 40054ac6f07bce479affd6b39772640e9321e57b -r f7baaf52e94c57cc1a3f7297c191a3e19826293d static/scripts/packed/mvc/history/history-panel.js
--- a/static/scripts/packed/mvc/history/history-panel.js
+++ b/static/scripts/packed/mvc/history/history-panel.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-model","mvc/dataset/hda-edit","mvc/history/readonly-history-panel","mvc/tags","mvc/annotations","utils/localization"],function(f,b,d,a,c,e){var g=d.ReadOnlyHistoryPanel.extend({HDAViewClass:b.HDAEditView,initialize:function(h){h=h||{};this.selectedHdaIds=[];this.tagsEditor=null;this.annotationEditor=null;this.purgeAllowed=h.purgeAllowed||false;this.selecting=h.selecting||false;this.annotationEditorShown=h.annotationEditorShown||false;this.tagsEditorShown=h.tagsEditorShown||false;d.ReadOnlyHistoryPanel.prototype.initialize.call(this,h)},_setUpModelEventHandlers:function(){d.ReadOnlyHistoryPanel.prototype._setUpModelEventHandlers.call(this);this.model.on("change:nice_size",this.updateHistoryDiskSize,this);this.model.hdas.on("change:deleted",this._handleHdaDeletionChange,this);this.model.hdas.on("change:visible",this._handleHdaVisibleChange,this);this.model.hdas.on("change:purged",function(h){this.model.fetch()},this)},renderModel:function(){var h=$("<div/>");h.append(g.templates.historyPanel(this.model.toJSON()));this.$emptyMessage(h).text(this.emptyMsg);if(Galaxy&&Galaxy.currUser&&Galaxy.currUser.id&&Galaxy.currUser.id===this.model.get("user_id")){this._renderTags(h);this._renderAnnotation(h)}h.find(".history-secondary-actions").prepend(this._renderSelectButton());h.find(".history-dataset-actions").toggle(this.selecting);h.find(".history-secondary-actions").prepend(this._renderSearchButton());this._setUpBehaviours(h);this.renderHdas(h);return h},_renderTags:function(h){var i=this;this.tagsEditor=new a.TagsEditor({model:this.model,el:h.find(".history-controls .tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){i.toggleHDATagEditors(true,i.fxSpeed)},onhide:function(){i.toggleHDATagEditors(false,i.fxSpeed)},$activator:faIconButton({title:e("Edit history tags"),classes:"history-tag-btn",faIcon:"fa-tags"}).appendTo(h.find(".history-secondary-actions"))})},_renderAnnotation:function(h){var i=this;this.annotationEditor=new c.AnnotationEditor({model:this.model,el:h.find(".history-controls .annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){i.toggleHDAAnnotationEditors(true,i.fxSpeed)},onhide:function(){i.toggleHDAAnnotationEditors(false,i.fxSpeed)},$activator:faIconButton({title:e("Edit history annotation"),classes:"history-annotate-btn",faIcon:"fa-comment"}).appendTo(h.find(".history-secondary-actions"))})},_renderSelectButton:function(h){return faIconButton({title:e("Operations on multiple datasets"),classes:"history-select-btn",faIcon:"fa-check-square-o"})},_setUpBehaviours:function(h){h=h||this.$el;d.ReadOnlyHistoryPanel.prototype._setUpBehaviours.call(this,h);if(!this.model){return}this._setUpDatasetActionsPopup(h);if((!Galaxy.currUser||Galaxy.currUser.isAnonymous())||(Galaxy.currUser.id!==this.model.get("user_id"))){return}var i=this;h.find(".history-name").attr("title",e("Click to rename history")).tooltip({placement:"bottom"}).make_text_editable({on_finish:function(j){var k=i.model.get("name");if(j&&j!==k){i.$el.find(".history-name").text(j);i.model.save({name:j}).fail(function(){i.$el.find(".history-name").text(i.model.previous("name"))})}else{i.$el.find(".history-name").text(k)}}})},_setUpDatasetActionsPopup:function(h){var i=this,j=[{html:e("Hide datasets"),func:function(){var k=f.HistoryDatasetAssociation.prototype.hide;i.getSelectedHdaCollection().ajaxQueue(k)}},{html:e("Unhide datasets"),func:function(){var k=f.HistoryDatasetAssociation.prototype.unhide;i.getSelectedHdaCollection().ajaxQueue(k)}},{html:e("Delete datasets"),func:function(){var k=f.HistoryDatasetAssociation.prototype["delete"];i.getSelectedHdaCollection().ajaxQueue(k)}},{html:e("Undelete datasets"),func:function(){var k=f.HistoryDatasetAssociation.prototype.undelete;i.getSelectedHdaCollection().ajaxQueue(k)}}];if(i.purgeAllowed){j.push({html:e("Permanently delete datasets"),func:function(){if(confirm(e("This will permanently remove the data in your datasets. Are you sure?"))){var k=f.HistoryDatasetAssociation.prototype.purge;i.getSelectedHdaCollection().ajaxQueue(k)}}})}return new PopupMenu(h.find(".history-dataset-action-popup-btn"),j)},_handleHdaDeletionChange:function(h){if(h.get("deleted")&&!this.storage.get("show_deleted")){this.removeHdaView(this.hdaViews[h.id])}},_handleHdaVisibleChange:function(h){if(h.hidden()&&!this.storage.get("show_hidden")){this.removeHdaView(this.hdaViews[h.id])}},_createHdaView:function(i){var h=i.get("id"),j=new this.HDAViewClass({model:i,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[h],selectable:this.selecting,purgeAllowed:this.purgeAllowed,hasUser:this.model.ownedByCurrUser(),logger:this.logger,tagsEditorShown:(this.tagsEditor&&!this.tagsEditor.hidden),annotationEditorShown:(this.annotationEditor&&!this.annotationEditor.hidden)});this._setUpHdaListeners(j);return j},_setUpHdaListeners:function(i){var h=this;d.ReadOnlyHistoryPanel.prototype._setUpHdaListeners.call(this,i);i.on("selected",function(j){var k=j.model.get("id");h.selectedHdaIds=_.union(h.selectedHdaIds,[k])});i.on("de-selected",function(j){var k=j.model.get("id");h.selectedHdaIds=_.without(h.selectedHdaIds,k)})},toggleHDATagEditors:function(h){var i=arguments;_.each(this.hdaViews,function(j){if(j.tagsEditor){j.tagsEditor.toggle.apply(j.tagsEditor,i)}})},toggleHDAAnnotationEditors:function(h){var i=arguments;_.each(this.hdaViews,function(j){if(j.annotationEditor){j.annotationEditor.toggle.apply(j.annotationEditor,i)}})},removeHdaView:function(i){if(!i){return}var h=this;i.$el.fadeOut(h.fxSpeed,function(){i.off();i.remove();delete h.hdaViews[i.model.id];if(_.isEmpty(h.hdaViews)){h.trigger("empty-history",h);h._renderEmptyMsg()}})},events:_.extend(_.clone(d.ReadOnlyHistoryPanel.prototype.events),{"click .history-select-btn":"toggleSelectors","click .history-select-all-datasets-btn":"selectAllDatasets","click .history-deselect-all-datasets-btn":"deselectAllDatasets"}),updateHistoryDiskSize:function(){this.$el.find(".history-size").text(this.model.get("nice_size"))},showSelectors:function(h){h=(h!==undefined)?(h):(this.fxSpeed);this.selecting=true;this.$(".history-dataset-actions").slideDown(h);_.each(this.hdaViews,function(i){i.showSelector()});this.selectedHdaIds=[]},hideSelectors:function(h){h=(h!==undefined)?(h):(this.fxSpeed);this.selecting=false;this.$(".history-dataset-actions").slideUp(h);_.each(this.hdaViews,function(i){i.hideSelector()});this.selectedHdaIds=[]},toggleSelectors:function(){if(!this.selecting){this.showSelectors()}else{this.hideSelectors()}},selectAllDatasets:function(h){_.each(this.hdaViews,function(i){i.select(h)})},deselectAllDatasets:function(h){_.each(this.hdaViews,function(i){i.deselect(h)})},getSelectedHdaViews:function(){return _.filter(this.hdaViews,function(h){return h.selected})},getSelectedHdaCollection:function(){return new f.HDACollection(_.map(this.getSelectedHdaViews(),function(h){return h.model}),{historyId:this.model.id})},toString:function(){return"HistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{HistoryPanel:g}});
\ No newline at end of file
+define(["mvc/dataset/hda-model","mvc/dataset/hda-edit","mvc/collection/dataset-collection-edit","mvc/history/readonly-history-panel","mvc/tags","mvc/annotations","utils/localization"],function(f,b,h,d,a,c,e){var g=d.ReadOnlyHistoryPanel.extend({HDAViewClass:b.HDAEditView,initialize:function(i){i=i||{};this.selectedHdaIds=[];this.tagsEditor=null;this.annotationEditor=null;this.purgeAllowed=i.purgeAllowed||false;this.selecting=i.selecting||false;this.annotationEditorShown=i.annotationEditorShown||false;this.tagsEditorShown=i.tagsEditorShown||false;d.ReadOnlyHistoryPanel.prototype.initialize.call(this,i)},_setUpModelEventHandlers:function(){d.ReadOnlyHistoryPanel.prototype._setUpModelEventHandlers.call(this);this.model.on("change:nice_size",this.updateHistoryDiskSize,this);this.model.hdas.on("change:deleted",this._handleHdaDeletionChange,this);this.model.hdas.on("change:visible",this._handleHdaVisibleChange,this);this.model.hdas.on("change:purged",function(i){this.model.fetch()},this)},renderModel:function(){var i=$("<div/>");i.append(g.templates.historyPanel(this.model.toJSON()));this.$emptyMessage(i).text(this.emptyMsg);if(Galaxy&&Galaxy.currUser&&Galaxy.currUser.id&&Galaxy.currUser.id===this.model.get("user_id")){this._renderTags(i);this._renderAnnotation(i)}i.find(".history-secondary-actions").prepend(this._renderSelectButton());i.find(".history-dataset-actions").toggle(this.selecting);i.find(".history-secondary-actions").prepend(this._renderSearchButton());this._setUpBehaviours(i);this.renderHdas(i);return i},_renderTags:function(i){var j=this;this.tagsEditor=new a.TagsEditor({model:this.model,el:i.find(".history-controls .tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){j.toggleHDATagEditors(true,j.fxSpeed)},onhide:function(){j.toggleHDATagEditors(false,j.fxSpeed)},$activator:faIconButton({title:e("Edit history tags"),classes:"history-tag-btn",faIcon:"fa-tags"}).appendTo(i.find(".history-secondary-actions"))})},_renderAnnotation:function(i){var j=this;this.annotationEditor=new c.AnnotationEditor({model:this.model,el:i.find(".history-controls .annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){j.toggleHDAAnnotationEditors(true,j.fxSpeed)},onhide:function(){j.toggleHDAAnnotationEditors(false,j.fxSpeed)},$activator:faIconButton({title:e("Edit history annotation"),classes:"history-annotate-btn",faIcon:"fa-comment"}).appendTo(i.find(".history-secondary-actions"))})},_renderSelectButton:function(i){return faIconButton({title:e("Operations on multiple datasets"),classes:"history-select-btn",faIcon:"fa-check-square-o"})},_setUpBehaviours:function(i){i=i||this.$el;d.ReadOnlyHistoryPanel.prototype._setUpBehaviours.call(this,i);if(!this.model){return}this._setUpDatasetActionsPopup(i);if((!Galaxy.currUser||Galaxy.currUser.isAnonymous())||(Galaxy.currUser.id!==this.model.get("user_id"))){return}var j=this;i.find(".history-name").attr("title",e("Click to rename history")).tooltip({placement:"bottom"}).make_text_editable({on_finish:function(k){var l=j.model.get("name");if(k&&k!==l){j.$el.find(".history-name").text(k);j.model.save({name:k}).fail(function(){j.$el.find(".history-name").text(j.model.previous("name"))})}else{j.$el.find(".history-name").text(l)}}})},_setUpDatasetActionsPopup:function(i){var j=this,k=[{html:e("Hide datasets"),func:function(){var l=f.HistoryDatasetAssociation.prototype.hide;j.getSelectedHdaCollection().ajaxQueue(l)}},{html:e("Unhide datasets"),func:function(){var l=f.HistoryDatasetAssociation.prototype.unhide;j.getSelectedHdaCollection().ajaxQueue(l)}},{html:e("Delete datasets"),func:function(){var l=f.HistoryDatasetAssociation.prototype["delete"];j.getSelectedHdaCollection().ajaxQueue(l)}},{html:e("Undelete datasets"),func:function(){var l=f.HistoryDatasetAssociation.prototype.undelete;j.getSelectedHdaCollection().ajaxQueue(l)}}];if(j.purgeAllowed){k.push({html:e("Permanently delete datasets"),func:function(){if(confirm(e("This will permanently remove the data in your datasets. Are you sure?"))){var l=f.HistoryDatasetAssociation.prototype.purge;j.getSelectedHdaCollection().ajaxQueue(l)}}})}k.push({html:e("Build Dataset List (Experimental)"),func:function(){j.getSelectedHdaCollection().promoteToHistoryDatasetCollection(j.model,"list")}});k.push({html:e("Build Dataset Pair (Experimental)"),func:function(){j.getSelectedHdaCollection().promoteToHistoryDatasetCollection(j.model,"paired")}});return new PopupMenu(i.find(".history-dataset-action-popup-btn"),k)},_handleHdaDeletionChange:function(i){if(i.get("deleted")&&!this.storage.get("show_deleted")){this.removeHdaView(this.hdaViews[i.id])}},_handleHdaVisibleChange:function(i){if(i.hidden()&&!this.storage.get("show_hidden")){this.removeHdaView(this.hdaViews[i.id])}},_createContentView:function(j){var i=j.get("id"),l=j.get("history_content_type"),k=null;if(l=="dataset"){k=new this.HDAViewClass({model:j,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[i],selectable:this.selecting,purgeAllowed:this.purgeAllowed,hasUser:this.model.ownedByCurrUser(),logger:this.logger,tagsEditorShown:(this.tagsEditor&&!this.tagsEditor.hidden),annotationEditorShown:(this.annotationEditor&&!this.annotationEditor.hidden)})}else{if(l=="dataset_collection"){k=new h.DatasetCollectionEditView({model:j,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[i],hasUser:this.model.ownedByCurrUser(),logger:this.logger})}}this._setUpHdaListeners(k);return k},_setUpHdaListeners:function(j){var i=this;d.ReadOnlyHistoryPanel.prototype._setUpHdaListeners.call(this,j);j.on("selected",function(k){var l=k.model.get("id");i.selectedHdaIds=_.union(i.selectedHdaIds,[l])});j.on("de-selected",function(k){var l=k.model.get("id");i.selectedHdaIds=_.without(i.selectedHdaIds,l)})},toggleHDATagEditors:function(i){var j=arguments;_.each(this.hdaViews,function(k){if(k.tagsEditor){k.tagsEditor.toggle.apply(k.tagsEditor,j)}})},toggleHDAAnnotationEditors:function(i){var j=arguments;_.each(this.hdaViews,function(k){if(k.annotationEditor){k.annotationEditor.toggle.apply(k.annotationEditor,j)}})},removeHdaView:function(j){if(!j){return}var i=this;j.$el.fadeOut(i.fxSpeed,function(){j.off();j.remove();delete i.hdaViews[j.model.id];if(_.isEmpty(i.hdaViews)){i.trigger("empty-history",i);i._renderEmptyMsg()}})},events:_.extend(_.clone(d.ReadOnlyHistoryPanel.prototype.events),{"click .history-select-btn":"toggleSelectors","click .history-select-all-datasets-btn":"selectAllDatasets","click .history-deselect-all-datasets-btn":"deselectAllDatasets"}),updateHistoryDiskSize:function(){this.$el.find(".history-size").text(this.model.get("nice_size"))},showSelectors:function(i){i=(i!==undefined)?(i):(this.fxSpeed);this.selecting=true;this.$(".history-dataset-actions").slideDown(i);_.each(this.hdaViews,function(j){j.showSelector()});this.selectedHdaIds=[]},hideSelectors:function(i){i=(i!==undefined)?(i):(this.fxSpeed);this.selecting=false;this.$(".history-dataset-actions").slideUp(i);_.each(this.hdaViews,function(j){j.hideSelector()});this.selectedHdaIds=[]},toggleSelectors:function(){if(!this.selecting){this.showSelectors()}else{this.hideSelectors()}},selectAllDatasets:function(i){_.each(this.hdaViews,function(j){j.select(i)})},deselectAllDatasets:function(i){_.each(this.hdaViews,function(j){j.deselect(i)})},getSelectedHdaViews:function(){return _.filter(this.hdaViews,function(i){return i.selected})},getSelectedHdaCollection:function(){return new f.HDACollection(_.map(this.getSelectedHdaViews(),function(i){return i.model}),{historyId:this.model.id})},toString:function(){return"HistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{HistoryPanel:g}});
\ No newline at end of file
diff -r 40054ac6f07bce479affd6b39772640e9321e57b -r f7baaf52e94c57cc1a3f7297c191a3e19826293d static/scripts/packed/mvc/history/readonly-history-panel.js
--- a/static/scripts/packed/mvc/history/readonly-history-panel.js
+++ b/static/scripts/packed/mvc/history/readonly-history-panel.js
@@ -1,1 +1,1 @@
-define(["mvc/history/history-model","mvc/dataset/hda-base","mvc/user/user-model","mvc/base-mvc","utils/localization"],function(g,b,a,f,d){var i=f.SessionStorageModel.extend({defaults:{expandedHdas:{},show_deleted:false,show_hidden:false},addExpandedHda:function(m){var l="expandedHdas";this.save(l,_.extend(this.get(l),_.object([m],[true])))},removeExpandedHda:function(m){var l="expandedHdas";this.save(l,_.omit(this.get(l),m))},toString:function(){return"HistoryPrefs("+this.id+")"}});i.storageKeyPrefix="history:";i.historyStorageKey=function e(l){if(!l){throw new Error("HistoryPrefs.historyStorageKey needs valid id: "+l)}return(i.storageKeyPrefix+l)};i.get=function c(l){return new i({id:i.historyStorageKey(l)})};i.clearAll=function h(m){for(var l in sessionStorage){if(l.indexOf(i.storageKeyPrefix)===0){sessionStorage.removeItem(l)}}};var j=Backbone.View.extend(f.LoggableMixin).extend({HDAViewClass:b.HDABaseView,tagName:"div",className:"history-panel",fxSpeed:"fast",emptyMsg:d("This history is empty"),noneFoundMsg:d("No matching datasets found"),initialize:function(l){l=l||{};if(l.logger){this.logger=l.logger}this.log(this+".initialize:",l);this.linkTarget=l.linkTarget||"_blank";this.fxSpeed=_.has(l,"fxSpeed")?(l.fxSpeed):(this.fxSpeed);this.filters=[];this.searchFor="";this.findContainerFn=l.findContainerFn;this.hdaViews={};this.indicator=new LoadingIndicator(this.$el);this._setUpListeners();var m=_.pick(l,"initiallyExpanded","show_deleted","show_hidden");this.setModel(this.model,m,false);if(l.onready){l.onready.call(this)}},_setUpListeners:function(){this.on("error",function(m,p,l,o,n){this.errorHandler(m,p,l,o,n)});this.on("loading-history",function(){this._showLoadingIndicator("loading history...",40)});this.on("loading-done",function(){this._hideLoadingIndicator(40);if(_.isEmpty(this.hdaViews)){this.trigger("empty-history",this)}});this.once("rendered",function(){this.trigger("rendered:initial",this);return false});if(this.logger){this.on("all",function(l){this.log(this+"",arguments)},this)}return this},errorHandler:function(n,q,m,p,o){console.error(n,q,m,p,o);if(q&&q.status===0&&q.readyState===0){}else{if(q&&q.status===502){}else{var l=this._parseErrorMessage(n,q,m,p,o);if(!this.$messages().is(":visible")){this.once("rendered",function(){this.displayMessage("error",l.message,l.details)})}else{this.displayMessage("error",l.message,l.details)}}}},_parseErrorMessage:function(o,s,n,r,q){var m=Galaxy.currUser,l={message:this._bePolite(r),details:{user:(m instanceof a.User)?(m.toJSON()):(m+""),source:(o instanceof Backbone.Model)?(o.toJSON()):(o+""),xhr:s,options:(s)?(_.omit(n,"xhr")):(n)}};_.extend(l.details,q||{});if(s&&_.isFunction(s.getAllResponseHeaders)){var p=s.getAllResponseHeaders();p=_.compact(p.split("\n"));p=_.map(p,function(t){return t.split(": ")});l.details.xhr.responseHeaders=_.object(p)}return l},_bePolite:function(l){l=l||d("An error occurred while getting updates from the server");return l+". "+d("Please contact a Galaxy administrator if the problem persists")+"."},loadHistoryWithHDADetails:function(n,m,l,p){var o=function(q){return _.keys(i.get(q.id).get("expandedHdas"))};return this.loadHistory(n,m,l,p,o)},loadHistory:function(o,n,m,r,p){var l=this;n=n||{};l.trigger("loading-history",l);var q=g.History.getHistoryData(o,{historyFn:m,hdaFn:r,hdaDetailIds:n.initiallyExpanded||p});return l._loadHistoryFromXHR(q,n).fail(function(u,s,t){l.trigger("error",l,u,n,d("An error was encountered while "+s),{historyId:o,history:t||{}})}).always(function(){l.trigger("loading-done",l)})},_loadHistoryFromXHR:function(n,m){var l=this;n.then(function(o,p){l.JSONToModel(o,p,m)});n.fail(function(p,o){l.render()});return n},JSONToModel:function(o,l,m){this.log("JSONToModel:",o,l,m);m=m||{};var n=new g.History(o,l,m);this.setModel(n);return this},setModel:function(m,l,n){l=l||{};n=(n!==undefined)?(n):(true);this.log("setModel:",m,l,n);this.freeModel();this.selectedHdaIds=[];if(m){this.model=m;if(this.logger){this.model.logger=this.logger}this._setUpWebStorage(l.initiallyExpanded,l.show_deleted,l.show_hidden);this._setUpModelEventHandlers();this.trigger("new-model",this)}if(n){this.render()}return this},freeModel:function(){if(this.model){this.model.clearUpdateTimeout();this.stopListening(this.model);this.stopListening(this.model.hdas)}this.freeHdaViews();return this},freeHdaViews:function(){this.hdaViews={};return this},_setUpWebStorage:function(m,l,n){this.storage=new i({id:i.historyStorageKey(this.model.get("id"))});if(_.isObject(m)){this.storage.set("exandedHdas",m)}if(_.isBoolean(l)){this.storage.set("show_deleted",l)}if(_.isBoolean(n)){this.storage.set("show_hidden",n)}this.trigger("new-storage",this.storage,this);this.log(this+" (init'd) storage:",this.storage.get());return this},_setUpModelEventHandlers:function(){this.model.hdas.on("add",this.addHdaView,this);this.model.on("error error:hdas",function(m,o,l,n){this.errorHandler(m,o,l,n)},this);return this},render:function(n,o){this.log("render:",n,o);n=(n===undefined)?(this.fxSpeed):(n);var l=this,m;if(this.model){m=this.renderModel()}else{m=this.renderWithoutModel()}$(l).queue("fx",[function(p){if(n&&l.$el.is(":visible")){l.$el.fadeOut(n,p)}else{p()}},function(p){l.$el.empty();if(m){l.$el.append(m.children())}p()},function(p){if(n&&!l.$el.is(":visible")){l.$el.fadeIn(n,p)}else{p()}},function(p){if(o){o.call(this)}l.trigger("rendered",this);p()}]);return this},renderWithoutModel:function(){var l=$("<div/>"),m=$("<div/>").addClass("message-container").css({margin:"4px"});return l.append(m)},renderModel:function(){var l=$("<div/>");l.append(j.templates.historyPanel(this.model.toJSON()));this.$emptyMessage(l).text(this.emptyMsg);l.find(".history-secondary-actions").prepend(this._renderSearchButton());this._setUpBehaviours(l);this.renderHdas(l);return l},_renderEmptyMsg:function(n){var m=this,l=m.$emptyMessage(n);if(!_.isEmpty(m.hdaViews)){l.hide()}else{if(m.searchFor){l.text(m.noneFoundMsg).show()}else{l.text(m.emptyMsg).show()}}return this},_renderSearchButton:function(l){return faIconButton({title:d("Search datasets"),classes:"history-search-btn",faIcon:"fa-search"})},_setUpBehaviours:function(l){l=l||this.$el;l.find("[title]").tooltip({placement:"bottom"});this._setUpSearchInput(l.find(".history-search-controls .history-search-input"));return this},$container:function(){return(this.findContainerFn)?(this.findContainerFn.call(this)):(this.$el.parent())},$datasetsList:function(l){return(l||this.$el).find(".datasets-list")},$messages:function(l){return(l||this.$el).find(".message-container")},$emptyMessage:function(l){return(l||this.$el).find(".empty-history-message")},renderHdas:function(m){m=m||this.$el;var l=this,o={},n=this.model.hdas.getVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"),this.filters);this.$datasetsList(m).empty();if(n.length){n.each(function(q){var p=q.get("id"),r=l._createHdaView(q);o[p]=r;if(_.contains(l.selectedHdaIds,p)){r.selected=true}l.attachHdaView(r.render(),m)})}this.hdaViews=o;this._renderEmptyMsg(m);return this.hdaViews},_createHdaView:function(m){var l=m.get("id"),n=new this.HDAViewClass({model:m,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[l],hasUser:this.model.ownedByCurrUser(),logger:this.logger});this._setUpHdaListeners(n);return n},_setUpHdaListeners:function(m){var l=this;m.on("error",function(o,q,n,p){l.errorHandler(o,q,n,p)});m.on("body-expanded",function(n){l.storage.addExpandedHda(n)});m.on("body-collapsed",function(n){l.storage.removeExpandedHda(n)});return this},attachHdaView:function(n,m){m=m||this.$el;var l=this.$datasetsList(m);l.prepend(n.$el);return this},addHdaView:function(o){this.log("add."+this,o);var m=this;if(!o.isVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"))){return m}$({}).queue([function n(q){var p=m.$emptyMessage();if(p.is(":visible")){p.fadeOut(m.fxSpeed,q)}else{q()}},function l(p){var q=m._createHdaView(o);m.hdaViews[o.id]=q;q.render().$el.hide();m.scrollToTop();m.attachHdaView(q);q.$el.slideDown(m.fxSpeed)}]);return m},refreshHdas:function(m,l){if(this.model){return this.model.refresh(m,l)}return $.when()},events:{"click .message-container":"clearMessages","click .history-search-btn":"toggleSearchControls"},collapseAllHdaBodies:function(){_.each(this.hdaViews,function(l){l.toggleBodyVisibility(null,false)});this.storage.set("expandedHdas",{});return this},toggleShowDeleted:function(l){l=(l!==undefined)?(l):(!this.storage.get("show_deleted"));this.storage.set("show_deleted",l);this.renderHdas();return this.storage.get("show_deleted")},toggleShowHidden:function(l){l=(l!==undefined)?(l):(!this.storage.get("show_hidden"));this.storage.set("show_hidden",l);this.renderHdas();return this.storage.get("show_hidden")},_setUpSearchInput:function(m){var n=this,o=".history-search-input";function l(p){if(n.model.hdas.haveDetails()){n.searchHdas(p);return}n.$el.find(o).searchInput("toggle-loading");n.model.hdas.fetchAllDetails({silent:true}).always(function(){n.$el.find(o).searchInput("toggle-loading")}).done(function(){n.searchHdas(p)})}m.searchInput({initialVal:n.searchFor,name:"history-search",placeholder:"search datasets",classes:"history-search",onfirstsearch:l,onsearch:_.bind(this.searchHdas,this),onclear:_.bind(this.clearHdaSearch,this)});return m},toggleSearchControls:function(n,l){var m=this.$el.find(".history-search-controls"),o=(jQuery.type(n)==="number")?(n):(this.fxSpeed);l=(l!==undefined)?(l):(!m.is(":visible"));if(l){m.slideDown(o,function(){$(this).find("input").focus()})}else{m.slideUp(o)}return l},searchHdas:function(l){var m=this;this.searchFor=l;this.filters=[function(n){return n.matchesAll(m.searchFor)}];this.trigger("search:searching",l,this);this.renderHdas();return this},clearHdaSearch:function(l){this.searchFor="";this.filters=[];this.trigger("search:clear",this);this.renderHdas();return this},_showLoadingIndicator:function(m,l,n){l=(l!==undefined)?(l):(this.fxSpeed);if(!this.indicator){this.indicator=new LoadingIndicator(this.$el,this.$el.parent())}if(!this.$el.is(":visible")){this.indicator.show(0,n)}else{this.$el.fadeOut(l);this.indicator.show(m,l,n)}},_hideLoadingIndicator:function(l,m){l=(l!==undefined)?(l):(this.fxSpeed);if(this.indicator){this.indicator.hide(l,m)}},displayMessage:function(q,r,p){var n=this;this.scrollToTop();var o=this.$messages(),l=$("<div/>").addClass(q+"message").html(r);if(!_.isEmpty(p)){var m=$('<a href="javascript:void(0)">Details</a>').click(function(){Galaxy.modal.show(n._messageToModalOptions(q,r,p));return false});l.append(" ",m)}return o.html(l)},_messageToModalOptions:function(p,r,o){var l=this,q=$("<div/>"),n={title:"Details"};function m(s){s=_.omit(s,_.functions(s));return["<table>",_.map(s,function(u,t){u=(_.isObject(u))?(m(u)):(u);return'<tr><td style="vertical-align: top; color: grey">'+t+'</td><td style="padding-left: 8px">'+u+"</td></tr>"}).join(""),"</table>"].join("")}if(_.isObject(o)){n.body=q.append(m(o))}else{n.body=q.html(o)}n.buttons={Ok:function(){Galaxy.modal.hide();l.clearMessages()}};return n},clearMessages:function(){this.$messages().empty();return this},scrollPosition:function(){return this.$container().scrollTop()},scrollTo:function(l){this.$container().scrollTop(l);return this},scrollToTop:function(){this.$container().scrollTop(0);return this},scrollToId:function(m){if((!m)||(!this.hdaViews[m])){return this}var l=this.hdaViews[m];this.scrollTo(l.el.offsetTop);return this},scrollToHid:function(l){var m=this.model.hdas.getByHid(l);if(!m){return this}return this.scrollToId(m.id)},toString:function(){return"ReadOnlyHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});var k=['<div class="history-controls">','<div class="history-search-controls">','<div class="history-search-input"></div>',"</div>",'<div class="history-title">',"<% if( history.name ){ %>",'<div class="history-name"><%= history.name %></div>',"<% } %>","</div>",'<div class="history-subtitle clear">',"<% if( history.nice_size ){ %>",'<div class="history-size"><%= history.nice_size %></div>',"<% } %>",'<div class="history-secondary-actions"></div>',"</div>","<% if( history.deleted ){ %>",'<div class="warningmessagesmall"><strong>',d("You are currently viewing a deleted history!"),"</strong></div>","<% } %>",'<div class="message-container">',"<% if( history.message ){ %>",'<div class="<%= history.status %>message"><%= history.message %></div>',"<% } %>","</div>",'<div class="quota-message errormessage">',d("You are over your disk quota"),". ",d("Tool execution is on hold until your disk usage drops below your allocated quota"),".","</div>",'<div class="tags-display"></div>','<div class="annotation-display"></div>','<div class="history-dataset-actions">','<div class="btn-group">','<button class="history-select-all-datasets-btn btn btn-default"','data-mode="select">',d("All"),"</button>",'<button class="history-deselect-all-datasets-btn btn btn-default"','data-mode="select">',d("None"),"</button>","</div>",'<button class="history-dataset-action-popup-btn btn btn-default">',d("For all selected"),"...</button>","</div>","</div>",'<div class="datasets-list"></div>','<div class="empty-history-message infomessagesmall">',d("This history is empty"),"</div>"].join("");j.templates={historyPanel:function(l){return _.template(k,l,{variable:"history"})}};return{ReadOnlyHistoryPanel:j}});
\ No newline at end of file
+define(["mvc/history/history-model","mvc/collection/dataset-collection-base","mvc/dataset/hda-base","mvc/user/user-model","mvc/base-mvc","utils/localization"],function(g,k,b,a,f,d){var i=f.SessionStorageModel.extend({defaults:{expandedHdas:{},show_deleted:false,show_hidden:false},addExpandedHda:function(n){var m="expandedHdas";this.save(m,_.extend(this.get(m),_.object([n],[true])))},removeExpandedHda:function(n){var m="expandedHdas";this.save(m,_.omit(this.get(m),n))},toString:function(){return"HistoryPrefs("+this.id+")"}});i.storageKeyPrefix="history:";i.historyStorageKey=function e(m){if(!m){throw new Error("HistoryPrefs.historyStorageKey needs valid id: "+m)}return(i.storageKeyPrefix+m)};i.get=function c(m){return new i({id:i.historyStorageKey(m)})};i.clearAll=function h(n){for(var m in sessionStorage){if(m.indexOf(i.storageKeyPrefix)===0){sessionStorage.removeItem(m)}}};var j=Backbone.View.extend(f.LoggableMixin).extend({HDAViewClass:b.HDABaseView,tagName:"div",className:"history-panel",fxSpeed:"fast",emptyMsg:d("This history is empty"),noneFoundMsg:d("No matching datasets found"),initialize:function(m){m=m||{};if(m.logger){this.logger=m.logger}this.log(this+".initialize:",m);this.linkTarget=m.linkTarget||"_blank";this.fxSpeed=_.has(m,"fxSpeed")?(m.fxSpeed):(this.fxSpeed);this.filters=[];this.searchFor="";this.findContainerFn=m.findContainerFn;this.hdaViews={};this.indicator=new LoadingIndicator(this.$el);this._setUpListeners();var n=_.pick(m,"initiallyExpanded","show_deleted","show_hidden");this.setModel(this.model,n,false);if(m.onready){m.onready.call(this)}},_setUpListeners:function(){this.on("error",function(n,q,m,p,o){this.errorHandler(n,q,m,p,o)});this.on("loading-history",function(){this._showLoadingIndicator("loading history...",40)});this.on("loading-done",function(){this._hideLoadingIndicator(40);if(_.isEmpty(this.hdaViews)){this.trigger("empty-history",this)}});this.once("rendered",function(){this.trigger("rendered:initial",this);return false});if(this.logger){this.on("all",function(m){this.log(this+"",arguments)},this)}return this},errorHandler:function(o,r,n,q,p){console.error(o,r,n,q,p);if(r&&r.status===0&&r.readyState===0){}else{if(r&&r.status===502){}else{var m=this._parseErrorMessage(o,r,n,q,p);if(!this.$messages().is(":visible")){this.once("rendered",function(){this.displayMessage("error",m.message,m.details)})}else{this.displayMessage("error",m.message,m.details)}}}},_parseErrorMessage:function(p,t,o,s,r){var n=Galaxy.currUser,m={message:this._bePolite(s),details:{user:(n instanceof a.User)?(n.toJSON()):(n+""),source:(p instanceof Backbone.Model)?(p.toJSON()):(p+""),xhr:t,options:(t)?(_.omit(o,"xhr")):(o)}};_.extend(m.details,r||{});if(t&&_.isFunction(t.getAllResponseHeaders)){var q=t.getAllResponseHeaders();q=_.compact(q.split("\n"));q=_.map(q,function(u){return u.split(": ")});m.details.xhr.responseHeaders=_.object(q)}return m},_bePolite:function(m){m=m||d("An error occurred while getting updates from the server");return m+". "+d("Please contact a Galaxy administrator if the problem persists")+"."},loadHistoryWithHDADetails:function(o,n,m,q){var p=function(r){return _.keys(i.get(r.id).get("expandedHdas"))};return this.loadHistory(o,n,m,q,p)},loadHistory:function(p,o,n,s,q){var m=this;o=o||{};m.trigger("loading-history",m);var r=g.History.getHistoryData(p,{historyFn:n,hdaFn:s,hdaDetailIds:o.initiallyExpanded||q});return m._loadHistoryFromXHR(r,o).fail(function(v,t,u){m.trigger("error",m,v,o,d("An error was encountered while "+t),{historyId:p,history:u||{}})}).always(function(){m.trigger("loading-done",m)})},_loadHistoryFromXHR:function(o,n){var m=this;o.then(function(p,q){m.JSONToModel(p,q,n)});o.fail(function(q,p){m.render()});return o},JSONToModel:function(p,m,n){this.log("JSONToModel:",p,m,n);n=n||{};var o=new g.History(p,m,n);this.setModel(o);return this},setModel:function(n,m,o){m=m||{};o=(o!==undefined)?(o):(true);this.log("setModel:",n,m,o);this.freeModel();this.selectedHdaIds=[];if(n){this.model=n;if(this.logger){this.model.logger=this.logger}this._setUpWebStorage(m.initiallyExpanded,m.show_deleted,m.show_hidden);this._setUpModelEventHandlers();this.trigger("new-model",this)}if(o){this.render()}return this},freeModel:function(){if(this.model){this.model.clearUpdateTimeout();this.stopListening(this.model);this.stopListening(this.model.hdas)}this.freeHdaViews();return this},freeHdaViews:function(){this.hdaViews={};return this},_setUpWebStorage:function(n,m,o){this.storage=new i({id:i.historyStorageKey(this.model.get("id"))});if(_.isObject(n)){this.storage.set("exandedHdas",n)}if(_.isBoolean(m)){this.storage.set("show_deleted",m)}if(_.isBoolean(o)){this.storage.set("show_hidden",o)}this.trigger("new-storage",this.storage,this);this.log(this+" (init'd) storage:",this.storage.get());return this},_setUpModelEventHandlers:function(){this.model.hdas.on("add",this.addContentView,this);this.model.on("error error:hdas",function(n,p,m,o){this.errorHandler(n,p,m,o)},this);return this},render:function(o,p){this.log("render:",o,p);o=(o===undefined)?(this.fxSpeed):(o);var m=this,n;if(this.model){n=this.renderModel()}else{n=this.renderWithoutModel()}$(m).queue("fx",[function(q){if(o&&m.$el.is(":visible")){m.$el.fadeOut(o,q)}else{q()}},function(q){m.$el.empty();if(n){m.$el.append(n.children())}q()},function(q){if(o&&!m.$el.is(":visible")){m.$el.fadeIn(o,q)}else{q()}},function(q){if(p){p.call(this)}m.trigger("rendered",this);q()}]);return this},renderWithoutModel:function(){var m=$("<div/>"),n=$("<div/>").addClass("message-container").css({margin:"4px"});return m.append(n)},renderModel:function(){var m=$("<div/>");m.append(j.templates.historyPanel(this.model.toJSON()));this.$emptyMessage(m).text(this.emptyMsg);m.find(".history-secondary-actions").prepend(this._renderSearchButton());this._setUpBehaviours(m);this.renderHdas(m);return m},_renderEmptyMsg:function(o){var n=this,m=n.$emptyMessage(o);if(!_.isEmpty(n.hdaViews)){m.hide()}else{if(n.searchFor){m.text(n.noneFoundMsg).show()}else{m.text(n.emptyMsg).show()}}return this},_renderSearchButton:function(m){return faIconButton({title:d("Search datasets"),classes:"history-search-btn",faIcon:"fa-search"})},_setUpBehaviours:function(m){m=m||this.$el;m.find("[title]").tooltip({placement:"bottom"});this._setUpSearchInput(m.find(".history-search-controls .history-search-input"));return this},$container:function(){return(this.findContainerFn)?(this.findContainerFn.call(this)):(this.$el.parent())},$datasetsList:function(m){return(m||this.$el).find(".datasets-list")},$messages:function(m){return(m||this.$el).find(".message-container")},$emptyMessage:function(m){return(m||this.$el).find(".empty-history-message")},renderHdas:function(n){n=n||this.$el;var m=this,p={},o=this.model.hdas.getVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"),this.filters);this.$datasetsList(n).empty();if(o.length){o.each(function(r){var q=r.get("id"),s=m._createContentView(r);p[q]=s;if(_.contains(m.selectedHdaIds,q)){s.selected=true}m.attachContentView(s.render(),n)})}this.hdaViews=p;this._renderEmptyMsg(n);return this.hdaViews},_createContentView:function(n){var m=n.get("id"),p=n.get("history_content_type"),o=null;if(p=="dataset"){o=new this.HDAViewClass({model:n,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[m],hasUser:this.model.ownedByCurrUser(),logger:this.logger})}else{o=new k.DatasetCollectionBaseView({model:n,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[m],hasUser:this.model.ownedByCurrUser(),logger:this.logger})}this._setUpHdaListeners(o);return o},_setUpHdaListeners:function(n){var m=this;n.on("error",function(p,r,o,q){m.errorHandler(p,r,o,q)});n.on("body-expanded",function(o){m.storage.addExpandedHda(o)});n.on("body-collapsed",function(o){m.storage.removeExpandedHda(o)});return this},attachContentView:function(o,n){n=n||this.$el;var m=this.$datasetsList(n);m.prepend(o.$el);return this},addContentView:function(p){this.log("add."+this,p);var n=this;if(!p.isVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"))){return n}$({}).queue([function o(r){var q=n.$emptyMessage();if(q.is(":visible")){q.fadeOut(n.fxSpeed,r)}else{r()}},function m(q){var r=n._createContentView(p);n.hdaViews[p.id]=r;r.render().$el.hide();n.scrollToTop();n.attachContentView(r);r.$el.slideDown(n.fxSpeed)}]);return n},refreshContents:function(n,m){if(this.model){return this.model.refresh(n,m)}return $.when()},events:{"click .message-container":"clearMessages","click .history-search-btn":"toggleSearchControls"},collapseAllHdaBodies:function(){_.each(this.hdaViews,function(m){m.toggleBodyVisibility(null,false)});this.storage.set("expandedHdas",{});return this},toggleShowDeleted:function(m){m=(m!==undefined)?(m):(!this.storage.get("show_deleted"));this.storage.set("show_deleted",m);this.renderHdas();return this.storage.get("show_deleted")},toggleShowHidden:function(m){m=(m!==undefined)?(m):(!this.storage.get("show_hidden"));this.storage.set("show_hidden",m);this.renderHdas();return this.storage.get("show_hidden")},_setUpSearchInput:function(n){var o=this,p=".history-search-input";function m(q){if(o.model.hdas.haveDetails()){o.searchHdas(q);return}o.$el.find(p).searchInput("toggle-loading");o.model.hdas.fetchAllDetails({silent:true}).always(function(){o.$el.find(p).searchInput("toggle-loading")}).done(function(){o.searchHdas(q)})}n.searchInput({initialVal:o.searchFor,name:"history-search",placeholder:"search datasets",classes:"history-search",onfirstsearch:m,onsearch:_.bind(this.searchHdas,this),onclear:_.bind(this.clearHdaSearch,this)});return n},toggleSearchControls:function(o,m){var n=this.$el.find(".history-search-controls"),p=(jQuery.type(o)==="number")?(o):(this.fxSpeed);m=(m!==undefined)?(m):(!n.is(":visible"));if(m){n.slideDown(p,function(){$(this).find("input").focus()})}else{n.slideUp(p)}return m},searchHdas:function(m){var n=this;this.searchFor=m;this.filters=[function(o){return o.matchesAll(n.searchFor)}];this.trigger("search:searching",m,this);this.renderHdas();return this},clearHdaSearch:function(m){this.searchFor="";this.filters=[];this.trigger("search:clear",this);this.renderHdas();return this},_showLoadingIndicator:function(n,m,o){m=(m!==undefined)?(m):(this.fxSpeed);if(!this.indicator){this.indicator=new LoadingIndicator(this.$el,this.$el.parent())}if(!this.$el.is(":visible")){this.indicator.show(0,o)}else{this.$el.fadeOut(m);this.indicator.show(n,m,o)}},_hideLoadingIndicator:function(m,n){m=(m!==undefined)?(m):(this.fxSpeed);if(this.indicator){this.indicator.hide(m,n)}},displayMessage:function(r,s,q){var o=this;this.scrollToTop();var p=this.$messages(),m=$("<div/>").addClass(r+"message").html(s);if(!_.isEmpty(q)){var n=$('<a href="javascript:void(0)">Details</a>').click(function(){Galaxy.modal.show(o._messageToModalOptions(r,s,q));return false});m.append(" ",n)}return p.html(m)},_messageToModalOptions:function(q,s,p){var m=this,r=$("<div/>"),o={title:"Details"};function n(t){t=_.omit(t,_.functions(t));return["<table>",_.map(t,function(v,u){v=(_.isObject(v))?(n(v)):(v);return'<tr><td style="vertical-align: top; color: grey">'+u+'</td><td style="padding-left: 8px">'+v+"</td></tr>"}).join(""),"</table>"].join("")}if(_.isObject(p)){o.body=r.append(n(p))}else{o.body=r.html(p)}o.buttons={Ok:function(){Galaxy.modal.hide();m.clearMessages()}};return o},clearMessages:function(){this.$messages().empty();return this},scrollPosition:function(){return this.$container().scrollTop()},scrollTo:function(m){this.$container().scrollTop(m);return this},scrollToTop:function(){this.$container().scrollTop(0);return this},scrollToId:function(n){if((!n)||(!this.hdaViews[n])){return this}var m=this.hdaViews[n];this.scrollTo(m.el.offsetTop);return this},scrollToHid:function(m){var n=this.model.hdas.getByHid(m);if(!n){return this}return this.scrollToId(n.id)},toString:function(){return"ReadOnlyHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});var l=['<div class="history-controls">','<div class="history-search-controls">','<div class="history-search-input"></div>',"</div>",'<div class="history-title">',"<% if( history.name ){ %>",'<div class="history-name"><%= history.name %></div>',"<% } %>","</div>",'<div class="history-subtitle clear">',"<% if( history.nice_size ){ %>",'<div class="history-size"><%= history.nice_size %></div>',"<% } %>",'<div class="history-secondary-actions"></div>',"</div>","<% if( history.deleted ){ %>",'<div class="warningmessagesmall"><strong>',d("You are currently viewing a deleted history!"),"</strong></div>","<% } %>",'<div class="message-container">',"<% if( history.message ){ %>",'<div class="<%= history.status %>message"><%= history.message %></div>',"<% } %>","</div>",'<div class="quota-message errormessage">',d("You are over your disk quota"),". ",d("Tool execution is on hold until your disk usage drops below your allocated quota"),".","</div>",'<div class="tags-display"></div>','<div class="annotation-display"></div>','<div class="history-dataset-actions">','<div class="btn-group">','<button class="history-select-all-datasets-btn btn btn-default"','data-mode="select">',d("All"),"</button>",'<button class="history-deselect-all-datasets-btn btn btn-default"','data-mode="select">',d("None"),"</button>","</div>",'<button class="history-dataset-action-popup-btn btn btn-default">',d("For all selected"),"...</button>","</div>","</div>",'<div class="datasets-list"></div>','<div class="empty-history-message infomessagesmall">',d("This history is empty"),"</div>"].join("");j.templates={historyPanel:function(m){return _.template(l,m,{variable:"history"})}};return{ReadOnlyHistoryPanel:j}});
\ No newline at end of file
diff -r 40054ac6f07bce479affd6b39772640e9321e57b -r f7baaf52e94c57cc1a3f7297c191a3e19826293d static/scripts/packed/mvc/upload/upload-view.js
--- a/static/scripts/packed/mvc/upload/upload-view.js
+++ b/static/scripts/packed/mvc/upload/upload-view.js
@@ -1,1 +1,1 @@
-define(["utils/utils","mvc/upload/upload-button","mvc/upload/upload-model","mvc/upload/upload-row","mvc/upload/upload-ftp","mvc/ui/ui-popover","mvc/ui/ui-modal","mvc/ui","utils/uploadbox"],function(f,e,c,b,g,d,a){return Backbone.View.extend({options:{nginx_upload_path:""},modal:null,ui_button:null,uploadbox:null,current_history:null,upload_size:0,list_extensions:[],list_genomes:[],auto:{id:"auto",text:"Auto-detect",description:"This system will try to detect the file type automatically. If your file is not detected properly as one of the known formats, it most likely means that it has some format problems (e.g., different number of columns on different rows). You can still coerce the system to set your data to the format you think it should be. You can also upload compressed files, which will automatically be decompressed."},collection:new c.Collection(),ftp:null,counter:{announce:0,success:0,error:0,running:0,reset:function(){this.announce=this.success=this.error=this.running=0}},initialize:function(i){var h=this;if(i){this.options=_.defaults(i,this.options)}if(!Galaxy.currHistoryPanel||!Galaxy.currHistoryPanel.model){window.setTimeout(function(){h.initialize()},500);return}this.ui_button=new e.Model({icon:"fa-upload",tooltip:"Download from URL or upload files from disk",label:"Load Data",onclick:function(j){if(j){h._eventShow(j)}},onunload:function(){if(h.counter.running>0){return"Several uploads are still processing."}}});$("#left .unified-panel-header-inner").append((new e.View(this.ui_button)).$el);var h=this;f.get(galaxy_config.root+"api/datatypes?extension_only=False",function(j){for(key in j){h.list_extensions.push({id:j[key].extension,text:j[key].extension,description:j[key].description,description_url:j[key].description_url})}h.list_extensions.sort(function(l,k){return l.id>k.id?1:l.id<k.id?-1:0});if(!h.options.datatypes_disable_auto){h.list_extensions.unshift(h.auto)}});f.get(galaxy_config.root+"api/genomes",function(j){for(key in j){h.list_genomes.push({id:j[key][1],text:j[key][0]})}h.list_genomes.sort(function(l,k){return l.id>k.id?1:l.id<k.id?-1:0})});this.collection.on("remove",function(j){h._eventRemove(j)});this.collection.on("change:genome",function(k){var j=k.get("genome");h.collection.each(function(l){if(l.get("status")=="init"&&l.get("genome")=="?"){l.set("genome",j)}})})},_eventShow:function(j){j.preventDefault();if(!this.modal){var h=this;this.modal=new a.View({title:"Download data directly from web or upload files from your disk",body:this._template("upload-box","upload-info"),buttons:{"Choose local file":function(){h.uploadbox.select()},"Choose FTP file":function(){h._eventFtp()},"Paste/Fetch data":function(){h._eventCreate()},Start:function(){h._eventStart()},Pause:function(){h._eventStop()},Reset:function(){h._eventReset()},Close:function(){h.modal.hide()},},height:"400",width:"900",closing_events:true});this.setElement("#upload-box");var h=this;this.uploadbox=this.$el.uploadbox({announce:function(k,l,m){h._eventAnnounce(k,l,m)},initialize:function(k,l,m){return h._eventInitialize(k,l,m)},progress:function(k,l,m){h._eventProgress(k,l,m)},success:function(k,l,m){h._eventSuccess(k,l,m)},error:function(k,l,m){h._eventError(k,l,m)},complete:function(){h._eventComplete()}});var i=this.modal.getButton("Choose FTP file");this.ftp=new d.View({title:"FTP files",container:i})}this.modal.show();this._updateUser();this._updateScreen()},_eventRemove:function(i){var h=i.get("status");if(h=="success"){this.counter.success--}else{if(h=="error"){this.counter.error--}else{this.counter.announce--}}this._updateScreen();this.uploadbox.remove(i.id)},_eventAnnounce:function(h,i,k){this.counter.announce++;this._updateScreen();var j=new b(this,{id:h,file_name:i.name,file_size:i.size,file_mode:i.mode,file_path:i.path});this.collection.add(j.model);$(this.el).find("tbody:first").append(j.$el);j.render()},_eventInitialize:function(m,j,s){var k=this.collection.get(m);k.set("status","running");var o=k.get("file_name");var n=k.get("file_path");var h=k.get("file_mode");var p=k.get("extension");var r=k.get("genome");var q=k.get("url_paste");var l=k.get("space_to_tabs");var i=k.get("to_posix_lines");if(!q&&!(j.size>0)){return null}this.uploadbox.configure({url:this.options.nginx_upload_path});if(h=="local"){this.uploadbox.configure({paramname:"files_0|file_data"})}else{this.uploadbox.configure({paramname:null})}tool_input={};if(h=="new"){tool_input["files_0|url_paste"]=q}if(h=="ftp"){tool_input["files_0|ftp_files"]=n}tool_input.dbkey=r;tool_input.file_type=p;tool_input["files_0|type"]="upload_dataset";tool_input.space_to_tabs=l;tool_input.to_posix_lines=i;data={};data.history_id=this.current_history;data.tool_id="upload1";data.inputs=JSON.stringify(tool_input);return data},_eventProgress:function(i,j,h){var k=this.collection.get(i);k.set("percentage",h);this.ui_button.set("percentage",this._upload_percentage(h,j.size))},_eventSuccess:function(i,j,l){var k=this.collection.get(i);k.set("percentage",100);k.set("status","success");var h=k.get("file_size");this.ui_button.set("percentage",this._upload_percentage(100,h));this.upload_completed+=h*100;this.counter.announce--;this.counter.success++;this._updateScreen();Galaxy.currHistoryPanel.refreshHdas()},_eventError:function(h,i,k){var j=this.collection.get(h);j.set("percentage",100);j.set("status","error");j.set("info",k);this.ui_button.set("percentage",this._upload_percentage(100,i.size));this.ui_button.set("status","danger");this.upload_completed+=i.size*100;this.counter.announce--;this.counter.error++;this._updateScreen()},_eventComplete:function(){this.collection.each(function(h){if(h.get("status")=="queued"){h.set("status","init")}});this.counter.running=0;this._updateScreen()},_eventFtp:function(){if(!this.ftp.visible){this.ftp.empty();this.ftp.append((new g(this)).$el);this.ftp.show()}else{this.ftp.hide()}},_eventCreate:function(){this.uploadbox.add([{name:"New File",size:0,mode:"new"}])},_eventStart:function(){if(this.counter.announce==0||this.counter.running>0){return}var h=this;this.upload_size=0;this.upload_completed=0;this.collection.each(function(i){if(i.get("status")=="init"){i.set("status","queued");h.upload_size+=i.get("file_size")}});this.ui_button.set("percentage",0);this.ui_button.set("status","success");this.counter.running=this.counter.announce;this._updateScreen();this.uploadbox.start()},_eventStop:function(){if(this.counter.running==0){return}this.ui_button.set("status","info");this.uploadbox.stop();$("#upload-info").html("Queue will pause after completing the current file...")},_eventReset:function(){if(this.counter.running==0){this.collection.reset();this.counter.reset();this._updateScreen();this.uploadbox.reset();this.ui_button.set("percentage",0)}},_updateUser:function(){this.current_user=Galaxy.currUser.get("id");this.current_history=null;if(this.current_user){this.current_history=Galaxy.currHistoryPanel.model.get("id")}},_updateScreen:function(){if(this.counter.announce==0){if(this.uploadbox.compatible()){message="You can Drag & Drop files into this box."}else{message="Unfortunately, your browser does not support multiple file uploads or drag&drop.<br>Some supported browsers are: Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+."}}else{if(this.counter.running==0){message="You added "+this.counter.announce+" file(s) to the queue. Add more files or click 'Start' to proceed."}else{message="Please wait..."+this.counter.announce+" out of "+this.counter.running+" remaining."}}$("#upload-info").html(message);if(this.counter.running==0&&this.counter.announce+this.counter.success+this.counter.error>0){this.modal.enableButton("Reset")}else{this.modal.disableButton("Reset")}if(this.counter.running==0&&this.counter.announce>0){this.modal.enableButton("Start")}else{this.modal.disableButton("Start")}if(this.counter.running>0){this.modal.enableButton("Pause")}else{this.modal.disableButton("Pause")}if(this.counter.running==0){this.modal.enableButton("Choose local file");this.modal.enableButton("Choose FTP file");this.modal.enableButton("Paste/Fetch data")}else{this.modal.disableButton("Choose local file");this.modal.disableButton("Choose FTP file");this.modal.disableButton("Paste/Fetch data")}if(this.current_user&&this.options.ftp_upload_dir&&this.options.ftp_upload_site){this.modal.showButton("Choose FTP file")}else{this.modal.hideButton("Choose FTP file")}if(this.counter.announce+this.counter.success+this.counter.error>0){$(this.el).find("#upload-table").show()}else{$(this.el).find("#upload-table").hide()}},_upload_percentage:function(h,i){return(this.upload_completed+(h*i))/this.upload_size},_template:function(i,h){return'<div id="'+i+'" class="upload-box"><table id="upload-table" class="table table-striped" style="display: none;"><thead><tr><th>Name</th><th>Size</th><th>Type</th><th>Genome</th><th>Settings</th><th>Status</th><th></th></tr></thead><tbody></tbody></table></div><h6 id="'+h+'" class="upload-info"></h6>'}})});
\ No newline at end of file
+define(["utils/utils","mvc/upload/upload-button","mvc/upload/upload-model","mvc/upload/upload-row","mvc/upload/upload-ftp","mvc/ui/ui-popover","mvc/ui/ui-modal","mvc/ui","utils/uploadbox"],function(f,e,c,b,g,d,a){return Backbone.View.extend({options:{nginx_upload_path:""},modal:null,ui_button:null,uploadbox:null,current_history:null,upload_size:0,list_extensions:[],list_genomes:[],auto:{id:"auto",text:"Auto-detect",description:"This system will try to detect the file type automatically. If your file is not detected properly as one of the known formats, it most likely means that it has some format problems (e.g., different number of columns on different rows). You can still coerce the system to set your data to the format you think it should be. You can also upload compressed files, which will automatically be decompressed."},collection:new c.Collection(),ftp:null,counter:{announce:0,success:0,error:0,running:0,reset:function(){this.announce=this.success=this.error=this.running=0}},initialize:function(i){var h=this;if(i){this.options=_.defaults(i,this.options)}if(!Galaxy.currHistoryPanel||!Galaxy.currHistoryPanel.model){window.setTimeout(function(){h.initialize()},500);return}this.ui_button=new e.Model({icon:"fa-upload",tooltip:"Download from URL or upload files from disk",label:"Load Data",onclick:function(j){if(j){h._eventShow(j)}},onunload:function(){if(h.counter.running>0){return"Several uploads are still processing."}}});$("#left .unified-panel-header-inner").append((new e.View(this.ui_button)).$el);var h=this;f.get(galaxy_config.root+"api/datatypes?extension_only=False",function(j){for(key in j){h.list_extensions.push({id:j[key].extension,text:j[key].extension,description:j[key].description,description_url:j[key].description_url})}h.list_extensions.sort(function(l,k){return l.id>k.id?1:l.id<k.id?-1:0});if(!h.options.datatypes_disable_auto){h.list_extensions.unshift(h.auto)}});f.get(galaxy_config.root+"api/genomes",function(j){for(key in j){h.list_genomes.push({id:j[key][1],text:j[key][0]})}h.list_genomes.sort(function(l,k){return l.id>k.id?1:l.id<k.id?-1:0})});this.collection.on("remove",function(j){h._eventRemove(j)});this.collection.on("change:genome",function(k){var j=k.get("genome");h.collection.each(function(l){if(l.get("status")=="init"&&l.get("genome")=="?"){l.set("genome",j)}})})},_eventShow:function(j){j.preventDefault();if(!this.modal){var h=this;this.modal=new a.View({title:"Download data directly from web or upload files from your disk",body:this._template("upload-box","upload-info"),buttons:{"Choose local file":function(){h.uploadbox.select()},"Choose FTP file":function(){h._eventFtp()},"Paste/Fetch data":function(){h._eventCreate()},Start:function(){h._eventStart()},Pause:function(){h._eventStop()},Reset:function(){h._eventReset()},Close:function(){h.modal.hide()},},height:"400",width:"900",closing_events:true});this.setElement("#upload-box");var h=this;this.uploadbox=this.$el.uploadbox({announce:function(k,l,m){h._eventAnnounce(k,l,m)},initialize:function(k,l,m){return h._eventInitialize(k,l,m)},progress:function(k,l,m){h._eventProgress(k,l,m)},success:function(k,l,m){h._eventSuccess(k,l,m)},error:function(k,l,m){h._eventError(k,l,m)},complete:function(){h._eventComplete()}});var i=this.modal.getButton("Choose FTP file");this.ftp=new d.View({title:"FTP files",container:i})}this.modal.show();this._updateUser();this._updateScreen()},_eventRemove:function(i){var h=i.get("status");if(h=="success"){this.counter.success--}else{if(h=="error"){this.counter.error--}else{this.counter.announce--}}this._updateScreen();this.uploadbox.remove(i.id)},_eventAnnounce:function(h,i,k){this.counter.announce++;this._updateScreen();var j=new b(this,{id:h,file_name:i.name,file_size:i.size,file_mode:i.mode,file_path:i.path});this.collection.add(j.model);$(this.el).find("tbody:first").append(j.$el);j.render()},_eventInitialize:function(m,j,s){var k=this.collection.get(m);k.set("status","running");var o=k.get("file_name");var n=k.get("file_path");var h=k.get("file_mode");var p=k.get("extension");var r=k.get("genome");var q=k.get("url_paste");var l=k.get("space_to_tabs");var i=k.get("to_posix_lines");if(!q&&!(j.size>0)){return null}this.uploadbox.configure({url:this.options.nginx_upload_path});if(h=="local"){this.uploadbox.configure({paramname:"files_0|file_data"})}else{this.uploadbox.configure({paramname:null})}tool_input={};if(h=="new"){tool_input["files_0|url_paste"]=q}if(h=="ftp"){tool_input["files_0|ftp_files"]=n}tool_input.dbkey=r;tool_input.file_type=p;tool_input["files_0|type"]="upload_dataset";tool_input.space_to_tabs=l;tool_input.to_posix_lines=i;data={};data.history_id=this.current_history;data.tool_id="upload1";data.inputs=JSON.stringify(tool_input);return data},_eventProgress:function(i,j,h){var k=this.collection.get(i);k.set("percentage",h);this.ui_button.set("percentage",this._upload_percentage(h,j.size))},_eventSuccess:function(i,j,l){var k=this.collection.get(i);k.set("percentage",100);k.set("status","success");var h=k.get("file_size");this.ui_button.set("percentage",this._upload_percentage(100,h));this.upload_completed+=h*100;this.counter.announce--;this.counter.success++;this._updateScreen();Galaxy.currHistoryPanel.refreshContents()},_eventError:function(h,i,k){var j=this.collection.get(h);j.set("percentage",100);j.set("status","error");j.set("info",k);this.ui_button.set("percentage",this._upload_percentage(100,i.size));this.ui_button.set("status","danger");this.upload_completed+=i.size*100;this.counter.announce--;this.counter.error++;this._updateScreen()},_eventComplete:function(){this.collection.each(function(h){if(h.get("status")=="queued"){h.set("status","init")}});this.counter.running=0;this._updateScreen()},_eventFtp:function(){if(!this.ftp.visible){this.ftp.empty();this.ftp.append((new g(this)).$el);this.ftp.show()}else{this.ftp.hide()}},_eventCreate:function(){this.uploadbox.add([{name:"New File",size:0,mode:"new"}])},_eventStart:function(){if(this.counter.announce==0||this.counter.running>0){return}var h=this;this.upload_size=0;this.upload_completed=0;this.collection.each(function(i){if(i.get("status")=="init"){i.set("status","queued");h.upload_size+=i.get("file_size")}});this.ui_button.set("percentage",0);this.ui_button.set("status","success");this.counter.running=this.counter.announce;this._updateScreen();this.uploadbox.start()},_eventStop:function(){if(this.counter.running==0){return}this.ui_button.set("status","info");this.uploadbox.stop();$("#upload-info").html("Queue will pause after completing the current file...")},_eventReset:function(){if(this.counter.running==0){this.collection.reset();this.counter.reset();this._updateScreen();this.uploadbox.reset();this.ui_button.set("percentage",0)}},_updateUser:function(){this.current_user=Galaxy.currUser.get("id");this.current_history=null;if(this.current_user){this.current_history=Galaxy.currHistoryPanel.model.get("id")}},_updateScreen:function(){if(this.counter.announce==0){if(this.uploadbox.compatible()){message="You can Drag & Drop files into this box."}else{message="Unfortunately, your browser does not support multiple file uploads or drag&drop.<br>Some supported browsers are: Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+."}}else{if(this.counter.running==0){message="You added "+this.counter.announce+" file(s) to the queue. Add more files or click 'Start' to proceed."}else{message="Please wait..."+this.counter.announce+" out of "+this.counter.running+" remaining."}}$("#upload-info").html(message);if(this.counter.running==0&&this.counter.announce+this.counter.success+this.counter.error>0){this.modal.enableButton("Reset")}else{this.modal.disableButton("Reset")}if(this.counter.running==0&&this.counter.announce>0){this.modal.enableButton("Start")}else{this.modal.disableButton("Start")}if(this.counter.running>0){this.modal.enableButton("Pause")}else{this.modal.disableButton("Pause")}if(this.counter.running==0){this.modal.enableButton("Choose local file");this.modal.enableButton("Choose FTP file");this.modal.enableButton("Paste/Fetch data")}else{this.modal.disableButton("Choose local file");this.modal.disableButton("Choose FTP file");this.modal.disableButton("Paste/Fetch data")}if(this.current_user&&this.options.ftp_upload_dir&&this.options.ftp_upload_site){this.modal.showButton("Choose FTP file")}else{this.modal.hideButton("Choose FTP file")}if(this.counter.announce+this.counter.success+this.counter.error>0){$(this.el).find("#upload-table").show()}else{$(this.el).find("#upload-table").hide()}},_upload_percentage:function(h,i){return(this.upload_completed+(h*i))/this.upload_size},_template:function(i,h){return'<div id="'+i+'" class="upload-box"><table id="upload-table" class="table table-striped" style="display: none;"><thead><tr><th>Name</th><th>Size</th><th>Type</th><th>Genome</th><th>Settings</th><th>Status</th><th></th></tr></thead><tbody></tbody></table></div><h6 id="'+h+'" class="upload-info"></h6>'}})});
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: martenson: allow JS modal to execute callbacks on hide()
by commits-noreply@bitbucket.org 07 May '14
by commits-noreply@bitbucket.org 07 May '14
07 May '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/40054ac6f07b/
Changeset: 40054ac6f07b
User: martenson
Date: 2014-05-07 20:23:15
Summary: allow JS modal to execute callbacks on hide()
Affected #: 2 files
diff -r a73c02cd503ef2bec4819013517530df7045127e -r 40054ac6f07bce479affd6b39772640e9321e57b static/scripts/mvc/ui/ui-modal.js
--- a/static/scripts/mvc/ui/ui-modal.js
+++ b/static/scripts/mvc/ui/ui-modal.js
@@ -7,12 +7,13 @@
// defaults options
optionsDefault: {
- title : 'ui-modal',
- body : '',
- backdrop : true,
- height : null,
- width : null,
- closing_events : false
+ title : 'ui-modal',
+ body : '',
+ backdrop : true,
+ height : null,
+ width : null,
+ closing_events : false,
+ closing_callback : null
},
// button list
@@ -58,6 +59,9 @@
hide: function() {
this.visible = false;
this.$el.fadeOut('fast');
+ if (this.options.closing_callback){
+ this.options.closing_callback();
+ }
},
// enable buttons
diff -r a73c02cd503ef2bec4819013517530df7045127e -r 40054ac6f07bce479affd6b39772640e9321e57b static/scripts/packed/mvc/ui/ui-modal.js
--- a/static/scripts/packed/mvc/ui/ui-modal.js
+++ b/static/scripts/packed/mvc/ui/ui-modal.js
@@ -1,1 +1,1 @@
-define([],function(){var a=Backbone.View.extend({elMain:"body",optionsDefault:{title:"ui-modal",body:"",backdrop:true,height:null,width:null,closing_events:false},buttonList:{},initialize:function(b){if(b){this._create(b)}},show:function(b){this.initialize(b);if(this.options.height){this.$body.css("height",this.options.height);this.$body.css("overflow","hidden")}else{this.$body.css("max-height",$(window).height()/2)}if(this.options.width){this.$dialog.css("width",this.options.width)}if(this.visible){this.$el.show()}else{this.$el.fadeIn("fast")}this.visible=true},hide:function(){this.visible=false;this.$el.fadeOut("fast")},enableButton:function(b){var c=this.buttonList[b];this.$buttons.find("#"+c).prop("disabled",false)},disableButton:function(b){var c=this.buttonList[b];this.$buttons.find("#"+c).prop("disabled",true)},showButton:function(b){var c=this.buttonList[b];this.$buttons.find("#"+c).show()},hideButton:function(b){var c=this.buttonList[b];this.$buttons.find("#"+c).hide()},getButton:function(b){var c=this.buttonList[b];return this.$buttons.find("#"+c)},scrollTop:function(){return this.$body.scrollTop()},_create:function(d){var c=this;this.options=_.defaults(d,this.optionsDefault);if(this.options.body=="progress"){this.options.body=$('<div class="progress progress-striped active"><div class="progress-bar progress-bar-info" style="width:100%"></div></div>')}if(this.$el){this.$el.remove();$(document).off("keyup.ui-modal")}this.setElement(this._template(this.options.title));this.$dialog=(this.$el).find(".modal-dialog");this.$body=(this.$el).find(".modal-body");this.$footer=(this.$el).find(".modal-footer");this.$buttons=(this.$el).find(".buttons");this.$backdrop=(this.$el).find(".modal-backdrop");this.$body.html(this.options.body);if(!this.options.backdrop){this.$backdrop.removeClass("in")}if(this.options.buttons){this.buttonList={};var b=0;$.each(this.options.buttons,function(e,g){var f="button-"+b++;c.$buttons.append($('<button id="'+f+'"></button>').text(e).click(g)).append(" ");c.buttonList[e]=f})}else{this.$footer.hide()}$(this.elMain).append($(this.el));if(this.options.closing_events){$(document).on("keyup.ui-modal",function(f){if(f.keyCode==27){c.hide()}});this.$el.find(".modal-backdrop").on("click",function(){c.hide()})}},_template:function(b){return'<div class="modal"><div class="modal-backdrop fade in" style="z-index: -1;"></div><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><button type="button" class="close" style="display: none;">×</button><h4 class="title">'+b+'</h4></div><div class="modal-body" style="position: static;"></div><div class="modal-footer"><div class="buttons" style="float: right;"></div></div></div</div></div>'}});return{View:a}});
\ No newline at end of file
+define([],function(){var a=Backbone.View.extend({elMain:"body",optionsDefault:{title:"ui-modal",body:"",backdrop:true,height:null,width:null,closing_events:false,closing_callback:null},buttonList:{},initialize:function(b){if(b){this._create(b)}},show:function(b){this.initialize(b);if(this.options.height){this.$body.css("height",this.options.height);this.$body.css("overflow","hidden")}else{this.$body.css("max-height",$(window).height()/2)}if(this.options.width){this.$dialog.css("width",this.options.width)}if(this.visible){this.$el.show()}else{this.$el.fadeIn("fast")}this.visible=true},hide:function(){this.visible=false;this.$el.fadeOut("fast");if(this.options.closing_callback){this.options.closing_callback()}},enableButton:function(b){var c=this.buttonList[b];this.$buttons.find("#"+c).prop("disabled",false)},disableButton:function(b){var c=this.buttonList[b];this.$buttons.find("#"+c).prop("disabled",true)},showButton:function(b){var c=this.buttonList[b];this.$buttons.find("#"+c).show()},hideButton:function(b){var c=this.buttonList[b];this.$buttons.find("#"+c).hide()},getButton:function(b){var c=this.buttonList[b];return this.$buttons.find("#"+c)},scrollTop:function(){return this.$body.scrollTop()},_create:function(d){var c=this;this.options=_.defaults(d,this.optionsDefault);if(this.options.body=="progress"){this.options.body=$('<div class="progress progress-striped active"><div class="progress-bar progress-bar-info" style="width:100%"></div></div>')}if(this.$el){this.$el.remove();$(document).off("keyup.ui-modal")}this.setElement(this._template(this.options.title));this.$dialog=(this.$el).find(".modal-dialog");this.$body=(this.$el).find(".modal-body");this.$footer=(this.$el).find(".modal-footer");this.$buttons=(this.$el).find(".buttons");this.$backdrop=(this.$el).find(".modal-backdrop");this.$body.html(this.options.body);if(!this.options.backdrop){this.$backdrop.removeClass("in")}if(this.options.buttons){this.buttonList={};var b=0;$.each(this.options.buttons,function(e,g){var f="button-"+b++;c.$buttons.append($('<button id="'+f+'"></button>').text(e).click(g)).append(" ");c.buttonList[e]=f})}else{this.$footer.hide()}$(this.elMain).append($(this.el));if(this.options.closing_events){$(document).on("keyup.ui-modal",function(f){if(f.keyCode==27){c.hide()}});this.$el.find(".modal-backdrop").on("click",function(){c.hide()})}},_template:function(b){return'<div class="modal"><div class="modal-backdrop fade in" style="z-index: -1;"></div><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><button type="button" class="close" style="display: none;">×</button><h4 class="title">'+b+'</h4></div><div class="modal-body" style="position: static;"></div><div class="modal-footer"><div class="buttons" style="float: right;"></div></div></div</div></div>'}});return{View:a}});
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: martenson: data libraries: API pep8, cleanup, repaired yet another encoding bug
by commits-noreply@bitbucket.org 07 May '14
by commits-noreply@bitbucket.org 07 May '14
07 May '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/a73c02cd503e/
Changeset: a73c02cd503e
User: martenson
Date: 2014-05-07 20:10:30
Summary: data libraries: API pep8, cleanup, repaired yet another encoding bug
Affected #: 2 files
diff -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c -r a73c02cd503ef2bec4819013517530df7045127e lib/galaxy/webapps/galaxy/api/folder_contents.py
--- a/lib/galaxy/webapps/galaxy/api/folder_contents.py
+++ b/lib/galaxy/webapps/galaxy/api/folder_contents.py
@@ -13,6 +13,7 @@
import logging
log = logging.getLogger( __name__ )
+
class FolderContentsController( BaseAPIController, UsesLibraryMixin, UsesLibraryMixinItems, UsesHistoryDatasetAssociationMixin ):
"""
Class controls retrieval, creation and updating of folder contents.
@@ -23,14 +24,14 @@
"""
GET /api/folders/{encoded_folder_id}/contents
- Displays a collection (list) of a folder's contents
+ Displays a collection (list) of a folder's contents
(files and folders). Encoded folder ID is prepended
with 'F' if it is a folder as opposed to a data set
which does not have it. Full path is provided in
- response as a separate object providing data for
+ response as a separate object providing data for
breadcrumb path building.
- :param folder_id: encoded ID of the folder which
+ :param folder_id: encoded ID of the folder which
contents should be library_dataset_dict
:type folder_id: encoded string
@@ -70,7 +71,7 @@
can_add_library_item = trans.user_is_admin() or trans.app.security_agent.can_add_library_item( current_user_roles, folder )
if not ( trans.user_is_admin() or trans.app.security_agent.can_access_library_item( current_user_roles, folder, trans.user ) ):
- if folder.parent_id == None:
+ if folder.parent_id is None:
try:
library = trans.sa_session.query( trans.app.model.Library ).filter( trans.app.model.Library.table.c.root_folder_id == decoded_folder_id ).one()
except Exception:
@@ -82,14 +83,14 @@
log.warning( "SECURITY: User (id: %s) without proper access rights is trying to load folder with ID of %s" % ( trans.user.id, decoded_folder_id ) )
else:
log.warning( "SECURITY: Anonymous user without proper access rights is trying to load folder with ID of %s" % ( decoded_folder_id ) )
- raise exceptions.ObjectNotFound( 'Folder with the id provided ( %s ) was not found' % str( folder_id ) )
+ raise exceptions.ObjectNotFound( 'Folder with the id provided ( %s ) was not found' % str( folder_id ) )
else:
if trans.user:
log.warning( "SECURITY: User (id: %s) without proper access rights is trying to load folder with ID of %s" % ( trans.user.id, decoded_folder_id ) )
else:
log.warning( "SECURITY: Anonymous user without proper access rights is trying to load folder with ID of %s" % ( decoded_folder_id ) )
raise exceptions.ObjectNotFound( 'Folder with the id provided ( %s ) was not found' % str( folder_id ) )
-
+
def build_path( folder ):
"""
Search the path upwards recursively and load the whole route of
@@ -111,50 +112,45 @@
upper_folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( folder.parent_id )
path_to_root.extend( build_path( upper_folder ) )
return path_to_root
-
+
# Return the reversed path so it starts with the library node.
full_path = build_path( folder )[::-1]
folder_contents = []
update_time = ''
create_time = ''
- # Go through every accessible item in the folder and include its meta-data.
+ # Go through every accessible item (folders, datasets) in the folder and include its meta-data.
for content_item in self._load_folder_contents( trans, folder, deleted ):
- can_access = trans.app.security_agent.can_access_library_item( current_user_roles, content_item, trans.user )
- if ( can_access or ( content_item.api_type == 'folder' and trans.app.security_agent.folder_is_unrestricted( content_item ) ) ):
- return_item = {}
- encoded_id = trans.security.encode_id( content_item.id )
- update_time = content_item.update_time.strftime( "%Y-%m-%d %I:%M %p" )
- create_time = content_item.create_time.strftime( "%Y-%m-%d %I:%M %p" )
+ return_item = {}
+ encoded_id = trans.security.encode_id( content_item.id )
+ update_time = content_item.update_time.strftime( "%Y-%m-%d %I:%M %p" )
+ create_time = content_item.create_time.strftime( "%Y-%m-%d %I:%M %p" )
- # For folder return also hierarchy values
- if content_item.api_type == 'folder':
- encoded_id = 'F' + encoded_id
+ if content_item.api_type == 'folder':
+ encoded_id = 'F' + encoded_id
- # this is commented for now as it includes the items that might not be accessible
- # item_count = content_item.item_count if can_access else 0
- # return_item.update ( dict ( item_count = item_count ) )
+ if content_item.api_type == 'file':
+ library_dataset_dict = content_item.to_dict()
+ library_dataset_dict[ 'is_unrestricted' ] = trans.app.security_agent.dataset_is_public( content_item.library_dataset_dataset_association.dataset )
+ library_dataset_dict[ 'is_private' ] = trans.app.security_agent.dataset_is_private_to_user( trans, content_item )
- if content_item.api_type == 'file':
- library_dataset_dict = content_item.to_dict()
- library_dataset_dict['data_type']
- library_dataset_dict['file_size']
- library_dataset_dict['date_uploaded']
- return_item.update ( dict ( data_type = library_dataset_dict['data_type'],
- file_size = library_dataset_dict['file_size'],
- date_uploaded = library_dataset_dict['date_uploaded'] ) )
+ return_item.update( dict( data_type=library_dataset_dict[ 'data_type' ],
+ file_size=library_dataset_dict[ 'file_size' ],
+ date_uploaded=library_dataset_dict[ 'date_uploaded' ],
+ is_unrestricted=library_dataset_dict[ 'is_unrestricted' ],
+ is_private=library_dataset_dict[ 'is_private' ] ) )
- # For every item return also the default meta-data
- return_item.update( dict( id = encoded_id,
- type = content_item.api_type,
- name = content_item.name,
- update_time = update_time,
- create_time = create_time,
- deleted = content_item.deleted
- ) )
- folder_contents.append( return_item )
+ # For every item include the default meta-data
+ return_item.update( dict( id=encoded_id,
+ type=content_item.api_type,
+ name=content_item.name,
+ update_time=update_time,
+ create_time=create_time,
+ deleted=content_item.deleted
+ ) )
+ folder_contents.append( return_item )
- return { 'metadata' : { 'full_path' : full_path, 'can_add_library_item': can_add_library_item, 'folder_name': folder.name }, 'folder_contents' : folder_contents }
+ return { 'metadata': { 'full_path': full_path, 'can_add_library_item': can_add_library_item, 'folder_name': folder.name }, 'folder_contents': folder_contents }
def _load_folder_contents( self, trans, folder, include_deleted ):
"""
@@ -166,7 +162,7 @@
:type folder: Galaxy LibraryFolder
:param include_deleted: flag, when true the items that are deleted
- and can be undeleted by current user are shown
+ and can be undeleted by current user are shown
:type include_deleted: boolean
:returns: a list containing the requested items
@@ -175,34 +171,42 @@
current_user_roles = trans.get_current_user_roles()
is_admin = trans.user_is_admin()
content_items = []
- for subfolder in folder.active_folders:
+ for subfolder in folder.folders:
if subfolder.deleted:
if include_deleted:
if is_admin:
+ # Admins can see all deleted folders.
subfolder.api_type = 'folder'
content_items.append( subfolder )
else:
+ # Users with MODIFY permissions can see deleted folders.
can_modify = trans.app.security_agent.can_modify_library_item( current_user_roles, subfolder )
if can_modify:
subfolder.api_type = 'folder'
content_items.append( subfolder )
else:
- if is_admin:
- subfolder.api_type = 'folder'
- content_items.append( subfolder )
- else:
- can_access, folder_ids = trans.app.security_agent.check_folder_contents( trans.user, current_user_roles, subfolder )
- if can_access:
- subfolder.api_type = 'folder'
- content_items.append( subfolder )
+ # Undeleted folders are non-restricted for now. The contents are not.
+ # TODO decide on restrictions
+ subfolder.api_type = 'folder'
+ content_items.append( subfolder )
+ # if is_admin:
+ # subfolder.api_type = 'folder'
+ # content_items.append( subfolder )
+ # else:
+ # can_access, folder_ids = trans.app.security_agent.check_folder_contents( trans.user, current_user_roles, subfolder )
+ # if can_access:
+ # subfolder.api_type = 'folder'
+ # content_items.append( subfolder )
for dataset in folder.datasets:
if dataset.deleted:
if include_deleted:
if is_admin:
+ # Admins can see all deleted datasets.
dataset.api_type = 'file'
content_items.append( dataset )
else:
+ # Users with MODIFY permissions on the item can see the deleted item.
can_modify = trans.app.security_agent.can_modify_library_item( current_user_roles, dataset )
if can_modify:
dataset.api_type = 'file'
@@ -230,19 +234,19 @@
:type payload: dict
* folder_id: the parent folder of the new item
- * from_hda_id: (optional) the id of an accessible HDA to copy
+ * from_hda_id: (optional) the id of an accessible HDA to copy
into the library
* ldda_message: (optional) the new message attribute of the LDDA
created
- * extended_metadata: (optional) dub-dictionary containing any
+ * extended_metadata: (optional) dub-dictionary containing any
extended metadata to associate with the item
- :returns: a dictionary containing the id, name,
+ :returns: a dictionary containing the id, name,
and 'show' url of the new item
:rtype: dict
- :raises: ObjectAttributeInvalidException,
- InsufficientPermissionsException, ItemAccessibilityException,
+ :raises: ObjectAttributeInvalidException,
+ InsufficientPermissionsException, ItemAccessibilityException,
InternalServerError
"""
encoded_folder_id_16 = self.__decode_library_content_id( trans, encoded_folder_id )
@@ -287,13 +291,13 @@
:param encoded_folder_id: encoded id of Galaxy LibraryFolder
:type encoded_folder_id: encoded string
- :returns: last 16 chars of the encoded id in case it was Folder
+ :returns: last 16 chars of the encoded id in case it was Folder
(had 'F' prepended)
:type: string
:raises: MalformedId
"""
- if ( len( encoded_folder_id ) == 17 and encoded_folder_id.startswith( 'F' )):
+ if ( len( encoded_folder_id ) == 17 and encoded_folder_id.startswith( 'F' )):
return encoded_folder_id[1:]
else:
raise exceptions.MalformedId( 'Malformed folder id ( %s ) specified, unable to decode.' % str( encoded_folder_id ) )
diff -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c -r a73c02cd503ef2bec4819013517530df7045127e lib/galaxy/webapps/galaxy/api/folders.py
--- a/lib/galaxy/webapps/galaxy/api/folders.py
+++ b/lib/galaxy/webapps/galaxy/api/folders.py
@@ -1,18 +1,24 @@
"""
API operations on library folders
"""
-import os, string, shutil, urllib, re, socket, traceback
-from galaxy import datatypes, jobs, web, security
+# import os
+# import shutil
+# import urllib
+# import re
+# import socket
+# import traceback
+# import string
+from galaxy import web
from galaxy import exceptions
from galaxy.web import _future_expose_api as expose_api
-from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous
-from galaxy.web.base.controller import BaseAPIController,UsesLibraryMixin,UsesLibraryMixinItems
+from galaxy.web.base.controller import BaseAPIController, UsesLibraryMixin, UsesLibraryMixinItems
from sqlalchemy.orm.exc import MultipleResultsFound
from sqlalchemy.orm.exc import NoResultFound
import logging
log = logging.getLogger( __name__ )
+
class FoldersController( BaseAPIController, UsesLibraryMixin, UsesLibraryMixinItems ):
@web.expose_api
@@ -32,18 +38,19 @@
Displays information about a folder.
- :param encoded_parent_folder_id: the parent folder's id (required)
- :type encoded_parent_folder_id: an encoded id string (should be prefixed by 'F')
+ :param id: the folder's encoded id (required)
+ :type id: an encoded id string (has to be prefixed by 'F')
+
+ :returns: dictionary including details of the folder
+ :rtype: dict
"""
- # Eliminate any 'F' in front of the folder id. Just take the
- # last 16 characters:
- if ( len( id ) >= 17 ):
- id = id[-16:]
- # Retrieve the folder and return its contents encoded. Note that the
- # check_ownership=false since we are only displaying it.
- content = self.get_library_folder( trans, id, check_ownership=False,
- check_accessible=True )
- return self.encode_all_ids( trans, content.to_dict( view='element' ) )
+ folder_id_without_prefix = self.__cut_the_prefix( id )
+ content = self.get_library_folder( trans, folder_id_without_prefix, check_ownership=False, check_accessible=True )
+ return_dict = self.encode_all_ids( trans, content.to_dict( view='element' ) )
+ return_dict[ 'id' ] = 'F' + return_dict[ 'id' ]
+ if return_dict[ 'parent_id' ] is not None:
+ return_dict[ 'parent_id' ] = 'F' + return_dict[ 'parent_id' ]
+ return return_dict
@expose_api
def create( self, trans, encoded_parent_folder_id, **kwd ):
@@ -53,13 +60,13 @@
*POST /api/folders/{encoded_parent_folder_id}
Create a new folder object underneath the one specified in the parameters.
-
+
:param encoded_parent_folder_id: the parent folder's id (required)
- :type encoded_parent_folder_id: an encoded id string (should be prefixed by 'F')
+ :type encoded_parent_folder_id: an encoded id string (should be prefixed by 'F')
:param name: the name of the new folder (required)
- :type name: str
-
+ :type name: str
+
:param description: the description of the new folder
:type description: str
@@ -70,16 +77,16 @@
"""
payload = kwd.get( 'payload', None )
- if payload == None:
+ if payload is None:
raise exceptions.RequestParameterMissingException( "Missing required parameters 'encoded_parent_folder_id' and 'name'." )
name = payload.get( 'name', None )
description = payload.get( 'description', '' )
- if encoded_parent_folder_id == None:
+ if encoded_parent_folder_id is None:
raise exceptions.RequestParameterMissingException( "Missing required parameter 'encoded_parent_folder_id'." )
- elif name == None:
+ elif name is None:
raise exceptions.RequestParameterMissingException( "Missing required parameter 'name'." )
- # encoded_parent_folder_id may be prefixed by 'F'
+ # encoded_parent_folder_id should be prefixed by 'F'
encoded_parent_folder_id = self.__cut_the_prefix( encoded_parent_folder_id )
try:
decoded_parent_folder_id = trans.security.decode_id( encoded_parent_folder_id )
@@ -93,7 +100,7 @@
except NoResultFound:
raise exceptions.RequestParameterInvalidException( 'No folder found with the id provided.' )
except Exception, e:
- raise exceptions.InternalServerError( 'Error loading from the database.' + str(e))
+ raise exceptions.InternalServerError( 'Error loading from the database.' + str( e ) )
library = parent_folder.parent_library
if library.deleted:
@@ -108,29 +115,28 @@
try:
folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( v.id )
except Exception, e:
- raise exceptions.InternalServerError( 'Error loading from the database.' + str( e ))
+ raise exceptions.InternalServerError( 'Error loading from the database.' + str( e ))
if folder:
- return_dict = folder.to_dict( view='element' )
- return_dict[ 'parent_library_id' ] = trans.security.encode_id( return_dict[ 'parent_library_id' ] )
- return_dict[ 'parent_id' ] = 'F' + trans.security.encode_id( return_dict[ 'parent_id' ] )
- return_dict[ 'id' ] = 'F' + trans.security.encode_id( return_dict[ 'id' ] )
+ update_time = folder.update_time.strftime( "%Y-%m-%d %I:%M %p" )
+ return_dict = self.encode_all_ids( trans, folder.to_dict( view='element' ) )
+ return_dict[ 'update_time' ] = update_time
+ return_dict[ 'parent_id' ] = 'F' + return_dict[ 'parent_id' ]
+ return_dict[ 'id' ] = 'F' + return_dict[ 'id' ]
return return_dict
else:
- raise exceptions.InternalServerError( 'Error while creating a folder.' + str(e))
+ raise exceptions.InternalServerError( 'Error while creating a folder.' + str( e ) )
@web.expose_api
def update( self, trans, id, library_id, payload, **kwd ):
"""
PUT /api/folders/{encoded_folder_id}
- For now this does nothing. There are no semantics for folders that
- indicates that an update operation is needed; the existing
- library_contents folder does not allow for update, either.
+
"""
- pass
+ raise exceptions.NotImplemented( 'Updating folder through this endpoint is not implemented yet.' )
def __cut_the_prefix(self, encoded_id):
- if ( len( encoded_id ) == 17 and encoded_id.startswith( 'F' ) ):
- return encoded_id[-16:]
+
+ if ( ( len( encoded_id ) % 2 == 1 ) and encoded_id.startswith( 'F' ) ):
+ return encoded_id[ 1: ]
else:
- return encoded_id
-
+ raise exceptions.MalformedId( 'Malformed folder id ( %s ) specified, unable to decode.' % str( encoded_id ) )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: jmchilton: Merged in jmchilton/galaxy-central-fork-1 (pull request #385)
by commits-noreply@bitbucket.org 07 May '14
by commits-noreply@bitbucket.org 07 May '14
07 May '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/9eb2b42e21fe/
Changeset: 9eb2b42e21fe
User: jmchilton
Date: 2014-05-07 18:57:21
Summary: Merged in jmchilton/galaxy-central-fork-1 (pull request #385)
CLI Job Runner Enhancements
Affected #: 14 files
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/cli.py
--- a/lib/galaxy/jobs/runners/cli.py
+++ b/lib/galaxy/jobs/runners/cli.py
@@ -3,18 +3,18 @@
"""
import os
-import time
-import glob
import logging
from galaxy import model
from galaxy.jobs import JobDestination
from galaxy.jobs.runners import AsynchronousJobState, AsynchronousJobRunner
+from .util.cli import CliInterface, split_params
log = logging.getLogger( __name__ )
__all__ = [ 'ShellJobRunner' ]
+
class ShellJobRunner( AsynchronousJobRunner ):
"""
Job runner backed by a finite pool of worker threads. FIFO scheduling
@@ -25,53 +25,27 @@
"""Start the job runner """
super( ShellJobRunner, self ).__init__( app, nworkers )
- self.cli_shells = None
- self.cli_job_interfaces = None
- self.__load_cli_plugins()
-
+ self.cli_interface = CliInterface()
self._init_monitor_thread()
self._init_worker_threads()
- def __load_cli_plugins(self):
- def __load(module_path, d):
- for file in glob.glob(os.path.join(os.path.join(os.getcwd(), 'lib', *module_path.split('.')), '*.py')):
- if os.path.basename(file).startswith('_'):
- continue
- module_name = '%s.%s' % (module_path, os.path.basename(file).rsplit('.py', 1)[0])
- module = __import__(module_name)
- for comp in module_name.split( "." )[1:]:
- module = getattr(module, comp)
- for name in module.__all__:
- log.debug('Loaded cli plugin %s' % name)
- d[name] = getattr(module, name)
-
- self.cli_shells = {}
- self.cli_job_interfaces = {}
- __load('galaxy.jobs.runners.cli_shell', self.cli_shells)
- __load('galaxy.jobs.runners.cli_job', self.cli_job_interfaces)
-
def get_cli_plugins( self, shell_params, job_params ):
- # load shell plugin
- shell = self.cli_shells[shell_params['plugin']](**shell_params)
- job_interface = self.cli_job_interfaces[job_params['plugin']](**job_params)
- return shell, job_interface
+ return self.cli_interface.get_plugins( shell_params, job_params )
def url_to_destination( self, url ):
params = {}
- shell_params, job_params = url.split('/')[2:4]
+ shell_params, job_params = url.split( '/' )[ 2:4 ]
# split 'foo=bar&baz=quux' into { 'foo' : 'bar', 'baz' : 'quux' }
- shell_params = dict ( [ ( 'shell_' + k, v ) for k, v in [ kv.split('=', 1) for kv in shell_params.split('&') ] ] )
- job_params = dict ( [ ( 'job_' + k, v ) for k, v in [ kv.split('=', 1) for kv in job_params.split('&') ] ] )
+ shell_params = dict( [ ( 'shell_' + k, v ) for k, v in [ kv.split( '=', 1 ) for kv in shell_params.split( '&' ) ] ] )
+ job_params = dict( [ ( 'job_' + k, v ) for k, v in [ kv.split( '=', 1 ) for kv in job_params.split( '&' ) ] ] )
params.update( shell_params )
params.update( job_params )
- log.debug("Converted URL '%s' to destination runner=cli, params=%s" % (url, params))
+ log.debug( "Converted URL '%s' to destination runner=cli, params=%s" % ( url, params ) )
# Create a dynamic JobDestination
- return JobDestination(runner='cli', params=params)
+ return JobDestination( runner='cli', params=params )
def parse_destination_params( self, params ):
- shell_params = dict((k.replace('shell_', '', 1), v) for k, v in params.items() if k.startswith('shell_'))
- job_params = dict((k.replace('job_', '', 1), v) for k, v in params.items() if k.startswith('job_'))
- return shell_params, job_params
+ return split_params( params )
def queue_job( self, job_wrapper ):
"""Create job script and submit it to the DRM"""
@@ -93,8 +67,12 @@
# define job attributes
ajs = AsynchronousJobState( files_dir=job_wrapper.working_directory, job_wrapper=job_wrapper )
- # fill in the DRM's job run template
- script = job_interface.get_job_template(ajs.output_file, ajs.error_file, ajs.job_name, job_wrapper, command_line, ajs.exit_code_file)
+ job_file_kwargs = job_interface.job_script_kwargs(ajs.output_file, ajs.error_file, ajs.job_name)
+ script = self.get_job_file(
+ job_wrapper,
+ exit_code_path=ajs.exit_code_file,
+ **job_file_kwargs
+ )
try:
fh = file(ajs.job_file, "w")
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/cli_job/__init__.py
--- a/lib/galaxy/jobs/runners/cli_job/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""
-Base class for cli job plugins
-"""
-
-class BaseJobExec(object):
- def __init__(self, **params):
- raise NotImplementedError()
- def get_job_template(self, ofile, efile, job_name, job_wrapper, command_line, ecfile):
- raise NotImplementedError()
- def submit(self, script_file):
- raise NotImplementedError()
- def delete(self, job_id):
- raise NotImplementedError()
- def get_status(self, job_ids=None):
- raise NotImplementedError()
- def get_single_status(self, job_id):
- raise NotImplementedError()
- def parse_status(self, status, job_ids):
- raise NotImplementedError()
- def parse_single_status(self, status, job_id):
- raise NotImplementedError()
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/cli_job/torque.py
--- a/lib/galaxy/jobs/runners/cli_job/torque.py
+++ /dev/null
@@ -1,135 +0,0 @@
-"""
-Command-line interface to TORQUE PBS
-"""
-
-import os
-import logging
-
-from galaxy.model import Job
-job_states = Job.states
-
-from galaxy.jobs.runners.cli_job import BaseJobExec
-
-log = logging.getLogger( __name__ )
-
-__all__ = ('Torque',)
-
-try:
- import xml.etree.cElementTree as et
-except:
- import xml.etree.ElementTree as et
-
-job_template = """#!/bin/sh
-%s
-GALAXY_LIB="%s"
-if [ "$GALAXY_LIB" != "None" ]; then
- if [ -n "$PYTHONPATH" ]; then
- export PYTHONPATH="$GALAXY_LIB:$PYTHONPATH"
- else
- export PYTHONPATH="$GALAXY_LIB"
- fi
-fi
-%s
-cd %s
-%s
-echo $? > %s
-"""
-
-argmap = { 'destination' : '-q',
- 'Execution_Time' : '-a',
- 'Account_Name' : '-A',
- 'Checkpoint' : '-c',
- 'Error_Path' : '-e',
- 'Group_List' : '-g',
- 'Hold_Types' : '-h',
- 'Join_Paths' : '-j',
- 'Keep_Files' : '-k',
- 'Resource_List' : '-l',
- 'Mail_Points' : '-m',
- 'Mail_Users' : '-M',
- 'Job_Name' : '-N',
- 'Output_Path' : '-o',
- 'Priority' : '-p',
- 'Rerunable' : '-r',
- 'Shell_Path_List' : '-S',
- 'job_array_request' : '-t',
- 'User_List' : '-u',
- 'Variable_List' : '-v' }
-
-class Torque(BaseJobExec):
- def __init__(self, **params):
- self.params = {}
- for k, v in params.items():
- self.params[k] = v
-
- def get_job_template(self, ofile, efile, job_name, job_wrapper, command_line, ecfile):
- pbsargs = { '-o' : ofile,
- '-e' : efile,
- '-N' : job_name }
- for k, v in self.params.items():
- if k == 'plugin':
- continue
- try:
- if not k.startswith('-'):
- k = argmap[k]
- pbsargs[k] = v
- except:
- log.warning('Unrecognized long argument passed to Torque CLI plugin: %s' % k)
- template_pbsargs = ''
- for k, v in pbsargs.items():
- template_pbsargs += '#PBS %s %s\n' % (k, v)
- return job_template % (template_pbsargs,
- job_wrapper.galaxy_lib_dir,
- job_wrapper.get_env_setup_clause(),
- os.path.abspath(job_wrapper.working_directory),
- command_line,
- ecfile)
-
- def submit(self, script_file):
- return 'qsub %s' % script_file
-
- def delete(self, job_id):
- return 'qdel %s' % job_id
-
- def get_status(self, job_ids=None):
- return 'qstat -x'
-
- def get_single_status(self, job_id):
- return 'qstat -f %s' % job_id
-
- def parse_status(self, status, job_ids):
- # in case there's noise in the output, find the big blob 'o xml
- tree = None
- rval = {}
- for line in status.strip().splitlines():
- try:
- tree = et.fromstring(line.strip())
- assert tree.tag == 'Data'
- break
- except Exception, e:
- tree = None
- if tree is None:
- log.warning('No valid qstat XML return from `qstat -x`, got the following: %s' % status)
- return None
- else:
- for job in tree.findall('Job'):
- id = job.find('Job_Id').text
- if id in job_ids:
- state = job.find('job_state').text
- # map PBS job states to Galaxy job states.
- rval[id] = self.__get_job_state(state)
- return rval
-
- def parse_single_status(self, status, job_id):
- for line in status.splitlines():
- line = line.split(' = ')
- if line[0] == 'job_state':
- return line[1]
- # no state found, job has exited
- return job_states.OK
-
- def __get_job_state(self, state):
- return { 'E' : job_states.RUNNING,
- 'R' : job_states.RUNNING,
- 'Q' : job_states.QUEUED,
- 'C' : job_states.OK }.get(state, state)
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/cli_shell/__init__.py
--- a/lib/galaxy/jobs/runners/cli_shell/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""
-Base class for runners which execute commands via a shell
-"""
-
-class BaseShellExec(object):
- def __init__(self, *args, **kwargs):
- raise NotImplementedError()
- def copy(self, rcp_cmd, files, dest):
- raise NotImplementedError()
- def execute(self, cmd, persist=False, timeout=60):
- raise NotImplementedError()
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/cli_shell/rsh.py
--- a/lib/galaxy/jobs/runners/cli_shell/rsh.py
+++ /dev/null
@@ -1,64 +0,0 @@
-"""
-Interface for remote shell commands (rsh, rcp) and derivatives that use the same syntax (ssh, scp)
-"""
-
-import logging
-import os
-import subprocess
-import tempfile
-import time
-
-from galaxy.util.bunch import Bunch
-from galaxy.jobs.runners.cli_shell import BaseShellExec
-
-log = logging.getLogger( __name__ )
-
-__all__ = ('RemoteShell', 'SecureShell', 'GlobusSecureShell')
-
-class RemoteShell(BaseShellExec):
- def __init__(self, rsh='rsh', rcp='rcp', hostname=None, username=None, **kwargs):
- self.rsh = rsh
- self.rcp = rcp
- self.hostname = hostname
- self.username = username
- self.sessions = {}
- def copy(self, rcp_cmd, files, dest):
- pass
- def execute(self, cmd, persist=False, timeout=60):
- # TODO: implement persistence
- if self.username is None:
- fullcmd = '%s %s %s' % (self.rsh, self.hostname, cmd)
- else:
- fullcmd = '%s -l %s %s %s' % (self.rsh, self.username, self.hostname, cmd)
- # Read stdout to a tempfile in case it's large (>65K)
- outf = tempfile.TemporaryFile()
- p = subprocess.Popen(fullcmd, shell=True, stdin=None, stdout=outf, stderr=subprocess.PIPE)
- # poll until timeout
- for i in range(timeout/3):
- r = p.poll()
- if r is not None:
- break
- time.sleep(3)
- else:
- pid = int(p.pid)
- for sig in (15, 9):
- try:
- os.kill(pid, sig)
- time.sleep(3)
- except:
- log.warning('Killing pid %s (cmd: "%s") with signal %s failed' % (p.pid, fullcmd, sig))
- return Bunch(stdout='', stderr='Execution timed out', returncode=-1)
- outf.seek(0)
- return Bunch(stdout=outf.read(), stderr=p.stderr.read(), returncode=p.returncode)
-
-
-class SecureShell(RemoteShell):
- SSH_NEW_KEY_STRING = 'Are you sure you want to continue connecting'
- def __init__(self, rsh='ssh', rcp='scp', **kwargs):
- rsh += ' -oStrictHostKeyChecking=yes -oConnectTimeout=60'
- rcp += ' -oStrictHostKeyChecking=yes -oConnectTimeout=60'
- super(SecureShell, self).__init__(rsh=rsh, rcp=rcp, **kwargs)
-
-class GlobusSecureShell(SecureShell):
- def __init__(self, rsh='gsissh', rcp='gsiscp', **kwargs):
- super(SecureShell, self).__init__(rsh=rsh, rcp=rcp, **kwargs)
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/__init__.py
--- a/lib/galaxy/jobs/runners/util/__init__.py
+++ b/lib/galaxy/jobs/runners/util/__init__.py
@@ -3,5 +3,8 @@
processes and interfacing with job managers. This module should contain
functionality shared between Galaxy and the LWR.
"""
+from galaxy.util.bunch import Bunch
-from galaxy.util.bunch import Bunch
+from .kill import kill_pid
+
+__all__ = [kill_pid, Bunch]
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/__init__.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/__init__.py
@@ -0,0 +1,56 @@
+"""
+"""
+from glob import glob
+from os.path import basename, join
+from os import getcwd
+
+DEFAULT_SHELL_PLUGIN = 'LocalShell'
+
+
+class CliInterface(object):
+ """
+ High-level interface for loading shell and job plugins and matching
+ them to specified parameters.
+ """
+
+ def __init__(self, code_dir='lib'):
+ """
+ """
+ def __load(module_path, d):
+ module_pattern = join(join(getcwd(), code_dir, *module_path.split('.')), '*.py')
+ for file in glob(module_pattern):
+ if basename(file).startswith('_'):
+ continue
+ module_name = '%s.%s' % (module_path, basename(file).rsplit('.py', 1)[0])
+ module = __import__(module_name)
+ for comp in module_name.split(".")[1:]:
+ module = getattr(module, comp)
+ for name in module.__all__:
+ try:
+ d[name] = getattr(module, name)
+ except TypeError:
+ raise TypeError("Invalid type for name %s" % name)
+
+ self.cli_shells = {}
+ self.cli_job_interfaces = {}
+
+ module_prefix = self.__module__
+ __load('%s.shell' % module_prefix, self.cli_shells)
+ __load('%s.job' % module_prefix, self.cli_job_interfaces)
+
+ def get_plugins(self, shell_params, job_params):
+ """
+ Return shell and job interface defined by and configured via
+ specified params.
+ """
+ shell_plugin = shell_params.get('plugin', DEFAULT_SHELL_PLUGIN)
+ job_plugin = job_params['plugin']
+ shell = self.cli_shells[shell_plugin](**shell_params)
+ job_interface = self.cli_job_interfaces[job_plugin](**job_params)
+ return shell, job_interface
+
+
+def split_params(params):
+ shell_params = dict((k.replace('shell_', '', 1), v) for k, v in params.items() if k.startswith('shell_'))
+ job_params = dict((k.replace('job_', '', 1), v) for k, v in params.items() if k.startswith('job_'))
+ return shell_params, job_params
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/job/__init__.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/job/__init__.py
@@ -0,0 +1,58 @@
+"""
+Abstract base class for cli job plugins.
+"""
+from abc import ABCMeta, abstractmethod
+
+
+class BaseJobExec(object):
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def __init__(self, **params):
+ """
+ Constructor for CLI job executor.
+ """
+
+ def job_script_kwargs(self, ofile, efile, job_name):
+ """ Return extra keyword argument for consumption by job script
+ module.
+ """
+ return {}
+
+ @abstractmethod
+ def submit(self, script_file):
+ """
+ Given specified script_file path, yield command to submit it
+ to external job manager.
+ """
+
+ @abstractmethod
+ def delete(self, job_id):
+ """
+ Given job id, return command to stop execution or dequeue specified
+ job.
+ """
+
+ @abstractmethod
+ def get_status(self, job_ids=None):
+ """
+ Return command to get statuses of specified job ids.
+ """
+
+ @abstractmethod
+ def get_single_status(self, job_id):
+ """
+ Return command to get the status of a single, specified job.
+ """
+
+ @abstractmethod
+ def parse_status(self, status, job_ids):
+ """
+ Parse the statuses of output from get_status command.
+ """
+
+ @abstractmethod
+ def parse_single_status(self, status, job_id):
+ """
+ Parse the status of output from get_single_status command.
+ """
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/job/slurm_torque.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/job/slurm_torque.py
@@ -0,0 +1,29 @@
+import re
+from .torque import Torque
+
+__all__ = ('SlurmTorque',)
+
+
+class SlurmTorque(Torque):
+ """ A CLI job executor for Slurm's Torque compatibility mode. This differs
+ from real torque CLI in that -x command line is not available so job status
+ needs to be parsed from qstat table instead of XML.
+ """
+
+ def get_status(self, job_ids=None):
+ return 'qstat'
+
+ def parse_status(self, status, job_ids):
+ rval = {}
+ for line in status.strip().splitlines():
+ if line.startswith("Job ID"):
+ continue
+ line_parts = re.compile("\s+").split(line)
+ if len(line_parts) < 5:
+ continue
+ id = line_parts[0]
+ state = line_parts[4]
+ if id in job_ids:
+ # map PBS job states to Galaxy job states.
+ rval[id] = self._get_job_state(state)
+ return rval
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/job/torque.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/job/torque.py
@@ -0,0 +1,120 @@
+try:
+ import xml.etree.cElementTree as et
+except:
+ import xml.etree.ElementTree as et
+
+try:
+ from galaxy.model import Job
+ job_states = Job.states
+except ImportError:
+ # Not in Galaxy, map Galaxy job states to LWR ones.
+ from galaxy.util import enum
+ job_states = enum(RUNNING='running', OK='complete', QUEUED='queued')
+
+from ..job import BaseJobExec
+
+__all__ = ('Torque',)
+
+from logging import getLogger
+log = getLogger(__name__)
+
+argmap = {'destination': '-q',
+ 'Execution_Time': '-a',
+ 'Account_Name': '-A',
+ 'Checkpoint': '-c',
+ 'Error_Path': '-e',
+ 'Group_List': '-g',
+ 'Hold_Types': '-h',
+ 'Join_Paths': '-j',
+ 'Keep_Files': '-k',
+ 'Resource_List': '-l',
+ 'Mail_Points': '-m',
+ 'Mail_Users': '-M',
+ 'Job_Name': '-N',
+ 'Output_Path': '-o',
+ 'Priority': '-p',
+ 'Rerunable': '-r',
+ 'Shell_Path_List': '-S',
+ 'job_array_request': '-t',
+ 'User_List': '-u',
+ 'Variable_List': '-v'}
+
+
+class Torque(BaseJobExec):
+
+ def __init__(self, **params):
+ self.params = {}
+ for k, v in params.items():
+ self.params[k] = v
+
+ def job_script_kwargs(self, ofile, efile, job_name):
+ pbsargs = {'-o': ofile,
+ '-e': efile,
+ '-N': job_name}
+ for k, v in self.params.items():
+ if k == 'plugin':
+ continue
+ try:
+ if not k.startswith('-'):
+ k = argmap[k]
+ pbsargs[k] = v
+ except:
+ log.warning('Unrecognized long argument passed to Torque CLI plugin: %s' % k)
+ template_pbsargs = ''
+ for k, v in pbsargs.items():
+ template_pbsargs += '#PBS %s %s\n' % (k, v)
+ return dict(headers=template_pbsargs)
+
+ def submit(self, script_file):
+ return 'qsub %s' % script_file
+
+ def delete(self, job_id):
+ return 'qdel %s' % job_id
+
+ def get_status(self, job_ids=None):
+ return 'qstat -x'
+
+ def get_single_status(self, job_id):
+ return 'qstat -f %s' % job_id
+
+ def parse_status(self, status, job_ids):
+ # in case there's noise in the output, find the big blob 'o xml
+ tree = None
+ rval = {}
+ for line in status.strip().splitlines():
+ try:
+ tree = et.fromstring(line.strip())
+ assert tree.tag == 'Data'
+ break
+ except Exception:
+ tree = None
+ if tree is None:
+ log.warning('No valid qstat XML return from `qstat -x`, got the following: %s' % status)
+ return None
+ else:
+ for job in tree.findall('Job'):
+ id = job.find('Job_Id').text
+ if id in job_ids:
+ state = job.find('job_state').text
+ # map PBS job states to Galaxy job states.
+ rval[id] = self._get_job_state(state)
+ return rval
+
+ def parse_single_status(self, status, job_id):
+ for line in status.splitlines():
+ line = line.split(' = ')
+ if line[0] == 'job_state':
+ return self._get_job_state(line[1].strip())
+ # no state found, job has exited
+ return job_states.OK
+
+ def _get_job_state(self, state):
+ try:
+ return {
+ 'E': job_states.RUNNING,
+ 'R': job_states.RUNNING,
+ 'Q': job_states.QUEUED,
+ 'C': job_states.OK
+ }.get(state)
+ except KeyError:
+ raise KeyError("Failed to map torque status code [%s] to job state." % state)
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/shell/__init__.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/shell/__init__.py
@@ -0,0 +1,19 @@
+"""
+Abstract base class for runners which execute commands via a shell.
+"""
+from abc import ABCMeta, abstractmethod
+
+
+class BaseShellExec(object):
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def __init__(self, *args, **kwargs):
+ """
+ Constructor for shell executor instance.
+ """
+
+ def execute(self, cmd, persist=False, timeout=60):
+ """
+ Execute the specified command via defined shell.
+ """
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/shell/local.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/shell/local.py
@@ -0,0 +1,61 @@
+from tempfile import TemporaryFile
+from time import sleep
+from subprocess import Popen, PIPE
+
+from ..shell import BaseShellExec
+from ....util import Bunch, kill_pid
+
+from logging import getLogger
+log = getLogger(__name__)
+
+TIMEOUT_ERROR_MESSAGE = u'Execution timed out'
+TIMEOUT_RETURN_CODE = -1
+DEFAULT_TIMEOUT = 60
+DEFAULT_TIMEOUT_CHECK_INTERVAL = 3
+
+
+class LocalShell(BaseShellExec):
+ """
+
+ >>> shell = LocalShell()
+ >>> def exec_python(script, **kwds): return shell.execute('python -c "%s"' % script, **kwds)
+ >>> exec_result = exec_python("from __future__ import print_function; print('Hello World')")
+ >>> exec_result.stderr == u''
+ True
+ >>> exec_result.stdout.strip() == u'Hello World'
+ True
+ >>> exec_result = exec_python("import time; time.sleep(90)", timeout=3, timeout_check_interval=1)
+ >>> exec_result.stdout == u''
+ True
+ >>> exec_result.stderr == 'Execution timed out'
+ True
+ >>> exec_result.returncode == TIMEOUT_RETURN_CODE
+ True
+ """
+
+ def __init__(self, **kwds):
+ pass
+
+ def execute(self, cmd, persist=False, timeout=DEFAULT_TIMEOUT, timeout_check_interval=DEFAULT_TIMEOUT_CHECK_INTERVAL, **kwds):
+ outf = TemporaryFile()
+ p = Popen(cmd, shell=True, stdin=None, stdout=outf, stderr=PIPE)
+ # poll until timeout
+
+ for i in range(int(timeout / timeout_check_interval)):
+ r = p.poll()
+ if r is not None:
+ break
+ sleep(timeout_check_interval)
+ else:
+ kill_pid(p.pid)
+ return Bunch(stdout=u'', stderr=TIMEOUT_ERROR_MESSAGE, returncode=TIMEOUT_RETURN_CODE)
+ outf.seek(0)
+ return Bunch(stdout=_read_str(outf), stderr=_read_str(p.stderr), returncode=p.returncode)
+
+
+def _read_str(stream):
+ contents = stream.read()
+ return contents.decode('UTF-8') if isinstance(contents, bytes) else contents
+
+
+__all__ = ('LocalShell',)
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/shell/rsh.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/shell/rsh.py
@@ -0,0 +1,40 @@
+from .local import LocalShell
+
+from logging import getLogger
+log = getLogger(__name__)
+
+__all__ = ('RemoteShell', 'SecureShell', 'GlobusSecureShell')
+
+
+class RemoteShell(LocalShell):
+
+ def __init__(self, rsh='rsh', rcp='rcp', hostname='localhost', username=None, **kwargs):
+ super(RemoteShell, self).__init__(**kwargs)
+ self.rsh = rsh
+ self.rcp = rcp
+ self.hostname = hostname
+ self.username = username
+ self.sessions = {}
+
+ def execute(self, cmd, persist=False, timeout=60):
+ # TODO: implement persistence
+ if self.username is None:
+ fullcmd = '%s %s %s' % (self.rsh, self.hostname, cmd)
+ else:
+ fullcmd = '%s -l %s %s %s' % (self.rsh, self.username, self.hostname, cmd)
+ return super(RemoteShell, self).execute(fullcmd, persist, timeout)
+
+
+class SecureShell(RemoteShell):
+ SSH_NEW_KEY_STRING = 'Are you sure you want to continue connecting'
+
+ def __init__(self, rsh='ssh', rcp='scp', **kwargs):
+ rsh += ' -oStrictHostKeyChecking=yes -oConnectTimeout=60'
+ rcp += ' -oStrictHostKeyChecking=yes -oConnectTimeout=60'
+ super(SecureShell, self).__init__(rsh=rsh, rcp=rcp, **kwargs)
+
+
+class GlobusSecureShell(SecureShell):
+
+ def __init__(self, rsh='gsissh', rcp='gsiscp', **kwargs):
+ super(GlobusSecureShell, self).__init__(rsh=rsh, rcp=rcp, **kwargs)
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/kill.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/kill.py
@@ -0,0 +1,65 @@
+import os
+from platform import system
+from time import sleep
+from subprocess import Popen
+
+try:
+ from psutil import Process, NoSuchProcess
+except ImportError:
+ """ Don't make psutil a strict requirement, but use if available. """
+ Process = None
+
+
+def kill_pid(pid, use_psutil=True):
+ if use_psutil and Process:
+ _psutil_kill_pid(pid)
+ else:
+ _stock_kill_pid(pid)
+
+
+def _psutil_kill_pid(pid):
+ """
+ http://stackoverflow.com/questions/1230669/subprocess-deleting-child-proces…
+ """
+ try:
+ parent = Process(pid)
+ for child in parent.get_children(recursive=True):
+ child.kill()
+ parent.kill()
+ except NoSuchProcess:
+ return
+
+
+def _stock_kill_pid(pid):
+ is_windows = system() == 'Windows'
+
+ if is_windows:
+ __kill_windows(pid)
+ else:
+ __kill_posix(pid)
+
+
+def __kill_windows(pid):
+ try:
+ Popen("taskkill /F /T /PID %i" % pid, shell=True)
+ except Exception:
+ pass
+
+
+def __kill_posix(pid):
+ def __check_pid():
+ try:
+ os.kill(pid, 0)
+ return True
+ except OSError:
+ return False
+
+ if __check_pid():
+ for sig in [15, 9]:
+ try:
+ os.killpg(pid, sig)
+ except OSError:
+ return
+ sleep(1)
+ if not __check_pid():
+ return
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/a300bc54ca1f/
Changeset: a300bc54ca1f
User: jmchilton
Date: 2014-05-05 19:34:38
Summary: Backport CLI-manager infrastructure from LWR.
I had previously reworked this CLI stuff to work with LWR - this changeset brings those changes back to Galaxy. This version has the advantage of using the job script module - the upshot is that GALAXY_SLOTS, job_conf env directives, and job metrics should all be usable now with the CLI runner. This version also has a local shell variant (to eliminate the use of SSH and run qsub directly on the same machine), a variant of the Torque executor to target slurm's torque compatibility executables (doesn't support -x), and some other small bug fixes.
Affected #: 14 files
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/cli.py
--- a/lib/galaxy/jobs/runners/cli.py
+++ b/lib/galaxy/jobs/runners/cli.py
@@ -3,18 +3,18 @@
"""
import os
-import time
-import glob
import logging
from galaxy import model
from galaxy.jobs import JobDestination
from galaxy.jobs.runners import AsynchronousJobState, AsynchronousJobRunner
+from .util.cli import CliInterface, split_params
log = logging.getLogger( __name__ )
__all__ = [ 'ShellJobRunner' ]
+
class ShellJobRunner( AsynchronousJobRunner ):
"""
Job runner backed by a finite pool of worker threads. FIFO scheduling
@@ -25,53 +25,27 @@
"""Start the job runner """
super( ShellJobRunner, self ).__init__( app, nworkers )
- self.cli_shells = None
- self.cli_job_interfaces = None
- self.__load_cli_plugins()
-
+ self.cli_interface = CliInterface()
self._init_monitor_thread()
self._init_worker_threads()
- def __load_cli_plugins(self):
- def __load(module_path, d):
- for file in glob.glob(os.path.join(os.path.join(os.getcwd(), 'lib', *module_path.split('.')), '*.py')):
- if os.path.basename(file).startswith('_'):
- continue
- module_name = '%s.%s' % (module_path, os.path.basename(file).rsplit('.py', 1)[0])
- module = __import__(module_name)
- for comp in module_name.split( "." )[1:]:
- module = getattr(module, comp)
- for name in module.__all__:
- log.debug('Loaded cli plugin %s' % name)
- d[name] = getattr(module, name)
-
- self.cli_shells = {}
- self.cli_job_interfaces = {}
- __load('galaxy.jobs.runners.cli_shell', self.cli_shells)
- __load('galaxy.jobs.runners.cli_job', self.cli_job_interfaces)
-
def get_cli_plugins( self, shell_params, job_params ):
- # load shell plugin
- shell = self.cli_shells[shell_params['plugin']](**shell_params)
- job_interface = self.cli_job_interfaces[job_params['plugin']](**job_params)
- return shell, job_interface
+ return self.cli_interface.get_plugins( shell_params, job_params )
def url_to_destination( self, url ):
params = {}
- shell_params, job_params = url.split('/')[2:4]
+ shell_params, job_params = url.split( '/' )[ 2:4 ]
# split 'foo=bar&baz=quux' into { 'foo' : 'bar', 'baz' : 'quux' }
- shell_params = dict ( [ ( 'shell_' + k, v ) for k, v in [ kv.split('=', 1) for kv in shell_params.split('&') ] ] )
- job_params = dict ( [ ( 'job_' + k, v ) for k, v in [ kv.split('=', 1) for kv in job_params.split('&') ] ] )
+ shell_params = dict( [ ( 'shell_' + k, v ) for k, v in [ kv.split( '=', 1 ) for kv in shell_params.split( '&' ) ] ] )
+ job_params = dict( [ ( 'job_' + k, v ) for k, v in [ kv.split( '=', 1 ) for kv in job_params.split( '&' ) ] ] )
params.update( shell_params )
params.update( job_params )
- log.debug("Converted URL '%s' to destination runner=cli, params=%s" % (url, params))
+ log.debug( "Converted URL '%s' to destination runner=cli, params=%s" % ( url, params ) )
# Create a dynamic JobDestination
- return JobDestination(runner='cli', params=params)
+ return JobDestination( runner='cli', params=params )
def parse_destination_params( self, params ):
- shell_params = dict((k.replace('shell_', '', 1), v) for k, v in params.items() if k.startswith('shell_'))
- job_params = dict((k.replace('job_', '', 1), v) for k, v in params.items() if k.startswith('job_'))
- return shell_params, job_params
+ return split_params( params )
def queue_job( self, job_wrapper ):
"""Create job script and submit it to the DRM"""
@@ -93,8 +67,12 @@
# define job attributes
ajs = AsynchronousJobState( files_dir=job_wrapper.working_directory, job_wrapper=job_wrapper )
- # fill in the DRM's job run template
- script = job_interface.get_job_template(ajs.output_file, ajs.error_file, ajs.job_name, job_wrapper, command_line, ajs.exit_code_file)
+ job_file_kwargs = job_interface.job_script_kwargs(ajs.output_file, ajs.error_file, ajs.job_name)
+ script = self.get_job_file(
+ job_wrapper,
+ exit_code_path=ajs.exit_code_file,
+ **job_file_kwargs
+ )
try:
fh = file(ajs.job_file, "w")
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/cli_job/__init__.py
--- a/lib/galaxy/jobs/runners/cli_job/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""
-Base class for cli job plugins
-"""
-
-class BaseJobExec(object):
- def __init__(self, **params):
- raise NotImplementedError()
- def get_job_template(self, ofile, efile, job_name, job_wrapper, command_line, ecfile):
- raise NotImplementedError()
- def submit(self, script_file):
- raise NotImplementedError()
- def delete(self, job_id):
- raise NotImplementedError()
- def get_status(self, job_ids=None):
- raise NotImplementedError()
- def get_single_status(self, job_id):
- raise NotImplementedError()
- def parse_status(self, status, job_ids):
- raise NotImplementedError()
- def parse_single_status(self, status, job_id):
- raise NotImplementedError()
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/cli_job/torque.py
--- a/lib/galaxy/jobs/runners/cli_job/torque.py
+++ /dev/null
@@ -1,135 +0,0 @@
-"""
-Command-line interface to TORQUE PBS
-"""
-
-import os
-import logging
-
-from galaxy.model import Job
-job_states = Job.states
-
-from galaxy.jobs.runners.cli_job import BaseJobExec
-
-log = logging.getLogger( __name__ )
-
-__all__ = ('Torque',)
-
-try:
- import xml.etree.cElementTree as et
-except:
- import xml.etree.ElementTree as et
-
-job_template = """#!/bin/sh
-%s
-GALAXY_LIB="%s"
-if [ "$GALAXY_LIB" != "None" ]; then
- if [ -n "$PYTHONPATH" ]; then
- export PYTHONPATH="$GALAXY_LIB:$PYTHONPATH"
- else
- export PYTHONPATH="$GALAXY_LIB"
- fi
-fi
-%s
-cd %s
-%s
-echo $? > %s
-"""
-
-argmap = { 'destination' : '-q',
- 'Execution_Time' : '-a',
- 'Account_Name' : '-A',
- 'Checkpoint' : '-c',
- 'Error_Path' : '-e',
- 'Group_List' : '-g',
- 'Hold_Types' : '-h',
- 'Join_Paths' : '-j',
- 'Keep_Files' : '-k',
- 'Resource_List' : '-l',
- 'Mail_Points' : '-m',
- 'Mail_Users' : '-M',
- 'Job_Name' : '-N',
- 'Output_Path' : '-o',
- 'Priority' : '-p',
- 'Rerunable' : '-r',
- 'Shell_Path_List' : '-S',
- 'job_array_request' : '-t',
- 'User_List' : '-u',
- 'Variable_List' : '-v' }
-
-class Torque(BaseJobExec):
- def __init__(self, **params):
- self.params = {}
- for k, v in params.items():
- self.params[k] = v
-
- def get_job_template(self, ofile, efile, job_name, job_wrapper, command_line, ecfile):
- pbsargs = { '-o' : ofile,
- '-e' : efile,
- '-N' : job_name }
- for k, v in self.params.items():
- if k == 'plugin':
- continue
- try:
- if not k.startswith('-'):
- k = argmap[k]
- pbsargs[k] = v
- except:
- log.warning('Unrecognized long argument passed to Torque CLI plugin: %s' % k)
- template_pbsargs = ''
- for k, v in pbsargs.items():
- template_pbsargs += '#PBS %s %s\n' % (k, v)
- return job_template % (template_pbsargs,
- job_wrapper.galaxy_lib_dir,
- job_wrapper.get_env_setup_clause(),
- os.path.abspath(job_wrapper.working_directory),
- command_line,
- ecfile)
-
- def submit(self, script_file):
- return 'qsub %s' % script_file
-
- def delete(self, job_id):
- return 'qdel %s' % job_id
-
- def get_status(self, job_ids=None):
- return 'qstat -x'
-
- def get_single_status(self, job_id):
- return 'qstat -f %s' % job_id
-
- def parse_status(self, status, job_ids):
- # in case there's noise in the output, find the big blob 'o xml
- tree = None
- rval = {}
- for line in status.strip().splitlines():
- try:
- tree = et.fromstring(line.strip())
- assert tree.tag == 'Data'
- break
- except Exception, e:
- tree = None
- if tree is None:
- log.warning('No valid qstat XML return from `qstat -x`, got the following: %s' % status)
- return None
- else:
- for job in tree.findall('Job'):
- id = job.find('Job_Id').text
- if id in job_ids:
- state = job.find('job_state').text
- # map PBS job states to Galaxy job states.
- rval[id] = self.__get_job_state(state)
- return rval
-
- def parse_single_status(self, status, job_id):
- for line in status.splitlines():
- line = line.split(' = ')
- if line[0] == 'job_state':
- return line[1]
- # no state found, job has exited
- return job_states.OK
-
- def __get_job_state(self, state):
- return { 'E' : job_states.RUNNING,
- 'R' : job_states.RUNNING,
- 'Q' : job_states.QUEUED,
- 'C' : job_states.OK }.get(state, state)
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/cli_shell/__init__.py
--- a/lib/galaxy/jobs/runners/cli_shell/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""
-Base class for runners which execute commands via a shell
-"""
-
-class BaseShellExec(object):
- def __init__(self, *args, **kwargs):
- raise NotImplementedError()
- def copy(self, rcp_cmd, files, dest):
- raise NotImplementedError()
- def execute(self, cmd, persist=False, timeout=60):
- raise NotImplementedError()
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/cli_shell/rsh.py
--- a/lib/galaxy/jobs/runners/cli_shell/rsh.py
+++ /dev/null
@@ -1,64 +0,0 @@
-"""
-Interface for remote shell commands (rsh, rcp) and derivatives that use the same syntax (ssh, scp)
-"""
-
-import logging
-import os
-import subprocess
-import tempfile
-import time
-
-from galaxy.util.bunch import Bunch
-from galaxy.jobs.runners.cli_shell import BaseShellExec
-
-log = logging.getLogger( __name__ )
-
-__all__ = ('RemoteShell', 'SecureShell', 'GlobusSecureShell')
-
-class RemoteShell(BaseShellExec):
- def __init__(self, rsh='rsh', rcp='rcp', hostname=None, username=None, **kwargs):
- self.rsh = rsh
- self.rcp = rcp
- self.hostname = hostname
- self.username = username
- self.sessions = {}
- def copy(self, rcp_cmd, files, dest):
- pass
- def execute(self, cmd, persist=False, timeout=60):
- # TODO: implement persistence
- if self.username is None:
- fullcmd = '%s %s %s' % (self.rsh, self.hostname, cmd)
- else:
- fullcmd = '%s -l %s %s %s' % (self.rsh, self.username, self.hostname, cmd)
- # Read stdout to a tempfile in case it's large (>65K)
- outf = tempfile.TemporaryFile()
- p = subprocess.Popen(fullcmd, shell=True, stdin=None, stdout=outf, stderr=subprocess.PIPE)
- # poll until timeout
- for i in range(timeout/3):
- r = p.poll()
- if r is not None:
- break
- time.sleep(3)
- else:
- pid = int(p.pid)
- for sig in (15, 9):
- try:
- os.kill(pid, sig)
- time.sleep(3)
- except:
- log.warning('Killing pid %s (cmd: "%s") with signal %s failed' % (p.pid, fullcmd, sig))
- return Bunch(stdout='', stderr='Execution timed out', returncode=-1)
- outf.seek(0)
- return Bunch(stdout=outf.read(), stderr=p.stderr.read(), returncode=p.returncode)
-
-
-class SecureShell(RemoteShell):
- SSH_NEW_KEY_STRING = 'Are you sure you want to continue connecting'
- def __init__(self, rsh='ssh', rcp='scp', **kwargs):
- rsh += ' -oStrictHostKeyChecking=yes -oConnectTimeout=60'
- rcp += ' -oStrictHostKeyChecking=yes -oConnectTimeout=60'
- super(SecureShell, self).__init__(rsh=rsh, rcp=rcp, **kwargs)
-
-class GlobusSecureShell(SecureShell):
- def __init__(self, rsh='gsissh', rcp='gsiscp', **kwargs):
- super(SecureShell, self).__init__(rsh=rsh, rcp=rcp, **kwargs)
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/util/__init__.py
--- a/lib/galaxy/jobs/runners/util/__init__.py
+++ b/lib/galaxy/jobs/runners/util/__init__.py
@@ -3,5 +3,8 @@
processes and interfacing with job managers. This module should contain
functionality shared between Galaxy and the LWR.
"""
+from galaxy.util.bunch import Bunch
-from galaxy.util.bunch import Bunch
+from .kill import kill_pid
+
+__all__ = [kill_pid, Bunch]
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/util/cli/__init__.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/__init__.py
@@ -0,0 +1,56 @@
+"""
+"""
+from glob import glob
+from os.path import basename, join
+from os import getcwd
+
+DEFAULT_SHELL_PLUGIN = 'LocalShell'
+
+
+class CliInterface(object):
+ """
+ High-level interface for loading shell and job plugins and matching
+ them to specified parameters.
+ """
+
+ def __init__(self, code_dir='lib'):
+ """
+ """
+ def __load(module_path, d):
+ module_pattern = join(join(getcwd(), code_dir, *module_path.split('.')), '*.py')
+ for file in glob(module_pattern):
+ if basename(file).startswith('_'):
+ continue
+ module_name = '%s.%s' % (module_path, basename(file).rsplit('.py', 1)[0])
+ module = __import__(module_name)
+ for comp in module_name.split(".")[1:]:
+ module = getattr(module, comp)
+ for name in module.__all__:
+ try:
+ d[name] = getattr(module, name)
+ except TypeError:
+ raise TypeError("Invalid type for name %s" % name)
+
+ self.cli_shells = {}
+ self.cli_job_interfaces = {}
+
+ module_prefix = self.__module__
+ __load('%s.shell' % module_prefix, self.cli_shells)
+ __load('%s.job' % module_prefix, self.cli_job_interfaces)
+
+ def get_plugins(self, shell_params, job_params):
+ """
+ Return shell and job interface defined by and configured via
+ specified params.
+ """
+ shell_plugin = shell_params.get('plugin', DEFAULT_SHELL_PLUGIN)
+ job_plugin = job_params['plugin']
+ shell = self.cli_shells[shell_plugin](**shell_params)
+ job_interface = self.cli_job_interfaces[job_plugin](**job_params)
+ return shell, job_interface
+
+
+def split_params(params):
+ shell_params = dict((k.replace('shell_', '', 1), v) for k, v in params.items() if k.startswith('shell_'))
+ job_params = dict((k.replace('job_', '', 1), v) for k, v in params.items() if k.startswith('job_'))
+ return shell_params, job_params
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/util/cli/job/__init__.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/job/__init__.py
@@ -0,0 +1,58 @@
+"""
+Abstract base class for cli job plugins.
+"""
+from abc import ABCMeta, abstractmethod
+
+
+class BaseJobExec(object):
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def __init__(self, **params):
+ """
+ Constructor for CLI job executor.
+ """
+
+ def job_script_kwargs(self, ofile, efile, job_name):
+ """ Return extra keyword argument for consumption by job script
+ module.
+ """
+ return {}
+
+ @abstractmethod
+ def submit(self, script_file):
+ """
+ Given specified script_file path, yield command to submit it
+ to external job manager.
+ """
+
+ @abstractmethod
+ def delete(self, job_id):
+ """
+ Given job id, return command to stop execution or dequeue specified
+ job.
+ """
+
+ @abstractmethod
+ def get_status(self, job_ids=None):
+ """
+ Return command to get statuses of specified job ids.
+ """
+
+ @abstractmethod
+ def get_single_status(self, job_id):
+ """
+ Return command to get the status of a single, specified job.
+ """
+
+ @abstractmethod
+ def parse_status(self, status, job_ids):
+ """
+ Parse the statuses of output from get_status command.
+ """
+
+ @abstractmethod
+ def parse_single_status(self, status, job_id):
+ """
+ Parse the status of output from get_single_status command.
+ """
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/util/cli/job/slurm_torque.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/job/slurm_torque.py
@@ -0,0 +1,29 @@
+import re
+from .torque import Torque
+
+__all__ = ('SlurmTorque',)
+
+
+class SlurmTorque(Torque):
+ """ A CLI job executor for Slurm's Torque compatibility mode. This differs
+ from real torque CLI in that -x command line is not available so job status
+ needs to be parsed from qstat table instead of XML.
+ """
+
+ def get_status(self, job_ids=None):
+ return 'qstat'
+
+ def parse_status(self, status, job_ids):
+ rval = {}
+ for line in status.strip().splitlines():
+ if line.startswith("Job ID"):
+ continue
+ line_parts = re.compile("\s+").split(line)
+ if len(line_parts) < 5:
+ continue
+ id = line_parts[0]
+ state = line_parts[4]
+ if id in job_ids:
+ # map PBS job states to Galaxy job states.
+ rval[id] = self._get_job_state(state)
+ return rval
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/util/cli/job/torque.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/job/torque.py
@@ -0,0 +1,120 @@
+try:
+ import xml.etree.cElementTree as et
+except:
+ import xml.etree.ElementTree as et
+
+try:
+ from galaxy.model import Job
+ job_states = Job.states
+except ImportError:
+ # Not in Galaxy, map Galaxy job states to LWR ones.
+ from galaxy.util import enum
+ job_states = enum(RUNNING='running', OK='complete', QUEUED='queued')
+
+from ..job import BaseJobExec
+
+__all__ = ('Torque',)
+
+from logging import getLogger
+log = getLogger(__name__)
+
+argmap = {'destination': '-q',
+ 'Execution_Time': '-a',
+ 'Account_Name': '-A',
+ 'Checkpoint': '-c',
+ 'Error_Path': '-e',
+ 'Group_List': '-g',
+ 'Hold_Types': '-h',
+ 'Join_Paths': '-j',
+ 'Keep_Files': '-k',
+ 'Resource_List': '-l',
+ 'Mail_Points': '-m',
+ 'Mail_Users': '-M',
+ 'Job_Name': '-N',
+ 'Output_Path': '-o',
+ 'Priority': '-p',
+ 'Rerunable': '-r',
+ 'Shell_Path_List': '-S',
+ 'job_array_request': '-t',
+ 'User_List': '-u',
+ 'Variable_List': '-v'}
+
+
+class Torque(BaseJobExec):
+
+ def __init__(self, **params):
+ self.params = {}
+ for k, v in params.items():
+ self.params[k] = v
+
+ def job_script_kwargs(self, ofile, efile, job_name):
+ pbsargs = {'-o': ofile,
+ '-e': efile,
+ '-N': job_name}
+ for k, v in self.params.items():
+ if k == 'plugin':
+ continue
+ try:
+ if not k.startswith('-'):
+ k = argmap[k]
+ pbsargs[k] = v
+ except:
+ log.warning('Unrecognized long argument passed to Torque CLI plugin: %s' % k)
+ template_pbsargs = ''
+ for k, v in pbsargs.items():
+ template_pbsargs += '#PBS %s %s\n' % (k, v)
+ return dict(headers=template_pbsargs)
+
+ def submit(self, script_file):
+ return 'qsub %s' % script_file
+
+ def delete(self, job_id):
+ return 'qdel %s' % job_id
+
+ def get_status(self, job_ids=None):
+ return 'qstat -x'
+
+ def get_single_status(self, job_id):
+ return 'qstat -f %s' % job_id
+
+ def parse_status(self, status, job_ids):
+ # in case there's noise in the output, find the big blob 'o xml
+ tree = None
+ rval = {}
+ for line in status.strip().splitlines():
+ try:
+ tree = et.fromstring(line.strip())
+ assert tree.tag == 'Data'
+ break
+ except Exception:
+ tree = None
+ if tree is None:
+ log.warning('No valid qstat XML return from `qstat -x`, got the following: %s' % status)
+ return None
+ else:
+ for job in tree.findall('Job'):
+ id = job.find('Job_Id').text
+ if id in job_ids:
+ state = job.find('job_state').text
+ # map PBS job states to Galaxy job states.
+ rval[id] = self._get_job_state(state)
+ return rval
+
+ def parse_single_status(self, status, job_id):
+ for line in status.splitlines():
+ line = line.split(' = ')
+ if line[0] == 'job_state':
+ return self._get_job_state(line[1].strip())
+ # no state found, job has exited
+ return job_states.OK
+
+ def _get_job_state(self, state):
+ try:
+ return {
+ 'E': job_states.RUNNING,
+ 'R': job_states.RUNNING,
+ 'Q': job_states.QUEUED,
+ 'C': job_states.OK
+ }.get(state)
+ except KeyError:
+ raise KeyError("Failed to map torque status code [%s] to job state." % state)
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/util/cli/shell/__init__.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/shell/__init__.py
@@ -0,0 +1,19 @@
+"""
+Abstract base class for runners which execute commands via a shell.
+"""
+from abc import ABCMeta, abstractmethod
+
+
+class BaseShellExec(object):
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def __init__(self, *args, **kwargs):
+ """
+ Constructor for shell executor instance.
+ """
+
+ def execute(self, cmd, persist=False, timeout=60):
+ """
+ Execute the specified command via defined shell.
+ """
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/util/cli/shell/local.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/shell/local.py
@@ -0,0 +1,61 @@
+from tempfile import TemporaryFile
+from time import sleep
+from subprocess import Popen, PIPE
+
+from ..shell import BaseShellExec
+from ....util import Bunch, kill_pid
+
+from logging import getLogger
+log = getLogger(__name__)
+
+TIMEOUT_ERROR_MESSAGE = u'Execution timed out'
+TIMEOUT_RETURN_CODE = -1
+DEFAULT_TIMEOUT = 60
+DEFAULT_TIMEOUT_CHECK_INTERVAL = 3
+
+
+class LocalShell(BaseShellExec):
+ """
+
+ >>> shell = LocalShell()
+ >>> def exec_python(script, **kwds): return shell.execute('python -c "%s"' % script, **kwds)
+ >>> exec_result = exec_python("from __future__ import print_function; print('Hello World')")
+ >>> exec_result.stderr == u''
+ True
+ >>> exec_result.stdout.strip() == u'Hello World'
+ True
+ >>> exec_result = exec_python("import time; time.sleep(90)", timeout=3, timeout_check_interval=1)
+ >>> exec_result.stdout == u''
+ True
+ >>> exec_result.stderr == 'Execution timed out'
+ True
+ >>> exec_result.returncode == TIMEOUT_RETURN_CODE
+ True
+ """
+
+ def __init__(self, **kwds):
+ pass
+
+ def execute(self, cmd, persist=False, timeout=DEFAULT_TIMEOUT, timeout_check_interval=DEFAULT_TIMEOUT_CHECK_INTERVAL, **kwds):
+ outf = TemporaryFile()
+ p = Popen(cmd, shell=True, stdin=None, stdout=outf, stderr=PIPE)
+ # poll until timeout
+
+ for i in range(int(timeout / timeout_check_interval)):
+ r = p.poll()
+ if r is not None:
+ break
+ sleep(timeout_check_interval)
+ else:
+ kill_pid(p.pid)
+ return Bunch(stdout=u'', stderr=TIMEOUT_ERROR_MESSAGE, returncode=TIMEOUT_RETURN_CODE)
+ outf.seek(0)
+ return Bunch(stdout=_read_str(outf), stderr=_read_str(p.stderr), returncode=p.returncode)
+
+
+def _read_str(stream):
+ contents = stream.read()
+ return contents.decode('UTF-8') if isinstance(contents, bytes) else contents
+
+
+__all__ = ('LocalShell',)
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/util/cli/shell/rsh.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/shell/rsh.py
@@ -0,0 +1,40 @@
+from .local import LocalShell
+
+from logging import getLogger
+log = getLogger(__name__)
+
+__all__ = ('RemoteShell', 'SecureShell', 'GlobusSecureShell')
+
+
+class RemoteShell(LocalShell):
+
+ def __init__(self, rsh='rsh', rcp='rcp', hostname='localhost', username=None, **kwargs):
+ super(RemoteShell, self).__init__(**kwargs)
+ self.rsh = rsh
+ self.rcp = rcp
+ self.hostname = hostname
+ self.username = username
+ self.sessions = {}
+
+ def execute(self, cmd, persist=False, timeout=60):
+ # TODO: implement persistence
+ if self.username is None:
+ fullcmd = '%s %s %s' % (self.rsh, self.hostname, cmd)
+ else:
+ fullcmd = '%s -l %s %s %s' % (self.rsh, self.username, self.hostname, cmd)
+ return super(RemoteShell, self).execute(fullcmd, persist, timeout)
+
+
+class SecureShell(RemoteShell):
+ SSH_NEW_KEY_STRING = 'Are you sure you want to continue connecting'
+
+ def __init__(self, rsh='ssh', rcp='scp', **kwargs):
+ rsh += ' -oStrictHostKeyChecking=yes -oConnectTimeout=60'
+ rcp += ' -oStrictHostKeyChecking=yes -oConnectTimeout=60'
+ super(SecureShell, self).__init__(rsh=rsh, rcp=rcp, **kwargs)
+
+
+class GlobusSecureShell(SecureShell):
+
+ def __init__(self, rsh='gsissh', rcp='gsiscp', **kwargs):
+ super(GlobusSecureShell, self).__init__(rsh=rsh, rcp=rcp, **kwargs)
diff -r b94b4da49bfdf4f204d0ad2dce878a37e6e4f3d3 -r a300bc54ca1fd0479ae94541f56ec7899f2e2c90 lib/galaxy/jobs/runners/util/kill.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/kill.py
@@ -0,0 +1,65 @@
+import os
+from platform import system
+from time import sleep
+from subprocess import Popen
+
+try:
+ from psutil import Process, NoSuchProcess
+except ImportError:
+ """ Don't make psutil a strict requirement, but use if available. """
+ Process = None
+
+
+def kill_pid(pid, use_psutil=True):
+ if use_psutil and Process:
+ _psutil_kill_pid(pid)
+ else:
+ _stock_kill_pid(pid)
+
+
+def _psutil_kill_pid(pid):
+ """
+ http://stackoverflow.com/questions/1230669/subprocess-deleting-child-proces…
+ """
+ try:
+ parent = Process(pid)
+ for child in parent.get_children(recursive=True):
+ child.kill()
+ parent.kill()
+ except NoSuchProcess:
+ return
+
+
+def _stock_kill_pid(pid):
+ is_windows = system() == 'Windows'
+
+ if is_windows:
+ __kill_windows(pid)
+ else:
+ __kill_posix(pid)
+
+
+def __kill_windows(pid):
+ try:
+ Popen("taskkill /F /T /PID %i" % pid, shell=True)
+ except Exception:
+ pass
+
+
+def __kill_posix(pid):
+ def __check_pid():
+ try:
+ os.kill(pid, 0)
+ return True
+ except OSError:
+ return False
+
+ if __check_pid():
+ for sig in [15, 9]:
+ try:
+ os.killpg(pid, sig)
+ except OSError:
+ return
+ sleep(1)
+ if not __check_pid():
+ return
https://bitbucket.org/galaxy/galaxy-central/commits/9eb2b42e21fe/
Changeset: 9eb2b42e21fe
User: jmchilton
Date: 2014-05-07 18:57:21
Summary: Merged in jmchilton/galaxy-central-fork-1 (pull request #385)
CLI Job Runner Enhancements
Affected #: 14 files
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/cli.py
--- a/lib/galaxy/jobs/runners/cli.py
+++ b/lib/galaxy/jobs/runners/cli.py
@@ -3,18 +3,18 @@
"""
import os
-import time
-import glob
import logging
from galaxy import model
from galaxy.jobs import JobDestination
from galaxy.jobs.runners import AsynchronousJobState, AsynchronousJobRunner
+from .util.cli import CliInterface, split_params
log = logging.getLogger( __name__ )
__all__ = [ 'ShellJobRunner' ]
+
class ShellJobRunner( AsynchronousJobRunner ):
"""
Job runner backed by a finite pool of worker threads. FIFO scheduling
@@ -25,53 +25,27 @@
"""Start the job runner """
super( ShellJobRunner, self ).__init__( app, nworkers )
- self.cli_shells = None
- self.cli_job_interfaces = None
- self.__load_cli_plugins()
-
+ self.cli_interface = CliInterface()
self._init_monitor_thread()
self._init_worker_threads()
- def __load_cli_plugins(self):
- def __load(module_path, d):
- for file in glob.glob(os.path.join(os.path.join(os.getcwd(), 'lib', *module_path.split('.')), '*.py')):
- if os.path.basename(file).startswith('_'):
- continue
- module_name = '%s.%s' % (module_path, os.path.basename(file).rsplit('.py', 1)[0])
- module = __import__(module_name)
- for comp in module_name.split( "." )[1:]:
- module = getattr(module, comp)
- for name in module.__all__:
- log.debug('Loaded cli plugin %s' % name)
- d[name] = getattr(module, name)
-
- self.cli_shells = {}
- self.cli_job_interfaces = {}
- __load('galaxy.jobs.runners.cli_shell', self.cli_shells)
- __load('galaxy.jobs.runners.cli_job', self.cli_job_interfaces)
-
def get_cli_plugins( self, shell_params, job_params ):
- # load shell plugin
- shell = self.cli_shells[shell_params['plugin']](**shell_params)
- job_interface = self.cli_job_interfaces[job_params['plugin']](**job_params)
- return shell, job_interface
+ return self.cli_interface.get_plugins( shell_params, job_params )
def url_to_destination( self, url ):
params = {}
- shell_params, job_params = url.split('/')[2:4]
+ shell_params, job_params = url.split( '/' )[ 2:4 ]
# split 'foo=bar&baz=quux' into { 'foo' : 'bar', 'baz' : 'quux' }
- shell_params = dict ( [ ( 'shell_' + k, v ) for k, v in [ kv.split('=', 1) for kv in shell_params.split('&') ] ] )
- job_params = dict ( [ ( 'job_' + k, v ) for k, v in [ kv.split('=', 1) for kv in job_params.split('&') ] ] )
+ shell_params = dict( [ ( 'shell_' + k, v ) for k, v in [ kv.split( '=', 1 ) for kv in shell_params.split( '&' ) ] ] )
+ job_params = dict( [ ( 'job_' + k, v ) for k, v in [ kv.split( '=', 1 ) for kv in job_params.split( '&' ) ] ] )
params.update( shell_params )
params.update( job_params )
- log.debug("Converted URL '%s' to destination runner=cli, params=%s" % (url, params))
+ log.debug( "Converted URL '%s' to destination runner=cli, params=%s" % ( url, params ) )
# Create a dynamic JobDestination
- return JobDestination(runner='cli', params=params)
+ return JobDestination( runner='cli', params=params )
def parse_destination_params( self, params ):
- shell_params = dict((k.replace('shell_', '', 1), v) for k, v in params.items() if k.startswith('shell_'))
- job_params = dict((k.replace('job_', '', 1), v) for k, v in params.items() if k.startswith('job_'))
- return shell_params, job_params
+ return split_params( params )
def queue_job( self, job_wrapper ):
"""Create job script and submit it to the DRM"""
@@ -93,8 +67,12 @@
# define job attributes
ajs = AsynchronousJobState( files_dir=job_wrapper.working_directory, job_wrapper=job_wrapper )
- # fill in the DRM's job run template
- script = job_interface.get_job_template(ajs.output_file, ajs.error_file, ajs.job_name, job_wrapper, command_line, ajs.exit_code_file)
+ job_file_kwargs = job_interface.job_script_kwargs(ajs.output_file, ajs.error_file, ajs.job_name)
+ script = self.get_job_file(
+ job_wrapper,
+ exit_code_path=ajs.exit_code_file,
+ **job_file_kwargs
+ )
try:
fh = file(ajs.job_file, "w")
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/cli_job/__init__.py
--- a/lib/galaxy/jobs/runners/cli_job/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-"""
-Base class for cli job plugins
-"""
-
-class BaseJobExec(object):
- def __init__(self, **params):
- raise NotImplementedError()
- def get_job_template(self, ofile, efile, job_name, job_wrapper, command_line, ecfile):
- raise NotImplementedError()
- def submit(self, script_file):
- raise NotImplementedError()
- def delete(self, job_id):
- raise NotImplementedError()
- def get_status(self, job_ids=None):
- raise NotImplementedError()
- def get_single_status(self, job_id):
- raise NotImplementedError()
- def parse_status(self, status, job_ids):
- raise NotImplementedError()
- def parse_single_status(self, status, job_id):
- raise NotImplementedError()
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/cli_job/torque.py
--- a/lib/galaxy/jobs/runners/cli_job/torque.py
+++ /dev/null
@@ -1,135 +0,0 @@
-"""
-Command-line interface to TORQUE PBS
-"""
-
-import os
-import logging
-
-from galaxy.model import Job
-job_states = Job.states
-
-from galaxy.jobs.runners.cli_job import BaseJobExec
-
-log = logging.getLogger( __name__ )
-
-__all__ = ('Torque',)
-
-try:
- import xml.etree.cElementTree as et
-except:
- import xml.etree.ElementTree as et
-
-job_template = """#!/bin/sh
-%s
-GALAXY_LIB="%s"
-if [ "$GALAXY_LIB" != "None" ]; then
- if [ -n "$PYTHONPATH" ]; then
- export PYTHONPATH="$GALAXY_LIB:$PYTHONPATH"
- else
- export PYTHONPATH="$GALAXY_LIB"
- fi
-fi
-%s
-cd %s
-%s
-echo $? > %s
-"""
-
-argmap = { 'destination' : '-q',
- 'Execution_Time' : '-a',
- 'Account_Name' : '-A',
- 'Checkpoint' : '-c',
- 'Error_Path' : '-e',
- 'Group_List' : '-g',
- 'Hold_Types' : '-h',
- 'Join_Paths' : '-j',
- 'Keep_Files' : '-k',
- 'Resource_List' : '-l',
- 'Mail_Points' : '-m',
- 'Mail_Users' : '-M',
- 'Job_Name' : '-N',
- 'Output_Path' : '-o',
- 'Priority' : '-p',
- 'Rerunable' : '-r',
- 'Shell_Path_List' : '-S',
- 'job_array_request' : '-t',
- 'User_List' : '-u',
- 'Variable_List' : '-v' }
-
-class Torque(BaseJobExec):
- def __init__(self, **params):
- self.params = {}
- for k, v in params.items():
- self.params[k] = v
-
- def get_job_template(self, ofile, efile, job_name, job_wrapper, command_line, ecfile):
- pbsargs = { '-o' : ofile,
- '-e' : efile,
- '-N' : job_name }
- for k, v in self.params.items():
- if k == 'plugin':
- continue
- try:
- if not k.startswith('-'):
- k = argmap[k]
- pbsargs[k] = v
- except:
- log.warning('Unrecognized long argument passed to Torque CLI plugin: %s' % k)
- template_pbsargs = ''
- for k, v in pbsargs.items():
- template_pbsargs += '#PBS %s %s\n' % (k, v)
- return job_template % (template_pbsargs,
- job_wrapper.galaxy_lib_dir,
- job_wrapper.get_env_setup_clause(),
- os.path.abspath(job_wrapper.working_directory),
- command_line,
- ecfile)
-
- def submit(self, script_file):
- return 'qsub %s' % script_file
-
- def delete(self, job_id):
- return 'qdel %s' % job_id
-
- def get_status(self, job_ids=None):
- return 'qstat -x'
-
- def get_single_status(self, job_id):
- return 'qstat -f %s' % job_id
-
- def parse_status(self, status, job_ids):
- # in case there's noise in the output, find the big blob 'o xml
- tree = None
- rval = {}
- for line in status.strip().splitlines():
- try:
- tree = et.fromstring(line.strip())
- assert tree.tag == 'Data'
- break
- except Exception, e:
- tree = None
- if tree is None:
- log.warning('No valid qstat XML return from `qstat -x`, got the following: %s' % status)
- return None
- else:
- for job in tree.findall('Job'):
- id = job.find('Job_Id').text
- if id in job_ids:
- state = job.find('job_state').text
- # map PBS job states to Galaxy job states.
- rval[id] = self.__get_job_state(state)
- return rval
-
- def parse_single_status(self, status, job_id):
- for line in status.splitlines():
- line = line.split(' = ')
- if line[0] == 'job_state':
- return line[1]
- # no state found, job has exited
- return job_states.OK
-
- def __get_job_state(self, state):
- return { 'E' : job_states.RUNNING,
- 'R' : job_states.RUNNING,
- 'Q' : job_states.QUEUED,
- 'C' : job_states.OK }.get(state, state)
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/cli_shell/__init__.py
--- a/lib/galaxy/jobs/runners/cli_shell/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""
-Base class for runners which execute commands via a shell
-"""
-
-class BaseShellExec(object):
- def __init__(self, *args, **kwargs):
- raise NotImplementedError()
- def copy(self, rcp_cmd, files, dest):
- raise NotImplementedError()
- def execute(self, cmd, persist=False, timeout=60):
- raise NotImplementedError()
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/cli_shell/rsh.py
--- a/lib/galaxy/jobs/runners/cli_shell/rsh.py
+++ /dev/null
@@ -1,64 +0,0 @@
-"""
-Interface for remote shell commands (rsh, rcp) and derivatives that use the same syntax (ssh, scp)
-"""
-
-import logging
-import os
-import subprocess
-import tempfile
-import time
-
-from galaxy.util.bunch import Bunch
-from galaxy.jobs.runners.cli_shell import BaseShellExec
-
-log = logging.getLogger( __name__ )
-
-__all__ = ('RemoteShell', 'SecureShell', 'GlobusSecureShell')
-
-class RemoteShell(BaseShellExec):
- def __init__(self, rsh='rsh', rcp='rcp', hostname=None, username=None, **kwargs):
- self.rsh = rsh
- self.rcp = rcp
- self.hostname = hostname
- self.username = username
- self.sessions = {}
- def copy(self, rcp_cmd, files, dest):
- pass
- def execute(self, cmd, persist=False, timeout=60):
- # TODO: implement persistence
- if self.username is None:
- fullcmd = '%s %s %s' % (self.rsh, self.hostname, cmd)
- else:
- fullcmd = '%s -l %s %s %s' % (self.rsh, self.username, self.hostname, cmd)
- # Read stdout to a tempfile in case it's large (>65K)
- outf = tempfile.TemporaryFile()
- p = subprocess.Popen(fullcmd, shell=True, stdin=None, stdout=outf, stderr=subprocess.PIPE)
- # poll until timeout
- for i in range(timeout/3):
- r = p.poll()
- if r is not None:
- break
- time.sleep(3)
- else:
- pid = int(p.pid)
- for sig in (15, 9):
- try:
- os.kill(pid, sig)
- time.sleep(3)
- except:
- log.warning('Killing pid %s (cmd: "%s") with signal %s failed' % (p.pid, fullcmd, sig))
- return Bunch(stdout='', stderr='Execution timed out', returncode=-1)
- outf.seek(0)
- return Bunch(stdout=outf.read(), stderr=p.stderr.read(), returncode=p.returncode)
-
-
-class SecureShell(RemoteShell):
- SSH_NEW_KEY_STRING = 'Are you sure you want to continue connecting'
- def __init__(self, rsh='ssh', rcp='scp', **kwargs):
- rsh += ' -oStrictHostKeyChecking=yes -oConnectTimeout=60'
- rcp += ' -oStrictHostKeyChecking=yes -oConnectTimeout=60'
- super(SecureShell, self).__init__(rsh=rsh, rcp=rcp, **kwargs)
-
-class GlobusSecureShell(SecureShell):
- def __init__(self, rsh='gsissh', rcp='gsiscp', **kwargs):
- super(SecureShell, self).__init__(rsh=rsh, rcp=rcp, **kwargs)
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/__init__.py
--- a/lib/galaxy/jobs/runners/util/__init__.py
+++ b/lib/galaxy/jobs/runners/util/__init__.py
@@ -3,5 +3,8 @@
processes and interfacing with job managers. This module should contain
functionality shared between Galaxy and the LWR.
"""
+from galaxy.util.bunch import Bunch
-from galaxy.util.bunch import Bunch
+from .kill import kill_pid
+
+__all__ = [kill_pid, Bunch]
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/__init__.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/__init__.py
@@ -0,0 +1,56 @@
+"""
+"""
+from glob import glob
+from os.path import basename, join
+from os import getcwd
+
+DEFAULT_SHELL_PLUGIN = 'LocalShell'
+
+
+class CliInterface(object):
+ """
+ High-level interface for loading shell and job plugins and matching
+ them to specified parameters.
+ """
+
+ def __init__(self, code_dir='lib'):
+ """
+ """
+ def __load(module_path, d):
+ module_pattern = join(join(getcwd(), code_dir, *module_path.split('.')), '*.py')
+ for file in glob(module_pattern):
+ if basename(file).startswith('_'):
+ continue
+ module_name = '%s.%s' % (module_path, basename(file).rsplit('.py', 1)[0])
+ module = __import__(module_name)
+ for comp in module_name.split(".")[1:]:
+ module = getattr(module, comp)
+ for name in module.__all__:
+ try:
+ d[name] = getattr(module, name)
+ except TypeError:
+ raise TypeError("Invalid type for name %s" % name)
+
+ self.cli_shells = {}
+ self.cli_job_interfaces = {}
+
+ module_prefix = self.__module__
+ __load('%s.shell' % module_prefix, self.cli_shells)
+ __load('%s.job' % module_prefix, self.cli_job_interfaces)
+
+ def get_plugins(self, shell_params, job_params):
+ """
+ Return shell and job interface defined by and configured via
+ specified params.
+ """
+ shell_plugin = shell_params.get('plugin', DEFAULT_SHELL_PLUGIN)
+ job_plugin = job_params['plugin']
+ shell = self.cli_shells[shell_plugin](**shell_params)
+ job_interface = self.cli_job_interfaces[job_plugin](**job_params)
+ return shell, job_interface
+
+
+def split_params(params):
+ shell_params = dict((k.replace('shell_', '', 1), v) for k, v in params.items() if k.startswith('shell_'))
+ job_params = dict((k.replace('job_', '', 1), v) for k, v in params.items() if k.startswith('job_'))
+ return shell_params, job_params
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/job/__init__.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/job/__init__.py
@@ -0,0 +1,58 @@
+"""
+Abstract base class for cli job plugins.
+"""
+from abc import ABCMeta, abstractmethod
+
+
+class BaseJobExec(object):
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def __init__(self, **params):
+ """
+ Constructor for CLI job executor.
+ """
+
+ def job_script_kwargs(self, ofile, efile, job_name):
+ """ Return extra keyword argument for consumption by job script
+ module.
+ """
+ return {}
+
+ @abstractmethod
+ def submit(self, script_file):
+ """
+ Given specified script_file path, yield command to submit it
+ to external job manager.
+ """
+
+ @abstractmethod
+ def delete(self, job_id):
+ """
+ Given job id, return command to stop execution or dequeue specified
+ job.
+ """
+
+ @abstractmethod
+ def get_status(self, job_ids=None):
+ """
+ Return command to get statuses of specified job ids.
+ """
+
+ @abstractmethod
+ def get_single_status(self, job_id):
+ """
+ Return command to get the status of a single, specified job.
+ """
+
+ @abstractmethod
+ def parse_status(self, status, job_ids):
+ """
+ Parse the statuses of output from get_status command.
+ """
+
+ @abstractmethod
+ def parse_single_status(self, status, job_id):
+ """
+ Parse the status of output from get_single_status command.
+ """
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/job/slurm_torque.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/job/slurm_torque.py
@@ -0,0 +1,29 @@
+import re
+from .torque import Torque
+
+__all__ = ('SlurmTorque',)
+
+
+class SlurmTorque(Torque):
+ """ A CLI job executor for Slurm's Torque compatibility mode. This differs
+ from real torque CLI in that -x command line is not available so job status
+ needs to be parsed from qstat table instead of XML.
+ """
+
+ def get_status(self, job_ids=None):
+ return 'qstat'
+
+ def parse_status(self, status, job_ids):
+ rval = {}
+ for line in status.strip().splitlines():
+ if line.startswith("Job ID"):
+ continue
+ line_parts = re.compile("\s+").split(line)
+ if len(line_parts) < 5:
+ continue
+ id = line_parts[0]
+ state = line_parts[4]
+ if id in job_ids:
+ # map PBS job states to Galaxy job states.
+ rval[id] = self._get_job_state(state)
+ return rval
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/job/torque.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/job/torque.py
@@ -0,0 +1,120 @@
+try:
+ import xml.etree.cElementTree as et
+except:
+ import xml.etree.ElementTree as et
+
+try:
+ from galaxy.model import Job
+ job_states = Job.states
+except ImportError:
+ # Not in Galaxy, map Galaxy job states to LWR ones.
+ from galaxy.util import enum
+ job_states = enum(RUNNING='running', OK='complete', QUEUED='queued')
+
+from ..job import BaseJobExec
+
+__all__ = ('Torque',)
+
+from logging import getLogger
+log = getLogger(__name__)
+
+argmap = {'destination': '-q',
+ 'Execution_Time': '-a',
+ 'Account_Name': '-A',
+ 'Checkpoint': '-c',
+ 'Error_Path': '-e',
+ 'Group_List': '-g',
+ 'Hold_Types': '-h',
+ 'Join_Paths': '-j',
+ 'Keep_Files': '-k',
+ 'Resource_List': '-l',
+ 'Mail_Points': '-m',
+ 'Mail_Users': '-M',
+ 'Job_Name': '-N',
+ 'Output_Path': '-o',
+ 'Priority': '-p',
+ 'Rerunable': '-r',
+ 'Shell_Path_List': '-S',
+ 'job_array_request': '-t',
+ 'User_List': '-u',
+ 'Variable_List': '-v'}
+
+
+class Torque(BaseJobExec):
+
+ def __init__(self, **params):
+ self.params = {}
+ for k, v in params.items():
+ self.params[k] = v
+
+ def job_script_kwargs(self, ofile, efile, job_name):
+ pbsargs = {'-o': ofile,
+ '-e': efile,
+ '-N': job_name}
+ for k, v in self.params.items():
+ if k == 'plugin':
+ continue
+ try:
+ if not k.startswith('-'):
+ k = argmap[k]
+ pbsargs[k] = v
+ except:
+ log.warning('Unrecognized long argument passed to Torque CLI plugin: %s' % k)
+ template_pbsargs = ''
+ for k, v in pbsargs.items():
+ template_pbsargs += '#PBS %s %s\n' % (k, v)
+ return dict(headers=template_pbsargs)
+
+ def submit(self, script_file):
+ return 'qsub %s' % script_file
+
+ def delete(self, job_id):
+ return 'qdel %s' % job_id
+
+ def get_status(self, job_ids=None):
+ return 'qstat -x'
+
+ def get_single_status(self, job_id):
+ return 'qstat -f %s' % job_id
+
+ def parse_status(self, status, job_ids):
+ # in case there's noise in the output, find the big blob 'o xml
+ tree = None
+ rval = {}
+ for line in status.strip().splitlines():
+ try:
+ tree = et.fromstring(line.strip())
+ assert tree.tag == 'Data'
+ break
+ except Exception:
+ tree = None
+ if tree is None:
+ log.warning('No valid qstat XML return from `qstat -x`, got the following: %s' % status)
+ return None
+ else:
+ for job in tree.findall('Job'):
+ id = job.find('Job_Id').text
+ if id in job_ids:
+ state = job.find('job_state').text
+ # map PBS job states to Galaxy job states.
+ rval[id] = self._get_job_state(state)
+ return rval
+
+ def parse_single_status(self, status, job_id):
+ for line in status.splitlines():
+ line = line.split(' = ')
+ if line[0] == 'job_state':
+ return self._get_job_state(line[1].strip())
+ # no state found, job has exited
+ return job_states.OK
+
+ def _get_job_state(self, state):
+ try:
+ return {
+ 'E': job_states.RUNNING,
+ 'R': job_states.RUNNING,
+ 'Q': job_states.QUEUED,
+ 'C': job_states.OK
+ }.get(state)
+ except KeyError:
+ raise KeyError("Failed to map torque status code [%s] to job state." % state)
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/shell/__init__.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/shell/__init__.py
@@ -0,0 +1,19 @@
+"""
+Abstract base class for runners which execute commands via a shell.
+"""
+from abc import ABCMeta, abstractmethod
+
+
+class BaseShellExec(object):
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def __init__(self, *args, **kwargs):
+ """
+ Constructor for shell executor instance.
+ """
+
+ def execute(self, cmd, persist=False, timeout=60):
+ """
+ Execute the specified command via defined shell.
+ """
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/shell/local.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/shell/local.py
@@ -0,0 +1,61 @@
+from tempfile import TemporaryFile
+from time import sleep
+from subprocess import Popen, PIPE
+
+from ..shell import BaseShellExec
+from ....util import Bunch, kill_pid
+
+from logging import getLogger
+log = getLogger(__name__)
+
+TIMEOUT_ERROR_MESSAGE = u'Execution timed out'
+TIMEOUT_RETURN_CODE = -1
+DEFAULT_TIMEOUT = 60
+DEFAULT_TIMEOUT_CHECK_INTERVAL = 3
+
+
+class LocalShell(BaseShellExec):
+ """
+
+ >>> shell = LocalShell()
+ >>> def exec_python(script, **kwds): return shell.execute('python -c "%s"' % script, **kwds)
+ >>> exec_result = exec_python("from __future__ import print_function; print('Hello World')")
+ >>> exec_result.stderr == u''
+ True
+ >>> exec_result.stdout.strip() == u'Hello World'
+ True
+ >>> exec_result = exec_python("import time; time.sleep(90)", timeout=3, timeout_check_interval=1)
+ >>> exec_result.stdout == u''
+ True
+ >>> exec_result.stderr == 'Execution timed out'
+ True
+ >>> exec_result.returncode == TIMEOUT_RETURN_CODE
+ True
+ """
+
+ def __init__(self, **kwds):
+ pass
+
+ def execute(self, cmd, persist=False, timeout=DEFAULT_TIMEOUT, timeout_check_interval=DEFAULT_TIMEOUT_CHECK_INTERVAL, **kwds):
+ outf = TemporaryFile()
+ p = Popen(cmd, shell=True, stdin=None, stdout=outf, stderr=PIPE)
+ # poll until timeout
+
+ for i in range(int(timeout / timeout_check_interval)):
+ r = p.poll()
+ if r is not None:
+ break
+ sleep(timeout_check_interval)
+ else:
+ kill_pid(p.pid)
+ return Bunch(stdout=u'', stderr=TIMEOUT_ERROR_MESSAGE, returncode=TIMEOUT_RETURN_CODE)
+ outf.seek(0)
+ return Bunch(stdout=_read_str(outf), stderr=_read_str(p.stderr), returncode=p.returncode)
+
+
+def _read_str(stream):
+ contents = stream.read()
+ return contents.decode('UTF-8') if isinstance(contents, bytes) else contents
+
+
+__all__ = ('LocalShell',)
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/cli/shell/rsh.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/cli/shell/rsh.py
@@ -0,0 +1,40 @@
+from .local import LocalShell
+
+from logging import getLogger
+log = getLogger(__name__)
+
+__all__ = ('RemoteShell', 'SecureShell', 'GlobusSecureShell')
+
+
+class RemoteShell(LocalShell):
+
+ def __init__(self, rsh='rsh', rcp='rcp', hostname='localhost', username=None, **kwargs):
+ super(RemoteShell, self).__init__(**kwargs)
+ self.rsh = rsh
+ self.rcp = rcp
+ self.hostname = hostname
+ self.username = username
+ self.sessions = {}
+
+ def execute(self, cmd, persist=False, timeout=60):
+ # TODO: implement persistence
+ if self.username is None:
+ fullcmd = '%s %s %s' % (self.rsh, self.hostname, cmd)
+ else:
+ fullcmd = '%s -l %s %s %s' % (self.rsh, self.username, self.hostname, cmd)
+ return super(RemoteShell, self).execute(fullcmd, persist, timeout)
+
+
+class SecureShell(RemoteShell):
+ SSH_NEW_KEY_STRING = 'Are you sure you want to continue connecting'
+
+ def __init__(self, rsh='ssh', rcp='scp', **kwargs):
+ rsh += ' -oStrictHostKeyChecking=yes -oConnectTimeout=60'
+ rcp += ' -oStrictHostKeyChecking=yes -oConnectTimeout=60'
+ super(SecureShell, self).__init__(rsh=rsh, rcp=rcp, **kwargs)
+
+
+class GlobusSecureShell(SecureShell):
+
+ def __init__(self, rsh='gsissh', rcp='gsiscp', **kwargs):
+ super(GlobusSecureShell, self).__init__(rsh=rsh, rcp=rcp, **kwargs)
diff -r a014c2d841a8490f017086c687997a191f3a602f -r 9eb2b42e21fee519b1ea5e9c791bb6b8989f6b5c lib/galaxy/jobs/runners/util/kill.py
--- /dev/null
+++ b/lib/galaxy/jobs/runners/util/kill.py
@@ -0,0 +1,65 @@
+import os
+from platform import system
+from time import sleep
+from subprocess import Popen
+
+try:
+ from psutil import Process, NoSuchProcess
+except ImportError:
+ """ Don't make psutil a strict requirement, but use if available. """
+ Process = None
+
+
+def kill_pid(pid, use_psutil=True):
+ if use_psutil and Process:
+ _psutil_kill_pid(pid)
+ else:
+ _stock_kill_pid(pid)
+
+
+def _psutil_kill_pid(pid):
+ """
+ http://stackoverflow.com/questions/1230669/subprocess-deleting-child-proces…
+ """
+ try:
+ parent = Process(pid)
+ for child in parent.get_children(recursive=True):
+ child.kill()
+ parent.kill()
+ except NoSuchProcess:
+ return
+
+
+def _stock_kill_pid(pid):
+ is_windows = system() == 'Windows'
+
+ if is_windows:
+ __kill_windows(pid)
+ else:
+ __kill_posix(pid)
+
+
+def __kill_windows(pid):
+ try:
+ Popen("taskkill /F /T /PID %i" % pid, shell=True)
+ except Exception:
+ pass
+
+
+def __kill_posix(pid):
+ def __check_pid():
+ try:
+ os.kill(pid, 0)
+ return True
+ except OSError:
+ return False
+
+ if __check_pid():
+ for sig in [15, 9]:
+ try:
+ os.killpg(pid, sig)
+ except OSError:
+ return
+ sleep(1)
+ if not __check_pid():
+ return
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
6 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/fef831b5e2bd/
Changeset: fef831b5e2bd
User: nsoranzo
Date: 2014-05-05 19:07:07
Summary: Update code checks and documentation after commit 17a2dc7.
Affected #: 1 file
diff -r fe6c48de949dd4e640d3632ab23cfe6ab5fa0494 -r fef831b5e2bd3feaaef58212eb695943d8969212 lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -1,9 +1,9 @@
-from __future__ import absolute_import
-
"""
API operations for Workflows
"""
+from __future__ import absolute_import
+
import logging
from sqlalchemy import desc, or_
from galaxy import exceptions
@@ -12,7 +12,6 @@
from galaxy.web import _future_expose_api as expose_api
from galaxy.web.base.controller import BaseAPIController, url_for, UsesStoredWorkflowMixin
from galaxy.web.base.controller import UsesHistoryMixin
-from galaxy.workflow.modules import module_factory
from galaxy.workflow.run import invoke
from galaxy.workflow.run import WorkflowRunConfig
from galaxy.workflow.extract import extract_workflow
@@ -120,76 +119,79 @@
workflow will be created for this user. Otherwise, workflow_id must be
specified and this API method will cause a workflow to execute.
- :param installed_repository_file The path of a workflow to import. Either workflow_id or installed_repository_file must be specified
+ :param installed_repository_file The path of a workflow to import. Either workflow_id, installed_repository_file or from_history_id must be specified
:type installed_repository_file str
- :param workflow_id: an existing workflow id. Either workflow_id or installed_repository_file must be specified
+ :param workflow_id: An existing workflow id. Either workflow_id, installed_repository_file or from_history_id must be specified
:type workflow_id: str
- :param parameters: See _update_step_parameters()
+ :param parameters: If workflow_id is set - see _update_step_parameters()
:type parameters: dict
- :param ds_map: A dictionary mapping each input step id to a dictionary with 2 keys: 'src' (which can be 'ldda', 'ld' or 'hda') and 'id' (which should be the id of a LibraryDatasetDatasetAssociation, LibraryDataset or HistoryDatasetAssociation respectively)
+ :param ds_map: If workflow_id is set - a dictionary mapping each input step id to a dictionary with 2 keys: 'src' (which can be 'ldda', 'ld' or 'hda') and 'id' (which should be the id of a LibraryDatasetDatasetAssociation, LibraryDataset or HistoryDatasetAssociation respectively)
:type ds_map: dict
- :param no_add_to_history: if present in the payload with any value, the input datasets will not be added to the selected history
+ :param no_add_to_history: If workflow_id is set - if present in the payload with any value, the input datasets will not be added to the selected history
:type no_add_to_history: str
- :param history: Either the name of a new history or "hist_id=HIST_ID" where HIST_ID is the id of an existing history
+ :param history: If workflow_id is set - optional history where to run the workflow, either the name of a new history or "hist_id=HIST_ID" where HIST_ID is the id of an existing history. If not specified, the workflow will be run a new unnamed history
:type history: str
- :param replacement_params: A dictionary used when renaming datasets
+ :param replacement_params: If workflow_id is set - an optional dictionary used when renaming datasets
:type replacement_params: dict
- :param from_history_id: Id of history to extract a workflow from. Should not be used with worfklow_id or installed_repository_file.
+ :param from_history_id: Id of history to extract a workflow from. Either workflow_id, installed_repository_file or from_history_id must be specified
:type from_history_id: str
- :param job_ids: If from_history_id is set - this should be a list of jobs to include when extracting workflow from history.
+ :param job_ids: If from_history_id is set - optional list of jobs to include when extracting workflow from history.
:type job_ids: str
- :param dataset_ids: If from_history_id is set - this should be a list of HDA ids corresponding to workflow inputs when extracting workflow from history.
+ :param dataset_ids: If from_history_id is set - optional list of HDA ids corresponding to workflow inputs when extracting workflow from history.
:type dataset_ids: str
+
+ :param workflow_name: If from_history_id is set - name of the workflow to create
+ :type workflow_name: str
"""
- # Pull parameters out of payload.
- workflow_id = payload.get('workflow_id', None)
- param_map = payload.get('parameters', {})
- ds_map = payload.get('ds_map', {})
+ if len( set( ['workflow_id', 'installed_repository_file', 'from_history_id'] ).intersection( payload ) ) > 1:
+ trans.response.status = 403
+ return "Only one among 'workflow_id', 'installed_repository_file', 'from_history_id' must be specified"
+
+ if 'installed_repository_file' in payload:
+ workflow_controller = trans.webapp.controllers[ 'workflow' ]
+ result = workflow_controller.import_workflow( trans=trans,
+ cntrller='api',
+ **payload)
+ return result
+
+ if 'from_history_id' in payload:
+ from_history_id = payload.get( 'from_history_id' )
+ history = self.get_history( trans, from_history_id, check_ownership=False, check_accessible=True )
+ job_ids = map( trans.security.decode_id, payload.get( 'job_ids', [] ) )
+ dataset_ids = map( trans.security.decode_id, payload.get( 'dataset_ids', [] ) )
+ workflow_name = payload[ 'workflow_name' ]
+ stored_workflow = extract_workflow(
+ trans=trans,
+ user=trans.get_user(),
+ history=history,
+ job_ids=job_ids,
+ dataset_ids=dataset_ids,
+ workflow_name=workflow_name,
+ )
+ item = stored_workflow.to_dict( value_mapper={ 'id': trans.security.encode_id } )
+ item[ 'url' ] = url_for( 'workflow', id=item[ 'id' ] )
+ return item
+
+ workflow_id = payload.get( 'workflow_id', None )
+ if not workflow_id:
+ trans.response.status = 403
+ return "Either workflow_id, installed_repository_file or from_history_id must be specified"
+
+ # Pull other parameters out of payload.
+ param_map = payload.get( 'parameters', {} )
+ ds_map = payload.get( 'ds_map', {} )
add_to_history = 'no_add_to_history' not in payload
- history_param = payload.get('history', '')
-
- # Get/create workflow.
- if not workflow_id:
- # create new
- if 'installed_repository_file' in payload:
- workflow_controller = trans.webapp.controllers[ 'workflow' ]
- result = workflow_controller.import_workflow( trans=trans,
- cntrller='api',
- **payload)
- return result
- if 'from_history_id' in payload:
- from_history_id = payload.get( 'from_history_id' )
- history = self.get_history( trans, from_history_id, check_ownership=False, check_accessible=True )
- job_ids = map( trans.security.decode_id, payload.get( "job_ids", [] ) )
- dataset_ids = map( trans.security.decode_id, payload.get( "dataset_ids", [] ) )
- workflow_name = payload[ "workflow_name" ]
- stored_workflow = extract_workflow(
- trans=trans,
- user=trans.get_user(),
- history=history,
- job_ids=job_ids,
- dataset_ids=dataset_ids,
- workflow_name=workflow_name,
- )
- item = stored_workflow.to_dict( value_mapper={ "id": trans.security.encode_id } )
- item[ 'url' ] = url_for( 'workflow', id=item[ "id" ] )
- return item
-
- trans.response.status = 403
- return "Either workflow_id or installed_repository_file must be specified"
- if 'installed_repository_file' in payload:
- trans.response.status = 403
- return "installed_repository_file may not be specified with workflow_id"
+ history_param = payload.get( 'history', '' )
# Get workflow + accessibility check.
stored_workflow = trans.sa_session.query(self.app.model.StoredWorkflow).get(
https://bitbucket.org/galaxy/galaxy-central/commits/ef7bd8c935bc/
Changeset: ef7bd8c935bc
User: nsoranzo
Date: 2014-05-06 19:15:26
Summary: Merged galaxy/galaxy-central into default
Affected #: 117 files
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 config/plugins/visualizations/charts/static/charts/heatmap/config.js
--- a/config/plugins/visualizations/charts/static/charts/heatmap/config.js
+++ b/config/plugins/visualizations/charts/static/charts/heatmap/config.js
@@ -1,10 +1,12 @@
define([], function() {
return {
- title : 'Heatmap',
- library : '',
- tag : 'div',
+ title : 'Heatmap',
+ library : '',
+ tag : 'div',
use_panels : true,
+
+ // columns
columns : {
col_label : {
title : 'Columns',
@@ -19,6 +21,7 @@
},
},
+ // settings
settings: {
color_set : {
title : 'Color scheme',
@@ -107,6 +110,38 @@
value : 'wysiwyg'
}
]
+ },
+
+ sorting : {
+ title : 'Sorting',
+ info : 'How should the columns be clustered?',
+ type : 'select',
+ init : 'hclust',
+ data : [
+ {
+ label : 'Read from dataset',
+ value : 'hclust'
+ },
+ {
+ label : 'Sort column and row labels',
+ value : 'byboth'
+ },
+ {
+ label : 'Sort column labels',
+ value : 'bycolumns'
+ },
+ {
+ label : 'Sort by rows',
+ value : 'byrow'
+ }
+ ]
+ }
+ },
+
+ // menu definition
+ menu : function() {
+ return {
+ color_set : this.settings.color_set
}
}
};
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 config/plugins/visualizations/charts/static/charts/heatmap/heatmap-plugin.js
--- a/config/plugins/visualizations/charts/static/charts/heatmap/heatmap-plugin.js
+++ b/config/plugins/visualizations/charts/static/charts/heatmap/heatmap-plugin.js
@@ -180,26 +180,22 @@
//
// add ui elements
//
- // create ui elements
+ // create tooltip
this.$tooltip = $(this._templateTooltip());
- this.$select = $(this._templateSelect());
-
- // append
this.$el.append(this.$tooltip);
- this.$el.append(this.$select);
-
- // add event to select field
- this.$select.on('change', function(){
- self._sortByOrder(this.value);
- });
//
// finally draw the heatmap
//
- this._draw();
+ this._build();
+
+ // catch window resize event
+ $(window).resize(function () {
+ self._build();
+ });
},
- _draw: function() {
+ _build: function() {
// link this
var self = this;
@@ -212,7 +208,7 @@
this.height = this.heightContainer;
// calculate cell size
- this.cellSize = Math.min(((this.height - 50) / (this.rowNumber + this.margin.top + this.margin.bottom)),
+ this.cellSize = Math.min(((this.height) / (this.rowNumber + this.margin.top + this.margin.bottom)),
(this.width / (this.colNumber + this.margin.left + this.margin.right)));
// set font size
@@ -227,22 +223,27 @@
var width = this.width;
var height = this.height;
+ // reset svg
+ if (this.svg !== undefined) {
+ this.$el.find('svg').remove();
+ }
+
// add main group and translate
this.svg = d3.select(this.$el[0]).append('svg')
.append('g')
.attr('transform', 'translate(' + (this.widthContainer - width) / 2 + ',' +
- (this.heightContainer - height) / 2 + ')')
-
+ (this.heightContainer - height) / 2 + ')');
+
// reset sorting
this.rowSortOrder = false;
this.colSortOrder = false;
// build
- this._buildRowLabels();
- this._buildColLabels();
- this._buildHeatMap();
- this._buildLegend();
- this._buildTitle();
+ this.d3RowLabels = this._buildRowLabels();
+ this.d3ColLabels = this._buildColLabels();
+ this.d3HeatMap = this._buildHeatMap();
+ this.d3Legend = this._buildLegend();
+ this.d3Title = this._buildTitle();
},
// build title
@@ -258,7 +259,7 @@
var title = this.options.title;
// add title
- this.svg.append('g')
+ return this.svg.append('g')
.append('text')
.attr('x', width / 2)
.attr('y', height + 3 * cellSize + fontSize + 3)
@@ -323,6 +324,9 @@
})
.attr('y', height + cellSize - 3)
.style('font-size', fontSize + 'px');
+
+ // return
+ return legend;
},
// build column labels
@@ -366,6 +370,9 @@
self._sortByLabel('c', 'row', self.rowNumber, i, self.colSortOrder);
d3.select('#order').property('selectedIndex', 4).node().focus();
});
+
+ // return
+ return colLabels;
},
// build row labels
@@ -409,6 +416,9 @@
self._sortByLabel('r', 'col', self.colNumber, i, self.rowSortOrder);
d3.select('#order').property('selectedIndex', 4).node().focus();
});
+
+ // return
+ return rowLabels;
},
// build heat map
@@ -424,7 +434,7 @@
var colLabel = this.colLabel;
// heat map
- var heatMap = this.svg.append('g').attr('class','g3')
+ var heatmap = this.svg.append('g').attr('class','g3')
.selectAll('.cellg')
.data(self.data, function(d) {
return d.row + ':' + d.col;
@@ -466,6 +476,9 @@
d3.selectAll('.colLabel').classed('text-highlight',false);
d3.select('#heatmap-tooltip').classed('hidden', true);
});
+
+ // return
+ return heatmap;
},
// change ordering of cells
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 config/plugins/visualizations/charts/static/views/viewport.js
--- a/config/plugins/visualizations/charts/static/views/viewport.js
+++ b/config/plugins/visualizations/charts/static/views/viewport.js
@@ -6,7 +6,8 @@
return Backbone.View.extend({
// list of canvas elements
- canvas: [],
+ container_list: [],
+ canvas_list: [],
// initialize
initialize: function(app, options) {
@@ -26,7 +27,7 @@
this._fullscreen(this.$el, 80);
// create canvas element
- this._createCanvas('div');
+ this._createContainer('div');
// events
var self = this;
@@ -38,6 +39,7 @@
this.chart.on('set:state', function() {
// get info element
var $info = self.$el.find('#info');
+ var $container = self.$el.find('container');
// get icon
var $icon = $info.find('#icon');
@@ -49,11 +51,15 @@
$info.show();
$info.find('#text').html(self.chart.get('state_info'));
+ // hide containers
+ $container.hide();
+
// check status
var state = self.chart.get('state');
switch (state) {
case 'ok':
$info.hide();
+ $container.show()
break;
case 'failed':
$icon.addClass('fa fa-warning');
@@ -86,29 +92,35 @@
},
// creates n canvas elements
- _createCanvas: function(tag, n) {
+ _createContainer: function(tag, n) {
// check size of requested canvas elements
n = n || 1;
// clear previous canvas elements
- for (var i in this.canvas) {
- this.canvas[i].remove();
- this.canvas.slice(i, 0);
+ for (var i in this.container_list) {
+ this.container_list[i].remove();
}
+ // reset lists
+ this.container_list = [];
+ this.canvas_list = [];
+
// create requested canvas elements
for (var i = 0; i < n; i++) {
// create element
- var canvas_el = $(this._templateCanvas(tag, parseInt(100 / n)));
+ var canvas_el = $(this._templateContainer(tag, parseInt(100 / n)));
// add to view
this.$el.append(canvas_el);
- // find canvas element
+ // add to list
+ this.container_list[i] = canvas_el;
+
+ // add a separate list for canvas elements
if (tag == 'svg') {
- this.canvas[i] = d3.select(canvas_el[0]);
+ this.canvas_list[i] = d3.select(canvas_el.find('#canvas')[0]);
} else {
- this.canvas[i] = canvas_el;
+ this.canvas_list[i] = canvas_el.find('#canvas');
}
}
},
@@ -137,7 +149,7 @@
}
// create canvas element and add to canvas list
- this._createCanvas(this.chart_settings.tag, n_panels);
+ this._createContainer(this.chart_settings.tag, n_panels);
// set chart state
chart.state('wait', 'Please wait...');
@@ -145,6 +157,7 @@
// clean up data if there is any from previous jobs
if (!this.chart_settings.execute ||
(this.chart_settings.execute && chart.get('modified'))) {
+
// reset jobs
this.app.jobs.cleanup(chart);
@@ -156,7 +169,7 @@
var self = this;
require(['plugin/charts/' + chart_type + '/' + chart_type], function(ChartView) {
// create chart
- var view = new ChartView(self.app, {canvas : self.canvas});
+ var view = new ChartView(self.app, {canvas : self.canvas_list});
// request data
if (self.chart_settings.execute) {
@@ -269,8 +282,11 @@
},
// template svg/div element
- _templateCanvas: function(tag, width) {
- return '<' + tag + ' class="canvas" style="float: left; display: block; width:' + width + '%; height: 100%;"/>';
+ _templateContainer: function(tag, width) {
+ return '<div class="container" style="float: left; display: block; width:' + width + '%; height: 100%;">' +
+ '<div id="menu"/>' +
+ '<' + tag + ' id="canvas" class="canvas" style="display: block; width:100%; height: inherit;">' +
+ '</div>';
}
});
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/app.py
--- a/lib/galaxy/app.py
+++ b/lib/galaxy/app.py
@@ -5,6 +5,7 @@
from galaxy import config, jobs
import galaxy.model
import galaxy.security
+from galaxy import dataset_collections
import galaxy.quota
from galaxy.tags.tag_handler import GalaxyTagHandler
from galaxy.visualization.genomes import Genomes
@@ -54,6 +55,8 @@
self._configure_security()
# Tag handler
self.tag_handler = GalaxyTagHandler()
+ # Dataset Collection Plugins
+ self.dataset_collections_service = dataset_collections.DatasetCollectionsService(self)
# Genomes
self.genomes = Genomes( self )
# Data providers registry.
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/dataset_collections/__init__.py
--- /dev/null
+++ b/lib/galaxy/dataset_collections/__init__.py
@@ -0,0 +1,274 @@
+from .registry import DatasetCollectionTypesRegistry
+from .matching import MatchingCollections
+from .type_description import CollectionTypeDescriptionFactory
+
+from galaxy import model
+from galaxy.exceptions import MessageException
+from galaxy.exceptions import ItemAccessibilityException
+from galaxy.exceptions import RequestParameterInvalidException
+from galaxy.web.base.controller import (
+ UsesHistoryDatasetAssociationMixin,
+ UsesLibraryMixinItems,
+ UsesTagsMixin,
+)
+from galaxy.managers import hdas # TODO: Refactor all mixin use into managers.
+
+from galaxy.util import validation
+from galaxy.util import odict
+
+import logging
+log = logging.getLogger( __name__ )
+
+
+ERROR_INVALID_ELEMENTS_SPECIFICATION = "Create called with invalid parameters, must specify element identifiers."
+ERROR_NO_COLLECTION_TYPE = "Create called without specifing a collection type."
+
+
+class DatasetCollectionsService(
+ UsesHistoryDatasetAssociationMixin,
+ UsesLibraryMixinItems,
+ UsesTagsMixin,
+):
+ """
+ Abstraction for interfacing with dataset collections instance - ideally abstarcts
+ out model and plugin details.
+ """
+
+ def __init__( self, app ):
+ self.type_registry = DatasetCollectionTypesRegistry( app )
+ self.collection_type_descriptions = CollectionTypeDescriptionFactory( self.type_registry )
+ self.model = app.model
+ self.security = app.security
+ self.hda_manager = hdas.HDAManager()
+
+ def create(
+ self,
+ trans,
+ parent, # PRECONDITION: security checks on ability to add to parent occurred during load.
+ name,
+ collection_type,
+ element_identifiers=None,
+ elements=None,
+ implicit_collection_info=None,
+ ):
+ """
+ """
+ dataset_collection = self.__create_dataset_collection(
+ trans=trans,
+ collection_type=collection_type,
+ element_identifiers=element_identifiers,
+ elements=elements,
+ )
+ if isinstance( parent, model.History ):
+ dataset_collection_instance = self.model.HistoryDatasetCollectionAssociation(
+ collection=dataset_collection,
+ name=name,
+ )
+ if implicit_collection_info:
+ for input_name, input_collection in implicit_collection_info[ "implicit_inputs" ]:
+ dataset_collection_instance.add_implicit_input_collection( input_name, input_collection )
+ for output_dataset in implicit_collection_info.get( "outputs_datasets" ):
+ output_dataset.hidden_beneath_collection_instance = dataset_collection_instance
+ trans.sa_session.add( output_dataset )
+
+ dataset_collection_instance.implicit_output_name = implicit_collection_info[ "implicit_output_name" ]
+ # Handle setting hid
+ parent.add_dataset_collection( dataset_collection_instance )
+ elif isinstance( parent, model.LibraryFolder ):
+ dataset_collection_instance = self.model.LibraryDatasetCollectionAssociation(
+ collection=dataset_collection,
+ folder=parent,
+ name=name,
+ )
+ else:
+ message = "Internal logic error - create called with unknown parent type %s" % type( parent )
+ log.exception( message )
+ raise MessageException( message )
+
+ return self.__persist( dataset_collection_instance )
+
+ def __create_dataset_collection(
+ self,
+ trans,
+ collection_type,
+ element_identifiers=None,
+ elements=None,
+ ):
+ if element_identifiers is None and elements is None:
+ raise RequestParameterInvalidException( ERROR_INVALID_ELEMENTS_SPECIFICATION )
+ if not collection_type:
+ raise RequestParameterInvalidException( ERROR_NO_COLLECTION_TYPE )
+ collection_type_description = self.collection_type_descriptions.for_collection_type( collection_type )
+ if elements is None:
+ if collection_type_description.has_subcollections( ):
+ # Nested collection - recursively create collections and update identifiers.
+ self.__recursively_create_collections( trans, element_identifiers )
+ elements = self.__load_elements( trans, element_identifiers )
+ # else if elements is set, it better be an ordered dict!
+
+ type_plugin = collection_type_description.rank_type_plugin()
+ dataset_collection = type_plugin.build_collection( elements )
+ dataset_collection.collection_type = collection_type
+ return dataset_collection
+
+ def delete( self, trans, instance_type, id ):
+ dataset_collection_instance = self.get_dataset_collection_instance( trans, instance_type, id, check_ownership=True )
+ dataset_collection_instance.deleted = True
+ trans.sa_session.add( dataset_collection_instance )
+ trans.sa_session.flush( )
+
+ def update( self, trans, instance_type, id, payload ):
+ dataset_collection_instance = self.get_dataset_collection_instance( trans, instance_type, id, check_ownership=True )
+ if trans.user is None:
+ anon_allowed_payload = {}
+ if 'deleted' in payload:
+ anon_allowed_payload[ 'deleted' ] = payload[ 'deleted' ]
+ if 'visible' in payload:
+ anon_allowed_payload[ 'visible' ] = payload[ 'visible' ]
+ payload = self._validate_and_parse_update_payload( anon_allowed_payload )
+ else:
+ payload = self._validate_and_parse_update_payload( payload )
+ changed = self._set_from_dict( trans, dataset_collection_instance, payload )
+ return changed
+
+ def _set_from_dict( self, trans, dataset_collection_instance, new_data ):
+ # Blatantly stolen from UsesHistoryDatasetAssociationMixin.set_hda_from_dict.
+
+ # send what we can down into the model
+ changed = dataset_collection_instance.set_from_dict( new_data )
+ # the rest (often involving the trans) - do here
+ if 'annotation' in new_data.keys() and trans.get_user():
+ dataset_collection_instance.add_item_annotation( trans.sa_session, trans.get_user(), dataset_collection_instance.collection, new_data[ 'annotation' ] )
+ changed[ 'annotation' ] = new_data[ 'annotation' ]
+ if 'tags' in new_data.keys() and trans.get_user():
+ self.set_tags_from_list( trans, dataset_collection_instance.collection, new_data[ 'tags' ], user=trans.user )
+
+ if changed.keys():
+ trans.sa_session.flush()
+
+ return changed
+
+ def _validate_and_parse_update_payload( self, payload ):
+ validated_payload = {}
+ for key, val in payload.items():
+ if val is None:
+ continue
+ if key in ( 'name' ):
+ val = validation.validate_and_sanitize_basestring( key, val )
+ validated_payload[ key ] = val
+ if key in ( 'deleted', 'visible' ):
+ validated_payload[ key ] = validation.validate_boolean( key, val )
+ elif key == 'tags':
+ validated_payload[ key ] = validation.validate_and_sanitize_basestring_list( key, val )
+ return validated_payload
+
+ def history_dataset_collections(self, history, query):
+ collections = history.dataset_collections
+ collections = filter( query.direct_match, collections )
+ return collections
+
+ def __persist( self, dataset_collection_instance ):
+ context = self.model.context
+ context.add( dataset_collection_instance )
+ context.flush()
+ return dataset_collection_instance
+
+ def __recursively_create_collections( self, trans, element_identifiers ):
+ # TODO: Optimize - don't recheck parent, reload created model, just use as is.
+ for index, element_identifier in enumerate( element_identifiers ):
+ try:
+ if not element_identifier[ "src" ] == "new_collection":
+ # not a new collection, keep moving...
+ continue
+ except KeyError:
+ # Not a dictionary, just an id of an HDA - move along.
+ continue
+
+ # element identifier is a dict with src new_collection...
+ collection_type = element_identifier.get( "collection_type", None )
+ if not collection_type:
+ raise RequestParameterInvalidException( "No collection_type define for nested collection." )
+ collection = self.__create_dataset_collection(
+ trans=trans,
+ collection_type=collection_type,
+ element_identifiers=element_identifier[ "element_identifiers" ],
+ )
+ self.__persist( collection )
+ element_identifier[ "src" ] = "dc"
+ element_identifier[ "id" ] = trans.security.encode_id( collection.id )
+
+ return element_identifiers
+
+ def __load_elements( self, trans, element_identifiers ):
+ elements = odict.odict()
+ for element_identifier in element_identifiers:
+ elements[ element_identifier[ "name" ] ] = self.__load_element( trans, element_identifier )
+ return elements
+
+ def __load_element( self, trans, element_identifier ):
+ #if not isinstance( element_identifier, dict ):
+ # # Is allowing this to just be the id of an hda too clever? Somewhat
+ # # consistent with other API methods though.
+ # element_identifier = dict( src='hda', id=str( element_identifier ) )
+
+ # dateset_identifier is dict {src=hda|ldda, id=<encoded_id>}
+ try:
+ src_type = element_identifier.get( 'src', 'hda' )
+ except AttributeError:
+ raise MessageException( "Dataset collection element definition (%s) not dictionary-like." % element_identifier )
+ encoded_id = element_identifier.get( 'id', None )
+ if not src_type or not encoded_id:
+ raise RequestParameterInvalidException( "Problem decoding element identifier %s" % element_identifier )
+
+ if src_type == 'hda':
+ decoded_id = int( trans.app.security.decode_id( encoded_id ) )
+ element = self.hda_manager.get( trans, decoded_id, check_ownership=False )
+ elif src_type == 'ldda':
+ element = self.get_library_dataset_dataset_association( trans, encoded_id )
+ elif src_type == 'hdca':
+ # TODO: Option to copy? Force copy? Copy or allow if not owned?
+ element = self.__get_history_collection_instance( trans, encoded_id ).collection
+ # TODO: ldca.
+ elif src_type == "dc":
+ # TODO: Force only used internally during nested creation so no
+ # need to recheck security.
+ element = self.get_dataset_collection( trans, encoded_id )
+ else:
+ raise RequestParameterInvalidException( "Unknown src_type parameter supplied '%s'." % src_type )
+ return element
+
+ def match_collections( self, collections_to_match ):
+ """
+ May seem odd to place it here, but planning to grow sophistication and
+ get plugin types involved so it will likely make sense in the future.
+ """
+ return MatchingCollections.for_collections( collections_to_match, self.collection_type_descriptions )
+
+ def get_dataset_collection_instance( self, trans, instance_type, id, **kwds ):
+ """
+ """
+ if instance_type == "history":
+ return self.__get_history_collection_instance( trans, id, **kwds )
+ elif instance_type == "library":
+ return self.__get_library_collection_instance( trans, id, **kwds )
+
+ def get_dataset_collection( self, trans, encoded_id ):
+ collection_id = int( trans.app.security.decode_id( encoded_id ) )
+ collection = trans.sa_session.query( trans.app.model.DatasetCollection ).get( collection_id )
+ return collection
+
+ def __get_history_collection_instance( self, trans, id, check_ownership=False, check_accessible=True ):
+ instance_id = int( trans.app.security.decode_id( id ) )
+ collection_instance = trans.sa_session.query( trans.app.model.HistoryDatasetCollectionAssociation ).get( instance_id )
+ self.security_check( trans, collection_instance.history, check_ownership=check_ownership, check_accessible=check_accessible )
+ return collection_instance
+
+ def __get_library_collection_instance( self, trans, id, check_ownership=False, check_accessible=True ):
+ if check_ownership:
+ raise NotImplemented( "Functionality (getting library dataset collection with ownership check) unimplemented." )
+ instance_id = int( trans.security.decode_id( id ) )
+ collection_instance = trans.sa_session.query( trans.app.model.LibraryDatasetCollectionAssociation ).get( instance_id )
+ if check_accessible:
+ if not trans.app.security_agent.can_access_library_item( trans.get_current_user_roles(), collection_instance, trans.user ):
+ raise ItemAccessibilityException( "LibraryDatasetCollectionAssociation is not accessible to the current user", type='error' )
+ return collection_instance
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/dataset_collections/matching.py
--- /dev/null
+++ b/lib/galaxy/dataset_collections/matching.py
@@ -0,0 +1,68 @@
+from galaxy.util import bunch
+from galaxy import exceptions
+from .structure import get_structure
+
+CANNOT_MATCH_ERROR_MESSAGE = "Cannot match collection types."
+
+
+class CollectionsToMatch( object ):
+ """ Structure representing a set of collections that need to be matched up
+ when running tools (possibly workflows in the future as well).
+ """
+
+ def __init__( self ):
+ self.collections = {}
+
+ def add( self, input_name, hdca, subcollection_type=None ):
+ self.collections[ input_name ] = bunch.Bunch(
+ hdca=hdca,
+ subcollection_type=subcollection_type,
+ )
+
+ def has_collections( self ):
+ return len( self.collections ) > 0
+
+ def iteritems( self ):
+ return self.collections.iteritems()
+
+
+class MatchingCollections( object ):
+ """ Structure holding the result of matching a list of collections
+ together. This class being different than the class above and being
+ created in the dataset_collections_service layer may seem like
+ overkill but I suspect in the future plugins will be subtypable for
+ instance so matching collections will need to make heavy use of the
+ dataset collection type registry managed by the dataset collections
+ sevice - hence the complexity now.
+ """
+
+ def __init__( self ):
+ self.structure = None
+ self.collections = {}
+
+ def __attempt_add_to_match( self, input_name, hdca, collection_type_description, subcollection_type ):
+ structure = get_structure( hdca, collection_type_description, leaf_subcollection_type=subcollection_type )
+ if not self.structure:
+ self.structure = structure
+ self.collections[ input_name ] = hdca
+ else:
+ if not self.structure.can_match( structure ):
+ raise exceptions.MessageException( CANNOT_MATCH_ERROR_MESSAGE )
+ self.collections[ input_name ] = hdca
+
+ def slice_collections( self ):
+ return self.structure.walk_collections( self.collections )
+
+ @staticmethod
+ def for_collections( collections_to_match, collection_type_descriptions ):
+ if not collections_to_match.has_collections():
+ return None
+
+ matching_collections = MatchingCollections()
+ for input_key, to_match in collections_to_match.iteritems():
+ hdca = to_match.hdca
+ subcollection_type = to_match = to_match.subcollection_type
+ collection_type_description = collection_type_descriptions.for_collection_type( hdca.collection.collection_type )
+ matching_collections.__attempt_add_to_match( input_key, hdca, collection_type_description, subcollection_type )
+
+ return matching_collections
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/dataset_collections/registry.py
--- /dev/null
+++ b/lib/galaxy/dataset_collections/registry.py
@@ -0,0 +1,14 @@
+from .types import list
+from .types import paired
+
+
+PLUGIN_CLASSES = [list.ListDatasetCollectionType, paired.PairedDatasetCollectionType]
+
+
+class DatasetCollectionTypesRegistry(object):
+
+ def __init__( self, app ):
+ self.__plugins = dict( [ ( p.collection_type, p() ) for p in PLUGIN_CLASSES ] )
+
+ def get( self, plugin_type ):
+ return self.__plugins[ plugin_type ]
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/dataset_collections/structure.py
--- /dev/null
+++ b/lib/galaxy/dataset_collections/structure.py
@@ -0,0 +1,105 @@
+""" Module for reasoning about structure of and matching hierarchical collections of data.
+"""
+import logging
+log = logging.getLogger( __name__ )
+
+
+class Leaf( object ):
+
+ def __len__( self ):
+ return 1
+
+ @property
+ def is_leaf( self ):
+ return True
+
+leaf = Leaf()
+
+
+class Tree( object ):
+
+ def __init__( self, dataset_collection, collection_type_description, leaf_subcollection_type ):
+ self.collection_type_description = collection_type_description
+ self.leaf_subcollection_type = leaf_subcollection_type # collection_type to trim tree at...
+ children = []
+ for element in dataset_collection.elements:
+ child_collection = element.child_collection
+ if child_collection:
+ subcollection_type_description = collection_type_description.subcollection_type_description() # Type description of children
+ if subcollection_type_description.can_match_type( leaf_subcollection_type ):
+ children.append( ( element.element_identifier, leaf ) )
+ else:
+ children.append( ( element.element_identifier, Tree( child_collection, collection_type_description=subcollection_type_description, leaf_subcollection_type=leaf_subcollection_type ) ) )
+ elif element.hda:
+ children.append( ( element.element_identifier, leaf ) )
+
+ self.children = children
+
+ def walk_collections( self, hdca_dict ):
+ return self._walk_collections( dict_map( lambda hdca: hdca.collection, hdca_dict ) )
+
+ def _walk_collections( self, collection_dict ):
+ for ( identifier, substructure ) in self.children:
+ def element( collection ):
+ return collection[ identifier ]
+
+ if substructure.is_leaf:
+ yield dict_map( element, collection_dict )
+ else:
+ sub_collections = dict_map( lambda collection: element( collection ).child_collection )
+ for element in substructure._walk_collections( sub_collections ):
+ yield element
+
+ @property
+ def is_leaf( self ):
+ return False
+
+ def can_match( self, other_structure ):
+ if not self.collection_type_description.can_match_type( other_structure.collection_type_description ):
+ # TODO: generalize
+ return False
+
+ if len( self.children ) != len( other_structure.children ):
+ return False
+
+ for my_child, other_child in zip( self.children, other_structure.children ):
+ if my_child[ 0 ] != other_child[ 0 ]: # Different identifiers, TODO: generalize
+ return False
+
+ # At least one is nested collection...
+ if my_child[ 1 ].is_leaf != other_child[ 1 ].is_leaf:
+ return False
+
+ if not my_child[ 1 ].is_leaf and not my_child[ 1 ].can_match( other_child[ 1 ]):
+ return False
+
+ return True
+
+ def __len__( self ):
+ return sum( [ len( c[ 1 ] ) for c in self.children ] )
+
+ def element_identifiers_for_datasets( self, trans, datasets ):
+ element_identifiers = []
+ for identifier, child in self.children:
+ if isinstance( child, Tree ):
+ child_identifiers = child.element_identifiers_for_datasets( trans, datasets[ 0:len( child ) ] )
+ child_identifiers[ "name" ] = identifier
+ element_identifiers.append( child_identifiers )
+ else:
+ element_identifiers.append( dict( name=identifier, src="hda", id=trans.security.encode_id( datasets[ 0 ].id ) ) )
+
+ datasets = datasets[ len( child ): ]
+
+ return dict(
+ src="new_collection",
+ collection_type=self.collection_type_description.collection_type,
+ element_identifiers=element_identifiers,
+ )
+
+
+def dict_map( func, input_dict ):
+ return dict( [ ( k, func(v) ) for k, v in input_dict.iteritems() ] )
+
+
+def get_structure( dataset_collection_instance, collection_type_description, leaf_subcollection_type=None ):
+ return Tree( dataset_collection_instance.collection, collection_type_description, leaf_subcollection_type=leaf_subcollection_type )
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/dataset_collections/subcollections.py
--- /dev/null
+++ b/lib/galaxy/dataset_collections/subcollections.py
@@ -0,0 +1,25 @@
+from galaxy import exceptions
+
+
+def split_dataset_collection_instance( dataset_collection_instance, collection_type ):
+ """ Split up collection into collection.
+ """
+ return _split_dataset_collection( dataset_collection_instance.collection, collection_type )
+
+
+def _split_dataset_collection( dataset_collection, collection_type ):
+ this_collection_type = dataset_collection.collection_type
+ if not this_collection_type.endswith( collection_type ) or this_collection_type == collection_type:
+ raise exceptions.MessageException( "Cannot split collection in desired fashion." )
+
+ split_elements = []
+ for element in dataset_collection.elements:
+ child_collection = element.child_collection
+ if child_collection is None:
+ raise exceptions.MessageException( "Cannot split collection in desired fashion." )
+ if child_collection.collection_type == collection_type:
+ split_elements.append( element )
+ else:
+ split_elements.extend( _split_dataset_collection( element.child_collection ) )
+
+ return split_elements
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/dataset_collections/type_description.py
--- /dev/null
+++ b/lib/galaxy/dataset_collections/type_description.py
@@ -0,0 +1,89 @@
+
+
+class CollectionTypeDescriptionFactory( object ):
+
+ def __init__( self, type_registry ):
+ # taking in type_registry though not using it, because we will someday
+ # I think.
+ self.type_registry = type_registry
+
+ def for_collection_type( self, collection_type ):
+ return CollectionTypeDescription( collection_type, self )
+
+
+class CollectionTypeDescription( object ):
+ """ Abstraction over dataset collection type that ties together string
+ reprentation in database/model with type registry.
+
+
+ >>> nested_type_description = CollectionTypeDescription( "list:paired", None )
+ >>> paired_type_description = CollectionTypeDescription( "paired", None )
+ >>> nested_type_description.has_subcollections_of_type( "list" )
+ False
+ >>> nested_type_description.has_subcollections_of_type( "list:paired" )
+ False
+ >>> nested_type_description.has_subcollections_of_type( "paired" )
+ True
+ >>> nested_type_description.has_subcollections_of_type( paired_type_description )
+ True
+ >>> nested_type_description.has_subcollections( )
+ True
+ >>> paired_type_description.has_subcollections( )
+ False
+ >>> paired_type_description.rank_collection_type()
+ 'paired'
+ >>> nested_type_description.rank_collection_type()
+ 'list'
+ """
+
+ def __init__( self, collection_type, collection_type_description_factory ):
+ self.collection_type = collection_type
+ self.collection_type_description_factory = collection_type_description_factory
+ self.__has_subcollections = self.collection_type.find( ":" ) > 0
+
+ def has_subcollections_of_type( self, other_collection_type ):
+ """ Take in another type (either flat string or another
+ CollectionTypeDescription) and determine if this collection contains
+ subcollections matching that type.
+
+ The way this is used in map/reduce it seems to make the most sense
+ for this to return True if these subtypes are proper (i.e. a type
+ is not considered to have subcollections of its own type).
+ """
+ if hasattr( other_collection_type, 'collection_type' ):
+ other_collection_type = other_collection_type.collection_type
+ collection_type = self.collection_type
+ return collection_type.endswith( other_collection_type ) and collection_type != other_collection_type
+
+ def is_subcollection_of_type( self, other_collection_type ):
+ if not hasattr( other_collection_type, 'collection_type' ):
+ other_collection_type = self.collection_type_description_factory.for_collection_type( other_collection_type )
+ return other_collection_type.has_subcollections_of_type( self )
+
+ def can_match_type( self, other_collection_type ):
+ if hasattr( other_collection_type, 'collection_type' ):
+ other_collection_type = other_collection_type.collection_type
+ collection_type = self.collection_type
+ return other_collection_type == collection_type
+
+ def subcollection_type_description( self ):
+ if not self.__has_subcollections:
+ raise ValueError( "Cannot generate subcollection type description for flat type %s" % self.collection_type )
+ subcollection_type = self.collection_type.split( ":", 1 )[ 1 ]
+ return self.collection_type_description_factory.for_collection_type( subcollection_type )
+
+ def has_subcollections( self ):
+ return self.__has_subcollections
+
+ def rank_collection_type( self ):
+ """ Return the top-level collection type corresponding to this
+ collection type. For instance the "rank" type of a list of paired
+ data ("list:paired") is "list".
+ """
+ return self.collection_type.split( ":" )[ 0 ]
+
+ def rank_type_plugin( self ):
+ return self.collection_type_description_factory.type_registry.get( self.rank_collection_type() )
+
+ def __str__( self ):
+ return "CollectionTypeDescription[%s]" % self.collection_type
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/dataset_collections/types/__init__.py
--- /dev/null
+++ b/lib/galaxy/dataset_collections/types/__init__.py
@@ -0,0 +1,34 @@
+from galaxy import exceptions
+from abc import ABCMeta
+from abc import abstractmethod
+
+from galaxy import model
+
+import logging
+log = logging.getLogger( __name__ )
+
+
+class DatasetCollectionType(object):
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def build_collection( self, dataset_instances ):
+ """
+ Build DatasetCollection with populated DatasetcollectionElement objects
+ corresponding to the supplied dataset instances or throw exception if
+ this is not a valid collection of the specified type.
+ """
+
+
+class BaseDatasetCollectionType( DatasetCollectionType ):
+
+ def _validation_failed( self, message ):
+ raise exceptions.ObjectAttributeInvalidException( message )
+
+ def _new_collection_for_elements( self, elements ):
+ dataset_collection = model.DatasetCollection( )
+ for index, element in enumerate( elements ):
+ element.element_index = index
+ element.collection = dataset_collection
+ dataset_collection.elements = elements
+ return dataset_collection
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/dataset_collections/types/list.py
--- /dev/null
+++ b/lib/galaxy/dataset_collections/types/list.py
@@ -0,0 +1,23 @@
+from ..types import BaseDatasetCollectionType
+
+from galaxy.model import DatasetCollectionElement
+
+
+class ListDatasetCollectionType( BaseDatasetCollectionType ):
+ """ A flat list of named elements.
+ """
+ collection_type = "list"
+
+ def __init__( self ):
+ pass
+
+ def build_collection( self, elements ):
+ associations = []
+ for identifier, element in elements.iteritems():
+ association = DatasetCollectionElement(
+ element=element,
+ element_identifier=identifier,
+ )
+ associations.append( association )
+
+ return self._new_collection_for_elements( associations )
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/dataset_collections/types/paired.py
--- /dev/null
+++ b/lib/galaxy/dataset_collections/types/paired.py
@@ -0,0 +1,31 @@
+from ..types import BaseDatasetCollectionType
+
+from galaxy.model import DatasetCollectionElement
+
+LEFT_IDENTIFIER = "left"
+RIGHT_IDENTIFIER = "right"
+
+
+class PairedDatasetCollectionType( BaseDatasetCollectionType ):
+ """
+ Paired (left/right) datasets.
+ """
+ collection_type = "paired"
+
+ def __init__( self ):
+ pass
+
+ def build_collection( self, elements ):
+ left_dataset = elements.get("left", None)
+ right_dataset = elements.get("right", None)
+ if not left_dataset or not right_dataset:
+ self._validation_failed("Paired instance must define 'left' and 'right' datasets .")
+ left_association = DatasetCollectionElement(
+ element=left_dataset,
+ element_identifier=LEFT_IDENTIFIER,
+ )
+ right_association = DatasetCollectionElement(
+ element=right_dataset,
+ element_identifier=RIGHT_IDENTIFIER,
+ )
+ return self._new_collection_for_elements([left_association, right_association])
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/dataset_collections/util.py
--- /dev/null
+++ b/lib/galaxy/dataset_collections/util.py
@@ -0,0 +1,52 @@
+from galaxy import exceptions
+from galaxy import web
+from galaxy import model
+
+
+def api_payload_to_create_params( payload ):
+ """
+ Cleanup API payload to pass into dataset_collections.
+ """
+ required_parameters = [ "collection_type", "element_identifiers" ]
+ missing_parameters = [ p for p in required_parameters if p not in payload ]
+ if missing_parameters:
+ message = "Missing required parameters %s" % missing_parameters
+ raise exceptions.ObjectAttributeMissingException( message )
+
+ params = dict(
+ collection_type=payload.get( "collection_type" ),
+ element_identifiers=payload.get( "element_identifiers" ),
+ name=payload.get( "name", None ),
+ )
+
+ return params
+
+
+def dictify_dataset_collection_instance( dataset_colleciton_instance, parent, security, view="element" ):
+ dict_value = dataset_colleciton_instance.to_dict( view=view )
+ encoded_id = security.encode_id( dataset_colleciton_instance.id )
+ if isinstance( parent, model.History ):
+ encoded_history_id = security.encode_id( parent.id )
+ dict_value[ 'url' ] = web.url_for( 'history_content', history_id=encoded_history_id, id=encoded_id, type="dataset_collection" )
+ elif isinstance( parent, model.LibraryFolder ):
+ encoded_library_id = security.encode_id( parent.library.id )
+ encoded_folder_id = security.encode_id( parent.id )
+ # TODO: Work in progress - this end-point is not right yet...
+ dict_value[ 'url' ] = web.url_for( 'library_content', library_id=encoded_library_id, id=encoded_id, folder_id=encoded_folder_id )
+ if view == "element":
+ dict_value[ 'elements' ] = map( dictify_element, dataset_colleciton_instance.collection.elements )
+ security.encode_all_ids( dict_value, recursive=True ) # TODO: Use Kyle's recusrive formulation of this.
+ return dict_value
+
+
+def dictify_element( element ):
+ dictified = element.to_dict( view="element" )
+ object_detials = element.element_object.to_dict()
+ if element.child_collection:
+ # Recursively yield elements for each nested collection...
+ object_detials[ "elements" ] = map( dictify_element, element.child_collection.elements )
+
+ dictified[ "object" ] = object_detials
+ return dictified
+
+__all__ = [ api_payload_to_create_params, dictify_dataset_collection_instance ]
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/exceptions/__init__.py
--- a/lib/galaxy/exceptions/__init__.py
+++ b/lib/galaxy/exceptions/__init__.py
@@ -68,6 +68,10 @@
status_code = 400
err_code = error_codes.USER_REQUEST_MISSING_PARAMETER
+class ToolMetaParameterException( MessageException ):
+ status_code = 400
+ err_code = error_codes.USER_TOOL_META_PARAMETER_PROBLEM
+
class RequestParameterInvalidException( MessageException ):
status_code = 400
err_code = error_codes.USER_REQUEST_INVALID_PARAMETER
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/exceptions/error_codes.json
--- a/lib/galaxy/exceptions/error_codes.json
+++ b/lib/galaxy/exceptions/error_codes.json
@@ -55,6 +55,11 @@
"message": "The request contains unknown type of contents."
},
{
+ "name": "USER_TOOL_META_PARAMETER_PROBLEM",
+ "code": 400011,
+ "message": "Supplied incorrect or incompatible tool meta parameters."
+ },
+ {
"name": "USER_NO_API_KEY",
"code": 403001,
"message": "API authentication required for this request"
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/jobs/__init__.py
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -964,6 +964,8 @@
trynum += 1
log.warning( 'Error accessing %s, will retry: %s', dataset.dataset.file_name, e )
time.sleep( 2 )
+ if getattr( dataset, "hidden_beneath_collection_instance", None ):
+ dataset.visible = False
dataset.blurb = 'done'
dataset.peek = 'no peek'
dataset.info = (dataset.info or '')
diff -r fef831b5e2bd3feaaef58212eb695943d8969212 -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -315,6 +315,8 @@
self.parameters = []
self.input_datasets = []
self.output_datasets = []
+ self.input_dataset_collections = []
+ self.output_dataset_collections = []
self.input_library_datasets = []
self.output_library_datasets = []
self.state = Job.states.NEW
@@ -454,6 +456,10 @@
self.input_datasets.append( JobToInputDatasetAssociation( name, dataset ) )
def add_output_dataset( self, name, dataset ):
self.output_datasets.append( JobToOutputDatasetAssociation( name, dataset ) )
+ def add_input_dataset_collection( self, name, dataset ):
+ self.input_dataset_collections.append( JobToInputDatasetCollectionAssociation( name, dataset ) )
+ def add_output_dataset_collection( self, name, dataset ):
+ self.output_dataset_collections.append( JobToOutputDatasetCollectionAssociation( name, dataset ) )
def add_input_library_dataset( self, name, dataset ):
self.input_library_datasets.append( JobToInputLibraryDatasetAssociation( name, dataset ) )
def add_output_library_dataset( self, name, dataset ):
@@ -696,6 +702,19 @@
self.name = name
self.dataset = dataset
+
+class JobToInputDatasetCollectionAssociation( object ):
+ def __init__( self, name, dataset ):
+ self.name = name
+ self.dataset = dataset
+
+
+class JobToOutputDatasetCollectionAssociation( object ):
+ def __init__( self, name, dataset_collection ):
+ self.name = name
+ self.dataset_collection = dataset_collection
+
+
class JobToInputLibraryDatasetAssociation( object ):
def __init__( self, name, dataset ):
self.name = name
@@ -706,6 +725,13 @@
self.name = name
self.dataset = dataset
+
+class ImplicitlyCreatedDatasetCollectionInput( object ):
+ def __init__( self, name, input_dataset_collection ):
+ self.name = name
+ self.input_dataset_collection = input_dataset_collection
+
+
class PostJobAction( object ):
def __init__( self, action_type, workflow_step, output_name = None, action_arguments = None):
self.action_type = action_type
@@ -907,6 +933,14 @@
self.datasets.append( dataset )
return dataset
+ def add_dataset_collection( self, history_dataset_collection, set_hid=True ):
+ if set_hid:
+ history_dataset_collection.hid = self._next_hid()
+ history_dataset_collection.history = self
+ # TODO: quota?
+ self.dataset_collections.append( history_dataset_collection )
+ return history_dataset_collection
+
def copy( self, name=None, target_user=None, activatable=False, all_datasets=False ):
"""
Return a copy of this history using the given `name` and `target_user`.
@@ -947,6 +981,19 @@
db_session.flush()
# Copy annotation.
self.copy_item_annotation( db_session, self.user, hda, target_user, new_hda )
+ # Copy history dataset collections
+ if all_datasets:
+ hdcas = self.dataset_collections
+ else:
+ hdcas = self.active_dataset_collections
+ for hdca in hdcas:
+ new_hdca = hdca.copy( )
+ new_history.add_dataset_collection( new_hdca, set_hid=False )
+ db_session.add( new_hdca )
+ db_session.flush()
+ # Copy annotation.
+ self.copy_item_annotation( db_session, self.user, hdca, target_user, new_hdca )
+
new_history.hid_counter = self.hid_counter
db_session.add( new_history )
db_session.flush()
@@ -1045,6 +1092,12 @@
self._active_datasets_children_and_roles = query.all()
return self._active_datasets_children_and_roles
+ @property
+ def active_contents( self ):
+ """ Return all active contents ordered by hid.
+ """
+ return self.contents_iter( types=[ "dataset", "dataset_collection" ], deleted=False, visible=True )
+
def contents_iter( self, **kwds ):
"""
Fetch filtered list of contents of history.
@@ -1056,6 +1109,8 @@
iters = []
if 'dataset' in types:
iters.append( self.__dataset_contents_iter( **kwds ) )
+ if 'dataset_collection' in types:
+ iters.append( self.__collection_contents_iter( **kwds ) )
return galaxy.util.merge_sorted_iterables( operator.attrgetter( "hid" ), *iters )
def __dataset_contents_iter(self, **kwds):
@@ -1085,6 +1140,9 @@
else:
return query
+ def __collection_contents_iter( self, **kwds ):
+ return self.__filter_contents( HistoryDatasetCollectionAssociation, **kwds )
+
def copy_tags_from(self,target_user,source_history):
for src_shta in source_history.tags:
new_shta = src_shta.copy()
@@ -1932,6 +1990,7 @@
purged = hda.purged,
visible = hda.visible,
state = hda.state,
+ history_content_type=hda.history_content_type,
file_size = int( hda.get_size() ),
update_time = hda.update_time.isoformat(),
data_type = hda.ext,
@@ -2001,6 +2060,10 @@
return changed
+ @property
+ def history_content_type( self ):
+ return "dataset"
+
class HistoryDatasetAssociationDisplayAtAuthorization( object ):
def __init__( self, hda=None, user=None, site=None ):
@@ -2516,6 +2579,313 @@
try: os.unlink( self.file_name )
except Exception, e: print "Failed to purge associated file (%s) from disk: %s" % ( self.file_name, e )
+
+DEFAULT_COLLECTION_NAME = "Unnamed Collection"
+
+
+class DatasetCollection( object, Dictifiable, UsesAnnotations ):
+ """
+ """
+ dict_collection_visible_keys = ( 'id', 'name', 'collection_type' )
+ dict_element_visible_keys = ( 'id', 'name', 'collection_type' )
+
+ def __init__(
+ self,
+ id=None,
+ collection_type=None,
+ ):
+ self.id = id
+ self.collection_type = collection_type
+
+ @property
+ def dataset_instances( self ):
+ instances = []
+ for element in self.elements:
+ if element.is_collection:
+ instances.extend( element.child_collection.dataset_instances )
+ else:
+ instance = element.dataset_instance
+ instances.append( instance )
+ return instances
+
+ @property
+ def state( self ):
+ # TODO: DatasetCollection state handling...
+ return 'ok'
+
+ def validate( self ):
+ if self.collection_type is None:
+ raise Exception("Each dataset collection must define a collection type.")
+
+ def __getitem__( self, key ):
+ get_by_attribute = "element_index" if isinstance( key, int ) else "element_identifier"
+ for element in self.elements:
+ if getattr( element, get_by_attribute ) == key:
+ return element
+ error_message = "Dataset collection has no %s with key %s." % ( get_by_attribute, key )
+ raise KeyError( error_message )
+
+ def copy( self ):
+ new_collection = DatasetCollection(
+ collection_type=self.collection_type,
+ )
+ for element in self.elements:
+ element.copy_to_collection( new_collection )
+ object_session( self ).add( new_collection )
+ object_session( self ).flush()
+ return new_collection
+
+ def set_from_dict( self, new_data ):
+ editable_keys = ( 'name' )
+ changed = {}
+
+ # unknown keys are ignored here
+ for key in [ k for k in new_data.keys() if k in editable_keys ]:
+ new_val = new_data[ key ]
+ old_val = self.__getattribute__( key )
+ if new_val == old_val:
+ continue
+
+ self.__setattr__( key, new_val )
+ changed[ key ] = new_val
+
+ return changed
+
+
+class DatasetCollectionInstance( object, HasName ):
+ """
+ """
+ def __init__(
+ self,
+ collection=None,
+ deleted=False,
+ ):
+ # Relationships
+ self.collection = collection
+ # Since deleted property is shared between history and dataset collections,
+ # it could be on either table - some places in the code however it is convient
+ # it is on instance instead of collection.
+ self.deleted = deleted
+
+ @property
+ def state( self ):
+ return self.collection.state
+
+ def display_name( self ):
+ return self.get_display_name()
+
+ def _base_to_dict( self, view ):
+ return dict(
+ id=self.id,
+ name=self.name,
+ collection_type=self.collection.collection_type,
+ type="collection", # contents type (distinguished from file or folder (in case of library))
+ )
+
+ def set_from_dict( self, new_data ):
+ """
+ Set object attributes to the values in dictionary new_data limiting
+ to only those keys in dict_element_visible_keys.
+
+ Returns a dictionary of the keys, values that have been changed.
+ """
+ # precondition: keys are proper, values are parsed and validated
+ changed = self.collection.set_from_dict( new_data )
+
+ # unknown keys are ignored here
+ for key in [ k for k in new_data.keys() if k in self.editable_keys ]:
+ new_val = new_data[ key ]
+ old_val = self.__getattribute__( key )
+ if new_val == old_val:
+ continue
+
+ self.__setattr__( key, new_val )
+ changed[ key ] = new_val
+
+ return changed
+
+
+class HistoryDatasetCollectionAssociation( DatasetCollectionInstance, Dictifiable ):
+ """ Associates a DatasetCollection with a History. """
+ editable_keys = ( 'name', 'deleted', 'visible' )
+
+ def __init__(
+ self,
+ id=None,
+ hid=None,
+ collection=None,
+ history=None,
+ name=None,
+ deleted=False,
+ visible=True,
+ copied_from_history_dataset_collection_association=None,
+ implicit_output_name=None,
+ implicit_input_collections=[],
+ ):
+ super( HistoryDatasetCollectionAssociation, self ).__init__(
+ collection=collection,
+ deleted=deleted,
+ )
+ self.id = id
+ self.hid = hid
+ self.history = history
+ self.name = name
+ self.visible = visible
+ self.copied_from_history_dataset_collection_association = copied_from_history_dataset_collection_association
+ self.implicit_output_name = implicit_output_name
+ self.implicit_input_collections = implicit_input_collections
+
+ @property
+ def history_content_type( self ):
+ return "dataset_collection"
+
+ def to_dict( self, view='collection' ):
+ dict_value = dict(
+ hid=self.hid,
+ history_id=self.history.id,
+ history_content_type=self.history_content_type,
+ visible=self.visible,
+ deleted=self.deleted,
+ **self._base_to_dict(view=view)
+ )
+ return dict_value
+
+ def add_implicit_input_collection( self, name, history_dataset_collection ):
+ self.implicit_input_collections.append( ImplicitlyCreatedDatasetCollectionInput( name, history_dataset_collection) )
+
+ def find_implicit_input_collection( self, name ):
+ matching_collection = None
+ for implicit_input_collection in self.implicit_input_collections:
+ if implicit_input_collection.name == name:
+ matching_collection = implicit_input_collection.input_dataset_collection
+ break
+ return matching_collection
+
+ def copy( self ):
+ """
+ Create a copy of this history dataset collection association. Copy
+ underlying collection.
+ """
+ hdca = HistoryDatasetCollectionAssociation(
+ hid=self.hid,
+ collection=self.collection.copy(),
+ visible=self.visible,
+ deleted=self.deleted,
+ name=self.name,
+ copied_from_history_dataset_collection_association=self,
+ )
+
+ object_session( self ).add( hdca )
+ object_session( self ).flush()
+ return hdca
+
+
+class LibraryDatasetCollectionAssociation( DatasetCollectionInstance, Dictifiable ):
+ """ Associates a DatasetCollection with a library folder. """
+ editable_keys = ( 'name', 'deleted' )
+
+ def __init__(
+ self,
+ id=None,
+ collection=None,
+ name=None,
+ deleted=False,
+ folder=None,
+ ):
+ super(LibraryDatasetCollectionAssociation, self).__init__(
+ collection=collection,
+ deleted=deleted,
+ )
+ self.id = id
+ self.folder = folder
+ self.name = name
+
+ def to_dict( self, view='collection' ):
+ dict_value = dict(
+ folder_id=self.folder.id,
+ **self._base_to_dict(view=view)
+ )
+ return dict_value
+
+
+class DatasetCollectionElement( object, Dictifiable ):
+ """ Associates a DatasetInstance (hda or ldda) with a DatasetCollection. """
+ # actionable dataset id needs to be available via API...
+ dict_collection_visible_keys = ( 'id', 'element_type', 'element_index', 'element_identifier' )
+ dict_element_visible_keys = ( 'id', 'element_type', 'element_index', 'element_identifier' )
+
+ def __init__(
+ self,
+ id=None,
+ collection=None,
+ element=None,
+ element_index=None,
+ element_identifier=None,
+ ):
+ if isinstance(element, HistoryDatasetAssociation):
+ self.hda = element
+ #self.instance_type = 'hda'
+ elif isinstance(element, LibraryDatasetDatasetAssociation):
+ self.ldda = element
+ #self.instance_type = 'ldda'
+ elif isinstance( element, DatasetCollection ):
+ self.child_collection = element
+ else:
+ raise AttributeError( 'Unknown element type provided: %s' % type( element ) )
+
+ self.id = id
+ self.collection = collection
+ self.element_index = element_index
+ self.element_identifier = element_identifier or str(element_index)
+
+ @property
+ def element_type( self ):
+ if self.hda:
+ return "hda"
+ elif self.ldda:
+ return "ldda"
+ elif self.child_collection:
+ #TOOD: Rename element_type to element_type.
+ return "dataset_collection"
+ else:
+ raise Exception( "Unknown element instance type" )
+
+ @property
+ def is_collection( self ):
+ return self.element_type == "dataset_collection"
+
+ @property
+ def element_object( self ):
+ if self.hda:
+ return self.hda
+ elif self.ldda:
+ return self.ldda
+ elif self.child_collection:
+ return self.child_collection
+ else:
+ raise Exception( "Unknown element instance type" )
+
+ @property
+ def dataset_instance( self ):
+ element_object = self.element_object
+ if isinstance( element_object, DatasetCollection ):
+ raise AttributeError( "Nested collection has no associated dataset_instance." )
+ return element_object
+
+ @property
+ def dataset( self ):
+ return self.dataset_instance.dataset
+
+ def copy_to_collection( self, collection ):
+ new_element = DatasetCollectionElement(
+ element=self.element_object,
+ collection=collection,
+ element_index=self.element_index,
+ element_identifier=self.element_identifier,
+ )
+ return new_element
+
+
class Event( object ):
def __init__( self, message=None, history=None, user=None, galaxy_session=None ):
self.history = history
@@ -3520,6 +3890,15 @@
class VisualizationTagAssociation ( ItemTagAssociation ):
pass
+
+class HistoryDatasetCollectionTagAssociation( ItemTagAssociation ):
+ pass
+
+
+class LibraryDatasetCollectionTagAssociation( ItemTagAssociation ):
+ pass
+
+
class ToolTagAssociation( ItemTagAssociation ):
def __init__( self, id=None, user=None, tool_id=None, tag_id=None, user_tname=None, value=None ):
self.id = id
@@ -3550,6 +3929,15 @@
class VisualizationAnnotationAssociation( object ):
pass
+
+class HistoryDatasetCollectionAnnotationAssociation( object ):
+ pass
+
+
+class LibraryDatasetCollectionAnnotationAssociation( object ):
+ pass
+
+
# Item rating classes.
class ItemRatingAssociation( object ):
@@ -3583,6 +3971,17 @@
def set_item( self, visualization ):
self.visualization = visualization
+
+class HistoryDatasetCollectionRatingAssociation( ItemRatingAssociation ):
+ def set_item( self, dataset_collection ):
+ self.dataset_collection = dataset_collection
+
+
+class LibraryDatasetCollectionRatingAssociation( ItemRatingAssociation ):
+ def set_item( self, dataset_collection ):
+ self.dataset_collection = dataset_collection
+
+
#Data Manager Classes
class DataManagerHistoryAssociation( object ):
def __init__( self, id=None, history=None, user=None ):
This diff is so big that we needed to truncate the remainder.
https://bitbucket.org/galaxy/galaxy-central/commits/45fd6f57a7a6/
Changeset: 45fd6f57a7a6
User: nsoranzo
Date: 2014-05-07 11:32:54
Summary: Merged galaxy/galaxy-central into default
Affected #: 10 files
diff -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 -r 45fd6f57a7a66298e4c9b5dd468f73c71225209f lib/galaxy/webapps/galaxy/api/configuration.py
--- a/lib/galaxy/webapps/galaxy/api/configuration.py
+++ b/lib/galaxy/webapps/galaxy/api/configuration.py
@@ -9,6 +9,7 @@
import logging
log = logging.getLogger( __name__ )
+
class ConfigurationController( BaseAPIController ):
# config attributes viewable by non-admin users
EXPOSED_USER_OPTIONS = [
@@ -20,6 +21,7 @@
'logo_url',
'terms_url',
'allow_user_dataset_purge',
+ 'use_remote_user'
]
# config attributes viewable by admin users
EXPOSED_ADMIN_OPTIONS = [
diff -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 -r 45fd6f57a7a66298e4c9b5dd468f73c71225209f lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -168,8 +168,8 @@
from_history_id = payload.get( 'from_history_id' )
history = self.get_history( trans, from_history_id, check_ownership=False, check_accessible=True )
job_ids = map( trans.security.decode_id, payload.get( 'job_ids', [] ) )
- dataset_ids = map( trans.security.decode_id, payload.get( 'dataset_ids', [] ) )
- dataset_collection_ids = map( trans.security.decode_id, payload.get( 'dataset_collection_ids', [] ) )
+ dataset_ids = payload.get( 'dataset_ids', [] )
+ dataset_collection_ids = payload.get( 'dataset_collection_ids', [] )
workflow_name = payload[ 'workflow_name' ]
stored_workflow = extract_workflow(
trans=trans,
diff -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 -r 45fd6f57a7a66298e4c9b5dd468f73c71225209f lib/galaxy/workflow/extract.py
--- a/lib/galaxy/workflow/extract.py
+++ b/lib/galaxy/workflow/extract.py
@@ -87,7 +87,8 @@
for hid in dataset_collection_ids:
step = model.WorkflowStep()
step.type = 'data_collection_input'
- step.tool_inputs = dict( name="Input Dataset Collection" )
+ collection_type = summary.collection_types[ hid ]
+ step.tool_inputs = dict( name="Input Dataset Collection", collection_type=collection_type )
hid_to_output_pair[ hid ] = ( step, 'output' )
steps.append( step )
# Tool steps
@@ -167,6 +168,8 @@
self.warnings = set()
self.jobs = odict()
self.implicit_map_jobs = []
+ self.collection_types = {}
+
self.__summarize()
def __summarize( self ):
@@ -177,7 +180,9 @@
implicit_outputs = []
for content in self.history.active_contents:
if content.history_content_type == "dataset_collection":
+ hid = content.hid
content = self.__original_hdca( content )
+ self.collection_types[ hid ] = content.collection.collection_type
if not content.implicit_output_name:
job = DatasetCollectionCreationJob( content )
self.jobs[ job ] = [ ( None, content ) ]
diff -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 -r 45fd6f57a7a66298e4c9b5dd468f73c71225209f test/api/helpers.py
--- a/test/api/helpers.py
+++ b/test/api/helpers.py
@@ -1,3 +1,5 @@
+from operator import itemgetter
+
import time
import json
import StringIO
@@ -12,6 +14,35 @@
DEFAULT_HISTORY_TIMEOUT = 5 # Secs to wait on history to turn ok
+def skip_without_tool( tool_id ):
+ """ Decorate an API test method as requiring a specific tool,
+ have nose skip the test case is the tool is unavailable.
+ """
+
+ def method_wrapper( method ):
+
+ def get_tool_ids( api_test_case ):
+ index = api_test_case.galaxy_interactor.get( "tools", data=dict(in_panel=False) )
+ tools = index.json()
+ # In panels by default, so flatten out sections...
+ tool_ids = map( itemgetter( "id" ), tools )
+ return tool_ids
+
+ def wrapped_method( api_test_case, *args, **kwargs ):
+ if tool_id not in get_tool_ids( api_test_case ):
+ from nose.plugins.skip import SkipTest
+ raise SkipTest( )
+
+ return method( api_test_case, *args, **kwargs )
+
+ # Must preserve method name so nose can detect and report tests by
+ # name.
+ wrapped_method.__name__ = method.__name__
+ return wrapped_method
+
+ return method_wrapper
+
+
# Deprecated mixin, use dataset populator instead.
# TODO: Rework existing tests to target DatasetPopulator in a setup method instead.
class TestsDatasets:
@@ -84,8 +115,8 @@
class WorkflowPopulator( object ):
# Impulse is to make this a Mixin, but probably better as an object.
- def __init__( self, api_test_case ):
- self.api_test_case = api_test_case
+ def __init__( self, galaxy_interactor ):
+ self.galaxy_interactor = galaxy_interactor
def load_workflow( self, name, content=workflow_str, add_pja=False ):
workflow = json.loads( content )
@@ -111,7 +142,7 @@
workflow=json.dumps( workflow ),
**create_kwds
)
- upload_response = self.api_test_case._post( "workflows/upload", data=data )
+ upload_response = self.galaxy_interactor.post( "workflows/upload", data=data )
uploaded_workflow_id = upload_response.json()[ "id" ]
return uploaded_workflow_id
@@ -189,6 +220,99 @@
return show().json()
+class DatasetCollectionPopulator( object ):
+
+ def __init__( self, galaxy_interactor ):
+ self.galaxy_interactor = galaxy_interactor
+ self.dataset_populator = DatasetPopulator( galaxy_interactor )
+
+ def create_list_from_pairs( self, history_id, pairs ):
+ element_identifiers = []
+ for i, pair in enumerate( pairs ):
+ element_identifiers.append( dict(
+ name="test%d" % i,
+ src="hdca",
+ id=pair
+ ) )
+
+ payload = dict(
+ instance_type="history",
+ history_id=history_id,
+ element_identifiers=json.dumps(element_identifiers),
+ collection_type="list:paired",
+ )
+ return self.__create( payload )
+
+ def create_pair_in_history( self, history_id, **kwds ):
+ payload = self.create_pair_payload(
+ history_id,
+ instance_type="history",
+ **kwds
+ )
+ return self.__create( payload )
+
+ def create_list_in_history( self, history_id, **kwds ):
+ payload = self.create_list_payload(
+ history_id,
+ instance_type="history",
+ **kwds
+ )
+ return self.__create( payload )
+
+ def create_list_payload( self, history_id, **kwds ):
+ return self.__create_payload( history_id, identifiers_func=self.list_identifiers, collection_type="list", **kwds )
+
+ def create_pair_payload( self, history_id, **kwds ):
+ return self.__create_payload( history_id, identifiers_func=self.pair_identifiers, collection_type="paired", **kwds )
+
+ def __create_payload( self, history_id, identifiers_func, collection_type, **kwds ):
+ contents = None
+ if "contents" in kwds:
+ contents = kwds[ "contents" ]
+ del kwds[ "contents" ]
+
+ if "element_identifiers" not in kwds:
+ kwds[ "element_identifiers" ] = json.dumps( identifiers_func( history_id, contents=contents ) )
+
+ payload = dict(
+ history_id=history_id,
+ collection_type=collection_type,
+ **kwds
+ )
+ return payload
+
+ def pair_identifiers( self, history_id, contents=None ):
+ hda1, hda2 = self.__datasets( history_id, count=2, contents=contents )
+
+ element_identifiers = [
+ dict( name="left", src="hda", id=hda1[ "id" ] ),
+ dict( name="right", src="hda", id=hda2[ "id" ] ),
+ ]
+ return element_identifiers
+
+ def list_identifiers( self, history_id, contents=None ):
+ hda1, hda2, hda3 = self.__datasets( history_id, count=3, contents=contents )
+ element_identifiers = [
+ dict( name="data1", src="hda", id=hda1[ "id" ] ),
+ dict( name="data2", src="hda", id=hda2[ "id" ] ),
+ dict( name="data3", src="hda", id=hda3[ "id" ] ),
+ ]
+ return element_identifiers
+
+ def __create( self, payload ):
+ create_response = self.galaxy_interactor.post( "dataset_collections", data=payload )
+ return create_response
+
+ def __datasets( self, history_id, count, contents=None ):
+ datasets = []
+ for i in xrange( count ):
+ new_kwds = {}
+ if contents:
+ new_kwds[ "content" ] = contents[ i ]
+ datasets.append( self.dataset_populator.new_dataset( history_id, **new_kwds ) )
+ return datasets
+
+
def wait_on_state( state_func, assert_ok=False, timeout=5 ):
delta = .1
iteration = 0
diff -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 -r 45fd6f57a7a66298e4c9b5dd468f73c71225209f test/api/test_dataset_collections.py
--- /dev/null
+++ b/test/api/test_dataset_collections.py
@@ -0,0 +1,113 @@
+from base import api
+import json
+from .helpers import DatasetPopulator
+from .helpers import DatasetCollectionPopulator
+
+
+class DatasetCollectionApiTestCase( api.ApiTestCase ):
+
+ def setUp( self ):
+ super( DatasetCollectionApiTestCase, self ).setUp()
+ self.dataset_populator = DatasetPopulator( self.galaxy_interactor )
+ self.dataset_collection_populator = DatasetCollectionPopulator( self.galaxy_interactor )
+ self.history_id = self.dataset_populator.new_history()
+
+ def test_create_pair_from_history( self ):
+ payload = self.dataset_collection_populator.create_pair_payload(
+ self.history_id,
+ instance_type="history",
+ )
+ create_response = self._post( "dataset_collections", payload )
+ dataset_collection = self._check_create_response( create_response )
+ returned_datasets = dataset_collection[ "elements" ]
+ assert len( returned_datasets ) == 2, dataset_collection
+
+ def test_create_list_from_history( self ):
+ element_identifiers = self.dataset_collection_populator.list_identifiers( self.history_id )
+
+ payload = dict(
+ instance_type="history",
+ history_id=self.history_id,
+ element_identifiers=json.dumps(element_identifiers),
+ collection_type="list",
+ )
+
+ create_response = self._post( "dataset_collections", payload )
+ dataset_collection = self._check_create_response( create_response )
+ returned_datasets = dataset_collection[ "elements" ]
+ assert len( returned_datasets ) == 3, dataset_collection
+
+ def test_create_list_of_existing_pairs( self ):
+ pair_payload = self.dataset_collection_populator.create_pair_payload(
+ self.history_id,
+ instance_type="history",
+ )
+ pair_create_response = self._post( "dataset_collections", pair_payload )
+ dataset_collection = self._check_create_response( pair_create_response )
+ hdca_id = dataset_collection[ "id" ]
+
+ element_identifiers = [
+ dict( name="test1", src="hdca", id=hdca_id )
+ ]
+
+ payload = dict(
+ instance_type="history",
+ history_id=self.history_id,
+ element_identifiers=json.dumps(element_identifiers),
+ collection_type="list",
+ )
+ create_response = self._post( "dataset_collections", payload )
+ dataset_collection = self._check_create_response( create_response )
+ returned_collections = dataset_collection[ "elements" ]
+ assert len( returned_collections ) == 1, dataset_collection
+
+ def test_create_list_of_new_pairs( self ):
+ pair_identifiers = self.dataset_collection_populator.pair_identifiers( self.history_id )
+ element_identifiers = [ dict(
+ src="new_collection",
+ name="test_pair",
+ collection_type="paired",
+ element_identifiers=pair_identifiers,
+ ) ]
+ payload = dict(
+ collection_type="list:paired",
+ instance_type="history",
+ history_id=self.history_id,
+ name="nested_collecion",
+ element_identifiers=json.dumps( element_identifiers ),
+ )
+ create_response = self._post( "dataset_collections", payload )
+ dataset_collection = self._check_create_response( create_response )
+ assert dataset_collection[ "collection_type" ] == "list:paired"
+ returned_collections = dataset_collection[ "elements" ]
+ assert len( returned_collections ) == 1, dataset_collection
+ pair_1_element = returned_collections[ 0 ]
+ self._assert_has_keys( pair_1_element, "element_index" )
+ pair_1_object = pair_1_element[ "object" ]
+ self._assert_has_keys( pair_1_object, "collection_type", "elements" )
+ self.assertEquals( pair_1_object[ "collection_type" ], "paired" )
+ pair_elements = pair_1_object[ "elements" ]
+ assert len( pair_elements ) == 2
+ pair_1_element_1 = pair_elements[ 0 ]
+ assert pair_1_element_1[ "element_index" ] == 0
+
+ def test_hda_security( self ):
+ element_identifiers = self.dataset_collection_populator.pair_identifiers( self.history_id )
+
+ with self._different_user( ):
+ history_id = self.dataset_populator.new_history()
+ payload = dict(
+ instance_type="history",
+ history_id=history_id,
+ element_identifiers=json.dumps(element_identifiers),
+ collection_type="paired",
+ )
+
+ create_response = self._post( "dataset_collections", payload )
+ self._assert_status_code_is( create_response, 403 )
+
+ def _check_create_response( self, create_response ):
+ self._assert_status_code_is( create_response, 200 )
+ dataset_collection = create_response.json()
+ self._assert_has_keys( dataset_collection, "elements", "url", "name", "collection_type" )
+ return dataset_collection
diff -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 -r 45fd6f57a7a66298e4c9b5dd468f73c71225209f test/api/test_history_contents.py
--- a/test/api/test_history_contents.py
+++ b/test/api/test_history_contents.py
@@ -3,7 +3,7 @@
from .helpers import TestsDatasets
from .helpers import LibraryPopulator
-from .test_dataset_collections import DatasetCollectionPopulator
+from .helpers import DatasetCollectionPopulator
from base.interactor import (
put_request,
delete_request,
diff -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 -r 45fd6f57a7a66298e4c9b5dd468f73c71225209f test/api/test_search.py
--- a/test/api/test_search.py
+++ b/test/api/test_search.py
@@ -7,7 +7,7 @@
class SearchApiTestCase( api.ApiTestCase ):
def test_search_workflows( self ):
- workflow_populator = WorkflowPopulator( self )
+ workflow_populator = WorkflowPopulator( self.galaxy_interactor )
workflow_id = workflow_populator.simple_workflow( "test_for_search" )
search_response = self.__search( "select * from workflow" )
assert self.__has_result_with_name( search_response, "test_for_search (imported from API)" ), search_response.json()
diff -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 -r 45fd6f57a7a66298e4c9b5dd468f73c71225209f test/api/test_tools.py
--- a/test/api/test_tools.py
+++ b/test/api/test_tools.py
@@ -2,7 +2,8 @@
from base import api
from operator import itemgetter
from .helpers import DatasetPopulator
-from .test_dataset_collections import DatasetCollectionPopulator
+from .helpers import DatasetCollectionPopulator
+from .helpers import skip_without_tool
class ToolsTestCase( api.ApiTestCase ):
@@ -51,8 +52,8 @@
result_content = self._upload_and_get_content( table )
self.assertEquals( result_content, table )
+ @skip_without_tool( "cat1" )
def test_run_cat1( self ):
- self.__skip_without_tool( "cat1" )
# Run simple non-upload tool with an input data parameter.
history_id = self.dataset_populator.new_history()
new_dataset = self.dataset_populator.new_dataset( history_id, content='Cat1Test' )
@@ -66,8 +67,8 @@
output1_content = self._get_content( history_id, dataset=output1 )
self.assertEqual( output1_content.strip(), "Cat1Test" )
+ @skip_without_tool( "cat1" )
def test_run_cat1_with_two_inputs( self ):
- self.__skip_without_tool( "cat1" )
# Run tool with an multiple data parameter and grouping (repeat)
history_id = self.dataset_populator.new_history()
new_dataset1 = self.dataset_populator.new_dataset( history_id, content='Cat1Test' )
@@ -83,8 +84,8 @@
output1_content = self._get_content( history_id, dataset=output1 )
self.assertEqual( output1_content.strip(), "Cat1Test\nCat2Test" )
+ @skip_without_tool( "cat1" )
def test_multirun_cat1( self ):
- self.__skip_without_tool( "cat1" )
history_id = self.dataset_populator.new_history()
new_dataset1 = self.dataset_populator.new_dataset( history_id, content='123' )
new_dataset2 = self.dataset_populator.new_dataset( history_id, content='456' )
@@ -104,8 +105,8 @@
self.assertEquals( output1_content.strip(), "123" )
self.assertEquals( output2_content.strip(), "456" )
+ @skip_without_tool( "cat1" )
def test_multirun_in_repeat( self ):
- self.__skip_without_tool( "cat1" )
history_id = self.dataset_populator.new_history()
new_dataset1 = self.dataset_populator.new_dataset( history_id, content='123' )
new_dataset2 = self.dataset_populator.new_dataset( history_id, content='456' )
@@ -127,8 +128,8 @@
self.assertEquals( output1_content.strip(), "Common\n123" )
self.assertEquals( output2_content.strip(), "Common\n456" )
+ @skip_without_tool( "cat1" )
def test_multirun_on_multiple_inputs( self ):
- self.__skip_without_tool( "cat1" )
history_id = self.dataset_populator.new_history()
new_dataset1 = self.dataset_populator.new_dataset( history_id, content='123' )
new_dataset2 = self.dataset_populator.new_dataset( history_id, content='456' )
@@ -153,8 +154,8 @@
assert "123\n0ab" in outputs_contents
assert "456\n0ab" in outputs_contents
+ @skip_without_tool( "cat1" )
def test_map_over_collection( self ):
- self.__skip_without_tool( "cat1" )
history_id = self.dataset_populator.new_history()
hdca_id = self.__build_pair( history_id, [ "123", "456" ] )
inputs = {
@@ -167,7 +168,6 @@
self.assertEquals( len( jobs ), 2 )
self.assertEquals( len( outputs ), 2 )
self.assertEquals( len( implicit_collections ), 1 )
-
self.dataset_populator.wait_for_history( history_id, assert_ok=True )
output1 = outputs[ 0 ]
output2 = outputs[ 1 ]
@@ -176,8 +176,8 @@
self.assertEquals( output1_content.strip(), "123" )
self.assertEquals( output2_content.strip(), "456" )
+ @skip_without_tool( "cat1" )
def test_map_over_nested_collections( self ):
- self.__skip_without_tool( "cat1" )
history_id = self.dataset_populator.new_history()
hdca_id = self.__build_nested_list( history_id )
inputs = {
@@ -204,6 +204,7 @@
first_object_left_element = first_object[ "elements" ][ 0 ]
self.assertEquals( outputs[ 0 ][ "id" ], first_object_left_element[ "object" ][ "id" ] )
+ @skip_without_tool( "cat1" )
def test_map_over_two_collections( self ):
history_id = self.dataset_populator.new_history()
hdca1_id = self.__build_pair( history_id, [ "123", "456" ] )
@@ -222,8 +223,8 @@
self.assertEquals( output1_content.strip(), "123\n789" )
self.assertEquals( output2_content.strip(), "456\n0ab" )
+ @skip_without_tool( "cat1" )
def test_cannot_map_over_incompatible_collections( self ):
- self.__skip_without_tool( "cat1" )
history_id = self.dataset_populator.new_history()
hdca1_id = self.__build_pair( history_id, [ "123", "456" ] )
hdca2_id = self.dataset_collection_populator.create_list_in_history( history_id ).json()[ "id" ]
@@ -236,8 +237,8 @@
# on server.
assert run_response.status_code >= 400
+ @skip_without_tool( "multi_data_param" )
def test_reduce_collections( self ):
- self.__skip_without_tool( "multi_data_param" )
history_id = self.dataset_populator.new_history()
hdca1_id = self.__build_pair( history_id, [ "123", "456" ] )
hdca2_id = self.dataset_collection_populator.create_list_in_history( history_id ).json()[ "id" ]
@@ -258,8 +259,8 @@
assert output1_content.strip() == "123\n456"
assert len( output2_content.strip().split("\n") ) == 3, output2_content
+ @skip_without_tool( "collection_paired_test" )
def test_subcollection_mapping( self ):
- self.__skip_without_tool( "collection_paired_test" )
history_id = self.dataset_populator.new_history()
hdca_list_id = self.__build_nested_list( history_id )
inputs = {
@@ -285,7 +286,8 @@
return self._run_outputs( self._run( tool_id, history_id, inputs ) )
def _run_outputs( self, create_response ):
- self._assert_status_code_is( create_response, 200, assert_ok=True )[ 'outputs' ]
+ self._assert_status_code_is( create_response, 200 )
+ return create_response.json()[ 'outputs' ]
def _run_cat1( self, history_id, inputs, assert_ok=False ):
return self._run( 'cat1', history_id, inputs, assert_ok=assert_ok )
@@ -334,11 +336,6 @@
tool_ids = map( itemgetter( "id" ), tools )
return tool_ids
- def __skip_without_tool( self, tool_id ):
- from nose.plugins.skip import SkipTest
- if tool_id not in self.__tool_ids( ):
- raise SkipTest( )
-
def __build_nested_list( self, history_id ):
hdca1_id = self.__build_pair( history_id, [ "123", "456" ] )
hdca2_id = self.__build_pair( history_id, [ "789", "0ab" ] )
diff -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 -r 45fd6f57a7a66298e4c9b5dd468f73c71225209f test/api/test_workflows.py
--- a/test/api/test_workflows.py
+++ b/test/api/test_workflows.py
@@ -1,8 +1,11 @@
from base import api
from json import dumps
+from json import loads
import time
-from .helpers import TestsDatasets
from .helpers import WorkflowPopulator
+from .helpers import DatasetPopulator
+from .helpers import DatasetCollectionPopulator
+from .helpers import skip_without_tool
from base.interactor import delete_request # requests like delete
@@ -12,11 +15,13 @@
# - Allow post to workflows/<workflow_id>/run in addition to posting to
# /workflows with id in payload.
# - Much more testing obviously, always more testing.
-class WorkflowsApiTestCase( api.ApiTestCase, TestsDatasets ):
+class WorkflowsApiTestCase( api.ApiTestCase ):
def setUp( self ):
super( WorkflowsApiTestCase, self ).setUp()
- self.workflow_populator = WorkflowPopulator( self )
+ self.workflow_populator = WorkflowPopulator( self.galaxy_interactor )
+ self.dataset_populator = DatasetPopulator( self.galaxy_interactor )
+ self.dataset_collection_populator = DatasetCollectionPopulator( self.galaxy_interactor )
def test_delete( self ):
workflow_id = self.workflow_populator.simple_workflow( "test_delete" )
@@ -58,6 +63,7 @@
first_input = downloaded_workflow[ "steps" ][ "0" ][ "inputs" ][ 0 ]
assert first_input[ "name" ] == "WorkflowInput1"
+ @skip_without_tool( "cat1" )
def test_run_workflow( self ):
workflow = self.workflow_populator.load_workflow( name="test_for_run" )
workflow_request, history_id = self._setup_workflow_run( workflow )
@@ -65,19 +71,20 @@
# something like that.
run_workflow_response = self._post( "workflows", data=workflow_request )
self._assert_status_code_is( run_workflow_response, 200 )
- self._wait_for_history( history_id, assert_ok=True )
+ self.dataset_populator.wait_for_history( history_id, assert_ok=True )
+ @skip_without_tool( "cat1" )
def test_extract_from_history( self ):
workflow = self.workflow_populator.load_workflow( name="test_for_extract" )
workflow_request, history_id = self._setup_workflow_run( workflow )
contents_response = self._get( "histories/%s/contents" % history_id )
self._assert_status_code_is( contents_response, 200 )
- hda_ids = map( lambda c: c[ "id" ], contents_response.json() )
+ hda_ids = map( lambda c: c[ "hid" ], contents_response.json() )
run_workflow_response = self._post( "workflows", data=workflow_request )
self._assert_status_code_is( run_workflow_response, 200 )
- self._wait_for_history( history_id, assert_ok=True )
+ self.dataset_populator.wait_for_history( history_id, assert_ok=True )
data = dict( history_id=history_id, tool_id="cat1" )
jobs_response = self._get( "jobs", data=data )
self._assert_status_code_is( jobs_response, 200 )
@@ -90,26 +97,66 @@
job_ids=dumps( [ cat1_job_id ] ),
workflow_name="test import from history",
)
- run_workflow_response = self._post( "workflows", data=create_from_data )
- self._assert_status_code_is( run_workflow_response, 200 )
+ create_workflow_response = self._post( "workflows", data=create_from_data )
+ self._assert_status_code_is( create_workflow_response, 200 )
- new_workflow_id = run_workflow_response.json()[ "id" ]
+ new_workflow_id = create_workflow_response.json()[ "id" ]
download_response = self._get( "workflows/%s/download" % new_workflow_id )
self._assert_status_code_is( download_response, 200 )
downloaded_workflow = download_response.json()
self.assertEquals( downloaded_workflow[ "name" ], "test import from history" )
assert len( downloaded_workflow[ "steps" ] ) == 3
+ @skip_without_tool( "collection_paired_test" )
+ def test_extract_workflows_with_dataset_collections( self ):
+ history_id = self.dataset_populator.new_history()
+ hdca = self.dataset_collection_populator.create_pair_in_history( history_id ).json()
+ hdca_id = hdca[ "id" ]
+ inputs = {
+ "f1": dict( src="hdca", id=hdca_id )
+ }
+ payload = self.dataset_populator.run_tool_payload(
+ tool_id="collection_paired_test",
+ inputs=inputs,
+ history_id=history_id,
+ )
+ tool_response = self._post( "tools", data=payload )
+ self._assert_status_code_is( tool_response, 200 )
+ job_id = tool_response.json()[ "jobs" ][ 0 ][ "id" ]
+ self.dataset_populator.wait_for_history( history_id, assert_ok=True )
+ create_from_data = dict(
+ from_history_id=history_id,
+ dataset_collection_ids=dumps( [ hdca[ "hid" ] ] ),
+ job_ids=dumps( [ job_id ] ),
+ workflow_name="test import from history",
+ )
+ create_workflow_response = self._post( "workflows", data=create_from_data )
+ self._assert_status_code_is( create_workflow_response, 200 )
+ create_workflow_response.json()[ "id" ]
+
+ new_workflow_id = create_workflow_response.json()[ "id" ]
+ download_response = self._get( "workflows/%s/download" % new_workflow_id )
+ self._assert_status_code_is( download_response, 200 )
+ downloaded_workflow = download_response.json()
+ assert len( downloaded_workflow[ "steps" ] ) == 2
+ collection_steps = [ s for s in downloaded_workflow[ "steps" ].values() if s[ "type" ] == "data_collection_input" ]
+ assert len( collection_steps ) == 1
+ collection_step = collection_steps[ 0 ]
+ collection_step_state = loads( collection_step[ "tool_state" ] )
+ self.assertEquals( collection_step_state[ "collection_type" ], u"paired" )
+
+ @skip_without_tool( "random_lines1" )
def test_run_replace_params_by_tool( self ):
workflow_request, history_id = self._setup_random_x2_workflow( "test_for_replace_tool_params" )
workflow_request[ "parameters" ] = dumps( dict( random_lines1=dict( num_lines=5 ) ) )
run_workflow_response = self._post( "workflows", data=workflow_request )
self._assert_status_code_is( run_workflow_response, 200 )
- self._wait_for_history( history_id, assert_ok=True )
+ self.dataset_populator.wait_for_history( history_id, assert_ok=True )
# Would be 8 and 6 without modification
self.__assert_lines_hid_line_count_is( history_id, 2, 5 )
self.__assert_lines_hid_line_count_is( history_id, 3, 5 )
+ @skip_without_tool( "random_lines1" )
def test_run_replace_params_by_steps( self ):
workflow_request, history_id = self._setup_random_x2_workflow( "test_for_replace_step_params" )
workflow_summary_response = self._get( "workflows/%s" % workflow_request[ "workflow_id" ] )
@@ -120,7 +167,7 @@
workflow_request[ "parameters" ] = params
run_workflow_response = self._post( "workflows", data=workflow_request )
self._assert_status_code_is( run_workflow_response, 200 )
- self._wait_for_history( history_id, assert_ok=True )
+ self.dataset_populator.wait_for_history( history_id, assert_ok=True )
# Would be 8 and 6 without modification
self.__assert_lines_hid_line_count_is( history_id, 2, 8 )
self.__assert_lines_hid_line_count_is( history_id, 3, 5 )
@@ -136,6 +183,7 @@
pja = pjas[ 0 ]
self._assert_has_keys( pja, "action_type", "output_name", "action_arguments" )
+ @skip_without_tool( "cat1" )
def test_invocation_usage( self ):
workflow = self.workflow_populator.load_workflow( name="test_usage" )
workflow_request, history_id = self._setup_workflow_run( workflow )
@@ -159,6 +207,7 @@
for step in usage_details[ "steps" ].values():
self._assert_has_keys( step, "workflow_step_id", "order_index" )
+ @skip_without_tool( "cat1" )
def test_post_job_action( self ):
""" Tests both import and execution of post job actions.
"""
@@ -166,7 +215,7 @@
workflow_request, history_id = self._setup_workflow_run( workflow )
run_workflow_response = self._post( "workflows", data=workflow_request )
self._assert_status_code_is( run_workflow_response, 200 )
- self._wait_for_history( history_id, assert_ok=True )
+ self.dataset_populator.wait_for_history( history_id, assert_ok=True )
time.sleep(.1) # Give another little bit of time for rename (needed?)
contents = self._get( "histories/%s/contents" % history_id ).json()
# loading workflow with add_pja=True causes workflow output to be
@@ -183,9 +232,9 @@
step_1 = key
if label == "WorkflowInput2":
step_2 = key
- history_id = self._new_history()
- hda1 = self._new_dataset( history_id, content="1 2 3" )
- hda2 = self._new_dataset( history_id, content="4 5 6" )
+ history_id = self.dataset_populator.new_history()
+ hda1 = self.dataset_populator.new_dataset( history_id, content="1 2 3" )
+ hda2 = self.dataset_populator.new_dataset( history_id, content="4 5 6" )
workflow_request = dict(
history="hist_id=%s" % history_id,
workflow_id=uploaded_workflow_id,
@@ -201,9 +250,9 @@
uploaded_workflow_id = self.workflow_populator.create_workflow( workflow )
workflow_inputs = self._workflow_inputs( uploaded_workflow_id )
key = workflow_inputs.keys()[ 0 ]
- history_id = self._new_history()
+ history_id = self.dataset_populator.new_history()
ten_lines = "\n".join( map( str, range( 10 ) ) )
- hda1 = self._new_dataset( history_id, content=ten_lines )
+ hda1 = self.dataset_populator.new_dataset( history_id, content=ten_lines )
workflow_request = dict(
history="hist_id=%s" % history_id,
workflow_id=uploaded_workflow_id,
diff -r ef7bd8c935bc2a01cf51dff8daa36e8ed7ac8ae6 -r 45fd6f57a7a66298e4c9b5dd468f73c71225209f test/functional/api/test_dataset_collections.py
--- a/test/functional/api/test_dataset_collections.py
+++ /dev/null
@@ -1,206 +0,0 @@
-from base import api
-import json
-from .helpers import DatasetPopulator
-
-
-# TODO: Move into helpers with rest of populators
-class DatasetCollectionPopulator( object ):
-
- def __init__( self, galaxy_interactor ):
- self.galaxy_interactor = galaxy_interactor
- self.dataset_populator = DatasetPopulator( galaxy_interactor )
-
- def create_list_from_pairs( self, history_id, pairs ):
- element_identifiers = []
- for i, pair in enumerate( pairs ):
- element_identifiers.append( dict(
- name="test%d" % i,
- src="hdca",
- id=pair
- ) )
-
- payload = dict(
- instance_type="history",
- history_id=history_id,
- element_identifiers=json.dumps(element_identifiers),
- collection_type="list:paired",
- )
- return self.__create( payload )
-
- def create_pair_in_history( self, history_id, **kwds ):
- payload = self.create_pair_payload(
- history_id,
- instance_type="history",
- **kwds
- )
- return self.__create( payload )
-
- def create_list_in_history( self, history_id, **kwds ):
- payload = self.create_list_payload(
- history_id,
- instance_type="history",
- **kwds
- )
- return self.__create( payload )
-
- def create_list_payload( self, history_id, **kwds ):
- return self.__create_payload( history_id, identifiers_func=self.list_identifiers, collection_type="list", **kwds )
-
- def create_pair_payload( self, history_id, **kwds ):
- return self.__create_payload( history_id, identifiers_func=self.pair_identifiers, collection_type="paired", **kwds )
-
- def __create_payload( self, history_id, identifiers_func, collection_type, **kwds ):
- contents = None
- if "contents" in kwds:
- contents = kwds[ "contents" ]
- del kwds[ "contents" ]
-
- if "element_identifiers" not in kwds:
- kwds[ "element_identifiers" ] = json.dumps( identifiers_func( history_id, contents=contents ) )
-
- payload = dict(
- history_id=history_id,
- collection_type=collection_type,
- **kwds
- )
- return payload
-
- def pair_identifiers( self, history_id, contents=None ):
- hda1, hda2 = self.__datasets( history_id, count=2, contents=contents )
-
- element_identifiers = [
- dict( name="left", src="hda", id=hda1[ "id" ] ),
- dict( name="right", src="hda", id=hda2[ "id" ] ),
- ]
- return element_identifiers
-
- def list_identifiers( self, history_id, contents=None ):
- hda1, hda2, hda3 = self.__datasets( history_id, count=3, contents=contents )
- element_identifiers = [
- dict( name="data1", src="hda", id=hda1[ "id" ] ),
- dict( name="data2", src="hda", id=hda2[ "id" ] ),
- dict( name="data3", src="hda", id=hda3[ "id" ] ),
- ]
- return element_identifiers
-
- def __create( self, payload ):
- create_response = self.galaxy_interactor.post( "dataset_collections", data=payload )
- return create_response
-
- def __datasets( self, history_id, count, contents=None ):
- datasets = []
- for i in xrange( count ):
- new_kwds = {}
- if contents:
- new_kwds[ "content" ] = contents[ i ]
- datasets.append( self.dataset_populator.new_dataset( history_id, **new_kwds ) )
- return datasets
-
-
-class DatasetCollectionApiTestCase( api.ApiTestCase ):
-
- def setUp( self ):
- super( DatasetCollectionApiTestCase, self ).setUp()
- self.dataset_populator = DatasetPopulator( self.galaxy_interactor )
- self.dataset_collection_populator = DatasetCollectionPopulator( self.galaxy_interactor )
- self.history_id = self.dataset_populator.new_history()
-
- def test_create_pair_from_history( self ):
- payload = self.dataset_collection_populator.create_pair_payload(
- self.history_id,
- instance_type="history",
- )
- create_response = self._post( "dataset_collections", payload )
- dataset_collection = self._check_create_response( create_response )
- returned_datasets = dataset_collection[ "elements" ]
- assert len( returned_datasets ) == 2, dataset_collection
-
- def test_create_list_from_history( self ):
- element_identifiers = self.dataset_collection_populator.list_identifiers( self.history_id )
-
- payload = dict(
- instance_type="history",
- history_id=self.history_id,
- element_identifiers=json.dumps(element_identifiers),
- collection_type="list",
- )
-
- create_response = self._post( "dataset_collections", payload )
- dataset_collection = self._check_create_response( create_response )
- returned_datasets = dataset_collection[ "elements" ]
- assert len( returned_datasets ) == 3, dataset_collection
-
- def test_create_list_of_existing_pairs( self ):
- pair_payload = self.dataset_collection_populator.create_pair_payload(
- self.history_id,
- instance_type="history",
- )
- pair_create_response = self._post( "dataset_collections", pair_payload )
- dataset_collection = self._check_create_response( pair_create_response )
- hdca_id = dataset_collection[ "id" ]
-
- element_identifiers = [
- dict( name="test1", src="hdca", id=hdca_id )
- ]
-
- payload = dict(
- instance_type="history",
- history_id=self.history_id,
- element_identifiers=json.dumps(element_identifiers),
- collection_type="list",
- )
- create_response = self._post( "dataset_collections", payload )
- dataset_collection = self._check_create_response( create_response )
- returned_collections = dataset_collection[ "elements" ]
- assert len( returned_collections ) == 1, dataset_collection
-
- def test_create_list_of_new_pairs( self ):
- pair_identifiers = self.dataset_collection_populator.pair_identifiers( self.history_id )
- element_identifiers = [ dict(
- src="new_collection",
- name="test_pair",
- collection_type="paired",
- element_identifiers=pair_identifiers,
- ) ]
- payload = dict(
- collection_type="list:paired",
- instance_type="history",
- history_id=self.history_id,
- name="nested_collecion",
- element_identifiers=json.dumps( element_identifiers ),
- )
- create_response = self._post( "dataset_collections", payload )
- dataset_collection = self._check_create_response( create_response )
- assert dataset_collection[ "collection_type" ] == "list:paired"
- returned_collections = dataset_collection[ "elements" ]
- assert len( returned_collections ) == 1, dataset_collection
- pair_1_element = returned_collections[ 0 ]
- self._assert_has_keys( pair_1_element, "element_index" )
- pair_1_object = pair_1_element[ "object" ]
- self._assert_has_keys( pair_1_object, "collection_type", "elements" )
- self.assertEquals( pair_1_object[ "collection_type" ], "paired" )
- pair_elements = pair_1_object[ "elements" ]
- assert len( pair_elements ) == 2
- pair_1_element_1 = pair_elements[ 0 ]
- assert pair_1_element_1[ "element_index" ] == 0
-
- def test_hda_security( self ):
- element_identifiers = self.dataset_collection_populator.pair_identifiers( self.history_id )
-
- with self._different_user( ):
- history_id = self.dataset_populator.new_history()
- payload = dict(
- instance_type="history",
- history_id=history_id,
- element_identifiers=json.dumps(element_identifiers),
- collection_type="paired",
- )
-
- create_response = self._post( "dataset_collections", payload )
- self._assert_status_code_is( create_response, 403 )
-
- def _check_create_response( self, create_response ):
- self._assert_status_code_is( create_response, 200 )
- dataset_collection = create_response.json()
- self._assert_has_keys( dataset_collection, "elements", "url", "name", "collection_type" )
- return dataset_collection
https://bitbucket.org/galaxy/galaxy-central/commits/f58a4a9185e1/
Changeset: f58a4a9185e1
User: nsoranzo
Date: 2014-05-07 11:39:06
Summary: Workflow API: Add documentation for dataset_collection_ids param.
Affected #: 1 file
diff -r 45fd6f57a7a66298e4c9b5dd468f73c71225209f -r f58a4a9185e133a987d032e4563c043477790a71 lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -143,13 +143,16 @@
:param from_history_id: Id of history to extract a workflow from. Either workflow_id, installed_repository_file or from_history_id must be specified
:type from_history_id: str
- :param job_ids: If from_history_id is set - optional list of jobs to include when extracting workflow from history.
+ :param job_ids: If from_history_id is set - optional list of jobs to include when extracting a workflow from history
:type job_ids: str
- :param dataset_ids: If from_history_id is set - optional list of HDA ids corresponding to workflow inputs when extracting workflow from history.
+ :param dataset_ids: If from_history_id is set - optional list of HDA ids corresponding to workflow inputs when extracting a workflow from history
:type dataset_ids: str
- :param workflow_name: If from_history_id is set - name of the workflow to create
+ :param dataset_collection_ids: If from_history_id is set - optional list of HDCA ids corresponding to workflow inputs when extracting a workflow from history
+ :type dataset_collection_ids: str
+
+ :param workflow_name: If from_history_id is set - name of the workflow to create when extracting a workflow from history
:type workflow_name: str
"""
https://bitbucket.org/galaxy/galaxy-central/commits/83531e44b244/
Changeset: 83531e44b244
User: jmchilton
Date: 2014-05-07 18:43:03
Summary: Merge pull request #383.
Affected #: 1 file
diff -r 9e502242af28473660ee70ee0f98345f3604e137 -r 83531e44b2445dad9a4cf2c262e210c12498d9bd lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -1,9 +1,9 @@
-from __future__ import absolute_import
-
"""
API operations for Workflows
"""
+from __future__ import absolute_import
+
import logging
from sqlalchemy import desc, or_
from galaxy import exceptions
@@ -12,7 +12,6 @@
from galaxy.web import _future_expose_api as expose_api
from galaxy.web.base.controller import BaseAPIController, url_for, UsesStoredWorkflowMixin
from galaxy.web.base.controller import UsesHistoryMixin
-from galaxy.workflow.modules import module_factory
from galaxy.workflow.run import invoke
from galaxy.workflow.run import WorkflowRunConfig
from galaxy.workflow.extract import extract_workflow
@@ -120,79 +119,85 @@
workflow will be created for this user. Otherwise, workflow_id must be
specified and this API method will cause a workflow to execute.
- :param installed_repository_file The path of a workflow to import. Either workflow_id or installed_repository_file must be specified
+ :param installed_repository_file The path of a workflow to import. Either workflow_id, installed_repository_file or from_history_id must be specified
:type installed_repository_file str
- :param workflow_id: an existing workflow id. Either workflow_id or installed_repository_file must be specified
+ :param workflow_id: An existing workflow id. Either workflow_id, installed_repository_file or from_history_id must be specified
:type workflow_id: str
- :param parameters: See _update_step_parameters()
+ :param parameters: If workflow_id is set - see _update_step_parameters()
:type parameters: dict
- :param ds_map: A dictionary mapping each input step id to a dictionary with 2 keys: 'src' (which can be 'ldda', 'ld' or 'hda') and 'id' (which should be the id of a LibraryDatasetDatasetAssociation, LibraryDataset or HistoryDatasetAssociation respectively)
+ :param ds_map: If workflow_id is set - a dictionary mapping each input step id to a dictionary with 2 keys: 'src' (which can be 'ldda', 'ld' or 'hda') and 'id' (which should be the id of a LibraryDatasetDatasetAssociation, LibraryDataset or HistoryDatasetAssociation respectively)
:type ds_map: dict
- :param no_add_to_history: if present in the payload with any value, the input datasets will not be added to the selected history
+ :param no_add_to_history: If workflow_id is set - if present in the payload with any value, the input datasets will not be added to the selected history
:type no_add_to_history: str
- :param history: Either the name of a new history or "hist_id=HIST_ID" where HIST_ID is the id of an existing history
+ :param history: If workflow_id is set - optional history where to run the workflow, either the name of a new history or "hist_id=HIST_ID" where HIST_ID is the id of an existing history. If not specified, the workflow will be run a new unnamed history
:type history: str
- :param replacement_params: A dictionary used when renaming datasets
+ :param replacement_params: If workflow_id is set - an optional dictionary used when renaming datasets
:type replacement_params: dict
- :param from_history_id: Id of history to extract a workflow from. Should not be used with worfklow_id or installed_repository_file.
+ :param from_history_id: Id of history to extract a workflow from. Either workflow_id, installed_repository_file or from_history_id must be specified
:type from_history_id: str
- :param job_ids: If from_history_id is set - this should be a list of jobs to include when extracting workflow from history.
+ :param job_ids: If from_history_id is set - optional list of jobs to include when extracting a workflow from history
:type job_ids: str
- :param dataset_ids: If from_history_id is set - this should be a list of HDA ids corresponding to workflow inputs when extracting workflow from history.
+ :param dataset_ids: If from_history_id is set - optional list of HDA ids corresponding to workflow inputs when extracting a workflow from history
:type dataset_ids: str
+
+ :param dataset_collection_ids: If from_history_id is set - optional list of HDCA ids corresponding to workflow inputs when extracting a workflow from history
+ :type dataset_collection_ids: str
+
+ :param workflow_name: If from_history_id is set - name of the workflow to create when extracting a workflow from history
+ :type workflow_name: str
"""
- # Pull parameters out of payload.
- workflow_id = payload.get('workflow_id', None)
- param_map = payload.get('parameters', {})
- ds_map = payload.get('ds_map', {})
+ if len( set( ['workflow_id', 'installed_repository_file', 'from_history_id'] ).intersection( payload ) ) > 1:
+ trans.response.status = 403
+ return "Only one among 'workflow_id', 'installed_repository_file', 'from_history_id' must be specified"
+
+ if 'installed_repository_file' in payload:
+ workflow_controller = trans.webapp.controllers[ 'workflow' ]
+ result = workflow_controller.import_workflow( trans=trans,
+ cntrller='api',
+ **payload)
+ return result
+
+ if 'from_history_id' in payload:
+ from_history_id = payload.get( 'from_history_id' )
+ history = self.get_history( trans, from_history_id, check_ownership=False, check_accessible=True )
+ job_ids = map( trans.security.decode_id, payload.get( 'job_ids', [] ) )
+ dataset_ids = payload.get( 'dataset_ids', [] )
+ dataset_collection_ids = payload.get( 'dataset_collection_ids', [] )
+ workflow_name = payload[ 'workflow_name' ]
+ stored_workflow = extract_workflow(
+ trans=trans,
+ user=trans.get_user(),
+ history=history,
+ job_ids=job_ids,
+ dataset_ids=dataset_ids,
+ dataset_collection_ids=dataset_collection_ids,
+ workflow_name=workflow_name,
+ )
+ item = stored_workflow.to_dict( value_mapper={ 'id': trans.security.encode_id } )
+ item[ 'url' ] = url_for( 'workflow', id=item[ 'id' ] )
+ return item
+
+ workflow_id = payload.get( 'workflow_id', None )
+ if not workflow_id:
+ trans.response.status = 403
+ return "Either workflow_id, installed_repository_file or from_history_id must be specified"
+
+ # Pull other parameters out of payload.
+ param_map = payload.get( 'parameters', {} )
+ ds_map = payload.get( 'ds_map', {} )
add_to_history = 'no_add_to_history' not in payload
history_param = payload.get('history', '')
- # Get/create workflow.
- if not workflow_id:
- # create new
- if 'installed_repository_file' in payload:
- workflow_controller = trans.webapp.controllers[ 'workflow' ]
- result = workflow_controller.import_workflow( trans=trans,
- cntrller='api',
- **payload)
- return result
- if 'from_history_id' in payload:
- from_history_id = payload.get( 'from_history_id' )
- history = self.get_history( trans, from_history_id, check_ownership=False, check_accessible=True )
- job_ids = map( trans.security.decode_id, payload.get( "job_ids", [] ) )
- dataset_ids = payload.get( "dataset_ids", [] )
- dataset_collection_ids = payload.get( "dataset_collection_ids", [] )
- workflow_name = payload[ "workflow_name" ]
- stored_workflow = extract_workflow(
- trans=trans,
- user=trans.get_user(),
- history=history,
- job_ids=job_ids,
- dataset_ids=dataset_ids,
- dataset_collection_ids=dataset_collection_ids,
- workflow_name=workflow_name,
- )
- item = stored_workflow.to_dict( value_mapper={ "id": trans.security.encode_id } )
- item[ 'url' ] = url_for( 'workflow', id=item[ "id" ] )
- return item
-
- trans.response.status = 403
- return "Either workflow_id or installed_repository_file must be specified"
- if 'installed_repository_file' in payload:
- trans.response.status = 403
- return "installed_repository_file may not be specified with workflow_id"
-
# Get workflow + accessibility check.
stored_workflow = trans.sa_session.query(self.app.model.StoredWorkflow).get(
trans.security.decode_id(workflow_id))
https://bitbucket.org/galaxy/galaxy-central/commits/a014c2d841a8/
Changeset: a014c2d841a8
User: jmchilton
Date: 2014-05-07 18:49:59
Summary: Re-update documentation for fixes in a44bea8.
Affected #: 1 file
diff -r 83531e44b2445dad9a4cf2c262e210c12498d9bd -r a014c2d841a8490f017086c687997a191f3a602f lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -146,10 +146,10 @@
:param job_ids: If from_history_id is set - optional list of jobs to include when extracting a workflow from history
:type job_ids: str
- :param dataset_ids: If from_history_id is set - optional list of HDA ids corresponding to workflow inputs when extracting a workflow from history
+ :param dataset_ids: If from_history_id is set - optional list of HDA `hid`s corresponding to workflow inputs when extracting a workflow from history
:type dataset_ids: str
- :param dataset_collection_ids: If from_history_id is set - optional list of HDCA ids corresponding to workflow inputs when extracting a workflow from history
+ :param dataset_collection_ids: If from_history_id is set - optional list of HDCA `hid`s corresponding to workflow inputs when extracting a workflow from history
:type dataset_collection_ids: str
:param workflow_name: If from_history_id is set - name of the workflow to create when extracting a workflow from history
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0