galaxy-commits
Threads by month
- ----- 2026 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 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
- 15302 discussions
5 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/b7d43f49b239/
Changeset: b7d43f49b239
User: jmchilton
Date: 2014-05-08 23:12:07
Summary: Doc fix in Galaxy's tool shed repositories API.
Affected #: 1 file
diff -r 521fead562531f96564c0e08f2c3ac523a92c6e5 -r b7d43f49b239e99fdb65e4b049bc2b633aafff98 lib/galaxy/webapps/galaxy/api/tool_shed_repositories.py
--- a/lib/galaxy/webapps/galaxy/api/tool_shed_repositories.py
+++ b/lib/galaxy/webapps/galaxy/api/tool_shed_repositories.py
@@ -107,7 +107,7 @@
@web.expose_api
def import_workflows( self, trans, **kwd ):
"""
- POST /api/tool_shed_repositories/import_workflow
+ POST /api/tool_shed_repositories/import_workflows
Import all of the exported workflows contained in the specified installed tool shed repository into Galaxy.
https://bitbucket.org/galaxy/galaxy-central/commits/7c302d65be90/
Changeset: 7c302d65be90
User: jmchilton
Date: 2014-05-08 23:12:07
Summary: Reduce polling frequency of API tests, seems to help prevent sqlite locks.
Affected #: 1 file
diff -r b7d43f49b239e99fdb65e4b049bc2b633aafff98 -r 7c302d65be90f414cff9bf067cf426f5ea3b3207 test/api/helpers.py
--- a/test/api/helpers.py
+++ b/test/api/helpers.py
@@ -312,7 +312,7 @@
def wait_on_state( state_func, assert_ok=False, timeout=5 ):
- delta = .1
+ delta = .25
iteration = 0
while True:
if (delta * iteration) > timeout:
https://bitbucket.org/galaxy/galaxy-central/commits/376d5e107a58/
Changeset: 376d5e107a58
User: jmchilton
Date: 2014-05-08 23:12:07
Summary: Refactor API assertions for reuse outside explicit TestCase classes.
Affected #: 2 files
diff -r 7c302d65be90f414cff9bf067cf426f5ea3b3207 -r 376d5e107a584ac32e5f827b47cd962b79e4248c test/base/api.py
--- a/test/base/api.py
+++ b/test/base/api.py
@@ -8,6 +8,11 @@
from .api_util import get_master_api_key
from .api_util import get_user_api_key
+from .api_asserts import (
+ assert_status_code_is,
+ assert_has_keys,
+ assert_error_code_is,
+)
from urllib import urlencode
@@ -73,25 +78,13 @@
return self.galaxy_interactor.post( *args, **kwds )
def _assert_status_code_is( self, response, expected_status_code ):
- response_status_code = response.status_code
- if expected_status_code != response_status_code:
- try:
- body = response.json()
- except Exception:
- body = "INVALID JSON RESPONSE <%s>" % response.content
- assertion_message_template = "Request status code (%d) was not expected value %d. Body was %s"
- assertion_message = assertion_message_template % ( response_status_code, expected_status_code, body )
- raise AssertionError( assertion_message )
+ assert_status_code_is( response, expected_status_code )
def _assert_has_keys( self, response, *keys ):
- for key in keys:
- assert key in response, "Response [%s] does not contain key [%s]" % ( response, key )
+ assert_has_keys( response, *keys )
def _assert_error_code_is( self, response, error_code ):
- if hasattr( response, "json" ):
- response = response.json()
- self._assert_has_keys( response, "err_code" )
- self.assertEquals( response[ "err_code" ], int( error_code ) )
+ assert_error_code_is( response, error_code )
def _random_key( self ): # Used for invalid request testing...
return "1234567890123456"
diff -r 7c302d65be90f414cff9bf067cf426f5ea3b3207 -r 376d5e107a584ac32e5f827b47cd962b79e4248c test/base/api_asserts.py
--- /dev/null
+++ b/test/base/api_asserts.py
@@ -0,0 +1,30 @@
+""" Utility methods for making assertions about Galaxy API responses, etc...
+"""
+ASSERT_FAIL_ERROR_CODE = "Expected Galaxy error code %d, obtained %d"
+ASSERT_FAIL_STATUS_CODE = "Request status code (%d) was not expected value %d. Body was %s"
+
+
+def assert_status_code_is( response, expected_status_code ):
+ response_status_code = response.status_code
+ if expected_status_code != response_status_code:
+ try:
+ body = response.json()
+ except Exception:
+ body = "INVALID JSON RESPONSE <%s>" % response.content
+ assertion_message = ASSERT_FAIL_STATUS_CODE % ( response_status_code, expected_status_code, body )
+ raise AssertionError( assertion_message )
+
+
+def assert_has_keys( response, *keys ):
+ for key in keys:
+ assert key in response, "Response [%s] does not contain key [%s]" % ( response, key )
+
+
+def assert_error_code_is( response, error_code ):
+ if hasattr( response, "json" ):
+ response = response.json()
+ assert_has_keys( response, "err_code" )
+ err_code = response[ "err_code" ]
+ assert err_code == int( error_code ), ASSERT_FAIL_ERROR_CODE % ( err_code, int( error_code ) )
+
+assert_has_key = assert_has_keys
https://bitbucket.org/galaxy/galaxy-central/commits/a314a3464b08/
Changeset: a314a3464b08
User: jmchilton
Date: 2014-05-08 23:12:07
Summary: Add simple collection mapping workflow extraction functional test.
Affected #: 3 files
diff -r 376d5e107a584ac32e5f827b47cd962b79e4248c -r a314a3464b0806c5f7cb92379ce0788260637ba4 lib/galaxy/workflow/extract.py
--- a/lib/galaxy/workflow/extract.py
+++ b/lib/galaxy/workflow/extract.py
@@ -93,7 +93,9 @@
steps.append( step )
# Tool steps
for job_id in job_ids:
- assert job_id in jobs_by_id, "Attempt to create workflow with job not connected to current history"
+ if job_id not in jobs_by_id:
+ log.warn( "job_id %s not found in jobs_by_id %s" % ( job_id, jobs_by_id ) )
+ raise AssertionError( "Attempt to create workflow with job not connected to current history" )
job = jobs_by_id[ job_id ]
tool_inputs, associations = step_inputs( trans, job )
step = model.WorkflowStep()
diff -r 376d5e107a584ac32e5f827b47cd962b79e4248c -r a314a3464b0806c5f7cb92379ce0788260637ba4 test/api/helpers.py
--- a/test/api/helpers.py
+++ b/test/api/helpers.py
@@ -1,3 +1,4 @@
+from base import api_asserts
from operator import itemgetter
import time
@@ -111,6 +112,12 @@
**kwds
)
+ def run_tool( self, tool_id, inputs, history_id, **kwds ):
+ payload = self.run_tool_payload( tool_id, inputs, history_id, **kwds )
+ tool_response = self.galaxy_interactor.post( "tools", data=payload )
+ api_asserts.assert_status_code_is( tool_response, 200 )
+ return tool_response.json()
+
class WorkflowPopulator( object ):
# Impulse is to make this a Mixin, but probably better as an object.
diff -r 376d5e107a584ac32e5f827b47cd962b79e4248c -r a314a3464b0806c5f7cb92379ce0788260637ba4 test/api/test_workflows.py
--- a/test/api/test_workflows.py
+++ b/test/api/test_workflows.py
@@ -115,35 +115,81 @@
inputs = {
"f1": dict( src="hdca", id=hdca_id )
}
- payload = self.dataset_populator.run_tool_payload(
+ run_output = self.dataset_populator.run_tool(
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" ]
+ job_id = run_output[ "jobs" ][ 0 ][ "id" ]
self.dataset_populator.wait_for_history( history_id, assert_ok=True )
- create_from_data = dict(
+ downloaded_workflow = self._extract_and_download_workflow(
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 )
+ collection_steps = self._get_steps_of_type( downloaded_workflow, "data_collection_input", expected_len=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_extract_mapping_workflow_from_history( self ):
+ history_id = self.dataset_populator.new_history()
+ hdca = self.dataset_collection_populator.create_pair_in_history( history_id, contents=["1 2 3\n4 5 6", "7 8 9\n10 11 10"] ).json()
+ hdca_id = hdca[ "id" ]
+ inputs1 = {
+ "input|__collection_multirun__": hdca_id,
+ "num_lines": 2
+ }
+ run_output1 = self.dataset_populator.run_tool(
+ tool_id="random_lines1",
+ inputs=inputs1,
+ history_id=history_id,
+ )
+ implicit_hdca1 = run_output1[ "implicit_collections" ][ 0 ]
+ job_id1 = run_output1[ "jobs" ][ 0 ][ "id" ]
+ self.dataset_populator.wait_for_history( history_id, assert_ok=True, timeout=20 )
+ inputs2 = {
+ "input|__collection_multirun__": implicit_hdca1[ "id" ],
+ "num_lines": 1
+ }
+ run_output2 = self.dataset_populator.run_tool(
+ tool_id="random_lines1",
+ inputs=inputs2,
+ history_id=history_id,
+ )
+ # implicit_hdca2 = run_output2[ "implicit_collections" ][ 0 ]
+ job_id2 = run_output2[ "jobs" ][ 0 ][ "id" ]
+ self.dataset_populator.wait_for_history( history_id, assert_ok=True, timeout=20 )
+ downloaded_workflow = self._extract_and_download_workflow(
+ from_history_id=history_id,
+ dataset_collection_ids=dumps( [ hdca[ "hid" ] ] ),
+ job_ids=dumps( [ job_id1, job_id2 ] ),
+ workflow_name="test import from mapping history",
+ )
+ assert len( downloaded_workflow[ "steps" ] ) == 3
+ collection_steps = self._get_steps_of_type( downloaded_workflow, "data_collection_input", expected_len=1 )
+ collection_step = collection_steps[ 0 ]
+ collection_step_state = loads( collection_step[ "tool_state" ] )
+ self.assertEquals( collection_step_state[ "collection_type" ], u"paired" )
+ self._get_steps_of_type( downloaded_workflow, "tool", expected_len=2 )
+
+ def _extract_and_download_workflow( self, **extract_payload ):
+ create_workflow_response = self._post( "workflows", data=extract_payload )
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" )
+ return downloaded_workflow
+
+ def _get_steps_of_type( self, downloaded_workflow, type, expected_len=None ):
+ steps = [ s for s in downloaded_workflow[ "steps" ].values() if s[ "type" ] == type ]
+ if expected_len is not None:
+ assert len( steps ) == expected_len
+ return steps
@skip_without_tool( "random_lines1" )
def test_run_replace_params_by_tool( self ):
https://bitbucket.org/galaxy/galaxy-central/commits/f2f32d8b8a06/
Changeset: f2f32d8b8a06
User: jmchilton
Date: 2014-05-08 23:12:07
Summary: Bugfix: (For bf60fa7) Only initial mapping steps in map reduce workflows were being connected correctly.
With expanded test case that checks such a connection. This takes care of of subsequent connections by iteratively updating hid_to_output_pair correctly (... I think, test works with at least two).
Affected #: 2 files
diff -r a314a3464b0806c5f7cb92379ce0788260637ba4 -r f2f32d8b8a06ff370145360b5c75c90e2e2447a9 lib/galaxy/workflow/extract.py
--- a/lib/galaxy/workflow/extract.py
+++ b/lib/galaxy/workflow/extract.py
@@ -123,7 +123,18 @@
steps_by_job_id[ job_id ] = step
# Store created dataset hids
for assoc in job.output_datasets:
- hid_to_output_pair[ assoc.dataset.hid ] = ( step, assoc.name )
+ if job in summary.implicit_map_jobs:
+ hid = None
+ for implicit_pair in jobs[ job ]:
+ query_assoc_name, dataset_collection = implicit_pair
+ if query_assoc_name == assoc.name:
+ hid = dataset_collection.hid
+ if hid is None:
+ log.warn("Failed to find matching implicit job.")
+ raise Exception( "Failed to extract job." )
+ else:
+ hid = assoc.dataset.hid
+ hid_to_output_pair[ hid ] = ( step, assoc.name )
return steps
diff -r a314a3464b0806c5f7cb92379ce0788260637ba4 -r f2f32d8b8a06ff370145360b5c75c90e2e2447a9 test/api/test_workflows.py
--- a/test/api/test_workflows.py
+++ b/test/api/test_workflows.py
@@ -173,7 +173,19 @@
collection_step = collection_steps[ 0 ]
collection_step_state = loads( collection_step[ "tool_state" ] )
self.assertEquals( collection_step_state[ "collection_type" ], u"paired" )
- self._get_steps_of_type( downloaded_workflow, "tool", expected_len=2 )
+ collect_step_idx = collection_step[ "id" ]
+ tool_steps = self._get_steps_of_type( downloaded_workflow, "tool", expected_len=2 )
+ tool_step_idxs = []
+ tool_input_step_idxs = []
+ for tool_step in tool_steps:
+ self._assert_has_key( tool_step[ "input_connections" ], "input" )
+ input_step_idx = tool_step[ "input_connections" ][ "input" ][ "id" ]
+ tool_step_idxs.append( tool_step[ "id" ] )
+ tool_input_step_idxs.append( input_step_idx )
+
+ assert collect_step_idx not in tool_step_idxs
+ assert tool_input_step_idxs[ 0 ] == collect_step_idx
+ assert tool_input_step_idxs[ 1 ] == tool_step_idxs[ 0 ]
def _extract_and_download_workflow( self, **extract_payload ):
create_workflow_response = self._post( "workflows", data=extract_payload )
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: dannon: Update authenticate/baseauth doc with sample usage.
by commits-noreply@bitbucket.org 08 May '14
by commits-noreply@bitbucket.org 08 May '14
08 May '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/521fead56253/
Changeset: 521fead56253
User: dannon
Date: 2014-05-08 17:47:24
Summary: Update authenticate/baseauth doc with sample usage.
Affected #: 1 file
diff -r fc7de175ea2f59b0389a74f30e1152ff4563ad0e -r 521fead562531f96564c0e08f2c3ac523a92c6e5 lib/galaxy/webapps/galaxy/api/authenticate.py
--- a/lib/galaxy/webapps/galaxy/api/authenticate.py
+++ b/lib/galaxy/webapps/galaxy/api/authenticate.py
@@ -1,5 +1,14 @@
"""
API key retrieval through BaseAuth
+Sample usage:
+
+curl --user zipzap@foo.com:password http://localhost:8080/api/authenticate/baseauth
+
+Returns:
+
+{
+ "api_key": "baa4d6e3a156d3033f05736255f195f9"
+}
"""
from base64 import b64decode
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: dannon: Added the ability for /api/authenticate/baseauth to create an API key for a user if one is not set. Misc cleanup, using mappings instead of raw queries.
by commits-noreply@bitbucket.org 08 May '14
by commits-noreply@bitbucket.org 08 May '14
08 May '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/fc7de175ea2f/
Changeset: fc7de175ea2f
User: dannon
Date: 2014-05-08 17:33:55
Summary: Added the ability for /api/authenticate/baseauth to create an API key for a user if one is not set. Misc cleanup, using mappings instead of raw queries.
Affected #: 1 file
diff -r d6826c29e15a1f17553ae2fe2b149ef65ac4da9d -r fc7de175ea2f59b0389a74f30e1152ff4563ad0e lib/galaxy/webapps/galaxy/api/authenticate.py
--- a/lib/galaxy/webapps/galaxy/api/authenticate.py
+++ b/lib/galaxy/webapps/galaxy/api/authenticate.py
@@ -2,18 +2,19 @@
API key retrieval through BaseAuth
"""
-from galaxy import util, web
-from pprint import pprint
-from galaxy.web.base.controller import BaseAPIController
-from base64 import b64decode, b64encode
-from urllib import quote, unquote
+from base64 import b64decode
+from paste.httpexceptions import HTTPBadRequest
+from urllib import unquote
+
+from galaxy import web
from galaxy.exceptions import ObjectNotFound
-from paste.httpexceptions import HTTPBadRequest
+from galaxy.web.base.controller import BaseAPIController, CreatesApiKeysMixin
import logging
log = logging.getLogger( __name__ )
-class AuthenticationController( BaseAPIController ):
+
+class AuthenticationController( BaseAPIController, CreatesApiKeysMixin ):
@web.expose_api_anonymous
def get_api_key( self, trans, **kwd ):
@@ -37,15 +38,15 @@
else:
user = user[0]
is_valid_user = user.check_password( password )
-
if ( is_valid_user ):
- user_id = user.id
- api_key_row = trans.sa_session.query( trans.app.model.APIKeys ).filter( trans.app.model.APIKeys.table.c.user_id == user_id ).first()
+ if user.api_keys:
+ key = user.api_keys[0].key
+ else:
+ key = self.create_api_key( trans, user )
+ return dict( api_key=key )
else:
trans.response.status = 500
- return "invalid password"
-
- return dict( api_key= api_key_row.key )
+ return "invalid password"
def _decode_baseauth( self, encoded_str ):
"""
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: Add some documentation to job_conf.xml.sample_advanced about LWR caching.
by commits-noreply@bitbucket.org 08 May '14
by commits-noreply@bitbucket.org 08 May '14
08 May '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/d6826c29e15a/
Changeset: d6826c29e15a
User: jmchilton
Date: 2014-05-08 16:14:24
Summary: Add some documentation to job_conf.xml.sample_advanced about LWR caching.
Affected #: 1 file
diff -r 56d4cb37d62700e5baa94cc63e56041556844c43 -r d6826c29e15a1f17553ae2fe2b149ef65ac4da9d job_conf.xml.sample_advanced
--- a/job_conf.xml.sample_advanced
+++ b/job_conf.xml.sample_advanced
@@ -28,6 +28,14 @@
file actions are remote executable. --><!-- <param id="url">amqp://guest:guest@localhost:5672//</param> --><!-- <param id="manager">_default_</param> -->
+ <!-- *Experimental Caching*: Uncomment next parameters to enable
+ caching and specify the number of caching threads to enable on Galaxy
+ side. Likely will not work with newer features such as MQ support.
+ If this is enabled be sure to specify a `file_cache_dir` in the remote
+ LWR's main configuration file.
+ -->
+ <!-- <param id="cache">True</param> -->
+ <!-- <param id="transfer_threads">2</param> --></plugin><plugin id="cli" type="runner" load="galaxy.jobs.runners.cli:ShellJobRunner" /><plugin id="condor" type="runner" load="galaxy.jobs.runners.condor:CondorJobRunner" />
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: introduced three visible share states for datasets in libraries private(only the current users private role has access)/restricted(not private and not non-restricted)/non-restricted(there is not a single role in the access set of roles)
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/56d4cb37d627/
Changeset: 56d4cb37d627
User: martenson
Date: 2014-05-07 22:51:36
Summary: data libraries: introduced three visible share states for datasets in libraries private(only the current users private role has access)/restricted(not private and not non-restricted)/non-restricted(there is not a single role in the access set of roles)
also callback on modal hide() to call router and modify URL, some other minor fixes, pep8
Affected #: 14 files
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 lib/galaxy/security/__init__.py
--- a/lib/galaxy/security/__init__.py
+++ b/lib/galaxy/security/__init__.py
@@ -969,11 +969,37 @@
self.make_dataset_public( dataset )
def dataset_is_public( self, dataset ):
- # A dataset is considered public if there are no "access" actions associated with it. Any
- # other actions ( 'manage permissions', 'edit metadata' ) are irrelevant.
- # SM: Accessing dataset.actions will cause a query to be emitted.
+ """
+ A dataset is considered public if there are no "access" actions
+ associated with it. Any other actions ( 'manage permissions',
+ 'edit metadata' ) are irrelevant. Accessing dataset.actions
+ will cause a query to be emitted.
+ """
return self.permitted_actions.DATASET_ACCESS.action not in [ a.action for a in dataset.actions ]
+ def dataset_is_unrestricted( self, trans, dataset):
+ """
+ Different implementation of the method above with signature:
+ def dataset_is_public( self, dataset )
+ """
+ return len( dataset.library_dataset_dataset_association.get_access_roles( trans ) ) == 0
+
+ def dataset_is_private_to_user( self, trans, dataset ):
+ """
+ If the dataset object has exactly one access role and that is the
+ current user's private role then we consider the dataset private.
+ """
+ private_role = self.get_private_user_role( trans.user )
+ access_roles = dataset.library_dataset_dataset_association.get_access_roles( trans )
+
+ if len(access_roles) != 1:
+ return False
+ else:
+ if access_roles[0].id == private_role.id:
+ return True
+ else:
+ return False
+
def datasets_are_public( self, trans, datasets ):
'''
Given a transaction object and a list of Datasets, return
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 lib/galaxy/webapps/galaxy/api/lda_datasets.py
--- a/lib/galaxy/webapps/galaxy/api/lda_datasets.py
+++ b/lib/galaxy/webapps/galaxy/api/lda_datasets.py
@@ -12,12 +12,13 @@
import urllib
import urllib2
import zipfile
+from galaxy import web
+from galaxy import util
from galaxy import exceptions
-from galaxy.exceptions import ItemAccessibilityException, MessageException, ItemDeletionException, ObjectNotFound
+from galaxy.exceptions import ObjectNotFound
+from paste.httpexceptions import HTTPBadRequest, HTTPInternalServerError
from galaxy.web import _future_expose_api as expose_api
from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous
-from galaxy import util
-from galaxy import web
from galaxy.security import Action
from galaxy.util.streamball import StreamBall
from galaxy.web.base.controller import BaseAPIController, UsesVisualizationMixin
@@ -25,6 +26,7 @@
import logging
log = logging.getLogger( __name__ )
+
class LibraryDatasetsController( BaseAPIController, UsesVisualizationMixin ):
@expose_api_anonymous
@@ -42,28 +44,25 @@
.. seealso:: :attr:`galaxy.web.base.controller.UsesLibraryMixinItems.get_library_dataset`
"""
try:
- dataset = self.get_library_dataset( trans, id = id, check_ownership=False, check_accessible=True )
- except Exception, e:
+ dataset = self.get_library_dataset( trans, id=id, check_ownership=False, check_accessible=True )
+ except Exception:
raise exceptions.ObjectNotFound( 'Requested dataset was not found.' )
- rval = dataset.to_dict()
- rval['deleted'] = dataset.deleted
- rval['id'] = trans.security.encode_id(rval['id'])
- rval['ldda_id'] = trans.security.encode_id(rval['ldda_id'])
- rval['parent_library_id'] = trans.security.encode_id(rval['parent_library_id'])
- rval['folder_id'] = 'F' + trans.security.encode_id(rval['folder_id'])
+ rval = trans.security.encode_all_ids( dataset.to_dict() )
+ rval[ 'deleted' ] = dataset.deleted
+ rval[ 'folder_id' ] = 'F' + rval[ 'folder_id' ]
return rval
-
@expose_api
def delete( self, trans, encoded_dataset_id, **kwd ):
"""
delete( self, trans, encoded_dataset_id, **kwd ):
* DELETE /api/libraries/datasets/{encoded_dataset_id}
- Marks the dataset deleted.
+ Marks the dataset deleted or undeletes it based on the value
+ of the undelete flag (if present).
"""
undelete = util.string_as_bool( kwd.get( 'undelete', False ) )
try:
- dataset = self.get_library_dataset( trans, id = encoded_dataset_id, check_ownership=False, check_accessible=False )
+ dataset = self.get_library_dataset( trans, id=encoded_dataset_id, check_ownership=False, check_accessible=False )
except Exception, e:
raise exceptions.ObjectNotFound( 'Requested dataset was not found.' + str(e) )
current_user_roles = trans.get_current_user_roles()
@@ -79,21 +78,18 @@
trans.sa_session.add( dataset )
trans.sa_session.flush()
- rval = dataset.to_dict()
- rval['update_time'] = dataset.update_time.strftime( "%Y-%m-%d %I:%M %p" )
- rval['deleted'] = dataset.deleted
- rval['id'] = trans.security.encode_id(rval['id'])
- rval['ldda_id'] = trans.security.encode_id(rval['ldda_id'])
- rval['parent_library_id'] = trans.security.encode_id(rval['parent_library_id'])
- rval['folder_id'] = 'F' + trans.security.encode_id(rval['folder_id'])
+ rval = trans.security.encode_all_ids( dataset.to_dict() )
+ rval[ 'update_time' ] = dataset.update_time.strftime( "%Y-%m-%d %I:%M %p" )
+ rval[ 'deleted' ] = dataset.deleted
+ rval[ 'folder_id' ] = 'F' + rval[ 'folder_id' ]
return rval
-
@web.expose
def download( self, trans, format, **kwd ):
"""
download( self, trans, format, **kwd )
* GET /api/libraries/datasets/download/{format}
+ * POST /api/libraries/datasets/download/{format}
Downloads requested datasets (identified by encoded IDs) in requested format.
example: ``GET localhost:8080/api/libraries/datasets/download/tbz?ldda_ids%255B%255D=a0d84b45643a2678&ldda_ids%255B%255D=fe38c84dcd46c828``
@@ -111,33 +107,25 @@
:raises: MessageException, ItemDeletionException, ItemAccessibilityException, HTTPBadRequest, OSError, IOError, ObjectNotFound
"""
lddas = []
- datasets_to_download = kwd['ldda_ids%5B%5D']
+ datasets_to_download = kwd[ 'ldda_ids%5B%5D' ]
- if ( datasets_to_download != None ):
+ if ( datasets_to_download is not None ):
datasets_to_download = util.listify( datasets_to_download )
for dataset_id in datasets_to_download:
try:
ldda = self.get_hda_or_ldda( trans, hda_ldda='ldda', dataset_id=dataset_id )
lddas.append( ldda )
- except ItemAccessibilityException:
- trans.response.status = 403
- return 'Insufficient rights to access library dataset with id: (%s)' % str( dataset_id )
- except MessageException:
- trans.response.status = 400
- return 'Wrong library dataset id: (%s)' % str( dataset_id )
- except ItemDeletionException:
- trans.response.status = 400
- return 'The item with library dataset id: (%s) is deleted' % str( dataset_id )
except HTTPBadRequest, e:
- return 'http bad request' + str( e.err_msg )
+ raise exceptions.RequestParameterInvalidException( 'Bad Request. ' + str( e.err_msg ) )
+ except HTTPInternalServerError, e:
+ raise exceptions.InternalServerError( 'Internal error. ' + str( e.err_msg ) )
except Exception, e:
- trans.response.status = 500
- return 'error of unknown kind' + str( e )
+ raise exceptions.InternalServerError( 'Unknown error. ' + str( e ) )
- if format in [ 'zip','tgz','tbz' ]:
+ if format in [ 'zip', 'tgz', 'tbz' ]:
# error = False
killme = string.punctuation + string.whitespace
- trantab = string.maketrans(killme,'_'*len(killme))
+ trantab = string.maketrans( killme, '_'*len( killme ) )
try:
outext = 'zip'
if format == 'zip':
@@ -149,7 +137,7 @@
archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_STORED, True )
else:
archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_DEFLATED, True )
- archive.add = lambda x, y: archive.write( x, y.encode('CP437') )
+ archive.add = lambda x, y: archive.write( x, y.encode( 'CP437' ) )
elif format == 'tgz':
if trans.app.config.upstream_gzip:
archive = StreamBall( 'w|' )
@@ -162,12 +150,10 @@
outext = 'tbz2'
except ( OSError, zipfile.BadZipfile ):
log.exception( "Unable to create archive for download" )
- trans.response.status = 500
- return "Unable to create archive for download, please report this error"
- except:
- log.exception( "Unexpected error %s in create archive for download" % sys.exc_info()[0] )
- trans.response.status = 500
- return "Unable to create archive for download, please report - %s" % sys.exc_info()[0]
+ raise exceptions.InternalServerError( "Unable to create archive for download." )
+ except Exception:
+ log.exception( "Unexpected error %s in create archive for download" % sys.exc_info()[ 0 ] )
+ raise exceptions.InternalServerError( "Unable to create archive for download." )
composite_extensions = trans.app.datatypes_registry.get_composite_extensions()
seen = []
for ldda in lddas:
@@ -178,7 +164,7 @@
while parent_folder is not None:
# Exclude the now-hidden "root folder"
if parent_folder.parent is None:
- path = os.path.join( parent_folder.library_root[0].name, path )
+ path = os.path.join( parent_folder.library_root[ 0 ].name, path )
break
path = os.path.join( parent_folder.name, path )
parent_folder = parent_folder.parent
@@ -186,87 +172,83 @@
while path in seen:
path += '_'
seen.append( path )
- zpath = os.path.split(path)[-1] # comes as base_name/fname
- outfname,zpathext = os.path.splitext(zpath)
- if is_composite: # need to add all the components from the extra_files_path to the zip
+ zpath = os.path.split(path)[ -1 ] # comes as base_name/fname
+ outfname, zpathext = os.path.splitext( zpath )
+
+ if is_composite: # need to add all the components from the extra_files_path to the zip
if zpathext == '':
- zpath = '%s.html' % zpath # fake the real nature of the html file
+ zpath = '%s.html' % zpath # fake the real nature of the html file
try:
- if format=='zip':
- archive.add( ldda.dataset.file_name, zpath ) # add the primary of a composite set
- else:
- archive.add( ldda.dataset.file_name, zpath, check_file=True ) # add the primary of a composite set
+ if format == 'zip':
+ archive.add( ldda.dataset.file_name, zpath ) # add the primary of a composite set
+ else:
+ archive.add( ldda.dataset.file_name, zpath, check_file=True ) # add the primary of a composite set
except IOError:
- log.exception( "Unable to add composite parent %s to temporary library download archive" % ldda.dataset.file_name)
- trans.response.status = 500
- return "Unable to create archive for download, please report this error"
+ log.exception( "Unable to add composite parent %s to temporary library download archive" % ldda.dataset.file_name )
+ raise exceptions.InternalServerError( "Unable to create archive for download." )
except ObjectNotFound:
log.exception( "Requested dataset %s does not exist on the host." % ldda.dataset.file_name )
- trans.response.status = 500
- return "Requested dataset does not exist on the host."
- except:
- trans.response.status = 500
- return "Unknown error, please report this error"
- flist = glob.glob(os.path.join(ldda.dataset.extra_files_path,'*.*')) # glob returns full paths
+ raise exceptions.ObjectNotFound( "Requested dataset not found. " )
+ except Exception, e:
+ log.exception( "Unable to add composite parent %s to temporary library download archive" % ldda.dataset.file_name )
+ raise exceptions.InternalServerError( "Unable to add composite parent to temporary library download archive. " + str( e ) )
+
+ flist = glob.glob(os.path.join(ldda.dataset.extra_files_path, '*.*')) # glob returns full paths
for fpath in flist:
- efp,fname = os.path.split(fpath)
+ efp, fname = os.path.split(fpath)
if fname > '':
fname = fname.translate(trantab)
try:
- if format=='zip':
- archive.add( fpath,fname )
+ if format == 'zip':
+ archive.add( fpath, fname )
else:
- archive.add( fpath,fname, check_file=True )
+ archive.add( fpath, fname, check_file=True )
except IOError:
- log.exception( "Unable to add %s to temporary library download archive %s" % (fname,outfname))
- trans.response.status = 500
- return "Unable to create archive for download, please report this error"
+ log.exception( "Unable to add %s to temporary library download archive %s" % ( fname, outfname) )
+ raise exceptions.InternalServerError( "Unable to create archive for download." )
except ObjectNotFound:
log.exception( "Requested dataset %s does not exist on the host." % fpath )
- trans.response.status = 500
- return "Requested dataset does not exist on the host."
- except:
- trans.response.status = 500
- return "Unknown error, please report this error"
- else: # simple case
+ raise exceptions.ObjectNotFound( "Requested dataset not found." )
+ except Exception, e:
+ log.exception( "Unable to add %s to temporary library download archive %s" % ( fname, outfname ) )
+ raise exceptions.InternalServerError( "Unable to add dataset to temporary library download archive . " + str( e ) )
+
+ else: # simple case
try:
- if format=='zip':
+ if format == 'zip':
archive.add( ldda.dataset.file_name, path )
else:
- archive.add( ldda.dataset.file_name, path, check_file=True )
+ archive.add( ldda.dataset.file_name, path, check_file=True )
except IOError:
- log.exception( "Unable to write %s to temporary library download archive" % ldda.dataset.file_name)
- trans.response.status = 500
- return "Unable to create archive for download, please report this error"
+ log.exception( "Unable to write %s to temporary library download archive" % ldda.dataset.file_name )
+ raise exceptions.InternalServerError( "Unable to create archive for download" )
except ObjectNotFound:
log.exception( "Requested dataset %s does not exist on the host." % ldda.dataset.file_name )
- trans.response.status = 500
- return "Requested dataset does not exist on the host."
- except:
- trans.response.status = 500
- return "Unknown error, please report this error"
+ raise exceptions.ObjectNotFound( "Requested dataset not found." )
+ except Exception, e:
+ log.exception( "Unable to add %s to temporary library download archive %s" % ( fname, outfname ) )
+ raise exceptions.InternalServerError( "Unknown error. " + str( e ) )
lname = 'selected_dataset'
fname = lname.replace( ' ', '_' ) + '_files'
if format == 'zip':
archive.close()
trans.response.set_content_type( "application/octet-stream" )
- trans.response.headers[ "Content-Disposition" ] = 'attachment; filename="%s.%s"' % (fname,outext)
- archive = util.streamball.ZipBall(tmpf, tmpd)
+ trans.response.headers[ "Content-Disposition" ] = 'attachment; filename="%s.%s"' % ( fname, outext )
+ archive = util.streamball.ZipBall( tmpf, tmpd )
archive.wsgi_status = trans.response.wsgi_status()
archive.wsgi_headeritems = trans.response.wsgi_headeritems()
return archive.stream
else:
trans.response.set_content_type( "application/x-tar" )
- trans.response.headers[ "Content-Disposition" ] = 'attachment; filename="%s.%s"' % (fname,outext)
+ trans.response.headers[ "Content-Disposition" ] = 'attachment; filename="%s.%s"' % ( fname, outext )
archive.wsgi_status = trans.response.wsgi_status()
archive.wsgi_headeritems = trans.response.wsgi_headeritems()
return archive.stream
elif format == 'uncompressed':
if len(lddas) != 1:
- trans.response.status = 400
- return 'Wrong request'
+ raise exceptions.RequestParameterInvalidException( "You can download only one uncompressed file at once." )
else:
- single_dataset = lddas[0]
+ single_dataset = lddas[ 0 ]
trans.response.set_content_type( single_dataset.get_mime() )
fStat = os.stat( ldda.file_name )
trans.response.headers[ 'Content-Length' ] = int( fStat.st_size )
@@ -277,8 +259,6 @@
try:
return open( single_dataset.file_name )
except:
- trans.response.status = 500
- return 'This dataset contains no content'
+ raise exceptions.InternalServerError( "This dataset contains no content." )
else:
- trans.response.status = 400
- return 'Wrong format parameter specified';
+ raise exceptions.RequestParameterInvalidException( "Wrong format parameter specified" )
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 static/scripts/galaxy.library.js
--- a/static/scripts/galaxy.library.js
+++ b/static/scripts/galaxy.library.js
@@ -106,12 +106,12 @@
});
this.library_router.on('route:dataset_detail', function(folder_id, dataset_id){
- if (Galaxy.libraries.folderToolbarView){
- Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView({id: folder_id, dataset_id: dataset_id});
- } else {
+ // if (Galaxy.libraries.folderToolbarView){
+ // Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView({id: folder_id, dataset_id: dataset_id});
+ // } else {
Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView({id: folder_id});
Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView({id: folder_id, dataset_id: dataset_id});
- }
+ // }
});
Backbone.history.start({pushState: false});
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 static/scripts/mvc/library/library-folderlist-view.js
--- a/static/scripts/mvc/library/library-folderlist-view.js
+++ b/static/scripts/mvc/library/library-folderlist-view.js
@@ -283,21 +283,15 @@
},
makeDarkRow: function($row){
- $row.removeClass('light');
- $row.find('a').removeClass('light');
- $row.addClass('dark');
- $row.find('a').addClass('dark');
- $row.find('span').removeClass('fa-file-o');
- $row.find('span').addClass('fa-file');
+ $row.removeClass('light').addClass('dark');
+ $row.find('a').removeClass('light').addClass('dark');
+ $row.find('.fa-file-o').removeClass('fa-file-o').addClass('fa-file');
},
makeWhiteRow: function($row){
- $row.removeClass('dark');
- $row.find('a').removeClass('dark');
- $row.addClass('light');
- $row.find('a').addClass('light');
- $row.find('span').addClass('fa-file-o');
- $row.find('span').removeClass('fa-file');
+ $row.removeClass('dark').addClass('light');
+ $row.find('a').removeClass('dark').addClass('light');
+ $row.find('.fa-file').removeClass('fa-file').addClass('fa-file-o');
},
// MMMMMMMMMMMMMMMMMM
@@ -324,11 +318,11 @@
tmpl_array.push(' <thead>');
tmpl_array.push(' <th class="button_heading"></th>');
tmpl_array.push(' <th style="text-align: center; width: 20px; " title="Check to select all datasets"><input id="select-all-checkboxes" style="margin: 0;" type="checkbox"></th>');
- tmpl_array.push(' <th style="width:30%;"><a class="sort-folder-link" title="Click to reverse order" href="#">name</a><span title="Sorted alphabetically" class="fa fa-sort-alpha-<%- order %>"></span></th>');
- tmpl_array.push(' <th>data type</th>');
- tmpl_array.push(' <th>size</th>');
- tmpl_array.push(' <th>time updated (UTC)</th>');
- tmpl_array.push(' <th style="width:15%;"></th> ');
+ tmpl_array.push(' <th><a class="sort-folder-link" title="Click to reverse order" href="#">name</a><span title="Sorted alphabetically" class="fa fa-sort-alpha-<%- order %>"></span></th>');
+ tmpl_array.push(' <th style="width:5%;">data type</th>');
+ tmpl_array.push(' <th style="width:5%;">size</th>');
+ tmpl_array.push(' <th style="width:160px;">time updated (UTC)</th>');
+ tmpl_array.push(' <th style="width:10%;"></th> ');
tmpl_array.push(' </thead>');
tmpl_array.push(' <tbody id="folder_list_body">');
tmpl_array.push(' <tr id="first_folder_item">');
@@ -343,7 +337,7 @@
tmpl_array.push(' </tbody>');
tmpl_array.push('</table>');
- tmpl_array.push('<div class="empty-folder-message" style="display:none;">This folder is either empty or you do not have proper access permissions to see the contents.</div>');
+ tmpl_array.push('<div class="empty-folder-message" style="display:none;">This folder is either empty or you do not have proper access permissions to see the contents. If you expected something to show up please consult the <a href="https://wiki.galaxyproject.org/Admin/DataLibraries/LibrarySecurity">library security wikipage</a> or visit the <a href="https://biostar.usegalaxy.org/">Galaxy support site</a>.</div>');
return _.template(tmpl_array.join(''));
}
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 static/scripts/mvc/library/library-folderrow-view.js
--- a/static/scripts/mvc/library/library-folderrow-view.js
+++ b/static/scripts/mvc/library/library-folderrow-view.js
@@ -15,7 +15,6 @@
lastSelectedHistory: '',
events: {
- // 'click .library-dataset' : 'showDatasetDetails',
'click .undelete_dataset_btn' : 'undelete_dataset'
},
@@ -102,6 +101,10 @@
'Import' : function() { self.importCurrentIntoHistory(); },
'Download' : function() { self.downloadCurrent(); },
'Close' : function() { self.modal.hide(); }
+ },
+ closing_callback : function(){
+ var path_to_folder = Backbone.history.fragment.split('/datasets')[0];
+ Galaxy.libraries.library_router.navigate('#' + path_to_folder, {trigger:true, replace:true});
}
});
@@ -221,7 +224,7 @@
* bug in tooltip
*/
$(".tooltip").hide();
-
+ var that = this;
var dataset_id = $(event.target).closest('tr')[0].id;
var dataset = Galaxy.libraries.folderListView.collection.get(dataset_id);
dataset.url = dataset.urlRoot + dataset.id + '?undelete=true';
@@ -230,7 +233,7 @@
Galaxy.libraries.folderListView.collection.remove(dataset_id);
var updated_dataset = new mod_library_model.Item(response);
Galaxy.libraries.folderListView.collection.add(updated_dataset)
- mod_toastr.success('Dataset undeleted');
+ mod_toastr.success('Dataset undeleted. Click this to see it.', '', {onclick: function() {that.showDatasetDetails()}});
},
error : function(model, response){
if (typeof response.responseJSON !== "undefined"){
@@ -252,9 +255,9 @@
tmpl_array.push(' <td></td>');
tmpl_array.push(' <td>');
tmpl_array.push(' <a href="#folders/<%- content_item.id %>"><%- content_item.get("name") %></a>');
- tmpl_array.push(' <% if (content_item.get("item_count") === 0) { %>'); // empty folder
- tmpl_array.push(' <span>(empty folder)</span>');
- tmpl_array.push(' <% } %>');
+ // tmpl_array.push(' <% if (content_item.get("item_count") === 0) { %>'); // empty folder
+ // tmpl_array.push(' <span>(empty folder)</span>');
+ // tmpl_array.push(' <% } %>');
tmpl_array.push(' </td>');
tmpl_array.push(' <td>folder</td>');
tmpl_array.push(' <td></td>');
@@ -279,7 +282,12 @@
tmpl_array.push(' <td><%= _.escape(content_item.get("data_type")) %></td>'); // data type
tmpl_array.push(' <td><%= _.escape(content_item.get("readable_size")) %></td>'); // size
tmpl_array.push(' <td><%= _.escape(content_item.get("update_time")) %></td>'); // time updated
- tmpl_array.push(' <td></td>');
+ tmpl_array.push(' <td>');
+ tmpl_array.push(' <% if (content_item.get("is_unrestricted")) { %><span data-toggle="tooltip" data-placement="top" title="Unrestricted dataset" style="color:grey;" class="fa fa-globe fa-lg"></span><% } %>');
+ tmpl_array.push(' <% if (content_item.get("is_private")) { %><span data-toggle="tooltip" data-placement="top" title="Private dataset" style="color:grey;" class="fa fa-key fa-lg"></span><% } %>');
+ tmpl_array.push(' <% if ((content_item.get("is_unrestricted") === false) && (content_item.get("is_private") === false)) { %><span data-toggle="tooltip" data-placement="top" title="Restricted dataset" style="color:grey;" class="fa fa-group fa-lg"></span>');
+ tmpl_array.push(' <% } %>');
+ tmpl_array.push(' </td>');
tmpl_array.push('</tr>');
return _.template(tmpl_array.join(''));
@@ -292,12 +300,12 @@
tmpl_array.push(' <td>');
tmpl_array.push(' <span title="Dataset" class="fa fa-file-o"></span>');
tmpl_array.push(' </td>');
- tmpl_array.push(' <td><span data-toggle="tooltip" data-placement="top" title="Marked deleted" style="color:grey;" class="fa fa-ban fa-lg"></span></td>');
+ tmpl_array.push(' <td></td>');
tmpl_array.push(' <td style="color:grey;"><%- content_item.get("name") %></td>'); // dataset
tmpl_array.push(' <td><%= _.escape(content_item.get("data_type")) %></td>'); // data type
tmpl_array.push(' <td><%= _.escape(content_item.get("readable_size")) %></td>'); // size
tmpl_array.push(' <td><%= _.escape(content_item.get("update_time")) %></td>'); // time updated
- tmpl_array.push(' <td class="right-center"><button data-toggle="tooltip" data-placement="top" title="Undelete <%- content_item.get("name") %>" class="primary-button btn-xs undelete_dataset_btn show_on_hover" type="button" style="display:none;"><span class="fa fa-unlock"> Undelete</span></button></td>');
+ tmpl_array.push(' <td><span data-toggle="tooltip" data-placement="top" title="Marked deleted" style="color:grey;" class="fa fa-ban fa-lg"></span><button data-toggle="tooltip" data-placement="top" title="Undelete <%- content_item.get("name") %>" class="primary-button btn-xs undelete_dataset_btn show_on_hover" type="button" style="display:none; margin-left:1em;"><span class="fa fa-unlock"> Undelete</span></button></td>');
tmpl_array.push('</tr>');
return _.template(tmpl_array.join(''));
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 static/scripts/mvc/library/library-librarylist-view.js
--- a/static/scripts/mvc/library/library-librarylist-view.js
+++ b/static/scripts/mvc/library/library-librarylist-view.js
@@ -149,7 +149,7 @@
tmpl_array.push('<div class="library_container table-responsive">');
tmpl_array.push('<% if(length === 0) { %>');
- tmpl_array.push('<div>There are no libraries visible to you. If you expected some to show up please consult the <a href="https://wiki.galaxyproject.org/Admin/DataLibraries/LibrarySecurity">library security wikipage</a>.</div>');
+ tmpl_array.push('<div>There are no libraries visible to you. If you expected some to show up please consult the <a href="https://wiki.galaxyproject.org/Admin/DataLibraries/LibrarySecurity">library security wikipage</a> or visit the <a href="https://biostar.usegalaxy.org/">Galaxy support site</a>.</div>');
tmpl_array.push('<% } else{ %>');
tmpl_array.push('<table class="grid table table-condensed">');
tmpl_array.push(' <thead>');
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 static/scripts/mvc/library/library-libraryrow-view.js
--- a/static/scripts/mvc/library/library-libraryrow-view.js
+++ b/static/scripts/mvc/library/library-libraryrow-view.js
@@ -245,7 +245,7 @@
tmpl_array.push(' <% } %>');
tmpl_array.push(' <td class="right-center">');
tmpl_array.push(' <% if( (library.get("public")) && (library.get("deleted") === false) ) { %>');
- tmpl_array.push(' <span data-toggle="tooltip" data-placement="top" title="Public" style="color:grey;" class="fa fa-globe fa-lg public_lib_ico"></span>');
+ tmpl_array.push(' <span data-toggle="tooltip" data-placement="top" title="Unrestricted library" style="color:grey;" class="fa fa-globe fa-lg public_lib_ico"></span>');
tmpl_array.push(' <% }%>');
tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Modify <%- library.get("name") %>" class="primary-button btn-xs edit_library_btn" type="button" style="<% if(button_config.edit_library_btn === false) { print("display:none;") } %>"><span class="fa fa-pencil"></span></button>');
tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Modify permissions" class="primary-button btn-xs permission_library_btn" type="button" style="<% if(button_config.permission_library_btn === false) { print("display:none;") } %>"><span class="fa fa-group"></span></button>');
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 static/scripts/mvc/library/library-librarytoolbar-view.js
--- a/static/scripts/mvc/library/library-librarytoolbar-view.js
+++ b/static/scripts/mvc/library/library-librarytoolbar-view.js
@@ -116,7 +116,6 @@
tmpl_array.push('<div class="library_style_container">');
// TOOLBAR
tmpl_array.push(' <div id="toolbar_form" margin-top:0.5em; ">');
- tmpl_array.push(' <h3>Data Libraries Beta Test. This is work in progress. Please report problems & ideas via <a href="mailto:galaxy-bugs@bx.psu.edu?Subject=DataLibrariesBeta_Feedback" target="_blank">email</a> and <a href="https://trello.com/c/nwYQNFPK/56-data-library-ui-progressive-display-of-fol…" target="_blank">Trello</a>.</h3>');
tmpl_array.push(' <% if(admin_user === true) { %>');
tmpl_array.push(' <div id="library_toolbar">');
tmpl_array.push(' <span data-toggle="tooltip" data-placement="top" title="Include deleted libraries"><input id="include_deleted_chk" style="margin: 0;" type="checkbox"><span class="fa fa-trash-o fa-lg"></span></input></span>');
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 static/scripts/packed/galaxy.library.js
--- a/static/scripts/packed/galaxy.library.js
+++ b/static/scripts/packed/galaxy.library.js
@@ -1,1 +1,1 @@
-define(["galaxy.masthead","utils/utils","libs/toastr","mvc/base-mvc","mvc/library/library-model","mvc/library/library-folderlist-view","mvc/library/library-librarylist-view","mvc/library/library-librarytoolbar-view","mvc/library/library-foldertoolbar-view"],function(e,c,g,k,h,a,f,d,i){var l=Backbone.Router.extend({initialize:function(){this.routesHit=0;Backbone.history.on("route",function(){this.routesHit++},this)},routes:{"":"libraries","folders/:id":"folder_content","folders/:folder_id/datasets/:dataset_id":"dataset_detail","folders/:folder_id/download/:format":"download"},back:function(){if(this.routesHit>1){window.history.back()}else{this.navigate("#",{trigger:true,replace:true})}}});var j=k.SessionStorageModel.extend({defaults:{with_deleted:false,sort_order:"asc",sort_by:"name"}});var b=Backbone.View.extend({libraryToolbarView:null,libraryListView:null,library_router:null,folderToolbarView:null,folderListView:null,initialize:function(){Galaxy.libraries=this;this.preferences=new j({id:"global-lib-prefs"});this.library_router=new l();this.library_router.on("route:libraries",function(){Galaxy.libraries.libraryToolbarView=new d.LibraryToolbarView();Galaxy.libraries.libraryListView=new f.LibraryListView()});this.library_router.on("route:folder_content",function(m){if(Galaxy.libraries.folderToolbarView){Galaxy.libraries.folderToolbarView.$el.unbind("click")}Galaxy.libraries.folderToolbarView=new i.FolderToolbarView({id:m});Galaxy.libraries.folderListView=new a.FolderListView({id:m})});this.library_router.on("route:download",function(m,n){if($("#folder_list_body").find(":checked").length===0){g.info("You have to select some datasets to download");Galaxy.libraries.library_router.navigate("folders/"+m,{trigger:true,replace:true})}else{Galaxy.libraries.folderToolbarView.download(m,n);Galaxy.libraries.library_router.navigate("folders/"+m,{trigger:false,replace:true})}});this.library_router.on("route:dataset_detail",function(n,m){if(Galaxy.libraries.folderToolbarView){Galaxy.libraries.folderListView=new a.FolderListView({id:n,dataset_id:m})}else{Galaxy.libraries.folderToolbarView=new i.FolderToolbarView({id:n});Galaxy.libraries.folderListView=new a.FolderListView({id:n,dataset_id:m})}});Backbone.history.start({pushState:false})}});return{GalaxyApp:b}});
\ No newline at end of file
+define(["galaxy.masthead","utils/utils","libs/toastr","mvc/base-mvc","mvc/library/library-model","mvc/library/library-folderlist-view","mvc/library/library-librarylist-view","mvc/library/library-librarytoolbar-view","mvc/library/library-foldertoolbar-view"],function(e,c,g,k,h,a,f,d,i){var l=Backbone.Router.extend({initialize:function(){this.routesHit=0;Backbone.history.on("route",function(){this.routesHit++},this)},routes:{"":"libraries","folders/:id":"folder_content","folders/:folder_id/datasets/:dataset_id":"dataset_detail","folders/:folder_id/download/:format":"download"},back:function(){if(this.routesHit>1){window.history.back()}else{this.navigate("#",{trigger:true,replace:true})}}});var j=k.SessionStorageModel.extend({defaults:{with_deleted:false,sort_order:"asc",sort_by:"name"}});var b=Backbone.View.extend({libraryToolbarView:null,libraryListView:null,library_router:null,folderToolbarView:null,folderListView:null,initialize:function(){Galaxy.libraries=this;this.preferences=new j({id:"global-lib-prefs"});this.library_router=new l();this.library_router.on("route:libraries",function(){Galaxy.libraries.libraryToolbarView=new d.LibraryToolbarView();Galaxy.libraries.libraryListView=new f.LibraryListView()});this.library_router.on("route:folder_content",function(m){if(Galaxy.libraries.folderToolbarView){Galaxy.libraries.folderToolbarView.$el.unbind("click")}Galaxy.libraries.folderToolbarView=new i.FolderToolbarView({id:m});Galaxy.libraries.folderListView=new a.FolderListView({id:m})});this.library_router.on("route:download",function(m,n){if($("#folder_list_body").find(":checked").length===0){g.info("You have to select some datasets to download");Galaxy.libraries.library_router.navigate("folders/"+m,{trigger:true,replace:true})}else{Galaxy.libraries.folderToolbarView.download(m,n);Galaxy.libraries.library_router.navigate("folders/"+m,{trigger:false,replace:true})}});this.library_router.on("route:dataset_detail",function(n,m){Galaxy.libraries.folderToolbarView=new i.FolderToolbarView({id:n});Galaxy.libraries.folderListView=new a.FolderListView({id:n,dataset_id:m})});Backbone.history.start({pushState:false})}});return{GalaxyApp:b}});
\ No newline at end of file
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 static/scripts/packed/mvc/library/library-folderlist-view.js
--- a/static/scripts/packed/mvc/library/library-folderlist-view.js
+++ b/static/scripts/packed/mvc/library/library-folderlist-view.js
@@ -1,1 +1,1 @@
-define(["galaxy.masthead","utils/utils","libs/toastr","mvc/library/library-model","mvc/library/library-folderrow-view"],function(c,e,f,d,a){var b=Backbone.View.extend({el:"#folder_items_element",defaults:{include_deleted:false},progress:0,progressStep:1,modal:null,folderContainer:null,sort:"asc",events:{"click #select-all-checkboxes":"selectAll","click .dataset_row":"selectClickedRow","click .sort-folder-link":"sort_clicked"},rowViews:{},initialize:function(g){this.options=_.defaults(this.options||{},g);this.fetchFolder()},fetchFolder:function(g){var g=g||{};this.options.include_deleted=g.include_deleted;var h=this;this.collection=new d.Folder();this.listenTo(this.collection,"add",this.renderOne);this.listenTo(this.collection,"remove",this.removeOne);this.folderContainer=new d.FolderContainer({id:this.options.id});this.folderContainer.url=this.folderContainer.attributes.urlRoot+this.options.id+"/contents";if(this.options.include_deleted){this.folderContainer.url=this.folderContainer.url+"?include_deleted=true"}this.folderContainer.fetch({success:function(i){h.folder_container=i;h.render();h.addAll(i.get("folder").models);if(h.options.dataset_id){_.findWhere(h.rowViews,{id:h.options.dataset_id}).showDatasetDetails()}},error:function(j,i){if(typeof i.responseJSON!=="undefined"){f.error(i.responseJSON.err_msg+" Click this to go back.","",{onclick:function(){Galaxy.libraries.library_router.back()}})}else{f.error("An error ocurred :(. Click this to go back.","",{onclick:function(){Galaxy.libraries.library_router.back()}})}}})},render:function(g){this.options=_.defaults(this.options,g);var h=this.templateFolder();var i=this.folderContainer.attributes.metadata.full_path;var j;if(i.length===1){j=0}else{j=i[i.length-2][0]}this.$el.html(h({path:this.folderContainer.attributes.metadata.full_path,id:this.options.id,upper_folder_id:j,order:this.sort}));$("#center [data-toggle]").tooltip();$("#center").css("overflow","auto")},postRender:function(){var g=this.folderContainer.attributes.metadata;g.contains_file=typeof this.collection.findWhere({type:"file"})!=="undefined";Galaxy.libraries.folderToolbarView.configureElements(g);$(".deleted_dataset").hover(function(){$(this).find(".show_on_hover").show()},function(){$(this).find(".show_on_hover").hide()})},addAll:function(g){_.each(g.reverse(),function(h){Galaxy.libraries.folderListView.collection.add(h)});$("#center [data-toggle]").tooltip();this.checkEmptiness();this.postRender()},renderAll:function(){var g=this;_.each(this.collection.models.reverse(),function(h){g.renderOne(h)});this.postRender()},renderOne:function(h){if(h.get("data_type")!=="folder"){this.options.contains_file=true;h.set("readable_size",this.size_to_string(h.get("file_size")))}h.set("folder_id",this.id);var g=new a.FolderRowView(h);this.rowViews[h.get("id")]=g;this.$el.find("#first_folder_item").after(g.el);$(".deleted_dataset").hover(function(){$(this).find(".show_on_hover").show()},function(){$(this).find(".show_on_hover").hide()})},removeOne:function(g){this.$el.find("#"+g.id).remove()},checkEmptiness:function(){if((this.$el.find(".dataset_row").length===0)&&(this.$el.find(".folder_row").length===0)){this.$el.find(".empty-folder-message").show()}else{this.$el.find(".empty-folder-message").hide()}},sort_clicked:function(g){g.preventDefault();if(this.sort==="asc"){this.sortFolder("name","desc");this.sort="desc"}else{this.sortFolder("name","asc");this.sort="asc"}this.render();this.renderAll()},sortFolder:function(h,g){if(h==="name"){if(g==="asc"){return this.collection.sortByNameAsc()}else{if(g==="desc"){return this.collection.sortByNameDesc()}}}},size_to_string:function(g){var h="";if(g>=100000000000){g=g/100000000000;h="TB"}else{if(g>=100000000){g=g/100000000;h="GB"}else{if(g>=100000){g=g/100000;h="MB"}else{if(g>=100){g=g/100;h="KB"}else{g=g*10;h="b"}}}}return(Math.round(g)/10)+h},selectAll:function(h){var g=h.target.checked;that=this;$(":checkbox","#folder_list_body").each(function(){this.checked=g;$row=$(this.parentElement.parentElement);if(g){that.makeDarkRow($row)}else{that.makeWhiteRow($row)}})},selectClickedRow:function(h){var j="";var g;var i;if(h.target.localName==="input"){j=h.target;g=$(h.target.parentElement.parentElement);i="input"}else{if(h.target.localName==="td"){j=$("#"+h.target.parentElement.id).find(":checkbox")[0];g=$(h.target.parentElement);i="td"}}if(j.checked){if(i==="td"){j.checked="";this.makeWhiteRow(g)}else{if(i==="input"){this.makeDarkRow(g)}}}else{if(i==="td"){j.checked="selected";this.makeDarkRow(g)}else{if(i==="input"){this.makeWhiteRow(g)}}}},makeDarkRow:function(g){g.removeClass("light");g.find("a").removeClass("light");g.addClass("dark");g.find("a").addClass("dark");g.find("span").removeClass("fa-file-o");g.find("span").addClass("fa-file")},makeWhiteRow:function(g){g.removeClass("dark");g.find("a").removeClass("dark");g.addClass("light");g.find("a").addClass("light");g.find("span").addClass("fa-file-o");g.find("span").removeClass("fa-file")},templateFolder:function(){var g=[];g.push('<ol class="breadcrumb">');g.push(' <li><a title="Return to the list of libraries" href="#">Libraries</a></li>');g.push(" <% _.each(path, function(path_item) { %>");g.push(" <% if (path_item[0] != id) { %>");g.push(' <li><a title="Return to this folder" href="#/folders/<%- path_item[0] %>"><%- path_item[1] %></a></li> ');g.push("<% } else { %>");g.push(' <li class="active"><span title="You are in this folder"><%- path_item[1] %></span></li>');g.push(" <% } %>");g.push(" <% }); %>");g.push("</ol>");g.push('<table id="folder_table" class="grid table table-condensed">');g.push(" <thead>");g.push(' <th class="button_heading"></th>');g.push(' <th style="text-align: center; width: 20px; " title="Check to select all datasets"><input id="select-all-checkboxes" style="margin: 0;" type="checkbox"></th>');g.push(' <th style="width:30%;"><a class="sort-folder-link" title="Click to reverse order" href="#">name</a><span title="Sorted alphabetically" class="fa fa-sort-alpha-<%- order %>"></span></th>');g.push(" <th>data type</th>");g.push(" <th>size</th>");g.push(" <th>time updated (UTC)</th>");g.push(' <th style="width:15%;"></th> ');g.push(" </thead>");g.push(' <tbody id="folder_list_body">');g.push(' <tr id="first_folder_item">');g.push(' <td><a href="#<% if (upper_folder_id !== 0){ print("folders/" + upper_folder_id)} %>" title="Go to parent folder" class="btn_open_folder btn btn-default btn-xs">..<a></td>');g.push(" <td></td>");g.push(" <td></td>");g.push(" <td></td>");g.push(" <td></td>");g.push(" <td></td>");g.push(" <td></td>");g.push(" </tr>");g.push(" </tbody>");g.push("</table>");g.push('<div class="empty-folder-message" style="display:none;">This folder is either empty or you do not have proper access permissions to see the contents.</div>');return _.template(g.join(""))}});return{FolderListView:b}});
\ No newline at end of file
+define(["galaxy.masthead","utils/utils","libs/toastr","mvc/library/library-model","mvc/library/library-folderrow-view"],function(c,e,f,d,a){var b=Backbone.View.extend({el:"#folder_items_element",defaults:{include_deleted:false},progress:0,progressStep:1,modal:null,folderContainer:null,sort:"asc",events:{"click #select-all-checkboxes":"selectAll","click .dataset_row":"selectClickedRow","click .sort-folder-link":"sort_clicked"},rowViews:{},initialize:function(g){this.options=_.defaults(this.options||{},g);this.fetchFolder()},fetchFolder:function(g){var g=g||{};this.options.include_deleted=g.include_deleted;var h=this;this.collection=new d.Folder();this.listenTo(this.collection,"add",this.renderOne);this.listenTo(this.collection,"remove",this.removeOne);this.folderContainer=new d.FolderContainer({id:this.options.id});this.folderContainer.url=this.folderContainer.attributes.urlRoot+this.options.id+"/contents";if(this.options.include_deleted){this.folderContainer.url=this.folderContainer.url+"?include_deleted=true"}this.folderContainer.fetch({success:function(i){h.folder_container=i;h.render();h.addAll(i.get("folder").models);if(h.options.dataset_id){_.findWhere(h.rowViews,{id:h.options.dataset_id}).showDatasetDetails()}},error:function(j,i){if(typeof i.responseJSON!=="undefined"){f.error(i.responseJSON.err_msg+" Click this to go back.","",{onclick:function(){Galaxy.libraries.library_router.back()}})}else{f.error("An error ocurred :(. Click this to go back.","",{onclick:function(){Galaxy.libraries.library_router.back()}})}}})},render:function(g){this.options=_.defaults(this.options,g);var h=this.templateFolder();var i=this.folderContainer.attributes.metadata.full_path;var j;if(i.length===1){j=0}else{j=i[i.length-2][0]}this.$el.html(h({path:this.folderContainer.attributes.metadata.full_path,id:this.options.id,upper_folder_id:j,order:this.sort}));$("#center [data-toggle]").tooltip();$("#center").css("overflow","auto")},postRender:function(){var g=this.folderContainer.attributes.metadata;g.contains_file=typeof this.collection.findWhere({type:"file"})!=="undefined";Galaxy.libraries.folderToolbarView.configureElements(g);$(".deleted_dataset").hover(function(){$(this).find(".show_on_hover").show()},function(){$(this).find(".show_on_hover").hide()})},addAll:function(g){_.each(g.reverse(),function(h){Galaxy.libraries.folderListView.collection.add(h)});$("#center [data-toggle]").tooltip();this.checkEmptiness();this.postRender()},renderAll:function(){var g=this;_.each(this.collection.models.reverse(),function(h){g.renderOne(h)});this.postRender()},renderOne:function(h){if(h.get("data_type")!=="folder"){this.options.contains_file=true;h.set("readable_size",this.size_to_string(h.get("file_size")))}h.set("folder_id",this.id);var g=new a.FolderRowView(h);this.rowViews[h.get("id")]=g;this.$el.find("#first_folder_item").after(g.el);$(".deleted_dataset").hover(function(){$(this).find(".show_on_hover").show()},function(){$(this).find(".show_on_hover").hide()})},removeOne:function(g){this.$el.find("#"+g.id).remove()},checkEmptiness:function(){if((this.$el.find(".dataset_row").length===0)&&(this.$el.find(".folder_row").length===0)){this.$el.find(".empty-folder-message").show()}else{this.$el.find(".empty-folder-message").hide()}},sort_clicked:function(g){g.preventDefault();if(this.sort==="asc"){this.sortFolder("name","desc");this.sort="desc"}else{this.sortFolder("name","asc");this.sort="asc"}this.render();this.renderAll()},sortFolder:function(h,g){if(h==="name"){if(g==="asc"){return this.collection.sortByNameAsc()}else{if(g==="desc"){return this.collection.sortByNameDesc()}}}},size_to_string:function(g){var h="";if(g>=100000000000){g=g/100000000000;h="TB"}else{if(g>=100000000){g=g/100000000;h="GB"}else{if(g>=100000){g=g/100000;h="MB"}else{if(g>=100){g=g/100;h="KB"}else{g=g*10;h="b"}}}}return(Math.round(g)/10)+h},selectAll:function(h){var g=h.target.checked;that=this;$(":checkbox","#folder_list_body").each(function(){this.checked=g;$row=$(this.parentElement.parentElement);if(g){that.makeDarkRow($row)}else{that.makeWhiteRow($row)}})},selectClickedRow:function(h){var j="";var g;var i;if(h.target.localName==="input"){j=h.target;g=$(h.target.parentElement.parentElement);i="input"}else{if(h.target.localName==="td"){j=$("#"+h.target.parentElement.id).find(":checkbox")[0];g=$(h.target.parentElement);i="td"}}if(j.checked){if(i==="td"){j.checked="";this.makeWhiteRow(g)}else{if(i==="input"){this.makeDarkRow(g)}}}else{if(i==="td"){j.checked="selected";this.makeDarkRow(g)}else{if(i==="input"){this.makeWhiteRow(g)}}}},makeDarkRow:function(g){g.removeClass("light").addClass("dark");g.find("a").removeClass("light").addClass("dark");g.find(".fa-file-o").removeClass("fa-file-o").addClass("fa-file")},makeWhiteRow:function(g){g.removeClass("dark").addClass("light");g.find("a").removeClass("dark").addClass("light");g.find(".fa-file").removeClass("fa-file").addClass("fa-file-o")},templateFolder:function(){var g=[];g.push('<ol class="breadcrumb">');g.push(' <li><a title="Return to the list of libraries" href="#">Libraries</a></li>');g.push(" <% _.each(path, function(path_item) { %>");g.push(" <% if (path_item[0] != id) { %>");g.push(' <li><a title="Return to this folder" href="#/folders/<%- path_item[0] %>"><%- path_item[1] %></a></li> ');g.push("<% } else { %>");g.push(' <li class="active"><span title="You are in this folder"><%- path_item[1] %></span></li>');g.push(" <% } %>");g.push(" <% }); %>");g.push("</ol>");g.push('<table id="folder_table" class="grid table table-condensed">');g.push(" <thead>");g.push(' <th class="button_heading"></th>');g.push(' <th style="text-align: center; width: 20px; " title="Check to select all datasets"><input id="select-all-checkboxes" style="margin: 0;" type="checkbox"></th>');g.push(' <th><a class="sort-folder-link" title="Click to reverse order" href="#">name</a><span title="Sorted alphabetically" class="fa fa-sort-alpha-<%- order %>"></span></th>');g.push(' <th style="width:5%;">data type</th>');g.push(' <th style="width:5%;">size</th>');g.push(' <th style="width:160px;">time updated (UTC)</th>');g.push(' <th style="width:10%;"></th> ');g.push(" </thead>");g.push(' <tbody id="folder_list_body">');g.push(' <tr id="first_folder_item">');g.push(' <td><a href="#<% if (upper_folder_id !== 0){ print("folders/" + upper_folder_id)} %>" title="Go to parent folder" class="btn_open_folder btn btn-default btn-xs">..<a></td>');g.push(" <td></td>");g.push(" <td></td>");g.push(" <td></td>");g.push(" <td></td>");g.push(" <td></td>");g.push(" <td></td>");g.push(" </tr>");g.push(" </tbody>");g.push("</table>");g.push('<div class="empty-folder-message" style="display:none;">This folder is either empty or you do not have proper access permissions to see the contents. If you expected something to show up please consult the <a href="https://wiki.galaxyproject.org/Admin/DataLibraries/LibrarySecurity">library security wikipage</a> or visit the <a href="https://biostar.usegalaxy.org/">Galaxy support site</a>.</div>');return _.template(g.join(""))}});return{FolderListView:b}});
\ No newline at end of file
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 static/scripts/packed/mvc/library/library-folderrow-view.js
--- a/static/scripts/packed/mvc/library/library-folderrow-view.js
+++ b/static/scripts/packed/mvc/library/library-folderrow-view.js
@@ -1,1 +1,1 @@
-define(["galaxy.masthead","utils/utils","libs/toastr","mvc/library/library-model"],function(b,d,e,c){var a=Backbone.View.extend({lastSelectedHistory:"",events:{"click .undelete_dataset_btn":"undelete_dataset"},options:{type:null},initialize:function(f){this.render(f)},render:function(f){var g=null;if(f.get("type")==="folder"){this.options.type="folder";g=this.templateRowFolder()}else{this.options.type="file";if(f.get("deleted")){g=this.templateRowDeletedFile()}else{g=this.templateRowFile()}}this.setElement(g({content_item:f}));this.$el.show();return this},showDatasetDetails:function(){var i=this.id;var h=new c.Item();var g=new c.GalaxyHistories();h.id=i;var f=this;h.fetch({success:function(j){g.fetch({success:function(k){f.renderModalAfterFetch(j,k)},error:function(l,k){if(typeof k.responseJSON!=="undefined"){e.error(k.responseJSON.err_msg)}else{e.error("An error occured during fetching histories:(")}f.renderModalAfterFetch(j)}})},error:function(k,j){if(typeof j.responseJSON!=="undefined"){e.error(j.responseJSON.err_msg)}else{e.error("An error occured during loading dataset details :(")}}})},renderModalAfterFetch:function(k,h){var i=this.size_to_string(k.get("file_size"));var j=_.template(this.templateDatasetModal(),{item:k,size:i});var g=this;this.modal=Galaxy.modal;this.modal.show({closing_events:true,title:k.get("name"),body:j,buttons:{Import:function(){g.importCurrentIntoHistory()},Download:function(){g.downloadCurrent()},Close:function(){g.modal.hide()}}});$(".peek").html(k.get("peek"));if(typeof history.models!==undefined){var f=_.template(this.templateHistorySelectInModal(),{histories:h.models});$(this.modal.elMain).find(".buttons").prepend(f);if(g.lastSelectedHistory.length>0){$(this.modal.elMain).find("#dataset_import_single").val(g.lastSelectedHistory)}}},size_to_string:function(f){var g="";if(f>=100000000000){f=f/100000000000;g="TB"}else{if(f>=100000000){f=f/100000000;g="GB"}else{if(f>=100000){f=f/100000;g="MB"}else{if(f>=100){f=f/100;g="KB"}else{f=f*10;g="b"}}}}return(Math.round(f)/10)+g},downloadCurrent:function(){this.modal.disableButton("Import");this.modal.disableButton("Download");var f=[];f.push($("#id_row").attr("data-id"));var g="/api/libraries/datasets/download/uncompressed";var h={ldda_ids:f};this.processDownload(g,h);this.modal.enableButton("Import");this.modal.enableButton("Download")},processDownload:function(g,h,i){if(g&&h){h=typeof h=="string"?h:$.param(h);var f="";$.each(h.split("&"),function(){var j=this.split("=");f+='<input type="hidden" name="'+j[0]+'" value="'+j[1]+'" />'});$('<form action="'+g+'" method="'+(i||"post")+'">'+f+"</form>").appendTo("body").submit().remove();e.info("Your download will begin soon")}},importCurrentIntoHistory:function(){this.modal.disableButton("Import");this.modal.disableButton("Download");var i=$(this.modal.elMain).find("select[name=dataset_import_single] option:selected").val();this.lastSelectedHistory=i;var g=$("#id_row").attr("data-id");var j=new c.HistoryItem();var h=this;j.url=j.urlRoot+i+"/contents";var f="/api/histories/"+i+"/set_as_current";$.ajax({url:f,type:"PUT"});j.save({content:g,source:"library"},{success:function(){e.success("Dataset imported. Click this to start analysing it.","",{onclick:function(){window.location="/"}});h.modal.enableButton("Import");h.modal.enableButton("Download")},error:function(l,k){if(typeof k.responseJSON!=="undefined"){e.error("Dataset not imported. "+k.responseJSON.err_msg)}else{e.error("An error occured! Dataset not imported. Please try again.")}h.modal.enableButton("Import");h.modal.enableButton("Download")}})},undelete_dataset:function(g){$(".tooltip").hide();var f=$(g.target).closest("tr")[0].id;var h=Galaxy.libraries.folderListView.collection.get(f);h.url=h.urlRoot+h.id+"?undelete=true";h.destroy({success:function(j,i){Galaxy.libraries.folderListView.collection.remove(f);var k=new c.Item(i);Galaxy.libraries.folderListView.collection.add(k);e.success("Dataset undeleted")},error:function(j,i){if(typeof i.responseJSON!=="undefined"){e.error("Dataset was not undeleted. "+i.responseJSON.err_msg)}else{e.error("An error occured! Dataset was not undeleted. Please try again.")}}})},templateRowFolder:function(){tmpl_array=[];tmpl_array.push('<tr class="folder_row light" id="<%- content_item.id %>">');tmpl_array.push(" <td>");tmpl_array.push(' <span title="Folder" class="fa fa-folder-o"></span>');tmpl_array.push(" </td>");tmpl_array.push(" <td></td>");tmpl_array.push(" <td>");tmpl_array.push(' <a href="#folders/<%- content_item.id %>"><%- content_item.get("name") %></a>');tmpl_array.push(' <% if (content_item.get("item_count") === 0) { %>');tmpl_array.push(" <span>(empty folder)</span>");tmpl_array.push(" <% } %>");tmpl_array.push(" </td>");tmpl_array.push(" <td>folder</td>");tmpl_array.push(" <td></td>");tmpl_array.push(' <td><%= _.escape(content_item.get("update_time")) %></td>');tmpl_array.push(" <td></td>");tmpl_array.push("</tr>");return _.template(tmpl_array.join(""))},templateRowFile:function(){tmpl_array=[];tmpl_array.push('<tr class="dataset_row light" id="<%- content_item.id %>">');tmpl_array.push(" <td>");tmpl_array.push(' <span title="Dataset" class="fa fa-file-o"></span>');tmpl_array.push(" </td>");tmpl_array.push(' <td style="text-align: center; "><input style="margin: 0;" type="checkbox"></td>');tmpl_array.push(' <td><a href="#folders/<%- content_item.get("folder_id") %>/datasets/<%- content_item.id %>" class="library-dataset"><%- content_item.get("name") %><a></td>');tmpl_array.push(' <td><%= _.escape(content_item.get("data_type")) %></td>');tmpl_array.push(' <td><%= _.escape(content_item.get("readable_size")) %></td>');tmpl_array.push(' <td><%= _.escape(content_item.get("update_time")) %></td>');tmpl_array.push(" <td></td>");tmpl_array.push("</tr>");return _.template(tmpl_array.join(""))},templateRowDeletedFile:function(){tmpl_array=[];tmpl_array.push('<tr class="active deleted_dataset" id="<%- content_item.id %>">');tmpl_array.push(" <td>");tmpl_array.push(' <span title="Dataset" class="fa fa-file-o"></span>');tmpl_array.push(" </td>");tmpl_array.push(' <td><span data-toggle="tooltip" data-placement="top" title="Marked deleted" style="color:grey;" class="fa fa-ban fa-lg"></span></td>');tmpl_array.push(' <td style="color:grey;"><%- content_item.get("name") %></td>');tmpl_array.push(' <td><%= _.escape(content_item.get("data_type")) %></td>');tmpl_array.push(' <td><%= _.escape(content_item.get("readable_size")) %></td>');tmpl_array.push(' <td><%= _.escape(content_item.get("update_time")) %></td>');tmpl_array.push(' <td class="right-center"><button data-toggle="tooltip" data-placement="top" title="Undelete <%- content_item.get("name") %>" class="primary-button btn-xs undelete_dataset_btn show_on_hover" type="button" style="display:none;"><span class="fa fa-unlock"> Undelete</span></button></td>');tmpl_array.push("</tr>");return _.template(tmpl_array.join(""))},templateDatasetModal:function(){var f=[];f.push('<div class="modal_table">');f.push(' <table class="grid table table-striped table-condensed">');f.push(" <tr>");f.push(' <th scope="row" id="id_row" data-id="<%= _.escape(item.get("ldda_id")) %>">Name</th>');f.push(' <td><%= _.escape(item.get("name")) %></td>');f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Data type</th>');f.push(' <td><%= _.escape(item.get("data_type")) %></td>');f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Genome build</th>');f.push(' <td><%= _.escape(item.get("genome_build")) %></td>');f.push(" </tr>");f.push(' <th scope="row">Size</th>');f.push(" <td><%= _.escape(size) %></td>");f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Date uploaded (UTC)</th>');f.push(' <td><%= _.escape(item.get("date_uploaded")) %></td>');f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Uploaded by</th>');f.push(' <td><%= _.escape(item.get("uploaded_by")) %></td>');f.push(" </tr>");f.push(' <tr scope="row">');f.push(' <th scope="row">Data Lines</th>');f.push(' <td scope="row"><%= _.escape(item.get("metadata_data_lines")) %></td>');f.push(" </tr>");f.push(' <th scope="row">Comment Lines</th>');f.push(' <% if (item.get("metadata_comment_lines") === "") { %>');f.push(' <td scope="row"><%= _.escape(item.get("metadata_comment_lines")) %></td>');f.push(" <% } else { %>");f.push(' <td scope="row">unknown</td>');f.push(" <% } %>");f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Number of Columns</th>');f.push(' <td scope="row"><%= _.escape(item.get("metadata_columns")) %></td>');f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Column Types</th>');f.push(' <td scope="row"><%= _.escape(item.get("metadata_column_types")) %></td>');f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Miscellaneous information</th>');f.push(' <td scope="row"><%= _.escape(item.get("misc_blurb")) %></td>');f.push(" </tr>");f.push(" </table>");f.push(' <pre class="peek">');f.push(" </pre>");f.push("</div>");return f.join("")},templateHistorySelectInModal:function(){var f=[];f.push('<span id="history_modal_combo" style="width:100%; margin-left: 1em; margin-right: 1em; ">');f.push("Select history: ");f.push('<select id="dataset_import_single" name="dataset_import_single" style="width:40%; margin-bottom: 1em; "> ');f.push(" <% _.each(histories, function(history) { %>");f.push(' <option value="<%= _.escape(history.get("id")) %>"><%= _.escape(history.get("name")) %></option>');f.push(" <% }); %>");f.push("</select>");f.push("</span>");return f.join("")}});return{FolderRowView:a}});
\ No newline at end of file
+define(["galaxy.masthead","utils/utils","libs/toastr","mvc/library/library-model"],function(b,d,e,c){var a=Backbone.View.extend({lastSelectedHistory:"",events:{"click .undelete_dataset_btn":"undelete_dataset"},options:{type:null},initialize:function(f){this.render(f)},render:function(f){var g=null;if(f.get("type")==="folder"){this.options.type="folder";g=this.templateRowFolder()}else{this.options.type="file";if(f.get("deleted")){g=this.templateRowDeletedFile()}else{g=this.templateRowFile()}}this.setElement(g({content_item:f}));this.$el.show();return this},showDatasetDetails:function(){var i=this.id;var h=new c.Item();var g=new c.GalaxyHistories();h.id=i;var f=this;h.fetch({success:function(j){g.fetch({success:function(k){f.renderModalAfterFetch(j,k)},error:function(l,k){if(typeof k.responseJSON!=="undefined"){e.error(k.responseJSON.err_msg)}else{e.error("An error occured during fetching histories:(")}f.renderModalAfterFetch(j)}})},error:function(k,j){if(typeof j.responseJSON!=="undefined"){e.error(j.responseJSON.err_msg)}else{e.error("An error occured during loading dataset details :(")}}})},renderModalAfterFetch:function(k,h){var i=this.size_to_string(k.get("file_size"));var j=_.template(this.templateDatasetModal(),{item:k,size:i});var g=this;this.modal=Galaxy.modal;this.modal.show({closing_events:true,title:k.get("name"),body:j,buttons:{Import:function(){g.importCurrentIntoHistory()},Download:function(){g.downloadCurrent()},Close:function(){g.modal.hide()}},closing_callback:function(){var l=Backbone.history.fragment.split("/datasets")[0];Galaxy.libraries.library_router.navigate("#"+l,{trigger:true,replace:true})}});$(".peek").html(k.get("peek"));if(typeof history.models!==undefined){var f=_.template(this.templateHistorySelectInModal(),{histories:h.models});$(this.modal.elMain).find(".buttons").prepend(f);if(g.lastSelectedHistory.length>0){$(this.modal.elMain).find("#dataset_import_single").val(g.lastSelectedHistory)}}},size_to_string:function(f){var g="";if(f>=100000000000){f=f/100000000000;g="TB"}else{if(f>=100000000){f=f/100000000;g="GB"}else{if(f>=100000){f=f/100000;g="MB"}else{if(f>=100){f=f/100;g="KB"}else{f=f*10;g="b"}}}}return(Math.round(f)/10)+g},downloadCurrent:function(){this.modal.disableButton("Import");this.modal.disableButton("Download");var f=[];f.push($("#id_row").attr("data-id"));var g="/api/libraries/datasets/download/uncompressed";var h={ldda_ids:f};this.processDownload(g,h);this.modal.enableButton("Import");this.modal.enableButton("Download")},processDownload:function(g,h,i){if(g&&h){h=typeof h=="string"?h:$.param(h);var f="";$.each(h.split("&"),function(){var j=this.split("=");f+='<input type="hidden" name="'+j[0]+'" value="'+j[1]+'" />'});$('<form action="'+g+'" method="'+(i||"post")+'">'+f+"</form>").appendTo("body").submit().remove();e.info("Your download will begin soon")}},importCurrentIntoHistory:function(){this.modal.disableButton("Import");this.modal.disableButton("Download");var i=$(this.modal.elMain).find("select[name=dataset_import_single] option:selected").val();this.lastSelectedHistory=i;var g=$("#id_row").attr("data-id");var j=new c.HistoryItem();var h=this;j.url=j.urlRoot+i+"/contents";var f="/api/histories/"+i+"/set_as_current";$.ajax({url:f,type:"PUT"});j.save({content:g,source:"library"},{success:function(){e.success("Dataset imported. Click this to start analysing it.","",{onclick:function(){window.location="/"}});h.modal.enableButton("Import");h.modal.enableButton("Download")},error:function(l,k){if(typeof k.responseJSON!=="undefined"){e.error("Dataset not imported. "+k.responseJSON.err_msg)}else{e.error("An error occured! Dataset not imported. Please try again.")}h.modal.enableButton("Import");h.modal.enableButton("Download")}})},undelete_dataset:function(h){$(".tooltip").hide();var g=this;var f=$(h.target).closest("tr")[0].id;var i=Galaxy.libraries.folderListView.collection.get(f);i.url=i.urlRoot+i.id+"?undelete=true";i.destroy({success:function(k,j){Galaxy.libraries.folderListView.collection.remove(f);var l=new c.Item(j);Galaxy.libraries.folderListView.collection.add(l);e.success("Dataset undeleted. Click this to see it.","",{onclick:function(){g.showDatasetDetails()}})},error:function(k,j){if(typeof j.responseJSON!=="undefined"){e.error("Dataset was not undeleted. "+j.responseJSON.err_msg)}else{e.error("An error occured! Dataset was not undeleted. Please try again.")}}})},templateRowFolder:function(){tmpl_array=[];tmpl_array.push('<tr class="folder_row light" id="<%- content_item.id %>">');tmpl_array.push(" <td>");tmpl_array.push(' <span title="Folder" class="fa fa-folder-o"></span>');tmpl_array.push(" </td>");tmpl_array.push(" <td></td>");tmpl_array.push(" <td>");tmpl_array.push(' <a href="#folders/<%- content_item.id %>"><%- content_item.get("name") %></a>');tmpl_array.push(" </td>");tmpl_array.push(" <td>folder</td>");tmpl_array.push(" <td></td>");tmpl_array.push(' <td><%= _.escape(content_item.get("update_time")) %></td>');tmpl_array.push(" <td></td>");tmpl_array.push("</tr>");return _.template(tmpl_array.join(""))},templateRowFile:function(){tmpl_array=[];tmpl_array.push('<tr class="dataset_row light" id="<%- content_item.id %>">');tmpl_array.push(" <td>");tmpl_array.push(' <span title="Dataset" class="fa fa-file-o"></span>');tmpl_array.push(" </td>");tmpl_array.push(' <td style="text-align: center; "><input style="margin: 0;" type="checkbox"></td>');tmpl_array.push(' <td><a href="#folders/<%- content_item.get("folder_id") %>/datasets/<%- content_item.id %>" class="library-dataset"><%- content_item.get("name") %><a></td>');tmpl_array.push(' <td><%= _.escape(content_item.get("data_type")) %></td>');tmpl_array.push(' <td><%= _.escape(content_item.get("readable_size")) %></td>');tmpl_array.push(' <td><%= _.escape(content_item.get("update_time")) %></td>');tmpl_array.push(" <td>");tmpl_array.push(' <% if (content_item.get("is_unrestricted")) { %><span data-toggle="tooltip" data-placement="top" title="Unrestricted dataset" style="color:grey;" class="fa fa-globe fa-lg"></span><% } %>');tmpl_array.push(' <% if (content_item.get("is_private")) { %><span data-toggle="tooltip" data-placement="top" title="Private dataset" style="color:grey;" class="fa fa-key fa-lg"></span><% } %>');tmpl_array.push(' <% if ((content_item.get("is_unrestricted") === false) && (content_item.get("is_private") === false)) { %><span data-toggle="tooltip" data-placement="top" title="Restricted dataset" style="color:grey;" class="fa fa-group fa-lg"></span>');tmpl_array.push(" <% } %>");tmpl_array.push(" </td>");tmpl_array.push("</tr>");return _.template(tmpl_array.join(""))},templateRowDeletedFile:function(){tmpl_array=[];tmpl_array.push('<tr class="active deleted_dataset" id="<%- content_item.id %>">');tmpl_array.push(" <td>");tmpl_array.push(' <span title="Dataset" class="fa fa-file-o"></span>');tmpl_array.push(" </td>");tmpl_array.push(" <td></td>");tmpl_array.push(' <td style="color:grey;"><%- content_item.get("name") %></td>');tmpl_array.push(' <td><%= _.escape(content_item.get("data_type")) %></td>');tmpl_array.push(' <td><%= _.escape(content_item.get("readable_size")) %></td>');tmpl_array.push(' <td><%= _.escape(content_item.get("update_time")) %></td>');tmpl_array.push(' <td><span data-toggle="tooltip" data-placement="top" title="Marked deleted" style="color:grey;" class="fa fa-ban fa-lg"></span><button data-toggle="tooltip" data-placement="top" title="Undelete <%- content_item.get("name") %>" class="primary-button btn-xs undelete_dataset_btn show_on_hover" type="button" style="display:none; margin-left:1em;"><span class="fa fa-unlock"> Undelete</span></button></td>');tmpl_array.push("</tr>");return _.template(tmpl_array.join(""))},templateDatasetModal:function(){var f=[];f.push('<div class="modal_table">');f.push(' <table class="grid table table-striped table-condensed">');f.push(" <tr>");f.push(' <th scope="row" id="id_row" data-id="<%= _.escape(item.get("ldda_id")) %>">Name</th>');f.push(' <td><%= _.escape(item.get("name")) %></td>');f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Data type</th>');f.push(' <td><%= _.escape(item.get("data_type")) %></td>');f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Genome build</th>');f.push(' <td><%= _.escape(item.get("genome_build")) %></td>');f.push(" </tr>");f.push(' <th scope="row">Size</th>');f.push(" <td><%= _.escape(size) %></td>");f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Date uploaded (UTC)</th>');f.push(' <td><%= _.escape(item.get("date_uploaded")) %></td>');f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Uploaded by</th>');f.push(' <td><%= _.escape(item.get("uploaded_by")) %></td>');f.push(" </tr>");f.push(' <tr scope="row">');f.push(' <th scope="row">Data Lines</th>');f.push(' <td scope="row"><%= _.escape(item.get("metadata_data_lines")) %></td>');f.push(" </tr>");f.push(' <th scope="row">Comment Lines</th>');f.push(' <% if (item.get("metadata_comment_lines") === "") { %>');f.push(' <td scope="row"><%= _.escape(item.get("metadata_comment_lines")) %></td>');f.push(" <% } else { %>");f.push(' <td scope="row">unknown</td>');f.push(" <% } %>");f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Number of Columns</th>');f.push(' <td scope="row"><%= _.escape(item.get("metadata_columns")) %></td>');f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Column Types</th>');f.push(' <td scope="row"><%= _.escape(item.get("metadata_column_types")) %></td>');f.push(" </tr>");f.push(" <tr>");f.push(' <th scope="row">Miscellaneous information</th>');f.push(' <td scope="row"><%= _.escape(item.get("misc_blurb")) %></td>');f.push(" </tr>");f.push(" </table>");f.push(' <pre class="peek">');f.push(" </pre>");f.push("</div>");return f.join("")},templateHistorySelectInModal:function(){var f=[];f.push('<span id="history_modal_combo" style="width:100%; margin-left: 1em; margin-right: 1em; ">');f.push("Select history: ");f.push('<select id="dataset_import_single" name="dataset_import_single" style="width:40%; margin-bottom: 1em; "> ');f.push(" <% _.each(histories, function(history) { %>");f.push(' <option value="<%= _.escape(history.get("id")) %>"><%= _.escape(history.get("name")) %></option>');f.push(" <% }); %>");f.push("</select>");f.push("</span>");return f.join("")}});return{FolderRowView:a}});
\ No newline at end of file
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 static/scripts/packed/mvc/library/library-librarylist-view.js
--- a/static/scripts/packed/mvc/library/library-librarylist-view.js
+++ b/static/scripts/packed/mvc/library/library-librarylist-view.js
@@ -1,1 +1,1 @@
-define(["galaxy.masthead","mvc/base-mvc","utils/utils","libs/toastr","mvc/library/library-model","mvc/library/library-libraryrow-view"],function(b,g,d,e,c,a){var f=Backbone.View.extend({el:"#libraries_element",events:{"click .sort-libraries-link":"sort_clicked"},modal:null,collection:null,rowViews:{},initialize:function(i){this.options=_.defaults(this.options||{},i);var h=this;this.rowViews={};this.collection=new c.Libraries();this.collection.fetch({success:function(){h.render()},error:function(k,j){if(typeof j.responseJSON!=="undefined"){e.error(j.responseJSON.err_msg)}else{e.error("An error ocurred :(")}}})},render:function(i){var j=this.templateLibraryList();var k=null;var h=Galaxy.libraries.preferences.get("with_deleted");var l=null;if(typeof i!=="undefined"){h=typeof i.with_deleted!=="undefined"?i.with_deleted:false;l=typeof i.models!=="undefined"?i.models:null}if(this.collection!==null&&l===null){this.sortLibraries();if(h){k=this.collection.models}else{k=this.collection.where({deleted:false})}}else{if(l!==null){k=l}else{k=[]}}this.$el.html(j({length:k.length,order:Galaxy.libraries.preferences.get("sort_order")}));if(k.length>0){this.renderRows(k)}$("#center [data-toggle]").tooltip();$("#center").css("overflow","auto")},renderRows:function(l){for(var k=0;k<l.length;k++){var j=l[k];var h=_.findWhere(this.rowViews,{id:j.get("id")});if(h!==undefined&&this instanceof Backbone.View){h.delegateEvents();this.$el.find("#library_list_body").append(h.el)}else{this.renderOne({library:j})}}},renderOne:function(j){var i=j.library;var h=new a.LibraryRowView(i);if(j.prepend){this.$el.find("#library_list_body").prepend(h.el)}else{this.$el.find("#library_list_body").append(h.el)}this.rowViews[i.get("id")]=h},sort_clicked:function(){if(Galaxy.libraries.preferences.get("sort_order")==="asc"){Galaxy.libraries.preferences.set({sort_order:"desc"})}else{Galaxy.libraries.preferences.set({sort_order:"asc"})}this.render()},sortLibraries:function(){if(Galaxy.libraries.preferences.get("sort_by")==="name"){if(Galaxy.libraries.preferences.get("sort_order")==="asc"){this.collection.sortByNameAsc()}else{if(Galaxy.libraries.preferences.get("sort_order")==="desc"){this.collection.sortByNameDesc()}}}},templateLibraryList:function(){tmpl_array=[];tmpl_array.push('<div class="library_container table-responsive">');tmpl_array.push("<% if(length === 0) { %>");tmpl_array.push('<div>There are no libraries visible to you. If you expected some to show up please consult the <a href="https://wiki.galaxyproject.org/Admin/DataLibraries/LibrarySecurity">library security wikipage</a>.</div>');tmpl_array.push("<% } else{ %>");tmpl_array.push('<table class="grid table table-condensed">');tmpl_array.push(" <thead>");tmpl_array.push(' <th style="width:30%;"><a class="sort-libraries-link" title="Click to reverse order" href="#">name</a><span title="Sorted alphabetically" class="fa fa-sort-alpha-<%- order %>"></span></th>');tmpl_array.push(' <th style="width:22%;">description</th>');tmpl_array.push(' <th style="width:22%;">synopsis</th> ');tmpl_array.push(' <th style="width:26%;"></th> ');tmpl_array.push(" </thead>");tmpl_array.push(' <tbody id="library_list_body">');tmpl_array.push(" </tbody>");tmpl_array.push("</table>");tmpl_array.push("<% }%>");tmpl_array.push("</div>");return _.template(tmpl_array.join(""))},redirectToHome:function(){window.location="../"},redirectToLogin:function(){window.location="/user/login"},});return{LibraryListView:f}});
\ No newline at end of file
+define(["galaxy.masthead","mvc/base-mvc","utils/utils","libs/toastr","mvc/library/library-model","mvc/library/library-libraryrow-view"],function(b,g,d,e,c,a){var f=Backbone.View.extend({el:"#libraries_element",events:{"click .sort-libraries-link":"sort_clicked"},modal:null,collection:null,rowViews:{},initialize:function(i){this.options=_.defaults(this.options||{},i);var h=this;this.rowViews={};this.collection=new c.Libraries();this.collection.fetch({success:function(){h.render()},error:function(k,j){if(typeof j.responseJSON!=="undefined"){e.error(j.responseJSON.err_msg)}else{e.error("An error ocurred :(")}}})},render:function(i){var j=this.templateLibraryList();var k=null;var h=Galaxy.libraries.preferences.get("with_deleted");var l=null;if(typeof i!=="undefined"){h=typeof i.with_deleted!=="undefined"?i.with_deleted:false;l=typeof i.models!=="undefined"?i.models:null}if(this.collection!==null&&l===null){this.sortLibraries();if(h){k=this.collection.models}else{k=this.collection.where({deleted:false})}}else{if(l!==null){k=l}else{k=[]}}this.$el.html(j({length:k.length,order:Galaxy.libraries.preferences.get("sort_order")}));if(k.length>0){this.renderRows(k)}$("#center [data-toggle]").tooltip();$("#center").css("overflow","auto")},renderRows:function(l){for(var k=0;k<l.length;k++){var j=l[k];var h=_.findWhere(this.rowViews,{id:j.get("id")});if(h!==undefined&&this instanceof Backbone.View){h.delegateEvents();this.$el.find("#library_list_body").append(h.el)}else{this.renderOne({library:j})}}},renderOne:function(j){var i=j.library;var h=new a.LibraryRowView(i);if(j.prepend){this.$el.find("#library_list_body").prepend(h.el)}else{this.$el.find("#library_list_body").append(h.el)}this.rowViews[i.get("id")]=h},sort_clicked:function(){if(Galaxy.libraries.preferences.get("sort_order")==="asc"){Galaxy.libraries.preferences.set({sort_order:"desc"})}else{Galaxy.libraries.preferences.set({sort_order:"asc"})}this.render()},sortLibraries:function(){if(Galaxy.libraries.preferences.get("sort_by")==="name"){if(Galaxy.libraries.preferences.get("sort_order")==="asc"){this.collection.sortByNameAsc()}else{if(Galaxy.libraries.preferences.get("sort_order")==="desc"){this.collection.sortByNameDesc()}}}},templateLibraryList:function(){tmpl_array=[];tmpl_array.push('<div class="library_container table-responsive">');tmpl_array.push("<% if(length === 0) { %>");tmpl_array.push('<div>There are no libraries visible to you. If you expected some to show up please consult the <a href="https://wiki.galaxyproject.org/Admin/DataLibraries/LibrarySecurity">library security wikipage</a> or visit the <a href="https://biostar.usegalaxy.org/">Galaxy support site</a>.</div>');tmpl_array.push("<% } else{ %>");tmpl_array.push('<table class="grid table table-condensed">');tmpl_array.push(" <thead>");tmpl_array.push(' <th style="width:30%;"><a class="sort-libraries-link" title="Click to reverse order" href="#">name</a><span title="Sorted alphabetically" class="fa fa-sort-alpha-<%- order %>"></span></th>');tmpl_array.push(' <th style="width:22%;">description</th>');tmpl_array.push(' <th style="width:22%;">synopsis</th> ');tmpl_array.push(' <th style="width:26%;"></th> ');tmpl_array.push(" </thead>");tmpl_array.push(' <tbody id="library_list_body">');tmpl_array.push(" </tbody>");tmpl_array.push("</table>");tmpl_array.push("<% }%>");tmpl_array.push("</div>");return _.template(tmpl_array.join(""))},redirectToHome:function(){window.location="../"},redirectToLogin:function(){window.location="/user/login"},});return{LibraryListView:f}});
\ No newline at end of file
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 static/scripts/packed/mvc/library/library-libraryrow-view.js
--- a/static/scripts/packed/mvc/library/library-libraryrow-view.js
+++ b/static/scripts/packed/mvc/library/library-libraryrow-view.js
@@ -1,1 +1,1 @@
-define(["galaxy.masthead","utils/utils","libs/toastr"],function(b,c,d){var a=Backbone.View.extend({events:{"click .edit_library_btn":"edit_button_clicked","click .cancel_library_btn":"cancel_library_modification","click .save_library_btn":"save_library_modification","click .delete_library_btn":"delete_library","click .undelete_library_btn":"undelete_library","click .permission_library_btn":"permissions_on_library"},edit_mode:false,element_visibility_config:{upload_library_btn:false,edit_library_btn:false,permission_library_btn:false,save_library_btn:false,cancel_library_btn:false,delete_library_btn:false,undelete_library_btn:false},initialize:function(e){this.render(e)},render:function(f){if(typeof f==="undefined"){f=Galaxy.libraries.libraryListView.collection.get(this.$el.data("id"))}this.prepareButtons(f);var e=this.templateRow();this.setElement(e({library:f,button_config:this.element_visibility_config,edit_mode:this.edit_mode}));this.$el.show();return this},repaint:function(e){$(".tooltip").hide();var f=this.$el;this.render(e);f.replaceWith(this.$el);this.$el.find("[data-toggle]").tooltip()},prepareButtons:function(e){vis_config=this.element_visibility_config;if(this.edit_mode===false){vis_config.save_library_btn=false;vis_config.cancel_library_btn=false;vis_config.delete_library_btn=false;if(e.get("deleted")===true){vis_config.undelete_library_btn=true;vis_config.upload_library_btn=false;vis_config.edit_library_btn=false;vis_config.permission_library_btn=false}else{if(e.get("deleted")===false){vis_config.save_library_btn=false;vis_config.cancel_library_btn=false;vis_config.undelete_library_btn=false;if(e.get("can_user_add")===true){vis_config.upload_library_btn=true}if(e.get("can_user_modify")===true){vis_config.edit_library_btn=true}if(e.get("can_user_manage")===true){vis_config.permission_library_btn=true}}}}else{if(this.edit_mode===true){vis_config.upload_library_btn=false;vis_config.edit_library_btn=false;vis_config.permission_library_btn=false;vis_config.save_library_btn=true;vis_config.cancel_library_btn=true;vis_config.delete_library_btn=true;vis_config.undelete_library_btn=false}}this.element_visibility_config=vis_config},permissions_on_library:function(){d.info("Coming soon. Stay tuned.")},edit_button_clicked:function(){this.edit_mode=true;this.repaint()},cancel_library_modification:function(){d.info("Modifications canceled");this.edit_mode=false;this.repaint()},save_library_modification:function(){var f=Galaxy.libraries.libraryListView.collection.get(this.$el.data("id"));var g=false;var i=this.$el.find(".input_library_name").val();if(typeof i!=="undefined"&&i!==f.get("name")){if(i.length>2){f.set("name",i);g=true}else{d.warning("Library name has to be at least 3 characters long");return}}var h=this.$el.find(".input_library_description").val();if(typeof h!=="undefined"&&h!==f.get("description")){f.set("description",h);g=true}var j=this.$el.find(".input_library_synopsis").val();if(typeof j!=="undefined"&&j!==f.get("synopsis")){f.set("synopsis",j);g=true}if(g){var e=this;f.save(null,{patch:true,success:function(k){e.edit_mode=false;e.repaint(k);d.success("Changes to library saved")},error:function(l,k){if(typeof k.responseJSON!=="undefined"){d.error(k.responseJSON.err_msg)}else{d.error("An error occured during updating the library :(")}}})}else{this.edit_mode=false;this.repaint(f);d.info("Nothing has changed")}},delete_library:function(){var f=Galaxy.libraries.libraryListView.collection.get(this.$el.data("id"));var e=this;f.destroy({success:function(g){g.set("deleted",true);Galaxy.libraries.libraryListView.collection.add(g);e.edit_mode=false;if(Galaxy.libraries.preferences.get("with_deleted")===false){$(".tooltip").hide();e.$el.remove()}else{if(Galaxy.libraries.preferences.get("with_deleted")===true){e.repaint(g)}}d.success("Library has been marked deleted")},error:function(h,g){if(typeof g.responseJSON!=="undefined"){d.error(g.responseJSON.err_msg)}else{d.error("An error occured during deleting the library :(")}}})},undelete_library:function(){var f=Galaxy.libraries.libraryListView.collection.get(this.$el.data("id"));var e=this;f.url=f.urlRoot+f.id+"?undelete=true";f.destroy({success:function(g){g.set("deleted",false);Galaxy.libraries.libraryListView.collection.add(g);e.edit_mode=false;e.repaint(g);d.success("Library has been undeleted")},error:function(h,g){if(typeof g.responseJSON!=="undefined"){d.error(g.responseJSON.err_msg)}else{d.error("An error occured while undeleting the library :(")}}})},templateRow:function(){tmpl_array=[];tmpl_array.push(' <tr class="<% if(library.get("deleted") === true) { print("active") } %>" style="display:none;" data-id="<%- library.get("id") %>">');tmpl_array.push(" <% if(!edit_mode) { %>");tmpl_array.push(' <% if(library.get("deleted")) { %>');tmpl_array.push(' <td style="color:grey;"><span data-toggle="tooltip" data-placement="top" title="Marked deleted" style="color:grey;" class="fa fa-ban fa-lg deleted_lib_ico"></span><%- library.get("name") %></td>');tmpl_array.push(" <% } else { %>");tmpl_array.push(' <td><a href="#folders/<%- library.get("root_folder_id") %>"><%- library.get("name") %></a></td>');tmpl_array.push(" <% } %>");tmpl_array.push(' <td><%= _.escape(library.get("description")) %></td>');tmpl_array.push(' <td><%= _.escape(library.get("synopsis")) %></td>');tmpl_array.push(" <% } else if(edit_mode){ %>");tmpl_array.push(' <td><input type="text" class="form-control input_library_name" placeholder="name" value="<%- library.get("name") %>"></td>');tmpl_array.push(' <td><input type="text" class="form-control input_library_description" placeholder="description" value="<%- library.get("description") %>"></td>');tmpl_array.push(' <td><input type="text" class="form-control input_library_synopsis" placeholder="synopsis" value="<%- library.get("synopsis") %>"></td>');tmpl_array.push(" <% } %>");tmpl_array.push(' <td class="right-center">');tmpl_array.push(' <% if( (library.get("public")) && (library.get("deleted") === false) ) { %>');tmpl_array.push(' <span data-toggle="tooltip" data-placement="top" title="Public" style="color:grey;" class="fa fa-globe fa-lg public_lib_ico"></span>');tmpl_array.push(" <% }%>");tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Modify <%- library.get("name") %>" class="primary-button btn-xs edit_library_btn" type="button" style="<% if(button_config.edit_library_btn === false) { print("display:none;") } %>"><span class="fa fa-pencil"></span></button>');tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Modify permissions" class="primary-button btn-xs permission_library_btn" type="button" style="<% if(button_config.permission_library_btn === false) { print("display:none;") } %>"><span class="fa fa-group"></span></button>');tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Save changes" class="primary-button btn-xs save_library_btn" type="button" style="<% if(button_config.save_library_btn === false) { print("display:none;") } %>"><span class="fa fa-floppy-o"> Save</span></button>');tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Discard changes" class="primary-button btn-xs cancel_library_btn" type="button" style="<% if(button_config.cancel_library_btn === false) { print("display:none;") } %>"><span class="fa fa-times"> Cancel</span></button>');tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Delete <%- library.get("name") %>" class="primary-button btn-xs delete_library_btn" type="button" style="<% if(button_config.delete_library_btn === false) { print("display:none;") } %>"><span class="fa fa-trash-o"> Delete</span></button>');tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Undelete <%- library.get("name") %> " class="primary-button btn-xs undelete_library_btn" type="button" style="<% if(button_config.undelete_library_btn === false) { print("display:none;") } %>"><span class="fa fa-unlock"> Undelete</span></button>');tmpl_array.push(" </td>");tmpl_array.push(" </tr>");return _.template(tmpl_array.join(""))}});return{LibraryRowView:a}});
\ No newline at end of file
+define(["galaxy.masthead","utils/utils","libs/toastr"],function(b,c,d){var a=Backbone.View.extend({events:{"click .edit_library_btn":"edit_button_clicked","click .cancel_library_btn":"cancel_library_modification","click .save_library_btn":"save_library_modification","click .delete_library_btn":"delete_library","click .undelete_library_btn":"undelete_library","click .permission_library_btn":"permissions_on_library"},edit_mode:false,element_visibility_config:{upload_library_btn:false,edit_library_btn:false,permission_library_btn:false,save_library_btn:false,cancel_library_btn:false,delete_library_btn:false,undelete_library_btn:false},initialize:function(e){this.render(e)},render:function(f){if(typeof f==="undefined"){f=Galaxy.libraries.libraryListView.collection.get(this.$el.data("id"))}this.prepareButtons(f);var e=this.templateRow();this.setElement(e({library:f,button_config:this.element_visibility_config,edit_mode:this.edit_mode}));this.$el.show();return this},repaint:function(e){$(".tooltip").hide();var f=this.$el;this.render(e);f.replaceWith(this.$el);this.$el.find("[data-toggle]").tooltip()},prepareButtons:function(e){vis_config=this.element_visibility_config;if(this.edit_mode===false){vis_config.save_library_btn=false;vis_config.cancel_library_btn=false;vis_config.delete_library_btn=false;if(e.get("deleted")===true){vis_config.undelete_library_btn=true;vis_config.upload_library_btn=false;vis_config.edit_library_btn=false;vis_config.permission_library_btn=false}else{if(e.get("deleted")===false){vis_config.save_library_btn=false;vis_config.cancel_library_btn=false;vis_config.undelete_library_btn=false;if(e.get("can_user_add")===true){vis_config.upload_library_btn=true}if(e.get("can_user_modify")===true){vis_config.edit_library_btn=true}if(e.get("can_user_manage")===true){vis_config.permission_library_btn=true}}}}else{if(this.edit_mode===true){vis_config.upload_library_btn=false;vis_config.edit_library_btn=false;vis_config.permission_library_btn=false;vis_config.save_library_btn=true;vis_config.cancel_library_btn=true;vis_config.delete_library_btn=true;vis_config.undelete_library_btn=false}}this.element_visibility_config=vis_config},permissions_on_library:function(){d.info("Coming soon. Stay tuned.")},edit_button_clicked:function(){this.edit_mode=true;this.repaint()},cancel_library_modification:function(){d.info("Modifications canceled");this.edit_mode=false;this.repaint()},save_library_modification:function(){var f=Galaxy.libraries.libraryListView.collection.get(this.$el.data("id"));var g=false;var i=this.$el.find(".input_library_name").val();if(typeof i!=="undefined"&&i!==f.get("name")){if(i.length>2){f.set("name",i);g=true}else{d.warning("Library name has to be at least 3 characters long");return}}var h=this.$el.find(".input_library_description").val();if(typeof h!=="undefined"&&h!==f.get("description")){f.set("description",h);g=true}var j=this.$el.find(".input_library_synopsis").val();if(typeof j!=="undefined"&&j!==f.get("synopsis")){f.set("synopsis",j);g=true}if(g){var e=this;f.save(null,{patch:true,success:function(k){e.edit_mode=false;e.repaint(k);d.success("Changes to library saved")},error:function(l,k){if(typeof k.responseJSON!=="undefined"){d.error(k.responseJSON.err_msg)}else{d.error("An error occured during updating the library :(")}}})}else{this.edit_mode=false;this.repaint(f);d.info("Nothing has changed")}},delete_library:function(){var f=Galaxy.libraries.libraryListView.collection.get(this.$el.data("id"));var e=this;f.destroy({success:function(g){g.set("deleted",true);Galaxy.libraries.libraryListView.collection.add(g);e.edit_mode=false;if(Galaxy.libraries.preferences.get("with_deleted")===false){$(".tooltip").hide();e.$el.remove()}else{if(Galaxy.libraries.preferences.get("with_deleted")===true){e.repaint(g)}}d.success("Library has been marked deleted")},error:function(h,g){if(typeof g.responseJSON!=="undefined"){d.error(g.responseJSON.err_msg)}else{d.error("An error occured during deleting the library :(")}}})},undelete_library:function(){var f=Galaxy.libraries.libraryListView.collection.get(this.$el.data("id"));var e=this;f.url=f.urlRoot+f.id+"?undelete=true";f.destroy({success:function(g){g.set("deleted",false);Galaxy.libraries.libraryListView.collection.add(g);e.edit_mode=false;e.repaint(g);d.success("Library has been undeleted")},error:function(h,g){if(typeof g.responseJSON!=="undefined"){d.error(g.responseJSON.err_msg)}else{d.error("An error occured while undeleting the library :(")}}})},templateRow:function(){tmpl_array=[];tmpl_array.push(' <tr class="<% if(library.get("deleted") === true) { print("active") } %>" style="display:none;" data-id="<%- library.get("id") %>">');tmpl_array.push(" <% if(!edit_mode) { %>");tmpl_array.push(' <% if(library.get("deleted")) { %>');tmpl_array.push(' <td style="color:grey;"><span data-toggle="tooltip" data-placement="top" title="Marked deleted" style="color:grey;" class="fa fa-ban fa-lg deleted_lib_ico"></span><%- library.get("name") %></td>');tmpl_array.push(" <% } else { %>");tmpl_array.push(' <td><a href="#folders/<%- library.get("root_folder_id") %>"><%- library.get("name") %></a></td>');tmpl_array.push(" <% } %>");tmpl_array.push(' <td><%= _.escape(library.get("description")) %></td>');tmpl_array.push(' <td><%= _.escape(library.get("synopsis")) %></td>');tmpl_array.push(" <% } else if(edit_mode){ %>");tmpl_array.push(' <td><input type="text" class="form-control input_library_name" placeholder="name" value="<%- library.get("name") %>"></td>');tmpl_array.push(' <td><input type="text" class="form-control input_library_description" placeholder="description" value="<%- library.get("description") %>"></td>');tmpl_array.push(' <td><input type="text" class="form-control input_library_synopsis" placeholder="synopsis" value="<%- library.get("synopsis") %>"></td>');tmpl_array.push(" <% } %>");tmpl_array.push(' <td class="right-center">');tmpl_array.push(' <% if( (library.get("public")) && (library.get("deleted") === false) ) { %>');tmpl_array.push(' <span data-toggle="tooltip" data-placement="top" title="Unrestricted library" style="color:grey;" class="fa fa-globe fa-lg public_lib_ico"></span>');tmpl_array.push(" <% }%>");tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Modify <%- library.get("name") %>" class="primary-button btn-xs edit_library_btn" type="button" style="<% if(button_config.edit_library_btn === false) { print("display:none;") } %>"><span class="fa fa-pencil"></span></button>');tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Modify permissions" class="primary-button btn-xs permission_library_btn" type="button" style="<% if(button_config.permission_library_btn === false) { print("display:none;") } %>"><span class="fa fa-group"></span></button>');tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Save changes" class="primary-button btn-xs save_library_btn" type="button" style="<% if(button_config.save_library_btn === false) { print("display:none;") } %>"><span class="fa fa-floppy-o"> Save</span></button>');tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Discard changes" class="primary-button btn-xs cancel_library_btn" type="button" style="<% if(button_config.cancel_library_btn === false) { print("display:none;") } %>"><span class="fa fa-times"> Cancel</span></button>');tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Delete <%- library.get("name") %>" class="primary-button btn-xs delete_library_btn" type="button" style="<% if(button_config.delete_library_btn === false) { print("display:none;") } %>"><span class="fa fa-trash-o"> Delete</span></button>');tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Undelete <%- library.get("name") %> " class="primary-button btn-xs undelete_library_btn" type="button" style="<% if(button_config.undelete_library_btn === false) { print("display:none;") } %>"><span class="fa fa-unlock"> Undelete</span></button>');tmpl_array.push(" </td>");tmpl_array.push(" </tr>");return _.template(tmpl_array.join(""))}});return{LibraryRowView:a}});
\ No newline at end of file
diff -r f09f01f2b21ed47f3dca5104ea6c00dfb6c77210 -r 56d4cb37d62700e5baa94cc63e56041556844c43 static/scripts/packed/mvc/library/library-librarytoolbar-view.js
--- a/static/scripts/packed/mvc/library/library-librarytoolbar-view.js
+++ b/static/scripts/packed/mvc/library/library-librarytoolbar-view.js
@@ -1,1 +1,1 @@
-define(["libs/toastr","mvc/library/library-model"],function(b,a){var c=Backbone.View.extend({el:"#center",events:{"click #create_new_library_btn":"show_library_modal","click #include_deleted_chk":"check_include_deleted"},initialize:function(){this.render()},render:function(){var f=this.templateToolBar();var e=false;var d=true;if(Galaxy.currUser){e=Galaxy.currUser.isAdmin();d=Galaxy.currUser.isAnonymous()}this.$el.html(f({admin_user:e,anon_user:d}));if(e){this.$el.find("#include_deleted_chk")[0].checked=Galaxy.libraries.preferences.get("with_deleted")}},show_library_modal:function(e){e.preventDefault();e.stopPropagation();var d=this;this.modal=Galaxy.modal;this.modal.show({closing_events:true,title:"Create New Library",body:this.templateNewLibraryInModal(),buttons:{Create:function(){d.create_new_library_event()},Close:function(){d.modal.hide()}}})},create_new_library_event:function(){var f=this.serialize_new_library();if(this.validate_new_library(f)){var e=new a.Library();var d=this;e.save(f,{success:function(g){Galaxy.libraries.libraryListView.collection.add(g);d.modal.hide();d.clear_library_modal();Galaxy.libraries.libraryListView.render();b.success("Library created")},error:function(h,g){if(typeof g.responseJSON!=="undefined"){b.error(g.responseJSON.err_msg)}else{b.error("An error occured :(")}}})}else{b.error("Library's name is missing")}return false},clear_library_modal:function(){$("input[name='Name']").val("");$("input[name='Description']").val("");$("input[name='Synopsis']").val("")},serialize_new_library:function(){return{name:$("input[name='Name']").val(),description:$("input[name='Description']").val(),synopsis:$("input[name='Synopsis']").val()}},validate_new_library:function(d){return d.name!==""},check_include_deleted:function(d){if(d.target.checked){Galaxy.libraries.preferences.set({with_deleted:true});Galaxy.libraries.libraryListView.render()}else{Galaxy.libraries.preferences.set({with_deleted:false});Galaxy.libraries.libraryListView.render()}},templateToolBar:function(){tmpl_array=[];tmpl_array.push('<div class="library_style_container">');tmpl_array.push(' <div id="toolbar_form" margin-top:0.5em; ">');tmpl_array.push(' <h3>Data Libraries Beta Test. This is work in progress. Please report problems & ideas via <a href="mailto:galaxy-bugs@bx.psu.edu?Subject=DataLibrariesBeta_Feedback" target="_blank">email</a> and <a href="https://trello.com/c/nwYQNFPK/56-data-library-ui-progressive-display-of-fol…" target="_blank">Trello</a>.</h3>');tmpl_array.push(" <% if(admin_user === true) { %>");tmpl_array.push(' <div id="library_toolbar">');tmpl_array.push(' <span data-toggle="tooltip" data-placement="top" title="Include deleted libraries"><input id="include_deleted_chk" style="margin: 0;" type="checkbox"><span class="fa fa-trash-o fa-lg"></span></input></span>');tmpl_array.push(' <span data-toggle="tooltip" data-placement="top" title="Create New Library"><button id="create_new_library_btn" class="primary-button btn-xs" type="button"><span class="fa fa-plus"></span> New Library</button><span>');tmpl_array.push(" </div>");tmpl_array.push(" <% } %>");tmpl_array.push(" </div>");tmpl_array.push(' <div id="libraries_element">');tmpl_array.push(" </div>");tmpl_array.push("</div>");return _.template(tmpl_array.join(""))},templateNewLibraryInModal:function(){tmpl_array=[];tmpl_array.push('<div id="new_library_modal">');tmpl_array.push(" <form>");tmpl_array.push(' <input type="text" name="Name" value="" placeholder="Name">');tmpl_array.push(' <input type="text" name="Description" value="" placeholder="Description">');tmpl_array.push(' <input type="text" name="Synopsis" value="" placeholder="Synopsis">');tmpl_array.push(" </form>");tmpl_array.push("</div>");return tmpl_array.join("")}});return{LibraryToolbarView:c}});
\ No newline at end of file
+define(["libs/toastr","mvc/library/library-model"],function(b,a){var c=Backbone.View.extend({el:"#center",events:{"click #create_new_library_btn":"show_library_modal","click #include_deleted_chk":"check_include_deleted"},initialize:function(){this.render()},render:function(){var f=this.templateToolBar();var e=false;var d=true;if(Galaxy.currUser){e=Galaxy.currUser.isAdmin();d=Galaxy.currUser.isAnonymous()}this.$el.html(f({admin_user:e,anon_user:d}));if(e){this.$el.find("#include_deleted_chk")[0].checked=Galaxy.libraries.preferences.get("with_deleted")}},show_library_modal:function(e){e.preventDefault();e.stopPropagation();var d=this;this.modal=Galaxy.modal;this.modal.show({closing_events:true,title:"Create New Library",body:this.templateNewLibraryInModal(),buttons:{Create:function(){d.create_new_library_event()},Close:function(){d.modal.hide()}}})},create_new_library_event:function(){var f=this.serialize_new_library();if(this.validate_new_library(f)){var e=new a.Library();var d=this;e.save(f,{success:function(g){Galaxy.libraries.libraryListView.collection.add(g);d.modal.hide();d.clear_library_modal();Galaxy.libraries.libraryListView.render();b.success("Library created")},error:function(h,g){if(typeof g.responseJSON!=="undefined"){b.error(g.responseJSON.err_msg)}else{b.error("An error occured :(")}}})}else{b.error("Library's name is missing")}return false},clear_library_modal:function(){$("input[name='Name']").val("");$("input[name='Description']").val("");$("input[name='Synopsis']").val("")},serialize_new_library:function(){return{name:$("input[name='Name']").val(),description:$("input[name='Description']").val(),synopsis:$("input[name='Synopsis']").val()}},validate_new_library:function(d){return d.name!==""},check_include_deleted:function(d){if(d.target.checked){Galaxy.libraries.preferences.set({with_deleted:true});Galaxy.libraries.libraryListView.render()}else{Galaxy.libraries.preferences.set({with_deleted:false});Galaxy.libraries.libraryListView.render()}},templateToolBar:function(){tmpl_array=[];tmpl_array.push('<div class="library_style_container">');tmpl_array.push(' <div id="toolbar_form" margin-top:0.5em; ">');tmpl_array.push(" <% if(admin_user === true) { %>");tmpl_array.push(' <div id="library_toolbar">');tmpl_array.push(' <span data-toggle="tooltip" data-placement="top" title="Include deleted libraries"><input id="include_deleted_chk" style="margin: 0;" type="checkbox"><span class="fa fa-trash-o fa-lg"></span></input></span>');tmpl_array.push(' <span data-toggle="tooltip" data-placement="top" title="Create New Library"><button id="create_new_library_btn" class="primary-button btn-xs" type="button"><span class="fa fa-plus"></span> New Library</button><span>');tmpl_array.push(" </div>");tmpl_array.push(" <% } %>");tmpl_array.push(" </div>");tmpl_array.push(' <div id="libraries_element">');tmpl_array.push(" </div>");tmpl_array.push("</div>");return _.template(tmpl_array.join(""))},templateNewLibraryInModal:function(){tmpl_array=[];tmpl_array.push('<div id="new_library_modal">');tmpl_array.push(" <form>");tmpl_array.push(' <input type="text" name="Name" value="" placeholder="Name">');tmpl_array.push(' <input type="text" name="Description" value="" placeholder="Description">');tmpl_array.push(' <input type="text" name="Synopsis" value="" placeholder="Synopsis">');tmpl_array.push(" </form>");tmpl_array.push("</div>");return tmpl_array.join("")}});return{LibraryToolbarView:c}});
\ 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
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
commit/galaxy-central: jmchilton: Simpler, more powerful subcollection mapping.
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/9e502242af28/
Changeset: 9e502242af28
User: jmchilton
Date: 2014-05-07 18:29:51
Summary: Simpler, more powerful subcollection mapping.
Collections can be mapped over 'data' parameters and sufficiently nested collections can be mapped over 'data_collection' parameters (for instance a list of 5 pairs can be supplied to a tool taking in a pair and 5 jobs will be executed). I (perhaps poorly) term these concepts collection mapping and subcollection mapping.
Prior to this changeset - the API for doing collection 'mapping' and 'subcollection mapping' was somewhat more divergent and the tool execution code explicitly forbid doing both kinds of mappings in the same tool execution even if the effective collections could be matched (e.g. it could not map a 'data' parameter over a list of 5 datasets and a pair parameter a list of 5 pairs in the same execution).
This changeset should remedy this - as long as the effective collection mappings can match up such jobs should be possible. The workflow editor (and I think runner) already thought this was possible, so this changeset reduces the tool-workflow impedance mismatch - an existing problem exacerbated by recent dataset collections introduction.
This all needs much more testing - test workflows execute this way, functional test of a tool execution that combines collection mapping and subcollection mapping, etc....
Affected #: 4 files
diff -r c5b736d3e4e5027391a04fd187e2f9d2aa4ebe46 -r 9e502242af28473660ee70ee0f98345f3604e137 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -1925,7 +1925,7 @@
# Fixed set of input parameters may correspond to any number of jobs.
# Expand these out to individual parameters for given jobs (tool
# executions).
- expanded_incomings, collection_info = expand_meta_parameters( trans, incoming, self.inputs )
+ expanded_incomings, collection_info = expand_meta_parameters( trans, self, incoming )
if not expanded_incomings:
raise exceptions.MessageException( "Tool execution failed, trying to run a tool over an empty collection." )
diff -r c5b736d3e4e5027391a04fd187e2f9d2aa4ebe46 -r 9e502242af28473660ee70ee0f98345f3604e137 lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -2021,7 +2021,7 @@
self._ensure_selection( field )
return field
- def _get_select_dataset_collection_field( self, trans, history, multiple=False, suffix="|__subcollection_multirun__", value=None, other_values=None ):
+ def _get_select_dataset_collection_field( self, trans, history, multiple=False, suffix="|__collection_multirun__", value=None, other_values=None ):
field_name = "%s%s" % ( self.name, suffix )
field = form_builder.SelectField( field_name, multiple, None, self.refresh_on_change, refresh_on_change_values=self.refresh_on_change_values )
dataset_matcher = DatasetMatcher( trans, self, value, other_values )
diff -r c5b736d3e4e5027391a04fd187e2f9d2aa4ebe46 -r 9e502242af28473660ee70ee0f98345f3604e137 lib/galaxy/tools/parameters/meta.py
--- a/lib/galaxy/tools/parameters/meta.py
+++ b/lib/galaxy/tools/parameters/meta.py
@@ -9,7 +9,7 @@
log = logging.getLogger( __name__ )
-def expand_meta_parameters( trans, incoming, inputs ):
+def expand_meta_parameters( trans, tool, incoming ):
"""
Take in a dictionary of raw incoming parameters and expand to a list
of expanded incoming parameters (one set of parameters per tool
@@ -34,26 +34,23 @@
def collection_classifier( input_key ):
multirun_key = "%s|__collection_multirun__" % input_key
if multirun_key in incoming:
- encoded_hdc_id = incoming[ multirun_key ]
- hdc_id = trans.app.security.decode_id( encoded_hdc_id )
- hdc = trans.sa_session.query( model.HistoryDatasetCollectionAssociation ).get( hdc_id )
- collections_to_match.add( input_key, hdc )
- hdas = hdc.collection.dataset_instances
- return permutations.input_classification.MATCHED, hdas
- else:
- return permutations.input_classification.SINGLE, incoming[ input_key ]
-
- def subcollection_classifier( input_key ):
- multirun_key = "%s|__subcollection_multirun__" % input_key
- if multirun_key in incoming:
incoming_val = incoming[ multirun_key ]
- # value will be "hdca_id|subcollection_type"
- encoded_hdc_id, subcollection_type = incoming_val.split( "|", 1 )
+ # If subcollectin multirun of data_collection param - value will
+ # be "hdca_id|subcollection_type" else it will just be hdca_id
+ if "|" in incoming_val:
+ encoded_hdc_id, subcollection_type = incoming_val.split( "|", 1 )
+ else:
+ encoded_hdc_id = incoming_val
+ subcollection_type = None
hdc_id = trans.app.security.decode_id( encoded_hdc_id )
hdc = trans.sa_session.query( model.HistoryDatasetCollectionAssociation ).get( hdc_id )
collections_to_match.add( input_key, hdc, subcollection_type=subcollection_type )
- subcollection_elements = subcollections.split_dataset_collection_instance( hdc, subcollection_type )
- return permutations.input_classification.MATCHED, subcollection_elements
+ if subcollection_type is not None:
+ subcollection_elements = subcollections.split_dataset_collection_instance( hdc, subcollection_type )
+ return permutations.input_classification.MATCHED, subcollection_elements
+ else:
+ hdas = hdc.collection.dataset_instances
+ return permutations.input_classification.MATCHED, hdas
else:
return permutations.input_classification.SINGLE, incoming[ input_key ]
@@ -72,26 +69,17 @@
multirun_found = False
collection_multirun_found = False
- subcollection_multirun_found = False
for key, value in incoming.iteritems():
multirun_found = try_replace_key( key, "|__multirun__" ) or multirun_found
collection_multirun_found = try_replace_key( key, "|__collection_multirun__" ) or collection_multirun_found
- subcollection_multirun_found = try_replace_key( key, "|__subcollection_multirun__" ) or subcollection_multirun_found
- if sum( [ 1 if f else 0 for f in [ multirun_found, collection_multirun_found, subcollection_multirun_found ] ] ) > 1:
+ if sum( [ 1 if f else 0 for f in [ multirun_found, collection_multirun_found ] ] ) > 1:
# In theory doable, but to complicated for a first pass.
message = "Cannot specify parallel execution across both multiple datasets and dataset collections."
raise exceptions.ToolMetaParameterException( message )
if multirun_found:
return permutations.expand_multi_inputs( incoming_template, classifier ), None
- elif subcollection_multirun_found:
- expanded_incomings = permutations.expand_multi_inputs( incoming_template, subcollection_classifier )
- if collections_to_match.has_collections():
- collection_info = trans.app.dataset_collections_service.match_collections( collections_to_match )
- else:
- collection_info = None
- return expanded_incomings, collection_info
else:
expanded_incomings = permutations.expand_multi_inputs( incoming_template, collection_classifier )
if collections_to_match.has_collections():
diff -r c5b736d3e4e5027391a04fd187e2f9d2aa4ebe46 -r 9e502242af28473660ee70ee0f98345f3604e137 test/api/test_tools.py
--- a/test/api/test_tools.py
+++ b/test/api/test_tools.py
@@ -264,7 +264,7 @@
history_id = self.dataset_populator.new_history()
hdca_list_id = self.__build_nested_list( history_id )
inputs = {
- "f1|__subcollection_multirun__": "%s|paired" % hdca_list_id
+ "f1|__collection_multirun__": "%s|paired" % hdca_list_id
}
# Following wait not really needed - just getting so many database
# locked errors with sqlite.
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/fff767c478fb/
Changeset: fff767c478fb
User: dannon
Date: 2014-05-07 17:48:11
Summary: Merge stable.
Affected #: 1 file
https://bitbucket.org/galaxy/galaxy-central/commits/c5b736d3e4e5/
Changeset: c5b736d3e4e5
User: dannon
Date: 2014-05-07 17:49:14
Summary: Merge.
Affected #: 2 files
diff -r fff767c478fbce151720d49729d5444f4f85cd28 -r c5b736d3e4e5027391a04fd187e2f9d2aa4ebe46 lib/galaxy/exceptions/__init__.py
--- a/lib/galaxy/exceptions/__init__.py
+++ b/lib/galaxy/exceptions/__init__.py
@@ -104,16 +104,16 @@
class Conflict( MessageException ):
status_code = 409
- err_code = error_codes.CONFLICT
+ err_code = error_codes.CONFLICT
class InconsistentDatabase ( MessageException ):
status_code = 500
- err_code = error_codes.INCONSISTENT_DATABASE
+ err_code = error_codes.INCONSISTENT_DATABASE
class InternalServerError ( MessageException ):
status_code = 500
- err_code = error_codes.INTERNAL_SERVER_ERROR
+ err_code = error_codes.INTERNAL_SERVER_ERROR
class NotImplemented ( MessageException ):
status_code = 501
- err_code = error_codes.NOT_IMPLEMENTED
+ err_code = error_codes.NOT_IMPLEMENTED
diff -r fff767c478fbce151720d49729d5444f4f85cd28 -r c5b736d3e4e5027391a04fd187e2f9d2aa4ebe46 lib/galaxy/webapps/galaxy/buildapp.py
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -192,7 +192,6 @@
webapp.mapper.connect( 'workflow_usage', '/api/workflows/{workflow_id}/usage', controller='workflows', action='workflow_usage', conditions=dict(method=['GET']))
webapp.mapper.connect( 'workflow_usage_contents', '/api/workflows/{workflow_id}/usage/{usage_id}', controller='workflows', action='workflow_usage_contents', conditions=dict(method=['GET']))
-
# ============================
# ===== AUTHENTICATE API =====
# ============================
@@ -248,8 +247,8 @@
parent_resources=dict( member_name='library', collection_name='libraries' ) )
_add_item_extended_metadata_controller( webapp,
- name_prefix="library_dataset_",
- path_prefix='/api/libraries/:library_id/contents/:library_content_id' )
+ name_prefix="library_dataset_",
+ path_prefix='/api/libraries/:library_id/contents/:library_content_id' )
# =======================
# ===== FOLDERS API =====
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/e8336f4c485b/
Changeset: e8336f4c485b
User: martenson
Date: 2014-05-07 17:47:28
Summary: pep8 fixes
Affected #: 2 files
diff -r a9e4f0ea892c8b7da3f1e468195dc1d29914fd38 -r e8336f4c485b0eedd75b9185f78ff5acaca7a8b3 lib/galaxy/exceptions/__init__.py
--- a/lib/galaxy/exceptions/__init__.py
+++ b/lib/galaxy/exceptions/__init__.py
@@ -104,16 +104,16 @@
class Conflict( MessageException ):
status_code = 409
- err_code = error_codes.CONFLICT
+ err_code = error_codes.CONFLICT
class InconsistentDatabase ( MessageException ):
status_code = 500
- err_code = error_codes.INCONSISTENT_DATABASE
+ err_code = error_codes.INCONSISTENT_DATABASE
class InternalServerError ( MessageException ):
status_code = 500
- err_code = error_codes.INTERNAL_SERVER_ERROR
+ err_code = error_codes.INTERNAL_SERVER_ERROR
class NotImplemented ( MessageException ):
status_code = 501
- err_code = error_codes.NOT_IMPLEMENTED
+ err_code = error_codes.NOT_IMPLEMENTED
diff -r a9e4f0ea892c8b7da3f1e468195dc1d29914fd38 -r e8336f4c485b0eedd75b9185f78ff5acaca7a8b3 lib/galaxy/webapps/galaxy/buildapp.py
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -192,7 +192,6 @@
webapp.mapper.connect( 'workflow_usage', '/api/workflows/{workflow_id}/usage', controller='workflows', action='workflow_usage', conditions=dict(method=['GET']))
webapp.mapper.connect( 'workflow_usage_contents', '/api/workflows/{workflow_id}/usage/{usage_id}', controller='workflows', action='workflow_usage_contents', conditions=dict(method=['GET']))
-
# ============================
# ===== AUTHENTICATE API =====
# ============================
@@ -248,8 +247,8 @@
parent_resources=dict( member_name='library', collection_name='libraries' ) )
_add_item_extended_metadata_controller( webapp,
- name_prefix="library_dataset_",
- path_prefix='/api/libraries/:library_id/contents/:library_content_id' )
+ name_prefix="library_dataset_",
+ path_prefix='/api/libraries/:library_id/contents/:library_content_id' )
# =======================
# ===== FOLDERS API =====
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: dannon: Force an absolute path for the temp directory in the local runner
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/3534f67acd2a/
Changeset: 3534f67acd2a
Branch: stable
User: dannon
Date: 2014-05-07 15:45:53
Summary: Force an absolute path for the temp directory in the local runner
Affected #: 1 file
diff -r 61b4a993d39c5341afea857859221a4f5b94334f -r 3534f67acd2a4f84899335326ac2cc8bcf05201d lib/galaxy/jobs/runners/local.py
--- a/lib/galaxy/jobs/runners/local.py
+++ b/lib/galaxy/jobs/runners/local.py
@@ -39,7 +39,7 @@
#Set TEMP if a valid temp value is not already set
if not ( 'TMPDIR' in self._environ or 'TEMP' in self._environ or 'TMP' in self._environ ):
- self._environ[ 'TEMP' ] = tempfile.gettempdir()
+ self._environ[ 'TEMP' ] = os.path.abspath(tempfile.gettempdir())
super( LocalJobRunner, self ).__init__( app, nworkers )
self._init_worker_threads()
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: dannon: Force an absolute path for the temp directory in the local runner
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/a9e4f0ea892c/
Changeset: a9e4f0ea892c
User: dannon
Date: 2014-05-07 15:45:53
Summary: Force an absolute path for the temp directory in the local runner
Affected #: 1 file
diff -r ac95389c7eb502345d0321c54b750f134c5e6ba9 -r a9e4f0ea892c8b7da3f1e468195dc1d29914fd38 lib/galaxy/jobs/runners/local.py
--- a/lib/galaxy/jobs/runners/local.py
+++ b/lib/galaxy/jobs/runners/local.py
@@ -39,7 +39,7 @@
#Set TEMP if a valid temp value is not already set
if not ( 'TMPDIR' in self._environ or 'TEMP' in self._environ or 'TMP' in self._environ ):
- self._environ[ 'TEMP' ] = tempfile.gettempdir()
+ self._environ[ 'TEMP' ] = os.path.abspath(tempfile.gettempdir())
super( LocalJobRunner, self ).__init__( app, nworkers )
self._init_worker_threads()
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: greg: Fix type introduced in renaming the InstallManager to the ToolMigrationManager - thanks Nicola Soranzo.
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/ac95389c7eb5/
Changeset: ac95389c7eb5
User: greg
Date: 2014-05-07 12:42:28
Summary: Fix type introduced in renaming the InstallManager to the ToolMigrationManager - thanks Nicola Soranzo.
Affected #: 2 files
diff -r e7b579bec0e277f0c2e26cf23661ebfa7fa5ec2d -r ac95389c7eb502345d0321c54b750f134c5e6ba9 lib/tool_shed/galaxy_install/migrate/common.py
--- a/lib/tool_shed/galaxy_install/migrate/common.py
+++ b/lib/tool_shed/galaxy_install/migrate/common.py
@@ -48,7 +48,7 @@
latest_migration_script_number = int( tools_migration_config.split( '_' )[ 0 ] )
# The value of migrated_tools_config is migrated_tools_conf.xml, and is reserved for
# containing only those tools that have been eliminated from the distribution and moved
- # to the tool shed. A side-effect of instantiating the ToolMigrationlManager is the automatic
+ # to the tool shed. A side-effect of instantiating the ToolMigrationManager is the automatic
# installation of all appropriate tool shed repositories.
self.tool_migration_manager = \
tool_migration_manager.ToolMigrationManager( app=self,
diff -r e7b579bec0e277f0c2e26cf23661ebfa7fa5ec2d -r ac95389c7eb502345d0321c54b750f134c5e6ba9 lib/tool_shed/galaxy_install/tool_migration_manager.py
--- a/lib/tool_shed/galaxy_install/tool_migration_manager.py
+++ b/lib/tool_shed/galaxy_install/tool_migration_manager.py
@@ -24,7 +24,7 @@
log = logging.getLogger( __name__ )
-class ToolMigrationlManager( object ):
+class ToolMigrationManager( object ):
def __init__( self, app, latest_migration_script_number, tool_shed_install_config, migrated_tools_config, install_dependencies ):
"""
@@ -446,7 +446,7 @@
from_tool_migration_manager=True )
for installed_tool_dependency in installed_tool_dependencies:
if installed_tool_dependency.status == self.app.install_model.ToolDependency.installation_status.ERROR:
- print '\nThe following error occurred from the InstallManager while installing tool dependency ', installed_tool_dependency.name, ':'
+ print '\nThe ToolMigrationManager returned the following error while installing tool dependency ', installed_tool_dependency.name, ':'
print installed_tool_dependency.error_message, '\n\n'
if 'datatypes' in metadata_dict:
tool_shed_repository.status = self.app.install_model.ToolShedRepository.installation_status.LOADING_PROPRIETARY_DATATYPES
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: Expose use_remote_user via configuration API.
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/e7b579bec0e2/
Changeset: e7b579bec0e2
User: jmchilton
Date: 2014-05-07 08:19:09
Summary: Expose use_remote_user via configuration API.
Attempting to improve automated testing... marginally.
Affected #: 1 file
diff -r ea869e917062e0d92da847e50f210dcb1e0cbfd2 -r e7b579bec0e277f0c2e26cf23661ebfa7fa5ec2d 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 = [
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: Improve API test skipping when required tools are absent.
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/ea869e917062/
Changeset: ea869e917062
User: jmchilton
Date: 2014-05-07 07:24:00
Summary: Improve API test skipping when required tools are absent.
Replace ad-hoc tools API test method for skipping tests with a more general purpose decorator. Use new decorator to specify required tools for workflow tests.
Affected #: 3 files
diff -r 7eb53364de36c60654ee6e89207a63b280c74af1 -r ea869e917062e0d92da847e50f210dcb1e0cbfd2 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:
diff -r 7eb53364de36c60654ee6e89207a63b280c74af1 -r ea869e917062e0d92da847e50f210dcb1e0cbfd2 test/api/test_tools.py
--- a/test/api/test_tools.py
+++ b/test/api/test_tools.py
@@ -3,6 +3,7 @@
from operator import itemgetter
from .helpers import DatasetPopulator
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 = {
@@ -175,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 = {
@@ -203,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" ] )
@@ -221,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" ]
@@ -235,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" ]
@@ -257,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 = {
@@ -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 7eb53364de36c60654ee6e89207a63b280c74af1 -r ea869e917062e0d92da847e50f210dcb1e0cbfd2 test/api/test_workflows.py
--- a/test/api/test_workflows.py
+++ b/test/api/test_workflows.py
@@ -5,6 +5,7 @@
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
@@ -62,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 )
@@ -71,6 +73,7 @@
self._assert_status_code_is( run_workflow_response, 200 )
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 )
@@ -104,6 +107,7 @@
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()
@@ -141,6 +145,7 @@
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 ) ) )
@@ -151,6 +156,7 @@
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" ] )
@@ -177,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 )
@@ -200,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.
"""
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/a44bea8165f0/
Changeset: a44bea8165f0
User: jmchilton
Date: 2014-05-06 23:08:30
Summary: Bugfix: galaxy.workflow.extract expects hids - API was consuming encoded ids.
API now properly expects hids. That was unfortunate - need to figure out why the existing functional test did not break because of this and improve it.
Affected #: 2 files
diff -r c3370be77427ba8cdd575306d804b27facb9f49f -r a44bea8165f021e5f116bb99071526d4ab8dfb04 lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -171,8 +171,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 c3370be77427ba8cdd575306d804b27facb9f49f -r a44bea8165f021e5f116bb99071526d4ab8dfb04 test/api/test_workflows.py
--- a/test/api/test_workflows.py
+++ b/test/api/test_workflows.py
@@ -73,7 +73,7 @@
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 )
https://bitbucket.org/galaxy/galaxy-central/commits/7eb53364de36/
Changeset: 7eb53364de36
User: jmchilton
Date: 2014-05-06 23:08:30
Summary: Bugfix: Track collection_type when extracting collection inputs from histories.
Add functional test to verify the correctness of this tracking.
TODO: refactor __skip_unless_tool from tools test so this new workflow test can be skipped if not run with -with_framework_test_tools.
Affected #: 2 files
diff -r a44bea8165f021e5f116bb99071526d4ab8dfb04 -r 7eb53364de36c60654ee6e89207a63b280c74af1 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 a44bea8165f021e5f116bb99071526d4ab8dfb04 -r 7eb53364de36c60654ee6e89207a63b280c74af1 test/api/test_workflows.py
--- a/test/api/test_workflows.py
+++ b/test/api/test_workflows.py
@@ -1,8 +1,10 @@
from base import api
from json import dumps
+from json import loads
import time
from .helpers import WorkflowPopulator
from .helpers import DatasetPopulator
+from .helpers import DatasetCollectionPopulator
from base.interactor import delete_request # requests like delete
@@ -18,6 +20,7 @@
super( WorkflowsApiTestCase, self ).setUp()
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" )
@@ -91,16 +94,53 @@
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
+ 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" )
+
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 ) ) )
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
4 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/e3d9d40cd05a/
Changeset: e3d9d40cd05a
User: jmchilton
Date: 2014-05-06 20:52:49
Summary: More refactoring away from mixins and toward 'data populators' in API tests.
Affected #: 3 files
diff -r 6d6449af7d623b9b4a5bb0118750ad49f725b49a -r e3d9d40cd05a78d0a2202e68daab0c5c0f3b345f test/api/helpers.py
--- a/test/api/helpers.py
+++ b/test/api/helpers.py
@@ -84,8 +84,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 +111,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
diff -r 6d6449af7d623b9b4a5bb0118750ad49f725b49a -r e3d9d40cd05a78d0a2202e68daab0c5c0f3b345f 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 6d6449af7d623b9b4a5bb0118750ad49f725b49a -r e3d9d40cd05a78d0a2202e68daab0c5c0f3b345f test/api/test_workflows.py
--- a/test/api/test_workflows.py
+++ b/test/api/test_workflows.py
@@ -1,8 +1,8 @@
from base import api
from json import dumps
import time
-from .helpers import TestsDatasets
from .helpers import WorkflowPopulator
+from .helpers import DatasetPopulator
from base.interactor import delete_request # requests like delete
@@ -12,11 +12,12 @@
# - 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 )
def test_delete( self ):
workflow_id = self.workflow_populator.simple_workflow( "test_delete" )
@@ -65,7 +66,7 @@
# 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 )
def test_extract_from_history( self ):
workflow = self.workflow_populator.load_workflow( name="test_for_extract" )
@@ -77,7 +78,7 @@
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 )
@@ -105,7 +106,7 @@
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 )
@@ -120,7 +121,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 )
@@ -166,7 +167,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 +184,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 +202,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,
https://bitbucket.org/galaxy/galaxy-central/commits/d82e6db63d3b/
Changeset: d82e6db63d3b
User: jmchilton
Date: 2014-05-06 20:52:49
Summary: Fix location of test_dataset_collections.py.
Didn't rebase properly.
Affected #: 2 files
diff -r e3d9d40cd05a78d0a2202e68daab0c5c0f3b345f -r d82e6db63d3b581dadedcb4cc38e150445b9637c test/api/test_dataset_collections.py
--- /dev/null
+++ b/test/api/test_dataset_collections.py
@@ -0,0 +1,206 @@
+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
diff -r e3d9d40cd05a78d0a2202e68daab0c5c0f3b345f -r d82e6db63d3b581dadedcb4cc38e150445b9637c 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/4cf29a388e80/
Changeset: 4cf29a388e80
User: jmchilton
Date: 2014-05-06 20:52:49
Summary: Move dataset colleciton test populator.
Affected #: 4 files
diff -r d82e6db63d3b581dadedcb4cc38e150445b9637c -r 4cf29a388e802d073d28deb7c365c6ec5f555122 test/api/helpers.py
--- a/test/api/helpers.py
+++ b/test/api/helpers.py
@@ -189,6 +189,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 d82e6db63d3b581dadedcb4cc38e150445b9637c -r 4cf29a388e802d073d28deb7c365c6ec5f555122 test/api/test_dataset_collections.py
--- a/test/api/test_dataset_collections.py
+++ b/test/api/test_dataset_collections.py
@@ -1,100 +1,7 @@
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
+from .helpers import DatasetCollectionPopulator
class DatasetCollectionApiTestCase( api.ApiTestCase ):
diff -r d82e6db63d3b581dadedcb4cc38e150445b9637c -r 4cf29a388e802d073d28deb7c365c6ec5f555122 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 d82e6db63d3b581dadedcb4cc38e150445b9637c -r 4cf29a388e802d073d28deb7c365c6ec5f555122 test/api/test_tools.py
--- a/test/api/test_tools.py
+++ b/test/api/test_tools.py
@@ -2,7 +2,7 @@
from base import api
from operator import itemgetter
from .helpers import DatasetPopulator
-from .test_dataset_collections import DatasetCollectionPopulator
+from .helpers import DatasetCollectionPopulator
class ToolsTestCase( api.ApiTestCase ):
https://bitbucket.org/galaxy/galaxy-central/commits/c3370be77427/
Changeset: c3370be77427
User: jmchilton
Date: 2014-05-06 20:52:49
Summary: Fix bug in test/api/test_tools.py.
Affected #: 1 file
diff -r 4cf29a388e802d073d28deb7c365c6ec5f555122 -r c3370be77427ba8cdd575306d804b27facb9f49f test/api/test_tools.py
--- a/test/api/test_tools.py
+++ b/test/api/test_tools.py
@@ -167,7 +167,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 ]
@@ -285,7 +284,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 )
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