galaxy-commits
Threads by month
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
December 2014
- 2 participants
- 245 discussions
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/1e6035e53de5/
Changeset: 1e6035e53de5
User: jjohnson
Date: 2014-12-12 19:35:40+00:00
Summary: Add SQlite dataproviders: SQliteDataTableProvider - Allows any query to be run and returns the resulting rows as arrays of arrays, SQliteDataDictProvider - Allows any query to be run and returns the resulting rows as arrays of dicts
Affected #: 1 file
diff -r efd0286e7629289c4c583898ebecb32646834c64 -r 1e6035e53de58ce4fe1687ab74512da711d99989 lib/galaxy/datatypes/dataproviders/dataset.py
--- a/lib/galaxy/datatypes/dataproviders/dataset.py
+++ b/lib/galaxy/datatypes/dataproviders/dataset.py
@@ -13,6 +13,7 @@
import external
from galaxy.util import sqlite
import re
+import sys
from galaxy import eggs
eggs.require( 'bx-python' )
@@ -726,3 +727,55 @@
else:
yield
+class SQliteDataTableProvider( base.DataProvider ):
+ """
+ Data provider that uses a sqlite database file as its source.
+ Allows any query to be run and returns the resulting rows as arrays of arrays
+ """
+ settings = {
+ 'query': 'str',
+ 'headers': 'bool',
+ 'limit': 'int'
+ }
+
+ def __init__( self, source, query=None, headers=False, limit=sys.maxint, **kwargs ):
+ self.query = query
+ self.headers = headers
+ self.limit = limit
+ self.connection = sqlite.connect(source.dataset.file_name)
+ super( SQliteDataTableProvider, self ).__init__( source, **kwargs )
+
+ def __iter__( self ):
+ if (self.query is not None) and sqlite.is_read_only_query(self.query):
+ cur = self.connection.cursor()
+ results = cur.execute(self.query)
+ if self.headers:
+ yield [col[0] for col in cur.description]
+ for i,row in enumerate(results):
+ if i >= self.limit:
+ break
+ yield [val for val in row]
+ else:
+ yield
+
+class SQliteDataDictProvider( base.DataProvider ):
+ """
+ Data provider that uses a sqlite database file as its source.
+ Allows any query to be run and returns the resulting rows as arrays of dicts
+ """
+ settings = {
+ 'query': 'str'
+ }
+
+ def __init__( self, source, query=None, **kwargs ):
+ self.query = query
+ self.connection = sqlite.connect(source.dataset.file_name)
+ super( SQliteDataDictProvider, self ).__init__( source, **kwargs )
+
+ def __iter__( self ):
+ if (self.query is not None) and sqlite.is_read_only_query(self.query):
+ cur = self.connection.cursor()
+ for row in cur.execute(self.query):
+ yield [dict((cur.description[i][0], value) for i, value in enumerate(row))]
+ else:
+ yield
https://bitbucket.org/galaxy/galaxy-central/commits/c207054ea196/
Changeset: c207054ea196
User: jjohnson
Date: 2014-12-12 19:41:35+00:00
Summary: Add dataproviders to the SQlite dataype
Affected #: 1 file
diff -r 1e6035e53de58ce4fe1687ab74512da711d99989 -r c207054ea1966bc9d4640a6215192414e8071739 lib/galaxy/datatypes/binary.py
--- a/lib/galaxy/datatypes/binary.py
+++ b/lib/galaxy/datatypes/binary.py
@@ -620,6 +620,16 @@
dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
return dataproviders.dataset.SQliteDataProvider( dataset_source, **settings )
+ @dataproviders.decorators.dataprovider_factory( 'sqlite-table', dataproviders.dataset.SQliteDataTableProvider.settings )
+ def sqlite_datatableprovider( self, dataset, **settings ):
+ dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
+ return dataproviders.dataset.SQliteDataTableProvider( dataset_source, **settings )
+
+ @dataproviders.decorators.dataprovider_factory( 'sqlite-dict', dataproviders.dataset.SQliteDataDictProvider.settings )
+ def sqlite_datadictprovider( self, dataset, **settings ):
+ dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
+ return dataproviders.dataset.SQliteDataDictProvider( dataset_source, **settings )
+
Binary.register_sniffable_binary_format("sqlite", "sqlite", SQlite)
https://bitbucket.org/galaxy/galaxy-central/commits/ad28e42a8af3/
Changeset: ad28e42a8af3
User: dannon
Date: 2014-12-15 12:43:42+00:00
Summary: Merged in jjohnson/galaxy-central-7 (pull request #609)
Add dataproviders for SQlite datatype
Affected #: 2 files
diff -r ecac8a8679612d419347dcbedc398319418b077e -r ad28e42a8af34c81f0ebcbe6f3baef9bdcf80ae8 lib/galaxy/datatypes/binary.py
--- a/lib/galaxy/datatypes/binary.py
+++ b/lib/galaxy/datatypes/binary.py
@@ -620,6 +620,16 @@
dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
return dataproviders.dataset.SQliteDataProvider( dataset_source, **settings )
+ @dataproviders.decorators.dataprovider_factory( 'sqlite-table', dataproviders.dataset.SQliteDataTableProvider.settings )
+ def sqlite_datatableprovider( self, dataset, **settings ):
+ dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
+ return dataproviders.dataset.SQliteDataTableProvider( dataset_source, **settings )
+
+ @dataproviders.decorators.dataprovider_factory( 'sqlite-dict', dataproviders.dataset.SQliteDataDictProvider.settings )
+ def sqlite_datadictprovider( self, dataset, **settings ):
+ dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
+ return dataproviders.dataset.SQliteDataDictProvider( dataset_source, **settings )
+
Binary.register_sniffable_binary_format("sqlite", "sqlite", SQlite)
diff -r ecac8a8679612d419347dcbedc398319418b077e -r ad28e42a8af34c81f0ebcbe6f3baef9bdcf80ae8 lib/galaxy/datatypes/dataproviders/dataset.py
--- a/lib/galaxy/datatypes/dataproviders/dataset.py
+++ b/lib/galaxy/datatypes/dataproviders/dataset.py
@@ -13,6 +13,7 @@
import external
from galaxy.util import sqlite
import re
+import sys
from galaxy import eggs
eggs.require( 'bx-python' )
@@ -726,3 +727,55 @@
else:
yield
+class SQliteDataTableProvider( base.DataProvider ):
+ """
+ Data provider that uses a sqlite database file as its source.
+ Allows any query to be run and returns the resulting rows as arrays of arrays
+ """
+ settings = {
+ 'query': 'str',
+ 'headers': 'bool',
+ 'limit': 'int'
+ }
+
+ def __init__( self, source, query=None, headers=False, limit=sys.maxint, **kwargs ):
+ self.query = query
+ self.headers = headers
+ self.limit = limit
+ self.connection = sqlite.connect(source.dataset.file_name)
+ super( SQliteDataTableProvider, self ).__init__( source, **kwargs )
+
+ def __iter__( self ):
+ if (self.query is not None) and sqlite.is_read_only_query(self.query):
+ cur = self.connection.cursor()
+ results = cur.execute(self.query)
+ if self.headers:
+ yield [col[0] for col in cur.description]
+ for i,row in enumerate(results):
+ if i >= self.limit:
+ break
+ yield [val for val in row]
+ else:
+ yield
+
+class SQliteDataDictProvider( base.DataProvider ):
+ """
+ Data provider that uses a sqlite database file as its source.
+ Allows any query to be run and returns the resulting rows as arrays of dicts
+ """
+ settings = {
+ 'query': 'str'
+ }
+
+ def __init__( self, source, query=None, **kwargs ):
+ self.query = query
+ self.connection = sqlite.connect(source.dataset.file_name)
+ super( SQliteDataDictProvider, self ).__init__( source, **kwargs )
+
+ def __iter__( self ):
+ if (self.query is not None) and sqlite.is_read_only_query(self.query):
+ cur = self.connection.cursor()
+ for row in cur.execute(self.query):
+ yield [dict((cur.description[i][0], value) for i, value in enumerate(row))]
+ else:
+ yield
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
7 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/563a28409672/
Changeset: 563a28409672
User: jmchilton
Date: 2014-12-15 05:20:35+00:00
Summary: Allow multiple tool data table config files...
... use this functionality to implement tests for tool data API.
Affected #: 5 files
diff -r 6363c839528ca8bbbbc60c1b3784c788468d9445 -r 563a28409672988f6b30545ba987017e7da2e14c lib/galaxy/tools/data/__init__.py
--- a/lib/galaxy/tools/data/__init__.py
+++ b/lib/galaxy/tools/data/__init__.py
@@ -31,8 +31,10 @@
# at server startup. If tool shed repositories are installed that contain a valid file named tool_data_table_conf.xml.sample, entries
# from that file are inserted into this dict at the time of installation.
self.data_tables = {}
- if config_filename:
- self.load_from_config_file( config_filename, self.tool_data_path, from_shed_config=False )
+ for single_config_filename in util.listify( config_filename ):
+ if not single_config_filename:
+ continue
+ self.load_from_config_file( single_config_filename, self.tool_data_path, from_shed_config=False )
def __getitem__( self, key ):
return self.data_tables.__getitem__( key )
diff -r 6363c839528ca8bbbbc60c1b3784c788468d9445 -r 563a28409672988f6b30545ba987017e7da2e14c scripts/functional_tests.py
--- a/scripts/functional_tests.py
+++ b/scripts/functional_tests.py
@@ -231,7 +231,16 @@
start_server = 'GALAXY_TEST_EXTERNAL' not in os.environ
tool_data_table_config_path = None
if os.path.exists( 'tool_data_table_conf.test.xml' ):
+ # If explicitly defined tables for test, use those.
tool_data_table_config_path = 'tool_data_table_conf.test.xml'
+ else:
+ # ... otherise find whatever Galaxy would use as the default and
+ # the sample data for fucntional tests to that.
+ default_tool_data_config = 'config/tool_data_table_conf.xml.sample'
+ for tool_data_config in ['config/tool_data_table_conf.xml', 'tool_data_table_conf.xml' ]:
+ if os.path.exists( tool_data_config ):
+ default_tool_data_config = tool_data_config
+ tool_data_table_config_path = '%s,test/functional/tool-data/sample_tool_data_tables.xml' % default_tool_data_config
shed_tool_data_table_config = 'config/shed_tool_data_table_conf.xml'
tool_dependency_dir = os.environ.get( 'GALAXY_TOOL_DEPENDENCY_DIR', None )
use_distributed_object_store = os.environ.get( 'GALAXY_USE_DISTRIBUTED_OBJECT_STORE', False )
diff -r 6363c839528ca8bbbbc60c1b3784c788468d9445 -r 563a28409672988f6b30545ba987017e7da2e14c test/api/test_tool_data.py
--- /dev/null
+++ b/test/api/test_tool_data.py
@@ -0,0 +1,30 @@
+""" Tests for the tool data API.
+"""
+from base import api
+
+import operator
+
+
+class ToolDataApiTestCase( api.ApiTestCase ):
+
+ def test_admin_only( self ):
+ index_response = self._get( "tool_data", admin=False )
+ self._assert_status_code_is( index_response, 403 )
+
+ def test_list(self):
+ index_response = self._get( "tool_data", admin=True )
+ self._assert_status_code_is( index_response, 200 )
+ print index_response.content
+ index = index_response.json()
+ assert "testalpha" in map(operator.itemgetter("name"), index)
+
+ def test_show(self):
+ show_response = self._get( "tool_data/testalpha", admin=True )
+ self._assert_status_code_is( show_response, 200 )
+ print show_response.content
+ data_table = show_response.json()
+ assert data_table["columns"] == ["value", "name", "path"]
+ first_entry = data_table["fields"][0]
+ assert first_entry[0] == "data1"
+ assert first_entry[1] == "data1name"
+ assert first_entry[2].endswith("test/functional/tool-data/data1/entry.txt")
diff -r 6363c839528ca8bbbbc60c1b3784c788468d9445 -r 563a28409672988f6b30545ba987017e7da2e14c test/functional/tool-data/sample_tool_data_tables.xml
--- /dev/null
+++ b/test/functional/tool-data/sample_tool_data_tables.xml
@@ -0,0 +1,12 @@
+<tables>
+ <table name="testalpha" comment_char="#">
+ <columns>value, name, path</columns>
+ <file path="${__HERE__}/testalpha.loc" />
+ </table>
+ <!--
+ <table name="testbeta" comment_char="#">
+ <columns>value, foo, bar, path</columns>
+ <file path="${__HERE__}/testbeta.loc" />
+ </table>
+ -->
+</tables>
diff -r 6363c839528ca8bbbbc60c1b3784c788468d9445 -r 563a28409672988f6b30545ba987017e7da2e14c test/functional/tool-data/testalpha.loc
--- /dev/null
+++ b/test/functional/tool-data/testalpha.loc
@@ -0,0 +1,2 @@
+data1 data1name ${__HERE__}/data1/entry.txt
+data2 data2name ${__HERE__}/data2/entry.txt
https://bitbucket.org/galaxy/galaxy-central/commits/8795d0fe13c4/
Changeset: 8795d0fe13c4
User: jmchilton
Date: 2014-12-15 05:20:35+00:00
Summary: PEP-8 fixes for lib/galaxy/tools/data/__init__.py.
Affected #: 1 file
diff -r 563a28409672988f6b30545ba987017e7da2e14c -r 8795d0fe13c45804ea53f4543c71ee7ca6f41a0c lib/galaxy/tools/data/__init__.py
--- a/lib/galaxy/tools/data/__init__.py
+++ b/lib/galaxy/tools/data/__init__.py
@@ -9,9 +9,7 @@
import logging
import os
import os.path
-import shutil
import string
-import tempfile
from galaxy import util
from galaxy.util.odict import odict
@@ -22,6 +20,7 @@
DEFAULT_TABLE_TYPE = 'tabular'
+
class ToolDataTableManager( object ):
"""Manages a collection of tool data tables"""
@@ -81,7 +80,7 @@
log.debug( "Loaded tool data table '%s'", table.name )
else:
log.debug( "Loading another instance of data table '%s', attempting to merge content.", table.name )
- self.data_tables[ table.name ].merge_tool_data_table( table, allow_duplicates=False ) #only merge content, do not persist to disk, do not allow duplicate rows when merging
+ self.data_tables[ table.name ].merge_tool_data_table( table, allow_duplicates=False ) # only merge content, do not persist to disk, do not allow duplicate rows when merging
# FIXME: This does not account for an entry with the same unique build ID, but a different path.
return table_elems
@@ -128,7 +127,7 @@
"""
if not ( new_elems or remove_elems ):
log.debug( 'ToolDataTableManager.to_xml_file called without any elements to add or remove.' )
- return #no changes provided, no need to persist any changes
+ return # no changes provided, no need to persist any changes
if not new_elems:
new_elems = []
if not remove_elems:
@@ -175,7 +174,7 @@
assert table_type in tool_data_table_types, "Unknown data table type '%s'" % type
return tool_data_table_types[ table_type ]( table_elem, tool_data_path, from_shed_config=from_shed_config, filename=filename )
- def __init__( self, config_element, tool_data_path, from_shed_config = False, filename=None ):
+ def __init__( self, config_element, tool_data_path, from_shed_config=False, filename=None ):
self.name = config_element.get( 'name' )
self.comment_char = config_element.get( 'comment_char' )
self.empty_field_value = config_element.get( 'empty_field_value', '' )
@@ -187,7 +186,7 @@
# increment this variable any time a new entry is added, or when the table is totally reloaded
# This value has no external meaning, and does not represent an abstract version of the underlying data
self._loaded_content_version = 1
- self._load_info = ( [ config_element, tool_data_path ], { 'from_shed_config':from_shed_config } )
+ self._load_info = ( [ config_element, tool_data_path ], { 'from_shed_config': from_shed_config } )
self._merged_load_info = []
def _update_version( self, version=None ):
@@ -252,13 +251,13 @@
type_key = 'tabular'
- def __init__( self, config_element, tool_data_path, from_shed_config = False, filename=None ):
+ def __init__( self, config_element, tool_data_path, from_shed_config=False, filename=None ):
super( TabularToolDataTable, self ).__init__( config_element, tool_data_path, from_shed_config, filename)
self.config_element = config_element
self.data = []
self.configure_and_load( config_element, tool_data_path, from_shed_config)
- def configure_and_load( self, config_element, tool_data_path, from_shed_config = False):
+ def configure_and_load( self, config_element, tool_data_path, from_shed_config=False):
"""
Configure and load table from an XML element.
"""
@@ -323,7 +322,6 @@
else:
log.debug( "Filename '%s' already exists in filenames (%s), not adding", filename, self.filenames.keys() )
-
def merge_tool_data_table( self, other_table, allow_duplicates=True, persist=False, persist_on_error=False, entry_source=None, **kwd ):
assert self.columns == other_table.columns, "Merging tabular data tables with non matching columns is not allowed: %s:%s != %s:%s" % ( self.name, self.columns, other_table.name, other_table.columns )
#merge filename info
@@ -350,7 +348,7 @@
for i, field in enumerate( fields ):
field_name = named_colums[i]
if field_name is None:
- field_name = i #check that this is supposed to be 0 based.
+ field_name = i # check that this is supposed to be 0 based.
field_dict[ field_name ] = field
rval.append( field_dict )
return rval
@@ -508,7 +506,7 @@
data_table_fh = open( filename, 'wb' )
if os.stat( filename )[6] != 0:
# ensure last existing line ends with new line
- data_table_fh.seek( -1, 2 ) #last char in file
+ data_table_fh.seek( -1, 2 ) # last char in file
last_char = data_table_fh.read( 1 )
if last_char not in [ '\n', '\r' ]:
data_table_fh.write( '\n' )
@@ -577,7 +575,7 @@
def to_dict(self, view='collection'):
rval = super(TabularToolDataTable, self).to_dict()
if view == 'element':
- rval['columns'] = sorted(self.columns.keys(), key=lambda x:self.columns[x])
+ rval['columns'] = sorted(self.columns.keys(), key=lambda x: self.columns[x])
rval['fields'] = self.get_fields()
return rval
https://bitbucket.org/galaxy/galaxy-central/commits/6d54a7fd1fe9/
Changeset: 6d54a7fd1fe9
User: jmchilton
Date: 2014-12-15 05:20:35+00:00
Summary: Small improvements to tool data API.
Update tool data API to use newer API decorator for better error handling, create some abstractions for reuse down stream, etc...
Affected #: 1 file
diff -r 8795d0fe13c45804ea53f4543c71ee7ca6f41a0c -r 6d54a7fd1fe9edfdf2f5c2fdb1b5ddb7eebdf59a lib/galaxy/webapps/galaxy/api/tool_data.py
--- a/lib/galaxy/webapps/galaxy/api/tool_data.py
+++ b/lib/galaxy/webapps/galaxy/api/tool_data.py
@@ -1,4 +1,6 @@
+from galaxy import exceptions
from galaxy import web
+from galaxy.web import _future_expose_api as expose_api
from galaxy.web.base.controller import BaseAPIController
@@ -8,21 +10,21 @@
"""
@web.require_admin
- @web.expose_api
+ @expose_api
def index( self, trans, **kwds ):
"""
GET /api/tool_data: returns a list tool_data tables::
"""
- return list( a.to_dict() for a in trans.app.tool_data_tables.data_tables.values() )
+ return list( a.to_dict() for a in self._data_tables.values() )
@web.require_admin
- @web.expose_api
+ @expose_api
def show( self, trans, id, **kwds ):
- return trans.app.tool_data_tables.data_tables[id].to_dict(view='element')
+ return self._data_table(id).to_dict(view='element')
@web.require_admin
- @web.expose_api
+ @expose_api
def delete( self, trans, id, **kwd ):
"""
DELETE /api/tool_data/{id}
@@ -61,3 +63,13 @@
return "Invalid data table item ( %s ) specified. Wrong number of columns (%s given, %s required)." % ( str( values ), str(len(split_values)), str(len(data_table.get_column_name_list())))
return data_table.remove_entry(split_values)
+
+ def _data_table( self, id ):
+ try:
+ return self._data_tables[id]
+ except IndexError:
+ raise exceptions.ObjectNotFound("No such data table %s" % id)
+
+ @property
+ def _data_tables( self ):
+ return self.app.tool_data_tables.data_tables
https://bitbucket.org/galaxy/galaxy-central/commits/edbe286564de/
Changeset: edbe286564de
User: jmchilton
Date: 2014-12-15 05:20:35+00:00
Summary: Implement a detailed break down of data table fields via API.
Originally this approach was laid out by Kyle Ellrott in this pull request (https://bitbucket.org/galaxy/galaxy-central/pull-request/531/add-downloads-…) The changes to galaxy.tools.data are entirely his contribution, I only reworked the API and endpoint slightly and did some stylistic fixes and refactoring.
Affected #: 4 files
diff -r 6d54a7fd1fe9edfdf2f5c2fdb1b5ddb7eebdf59a -r edbe286564de14b2d51656c03443b3e4aa99e3ab lib/galaxy/tools/data/__init__.py
--- a/lib/galaxy/tools/data/__init__.py
+++ b/lib/galaxy/tools/data/__init__.py
@@ -9,7 +9,11 @@
import logging
import os
import os.path
+import re
import string
+import hashlib
+
+from glob import glob
from galaxy import util
from galaxy.util.odict import odict
@@ -340,6 +344,13 @@
def get_fields( self ):
return self.data
+ def get_field(self, value):
+ rval = None
+ for i in self.get_named_fields_list():
+ if i['value'] == value:
+ rval = TabularToolDataField(i)
+ return rval
+
def get_named_fields_list( self ):
rval = []
named_colums = self.get_column_name_list()
@@ -580,6 +591,58 @@
return rval
+class TabularToolDataField(Dictifiable, object):
+
+ dict_collection_visible_keys = []
+
+ def __init__(self, data):
+ self.data = data
+
+ def __getitem__(self, key):
+ return self.data[key]
+
+ def get_base_path(self):
+ return os.path.normpath(os.path.abspath( self.data['path'] ))
+
+ def get_base_dir(self):
+ path = self.get_base_path()
+ if not os.path.isdir(path):
+ path = os.path.dirname(path)
+ return path
+
+ def clean_base_dir(self, path):
+ return re.sub( "^" + self.get_base_dir() + r"/*", "", path )
+
+ def get_files(self):
+ return glob( self.get_base_path() + "*" )
+
+ def get_filesize_map(self, rm_base_dir=False):
+ out = {}
+ for path in self.get_files():
+ if rm_base_dir:
+ out[self.clean_base_dir(path)] = os.path.getsize(path)
+ else:
+ out[path] = os.path.getsize(path)
+ return out
+
+ def get_fingerprint(self):
+ sha1 = hashlib.sha1()
+ fmap = self.get_filesize_map(True)
+ for k in sorted(fmap.keys()):
+ sha1.update(k)
+ sha1.update(str(fmap[k]))
+ return sha1.hexdigest()
+
+ def to_dict(self):
+ rval = super(TabularToolDataField, self).to_dict()
+ rval['name'] = self.data['value']
+ rval['fields'] = self.data
+ rval['base_dir'] = self.get_base_dir(),
+ rval['files'] = self.get_filesize_map(True)
+ rval['fingerprint'] = self.get_fingerprint()
+ return rval
+
+
def expand_here_template(content, here=None):
if here and content:
content = string.Template(content).safe_substitute( { "__HERE__": here })
diff -r 6d54a7fd1fe9edfdf2f5c2fdb1b5ddb7eebdf59a -r edbe286564de14b2d51656c03443b3e4aa99e3ab lib/galaxy/webapps/galaxy/api/tool_data.py
--- a/lib/galaxy/webapps/galaxy/api/tool_data.py
+++ b/lib/galaxy/webapps/galaxy/api/tool_data.py
@@ -64,6 +64,22 @@
return data_table.remove_entry(split_values)
+ @web.require_admin
+ @expose_api
+ def show_field( self, trans, id, value, **kwds ):
+ """
+ GET /api/tool_data/<id>/fields/<value>
+
+ Get information about a partiular field in a tool_data table
+ """
+ return self._data_table_field( id, value ).to_dict()
+
+ def _data_table_field( self, id, value ):
+ out = self._data_table(id).get_field(value)
+ if out is None:
+ raise exceptions.ObjectNotFound("No such field %s in data table %s." % (value, id))
+ return out
+
def _data_table( self, id ):
try:
return self._data_tables[id]
diff -r 6d54a7fd1fe9edfdf2f5c2fdb1b5ddb7eebdf59a -r edbe286564de14b2d51656c03443b3e4aa99e3ab lib/galaxy/webapps/galaxy/buildapp.py
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -171,6 +171,7 @@
webapp.mapper.resource( 'dataset', 'datasets', path_prefix='/api' )
webapp.mapper.resource( 'tool_data', 'tool_data', path_prefix='/api' )
+ webapp.mapper.connect( '/api/tool_data/{id:.+?}/fields/{value:.+?}', action='show_field', controller="tool_data" )
webapp.mapper.resource( 'dataset_collection', 'dataset_collections', path_prefix='/api/')
webapp.mapper.resource( 'sample', 'samples', path_prefix='/api' )
webapp.mapper.resource( 'request', 'requests', path_prefix='/api' )
diff -r 6d54a7fd1fe9edfdf2f5c2fdb1b5ddb7eebdf59a -r edbe286564de14b2d51656c03443b3e4aa99e3ab test/api/test_tool_data.py
--- a/test/api/test_tool_data.py
+++ b/test/api/test_tool_data.py
@@ -28,3 +28,11 @@
assert first_entry[0] == "data1"
assert first_entry[1] == "data1name"
assert first_entry[2].endswith("test/functional/tool-data/data1/entry.txt")
+
+ def test_show_field(self):
+ show_field_response = self._get( "tool_data/testalpha/fields/data1", admin=True )
+ self._assert_status_code_is( show_field_response, 200 )
+ field = show_field_response.json()
+ self._assert_has_keys( field, "files", "name", "fields", "fingerprint", "base_dir" )
+ files = field[ "files" ]
+ assert len( files ) == 2
https://bitbucket.org/galaxy/galaxy-central/commits/e416c3a2f956/
Changeset: e416c3a2f956
User: jmchilton
Date: 2014-12-15 05:20:35+00:00
Summary: Allow downloading index files via tool data API.
This provides direct access to the files to admins - probably still wise to provide some mechanism to download a copressed archive of these files.
Affected #: 3 files
diff -r edbe286564de14b2d51656c03443b3e4aa99e3ab -r e416c3a2f956a28127eb730076f37ad6ee4b1d8b lib/galaxy/webapps/galaxy/api/tool_data.py
--- a/lib/galaxy/webapps/galaxy/api/tool_data.py
+++ b/lib/galaxy/webapps/galaxy/api/tool_data.py
@@ -1,6 +1,9 @@
+import os
+
from galaxy import exceptions
from galaxy import web
from galaxy.web import _future_expose_api as expose_api
+from galaxy.web import _future_expose_api_raw as expose_api_raw
from galaxy.web.base.controller import BaseAPIController
@@ -74,6 +77,16 @@
"""
return self._data_table_field( id, value ).to_dict()
+ @web.require_admin
+ @expose_api_raw
+ def download_field_file( self, trans, id, value, path, **kwds ):
+ field_value = self._data_table_field( id, value )
+ base_dir = field_value.get_base_dir()
+ full_path = os.path.join( base_dir, path )
+ if full_path not in field_value.get_files():
+ raise exceptions.ObjectNotFound("No such path in data table field.")
+ return open(full_path, "r")
+
def _data_table_field( self, id, value ):
out = self._data_table(id).get_field(value)
if out is None:
diff -r edbe286564de14b2d51656c03443b3e4aa99e3ab -r e416c3a2f956a28127eb730076f37ad6ee4b1d8b lib/galaxy/webapps/galaxy/buildapp.py
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -171,6 +171,7 @@
webapp.mapper.resource( 'dataset', 'datasets', path_prefix='/api' )
webapp.mapper.resource( 'tool_data', 'tool_data', path_prefix='/api' )
+ webapp.mapper.connect( '/api/tool_data/{id:.+?}/fields/{value:.+?}/files/{path:.+?}', action='download_field_file', controller="tool_data" )
webapp.mapper.connect( '/api/tool_data/{id:.+?}/fields/{value:.+?}', action='show_field', controller="tool_data" )
webapp.mapper.resource( 'dataset_collection', 'dataset_collections', path_prefix='/api/')
webapp.mapper.resource( 'sample', 'samples', path_prefix='/api' )
diff -r edbe286564de14b2d51656c03443b3e4aa99e3ab -r e416c3a2f956a28127eb730076f37ad6ee4b1d8b test/api/test_tool_data.py
--- a/test/api/test_tool_data.py
+++ b/test/api/test_tool_data.py
@@ -36,3 +36,9 @@
self._assert_has_keys( field, "files", "name", "fields", "fingerprint", "base_dir" )
files = field[ "files" ]
assert len( files ) == 2
+
+ def test_download_field_file(self):
+ show_field_response = self._get( "tool_data/testalpha/fields/data1/files/entry.txt", admin=True )
+ self._assert_status_code_is( show_field_response, 200 )
+ content = show_field_response.content
+ assert content == "This is data 1.", content
https://bitbucket.org/galaxy/galaxy-central/commits/66fbbbd58209/
Changeset: 66fbbbd58209
User: jmchilton
Date: 2014-12-15 05:20:35+00:00
Summary: Allow specification of multiple data manager configuration files...
... use this to create API functional tests for data managers.
Affected #: 7 files
diff -r e416c3a2f956a28127eb730076f37ad6ee4b1d8b -r 66fbbbd58209dba224f95c2aaf973ec7c64b7c11 lib/galaxy/tools/data_manager/manager.py
--- a/lib/galaxy/tools/data_manager/manager.py
+++ b/lib/galaxy/tools/data_manager/manager.py
@@ -25,7 +25,10 @@
self.managed_data_tables = odict()
self.tool_path = None
self.filename = xml_filename or self.app.config.data_manager_config_file
- self.load_from_xml( self.filename )
+ for filename in util.listify( self.filename ):
+ if not filename:
+ continue
+ self.load_from_xml( filename )
if self.app.config.shed_data_manager_config_file:
self.load_from_xml( self.app.config.shed_data_manager_config_file, store_tool_path=False, replace_existing=True )
diff -r e416c3a2f956a28127eb730076f37ad6ee4b1d8b -r 66fbbbd58209dba224f95c2aaf973ec7c64b7c11 scripts/functional_tests.py
--- a/scripts/functional_tests.py
+++ b/scripts/functional_tests.py
@@ -241,6 +241,12 @@
if os.path.exists( tool_data_config ):
default_tool_data_config = tool_data_config
tool_data_table_config_path = '%s,test/functional/tool-data/sample_tool_data_tables.xml' % default_tool_data_config
+
+ default_data_manager_config = 'config/data_manager_conf.xml.sample'
+ for data_manager_config in ['config/data_manager_conf.xml', 'data_manager_conf.xml' ]:
+ if os.path.exists( data_manager_config ):
+ default_data_manager_config = data_manager_config
+ data_manager_config_file = "%s,test/functional/tools/sample_data_manager_conf.xml" % default_data_manager_config
shed_tool_data_table_config = 'config/shed_tool_data_table_conf.xml'
tool_dependency_dir = os.environ.get( 'GALAXY_TOOL_DEPENDENCY_DIR', None )
use_distributed_object_store = os.environ.get( 'GALAXY_USE_DISTRIBUTED_OBJECT_STORE', False )
@@ -329,6 +335,7 @@
master_api_key=master_api_key,
use_tasked_jobs=True,
enable_beta_tool_formats=True,
+ data_manager_config_file=data_manager_config_file,
)
if install_database_connection is not None:
kwargs[ 'install_database_connection' ] = install_database_connection
diff -r e416c3a2f956a28127eb730076f37ad6ee4b1d8b -r 66fbbbd58209dba224f95c2aaf973ec7c64b7c11 test/api/test_tool_data.py
--- a/test/api/test_tool_data.py
+++ b/test/api/test_tool_data.py
@@ -1,6 +1,9 @@
""" Tests for the tool data API.
"""
+import json
+
from base import api
+from .helpers import DatasetPopulator
import operator
@@ -42,3 +45,19 @@
self._assert_status_code_is( show_field_response, 200 )
content = show_field_response.content
assert content == "This is data 1.", content
+
+ def test_create_data_with_manager(self):
+ dataset_populator = DatasetPopulator( self.galaxy_interactor )
+ history_id = dataset_populator.new_history()
+ payload = dataset_populator.run_tool_payload(
+ tool_id="data_manager",
+ inputs={"ignored_value": "moo"},
+ history_id=history_id,
+ )
+ create_response = self._post( "tools", data=payload )
+ self._assert_status_code_is( create_response, 200 )
+ dataset_populator.wait_for_history( history_id, assert_ok=True )
+ show_response = self._get( "tool_data/testbeta", admin=True )
+ print show_response.content
+ assert False
+
diff -r e416c3a2f956a28127eb730076f37ad6ee4b1d8b -r 66fbbbd58209dba224f95c2aaf973ec7c64b7c11 test/functional/tool-data/sample_tool_data_tables.xml
--- a/test/functional/tool-data/sample_tool_data_tables.xml
+++ b/test/functional/tool-data/sample_tool_data_tables.xml
@@ -3,10 +3,8 @@
<columns>value, name, path</columns><file path="${__HERE__}/testalpha.loc" /></table>
- <!--
<table name="testbeta" comment_char="#">
- <columns>value, foo, bar, path</columns>
+ <columns>value, path</columns><file path="${__HERE__}/testbeta.loc" /></table>
- --></tables>
diff -r e416c3a2f956a28127eb730076f37ad6ee4b1d8b -r 66fbbbd58209dba224f95c2aaf973ec7c64b7c11 test/functional/tools/data_manager.xml
--- /dev/null
+++ b/test/functional/tools/data_manager.xml
@@ -0,0 +1,16 @@
+<tool id="data_manager" name="Test Data Manager" tool_type="manage_data" version="0.0.1">
+ <configfiles>
+ <configfile name="static_test_data">{"data_tables": {"testbeta": [{"value": "newvalue", "path": "newvalue.txt"}]}}</configfile>
+ </configfiles>
+ <command>
+ mkdir $out_file.files_path ;
+ echo "A new value" > $out_file.files_path/newvalue.txt;
+ cp $static_test_data $out_file
+ </command>
+ <inputs>
+ <param type="text" name="ignored_value" value="" label="Ignored" />
+ </inputs>
+ <outputs>
+ <data name="out_file" format="data_manager_json"/>
+ </outputs>
+</tool>
diff -r e416c3a2f956a28127eb730076f37ad6ee4b1d8b -r 66fbbbd58209dba224f95c2aaf973ec7c64b7c11 test/functional/tools/sample_data_manager_conf.xml
--- /dev/null
+++ b/test/functional/tools/sample_data_manager_conf.xml
@@ -0,0 +1,16 @@
+<data_managers tool_path="test/functional/tools">
+ <data_manager tool_file="data_manager.xml" id="test_data_manager" version="1.0">
+ <data_table name="testbeta">
+ <output>
+ <column name="value" />
+ <column name="path" output_ref="out_file" >
+ <move type="directory" relativize_symlinks="True">
+ <target base="${GALAXY_DATA_MANAGER_DATA_PATH}">testbeta/${value}</target>
+ </move>
+ <value_translation>${GALAXY_DATA_MANAGER_DATA_PATH}/testbeta/${value}/${path}</value_translation>
+ <value_translation type="function">abspath</value_translation>
+ </column>
+ </output>
+ </data_table>
+ </data_manager>
+</data_managers>
https://bitbucket.org/galaxy/galaxy-central/commits/ecac8a867961/
Changeset: ecac8a867961
User: jmchilton
Date: 2014-12-15 05:20:35+00:00
Summary: Implement test case for Pull Request #577.
Affected #: 1 file
diff -r 66fbbbd58209dba224f95c2aaf973ec7c64b7c11 -r ecac8a8679612d419347dcbedc398319418b077e test/api/test_tool_data.py
--- a/test/api/test_tool_data.py
+++ b/test/api/test_tool_data.py
@@ -5,6 +5,8 @@
from base import api
from .helpers import DatasetPopulator
+from requests import delete
+
import operator
@@ -46,7 +48,10 @@
content = show_field_response.content
assert content == "This is data 1.", content
- def test_create_data_with_manager(self):
+ def test_delete_entry(self):
+ show_response = self._get( "tool_data/testbeta", admin=True )
+ original_count = len(show_response.json()["fields"])
+
dataset_populator = DatasetPopulator( self.galaxy_interactor )
history_id = dataset_populator.new_history()
payload = dataset_populator.run_tool_payload(
@@ -58,6 +63,12 @@
self._assert_status_code_is( create_response, 200 )
dataset_populator.wait_for_history( history_id, assert_ok=True )
show_response = self._get( "tool_data/testbeta", admin=True )
- print show_response.content
- assert False
+ updated_fields = show_response.json()["fields"]
+ assert len(updated_fields) == original_count + 1
+ field0 = updated_fields[0]
+ url = self._api_url( "tool_data/testbeta?key=%s" % self.galaxy_interactor.master_api_key )
+ delete( url, data=json.dumps({"values": "\t".join(field0)}) )
+ show_response = self._get( "tool_data/testbeta", admin=True )
+ updated_fields = show_response.json()["fields"]
+ assert len(updated_fields) == original_count
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 abretaud/galaxy-central (pull request #577)
by commits-noreply@bitbucket.org 14 Dec '14
by commits-noreply@bitbucket.org 14 Dec '14
14 Dec '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/6363c839528c/
Changeset: 6363c839528c
User: jmchilton
Date: 2014-12-15 05:17:31+00:00
Summary: Merged in abretaud/galaxy-central (pull request #577)
Add an API to remove items from tool data tables
Affected #: 2 files
diff -r ac537b0a4167cbd14e0cb93175cc85c096275697 -r 6363c839528ca8bbbbc60c1b3784c788468d9445 lib/galaxy/tools/data/__init__.py
--- a/lib/galaxy/tools/data/__init__.py
+++ b/lib/galaxy/tools/data/__init__.py
@@ -211,6 +211,13 @@
self.add_entry( entry, allow_duplicates=allow_duplicates, persist=persist, persist_on_error=persist_on_error, entry_source=entry_source, **kwd )
return self._loaded_content_version
+ def _remove_entry(self, values):
+ raise NotImplementedError( "Abstract method" )
+
+ def remove_entry(self, values):
+ self._remove_entry( values )
+ return self._update_version()
+
def is_current_version( self, other_version ):
return self._loaded_content_version == other_version
@@ -506,6 +513,42 @@
data_table_fh.write( "%s\n" % ( self.separator.join( fields ) ) )
return not is_error
+ def _remove_entry( self, values):
+
+ # update every file
+ for filename in self.filenames:
+
+ if os.path.exists( filename ):
+ values = self._replace_field_separators( values )
+ self.filter_file_fields( filename, values )
+ else:
+ log.warn( "Cannot find index file '%s' for tool data table '%s'" % ( filename, self.name ) )
+
+ self.reload_from_files()
+
+ def filter_file_fields( self, loc_file, values ):
+ """
+ Reads separated lines from file and print back only the lines that pass a filter.
+ """
+ separator_char = (lambda c: '<TAB>' if c == '\t' else c)(self.separator)
+
+ with open(loc_file) as reader:
+ rval = ""
+ for i, line in enumerate( reader ):
+ if line.lstrip().startswith( self.comment_char ):
+ rval += line
+ else:
+ line_s = line.rstrip( "\n\r" )
+ if line_s:
+ fields = line_s.split( self.separator )
+ if fields != values:
+ rval += line
+
+ with open(loc_file, 'wb') as writer:
+ writer.write(rval)
+
+ return rval
+
def _replace_field_separators( self, fields, separator=None, replace=None, comment_char=None ):
#make sure none of the fields contain separator
#make sure separator replace is different from comment_char,
diff -r ac537b0a4167cbd14e0cb93175cc85c096275697 -r 6363c839528ca8bbbbc60c1b3784c788468d9445 lib/galaxy/webapps/galaxy/api/tool_data.py
--- a/lib/galaxy/webapps/galaxy/api/tool_data.py
+++ b/lib/galaxy/webapps/galaxy/api/tool_data.py
@@ -20,3 +20,44 @@
@web.expose_api
def show( self, trans, id, **kwds ):
return trans.app.tool_data_tables.data_tables[id].to_dict(view='element')
+
+ @web.require_admin
+ @web.expose_api
+ def delete( self, trans, id, **kwd ):
+ """
+ DELETE /api/tool_data/{id}
+ Removes an item from a data table
+
+ :type id: str
+ :param id: the id of the data table containing the item to delete
+ :type kwd: dict
+ :param kwd: (required) dictionary structure containing:
+
+ * payload: a dictionary itself containing:
+ * values: <TAB> separated list of column contents, there must be a value for all the columns of the data table
+ """
+ decoded_tool_data_id = id
+
+ try:
+ data_table = trans.app.tool_data_tables.data_tables.get(decoded_tool_data_id)
+ except:
+ data_table = None
+ if not data_table:
+ trans.response.status = 400
+ return "Invalid data table id ( %s ) specified." % str( decoded_tool_data_id )
+
+ values = None
+ if kwd.get( 'payload', None ):
+ values = kwd['payload'].get( 'values', '' )
+
+ if not values:
+ trans.response.status = 400
+ return "Invalid data table item ( %s ) specified." % str( values )
+
+ split_values = values.split("\t")
+
+ if len(split_values) != len(data_table.get_column_name_list()):
+ trans.response.status = 400
+ return "Invalid data table item ( %s ) specified. Wrong number of columns (%s given, %s required)." % ( str( values ), str(len(split_values)), str(len(data_table.get_column_name_list())))
+
+ return data_table.remove_entry(split_values)
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/5edaafeecfed/
Changeset: 5edaafeecfed
User: abretaud
Date: 2014-11-27 13:18:18+00:00
Summary: Add an API to remove items from tool data tables
Affected #: 2 files
diff -r 3975b317ffdcbaa6c8dcb4b2b52404bfa34edbc2 -r 5edaafeecfed448ea259adaacf3a4ce6e6da15d1 lib/galaxy/tools/data/__init__.py
--- a/lib/galaxy/tools/data/__init__.py
+++ b/lib/galaxy/tools/data/__init__.py
@@ -211,6 +211,13 @@
self.add_entry( entry, allow_duplicates=allow_duplicates, persist=persist, persist_on_error=persist_on_error, entry_source=entry_source, **kwd )
return self._loaded_content_version
+ def _remove_entry(self, values, **kwd):
+ raise NotImplementedError( "Abstract method" )
+
+ def remove_entry(self, values, **kwd):
+ self._remove_entry_and_reload( values,**kwd )
+ return self._update_version()
+
def is_current_version( self, other_version ):
return self._loaded_content_version == other_version
@@ -506,6 +513,42 @@
data_table_fh.write( "%s\n" % ( self.separator.join( fields ) ) )
return not is_error
+ def _remove_entry_and_reload( self, values):
+
+ # update every file
+ for filename in self.filenames:
+
+ if os.path.exists( filename ):
+ values = self._replace_field_separators( values )
+ self.filter_file_fields( filename, values )
+ else:
+ log.warn( "Cannot find index file '%s' for tool data table '%s'" % ( filename, self.name ) )
+
+ self.reload_from_files()
+
+ def filter_file_fields( self, loc_file, values ):
+ """
+ Reads separated lines from file and print back only the lines that pass a filter.
+ """
+ separator_char = (lambda c: '<TAB>' if c == '\t' else c)(self.separator)
+
+ with open(loc_file) as reader:
+ rval = ""
+ for i, line in enumerate( reader ):
+ if line.lstrip().startswith( self.comment_char ):
+ rval += line
+ else:
+ line_s = line.rstrip( "\n\r" )
+ if line_s:
+ fields = line_s.split( self.separator )
+ if fields != values:
+ rval += line
+
+ with open(loc_file, 'wb') as writer:
+ writer.write(rval)
+
+ return rval
+
def _replace_field_separators( self, fields, separator=None, replace=None, comment_char=None ):
#make sure none of the fields contain separator
#make sure separator replace is different from comment_char,
diff -r 3975b317ffdcbaa6c8dcb4b2b52404bfa34edbc2 -r 5edaafeecfed448ea259adaacf3a4ce6e6da15d1 lib/galaxy/webapps/galaxy/api/tool_data.py
--- a/lib/galaxy/webapps/galaxy/api/tool_data.py
+++ b/lib/galaxy/webapps/galaxy/api/tool_data.py
@@ -20,3 +20,44 @@
@web.expose_api
def show( self, trans, id, **kwds ):
return trans.app.tool_data_tables.data_tables[id].to_dict(view='element')
+
+ @web.require_admin
+ @web.expose_api
+ def delete( self, trans, id, **kwd ):
+ """
+ DELETE /api/tool_data/{id}
+ Removes a role from a group
+
+ :type id: str
+ :param id: the encoded id of the history to delete
+ :type kwd: dict
+ :param kwd: (required) dictionary structure containing:
+
+ * payload: a dictionary itself containing:
+ * values: <TAB> separated list of column contents, there must be a value for all the columns of the data table
+ """
+ decoded_tool_data_id = id
+
+ try:
+ data_table = trans.app.tool_data_tables.data_tables.get(decoded_tool_data_id)
+ except:
+ data_table = None
+ if not data_table:
+ trans.response.status = 400
+ return "Invalid data table id ( %s ) specified." % str( decoded_tool_data_id )
+
+ values = None
+ if kwd.get( 'payload', None ):
+ values = kwd['payload'].get( 'values', '' )
+
+ if not values:
+ trans.response.status = 400
+ return "Invalid data table item ( %s ) specified." % str( values )
+
+ split_values = values.split("\t")
+
+ if len(split_values) != len(data_table.get_column_name_list()):
+ trans.response.status = 400
+ return "Invalid data table item ( %s ) specified. Wrong number of columns (%s given, %s required)." % ( str( values ), str(len(split_values)), str(len(data_table.get_column_name_list())))
+
+ return data_table.remove_entry(split_values)
https://bitbucket.org/galaxy/galaxy-central/commits/d76253bb22c4/
Changeset: d76253bb22c4
User: abretaud
Date: 2014-12-09 14:01:47+00:00
Summary: Merged galaxy/galaxy-central into default
Affected #: 184 files
diff -r 5edaafeecfed448ea259adaacf3a4ce6e6da15d1 -r d76253bb22c4f64c73523956d119c0c5ab0a357b .hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -20,4 +20,4 @@
ca45b78adb4152fc6e7395514d46eba6b7d0b838 release_2014.08.11
548ab24667d6206780237bd807f7d857a484c461 latest_2014.08.11
2092948937ac30ef82f71463a235c66d34987088 release_2014.10.06
-acc8d1e2bc88530aa8d8651cf5f88649f6769304 latest_2014.10.06
+acb2548443ae42d39ef200d035ccc0481d6b930c latest_2014.10.06
diff -r 5edaafeecfed448ea259adaacf3a4ce6e6da15d1 -r d76253bb22c4f64c73523956d119c0c5ab0a357b client/galaxy/scripts/galaxy.library.js
--- a/client/galaxy/scripts/galaxy.library.js
+++ b/client/galaxy/scripts/galaxy.library.js
@@ -36,7 +36,7 @@
initialize: function() {
this.routesHit = 0;
//keep count of number of routes handled by the application
- Backbone.history.on('route', function() { this.routesHit++; }, this);
+ Backbone.history.on( 'route', function() { this.routesHit++; }, this );
},
routes: {
@@ -45,6 +45,7 @@
"library/:library_id/permissions" : "library_permissions",
"folders/:folder_id/permissions" : "folder_permissions",
"folders/:id" : "folder_content",
+ "folders/:id/page/:show_page" : "folder_page",
"folders/:folder_id/datasets/:dataset_id" : "dataset_detail",
"folders/:folder_id/datasets/:dataset_id/permissions" : "dataset_permissions",
"folders/:folder_id/datasets/:dataset_id/versions/:ldda_id" : "dataset_version",
@@ -53,13 +54,13 @@
},
back: function() {
- if(this.routesHit > 1) {
+ if( this.routesHit > 1 ) {
//more than one route hit -> user did not land to current page directly
window.history.back();
} else {
//otherwise go to the home page. Use replaceState if available so
//the navigation doesn't create an extra history entry
- this.navigate('#', {trigger:true, replace:true});
+ this.navigate( '#', { trigger:true, replace:true } );
}
}
});
@@ -71,7 +72,8 @@
with_deleted : false,
sort_order : 'asc',
sort_by : 'name',
- library_page_size : 20
+ library_page_size : 20,
+ folder_page_size : 15
}
});
@@ -94,10 +96,10 @@
this.library_router = new LibraryRouter();
- this.library_router.on('route:libraries', function() {
- Galaxy.libraries.libraryToolbarView = new mod_librarytoolbar_view.LibraryToolbarView();
- Galaxy.libraries.libraryListView = new mod_librarylist_view.LibraryListView();
- });
+ this.library_router.on( 'route:libraries', function() {
+ Galaxy.libraries.libraryToolbarView = new mod_librarytoolbar_view.LibraryToolbarView();
+ Galaxy.libraries.libraryListView = new mod_librarylist_view.LibraryListView();
+ });
this.library_router.on('route:libraries_page', function( show_page ) {
if ( Galaxy.libraries.libraryToolbarView === null ){
@@ -108,66 +110,77 @@
}
});
- this.library_router.on('route:folder_content', function(id) {
+ this.library_router.on( 'route:folder_content', function( id ) {
if (Galaxy.libraries.folderToolbarView){
- Galaxy.libraries.folderToolbarView.$el.unbind('click');
- }
- Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView({id: id});
- Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView({id: id});
- });
+ Galaxy.libraries.folderToolbarView.$el.unbind( 'click' );
+ }
+ Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView( { id: id } );
+ Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView( { id: id } );
+ });
- this.library_router.on('route:download', function(folder_id, format) {
- if ($('#folder_list_body').find(':checked').length === 0) {
- mod_toastr.info( 'You must select at least one dataset to download' );
- Galaxy.libraries.library_router.navigate('folders/' + folder_id, {trigger: true, replace: true});
- } else {
- Galaxy.libraries.folderToolbarView.download(folder_id, format);
- Galaxy.libraries.library_router.navigate('folders/' + folder_id, {trigger: false, replace: true});
- }
- });
+ this.library_router.on( 'route:folder_page', function( id, show_page ) {
+ if ( Galaxy.libraries.folderToolbarView === null ){
+ Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView( {id: id} );
+ Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView( { id: id, show_page: show_page } );
+ } else {
+ Galaxy.libraries.folderListView.render( { id: id, show_page: parseInt( show_page ) } )
+ }
+ });
- this.library_router.on('route:dataset_detail', function(folder_id, dataset_id){
- if (Galaxy.libraries.datasetView){
- Galaxy.libraries.datasetView.$el.unbind('click');
- }
- Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id});
- });
- this.library_router.on('route:dataset_version', function(folder_id, dataset_id, ldda_id){
- if (Galaxy.libraries.datasetView){
- Galaxy.libraries.datasetView.$el.unbind('click');
- }
- Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id, ldda_id: ldda_id, show_version: true});
- });
+ this.library_router.on( 'route:download', function( folder_id, format ) {
+ if ( $( '#folder_list_body' ).find( ':checked' ).length === 0 ) {
+ mod_toastr.info( 'You must select at least one dataset to download' );
+ Galaxy.libraries.library_router.navigate( 'folders/' + folder_id, { trigger: true, replace: true } );
+ } else {
+ Galaxy.libraries.folderToolbarView.download( folder_id, format );
+ Galaxy.libraries.library_router.navigate( 'folders/' + folder_id, { trigger: false, replace: true } );
+ }
+ });
- this.library_router.on('route:dataset_permissions', function(folder_id, dataset_id){
- if (Galaxy.libraries.datasetView){
- Galaxy.libraries.datasetView.$el.unbind('click');
- }
- Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id, show_permissions: true});
- });
+ this.library_router.on( 'route:dataset_detail', function(folder_id, dataset_id){
+ if (Galaxy.libraries.datasetView){
+ Galaxy.libraries.datasetView.$el.unbind('click');
+ }
+ Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id});
+ });
- this.library_router.on('route:library_permissions', function(library_id){
- if (Galaxy.libraries.libraryView){
- Galaxy.libraries.libraryView.$el.unbind('click');
- }
- Galaxy.libraries.libraryView = new mod_library_library_view.LibraryView({id: library_id, show_permissions: true});
- });
+ this.library_router.on( 'route:dataset_version', function(folder_id, dataset_id, ldda_id){
+ if (Galaxy.libraries.datasetView){
+ Galaxy.libraries.datasetView.$el.unbind('click');
+ }
+ Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id, ldda_id: ldda_id, show_version: true});
+ });
- this.library_router.on('route:folder_permissions', function(folder_id){
- if (Galaxy.libraries.folderView){
- Galaxy.libraries.folderView.$el.unbind('click');
- }
- Galaxy.libraries.folderView = new mod_library_folder_view.FolderView({id: folder_id, show_permissions: true});
- });
- this.library_router.on('route:import_datasets', function(folder_id, source){
- if (Galaxy.libraries.folderToolbarView && Galaxy.libraries.folderListView){
- Galaxy.libraries.folderToolbarView.showImportModal({source:source});
- } else {
- Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView({id: folder_id});
- Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView({id: folder_id});
- Galaxy.libraries.folderToolbarView.showImportModal({source: source});
- }
- });
+ this.library_router.on( 'route:dataset_permissions', function(folder_id, dataset_id){
+ if (Galaxy.libraries.datasetView){
+ Galaxy.libraries.datasetView.$el.unbind('click');
+ }
+ Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id, show_permissions: true});
+ });
+
+ this.library_router.on( 'route:library_permissions', function(library_id){
+ if (Galaxy.libraries.libraryView){
+ Galaxy.libraries.libraryView.$el.unbind('click');
+ }
+ Galaxy.libraries.libraryView = new mod_library_library_view.LibraryView({id: library_id, show_permissions: true});
+ });
+
+ this.library_router.on( 'route:folder_permissions', function(folder_id){
+ if (Galaxy.libraries.folderView){
+ Galaxy.libraries.folderView.$el.unbind('click');
+ }
+ Galaxy.libraries.folderView = new mod_library_folder_view.FolderView({id: folder_id, show_permissions: true});
+ });
+
+ this.library_router.on( 'route:import_datasets', function( folder_id, source ){
+ if ( Galaxy.libraries.folderToolbarView && Galaxy.libraries.folderListView ){
+ Galaxy.libraries.folderToolbarView.showImportModal( { source:source } );
+ } else {
+ Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView( { id: folder_id } );
+ Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView( { id: folder_id } );
+ Galaxy.libraries.folderToolbarView.showImportModal( { source: source } );
+ }
+ });
Backbone.history.start({pushState: false});
}
diff -r 5edaafeecfed448ea259adaacf3a4ce6e6da15d1 -r d76253bb22c4f64c73523956d119c0c5ab0a357b client/galaxy/scripts/jq-plugins/ui/filter-control.js
--- /dev/null
+++ b/client/galaxy/scripts/jq-plugins/ui/filter-control.js
@@ -0,0 +1,204 @@
+// from: https://raw.githubusercontent.com/umdjs/umd/master/jqueryPlugin.js
+// Uses AMD or browser globals to create a jQuery plugin.
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], factory);
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+
+}(function ($) {
+ //==============================================================================
+ /**
+ * Template function that produces a bootstrap dropdown to replace the
+ * vanilla HTML select input. Pass in an array of options and an initial selection:
+ * $( '.my-div' ).append( dropDownSelect( [ 'option1', 'option2' ], 'option2' );
+ *
+ * When the user changes the selected option a 'change.dropdown-select' event will
+ * fire with both the jq event and the new selection text as arguments.
+ *
+ * Get the currently selected choice using:
+ * var userChoice = $( '.my-div .dropdown-select .dropdown-select-selected' ).text();
+ *
+ */
+ function dropDownSelect( options, selected ){
+ // replacement for vanilla select element using bootstrap dropdowns instead
+ selected = selected || (( !_.isEmpty( options ) )?( options[0] ):( '' ));
+ var $select = $([
+ '<div class="dropdown-select btn-group">',
+ '<button type="button" class="btn btn-default">',
+ '<span class="dropdown-select-selected">' + selected + '</span>',
+ '</button>',
+ '</div>'
+ ].join( '\n' ));
+
+ // if there's only one option, do not style/create as buttons, dropdown - use simple span
+ // otherwise, a dropdown displaying the current selection
+ if( options && options.length > 1 ){
+ $select.find( 'button' )
+ .addClass( 'dropdown-toggle' ).attr( 'data-toggle', 'dropdown' )
+ .append( ' <span class="caret"></span>' );
+ $select.append([
+ '<ul class="dropdown-menu" role="menu">',
+ _.map( options, function( option ){
+ return [
+ '<li><a href="javascript:void(0)">', option, '</a></li>'
+ ].join( '' );
+ }).join( '\n' ),
+ '</ul>'
+ ].join( '\n' ));
+ }
+
+ // trigger 'change.dropdown-select' when a new selection is made using the dropdown
+ function selectThis( event ){
+ var $this = $( this ),
+ $select = $this.parents( '.dropdown-select' ),
+ newSelection = $this.text();
+ $select.find( '.dropdown-select-selected' ).text( newSelection );
+ $select.trigger( 'change.dropdown-select', newSelection );
+ }
+
+ $select.find( 'a' ).click( selectThis );
+ return $select;
+ }
+
+ //==============================================================================
+ /**
+ * Creates a three part bootstrap button group (key, op, value) meant to
+ * allow the user control of filters (e.g. { key: 'name', op: 'contains', value: 'my_history' })
+ *
+ * Each field uses a dropDownSelect (from ui.js) to allow selection
+ * (with the 'value' field appearing as an input when set to do so).
+ *
+ * Any change or update in any of the fields will trigger a 'change.filter-control'
+ * event which will be passed an object containing those fields (as the example above).
+ *
+ * Pass in an array of possible filter objects to control what the user can select.
+ * Each filter object should have:
+ * key : generally the attribute name on which to filter something
+ * ops : an array of 1 or more filter operations (e.g. [ 'is', '<', 'contains', '!=' ])
+ * values (optional) : an array of possible values for the filter (e.g. [ 'true', 'false' ])
+ * @example:
+ * $( '.my-div' ).filterControl({
+ * filters : [
+ * { key: 'name', ops: [ 'is exactly', 'contains' ] }
+ * { key: 'deleted', ops: [ 'is' ], values: [ 'true', 'false' ] }
+ * ]
+ * });
+ * // after initialization, you can prog. get the current value using:
+ * $( '.my-div' ).filterControl( 'val' )
+ *
+ */
+ function FilterControl( element, options ){
+ return this.init( element, options );
+ }
+ /** the data key that this object will be stored under in the DOM element */
+ FilterControl.prototype.DATA_KEY = 'filter-control';
+
+ /** parses options, sets up instance vars, and does initial render */
+ FilterControl.prototype.init = function _init( element, options ){
+ options = options || { filters: [] };
+ this.$element = $( element ).addClass( 'filter-control btn-group' );
+ this.options = jQuery.extend( true, {}, this.defaults, options );
+
+ this.currFilter = this.options.filters[0];
+ return this.render();
+ };
+
+ /** render (or re-render) the controls on the element */
+ FilterControl.prototype.render = function _render(){
+ this.$element.empty()
+ .append([ this._renderKeySelect(), this._renderOpSelect(), this._renderValueInput() ]);
+ return this;
+ };
+
+ /** render the key dropDownSelect, bind a change event to it, and return it */
+ FilterControl.prototype._renderKeySelect = function __renderKeySelect(){
+ var filterControl = this;
+ var keys = this.options.filters.map( function( filter ){
+ return filter.key;
+ });
+ this.$keySelect = dropDownSelect( keys, this.currFilter.key )
+ .addClass( 'filter-control-key' )
+ .on( 'change.dropdown-select', function( event, selection ){
+ filterControl.currFilter = _.findWhere( filterControl.options.filters, { key: selection });
+ // when the filter/key changes, re-render the control entirely
+ filterControl.render()._triggerChange();
+ });
+ return this.$keySelect;
+ };
+
+ /** render the op dropDownSelect, bind a change event to it, and return it */
+ FilterControl.prototype._renderOpSelect = function __renderOpSelect(){
+ var filterControl = this,
+ ops = this.currFilter.ops;
+ //TODO: search for currOp in avail. ops: use that for selected if there; otherwise: first op
+ this.$opSelect = dropDownSelect( ops, ops[0] )
+ .addClass( 'filter-control-op' )
+ .on( 'change.dropdown-select', function( event, selection ){
+ filterControl._triggerChange();
+ });
+ return this.$opSelect;
+ };
+
+ /** render the value control, bind a change event to it, and return it */
+ FilterControl.prototype._renderValueInput = function __renderValueInput(){
+ var filterControl = this;
+ // if a values attribute is prov. on the filter - make this a dropdown; otherwise, use an input
+ if( this.currFilter.values ){
+ this.$valueSelect = dropDownSelect( this.currFilter.values, this.currFilter.values[0] )
+ .on( 'change.dropdown-select', function( event, selection ){
+ filterControl._triggerChange();
+ });
+ } else {
+ //TODO: allow setting a value type (mainly for which html5 input to use: range, number, etc.)
+ this.$valueSelect = $( '<input/>' ).addClass( 'form-control' )
+ .on( 'change', function( event, value ){
+ filterControl._triggerChange();
+ });
+ }
+ this.$valueSelect.addClass( 'filter-control-value' );
+ return this.$valueSelect;
+ };
+
+ /** return the current state/setting for the filter as a three key object: key, op, value */
+ FilterControl.prototype.val = function _val(){
+ var key = this.$element.find( '.filter-control-key .dropdown-select-selected' ).text(),
+ op = this.$element.find( '.filter-control-op .dropdown-select-selected' ).text(),
+ // handle either a dropdown or plain input
+ $value = this.$element.find( '.filter-control-value' ),
+ value = ( $value.hasClass( 'dropdown-select' ) )?( $value.find( '.dropdown-select-selected' ).text() )
+ :( $value.val() );
+ return { key: key, op: op, value: value };
+ };
+
+ // single point of change for change event
+ FilterControl.prototype._triggerChange = function __triggerChange(){
+ this.$element.trigger( 'change.filter-control', this.val() );
+ };
+
+ // as jq plugin
+ jQuery.fn.extend({
+ filterControl : function $filterControl( options ){
+ var nonOptionsArgs = jQuery.makeArray( arguments ).slice( 1 );
+ return this.map( function(){
+ var $this = $( this ),
+ data = $this.data( FilterControl.prototype.DATA_KEY );
+
+ if( jQuery.type( options ) === 'object' ){
+ data = new FilterControl( $this, options );
+ $this.data( FilterControl.prototype.DATA_KEY, data );
+ }
+ if( data && jQuery.type( options ) === 'string' ){
+ var fn = data[ options ];
+ if( jQuery.type( fn ) === 'function' ){
+ return fn.apply( data, nonOptionsArgs );
+ }
+ }
+ return this;
+ });
+ }
+ });
+}));
diff -r 5edaafeecfed448ea259adaacf3a4ce6e6da15d1 -r d76253bb22c4f64c73523956d119c0c5ab0a357b client/galaxy/scripts/jq-plugins/ui/mode-button.js
--- /dev/null
+++ b/client/galaxy/scripts/jq-plugins/ui/mode-button.js
@@ -0,0 +1,191 @@
+// from: https://raw.githubusercontent.com/umdjs/umd/master/jqueryPlugin.js
+// Uses AMD or browser globals to create a jQuery plugin.
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ //TODO: So...this turns out to be an all or nothing thing. If I load jQuery in the define below, it will
+ // (of course) wipe the old jquery *and all the plugins loaded into it*. So the define below *is still
+ // relying on jquery being loaded globally* in order to preserve plugins.
+ define([], factory);
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+
+}(function () {
+
+ /** Multi 'mode' button (or any element really) that changes the html
+ * contents of itself when clicked. Pass in an ordered list of
+ * objects with 'html' and (optional) onclick functions.
+ *
+ * When clicked in a particular node, the onclick function will
+ * be called (with the element as this) and the element will
+ * switch to the next mode, replacing its html content with
+ * that mode's html.
+ *
+ * If there is no next mode, the element will switch back to
+ * the first mode.
+ * @example:
+ * $( '.myElement' ).modeButton({
+ * modes : [
+ * {
+ * mode: 'bler',
+ * html: '<h5>Bler</h5>',
+ * onclick : function(){
+ * $( 'body' ).css( 'background-color', 'red' );
+ * }
+ * },
+ * {
+ * mode: 'bloo',
+ * html: '<h4>Bloo</h4>',
+ * onclick : function(){
+ * $( 'body' ).css( 'background-color', 'blue' );
+ * }
+ * },
+ * {
+ * mode: 'blah',
+ * html: '<h3>Blah</h3>',
+ * onclick : function(){
+ * $( 'body' ).css( 'background-color', 'grey' );
+ * }
+ * },
+ * ]
+ * });
+ * $( '.myElement' ).modeButton( 'callModeFn', 'bler' );
+ */
+ /** constructor */
+ function ModeButton( element, options ){
+ this.currModeIndex = 0;
+ return this._init( element, options );
+ }
+
+ /** html5 data key to store this object inside an element */
+ ModeButton.prototype.DATA_KEY = 'mode-button';
+ /** default options */
+ ModeButton.prototype.defaults = {
+ switchModesOnClick : true
+ };
+
+ // ---- private interface
+ /** set up options, intial mode, and the click handler */
+ ModeButton.prototype._init = function _init( element, options ){
+ //console.debug( 'ModeButton._init:', element, options );
+ options = options || {};
+ this.$element = $( element );
+ this.options = $.extend( true, {}, this.defaults, options );
+ if( !options.modes ){
+ throw new Error( 'ModeButton requires a "modes" array' );
+ }
+
+ var modeButton = this;
+ this.$element.click( function _ModeButtonClick( event ){
+ // call the curr mode fn
+ modeButton.callModeFn();
+ // inc the curr mode index
+ if( modeButton.options.switchModesOnClick ){ modeButton._incModeIndex(); }
+ // set the element html
+ $( this ).html( modeButton.options.modes[ modeButton.currModeIndex ].html );
+ });
+ return this.reset();
+ };
+ /** increment the mode index to the next in the array, looping back to zero if at the last */
+ ModeButton.prototype._incModeIndex = function _incModeIndex(){
+ this.currModeIndex += 1;
+ if( this.currModeIndex >= this.options.modes.length ){
+ this.currModeIndex = 0;
+ }
+ return this;
+ };
+ /** get the mode index in the modes array for the given key (mode name) */
+ ModeButton.prototype._getModeIndex = function _getModeIndex( modeKey ){
+ for( var i=0; i<this.options.modes.length; i+=1 ){
+ if( this.options.modes[ i ].mode === modeKey ){ return i; }
+ }
+ throw new Error( 'mode not found: ' + modeKey );
+ };
+ /** set the current mode to the one with the given index and set button html */
+ ModeButton.prototype._setModeByIndex = function _setModeByIndex( index ){
+ var newMode = this.options.modes[ index ];
+ if( !newMode ){
+ throw new Error( 'mode index not found: ' + index );
+ }
+ this.currModeIndex = index;
+ if( newMode.html ){
+ this.$element.html( newMode.html );
+ }
+ return this;
+ };
+
+ // ---- public interface
+ /** get the current mode object (not just the mode name) */
+ ModeButton.prototype.currentMode = function currentMode(){
+ return this.options.modes[ this.currModeIndex ];
+ };
+ /** return the mode key of the current mode */
+ ModeButton.prototype.current = function current(){
+ // sugar for returning mode name
+ return this.currentMode().mode;
+ };
+ /** get the mode with the given modeKey or the current mode if modeKey is undefined */
+ ModeButton.prototype.getMode = function getMode( modeKey ){
+ if( !modeKey ){ return this.currentMode(); }
+ return this.options.modes[( this._getModeIndex( modeKey ) )];
+ };
+ /** T/F if the button has the given mode */
+ ModeButton.prototype.hasMode = function hasMode( modeKey ){
+ try {
+ return !!this.getMode( modeKey );
+ } catch( err ){}
+ return false;
+ };
+ /** set the current mode to the mode with the given name */
+ ModeButton.prototype.setMode = function setMode( modeKey ){
+ return this._setModeByIndex( this._getModeIndex( modeKey ) );
+ };
+ /** reset to the initial mode */
+ ModeButton.prototype.reset = function reset(){
+ this.currModeIndex = 0;
+ if( this.options.initialMode ){
+ this.currModeIndex = this._getModeIndex( this.options.initialMode );
+ }
+ return this._setModeByIndex( this.currModeIndex );
+ };
+ /** manually call the click handler of the given mode */
+ ModeButton.prototype.callModeFn = function callModeFn( modeKey ){
+ var modeFn = this.getMode( modeKey ).onclick;
+ if( modeFn && $.type( modeFn === 'function' ) ){
+ // call with the element as context (std jquery pattern)
+ return modeFn.call( this.$element.get(0) );
+ }
+ return undefined;
+ };
+
+ // as jq plugin
+ $.fn.modeButton = function $modeButton( options ){
+ if( !this.size() ){ return this; }
+
+ //TODO: does map still work with jq multi selection (i.e. $( '.class-for-many-btns' ).modeButton)?
+ if( $.type( options ) === 'object' ){
+ return this.map( function(){
+ var $this = $( this );
+ $this.data( 'mode-button', new ModeButton( $this, options ) );
+ return this;
+ });
+ }
+
+ var $first = $( this[0] ),
+ button = $first.data( 'mode-button' );
+
+ if( !button ){
+ throw new Error( 'modeButton needs an options object or string name of a function' );
+ }
+
+ if( button && $.type( options ) === 'string' ){
+ var fnName = options;
+ if( button && $.type( button[ fnName ] ) === 'function' ){
+ return button[ fnName ].apply( button, $.makeArray( arguments ).slice( 1 ) );
+ }
+ }
+ return button;
+ };
+
+}));
diff -r 5edaafeecfed448ea259adaacf3a4ce6e6da15d1 -r d76253bb22c4f64c73523956d119c0c5ab0a357b client/galaxy/scripts/jq-plugins/ui/pagination.js
--- /dev/null
+++ b/client/galaxy/scripts/jq-plugins/ui/pagination.js
@@ -0,0 +1,226 @@
+// from: https://raw.githubusercontent.com/umdjs/umd/master/jqueryPlugin.js
+// Uses AMD or browser globals to create a jQuery plugin.
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], factory);
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+
+}(function ($) {
+
+ /** Builds (twitter bootstrap styled) pagination controls.
+ * If the totalDataSize is not null, a horizontal list of page buttons is displayed.
+ * If totalDataSize is null, two links ('Prev' and 'Next) are displayed.
+ * When pages are changed, a 'pagination.page-change' event is fired
+ * sending the event and the (0-based) page requested.
+ */
+ function Pagination( element, options ){
+ /** the total number of pages */
+ this.numPages = null;
+ /** the current, active page */
+ this.currPage = 0;
+ return this.init( element, options );
+ }
+
+ /** data key under which this object will be stored in the element */
+ Pagination.prototype.DATA_KEY = 'pagination';
+ /** default options */
+ Pagination.prototype.defaults = {
+ /** which page to begin at */
+ startingPage : 0,
+ /** number of data per page */
+ perPage : 20,
+ /** the total number of data (null == unknown) */
+ totalDataSize : null,
+ /** size of current data on current page */
+ currDataSize : null
+ };
+
+ /** init the control, calc numPages if possible, and render
+ * @param {jQuery} the element that will contain the pagination control
+ * @param {Object} options a map containing overrides to the pagination default options
+ */
+ Pagination.prototype.init = function _init( $element, options ){
+ options = options || {};
+ this.$element = $element;
+ this.options = jQuery.extend( true, {}, this.defaults, options );
+
+ this.currPage = this.options.startingPage;
+ if( this.options.totalDataSize !== null ){
+ this.numPages = Math.ceil( this.options.totalDataSize / this.options.perPage );
+ // limit currPage by numPages
+ if( this.currPage >= this.numPages ){
+ this.currPage = this.numPages - 1;
+ }
+ }
+ //console.debug( 'Pagination.prototype.init:', this.$element, this.currPage );
+ //console.debug( JSON.stringify( this.options ) );
+
+ // bind to data of element
+ this.$element.data( Pagination.prototype.DATA_KEY, this );
+
+ this._render();
+ return this;
+ };
+
+ /** helper to create a simple li + a combo */
+ function _make$Li( contents ){
+ return $([
+ '<li><a href="javascript:void(0);">', contents, '</a></li>'
+ ].join( '' ));
+ }
+
+ /** render previous and next pagination buttons */
+ Pagination.prototype._render = function __render(){
+ // no data - no pagination
+ if( this.options.totalDataSize === 0 ){ return this; }
+ // only one page
+ if( this.numPages === 1 ){ return this; }
+
+ // when the number of pages are known, render each page as a link
+ if( this.numPages > 0 ){
+ this._renderPages();
+ this._scrollToActivePage();
+
+ // when the number of pages is not known, render previous or next
+ } else {
+ this._renderPrevNext();
+ }
+ return this;
+ };
+
+ /** render previous and next pagination buttons */
+ Pagination.prototype._renderPrevNext = function __renderPrevNext(){
+ var pagination = this,
+ $prev = _make$Li( 'Prev' ),
+ $next = _make$Li( 'Next' ),
+ $paginationContainer = $( '<ul/>' ).addClass( 'pagination pagination-prev-next' );
+
+ // disable if it either end
+ if( this.currPage === 0 ){
+ $prev.addClass( 'disabled' );
+ } else {
+ $prev.click( function(){ pagination.prevPage(); });
+ }
+ if( ( this.numPages && this.currPage === ( this.numPages - 1 ) )
+ || ( this.options.currDataSize && this.options.currDataSize < this.options.perPage ) ){
+ $next.addClass( 'disabled' );
+ } else {
+ $next.click( function(){ pagination.nextPage(); });
+ }
+
+ this.$element.html( $paginationContainer.append([ $prev, $next ]) );
+ //console.debug( this.$element, this.$element.html() );
+ return this.$element;
+ };
+
+ /** render page links for each possible page (if we can) */
+ Pagination.prototype._renderPages = function __renderPages(){
+ // it's better to scroll the control and let the user see all pages
+ // than to force her/him to change pages in order to find the one they want (as traditional << >> does)
+ var pagination = this,
+ $scrollingContainer = $( '<div>' ).addClass( 'pagination-scroll-container' ),
+ $paginationContainer = $( '<ul/>' ).addClass( 'pagination pagination-page-list' ),
+ page$LiClick = function( ev ){
+ pagination.goToPage( $( this ).data( 'page' ) );
+ };
+
+ for( var i=0; i<this.numPages; i+=1 ){
+ // add html5 data tag 'page' for later click event handler use
+ var $pageLi = _make$Li( i + 1 ).attr( 'data-page', i ).click( page$LiClick );
+ // highlight the current page
+ if( i === this.currPage ){
+ $pageLi.addClass( 'active' );
+ }
+ //console.debug( '\t', $pageLi );
+ $paginationContainer.append( $pageLi );
+ }
+ return this.$element.html( $scrollingContainer.html( $paginationContainer ) );
+ };
+
+ /** scroll scroll-container (if any) to show the active page */
+ Pagination.prototype._scrollToActivePage = function __scrollToActivePage(){
+ // scroll to show active page in center of scrollable area
+ var $container = this.$element.find( '.pagination-scroll-container' );
+ // no scroll container : don't scroll
+ if( !$container.size() ){ return this; }
+
+ var $activePage = this.$element.find( 'li.active' ),
+ midpoint = $container.width() / 2;
+ //console.debug( $container, $activePage, midpoint );
+ $container.scrollLeft( $container.scrollLeft() + $activePage.position().left - midpoint );
+ return this;
+ };
+
+ /** go to a certain page */
+ Pagination.prototype.goToPage = function goToPage( page ){
+ if( page <= 0 ){ page = 0; }
+ if( this.numPages && page >= this.numPages ){ page = this.numPages - 1; }
+ if( page === this.currPage ){ return this; }
+
+ //console.debug( '\t going to page ' + page )
+ this.currPage = page;
+ this.$element.trigger( 'pagination.page-change', this.currPage );
+ //console.info( 'pagination:page-change', this.currPage );
+ this._render();
+ return this;
+ };
+
+ /** go to the previous page */
+ Pagination.prototype.prevPage = function prevPage(){
+ return this.goToPage( this.currPage - 1 );
+ };
+
+ /** go to the next page */
+ Pagination.prototype.nextPage = function nextPage(){
+ return this.goToPage( this.currPage + 1 );
+ };
+
+ /** return the current page */
+ Pagination.prototype.page = function page(){
+ return this.currPage;
+ };
+
+ // alternate constructor invocation
+ Pagination.create = function _create( $element, options ){
+ return new Pagination( $element, options );
+ };
+
+ // as jq plugin
+ jQuery.fn.extend({
+ pagination : function $pagination( options ){
+ var nonOptionsArgs = jQuery.makeArray( arguments ).slice( 1 );
+
+ // if passed an object - use that as an options map to create pagination for each selected
+ if( jQuery.type( options ) === 'object' ){
+ return this.map( function(){
+ Pagination.create( $( this ), options );
+ return this;
+ });
+ }
+
+ // (other invocations only work on the first element in selected)
+ var $firstElement = $( this[0] ),
+ previousControl = $firstElement.data( Pagination.prototype.DATA_KEY );
+ // if a pagination control was found for this element, either...
+ if( previousControl ){
+ // invoke a function on the pagination object if passed a string (the function name)
+ if( jQuery.type( options ) === 'string' ){
+ var fn = previousControl[ options ];
+ if( jQuery.type( fn ) === 'function' ){
+ return fn.apply( previousControl, nonOptionsArgs );
+ }
+
+ // if passed nothing, return the previously set control
+ } else {
+ return previousControl;
+ }
+ }
+ // if there is no control already set, return undefined
+ return undefined;
+ }
+ });
+}));
diff -r 5edaafeecfed448ea259adaacf3a4ce6e6da15d1 -r d76253bb22c4f64c73523956d119c0c5ab0a357b client/galaxy/scripts/jq-plugins/ui/peek-column-selector.js
--- /dev/null
+++ b/client/galaxy/scripts/jq-plugins/ui/peek-column-selector.js
@@ -0,0 +1,317 @@
+// from: https://raw.githubusercontent.com/umdjs/umd/master/jqueryPlugin.js
+// Uses AMD or browser globals to create a jQuery plugin.
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], factory);
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+
+}(function ($) {
+ //==============================================================================
+ /** Column selection using the peek display as the control.
+ * Adds rows to the bottom of the peek with clickable areas in each cell
+ * to allow the user to select columns.
+ * Column selection can be limited to a single column or multiple.
+ * (Optionally) adds a left hand column of column selection prompts.
+ * (Optionally) allows the column headers to be clicked/renamed
+ * and set to some initial value.
+ * (Optionally) hides comment rows.
+ * (Optionally) allows pre-selecting and disabling certain columns for
+ * each row control.
+ *
+ * Construct by selecting a peek table to be used with jQuery and
+ * calling 'peekColumnSelector' with options.
+ * Options must include a 'controls' array and can include other options
+ * listed below.
+ * @example:
+ * $( 'pre.peek' ).peekColumnSelector({
+ * columnNames : ["Chromosome", "Start", "Base", "", "", "Qual" ],
+ * controls : [
+ * { label: 'X Column', id: 'xColumn' },
+ * { label: 'Y Column', id: 'yColumn', selected: 2 },
+ * { label: 'ID Column', id: 'idColumn', selected: 4, disabled: [ 1, 5 ] },
+ * { label: 'Heatmap', id: 'heatmap', selected: [ 2, 4 ], disabled: [ 0, 1 ], multiselect: true,
+ * selectedText: 'Included', unselectedText: 'Excluded' }
+ * ],
+ * renameColumns : true,
+ * hideCommentRows : true,
+ * includePrompts : true,
+ * topLeftContent : 'Data sample:'
+ * }).on( 'peek-column-selector.change', function( ev, selection ){
+ * console.info( 'new selection:', selection );
+ * //{ yColumn: 2 }
+ * }).on( 'peek-column-selector.rename', function( ev, names ){
+ * console.info( 'column names', names );
+ * //[ 'Bler', 'Start', 'Base', '', '', 'Qual' ]
+ * });
+ *
+ * An event is fired when column selection is changed and the event
+ * is passed an object in the form: { the row id : the new selection value }.
+ * An event is also fired when the table headers are re-named and
+ * is passed the new array of column names.
+ */
+
+ /** option defaults */
+ var defaults = {
+ /** does this control allow renaming headers? */
+ renameColumns : false,
+ /** does this control allow renaming headers? */
+ columnNames : [],
+ /** the comment character used by the peek's datatype */
+ commentChar : '#',
+ /** should comment rows be shown or hidden in the peek */
+ hideCommentRows : false,
+ /** should a column of row control prompts be used */
+ includePrompts : true,
+ /** what is the content of the top left cell (often a title) */
+ topLeftContent : 'Columns:'
+ },
+ /** class added to the pre.peek element (to allow css on just the control) */
+ PEEKCONTROL_CLASS = 'peek-column-selector',
+ /** the string of the event fired when a control row changes */
+ CHANGE_EVENT = 'peek-column-selector.change',
+ /** the string of the event fired when a column is renamed */
+ RENAME_EVENT = 'peek-column-selector.rename',
+ /** class added to the control rows */
+ ROW_CLASS = 'control',
+ /** class added to the left-hand cells that serve as row prompts */
+ PROMPT_CLASS = 'control-prompt',
+ /** class added to selected _cells_/tds */
+ SELECTED_CLASS = 'selected',
+ /** class added to disabled/un-clickable cells/tds */
+ DISABLED_CLASS = 'disabled',
+ /** class added to the clickable surface within a cell to select it */
+ BUTTON_CLASS = 'button',
+ /** class added to peek table header (th) cells to indicate they can be clicked and are renamable */
+ RENAMABLE_HEADER_CLASS = 'renamable-header',
+ /** the data key used for each cell to store the column index ('data-...') */
+ COLUMN_INDEX_DATA_KEY = 'column-index',
+ /** renamable header data key used to store the column name (w/o the number and dot: '1.Bler') */
+ COLUMN_NAME_DATA_KEY = 'column-name';
+
+ //TODO: not happy with pure functional here - rows should polymorph (multi, single, etc.)
+ //TODO: needs clean up, move handlers to outer scope
+
+ // ........................................................................
+ /** validate the control data sent in for each row */
+ function validateControl( control ){
+ if( control.disabled && jQuery.type( control.disabled ) !== 'array' ){
+ throw new Error( '"disabled" must be defined as an array of indeces: ' + JSON.stringify( control ) );
+ }
+ if( control.multiselect && control.selected && jQuery.type( control.selected ) !== 'array' ){
+ throw new Error( 'Mulitselect rows need an array for "selected": ' + JSON.stringify( control ) );
+ }
+ if( !control.label || !control.id ){
+ throw new Error( 'Peek controls need a label and id for each control row: ' + JSON.stringify( control ) );
+ }
+ if( control.disabled && control.disabled.indexOf( control.selected ) !== -1 ){
+ throw new Error( 'Selected column is in the list of disabled columns: ' + JSON.stringify( control ) );
+ }
+ return control;
+ }
+
+ /** build the inner control surface (i.e. button-like) */
+ function buildButton( control, columnIndex ){
+ return $( '<div/>' ).addClass( BUTTON_CLASS ).text( control.label );
+ }
+
+ /** build the basic (shared) cell structure */
+ function buildControlCell( control, columnIndex ){
+ var $td = $( '<td/>' )
+ .html( buildButton( control, columnIndex ) )
+ .attr( 'data-' + COLUMN_INDEX_DATA_KEY, columnIndex );
+
+ // disable if index in disabled array
+ if( control.disabled && control.disabled.indexOf( columnIndex ) !== -1 ){
+ $td.addClass( DISABLED_CLASS );
+ }
+ return $td;
+ }
+
+ /** set the text of the control based on selected/un */
+ function setSelectedText( $cell, control, columnIndex ){
+ var $button = $cell.children( '.' + BUTTON_CLASS );
+ if( $cell.hasClass( SELECTED_CLASS ) ){
+ $button.html( ( control.selectedText !== undefined )?( control.selectedText ):( control.label ) );
+ } else {
+ $button.html( ( control.unselectedText !== undefined )?( control.unselectedText ):( control.label ) );
+ }
+ }
+
+ /** build a cell for a row that only allows one selection */
+ function buildSingleSelectCell( control, columnIndex ){
+ // only one selection - selected is single index
+ var $cell = buildControlCell( control, columnIndex );
+ if( control.selected === columnIndex ){
+ $cell.addClass( SELECTED_CLASS );
+ }
+ setSelectedText( $cell, control, columnIndex );
+
+ // only add the handler to non-disabled controls
+ if( !$cell.hasClass( DISABLED_CLASS ) ){
+ $cell.click( function selectClick( ev ){
+ var $cell = $( this );
+ // don't re-select or fire event if already selected
+ if( !$cell.hasClass( SELECTED_CLASS ) ){
+ // only one can be selected - remove selected on all others, add it here
+ var $otherSelected = $cell.parent().children( '.' + SELECTED_CLASS ).removeClass( SELECTED_CLASS );
+ $otherSelected.each( function(){
+ setSelectedText( $( this ), control, columnIndex );
+ });
+
+ $cell.addClass( SELECTED_CLASS );
+ setSelectedText( $cell, control, columnIndex );
+
+ // fire the event from the table itself, passing the id and index of selected
+ var eventData = {},
+ key = $cell.parent().attr( 'id' ),
+ val = $cell.data( COLUMN_INDEX_DATA_KEY );
+ eventData[ key ] = val;
+ $cell.parents( '.peek' ).trigger( CHANGE_EVENT, eventData );
+ }
+ });
+ }
+ return $cell;
+ }
+
+ /** build a cell for a row that allows multiple selections */
+ function buildMultiSelectCell( control, columnIndex ){
+ var $cell = buildControlCell( control, columnIndex );
+ // multiple selection - selected is an array
+ if( control.selected && control.selected.indexOf( columnIndex ) !== -1 ){
+ $cell.addClass( SELECTED_CLASS );
+ }
+ setSelectedText( $cell, control, columnIndex );
+
+ // only add the handler to non-disabled controls
+ if( !$cell.hasClass( DISABLED_CLASS ) ){
+ $cell.click( function multiselectClick( ev ){
+ var $cell = $( this );
+ // can be more than one selected - toggle selected on this cell
+ $cell.toggleClass( SELECTED_CLASS );
+ setSelectedText( $cell, control, columnIndex );
+ var selectedColumnIndeces = $cell.parent().find( '.' + SELECTED_CLASS ).map( function( i, e ){
+ return $( e ).data( COLUMN_INDEX_DATA_KEY );
+ });
+ // fire the event from the table itself, passing the id and index of selected
+ var eventData = {},
+ key = $cell.parent().attr( 'id' ),
+ val = jQuery.makeArray( selectedColumnIndeces );
+ eventData[ key ] = val;
+ $cell.parents( '.peek' ).trigger( CHANGE_EVENT, eventData );
+ });
+ }
+ return $cell;
+ }
+
+ /** iterate over columns in peek and create a control for each */
+ function buildControlCells( count, control ){
+ var $cells = [];
+ // build a control for each column - using a build fn based on control
+ for( var columnIndex=0; columnIndex<count; columnIndex+=1 ){
+ $cells.push( control.multiselect? buildMultiSelectCell( control, columnIndex )
+ : buildSingleSelectCell( control, columnIndex ) );
+ }
+ return $cells;
+ }
+
+ /** build a row of controls for the peek */
+ function buildControlRow( cellCount, control, includePrompts ){
+ var $controlRow = $( '<tr/>' ).attr( 'id', control.id ).addClass( ROW_CLASS );
+ if( includePrompts ){
+ var $promptCell = $( '<td/>' ).addClass( PROMPT_CLASS ).text( control.label + ':' );
+ $controlRow.append( $promptCell );
+ }
+ $controlRow.append( buildControlCells( cellCount, control ) );
+ return $controlRow;
+ }
+
+ // ........................................................................
+ /** add to the peek, using options for configuration, return the peek */
+ function peekColumnSelector( options ){
+ options = jQuery.extend( true, {}, defaults, options );
+
+ var $peek = $( this ).addClass( PEEKCONTROL_CLASS ),
+ $peektable = $peek.find( 'table' ),
+ // get the size of the tables - width and height, number of comment rows
+ columnCount = $peektable.find( 'th' ).size(),
+ rowCount = $peektable.find( 'tr' ).size(),
+ // get the rows containing text starting with the comment char (also make them grey)
+ $commentRows = $peektable.find( 'td[colspan]' ).map( function( e, i ){
+ var $this = $( this );
+ if( $this.text() && $this.text().match( new RegExp( '^' + options.commentChar ) ) ){
+ return $( this ).css( 'color', 'grey' ).parent().get(0);
+ }
+ return null;
+ });
+
+ // should comment rows in the peek be hidden?
+ if( options.hideCommentRows ){
+ $commentRows.hide();
+ rowCount -= $commentRows.size();
+ }
+ //console.debug( 'rowCount:', rowCount, 'columnCount:', columnCount, '$commentRows:', $commentRows );
+
+ // should a first column of control prompts be added?
+ if( options.includePrompts ){
+ var $topLeft = $( '<th/>' ).addClass( 'top-left' ).text( options.topLeftContent )
+ .attr( 'rowspan', rowCount );
+ $peektable.find( 'tr' ).first().prepend( $topLeft );
+ }
+
+ // save either the options column name or the parsed text of each column header in html5 data attr and text
+ var $headers = $peektable.find( 'th:not(.top-left)' ).each( function( i, e ){
+ var $this = $( this ),
+ // can be '1.name' or '1'
+ text = $this.text().replace( /^\d+\.*/, '' ),
+ name = options.columnNames[ i ] || text;
+ $this.attr( 'data-' + COLUMN_NAME_DATA_KEY, name )
+ .text( ( i + 1 ) + (( name )?( '.' + name ):( '' )) );
+ });
+
+ // allow renaming of columns when the header is clicked
+ if( options.renameColumns ){
+ $headers.addClass( RENAMABLE_HEADER_CLASS )
+ .click( function renameColumn(){
+ // prompt for new name
+ var $this = $( this ),
+ index = $this.index() + ( options.includePrompts? 0: 1 ),
+ prevName = $this.data( COLUMN_NAME_DATA_KEY ),
+ newColumnName = prompt( 'New column name:', prevName );
+ if( newColumnName !== null && newColumnName !== prevName ){
+ // set the new text and data
+ $this.text( index + ( newColumnName?( '.' + newColumnName ):'' ) )
+ .data( COLUMN_NAME_DATA_KEY, newColumnName )
+ .attr( 'data-', COLUMN_NAME_DATA_KEY, newColumnName );
+ // fire event for new column names
+ var columnNames = jQuery.makeArray(
+ $this.parent().children( 'th:not(.top-left)' ).map( function(){
+ return $( this ).data( COLUMN_NAME_DATA_KEY );
+ }));
+ $this.parents( '.peek' ).trigger( RENAME_EVENT, columnNames );
+ }
+ });
+ }
+
+ // build a row for each control
+ options.controls.forEach( function( control, i ){
+ validateControl( control );
+ var $controlRow = buildControlRow( columnCount, control, options.includePrompts );
+ $peektable.find( 'tbody' ).append( $controlRow );
+ });
+ return this;
+ }
+
+ // ........................................................................
+ // as jq plugin
+ jQuery.fn.extend({
+ peekColumnSelector : function $peekColumnSelector( options ){
+ return this.map( function(){
+ return peekColumnSelector.call( this, options );
+ });
+ }
+ });
+}));
diff -r 5edaafeecfed448ea259adaacf3a4ce6e6da15d1 -r d76253bb22c4f64c73523956d119c0c5ab0a357b client/galaxy/scripts/mvc/history/multi-panel.js
--- a/client/galaxy/scripts/mvc/history/multi-panel.js
+++ b/client/galaxy/scripts/mvc/history/multi-panel.js
@@ -2,7 +2,8 @@
"mvc/history/history-model",
"mvc/history/history-panel-edit",
"mvc/base-mvc",
- "utils/ajax-queue"
+ "utils/ajax-queue",
+ "jq-plugins/ui/mode-button"
], function( HISTORY_MODEL, HPANEL_EDIT, baseMVC, ajaxQueue ){
window.HISTORY_MODEL = HISTORY_MODEL;
//==============================================================================
diff -r 5edaafeecfed448ea259adaacf3a4ce6e6da15d1 -r d76253bb22c4f64c73523956d119c0c5ab0a357b client/galaxy/scripts/mvc/library/library-folderlist-view.js
--- a/client/galaxy/scripts/mvc/library/library-folderlist-view.js
+++ b/client/galaxy/scripts/mvc/library/library-folderlist-view.js
@@ -15,322 +15,359 @@
) {
var FolderListView = Backbone.View.extend({
- el : '#folder_items_element',
- defaults: {
- 'include_deleted' : false
- },
- // progress percentage
- progress: 0,
- // progress rate per one item
- progressStep: 1,
- // self modal
- modal : null,
+ el : '#folder_items_element',
+ // progress percentage
+ progress: 0,
+ // progress rate per one item
+ progressStep: 1,
- folderContainer: null,
+ folderContainer: null,
- sort: 'asc',
+ sort: 'asc',
- events: {
- 'click #select-all-checkboxes' : 'selectAll',
- 'click .dataset_row' : 'selectClickedRow',
- 'click .sort-folder-link' : 'sortColumnClicked'
- },
+ events: {
+ 'click #select-all-checkboxes' : 'selectAll',
+ 'click .dataset_row' : 'selectClickedRow',
+ 'click .sort-folder-link' : 'sortColumnClicked'
+ },
- // cache of rendered views
- rowViews: {},
-
- initialize : function(options){
- this.options = _.defaults(this.options || {}, options);
- this.fetchFolder();
- },
+ collection: null,
- fetchFolder: function(options){
- var options = options || {};
- this.options.include_deleted = options.include_deleted;
- var that = this;
+ defaults: {
+ include_deleted: false,
+ page_count: null,
+ show_page: null
+ },
- this.collection = new mod_library_model.Folder();
+ /**
+ * Initialize and fetch the folder from the server.
+ * @param {object} options an object with options
+ */
+ initialize : function( options ){
+ this.options = _.defaults( this.options || {}, this.defaults, options );
+ this.modal = null;
+ // map of folder item ids to item views = cache
+ this.rowViews = {};
- // start to listen if someone modifies collection
- this.listenTo(this.collection, 'add', this.renderOne);
- this.listenTo(this.collection, 'remove', this.removeOne);
+ // create a collection of folder items for this view
+ this.collection = new mod_library_model.Folder();
- this.folderContainer = new mod_library_model.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(folder_container) {
- that.folder_container = folder_container;
- that.render();
- that.addAll(folder_container.get('folder').models);
- if (that.options.dataset_id){
- row = _.findWhere(that.rowViews, {id: that.options.dataset_id});
- if (row) {
+ // start to listen if someone modifies the collection
+ this.listenTo( this.collection, 'add', this.renderOne );
+ this.listenTo( this.collection, 'remove', this.removeOne );
+
+ this.fetchFolder();
+ },
+
+ fetchFolder: function( options ){
+ var options = options || {};
+ this.options.include_deleted = options.include_deleted;
+ var that = this;
+
+ this.folderContainer = new mod_library_model.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( folder_container ) {
+ that.folder_container = folder_container;
+ that.render();
+ },
+ error: function( model, response ){
+ if ( typeof response.responseJSON !== "undefined" ){
+ mod_toastr.error( response.responseJSON.err_msg + ' Click this to go back.', '', { onclick: function() { Galaxy.libraries.library_router.back(); } } );
+ } else {
+ mod_toastr.error( 'An error ocurred. Click this to go back.', '', { onclick: function() { Galaxy.libraries.library_router.back(); } } );
+ }
+ }
+ });
+ },
+
+ render: function ( options ){
+ this.options = _.extend( this.options, options );
+ var template = this.templateFolder();
+ $(".tooltip").hide();
+
+ // find the upper id in the full path
+ var path = this.folderContainer.attributes.metadata.full_path;
+ var upper_folder_id;
+ if ( path.length === 1 ){ // the library is above us
+ upper_folder_id = 0;
+ } else {
+ upper_folder_id = path[ path.length-2 ][ 0 ];
+ }
+
+ this.$el.html( template( {
+ path: this.folderContainer.attributes.metadata.full_path,
+ parent_library_id: this.folderContainer.attributes.metadata.parent_library_id,
+ id: this.options.id,
+ upper_folder_id: upper_folder_id,
+ order: this.sort
+ } ) );
+
+ // when dataset_id is present render its details too
+ if ( this.options.dataset_id ){
+ row = _.findWhere( that.rowViews, { id: this.options.dataset_id } );
+ if ( row ) {
row.showDatasetDetails();
} else {
- mod_toastr.error('Dataset not found. Showing folder instead.');
+ mod_toastr.error( 'Requested dataset not found. Showing folder instead.' );
}
+ } else {
+ if ( this.options.show_page === null || this.options.show_page < 1 ){
+ this.options.show_page = 1;
+ }
+ this.paginate();
+ }
+ $("#center [data-toggle]").tooltip();
+ $("#center").css('overflow','auto');
+ },
+
+ paginate: function( options ){
+ this.options = _.extend( this.options, options );
+
+ if ( this.options.show_page === null || this.options.show_page < 1 ){
+ this.options.show_page = 1;
+ }
+ this.options.total_items_count = this.folder_container.get( 'folder' ).models.length;
+ this.options.page_count = Math.ceil( this.options.total_items_count / Galaxy.libraries.preferences.get( 'folder_page_size' ) );
+ var page_start = ( Galaxy.libraries.preferences.get( 'folder_page_size' ) * ( this.options.show_page - 1 ) );
+ var items_to_render = null;
+ items_to_render = this.folder_container.get( 'folder' ).models.slice( page_start, page_start + Galaxy.libraries.preferences.get( 'folder_page_size' ) );
+ this.options.items_shown = items_to_render.length;
+ // User requests page with no items
+ if ( Galaxy.libraries.preferences.get( 'folder_page_size' ) * this.options.show_page > ( this.options.total_items_count + Galaxy.libraries.preferences.get( 'folder_page_size' ) ) ){
+ items_to_render = [];
+ }
+ Galaxy.libraries.folderToolbarView.renderPaginator( this.options );
+ this.collection.reset();
+ this.addAll( items_to_render )
+ },
+
+ /**
+ * Adds all given models to the collection.
+ * @param {array of Item or FolderAsModel} array of models that should
+ * be added to the view's collection.
+ */
+ addAll: function( models ){
+ _.each(models, function( model ) {
+ Galaxy.libraries.folderListView.collection.add( model );
+ });
+ $( "#center [data-toggle]" ).tooltip();
+ this.checkEmptiness();
+ this.postRender();
+ },
+
+ /**
+ * Call this after all models are added to the collection
+ * to ensure that the folder toolbar will show proper options
+ * and that event will be bound on all subviews.
+ */
+ postRender: function(){
+ var fetched_metadata = this.folderContainer.attributes.metadata;
+ fetched_metadata.contains_file = typeof this.collection.findWhere({type: 'file'}) !== 'undefined';
+ Galaxy.libraries.folderToolbarView.configureElements(fetched_metadata);
+ $('.library-row').hover(function() {
+ $(this).find('.show_on_hover').show();
+ }, function () {
+ $(this).find('.show_on_hover').hide();
+ });
+ },
+
+ /**
+ * Iterates this view's collection and calls the render
+ * function for each. Also binds the hover behavior.
+ */
+ renderAll: function(){
+ var that = this;
+ _.each( this.collection.models.reverse(), function( model ) {
+ that.renderOne( model );
+ });
+ this.postRender();
+ },
+
+ /**
+ * Creates a view for the given model and adds it to the folder view.
+ * @param {Item or FolderAsModel} model of the view that will be rendered
+ */
+ renderOne: function(model){
+ if (model.get('type') !== 'folder'){
+ this.options.contains_file = true;
+ // model.set('readable_size', this.size_to_string(model.get('file_size')));
}
- },
- error: function(model, response){
- if (typeof response.responseJSON !== "undefined"){
- mod_toastr.error(response.responseJSON.err_msg + ' Click this to go back.', '', {onclick: function() {Galaxy.libraries.library_router.back();}});
- } else {
- mod_toastr.error('An error ocurred. Click this to go back.', '', {onclick: function() {Galaxy.libraries.library_router.back();}});
- }
+ model.set('folder_id', this.id);
+ var rowView = new mod_library_folderrow_view.FolderRowView(model);
+
+ // save new rowView to cache
+ this.rowViews[model.get('id')] = rowView;
+
+ this.$el.find('#first_folder_item').after(rowView.el);
+
+ $('.library-row').hover(function() {
+ $(this).find('.show_on_hover').show();
+ }, function () {
+ $(this).find('.show_on_hover').hide();
+ });
+ },
+
+ /**
+ * removes the view of the given model from the DOM
+ * @param {Item or FolderAsModel} model of the view that will be removed
+ */
+ removeOne: function( model ){
+ this.$el.find( '#' + model.id ).remove();
+ },
+
+ /** Checks whether the list is empty and adds/removes the message */
+ 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();
}
- });
- },
+ },
- render: function (options) {
- this.options = _.defaults(this.options, options);
- var template = this.templateFolder();
- $(".tooltip").hide();
+ /** User clicked the table heading = he wants to sort stuff */
+ sortColumnClicked : function(event){
+ event.preventDefault();
+ if (this.sort === 'asc'){
+ this.sortFolder('name','desc');
+ this.sort = 'desc';
+ } else {
+ this.sortFolder('name','asc');
+ this.sort = 'asc';
+ }
+ this.render();
+ this.renderAll();
+ this.checkEmptiness();
+ },
- // TODO move to server
- // find the upper id in the full path
- var path = this.folderContainer.attributes.metadata.full_path;
- var upper_folder_id;
- if (path.length === 1){ // the library is above us
- upper_folder_id = 0;
- } else {
- upper_folder_id = path[path.length-2][0];
+ /**
+ * Sorts the underlying collection according to the parameters received.
+ * Currently supports only sorting by name.
+ */
+ sortFolder: function(sort_by, order){
+ if (sort_by === 'name'){
+ if (order === 'asc'){
+ return this.collection.sortByNameAsc();
+ } else if (order === 'desc'){
+ return this.collection.sortByNameDesc();
+ }
+ }
+ },
+
+ /**
+ * User clicked the checkbox in the table heading
+ * @param {context} event
+ */
+ selectAll : function (event) {
+ var selected = event.target.checked;
+ that = this;
+ // Iterate each checkbox
+ $(':checkbox', '#folder_list_body').each(function() {
+ this.checked = selected;
+ $row = $(this.parentElement.parentElement);
+ // Change color of selected/unselected
+ if (selected) {
+ that.makeDarkRow($row);
+ } else {
+ that.makeWhiteRow($row);
+ }
+ });
+ },
+
+ /**
+ * Check checkbox if user clicks on the whole row or
+ * on the checkbox itself
+ */
+ selectClickedRow : function (event) {
+ var checkbox = '';
+ var $row;
+ var source;
+ if (event.target.localName === 'input'){
+ checkbox = event.target;
+ $row = $(event.target.parentElement.parentElement);
+ source = 'input';
+ } else if (event.target.localName === 'td') {
+ checkbox = $("#" + event.target.parentElement.id).find(':checkbox')[0];
+ $row = $(event.target.parentElement);
+ source = 'td';
+ }
+ if (checkbox.checked){
+ if (source==='td'){
+ checkbox.checked = '';
+ this.makeWhiteRow($row);
+ } else if (source==='input') {
+ this.makeDarkRow($row);
+ }
+ } else {
+ if (source==='td'){
+ checkbox.checked = 'selected';
+ this.makeDarkRow($row);
+ } else if (source==='input') {
+ this.makeWhiteRow($row);
+ }
+ }
+ },
+
+ makeDarkRow: function($row){
+ $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').addClass('light');
+ $row.find('a').removeClass('dark').addClass('light');
+ $row.find('.fa-file').removeClass('fa-file').addClass('fa-file-o');
+ },
+
+ templateFolder : function (){
+ var tmpl_array = [];
+
+ // BREADCRUMBS
+ tmpl_array.push('<ol class="breadcrumb">');
+ tmpl_array.push(' <li><a title="Return to the list of libraries" href="#">Libraries</a></li>');
+ tmpl_array.push(' <% _.each(path, function(path_item) { %>');
+ tmpl_array.push(' <% if (path_item[0] != id) { %>');
+ tmpl_array.push(' <li><a title="Return to this folder" href="#/folders/<%- path_item[0] %>"><%- path_item[1] %></a></li> ');
+ tmpl_array.push( '<% } else { %>');
+ tmpl_array.push(' <li class="active"><span title="You are in this folder"><%- path_item[1] %></span></li>');
+ tmpl_array.push(' <% } %>');
+ tmpl_array.push(' <% }); %>');
+ tmpl_array.push('</ol>');
+
+ // FOLDER CONTENT
+ tmpl_array.push('<table data-library-id="<%- parent_library_id %>" id="folder_table" class="grid table table-condensed">');
+ 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><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:10%;">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">');
+ tmpl_array.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>');
+ tmpl_array.push(' <td></td>');
+ tmpl_array.push(' <td></td>');
+ tmpl_array.push(' <td></td>');
+ tmpl_array.push(' <td></td>');
+ tmpl_array.push(' <td></td>');
+ tmpl_array.push(' <td></td>');
+ tmpl_array.push(' </tr>');
+
+ 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. If you expected something to show up please consult the <a href="https://wiki.galaxyproject.org/Admin/DataLibraries/LibrarySecurity" target="_blank">library security wikipage</a> or visit the <a href="https://biostar.usegalaxy.org/" target="_blank">Galaxy support site</a>.</div>');
+
+ return _.template(tmpl_array.join(''));
}
-
- this.$el.html(template({ path: this.folderContainer.attributes.metadata.full_path, parent_library_id: this.folderContainer.attributes.metadata.parent_library_id, id: this.options.id, upper_folder_id: upper_folder_id, order: this.sort}));
-
- // initialize the library tooltips
- $("#center [data-toggle]").tooltip();
- //hack to show scrollbars
- $("#center").css('overflow','auto');
- },
-
- /**
- * Call this after all models are added to the collection
- * to ensure that the folder toolbar will show proper options
- * and that event will be bound on all subviews.
- */
- postRender: function(){
- var fetched_metadata = this.folderContainer.attributes.metadata;
- fetched_metadata.contains_file = typeof this.collection.findWhere({type: 'file'}) !== 'undefined';
- Galaxy.libraries.folderToolbarView.configureElements(fetched_metadata);
- $('.library-row').hover(function() {
- $(this).find('.show_on_hover').show();
- }, function () {
- $(this).find('.show_on_hover').hide();
- });
- },
-
- /**
- * Adds all given models to the collection.
- * @param {array of Item or FolderAsModel} array of models that should
- * be added to the view's collection.
- */
- addAll: function(models){
- _.each(models.reverse(), function(model) {
- Galaxy.libraries.folderListView.collection.add(model);
- });
-
- $("#center [data-toggle]").tooltip();
- this.checkEmptiness();
-
- this.postRender();
- },
-
- /**
- * Iterates this view's collection and calls the render
- * function for each. Also binds the hover behavior.
- */
- renderAll: function(){
- var that = this;
- _.each(this.collection.models.reverse(), function(model) {
- that.renderOne(model);
- });
- this.postRender();
- },
-
- /**
- * Creates a view for the given model and adds it to the folder view.
- * @param {Item or FolderAsModel} model of the view that will be rendered
- */
- renderOne: function(model){
- if (model.get('type') !== 'folder'){
- this.options.contains_file = true;
- // model.set('readable_size', this.size_to_string(model.get('file_size')));
- }
- model.set('folder_id', this.id);
- var rowView = new mod_library_folderrow_view.FolderRowView(model);
-
- // save new rowView to cache
- this.rowViews[model.get('id')] = rowView;
-
- this.$el.find('#first_folder_item').after(rowView.el);
-
- $('.library-row').hover(function() {
- $(this).find('.show_on_hover').show();
- }, function () {
- $(this).find('.show_on_hover').hide();
- });
- },
-
- /**
- * removes the view of the given model from the DOM
- * @param {Item or FolderAsModel} model of the view that will be removed
- */
- removeOne: function(model){
- this.$el.find('#' + model.id).remove();
- },
-
- /** Checks whether the list is empty and adds/removes the message */
- 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();
- }
- },
-
- /** User clicked the table heading = he wants to sort stuff */
- sortColumnClicked : function(event){
- event.preventDefault();
- if (this.sort === 'asc'){
- this.sortFolder('name','desc');
- this.sort = 'desc';
- } else {
- this.sortFolder('name','asc');
- this.sort = 'asc';
- }
- this.render();
- this.renderAll();
- this.checkEmptiness();
- },
-
- /**
- * Sorts the underlying collection according to the parameters received.
- * Currently supports only sorting by name.
- */
- sortFolder: function(sort_by, order){
- if (sort_by === 'name'){
- if (order === 'asc'){
- return this.collection.sortByNameAsc();
- } else if (order === 'desc'){
- return this.collection.sortByNameDesc();
- }
- }
- },
-
- /**
- * User clicked the checkbox in the table heading
- * @param {context} event
- */
- selectAll : function (event) {
- var selected = event.target.checked;
- that = this;
- // Iterate each checkbox
- $(':checkbox', '#folder_list_body').each(function() {
- this.checked = selected;
- $row = $(this.parentElement.parentElement);
- // Change color of selected/unselected
- if (selected) {
- that.makeDarkRow($row);
- } else {
- that.makeWhiteRow($row);
- }
- });
- },
-
- /**
- * Check checkbox if user clicks on the whole row or
- * on the checkbox itself
- */
- selectClickedRow : function (event) {
- var checkbox = '';
- var $row;
- var source;
- if (event.target.localName === 'input'){
- checkbox = event.target;
- $row = $(event.target.parentElement.parentElement);
- source = 'input';
- } else if (event.target.localName === 'td') {
- checkbox = $("#" + event.target.parentElement.id).find(':checkbox')[0];
- $row = $(event.target.parentElement);
- source = 'td';
- }
- if (checkbox.checked){
- if (source==='td'){
- checkbox.checked = '';
- this.makeWhiteRow($row);
- } else if (source==='input') {
- this.makeDarkRow($row);
- }
- } else {
- if (source==='td'){
- checkbox.checked = 'selected';
- this.makeDarkRow($row);
- } else if (source==='input') {
- this.makeWhiteRow($row);
- }
- }
- },
-
- makeDarkRow: function($row){
- $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').addClass('light');
- $row.find('a').removeClass('dark').addClass('light');
- $row.find('.fa-file').removeClass('fa-file').addClass('fa-file-o');
- },
-
- templateFolder : function (){
- var tmpl_array = [];
-
- // BREADCRUMBS
- tmpl_array.push('<ol class="breadcrumb">');
- tmpl_array.push(' <li><a title="Return to the list of libraries" href="#">Libraries</a></li>');
- tmpl_array.push(' <% _.each(path, function(path_item) { %>');
- tmpl_array.push(' <% if (path_item[0] != id) { %>');
- tmpl_array.push(' <li><a title="Return to this folder" href="#/folders/<%- path_item[0] %>"><%- path_item[1] %></a></li> ');
- tmpl_array.push( '<% } else { %>');
- tmpl_array.push(' <li class="active"><span title="You are in this folder"><%- path_item[1] %></span></li>');
- tmpl_array.push(' <% } %>');
- tmpl_array.push(' <% }); %>');
- tmpl_array.push('</ol>');
-
- // FOLDER CONTENT
- tmpl_array.push('<table data-library-id="<%- parent_library_id %>" id="folder_table" class="grid table table-condensed">');
- 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><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:10%;">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">');
- tmpl_array.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>');
- tmpl_array.push(' <td></td>');
- tmpl_array.push(' <td></td>');
- tmpl_array.push(' <td></td>');
- tmpl_array.push(' <td></td>');
- tmpl_array.push(' <td></td>');
- tmpl_array.push(' <td></td>');
- tmpl_array.push(' </tr>');
-
- 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. If you expected something to show up please consult the <a href="https://wiki.galaxyproject.org/Admin/DataLibraries/LibrarySecurity" target="_blank">library security wikipage</a> or visit the <a href="https://biostar.usegalaxy.org/" target="_blank">Galaxy support site</a>.</div>');
-
- return _.template(tmpl_array.join(''));
- }
-
+
});
return {
diff -r 5edaafeecfed448ea259adaacf3a4ce6e6da15d1 -r d76253bb22c4f64c73523956d119c0c5ab0a357b client/galaxy/scripts/mvc/library/library-foldertoolbar-view.js
--- a/client/galaxy/scripts/mvc/library/library-foldertoolbar-view.js
+++ b/client/galaxy/scripts/mvc/library/library-foldertoolbar-view.js
@@ -20,7 +20,9 @@
'click #toolbtn_bulk_import' : 'modalBulkImport',
'click #include_deleted_datasets_chk' : 'checkIncludeDeleted',
'click #toolbtn_show_libinfo' : 'showLibInfo',
- 'click #toolbtn_bulk_delete' : 'deleteSelectedDatasets'
+ 'click #toolbtn_bulk_delete' : 'deleteSelectedDatasets',
+ 'click #page_size_prompt' : 'showPageSizePrompt'
+
},
defaults: {
@@ -90,6 +92,22 @@
this.$el.html(toolbar_template(template_defaults));
},
+ /**
+ * Called from FolderListView when needed.
+ * @param {object} options common options
+ */
+ renderPaginator: function( options ){
+ this.options = _.extend( this.options, options );
+ var paginator_template = this.templatePaginator();
+ this.$el.find( '#folder_paginator' ).html( paginator_template({
+ id: this.options.id,
+ show_page: parseInt( this.options.show_page ),
+ page_count: parseInt( this.options.page_count ),
+ total_items_count: this.options.total_items_count,
+ items_shown: this.options.items_shown
+ }));
+ },
+
configureElements: function(options){
this.options = _.extend(this.options, options);
@@ -833,11 +851,11 @@
var popped_item = lddas_set.pop();
if ( typeof popped_item === "undefined" ) {
if ( this.options.chain_call_control.failed_number === 0 ){
- mod_toastr.success( 'Selected datasets deleted' );
+ mod_toastr.success( 'Selected datasets were deleted.' );
} else if ( this.options.chain_call_control.failed_number === this.options.chain_call_control.total_number ){
- mod_toastr.error( 'There was an error and no datasets were deleted.' );
+ mod_toastr.error( 'There was an error and no datasets were deleted. Please make sure you have sufficient permissions.' );
} else if ( this.options.chain_call_control.failed_number < this.options.chain_call_control.total_number ){
- mod_toastr.warning( 'Some of the datasets could not be deleted' );
+ mod_toastr.warning( 'Some of the datasets could not be deleted. Please make sure you have sufficient permissions.' );
}
Galaxy.modal.hide();
return this.deleted_lddas;
@@ -976,6 +994,14 @@
}
},
+ showPageSizePrompt: function(){
+ var folder_page_size = prompt( 'How many items per page do you want to see?', Galaxy.libraries.preferences.get( 'folder_page_size' ) );
+ if ( ( folder_page_size != null ) && ( folder_page_size == parseInt( folder_page_size ) ) ) {
+ Galaxy.libraries.preferences.set( { 'folder_page_size': parseInt( folder_page_size ) } );
+ Galaxy.libraries.folderListView.render( { id: this.options.id, show_page: 1 } );
+ }
+ },
+
templateToolBar: function(){
tmpl_array = [];
@@ -1016,7 +1042,6 @@
tmpl_array.push(' <button style="display:none;" data-toggle="tooltip" data-placement="top" title="Add Datasets to Current Folder" id="toolbtn_add_files" class="btn btn-default toolbtn_add_files primary-button add-library-items" type="button"><span class="fa fa-plus"></span><span class="fa fa-file"></span></span></button>');
tmpl_array.push('<% } %>');
-
tmpl_array.push(' <button data-toggle="tooltip" data-placement="top" title="Import selected datasets into history" id="toolbtn_bulk_import" class="primary-button dataset-manipulation" style="margin-left: 0.5em; display:none;" type="button"><span class="fa fa-book"></span> to History</button>');
tmpl_array.push(' <div id="toolbtn_dl" class="btn-group dataset-manipulation" style="margin-left: 0.5em; display:none; ">');
tmpl_array.push(' <button title="Download selected datasets as archive" id="drop_toggle" type="button" class="primary-button dropdown-toggle" data-toggle="dropdown">');
@@ -1032,6 +1057,10 @@
tmpl_array.push(' <button data-id="<%- id %>" data-toggle="tooltip" data-placement="top" title="Show library information" id="toolbtn_show_libinfo" class="primary-button" style="margin-left: 0.5em;" type="button"><span class="fa fa-info-circle"></span> Library Info</button>');
tmpl_array.push(' <span class="help-button" data-toggle="tooltip" data-placement="top" title="Visit Libraries Wiki"><a href="https://wiki.galaxyproject.org/DataLibraries/screen/FolderContents" target="_blank"><button class="primary-button" type="button"><span class="fa fa-question-circle"></span> Help</button></a></span>');
+ tmpl_array.push(' <span id="folder_paginator" class="library-paginator">');
+ // paginator will append here
+ tmpl_array.push(' </span>');
+
tmpl_array.push(' </div>');
// TOOLBAR END
tmpl_array.push(' <div id="folder_items_element">');
@@ -1239,7 +1268,41 @@
tmpl_array.push('</ul>');
return _.template(tmpl_array.join(''));
- }
+ },
+
+ templatePaginator: function(){
+ tmpl_array = [];
+
+ tmpl_array.push(' <ul class="pagination pagination-sm">');
+ tmpl_array.push(' <% if ( ( show_page - 1 ) > 0 ) { %>');
+ tmpl_array.push(' <% if ( ( show_page - 1 ) > page_count ) { %>'); // we are on higher page than total page count
+ tmpl_array.push(' <li><a href="#folders/<%= id %>/page/1"><span class="fa fa-angle-double-left"></span></a></li>');
+ tmpl_array.push(' <li class="disabled"><a href="#folders/<%= id %>/page/<% print( show_page ) %>"><% print( show_page - 1 ) %></a></li>');
+ tmpl_array.push(' <% } else { %>');
+ tmpl_array.push(' <li><a href="#folders/<%= id %>/page/1"><span class="fa fa-angle-double-left"></span></a></li>');
+ tmpl_array.push(' <li><a href="#folders/<%= id %>/page/<% print( show_page - 1 ) %>"><% print( show_page - 1 ) %></a></li>');
+ tmpl_array.push(' <% } %>');
+ tmpl_array.push(' <% } else { %>'); // we are on the first page
+ tmpl_array.push(' <li class="disabled"><a href="#folders/<%= id %>/page/1"><span class="fa fa-angle-double-left"></span></a></li>');
+ tmpl_array.push(' <li class="disabled"><a href="#folders/<%= id %>/page/<% print( show_page ) %>"><% print( show_page - 1 ) %></a></li>');
+ tmpl_array.push(' <% } %>');
+ tmpl_array.push(' <li class="active">');
+ tmpl_array.push(' <a href="#folders/<%= id %>/page/<% print( show_page ) %>"><% print( show_page ) %></a>');
+ tmpl_array.push(' </li>');
+ tmpl_array.push(' <% if ( ( show_page ) < page_count ) { %>');
+ tmpl_array.push(' <li><a href="#folders/<%= id %>/page/<% print( show_page + 1 ) %>"><% print( show_page + 1 ) %></a></li>');
+ tmpl_array.push(' <li><a href="#folders/<%= id %>/page/<% print( page_count ) %>"><span class="fa fa-angle-double-right"></span></a></li>');
+ tmpl_array.push(' <% } else { %>');
+ tmpl_array.push(' <li class="disabled"><a href="#folders/<%= id %>/page/<% print( show_page ) %>"><% print( show_page + 1 ) %></a></li>');
+ tmpl_array.push(' <li class="disabled"><a href="#folders/<%= id %>/page/<% print( page_count ) %>"><span class="fa fa-angle-double-right"></span></a></li>');
+ tmpl_array.push(' <% } %>');
+ tmpl_array.push(' </ul>');
+ tmpl_array.push(' <span>');
+ tmpl_array.push(' showing <a data-toggle="tooltip" data-placement="top" title="Click to change the number of items on page" id="page_size_prompt"><%- items_shown %></a> of <%- total_items_count %> items');
+ tmpl_array.push(' </span>');
+
+ return _.template(tmpl_array.join(''));
+ },
});
This diff is so big that we needed to truncate the remainder.
https://bitbucket.org/galaxy/galaxy-central/commits/a0bbd59cb420/
Changeset: a0bbd59cb420
User: abretaud
Date: 2014-12-11 16:02:05+00:00
Summary: fix method names and api doc
Affected #: 2 files
diff -r d76253bb22c4f64c73523956d119c0c5ab0a357b -r a0bbd59cb420442c2fb518540fd2fdfbb2b2da0a lib/galaxy/tools/data/__init__.py
--- a/lib/galaxy/tools/data/__init__.py
+++ b/lib/galaxy/tools/data/__init__.py
@@ -211,11 +211,11 @@
self.add_entry( entry, allow_duplicates=allow_duplicates, persist=persist, persist_on_error=persist_on_error, entry_source=entry_source, **kwd )
return self._loaded_content_version
- def _remove_entry(self, values, **kwd):
+ def _remove_entry(self, values):
raise NotImplementedError( "Abstract method" )
- def remove_entry(self, values, **kwd):
- self._remove_entry_and_reload( values,**kwd )
+ def remove_entry(self, values):
+ self._remove_entry( values )
return self._update_version()
def is_current_version( self, other_version ):
@@ -513,7 +513,7 @@
data_table_fh.write( "%s\n" % ( self.separator.join( fields ) ) )
return not is_error
- def _remove_entry_and_reload( self, values):
+ def _remove_entry( self, values):
# update every file
for filename in self.filenames:
diff -r d76253bb22c4f64c73523956d119c0c5ab0a357b -r a0bbd59cb420442c2fb518540fd2fdfbb2b2da0a lib/galaxy/webapps/galaxy/api/tool_data.py
--- a/lib/galaxy/webapps/galaxy/api/tool_data.py
+++ b/lib/galaxy/webapps/galaxy/api/tool_data.py
@@ -26,10 +26,10 @@
def delete( self, trans, id, **kwd ):
"""
DELETE /api/tool_data/{id}
- Removes a role from a group
+ Removes an item from a data table
:type id: str
- :param id: the encoded id of the history to delete
+ :param id: the id of the data table containing the item to delete
:type kwd: dict
:param kwd: (required) dictionary structure containing:
https://bitbucket.org/galaxy/galaxy-central/commits/6363c839528c/
Changeset: 6363c839528c
User: jmchilton
Date: 2014-12-15 05:17:31+00:00
Summary: Merged in abretaud/galaxy-central (pull request #577)
Add an API to remove items from tool data tables
Affected #: 2 files
diff -r ac537b0a4167cbd14e0cb93175cc85c096275697 -r 6363c839528ca8bbbbc60c1b3784c788468d9445 lib/galaxy/tools/data/__init__.py
--- a/lib/galaxy/tools/data/__init__.py
+++ b/lib/galaxy/tools/data/__init__.py
@@ -211,6 +211,13 @@
self.add_entry( entry, allow_duplicates=allow_duplicates, persist=persist, persist_on_error=persist_on_error, entry_source=entry_source, **kwd )
return self._loaded_content_version
+ def _remove_entry(self, values):
+ raise NotImplementedError( "Abstract method" )
+
+ def remove_entry(self, values):
+ self._remove_entry( values )
+ return self._update_version()
+
def is_current_version( self, other_version ):
return self._loaded_content_version == other_version
@@ -506,6 +513,42 @@
data_table_fh.write( "%s\n" % ( self.separator.join( fields ) ) )
return not is_error
+ def _remove_entry( self, values):
+
+ # update every file
+ for filename in self.filenames:
+
+ if os.path.exists( filename ):
+ values = self._replace_field_separators( values )
+ self.filter_file_fields( filename, values )
+ else:
+ log.warn( "Cannot find index file '%s' for tool data table '%s'" % ( filename, self.name ) )
+
+ self.reload_from_files()
+
+ def filter_file_fields( self, loc_file, values ):
+ """
+ Reads separated lines from file and print back only the lines that pass a filter.
+ """
+ separator_char = (lambda c: '<TAB>' if c == '\t' else c)(self.separator)
+
+ with open(loc_file) as reader:
+ rval = ""
+ for i, line in enumerate( reader ):
+ if line.lstrip().startswith( self.comment_char ):
+ rval += line
+ else:
+ line_s = line.rstrip( "\n\r" )
+ if line_s:
+ fields = line_s.split( self.separator )
+ if fields != values:
+ rval += line
+
+ with open(loc_file, 'wb') as writer:
+ writer.write(rval)
+
+ return rval
+
def _replace_field_separators( self, fields, separator=None, replace=None, comment_char=None ):
#make sure none of the fields contain separator
#make sure separator replace is different from comment_char,
diff -r ac537b0a4167cbd14e0cb93175cc85c096275697 -r 6363c839528ca8bbbbc60c1b3784c788468d9445 lib/galaxy/webapps/galaxy/api/tool_data.py
--- a/lib/galaxy/webapps/galaxy/api/tool_data.py
+++ b/lib/galaxy/webapps/galaxy/api/tool_data.py
@@ -20,3 +20,44 @@
@web.expose_api
def show( self, trans, id, **kwds ):
return trans.app.tool_data_tables.data_tables[id].to_dict(view='element')
+
+ @web.require_admin
+ @web.expose_api
+ def delete( self, trans, id, **kwd ):
+ """
+ DELETE /api/tool_data/{id}
+ Removes an item from a data table
+
+ :type id: str
+ :param id: the id of the data table containing the item to delete
+ :type kwd: dict
+ :param kwd: (required) dictionary structure containing:
+
+ * payload: a dictionary itself containing:
+ * values: <TAB> separated list of column contents, there must be a value for all the columns of the data table
+ """
+ decoded_tool_data_id = id
+
+ try:
+ data_table = trans.app.tool_data_tables.data_tables.get(decoded_tool_data_id)
+ except:
+ data_table = None
+ if not data_table:
+ trans.response.status = 400
+ return "Invalid data table id ( %s ) specified." % str( decoded_tool_data_id )
+
+ values = None
+ if kwd.get( 'payload', None ):
+ values = kwd['payload'].get( 'values', '' )
+
+ if not values:
+ trans.response.status = 400
+ return "Invalid data table item ( %s ) specified." % str( values )
+
+ split_values = values.split("\t")
+
+ if len(split_values) != len(data_table.get_column_name_list()):
+ trans.response.status = 400
+ return "Invalid data table item ( %s ) specified. Wrong number of columns (%s given, %s required)." % ( str( values ), str(len(split_values)), str(len(data_table.get_column_name_list())))
+
+ return data_table.remove_entry(split_values)
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 lance_parsons/galaxy-central-pull-requests/blastdb_p.loc.sample (pull request #605)
by commits-noreply@bitbucket.org 14 Dec '14
by commits-noreply@bitbucket.org 14 Dec '14
14 Dec '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/ac537b0a4167/
Changeset: ac537b0a4167
User: jmchilton
Date: 2014-12-14 23:08:48+00:00
Summary: Merged in lance_parsons/galaxy-central-pull-requests/blastdb_p.loc.sample (pull request #605)
Removed blastdb_p.loc.sample, moved to tool-shed repo
Affected #: 1 file
diff -r 16e4b33e850e1a644624d97ecf676bbaaf1aabf9 -r ac537b0a4167cbd14e0cb93175cc85c096275697 tool-data/blastdb_p.loc.sample
--- a/tool-data/blastdb_p.loc.sample
+++ /dev/null
@@ -1,27 +0,0 @@
-#This is a sample file distributed with Galaxy that is used to define a
-#list of protein BLAST databases, using three columns tab separated
-#(longer whitespace are TAB characters):
-#
-#<unique_id><database_caption><base_name_path>
-#
-#The captions typically contain spaces and might end with the build date.
-#It is important that the actual database name does not have a space in it,
-#and that the first tab that appears in the line is right before the path.
-#
-#So, for example, if your database is NR and the path to your base name
-#is /data/blastdb/nr, then the blastdb_p.loc entry would look like this:
-#
-#nr NCBI NR (non redundant) /data/blastdb/nr
-#
-#and your /data/blastdb directory would contain all of the files associated
-#with the database, /data/blastdb/nr.*.
-#
-#Your blastdb_p.loc file should include an entry per line for each "base name"
-#you have stored. For example:
-#
-#nr_05Jun2010 NCBI NR (non redundant) 05 Jun 2010 /data/blastdb/05Jun2010/nr
-#nr_15Aug2010 NCBI NR (non redundant) 15 Aug 2010 /data/blastdb/15Aug2010/nr
-#...etc...
-#
-#See also blastdb.loc which is for any nucleotide BLAST database.
-#
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/6d6757abda20/
Changeset: 6d6757abda20
Branch: blastdb_p.loc.sample
User: lance_parsons
Date: 2014-12-10 17:43:33+00:00
Summary: Removed blastdb_p.loc.sample, moved to tool-shed repo
Affected #: 1 file
diff -r d66ad58a40e31defb6de0eb8b8b543b7e0f26ae5 -r 6d6757abda20cd49d85d8b59d382de9e69bf1588 tool-data/blastdb_p.loc.sample
--- a/tool-data/blastdb_p.loc.sample
+++ /dev/null
@@ -1,27 +0,0 @@
-#This is a sample file distributed with Galaxy that is used to define a
-#list of protein BLAST databases, using three columns tab separated
-#(longer whitespace are TAB characters):
-#
-#<unique_id><database_caption><base_name_path>
-#
-#The captions typically contain spaces and might end with the build date.
-#It is important that the actual database name does not have a space in it,
-#and that the first tab that appears in the line is right before the path.
-#
-#So, for example, if your database is NR and the path to your base name
-#is /data/blastdb/nr, then the blastdb_p.loc entry would look like this:
-#
-#nr NCBI NR (non redundant) /data/blastdb/nr
-#
-#and your /data/blastdb directory would contain all of the files associated
-#with the database, /data/blastdb/nr.*.
-#
-#Your blastdb_p.loc file should include an entry per line for each "base name"
-#you have stored. For example:
-#
-#nr_05Jun2010 NCBI NR (non redundant) 05 Jun 2010 /data/blastdb/05Jun2010/nr
-#nr_15Aug2010 NCBI NR (non redundant) 15 Aug 2010 /data/blastdb/15Aug2010/nr
-#...etc...
-#
-#See also blastdb.loc which is for any nucleotide BLAST database.
-#
https://bitbucket.org/galaxy/galaxy-central/commits/ac537b0a4167/
Changeset: ac537b0a4167
User: jmchilton
Date: 2014-12-14 23:08:48+00:00
Summary: Merged in lance_parsons/galaxy-central-pull-requests/blastdb_p.loc.sample (pull request #605)
Removed blastdb_p.loc.sample, moved to tool-shed repo
Affected #: 1 file
diff -r 16e4b33e850e1a644624d97ecf676bbaaf1aabf9 -r ac537b0a4167cbd14e0cb93175cc85c096275697 tool-data/blastdb_p.loc.sample
--- a/tool-data/blastdb_p.loc.sample
+++ /dev/null
@@ -1,27 +0,0 @@
-#This is a sample file distributed with Galaxy that is used to define a
-#list of protein BLAST databases, using three columns tab separated
-#(longer whitespace are TAB characters):
-#
-#<unique_id><database_caption><base_name_path>
-#
-#The captions typically contain spaces and might end with the build date.
-#It is important that the actual database name does not have a space in it,
-#and that the first tab that appears in the line is right before the path.
-#
-#So, for example, if your database is NR and the path to your base name
-#is /data/blastdb/nr, then the blastdb_p.loc entry would look like this:
-#
-#nr NCBI NR (non redundant) /data/blastdb/nr
-#
-#and your /data/blastdb directory would contain all of the files associated
-#with the database, /data/blastdb/nr.*.
-#
-#Your blastdb_p.loc file should include an entry per line for each "base name"
-#you have stored. For example:
-#
-#nr_05Jun2010 NCBI NR (non redundant) 05 Jun 2010 /data/blastdb/05Jun2010/nr
-#nr_15Aug2010 NCBI NR (non redundant) 15 Aug 2010 /data/blastdb/15Aug2010/nr
-#...etc...
-#
-#See also blastdb.loc which is for any nucleotide BLAST database.
-#
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: Allow specification of multiple datatypes_conf.xml files.
by commits-noreply@bitbucket.org 14 Dec '14
by commits-noreply@bitbucket.org 14 Dec '14
14 Dec '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/16e4b33e850e/
Changeset: 16e4b33e850e
User: jmchilton
Date: 2014-12-14 18:16:11+00:00
Summary: Allow specification of multiple datatypes_conf.xml files.
If there are conflicting datatype registrations - the registration that appears last in these files will be used. There is an open issue here regarding order of sniffers. This is going to just piggy back on whatever the tool shed would do - so however we decide for instance to resolve sniffer orders in that respect will work with this mechanism as well.
For now though - lets just specify that the best practice should be to just place all non-shed sniffers in one file if specifing multiple datatypes configurations manually like this.
Affected #: 2 files
diff -r 1422966f1ca88472b8685015f5a8cdd3dd0f1db9 -r 16e4b33e850e1a644624d97ecf676bbaaf1aabf9 config/galaxy.ini.sample
--- a/config/galaxy.ini.sample
+++ b/config/galaxy.ini.sample
@@ -212,8 +212,10 @@
# Directory where chrom len files are kept, currently mainly used by trackster
#len_file_path = tool-data/shared/ucsc/chrom
-# Datatypes config file, defines what data (file) types are available in
-# Galaxy (.sample is used if default does not exist).
+# Datatypes config file(s), defines what data (file) types are available in
+# Galaxy (.sample is used if default does not exist). If a datatype appears in
+# multiple files - the last definition is used (though the first sniffer is used
+# so limit sniffer definitions to one file).
#datatypes_config_file = config/datatypes_conf.xml
# Disable the 'Auto-detect' option for file uploads
diff -r 1422966f1ca88472b8685015f5a8cdd3dd0f1db9 -r 16e4b33e850e1a644624d97ecf676bbaaf1aabf9 lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -583,8 +583,9 @@
for path in tool_configs:
if not os.path.exists( path ):
raise ConfigurationError("Tool config file not found: %s" % path )
- if not os.path.isfile( self.datatypes_config ):
- raise ConfigurationError("Datatypes config file not found: %s" % self.datatypes_config )
+ for datatypes_config in listify( self.datatypes_config ):
+ if not os.path.isfile( datatypes_config ):
+ raise ConfigurationError("Datatypes config file not found: %s" % datatypes_config )
# Check for deprecated options.
for key in self.config_dict.keys():
if key in self.deprecated_options:
@@ -746,7 +747,12 @@
# between 2 proprietary datatypes, the datatype from the repository that was installed earliest will take precedence.
installed_repository_manager.load_proprietary_datatypes()
# Load the data types in the Galaxy distribution, which are defined in self.config.datatypes_config.
- self.datatypes_registry.load_datatypes( self.config.root, self.config.datatypes_config )
+ datatypes_configs = self.config.datatypes_config
+ for datatypes_config in listify( datatypes_configs ):
+ # Setting override=False would make earlier files would take
+ # precedence - but then they wouldn't override tool shed
+ # datatypes.
+ self.datatypes_registry.load_datatypes( self.config.root, datatypes_config, override=True )
def _configure_object_store( self, **kwds ):
from galaxy.objectstore import build_object_store_from_config
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/ff5c0b7bb315/
Changeset: ff5c0b7bb315
User: jmchilton
Date: 2014-12-14 04:04:00+00:00
Summary: Remove another incorrect lock statement.
Affected #: 1 file
diff -r 6db06b5b370ae0c955c66d6e2668aaf6260fe221 -r ff5c0b7bb3152804fa88454bee5f3c777a1b525e lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -184,9 +184,8 @@
if ok:
datatype_class = None
if proprietary_path and proprietary_datatype_module and datatype_class_name:
- # We need to change the value of sys.path, so do it in a way that is thread-safe.
- lock = threading.Lock()
- lock.acquire( True )
+ # TODO: previously comments suggested this needs to be locked because it modifys
+ # the sys.path, probably true but the previous lock wasn't doing that.
try:
imported_module = __import_module( proprietary_path,
proprietary_datatype_module,
@@ -198,8 +197,6 @@
except Exception, e:
full_path = os.path.join( proprietary_path, proprietary_datatype_module )
self.log.debug( "Exception importing proprietary code file %s: %s" % ( str( full_path ), str( e ) ) )
- finally:
- lock.release()
# Either the above exception was thrown because the proprietary_datatype_module is not derived from a class
# in the repository, or we are loading Galaxy's datatypes. In either case we'll look in the registry.
if datatype_class is None:
https://bitbucket.org/galaxy/galaxy-central/commits/1422966f1ca8/
Changeset: 1422966f1ca8
User: jmchilton
Date: 2014-12-14 04:04:00+00:00
Summary: Fix bug in registry.py requiring the odd relative import of locally defined data type modules.
So many times I helped people get stuff working by telling them to stick the import in this file without ever understanding why the import needed to be there, the answer would appear to be - because there was a bug.
Kanwei Li fixed the same bug in the sniffer code in d7c529f.
Fixing this bug should allow dynamically injecting datatypes (outside context of tool shed) without modifying Galaxy's source - important for OS packages and planemo.
Affected #: 1 file
diff -r ff5c0b7bb3152804fa88454bee5f3c777a1b525e -r 1422966f1ca88472b8685015f5a8cdd3dd0f1db9 lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -203,7 +203,7 @@
try:
# The datatype class name must be contained in one of the datatype modules in the Galaxy distribution.
fields = datatype_module.split( '.' )
- module = __import__( fields.pop( 0 ) )
+ module = __import__( datatype_module )
for mod in fields:
module = getattr( module, mod )
datatype_class = getattr( module, datatype_class_name )
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
5 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/3af604bcd666/
Changeset: 3af604bcd666
User: jmchilton
Date: 2014-12-14 03:09:51+00:00
Summary: Remove incorrect usage of threading.Lock().
Not sure what the intention was - but the Lock is created locally in the function so nothing else would be waiting on that particular lock.
At some point we may wish to revisit other usages introduced in 1bb04279216e0abf4e24b9299cccc06c10b9c0ac.
Affected #: 1 file
diff -r fa061192b5d9c6df7ef65edb39a846e5a9a227bf -r 3af604bcd66681f890ca86d3c7f0395e9d5e8d05 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -1688,14 +1688,10 @@
self.help = tool_source.root.find( 'help' )
if self.repository_id and self.help.text.find( '.. image:: ' ) >= 0:
# Handle tool help image display for tools that are contained in repositories in the tool shed or installed into Galaxy.
- lock = threading.Lock()
- lock.acquire( True )
try:
self.help.text = suc.set_image_paths( self.app, self.repository_id, self.help.text )
except Exception, e:
log.exception( "Exception in parse_help, so images may not be properly displayed:\n%s" % str( e ) )
- finally:
- lock.release()
help_pages = self.help.findall( "page" )
help_header = self.help.text
try:
https://bitbucket.org/galaxy/galaxy-central/commits/3d7d15601e49/
Changeset: 3d7d15601e49
User: jmchilton
Date: 2014-12-14 03:09:51+00:00
Summary: Improvements to loading help text from tools.
Do not convert rst to a mako template until needed, this is a costly operation and has the potential to speed update Galaxy start time. Move logic related to parsing of simple help text blocks out of tool and into the new tool parser interface and add implementation for YAML-based tools as well as unit tests for both. More advanced, multi-page tool help is still possible, workflows with the old tool form, but is only available to XML-based tools.
Affected #: 5 files
diff -r 3af604bcd66681f890ca86d3c7f0395e9d5e8d05 -r 3d7d15601e493d1d7ed6a10814f6ca2b36a611fb lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -101,6 +101,8 @@
</when></conditional>"""
+HELP_UNINITIALIZED = threading.Lock()
+
class ToolNotFoundException( Exception ):
pass
@@ -1680,43 +1682,9 @@
This implementation supports multiple pages.
"""
# TODO: Allow raw HTML or an external link.
- self.help = None
- self.help_by_page = list()
- help_header = ""
- help_footer = ""
- if hasattr( tool_source, 'root' ) and tool_source.root.find( 'help' ) is not None:
- self.help = tool_source.root.find( 'help' )
- if self.repository_id and self.help.text.find( '.. image:: ' ) >= 0:
- # Handle tool help image display for tools that are contained in repositories in the tool shed or installed into Galaxy.
- try:
- self.help.text = suc.set_image_paths( self.app, self.repository_id, self.help.text )
- except Exception, e:
- log.exception( "Exception in parse_help, so images may not be properly displayed:\n%s" % str( e ) )
- help_pages = self.help.findall( "page" )
- help_header = self.help.text
- try:
- self.help = Template( rst_to_html(self.help.text), input_encoding='utf-8',
- output_encoding='utf-8', default_filters=[ 'decode.utf8' ],
- encoding_errors='replace' )
- except:
- log.exception( "error in help for tool %s" % self.name )
- # Multiple help page case
- if help_pages:
- for help_page in help_pages:
- self.help_by_page.append( help_page.text )
- help_footer = help_footer + help_page.tail
- # Each page has to rendered all-together because of backreferences allowed by rst
- try:
- self.help_by_page = [ Template( rst_to_html( help_header + x + help_footer ),
- input_encoding='utf-8', output_encoding='utf-8',
- default_filters=[ 'decode.utf8' ],
- encoding_errors='replace' )
- for x in self.help_by_page ]
- except:
- log.exception( "error in multi-page help for tool %s" % self.name )
- # Pad out help pages to match npages ... could this be done better?
- while len( self.help_by_page ) < self.npages:
- self.help_by_page.append( self.help )
+ self.__help = HELP_UNINITIALIZED
+ self.__help_by_page = HELP_UNINITIALIZED
+ self.__help_source = tool_source
def parse_outputs( self, tool_source ):
"""
@@ -1891,6 +1859,67 @@
self.repository_owner = tool_shed_repository.owner
self.installed_changeset_revision = tool_shed_repository.installed_changeset_revision
+ @property
+ def help(self):
+ if self.__help is HELP_UNINITIALIZED:
+ self.__ensure_help()
+ return self.__help
+
+ @property
+ def help_by_page(self):
+ if self.__help_by_page is HELP_UNINITIALIZED:
+ self.__ensure_help()
+ return self.__help_by_page
+
+ def __ensure_help(self):
+ with HELP_UNINITIALIZED:
+ if self.__help is HELP_UNINITIALIZED:
+ self.__inititalize_help()
+
+ def __inititalize_help(self):
+ tool_source = self.__help_source
+ self.__help = None
+ self.__help_by_page = []
+ help_header = ""
+ help_footer = ""
+ help_text = tool_source.parse_help()
+ if help_text is not None:
+ if self.repository_id and help_text.find( '.. image:: ' ) >= 0:
+ # Handle tool help image display for tools that are contained in repositories in the tool shed or installed into Galaxy.
+ try:
+ help_text = suc.set_image_paths( self.app, self.repository_id, help_text )
+ except Exception, e:
+ log.exception( "Exception in parse_help, so images may not be properly displayed:\n%s" % str( e ) )
+ try:
+ self.__help = Template( rst_to_html(help_text), input_encoding='utf-8',
+ output_encoding='utf-8', default_filters=[ 'decode.utf8' ],
+ encoding_errors='replace' )
+ except:
+ log.exception( "error in help for tool %s" % self.name )
+
+ # Handle deprecated multi-page help text in XML case.
+ if hasattr(tool_source, "root"):
+ help_elem = tool_source.root.find("help")
+ help_header = help_text
+ help_pages = help_elem.findall( "page" )
+ # Multiple help page case
+ if help_pages:
+ for help_page in help_pages:
+ self.__help_by_page.append( help_page.text )
+ help_footer = help_footer + help_page.tail
+ # Each page has to rendered all-together because of backreferences allowed by rst
+ try:
+ self.__help_by_page = [ Template( rst_to_html( help_header + x + help_footer ),
+ input_encoding='utf-8', output_encoding='utf-8',
+ default_filters=[ 'decode.utf8' ],
+ encoding_errors='replace' )
+ for x in self.__help_by_page ]
+ except:
+ log.exception( "error in multi-page help for tool %s" % self.name )
+ # Pad out help pages to match npages ... could this be done better?
+ while len( self.__help_by_page ) < self.npages:
+ self.__help_by_page.append( self.__help )
+
def check_workflow_compatible( self, tool_source ):
"""
Determine if a tool can be used in workflows. External tools and the
diff -r 3af604bcd66681f890ca86d3c7f0395e9d5e8d05 -r 3d7d15601e493d1d7ed6a10814f6ca2b36a611fb lib/galaxy/tools/parser/interface.py
--- a/lib/galaxy/tools/parser/interface.py
+++ b/lib/galaxy/tools/parser/interface.py
@@ -140,6 +140,12 @@
"""
return [], []
+ @abstractmethod
+ def parse_help(self):
+ """ Return RST definition of help text for tool or None if the tool
+ doesn't define help text.
+ """
+
def parse_tests_to_dict(self):
return {'tests': []}
diff -r 3af604bcd66681f890ca86d3c7f0395e9d5e8d05 -r 3d7d15601e493d1d7ed6a10814f6ca2b36a611fb lib/galaxy/tools/parser/xml.py
--- a/lib/galaxy/tools/parser/xml.py
+++ b/lib/galaxy/tools/parser/xml.py
@@ -162,6 +162,10 @@
parser = StdioParser(self.root)
return parser.stdio_exit_codes, parser.stdio_regexes
+ def parse_help(self):
+ help_elem = self.root.find( 'help' )
+ return help_elem.text if help_elem is not None else None
+
def parse_tests_to_dict(self):
tests_elem = self.root.find("tests")
tests = []
diff -r 3af604bcd66681f890ca86d3c7f0395e9d5e8d05 -r 3d7d15601e493d1d7ed6a10814f6ca2b36a611fb lib/galaxy/tools/parser/yaml.py
--- a/lib/galaxy/tools/parser/yaml.py
+++ b/lib/galaxy/tools/parser/yaml.py
@@ -68,6 +68,9 @@
exit_code_lower.error_level = StdioErrorLevel.FATAL
return [exit_code_lower, exit_code_high], []
+ def parse_help(self):
+ return self.root_dict.get("help", None)
+
def parse_outputs(self, tool):
outputs = self.root_dict.get("outputs", {})
output_defs = []
diff -r 3af604bcd66681f890ca86d3c7f0395e9d5e8d05 -r 3d7d15601e493d1d7ed6a10814f6ca2b36a611fb test/unit/tools/test_parsing.py
--- a/test/unit/tools/test_parsing.py
+++ b/test/unit/tools/test_parsing.py
@@ -26,6 +26,7 @@
<stdio><exit_code range="1:" level="fatal" /></stdio>
+ <help>This is HELP TEXT1!!!</help><tests><test><param name="foo" value="5" />
@@ -75,6 +76,9 @@
type: data
- name: nestsample
type: text
+help:
+|
+ This is HELP TEXT2!!!
tests:
- inputs:
foo: 5
@@ -173,6 +177,10 @@
assert exit[0].range_start == 1
assert isinf(exit[0].range_end)
+ def test_help(self):
+ help_text = self._tool_source.parse_help()
+ assert help_text.strip() == "This is HELP TEXT1!!!"
+
def test_tests(self):
tests_dict = self._tool_source.parse_tests_to_dict()
tests = tests_dict["tests"]
@@ -278,6 +286,10 @@
assert exit[1].range_start == 1
assert isinf(exit[1].range_end)
+ def test_help(self):
+ help_text = self._tool_source.parse_help()
+ assert help_text.strip() == "This is HELP TEXT2!!!"
+
def test_inputs(self):
input_pages = self._tool_source.parse_input_pages()
assert input_pages.inputs_defined
https://bitbucket.org/galaxy/galaxy-central/commits/942a98d1548b/
Changeset: 942a98d1548b
User: jmchilton
Date: 2014-12-14 03:09:51+00:00
Summary: PEP-8 fixes for lib/galaxy/tools/search/__init__.py.
Affected #: 1 file
diff -r 3d7d15601e493d1d7ed6a10814f6ca2b36a611fb -r 942a98d1548b9e31960ecb208ae5cb15ae32a6fa lib/galaxy/tools/search/__init__.py
--- a/lib/galaxy/tools/search/__init__.py
+++ b/lib/galaxy/tools/search/__init__.py
@@ -3,11 +3,11 @@
require( "Whoosh" )
from whoosh.filedb.filestore import RamStorage
-from whoosh.fields import Schema, STORED, ID, KEYWORD, TEXT
-from whoosh.index import Index
+from whoosh.fields import Schema, STORED, TEXT
from whoosh.scoring import BM25F
from whoosh.qparser import MultifieldParser
-schema = Schema( id = STORED, title = TEXT, description = TEXT, help = TEXT )
+schema = Schema( id=STORED, title=TEXT, description=TEXT, help=TEXT )
+
class ToolBoxSearch( object ):
"""
@@ -33,10 +33,12 @@
def search( self, query, return_attribute='id' ):
# Change field boosts for searcher to place more weight on title, description than help.
- searcher = self.index.searcher( \
- weighting=BM25F( field_B={ 'title_B' : 3, 'description_B' : 2, 'help_B' : 1 } \
- ) )
+ searcher = self.index.searcher(
+ weighting=BM25F(
+ field_B={ 'title_B': 3, 'description_B': 2, 'help_B': 1 }
+ )
+ )
# Set query to search title, description, and help.
- parser = MultifieldParser( [ 'title', 'description', 'help' ], schema = schema )
+ parser = MultifieldParser( [ 'title', 'description', 'help' ], schema=schema )
results = searcher.search( parser.parse( query ) )
return [ result[ return_attribute ] for result in results ]
https://bitbucket.org/galaxy/galaxy-central/commits/5c44ac15c5a9/
Changeset: 5c44ac15c5a9
User: jmchilton
Date: 2014-12-14 03:09:51+00:00
Summary: Bugfix and enhancement related to indexing tool help in tool search.
Added a config option for disabling indexing of tool config. This combined with the previous commit, shave over a second off (slightly more than 25%) Galaxy's startup time for the default toolbox on my laptop. Enable this option for functional tests - which will likely never care about seaching this index.
When I went to test this - I noticed that searching wasn't actually working for help messages - this is because tool.help is not a string but a mako template - so I switched it over to render the page and stick it in the whoosh index instead of probably some default to string object pointer or something.
Affected #: 2 files
diff -r 942a98d1548b9e31960ecb208ae5cb15ae32a6fa -r 5c44ac15c5a96fad6995435f88fd230ef72a93c5 lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -256,6 +256,9 @@
self.allow_library_path_paste = kwargs.get( 'allow_library_path_paste', False )
self.disable_library_comptypes = kwargs.get( 'disable_library_comptypes', '' ).lower().split( ',' )
self.watch_tools = kwargs.get( 'watch_tools', False )
+ # On can mildly speed up Galaxy startup time by disabling index of help,
+ # not needed on production systems but useful if running many functional tests.
+ self.index_tool_help = string_as_bool( kwargs.get( "index_tool_help", True ) )
# Location for tool dependencies.
if 'tool_dependency_dir' in kwargs:
self.tool_dependency_dir = resolve_path( kwargs.get( "tool_dependency_dir" ), self.root )
@@ -705,7 +708,8 @@
self.toolbox = tools.ToolBox( tool_configs, self.config.tool_path, self )
# Search support for tools
import galaxy.tools.search
- self.toolbox_search = galaxy.tools.search.ToolBoxSearch( self.toolbox )
+ index_help = getattr( self.config, "index_tool_help", True )
+ self.toolbox_search = galaxy.tools.search.ToolBoxSearch( self.toolbox, index_help )
from galaxy.tools.deps import containers
galaxy_root_dir = os.path.abspath(self.config.root)
diff -r 942a98d1548b9e31960ecb208ae5cb15ae32a6fa -r 5c44ac15c5a96fad6995435f88fd230ef72a93c5 lib/galaxy/tools/search/__init__.py
--- a/lib/galaxy/tools/search/__init__.py
+++ b/lib/galaxy/tools/search/__init__.py
@@ -15,20 +15,33 @@
the "whoosh" search library.
"""
- def __init__( self, toolbox ):
+ def __init__( self, toolbox, index_help=True ):
"""
Create a searcher for `toolbox`.
"""
self.toolbox = toolbox
- self.build_index()
+ self.build_index( index_help )
- def build_index( self ):
+ def build_index( self, index_help ):
self.storage = RamStorage()
self.index = self.storage.create_index( schema )
writer = self.index.writer()
## TODO: would also be nice to search section headers.
for id, tool in self.toolbox.tools_by_id.iteritems():
- writer.add_document( id=id, title=to_unicode(tool.name), description=to_unicode(tool.description), help=to_unicode(tool.help) )
+ add_doc_kwds = {
+ "id": id,
+ "title": to_unicode(tool.name),
+ "description": to_unicode(tool.description),
+ "help": to_unicode(""),
+ }
+ if index_help and tool.help:
+ try:
+ add_doc_kwds['help'] = to_unicode(tool.help.render( host_url="", static_path="" ))
+ except Exception:
+ # Don't fail to build index just because a help message
+ # won't render.
+ pass
+ writer.add_document( **add_doc_kwds )
writer.commit()
def search( self, query, return_attribute='id' ):
https://bitbucket.org/galaxy/galaxy-central/commits/6db06b5b370a/
Changeset: 6db06b5b370a
User: jmchilton
Date: 2014-12-14 03:09:51+00:00
Summary: Fix bug with watch tool config option parsing.
Affected #: 1 file
diff -r 5c44ac15c5a96fad6995435f88fd230ef72a93c5 -r 6db06b5b370ae0c955c66d6e2668aaf6260fe221 lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -255,7 +255,7 @@
self.ftp_upload_site = kwargs.get( 'ftp_upload_site', None )
self.allow_library_path_paste = kwargs.get( 'allow_library_path_paste', False )
self.disable_library_comptypes = kwargs.get( 'disable_library_comptypes', '' ).lower().split( ',' )
- self.watch_tools = kwargs.get( 'watch_tools', False )
+ self.watch_tools = string_as_bool( kwargs.get( 'watch_tools', False ) )
# On can mildly speed up Galaxy startup time by disabling index of help,
# not needed on production systems but useful if running many functional tests.
self.index_tool_help = string_as_bool( kwargs.get( "index_tool_help", True ) )
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: Minor de-duplication in test_workflows.py API tests.
by commits-noreply@bitbucket.org 13 Dec '14
by commits-noreply@bitbucket.org 13 Dec '14
13 Dec '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/fa061192b5d9/
Changeset: fa061192b5d9
User: jmchilton
Date: 2014-12-13 18:02:35+00:00
Summary: Minor de-duplication in test_workflows.py API tests.
Affected #: 1 file
diff -r e2a806ffe023a33b43013323cc537967f1346ff3 -r fa061192b5d9c6df7ef65edb39a846e5a9a227bf test/api/test_workflows.py
--- a/test/api/test_workflows.py
+++ b/test/api/test_workflows.py
@@ -247,9 +247,8 @@
# Test annotations preserved during upload and copied over during
# import.
other_id = other_import_response.json()["id"]
- download_response = self._get( "workflows/%s" % other_id )
- imported_workflow = download_response.json()
- assert imported_workflow["annotation"] == "simple workflow", download_response.json()
+ imported_workflow = self._show_workflow( other_id )
+ assert imported_workflow["annotation"] == "simple workflow"
step_annotations = set(map(lambda step: step["annotation"], imported_workflow["steps"].values()))
assert "input1 description" in step_annotations
@@ -268,9 +267,7 @@
def test_export( self ):
uploaded_workflow_id = self.workflow_populator.simple_workflow( "test_for_export" )
- download_response = self._get( "workflows/%s/download" % uploaded_workflow_id )
- self._assert_status_code_is( download_response, 200 )
- downloaded_workflow = download_response.json()
+ downloaded_workflow = self._download_workflow( uploaded_workflow_id )
assert downloaded_workflow[ "name" ] == "test_for_export (imported from API)"
assert len( downloaded_workflow[ "steps" ] ) == 3
first_input = downloaded_workflow[ "steps" ][ "0" ][ "inputs" ][ 0 ]
@@ -280,8 +277,7 @@
def test_import_export_with_runtime_inputs( self ):
workflow = self.workflow_populator.load_workflow_from_resource( name="test_workflow_with_runtime_input" )
workflow_id = self.workflow_populator.create_workflow( workflow )
- download_response = self._get( "workflows/%s/download" % workflow_id )
- downloaded_workflow = download_response.json()
+ downloaded_workflow = self._download_workflow( workflow_id )
assert len( downloaded_workflow[ "steps" ] ) == 2
runtime_input = downloaded_workflow[ "steps" ][ "1" ][ "inputs" ][ 0 ]
assert runtime_input[ "description" ].startswith( "runtime parameter for tool" )
@@ -483,8 +479,7 @@
last_step_map = self._step_map( workflow )
for i in range(num_tests):
uploaded_workflow_id = self.workflow_populator.create_workflow( workflow )
- download_response = self._get( "workflows/%s/download" % uploaded_workflow_id )
- downloaded_workflow = download_response.json()
+ downloaded_workflow = self._download_workflow( uploaded_workflow_id )
step_map = self._step_map(downloaded_workflow)
assert step_map == last_step_map
last_step_map = step_map
@@ -589,8 +584,7 @@
def test_pja_import_export( self ):
workflow = self.workflow_populator.load_workflow( name="test_for_pja_import", add_pja=True )
uploaded_workflow_id = self.workflow_populator.create_workflow( workflow )
- download_response = self._get( "workflows/%s/download" % uploaded_workflow_id )
- downloaded_workflow = download_response.json()
+ downloaded_workflow = self._download_workflow( uploaded_workflow_id )
self._assert_has_keys( downloaded_workflow[ "steps" ], "0", "1", "2" )
pjas = downloaded_workflow[ "steps" ][ "2" ][ "post_job_actions" ].values()
assert len( pjas ) == 1, len( pjas )
@@ -726,6 +720,15 @@
)
return self._post( route, import_data )
+ def _download_workflow(self, workflow_id):
+ download_response = self._get( "workflows/%s/download" % workflow_id )
+ self._assert_status_code_is( download_response, 200 )
+ downloaded_workflow = download_response.json()
+ return downloaded_workflow
+
+ def _show_workflow(self, workflow_id):
+ show_response = self._get( "workflows/%s" % workflow_id )
+ self._assert_status_code_is( show_response, 200 )
+ return show_response.json()
RunJobsSummary = namedtuple('RunJobsSummary', ['history_id', 'workflow_id', 'inputs', 'jobs'])
-
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