galaxy-commits
Threads by month
- ----- 2026 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- 15302 discussions
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/a17810e4f3ce/
Changeset: a17810e4f3ce
User: jmchilton
Date: 2014-03-14 16:12:41
Summary: Check ftype attribute (if defined) on test output datasets.
Only works for newer API driven tests.
Affected #: 2 files
diff -r 3fb927653301a0c06a0bf94f2b6bd71b3595ec0d -r a17810e4f3ce1fce18565b33c1306885dfb3ef38 test/base/interactor.py
--- a/test/base/interactor.py
+++ b/test/base/interactor.py
@@ -45,13 +45,20 @@
fetcher = self.__dataset_fetcher( history_id )
## TODO: Twill version verifys dataset is 'ok' in here.
self.twill_test_case.verify_hid( outfile, hda_id=hid, attributes=attributes, dataset_fetcher=fetcher, shed_tool_id=shed_tool_id )
- metadata = attributes.get( 'metadata', {} )
+ metadata = attributes.get( 'metadata', {} ).copy()
+ for key, value in metadata.copy().iteritems():
+ new_key = "metadata_%s" % key
+ metadata[ new_key ] = metadata[ key ]
+ del metadata[ key ]
+ expected_file_type = attributes.get( 'ftype', None )
+ if expected_file_type:
+ metadata[ "file_ext" ] = expected_file_type
+
if metadata:
dataset = self._get( "histories/%s/contents/%s" % ( history_id, hid ) ).json()
for key, value in metadata.iteritems():
- dataset_key = "metadata_%s" % key
try:
- dataset_value = dataset.get( dataset_key, None )
+ dataset_value = dataset.get( key, None )
if dataset_value != value:
msg = "Dataset metadata verification for [%s] failed, expected [%s] but found [%s]."
msg_params = ( key, value, dataset_value )
diff -r 3fb927653301a0c06a0bf94f2b6bd71b3595ec0d -r a17810e4f3ce1fce18565b33c1306885dfb3ef38 test/functional/tools/metadata.xml
--- a/test/functional/tools/metadata.xml
+++ b/test/functional/tools/metadata.xml
@@ -16,7 +16,7 @@
<metadata name="base_name" value="Example Metadata" /></param><!-- This ouptut tests setting input metadata above -->
- <output name="output_of_input_metadata">
+ <output name="output_of_input_metadata" ftype="txt"><assert_contents><has_line line="Example Metadata" /></assert_contents>
https://bitbucket.org/galaxy/galaxy-central/commits/327583f6b058/
Changeset: 327583f6b058
User: jmchilton
Date: 2014-03-18 18:42:23
Summary: Merged in jmchilton/galaxy-central-fork-1 (pull request #347)
Check ftype attribute (if defined) on test output datasets.
Affected #: 2 files
diff -r 0a9263af45ec678bea82a6d128f49cf5372d370d -r 327583f6b0581c2bdf22b933b2659298af2ed699 test/base/interactor.py
--- a/test/base/interactor.py
+++ b/test/base/interactor.py
@@ -45,13 +45,20 @@
fetcher = self.__dataset_fetcher( history_id )
## TODO: Twill version verifys dataset is 'ok' in here.
self.twill_test_case.verify_hid( outfile, hda_id=hid, attributes=attributes, dataset_fetcher=fetcher, shed_tool_id=shed_tool_id )
- metadata = attributes.get( 'metadata', {} )
+ metadata = attributes.get( 'metadata', {} ).copy()
+ for key, value in metadata.copy().iteritems():
+ new_key = "metadata_%s" % key
+ metadata[ new_key ] = metadata[ key ]
+ del metadata[ key ]
+ expected_file_type = attributes.get( 'ftype', None )
+ if expected_file_type:
+ metadata[ "file_ext" ] = expected_file_type
+
if metadata:
dataset = self._get( "histories/%s/contents/%s" % ( history_id, hid ) ).json()
for key, value in metadata.iteritems():
- dataset_key = "metadata_%s" % key
try:
- dataset_value = dataset.get( dataset_key, None )
+ dataset_value = dataset.get( key, None )
if dataset_value != value:
msg = "Dataset metadata verification for [%s] failed, expected [%s] but found [%s]."
msg_params = ( key, value, dataset_value )
diff -r 0a9263af45ec678bea82a6d128f49cf5372d370d -r 327583f6b0581c2bdf22b933b2659298af2ed699 test/functional/tools/metadata.xml
--- a/test/functional/tools/metadata.xml
+++ b/test/functional/tools/metadata.xml
@@ -16,7 +16,7 @@
<metadata name="base_name" value="Example Metadata" /></param><!-- This ouptut tests setting input metadata above -->
- <output name="output_of_input_metadata">
+ <output name="output_of_input_metadata" ftype="txt"><assert_contents><has_line line="Example Metadata" /></assert_contents>
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: dannon: Update boto to 2.27.0 -- thirequired update to support VPC support that worked incorrectly in previous versions.
by commits-noreply@bitbucket.org 18 Mar '14
by commits-noreply@bitbucket.org 18 Mar '14
18 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0a9263af45ec/
Changeset: 0a9263af45ec
User: dannon
Date: 2014-03-18 16:04:55
Summary: Update boto to 2.27.0 -- thirequired update to support VPC support that worked incorrectly in previous versions.
Affected #: 1 file
diff -r 91aa1db362c9f5c76160b09bab52b8a77b0f30b0 -r 0a9263af45ec678bea82a6d128f49cf5372d370d eggs.ini
--- a/eggs.ini
+++ b/eggs.ini
@@ -35,7 +35,7 @@
amqp = 1.4.3
anyjson = 0.3.3
Beaker = 1.4
-boto = 2.25.0
+boto = 2.27.0
decorator = 3.1.2
docutils = 0.7
drmaa = 0.6
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: carlfeberhard: Saved visualizations: fix to display by slug to render built-in visualizations using original display code
by commits-noreply@bitbucket.org 18 Mar '14
by commits-noreply@bitbucket.org 18 Mar '14
18 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/91aa1db362c9/
Changeset: 91aa1db362c9
User: carlfeberhard
Date: 2014-03-18 14:48:47
Summary: Saved visualizations: fix to display by slug to render built-in visualizations using original display code
Affected #: 1 file
diff -r dea106a05af8597b66846bde6d28bfe97a8ea338 -r 91aa1db362c9f5c76160b09bab52b8a77b0f30b0 lib/galaxy/webapps/galaxy/controllers/visualization.py
--- a/lib/galaxy/webapps/galaxy/controllers/visualization.py
+++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py
@@ -584,7 +584,8 @@
ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, visualization )
# Display.
- if trans.app.visualizations_registry and visualization.type in trans.app.visualizations_registry.plugins:
+ if( ( trans.app.visualizations_registry and visualization.type in trans.app.visualizations_registry.plugins )
+ and ( visualization.type not in trans.app.visualizations_registry.BUILT_IN_VISUALIZATIONS ) ):
# if a registry visualization, load a version of display.mako that will load the vis into an iframe :(
#TODO: simplest path from A to B but not optimal - will be difficult to do reg visualizations any other way
#TODO: this will load the visualization twice (once above, once when the iframe src calls 'saved')
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/6b4139f8f40c/
Changeset: 6b4139f8f40c
User: mike8115
Date: 2014-02-22 02:32:01
Summary: Trello Card #1437: Optional Input Datasets Not Compatible with Parallelism Tag
multi.py edited to check for input.data is None.
Affected #: 1 file
diff -r 2403d6f67500ec35b16ce604142d98766286b65a -r 6b4139f8f40cda2bc21230fd4b32444474bfea8d lib/galaxy/jobs/splitters/multi.py
--- a/lib/galaxy/jobs/splitters/multi.py
+++ b/lib/galaxy/jobs/splitters/multi.py
@@ -44,18 +44,26 @@
# For things like paired end alignment, we need two inputs to be split. Since all inputs to all
# derived subtasks need to be correlated, allow only one input type to be split
+ # If shared_inputs are not specified, we assume all non-split inputs are shared inputs.
+ # For any input we must consider if each input is None. With optional arguments, a data input could be set to optional
type_to_input_map = {}
for input in parent_job.input_datasets:
- if input.name in split_inputs:
- type_to_input_map.setdefault(input.dataset.datatype, []).append(input.name)
- elif input.name in shared_inputs:
- pass # pass original file name
- elif auto_shared_inputs:
- shared_inputs.append(input.name)
+ if input.dataset is None:
+ if input.name in shared_inputs:
+ shared_inputs.remove(input.name)
+ else:
+ pass
else:
- log_error = "The input '%s' does not define a method for implementing parallelism" % str(input.name)
- log.exception(log_error)
- raise Exception(log_error)
+ if input.name in split_inputs:
+ type_to_input_map.setdefault(input.dataset.datatype, []).append(input.name)
+ elif input.name in shared_inputs:
+ pass # pass original file name
+ elif auto_shared_inputs:
+ shared_inputs.append(input.name)
+ else:
+ log_error = "The input '%s' does not define a method for implementing parallelism" % str(input.name)
+ log.exception(log_error)
+ raise Exception(log_error)
if len(type_to_input_map) > 1:
log_error = "The multi splitter does not support splitting inputs of more than one type"
https://bitbucket.org/galaxy/galaxy-central/commits/dea106a05af8/
Changeset: dea106a05af8
User: dannon
Date: 2014-03-18 13:57:36
Summary: Merged in mike8115/galaxy-central (pull request #334)
Trello Card #1437: Optional Input Datasets Not Compatible with Parallelism Tag
Affected #: 1 file
diff -r c53fbb2655e2756c8977782df2381d883c5aa8af -r dea106a05af8597b66846bde6d28bfe97a8ea338 lib/galaxy/jobs/splitters/multi.py
--- a/lib/galaxy/jobs/splitters/multi.py
+++ b/lib/galaxy/jobs/splitters/multi.py
@@ -44,18 +44,26 @@
# For things like paired end alignment, we need two inputs to be split. Since all inputs to all
# derived subtasks need to be correlated, allow only one input type to be split
+ # If shared_inputs are not specified, we assume all non-split inputs are shared inputs.
+ # For any input we must consider if each input is None. With optional arguments, a data input could be set to optional
type_to_input_map = {}
for input in parent_job.input_datasets:
- if input.name in split_inputs:
- type_to_input_map.setdefault(input.dataset.datatype, []).append(input.name)
- elif input.name in shared_inputs:
- pass # pass original file name
- elif auto_shared_inputs:
- shared_inputs.append(input.name)
+ if input.dataset is None:
+ if input.name in shared_inputs:
+ shared_inputs.remove(input.name)
+ else:
+ pass
else:
- log_error = "The input '%s' does not define a method for implementing parallelism" % str(input.name)
- log.exception(log_error)
- raise Exception(log_error)
+ if input.name in split_inputs:
+ type_to_input_map.setdefault(input.dataset.datatype, []).append(input.name)
+ elif input.name in shared_inputs:
+ pass # pass original file name
+ elif auto_shared_inputs:
+ shared_inputs.append(input.name)
+ else:
+ log_error = "The input '%s' does not define a method for implementing parallelism" % str(input.name)
+ log.exception(log_error)
+ raise Exception(log_error)
if len(type_to_input_map) > 1:
log_error = "The multi splitter does not support splitting inputs of more than one type"
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: jmchilton: Add tool functional test for parallelism with optional inputs.
by commits-noreply@bitbucket.org 17 Mar '14
by commits-noreply@bitbucket.org 17 Mar '14
17 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/c53fbb2655e2/
Changeset: c53fbb2655e2
User: jmchilton
Date: 2014-03-17 02:41:42
Summary: Add tool functional test for parallelism with optional inputs.
This functionality is broken without patch from pull request #334. To run this broken test:
% # First two exports are optional but speed up testing and force newest test framework.
% export GALAXY_TEST_DEFAULT_INTERACTOR=api;
% export GALAXY_TEST_DB_TEMPLATE=https://github.com/jmchilton/galaxy-bootstrap/raw/m…
% ./run_functional_tests.sh -framework -id parallelism_optional
Affected #: 2 files
diff -r 11e83ed03dab7f658e3b08e29be25768f3546c72 -r c53fbb2655e2756c8977782df2381d883c5aa8af test/functional/tools/parallelism_optional.xml
--- /dev/null
+++ b/test/functional/tools/parallelism_optional.xml
@@ -0,0 +1,21 @@
+<tool id="parallelism_optional" name="Split file line-by-line and rebuild dataset (with optional dataset)">
+ <parallelism method="multi" split_inputs="input1" split_mode="to_size" split_size="1" merge_outputs="out_file1" />
+ <command>
+ cat $input1 > $out_file1
+ </command>
+ <inputs>
+ <param name="input1" type="data" label="Dataset"/>
+ <param name="input2" type="data" label="Optional Dataset" optional="true" />
+ </inputs>
+ <outputs>
+ <data name="out_file1" format="txt" />
+ </outputs>
+ <tests>
+ <test>
+ <param name="input1" value="simple_line_x2.txt"/>
+ <output name="out_file1" file="simple_line_x2.txt" />
+ </test>
+ </tests>
+ <help>
+ </help>
+</tool>
diff -r 11e83ed03dab7f658e3b08e29be25768f3546c72 -r c53fbb2655e2756c8977782df2381d883c5aa8af test/functional/tools/samples_tool_conf.xml
--- a/test/functional/tools/samples_tool_conf.xml
+++ b/test/functional/tools/samples_tool_conf.xml
@@ -14,6 +14,7 @@
<tool file="disambiguate_repeats.xml" /><tool file="min_repeat.xml" /><tool file="parallelism.xml" />
+ <tool file="parallelism_optional.xml" /><tool file="implicit_default_conds.xml" /><tool file="multi_data_param.xml" /></toolbox>
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/6007992cbcd3/
Changeset: 6007992cbcd3
Branch: stable
User: nsoranzo
Date: 2014-03-14 15:21:25
Summary: Fix for https://trello.com/c/8NpgYM3U/1430-tool-panel-label-bug-introduced-with-201…
Bug was introduced in commit 5f6c670dca9f42d52480f66afeb91b63710e7ffb .
Affected #: 1 file
diff -r ac4e18e81ed5d9256cb1f07692da7928dcd7969f -r 6007992cbcd3626cbed38b0e883f516b016f98be lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -276,7 +276,7 @@
section = ToolSection( elem )
log.debug( "Loading section: %s" % elem.get( 'name' ) )
for section_key, section_val in val.elems.items():
- if section_key.startswith( 'tool_' ):
+ if isinstance( section_val, Tool ):
tool_id = section_key.replace( 'tool_', '', 1 )
if tool_id in self.tools_by_id:
self.__add_tool_to_tool_panel( tool_id, section, section=True )
@@ -286,7 +286,7 @@
workflow = self.workflows_by_id[ workflow_id ]
section.elems[ section_key ] = workflow
log.debug( "Loaded workflow: %s %s" % ( workflow_id, workflow.name ) )
- elif isinstance( val, ToolSectionLabel ):
+ elif isinstance( section_val, ToolSectionLabel ):
if section_val:
section.elems[ section_key ] = section_val
log.debug( "Loaded label: %s" % ( section_val.text ) )
https://bitbucket.org/galaxy/galaxy-central/commits/11e83ed03dab/
Changeset: 11e83ed03dab
User: jmchilton
Date: 2014-03-17 02:12:20
Summary: Merge latest stable.
Affected #: 1 file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/d3427fa33560/
Changeset: d3427fa33560
User: nsoranzo
Date: 2014-03-14 15:21:25
Summary: Fix for https://trello.com/c/8NpgYM3U/1430-tool-panel-label-bug-introduced-with-201…
Bug was introduced in commit 5f6c670dca9f42d52480f66afeb91b63710e7ffb .
Affected #: 1 file
diff -r 3fb927653301a0c06a0bf94f2b6bd71b3595ec0d -r d3427fa335602629611c7aedbc52196b3a0eea64 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -285,7 +285,7 @@
section = ToolSection( elem )
log.debug( "Loading section: %s" % elem.get( 'name' ) )
for section_key, section_val in val.elems.items():
- if section_key.startswith( 'tool_' ):
+ if isinstance( section_val, Tool ):
tool_id = section_key.replace( 'tool_', '', 1 )
if tool_id in self.tools_by_id:
self.__add_tool_to_tool_panel( tool_id, section, section=True )
@@ -295,7 +295,7 @@
workflow = self.workflows_by_id[ workflow_id ]
section.elems[ section_key ] = workflow
log.debug( "Loaded workflow: %s %s" % ( workflow_id, workflow.name ) )
- elif isinstance( val, ToolSectionLabel ):
+ elif isinstance( section_val, ToolSectionLabel ):
if section_val:
section.elems[ section_key ] = section_val
log.debug( "Loaded label: %s" % ( section_val.text ) )
https://bitbucket.org/galaxy/galaxy-central/commits/b58efefe1060/
Changeset: b58efefe1060
User: jmchilton
Date: 2014-03-17 02:10:01
Summary: Merged in nsoranzo/galaxy-central (pull request #346)
Fix for https://trello.com/c/8NpgYM3U/1430-tool-panel-label-bug-introduced-with-201…
Affected #: 1 file
diff -r 35e90d31c3c78e4710a0f73a0126ddbd9ca8397a -r b58efefe106088d30e234373363e3cb0d6434954 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -285,7 +285,7 @@
section = ToolSection( elem )
log.debug( "Loading section: %s" % elem.get( 'name' ) )
for section_key, section_val in val.elems.items():
- if section_key.startswith( 'tool_' ):
+ if isinstance( section_val, Tool ):
tool_id = section_key.replace( 'tool_', '', 1 )
if tool_id in self.tools_by_id:
self.__add_tool_to_tool_panel( tool_id, section, section=True )
@@ -295,7 +295,7 @@
workflow = self.workflows_by_id[ workflow_id ]
section.elems[ section_key ] = workflow
log.debug( "Loaded workflow: %s %s" % ( workflow_id, workflow.name ) )
- elif isinstance( val, ToolSectionLabel ):
+ elif isinstance( section_val, ToolSectionLabel ):
if section_val:
section.elems[ section_key ] = section_val
log.debug( "Loaded label: %s" % ( section_val.text ) )
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: PEP-8 fixes, var name fixes, and unit tests for SecurityHelper.
by commits-noreply@bitbucket.org 17 Mar '14
by commits-noreply@bitbucket.org 17 Mar '14
17 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/35e90d31c3c7/
Changeset: 35e90d31c3c7
User: jmchilton
Date: 2014-03-17 01:55:47
Summary: PEP-8 fixes, var name fixes, and unit tests for SecurityHelper.
Tests cover nearly all functionality - in particular newly added per-kind key generation.
Affected #: 2 files
diff -r 2727f16171610e0e27420d780e9a5ed56f1908f5 -r 35e90d31c3c78e4710a0f73a0126ddbd9ca8397a lib/galaxy/web/security/__init__.py
--- a/lib/galaxy/web/security/__init__.py
+++ b/lib/galaxy/web/security/__init__.py
@@ -1,5 +1,7 @@
import collections
-import os, os.path, logging
+import os
+import os.path
+import logging
import pkg_resources
pkg_resources.require( "pycrypto" )
@@ -13,6 +15,7 @@
if os.path.exists( "/dev/urandom" ):
# We have urandom, use it as the source of random data
random_fd = os.open( "/dev/urandom", os.O_RDONLY )
+
def get_random_bytes( nbytes ):
value = os.read( random_fd, nbytes )
# Normally we should get as much as we need
@@ -39,7 +42,7 @@
self.id_cipher = Blowfish.new( self.id_secret )
per_kind_id_secret_base = config.get( 'per_kind_id_secret_base', self.id_secret )
- self.id_ciphers_for_key = _cipher_cache( per_kind_id_secret_base )
+ self.id_ciphers_for_kind = _cipher_cache( per_kind_id_secret_base )
def encode_id( self, obj_id, kind=None ):
id_cipher = self.__id_cipher( kind )
@@ -84,7 +87,7 @@
if not kind:
id_cipher = self.id_cipher
else:
- id_cipher = self.id_ciphers_for_key[ kind ]
+ id_cipher = self.id_ciphers_for_kind[ kind ]
return id_cipher
diff -r 2727f16171610e0e27420d780e9a5ed56f1908f5 -r 35e90d31c3c78e4710a0f73a0126ddbd9ca8397a test/unit/test_security_helper.py
--- /dev/null
+++ b/test/unit/test_security_helper.py
@@ -0,0 +1,53 @@
+from galaxy.web import security
+
+
+test_helper_1 = security.SecurityHelper( id_secret="sec1" )
+test_helper_2 = security.SecurityHelper( id_secret="sec2" )
+
+
+def test_encode_decode():
+ # Different ids are encoded differently
+ assert test_helper_1.encode_id( 1 ) != test_helper_1.encode_id( 2 )
+ # But decoding and encoded id brings back to original id
+ assert 1 == test_helper_1.decode_id( test_helper_1.encode_id( 1 ) )
+
+
+def test_per_kind_encode_deocde():
+ # Different ids are encoded differently
+ assert test_helper_1.encode_id( 1, kind="k1" ) != test_helper_1.encode_id( 2, kind="k1" )
+ # But decoding and encoded id brings back to original id
+ assert 1 == test_helper_1.decode_id( test_helper_1.encode_id( 1, kind="k1" ), kind="k1" )
+
+
+def test_different_secrets_encode_differently():
+ assert test_helper_1.encode_id( 1 ) != test_helper_2.encode_id( 1 )
+
+
+def test_per_kind_encodes_id_differently():
+ assert test_helper_1.encode_id( 1 ) != test_helper_2.encode_id( 1, kind="new_kind" )
+
+
+def test_encode_dict():
+ test_dict = dict(
+ id=1,
+ other=2,
+ history_id=3,
+ )
+ encoded_dict = test_helper_1.encode_dict_ids( test_dict )
+ assert encoded_dict[ "id" ] == test_helper_1.encode_id( 1 )
+ assert encoded_dict[ "other" ] == 2
+ assert encoded_dict[ "history_id" ] == test_helper_1.encode_id( 3 )
+
+
+def test_guid_generation():
+ guids = set()
+ for i in range( 100 ):
+ guids.add( test_helper_1.get_new_guid() )
+ assert len( guids ) == 100 # Not duplicate guids generated.
+
+
+def test_encode_decode_guid():
+ session_key = test_helper_1.get_new_guid()
+ encoded_key = test_helper_1.encode_guid( session_key )
+ decoded_key = test_helper_1.decode_guid( encoded_key ).encode( "utf-8" )
+ assert session_key == decoded_key, "%s != %s" % ( session_key, decoded_key )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/d847b6985330/
Changeset: d847b6985330
User: jmchilton
Date: 2014-02-20 16:37:17
Summary: Extend SecurityHelper to allow generation of different "kind"s of keys.
There has been talk about including the object's class into the hash when generating keys so the ids are not overlapping between model types. This change could allow that, but my immediate desire is to just to create single-purpose per job keys to allow external job running code to access files on behalf of a given job.
Affected #: 1 file
diff -r 9c673e0ea8919fea6941f24fdf6936686840bb71 -r d847b69853302d9a127c31c612dd19cc7ffa9f61 lib/galaxy/web/security/__init__.py
--- a/lib/galaxy/web/security/__init__.py
+++ b/lib/galaxy/web/security/__init__.py
@@ -1,3 +1,4 @@
+import collections
import os, os.path, logging
import pkg_resources
@@ -37,27 +38,32 @@
self.id_secret = config['id_secret']
self.id_cipher = Blowfish.new( self.id_secret )
- def encode_id( self, obj_id ):
+ per_kind_id_secret_base = config.get( 'per_kind_id_secret_base', self.id_secret )
+ self.id_ciphers_for_key = _cipher_cache( per_kind_id_secret_base )
+
+ def encode_id( self, obj_id, kind=None ):
+ id_cipher = self.__id_cipher( kind )
# Convert to string
s = str( obj_id )
# Pad to a multiple of 8 with leading "!"
s = ( "!" * ( 8 - len(s) % 8 ) ) + s
# Encrypt
- return self.id_cipher.encrypt( s ).encode( 'hex' )
+ return id_cipher.encrypt( s ).encode( 'hex' )
- def encode_dict_ids( self, a_dict ):
+ def encode_dict_ids( self, a_dict, kind=None ):
"""
Encode all ids in dictionary. Ids are identified by (a) an 'id' key or
(b) a key that ends with '_id'
"""
for key, val in a_dict.items():
if key == 'id' or key.endswith('_id'):
- a_dict[ key ] = self.encode_id( val )
+ a_dict[ key ] = self.encode_id( val, kind=kind )
return a_dict
- def decode_id( self, obj_id ):
- return int( self.id_cipher.decrypt( obj_id.decode( 'hex' ) ).lstrip( "!" ) )
+ def decode_id( self, obj_id, kind=None ):
+ id_cipher = self.__id_cipher( kind )
+ return int( id_cipher.decrypt( obj_id.decode( 'hex' ) ).lstrip( "!" ) )
def encode_guid( self, session_key ):
# Session keys are strings
@@ -73,3 +79,19 @@
def get_new_guid( self ):
# Generate a unique, high entropy 128 bit random number
return get_random_bytes( 16 )
+
+ def __id_cipher( self, kind ):
+ if not kind:
+ id_cipher = self.id_cipher
+ else:
+ id_cipher = self.id_ciphers_for_key[ kind ]
+ return id_cipher
+
+
+class _cipher_cache( collections.defaultdict ):
+
+ def __init__( self, secret_base ):
+ self.secret_base = secret_base
+
+ def __missing__( self, key ):
+ return Blowfish.new( self.secret_base + "__" + key )
https://bitbucket.org/galaxy/galaxy-central/commits/58acdb670e4b/
Changeset: 58acdb670e4b
User: jmchilton
Date: 2014-02-20 16:37:17
Summary: Implement API allowing access to a job's files.
Provides a mechanism for remote job execution mechanisms to read and write files on behalf of an "active" jobs.
For a variety of reasons simply providing access to datasets is insuccifient for this - there are working directory files needed for metadata calculation, inputs and outputs may be of intermediate form (task split files for instance), tool files, location files, etc.... Therefore this API endpoint provides access to a jobs view of these files.
Some attempt is made to verify the paths that are written to are valid for the supplied job (they either correspond to the output datasets of the job or the working directory of the job). Authorizing suchs paths for reading is much more difficult and left undone due to the unstructure and arbitrary nature of .loc files.
To implement this securely and with minimal configuration required - single-purpose "job_key"s are generated to authenticate these API calls. Each key allow usage of the API only for a single, active job.
Update LWR job runner to be able to leverage this API to stage files remotely via HTTP for "remote_transfer" actions (added recently to the LWR client).
Affected #: 6 files
diff -r d847b69853302d9a127c31c612dd19cc7ffa9f61 -r 58acdb670e4b8ea9a35d3b19aaab1e66c5b75009 lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -28,6 +28,10 @@
NO_REMOTE_GALAXY_FOR_METADATA_MESSAGE = "LWR misconfiguration - LWR client configured to set metadata remotely, but remote LWR isn't properly configured with a galaxy_home directory."
NO_REMOTE_DATATYPES_CONFIG = "LWR client is configured to use remote datatypes configuration when setting metadata externally, but LWR is not configured with this information. Defaulting to datatypes_conf.xml."
+# Is there a good way to infer some default for this? Can only use
+# url_for from web threads. https://gist.github.com/jmchilton/9098762
+DEFAULT_GALAXY_URL = "http://localhost:8080"
+
class LwrJobRunner( AsynchronousJobRunner ):
"""
@@ -35,13 +39,14 @@
"""
runner_name = "LWRRunner"
- def __init__( self, app, nworkers, transport=None, cache=None, url=None ):
+ def __init__( self, app, nworkers, transport=None, cache=None, url=None, galaxy_url=DEFAULT_GALAXY_URL ):
"""Start the job runner """
super( LwrJobRunner, self ).__init__( app, nworkers )
self.async_status_updates = dict()
self._init_monitor_thread()
self._init_worker_threads()
client_manager_kwargs = {'transport_type': transport, 'cache': string_as_bool_or_none(cache), "url": url}
+ self.galaxy_url = galaxy_url
self.client_manager = build_client_manager(**client_manager_kwargs)
def url_to_destination( self, url ):
@@ -224,7 +229,21 @@
return self.get_client( job_destination_params, job_id )
def get_client( self, job_destination_params, job_id ):
- return self.client_manager.get_client( job_destination_params, str( job_id ) )
+ # Cannot use url_for outside of web thread.
+ #files_endpoint = url_for( controller="job_files", job_id=encoded_job_id )
+
+ encoded_job_id = self.app.security.encode_id(job_id)
+ job_key = self.app.security.encode_id( job_id, kind="jobs_files" )
+ files_endpoint = "%s/api/jobs/%s/files?job_key=%s" % (
+ self.galaxy_url,
+ encoded_job_id,
+ job_key
+ )
+ get_client_kwds = dict(
+ job_id=str( job_id ),
+ files_endpoint=files_endpoint,
+ )
+ return self.client_manager.get_client( job_destination_params, **get_client_kwds )
def finish_job( self, job_state ):
stderr = stdout = ''
diff -r d847b69853302d9a127c31c612dd19cc7ffa9f61 -r 58acdb670e4b8ea9a35d3b19aaab1e66c5b75009 lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -266,6 +266,16 @@
self.handler = None
self.exit_code = None
+ @property
+ def finished( self ):
+ states = self.states
+ return self.state in [
+ states.OK,
+ states.ERROR,
+ states.DELETED,
+ states.DELETED_NEW,
+ ]
+
# TODO: Add accessors for members defined in SQL Alchemy for the Job table and
# for the mapper defined to the Job table.
def get_external_output_metadata( self ):
diff -r d847b69853302d9a127c31c612dd19cc7ffa9f61 -r 58acdb670e4b8ea9a35d3b19aaab1e66c5b75009 lib/galaxy/web/__init__.py
--- a/lib/galaxy/web/__init__.py
+++ b/lib/galaxy/web/__init__.py
@@ -20,3 +20,4 @@
from framework import _future_expose_api
from framework import _future_expose_api_anonymous
from framework import _future_expose_api_raw
+from framework import _future_expose_api_raw_anonymous
diff -r d847b69853302d9a127c31c612dd19cc7ffa9f61 -r 58acdb670e4b8ea9a35d3b19aaab1e66c5b75009 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -278,6 +278,10 @@
return _future_expose_api( func, to_json=False, user_required=True )
+def _future_expose_api_raw_anonymous( func ):
+ return _future_expose_api( func, to_json=False, user_required=False )
+
+
# TODO: rename as expose_api and make default.
def _future_expose_api( func, to_json=True, user_required=True ):
"""
diff -r d847b69853302d9a127c31c612dd19cc7ffa9f61 -r 58acdb670e4b8ea9a35d3b19aaab1e66c5b75009 lib/galaxy/webapps/galaxy/api/job_files.py
--- /dev/null
+++ b/lib/galaxy/webapps/galaxy/api/job_files.py
@@ -0,0 +1,142 @@
+""" API for asynchronous job running mechanisms can use to fetch or put files
+related to running and queued jobs.
+"""
+import os
+import shutil
+
+from galaxy import exceptions
+from galaxy import util
+from galaxy import model
+from galaxy.web.base.controller import BaseAPIController
+from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous
+from galaxy.web import _future_expose_api_raw_anonymous as expose_api_raw_anonymous
+
+
+import logging
+log = logging.getLogger( __name__ )
+
+
+class JobFilesAPIController( BaseAPIController ):
+ """ This job files controller allows remote job running mechanisms to
+ read and modify the current state of files for queued and running jobs.
+ It is certainly not meant to represent part of Galaxy's stable, user
+ facing API.
+
+ Furthermore, even if a user key corresponds to the user running the job,
+ it should not be accepted for authorization - this API allows access to
+ low-level unfiltered files and such authorization would break Galaxy's
+ security model for tool execution.
+ """
+
+ @expose_api_raw_anonymous
+ def index( self, trans, job_id, **kwargs ):
+ """
+ index( self, trans, job_id, **kwargs )
+ * GET /api/jobs/{job_id}/files
+ Get a file required to staging a job (proper datasets, extra inputs,
+ task-split inputs, working directory files).
+
+ :type job_id: str
+ :param job_id: encoded id string of the job
+ :type path: str
+ :param path: Path to file.
+ :type job_key: str
+ :param job_key: A key used to authenticate this request as acting on
+ behalf or a job runner for the specified job.
+ ..note:
+ This API method is intended only for consumption by job runners,
+ not end users.
+
+ :rtype: binary
+ :returns: contents of file
+ """
+ self.__authorize_job_access( trans, job_id, **kwargs )
+ path = kwargs.get("path", None)
+ return open(path, 'rb')
+
+ @expose_api_anonymous
+ def create( self, trans, job_id, payload, **kwargs ):
+ """
+ create( self, trans, job_id, payload, **kwargs )
+ * POST /api/jobs/{job_id}/files
+ Populate an output file (formal dataset, task split part, working
+ directory file (such as those related to metadata)). This should be
+ a multipart post with a 'file' parameter containing the contents of
+ the actual file to create.
+
+ :type job_id: str
+ :param job_id: encoded id string of the job
+ :type payload: dict
+ :param payload: dictionary structure containing::
+ 'job_key' = Key authenticating
+ 'path' = Path to file to create.
+
+ ..note:
+ This API method is intended only for consumption by job runners,
+ not end users.
+
+ :rtype: dict
+ :returns: an okay message
+ """
+ job = self.__authorize_job_access( trans, job_id, **payload )
+ path = payload.get( "path" )
+ self.__check_job_can_write_to_path( trans, job, path )
+
+ # Is this writing an unneeded file? Should this just copy in Python?
+ input_file = payload.get( "file", payload.get( "__file", None ) ).file
+ try:
+ shutil.copyfile( input_file.name, path )
+ finally:
+ input_file.close()
+ return {"message": "ok"}
+
+ def __authorize_job_access(self, trans, encoded_job_id, **kwargs):
+ for key in [ "path", "job_key" ]:
+ if key not in kwargs:
+ error_message = "Job files action requires a valid '%s'." % key
+ raise exceptions.ObjectAttributeMissingException( error_message )
+
+ job_id = trans.security.decode_id( encoded_job_id )
+ job_key = trans.security.encode_id( job_id, kind="jobs_files" )
+ if not util.safe_str_cmp( kwargs[ "job_key" ], job_key ):
+ raise exceptions.ItemAccessibilityException("Invalid job_key supplied.")
+
+ # Verify job is active. Don't update the contents of complete jobs.
+ job = trans.sa_session.query( model.Job ).get( job_id )
+ if job.finished:
+ error_message = "Attempting to read or modify the files of a job that has already completed."
+ raise exceptions.MessageException( error_message )
+ return job
+
+ def __check_job_can_write_to_path( self, trans, job, path ):
+ """ Verify an idealized job runner should actually be able to write to
+ the specified path - it must be a dataset output, a dataset "extra
+ file", or a some place in the working directory of this job.
+
+ Would like similar checks for reading the unstructured nature of loc
+ files make this very difficult. (See abandoned work here
+ https://gist.github.com/jmchilton/9103619.)
+ """
+ in_work_dir = self.__in_working_directory( job, path, trans.app )
+ if not in_work_dir and not self.__is_output_dataset_path( job, path ):
+ raise exceptions.ItemAccessibilityException("Job is not authorized to write to supplied path.")
+
+ def __is_output_dataset_path( self, job, path ):
+ """ Check if is an output path for this job or a file in the an
+ output's extra files path.
+ """
+ da_lists = [ job.output_datasets, job.output_library_datasets ]
+ for da_list in da_lists:
+ for job_dataset_association in da_list:
+ dataset = job_dataset_association.dataset
+ if not dataset:
+ continue
+ if os.path.abspath( dataset.file_name ) == os.path.abspath( path ):
+ return True
+ elif util.in_directory( path, dataset.extra_files_path ):
+ return True
+ return False
+
+ def __in_working_directory( self, job, path, app ):
+ working_directory = app.object_store.get_filename(job, base_dir='job_work', dir_only=True, extra_dir=str(job.id))
+ return util.in_directory( path, working_directory )
diff -r d847b69853302d9a127c31c612dd19cc7ffa9f61 -r 58acdb670e4b8ea9a35d3b19aaab1e66c5b75009 lib/galaxy/webapps/galaxy/buildapp.py
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -233,12 +233,20 @@
'permissions',
path_prefix='/api/libraries/:library_id',
parent_resources=dict( member_name='library', collection_name='libraries' ) )
-
- webapp.mapper.resource( 'job',
- 'jobs',
+
+ webapp.mapper.resource( 'job',
+ 'jobs',
path_prefix='/api' )
webapp.mapper.connect( 'job_search', '/api/jobs/search', controller='jobs', action='search', conditions=dict( method=['POST'] ) )
+ # Job files controllers. Only for consumption by remote job runners.
+ webapp.mapper.resource( 'file',
+ 'files',
+ controller="job_files",
+ name_prefix="job_",
+ path_prefix='/api/jobs/:job_id',
+ parent_resources=dict( member_name="job", collection_name="jobs")
+ )
_add_item_extended_metadata_controller( webapp,
name_prefix="library_dataset_",
https://bitbucket.org/galaxy/galaxy-central/commits/2727f1617161/
Changeset: 2727f1617161
User: jmchilton
Date: 2014-03-17 01:54:02
Summary: Merged in jmchilton/galaxy-central-fork-1 (pull request #327)
Implement job files API.
Affected #: 7 files
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -28,6 +28,10 @@
NO_REMOTE_GALAXY_FOR_METADATA_MESSAGE = "LWR misconfiguration - LWR client configured to set metadata remotely, but remote LWR isn't properly configured with a galaxy_home directory."
NO_REMOTE_DATATYPES_CONFIG = "LWR client is configured to use remote datatypes configuration when setting metadata externally, but LWR is not configured with this information. Defaulting to datatypes_conf.xml."
+# Is there a good way to infer some default for this? Can only use
+# url_for from web threads. https://gist.github.com/jmchilton/9098762
+DEFAULT_GALAXY_URL = "http://localhost:8080"
+
class LwrJobRunner( AsynchronousJobRunner ):
"""
@@ -35,13 +39,14 @@
"""
runner_name = "LWRRunner"
- def __init__( self, app, nworkers, transport=None, cache=None, url=None ):
+ def __init__( self, app, nworkers, transport=None, cache=None, url=None, galaxy_url=DEFAULT_GALAXY_URL ):
"""Start the job runner """
super( LwrJobRunner, self ).__init__( app, nworkers )
self.async_status_updates = dict()
self._init_monitor_thread()
self._init_worker_threads()
client_manager_kwargs = {'transport_type': transport, 'cache': string_as_bool_or_none(cache), "url": url}
+ self.galaxy_url = galaxy_url
self.client_manager = build_client_manager(**client_manager_kwargs)
def url_to_destination( self, url ):
@@ -224,7 +229,21 @@
return self.get_client( job_destination_params, job_id )
def get_client( self, job_destination_params, job_id ):
- return self.client_manager.get_client( job_destination_params, str( job_id ) )
+ # Cannot use url_for outside of web thread.
+ #files_endpoint = url_for( controller="job_files", job_id=encoded_job_id )
+
+ encoded_job_id = self.app.security.encode_id(job_id)
+ job_key = self.app.security.encode_id( job_id, kind="jobs_files" )
+ files_endpoint = "%s/api/jobs/%s/files?job_key=%s" % (
+ self.galaxy_url,
+ encoded_job_id,
+ job_key
+ )
+ get_client_kwds = dict(
+ job_id=str( job_id ),
+ files_endpoint=files_endpoint,
+ )
+ return self.client_manager.get_client( job_destination_params, **get_client_kwds )
def finish_job( self, job_state ):
stderr = stdout = ''
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -268,6 +268,16 @@
self.handler = None
self.exit_code = None
+ @property
+ def finished( self ):
+ states = self.states
+ return self.state in [
+ states.OK,
+ states.ERROR,
+ states.DELETED,
+ states.DELETED_NEW,
+ ]
+
# TODO: Add accessors for members defined in SQL Alchemy for the Job table and
# for the mapper defined to the Job table.
def get_external_output_metadata( self ):
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/web/__init__.py
--- a/lib/galaxy/web/__init__.py
+++ b/lib/galaxy/web/__init__.py
@@ -20,3 +20,4 @@
from framework import _future_expose_api
from framework import _future_expose_api_anonymous
from framework import _future_expose_api_raw
+from framework import _future_expose_api_raw_anonymous
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -278,6 +278,10 @@
return _future_expose_api( func, to_json=False, user_required=True )
+def _future_expose_api_raw_anonymous( func ):
+ return _future_expose_api( func, to_json=False, user_required=False )
+
+
# TODO: rename as expose_api and make default.
def _future_expose_api( func, to_json=True, user_required=True ):
"""
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/web/security/__init__.py
--- a/lib/galaxy/web/security/__init__.py
+++ b/lib/galaxy/web/security/__init__.py
@@ -1,3 +1,4 @@
+import collections
import os, os.path, logging
import pkg_resources
@@ -37,27 +38,32 @@
self.id_secret = config['id_secret']
self.id_cipher = Blowfish.new( self.id_secret )
- def encode_id( self, obj_id ):
+ per_kind_id_secret_base = config.get( 'per_kind_id_secret_base', self.id_secret )
+ self.id_ciphers_for_key = _cipher_cache( per_kind_id_secret_base )
+
+ def encode_id( self, obj_id, kind=None ):
+ id_cipher = self.__id_cipher( kind )
# Convert to string
s = str( obj_id )
# Pad to a multiple of 8 with leading "!"
s = ( "!" * ( 8 - len(s) % 8 ) ) + s
# Encrypt
- return self.id_cipher.encrypt( s ).encode( 'hex' )
+ return id_cipher.encrypt( s ).encode( 'hex' )
- def encode_dict_ids( self, a_dict ):
+ def encode_dict_ids( self, a_dict, kind=None ):
"""
Encode all ids in dictionary. Ids are identified by (a) an 'id' key or
(b) a key that ends with '_id'
"""
for key, val in a_dict.items():
if key == 'id' or key.endswith('_id'):
- a_dict[ key ] = self.encode_id( val )
+ a_dict[ key ] = self.encode_id( val, kind=kind )
return a_dict
- def decode_id( self, obj_id ):
- return int( self.id_cipher.decrypt( obj_id.decode( 'hex' ) ).lstrip( "!" ) )
+ def decode_id( self, obj_id, kind=None ):
+ id_cipher = self.__id_cipher( kind )
+ return int( id_cipher.decrypt( obj_id.decode( 'hex' ) ).lstrip( "!" ) )
def encode_guid( self, session_key ):
# Session keys are strings
@@ -73,3 +79,19 @@
def get_new_guid( self ):
# Generate a unique, high entropy 128 bit random number
return get_random_bytes( 16 )
+
+ def __id_cipher( self, kind ):
+ if not kind:
+ id_cipher = self.id_cipher
+ else:
+ id_cipher = self.id_ciphers_for_key[ kind ]
+ return id_cipher
+
+
+class _cipher_cache( collections.defaultdict ):
+
+ def __init__( self, secret_base ):
+ self.secret_base = secret_base
+
+ def __missing__( self, key ):
+ return Blowfish.new( self.secret_base + "__" + key )
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/webapps/galaxy/api/job_files.py
--- /dev/null
+++ b/lib/galaxy/webapps/galaxy/api/job_files.py
@@ -0,0 +1,142 @@
+""" API for asynchronous job running mechanisms can use to fetch or put files
+related to running and queued jobs.
+"""
+import os
+import shutil
+
+from galaxy import exceptions
+from galaxy import util
+from galaxy import model
+from galaxy.web.base.controller import BaseAPIController
+from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous
+from galaxy.web import _future_expose_api_raw_anonymous as expose_api_raw_anonymous
+
+
+import logging
+log = logging.getLogger( __name__ )
+
+
+class JobFilesAPIController( BaseAPIController ):
+ """ This job files controller allows remote job running mechanisms to
+ read and modify the current state of files for queued and running jobs.
+ It is certainly not meant to represent part of Galaxy's stable, user
+ facing API.
+
+ Furthermore, even if a user key corresponds to the user running the job,
+ it should not be accepted for authorization - this API allows access to
+ low-level unfiltered files and such authorization would break Galaxy's
+ security model for tool execution.
+ """
+
+ @expose_api_raw_anonymous
+ def index( self, trans, job_id, **kwargs ):
+ """
+ index( self, trans, job_id, **kwargs )
+ * GET /api/jobs/{job_id}/files
+ Get a file required to staging a job (proper datasets, extra inputs,
+ task-split inputs, working directory files).
+
+ :type job_id: str
+ :param job_id: encoded id string of the job
+ :type path: str
+ :param path: Path to file.
+ :type job_key: str
+ :param job_key: A key used to authenticate this request as acting on
+ behalf or a job runner for the specified job.
+ ..note:
+ This API method is intended only for consumption by job runners,
+ not end users.
+
+ :rtype: binary
+ :returns: contents of file
+ """
+ self.__authorize_job_access( trans, job_id, **kwargs )
+ path = kwargs.get("path", None)
+ return open(path, 'rb')
+
+ @expose_api_anonymous
+ def create( self, trans, job_id, payload, **kwargs ):
+ """
+ create( self, trans, job_id, payload, **kwargs )
+ * POST /api/jobs/{job_id}/files
+ Populate an output file (formal dataset, task split part, working
+ directory file (such as those related to metadata)). This should be
+ a multipart post with a 'file' parameter containing the contents of
+ the actual file to create.
+
+ :type job_id: str
+ :param job_id: encoded id string of the job
+ :type payload: dict
+ :param payload: dictionary structure containing::
+ 'job_key' = Key authenticating
+ 'path' = Path to file to create.
+
+ ..note:
+ This API method is intended only for consumption by job runners,
+ not end users.
+
+ :rtype: dict
+ :returns: an okay message
+ """
+ job = self.__authorize_job_access( trans, job_id, **payload )
+ path = payload.get( "path" )
+ self.__check_job_can_write_to_path( trans, job, path )
+
+ # Is this writing an unneeded file? Should this just copy in Python?
+ input_file = payload.get( "file", payload.get( "__file", None ) ).file
+ try:
+ shutil.copyfile( input_file.name, path )
+ finally:
+ input_file.close()
+ return {"message": "ok"}
+
+ def __authorize_job_access(self, trans, encoded_job_id, **kwargs):
+ for key in [ "path", "job_key" ]:
+ if key not in kwargs:
+ error_message = "Job files action requires a valid '%s'." % key
+ raise exceptions.ObjectAttributeMissingException( error_message )
+
+ job_id = trans.security.decode_id( encoded_job_id )
+ job_key = trans.security.encode_id( job_id, kind="jobs_files" )
+ if not util.safe_str_cmp( kwargs[ "job_key" ], job_key ):
+ raise exceptions.ItemAccessibilityException("Invalid job_key supplied.")
+
+ # Verify job is active. Don't update the contents of complete jobs.
+ job = trans.sa_session.query( model.Job ).get( job_id )
+ if job.finished:
+ error_message = "Attempting to read or modify the files of a job that has already completed."
+ raise exceptions.MessageException( error_message )
+ return job
+
+ def __check_job_can_write_to_path( self, trans, job, path ):
+ """ Verify an idealized job runner should actually be able to write to
+ the specified path - it must be a dataset output, a dataset "extra
+ file", or a some place in the working directory of this job.
+
+ Would like similar checks for reading the unstructured nature of loc
+ files make this very difficult. (See abandoned work here
+ https://gist.github.com/jmchilton/9103619.)
+ """
+ in_work_dir = self.__in_working_directory( job, path, trans.app )
+ if not in_work_dir and not self.__is_output_dataset_path( job, path ):
+ raise exceptions.ItemAccessibilityException("Job is not authorized to write to supplied path.")
+
+ def __is_output_dataset_path( self, job, path ):
+ """ Check if is an output path for this job or a file in the an
+ output's extra files path.
+ """
+ da_lists = [ job.output_datasets, job.output_library_datasets ]
+ for da_list in da_lists:
+ for job_dataset_association in da_list:
+ dataset = job_dataset_association.dataset
+ if not dataset:
+ continue
+ if os.path.abspath( dataset.file_name ) == os.path.abspath( path ):
+ return True
+ elif util.in_directory( path, dataset.extra_files_path ):
+ return True
+ return False
+
+ def __in_working_directory( self, job, path, app ):
+ working_directory = app.object_store.get_filename(job, base_dir='job_work', dir_only=True, extra_dir=str(job.id))
+ return util.in_directory( path, working_directory )
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/webapps/galaxy/buildapp.py
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -261,6 +261,14 @@
path_prefix='/api' )
webapp.mapper.connect( 'job_search', '/api/jobs/search', controller='jobs', action='search', conditions=dict( method=['POST'] ) )
+ # Job files controllers. Only for consumption by remote job runners.
+ webapp.mapper.resource( 'file',
+ 'files',
+ controller="job_files",
+ name_prefix="job_",
+ path_prefix='/api/jobs/:job_id',
+ parent_resources=dict( member_name="job", collection_name="jobs")
+ )
_add_item_extended_metadata_controller( webapp,
name_prefix="library_dataset_",
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: jeremy goecks: Trackster: fix bug in edff37d that prevented naming and saving new track visualization.
by commits-noreply@bitbucket.org 16 Mar '14
by commits-noreply@bitbucket.org 16 Mar '14
16 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/6495ceccc87d/
Changeset: 6495ceccc87d
User: jeremy goecks
Date: 2014-03-16 05:06:07
Summary: Trackster: fix bug in edff37d that prevented naming and saving new track visualization.
Affected #: 1 file
diff -r 6f0f77a940fc4966ce5534a06866ae57899dd867 -r 6495ceccc87d467f73d76e21f801acc78934fc8f static/scripts/viz/trackster/tracks.js
--- a/static/scripts/viz/trackster/tracks.js
+++ b/static/scripts/viz/trackster/tracks.js
@@ -894,12 +894,13 @@
// Define track configuration
this.config = config_mod.ConfigSettingCollection.from_models_and_saved_values( [
+ { key: 'name', label: 'Name', type: 'text', default_value: '' },
{ key: 'a_color', label: 'A Color', type: 'color', default_value: "#FF0000" },
{ key: 'c_color', label: 'C Color', type: 'color', default_value: "#00FF00" },
{ key: 'g_color', label: 'G Color', type: 'color', default_value: "#0000FF" },
{ key: 't_color', label: 'T Color', type: 'color', default_value: "#FF00FF" },
{ key: 'n_color', label: 'N Color', type: 'color', default_value: "#AAAAAA" }
- ], obj_dict.prefs);
+ ], { name: obj_dict.name });
},
render: function() {
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: jeremy goecks: Config.js: make copy of model to avoid by reference errors. Remove debug statements.
by commits-noreply@bitbucket.org 15 Mar '14
by commits-noreply@bitbucket.org 15 Mar '14
15 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/6f0f77a940fc/
Changeset: 6f0f77a940fc
User: jeremy goecks
Date: 2014-03-15 18:26:03
Summary: Config.js: make copy of model to avoid by reference errors. Remove debug statements.
Affected #: 2 files
diff -r 9fd3b0fc3a70b40d8f35e72c4ef2513fdb03a852 -r 6f0f77a940fc4966ce5534a06866ae57899dd867 static/scripts/utils/config.js
--- a/static/scripts/utils/config.js
+++ b/static/scripts/utils/config.js
@@ -128,13 +128,10 @@
* and a saved_values dictionary.
*/
from_models_and_saved_values: function(models, saved_values) {
- // Update models with saved values.
+ // If there are saved values, copy models and update with saved values.
if (saved_values) {
- _.each(models, function(m) {
- if (saved_values[m.key]) {
- // Found saved value, so update model.
- m.value = saved_values[m.key];
- }
+ models = _.map(models, function(m) {
+ return _.extend({}, m, { value: saved_values[m.key] });
});
}
diff -r 9fd3b0fc3a70b40d8f35e72c4ef2513fdb03a852 -r 6f0f77a940fc4966ce5534a06866ae57899dd867 static/scripts/viz/trackster/tracks.js
--- a/static/scripts/viz/trackster/tracks.js
+++ b/static/scripts/viz/trackster/tracks.js
@@ -208,7 +208,6 @@
// -- Set up drawable configuration. --
this.config = config_mod.ConfigSettingCollection.from_models_and_saved_values(this.config_params, obj_dict.prefs);
- this.config.each(function(s) { console.log(s.id, s.get('value')) })
// If there's no saved name, use object name.
if (!this.config.get_value('name')) {
@@ -2688,7 +2687,6 @@
* Use from_dict to recreate object.
*/
to_dict: function() {
- console.log(this.config.to_key_value_dict());
return {
track_type: this.get_type(),
dataset: {
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: davebgx: Fix misquoted command for tool dependency environment variable inheritance.
by commits-noreply@bitbucket.org 14 Mar '14
by commits-noreply@bitbucket.org 14 Mar '14
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/9fd3b0fc3a70/
Changeset: 9fd3b0fc3a70
User: davebgx
Date: 2014-03-14 21:27:57
Summary: Fix misquoted command for tool dependency environment variable inheritance.
Affected #: 1 file
diff -r 61a423fddc6b82f9bc626318fddbf5c31a967c90 -r 9fd3b0fc3a70b40d8f35e72c4ef2513fdb03a852 lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
--- a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
@@ -462,7 +462,7 @@
inherited_env_var_name = env_var_value.split( '[' )[1].split( ']' )[0]
to_replace = '$ENV[%s]' % inherited_env_var_name
# Build a command line that outputs VARIABLE_NAME: <the value of the variable>.
- set_prior_environment_commands.append( 'echo "%s: $%s"' % ( inherited_env_var_name, inherited_env_var_name ) )
+ set_prior_environment_commands.append( 'echo %s: $%s' % ( inherited_env_var_name, inherited_env_var_name ) )
command = ' ; '.join( set_prior_environment_commands )
# Run the command and capture the output.
command_return = handle_command( app, tool_dependency, install_dir, command, return_output=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
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/61a423fddc6b/
Changeset: 61a423fddc6b
User: carlfeberhard
Date: 2014-03-14 21:03:48
Summary: Visualization Framework: add an 'embeddable' flag to visualization config files indicating whether the visualization can reliably be rendered outside a page or iframe and within another page; pass the embeddable flag to the links rendered from the registry; (more information and example at https://bitbucket.org/carlfeberhard/galaxy-embedded-visualization-template/…)
Affected #: 2 files
diff -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 -r 61a423fddc6b82f9bc626318fddbf5c31a967c90 config/plugins/visualizations/visualization.dtd
--- a/config/plugins/visualizations/visualization.dtd
+++ b/config/plugins/visualizations/visualization.dtd
@@ -3,10 +3,14 @@
<!-- visualization
name: the title/display name of the visualization (e.g. 'Trackster', 'Fastq Stats', etc.) REQUIRED
disabled: if included (value does not matter), this attribute will prevent the visualization being loaded
+ embeddable: if included (value does not matter), indicates that this visualization can be rendered as a DOM
+ fragment and won't render to a full page when passed the variable 'embedded' in the query string.
+ DEFAULT false.
--><!ATTLIST visualization
name CDATA #REQUIRED
disabled CDATA #IMPLIED
+ embeddable CDATA #IMPLIED
><!ELEMENT description (#PCDATA)>
diff -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 -r 61a423fddc6b82f9bc626318fddbf5c31a967c90 lib/galaxy/visualization/registry.py
--- a/lib/galaxy/visualization/registry.py
+++ b/lib/galaxy/visualization/registry.py
@@ -211,11 +211,13 @@
url = self.get_visualization_url( trans, target_object, visualization_name, param_data )
display_name = visualization.config.get( 'name', None )
render_target = visualization.config.get( 'render_target', 'galaxy_main' )
+ embeddable = visualization.config.get( 'embeddable', False )
# remap some of these vars for direct use in ui.js, PopupMenu (e.g. text->html)
return {
- 'href' : url,
- 'html' : display_name,
- 'target': render_target
+ 'href' : url,
+ 'html' : display_name,
+ 'target' : render_target,
+ 'embeddable': embeddable
}
return None
@@ -394,6 +396,14 @@
if not returned[ 'name' ]:
raise ParsingException( 'visualization needs a name attribute' )
+ # record the embeddable flag - defaults to false
+ # this is a design by contract promise that the visualization can be rendered inside another page
+ # often by rendering only a DOM fragment. Since this is an advanced feature that requires a bit more
+ # work from the creator's side - it defaults to False
+ returned[ 'embeddable' ] = False
+ if 'embeddable' in xml_tree.attrib:
+ returned[ 'embeddable' ] = xml_tree.attrib.get( 'embeddable', False ) == 'true'
+
# a (for now) text description of what the visualization does
description = xml_tree.find( 'description' )
returned[ 'description' ] = description.text.strip() if description is not None else None
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: carlfeberhard: Scatterplot: wind down current development, fix label assignment, flatten model config, style fixes
by commits-noreply@bitbucket.org 14 Mar '14
by commits-noreply@bitbucket.org 14 Mar '14
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/b8f9e583b040/
Changeset: b8f9e583b040
User: carlfeberhard
Date: 2014-03-14 18:44:26
Summary: Scatterplot: wind down current development, fix label assignment, flatten model config, style fixes
Affected #: 8 files
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/Gruntfile.js
--- a/config/plugins/visualizations/scatterplot/Gruntfile.js
+++ b/config/plugins/visualizations/scatterplot/Gruntfile.js
@@ -35,6 +35,7 @@
uglify: {
// uglify the concat single file directly into the static dir
options: {
+ // uncomment these to allow better source mapping during development
//mangle : false,
//beautify : true
},
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/src/handlebars/chartcontrol.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/chartcontrol.handlebars
+++ b/config/plugins/visualizations/scatterplot/src/handlebars/chartcontrol.handlebars
@@ -34,13 +34,13 @@
<div data-config-key="X-axis-label"class="text-input form-input"><label for="X-axis-label">Re-label the X axis: </label>
- <input type="text" name="X-axis-label" id="X-axis-label" value="{{x.label}}" />
+ <input type="text" name="X-axis-label" id="X-axis-label" value="{{xLabel}}" /><p class="form-help help-text-small"></p></div><div data-config-key="Y-axis-label" class="text-input form-input"><label for="Y-axis-label">Re-label the Y axis: </label>
- <input type="text" name="Y-axis-label" id="Y-axis-label" value="{{y.label}}" />
+ <input type="text" name="Y-axis-label" id="Y-axis-label" value="{{yLabel}}" /><p class="form-help help-text-small"></p></div>
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/src/handlebars/datacontrol.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/datacontrol.handlebars
+++ b/config/plugins/visualizations/scatterplot/src/handlebars/datacontrol.handlebars
@@ -17,8 +17,8 @@
</div><p class="help-text help-text-small">
- <b>Note</b>: If it can be determined from the dataset's filetype that column is not numeric, that
- column choice may be disabled for either the x or y axis.
+ <b>Note</b>: If it can be determined from the dataset's filetype that a column is not numeric,
+ that column choice may be disabled for either the x or y axis.
</p><button class="render-button btn btn-primary active">Draw</button>
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
@@ -1,5 +1,6 @@
/* =============================================================================
todo:
+ localize
import button(display), func(model) - when user doesn't match
Move margins into wid/hi calcs (so final svg dims are w/h)
Better separation of AJAX in scatterplot.js (maybe pass in function?)
@@ -141,18 +142,18 @@
/** tab content to control how the chart is rendered (data glyph size, chart size, etc.) */
_render_chartControls : function( $where ){
+//TODO: as controls on actual chart
$where = $where || this.$el;
var editor = this,
config = this.model.get( 'config' ),
$chartControls = $where.find( '#chart-control' );
-
+
// ---- skeleton/form for controls
$chartControls.html( ScatterplotConfigEditor.templates.chartControl( config ) );
//console.debug( '$chartControl:', $chartControls );
// ---- slider controls
// limits for controls (by control/chartConfig id)
- //TODO: as class attribute
var controlRanges = {
'datapointSize' : { min: 2, max: 10, step: 1 },
'width' : { min: 200, max: 800, step: 20 },
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/src/scatterplot-display.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot-display.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot-display.js
@@ -184,7 +184,7 @@
//console.debug( JSON.stringify( stats, null, ' ' ) );
var config = this.model.get( 'config' ),
$statsTable = this.$el.find( '.stats-display' ),
- xLabel = config.x.label, yLabel = config.y.label,
+ xLabel = config.xLabel, yLabel = config.yLabel,
$table = $( '<table/>' ).addClass( 'table' )
.append([ '<thead><th></th><th>', xLabel, '</th><th>', yLabel, '</th></thead>' ].join( '' ))
.append( _.map( stats, function( stat, key ){
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/src/scatterplot.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot.js
@@ -73,19 +73,19 @@
// .................................................................... axes
var axis = { x : {}, y : {} };
- //console.log( 'x.ticks:', config.x.ticks );
- //console.log( 'y.ticks:', config.y.ticks );
+ //console.log( 'xTicks:', config.xTicks );
+ //console.log( 'yTicks:', config.yTicks );
axis.x.fn = d3.svg.axis()
.orient( 'bottom' )
.scale( interpolaterFns.x )
- .ticks( config.x.ticks )
+ .ticks( config.xTicks )
// this will convert thousands -> k, millions -> M, etc.
.tickFormat( d3.format( 's' ) );
axis.y.fn = d3.svg.axis()
.orient( 'left' )
.scale( interpolaterFns.y )
- .ticks( config.y.ticks )
+ .ticks( config.yTicks )
.tickFormat( d3.format( 's' ) );
axis.x.g = content.append( 'g' )
@@ -103,8 +103,9 @@
var padding = 6;
// x-axis label
axis.x.label = svg.append( 'text' )
+ .attr( 'id', 'x-axis-label' )
.attr( 'class', 'axis-label' )
- .text( config.x.label )
+ .text( config.xLabel )
// align to the top-middle
.attr( 'text-anchor', 'middle' )
.attr( 'dominant-baseline', 'text-after-edge' )
@@ -117,8 +118,9 @@
// y-axis label
// place 4 pixels left of the axis.y.g left edge
axis.y.label = svg.append( 'text' )
+ .attr( 'id', 'y-axis-label' )
.attr( 'class', 'axis-label' )
- .text( config.y.label )
+ .text( config.yLabel )
// align to bottom-middle
.attr( 'text-anchor', 'middle' )
.attr( 'dominant-baseline', 'text-before-edge' )
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
--- a/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
+++ b/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
@@ -1,1 +1,1 @@
-function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){$(".chart-info-box").remove(),q.redraw(),e(),s=d(),$(o.node()).trigger("zoom.scatterplot",{scale:n.scale(),translate:n.translate()})}function g(a,c,d){return c+=8,$(['<div class="chart-info-box" style="position: absolute">',void 0!==b.idColumn?"<div>"+d[b.idColumn]+"</div>":"","<div>",j(d),"</div>","<div>",k(d),"</div>","</div>"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,30]).scale(b.scale||1).translate(b.translate||[0,0]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.x.ticks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.y.ticks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=6;q.x.label=o.append("text").attr("class","axis-label").text(b.x.label).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("class","axis-label").text(b.y.label).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",0);t.transition().duration(b.animDuration).attr("r",b.datapointSize),e(),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.classed("highlight",!0).style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")<b.height&&p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")).attr("y1",+d.attr("cy")+b.datapointSize).attr("x2",d.attr("cx")).attr("y2",b.height).classed("hoverline",!0);var e=this.getBoundingClientRect();$("body").append(g(e.top,e.right,a)),$(o.node()).trigger("mouseover-datapoint.scatterplot",[this,a,c])}),t.on("mouseout",function(){d3.select(this).classed("highlight",!1).style("fill","black").style("fill-opacity",.2),p.selectAll(".hoverline").remove(),$(".chart-info-box").remove()})}this.scatterplot=this.scatterplot||{},this.scatterplot.chartcontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function",i=this.escapeExpression;return g+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<div data-config-key="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">',(f=c.datapointSize)?f=f.call(b,{hash:{},data:e}):(f=b.datapointSize,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n</div>\n\n<div data-config-key="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">',(f=c.width)?f=f.call(b,{hash:{},data:e}):(f=b.width,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">',(f=c.height)?f=f.call(b,{hash:{},data:e}):(f=b.height,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="'+i((f=b.x,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<div data-config-key="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="'+i((f=b.y,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.datacontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function";return g+='<p class="help-text">\n Use the following control to change which columns are used by the chart. Click any cell\n from the last three rows of the table to select the column for the appropriate data.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<ul class="help-text" style="margin-left: 8px">\n <li><b>X Column</b>: which column values will be used for the x axis of the chart.</li>\n <li><b>Y Column</b>: which column values will be used for the y axis of the chart.</li>\n <li><b>ID Column</b>: an additional column value displayed when the user hovers over a data point.\n It may be useful to select unique or categorical identifiers here (such as gene ids).\n </li>\n</ul>\n\n<div class="column-selection">\n <pre class="peek">',(f=c.peek)?f=f.call(b,{hash:{},data:e}):(f=b.peek,f=typeof f===h?f.apply(b):f),(f||0===f)&&(g+=f),g+='</pre>\n</div>\n\n<p class="help-text help-text-small">\n <b>Note</b>: If it can be determined from the dataset\'s filetype that column is not numeric, that\n column choice may be disabled for either the x or y axis.\n</p>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.editor=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f="";return f+='<div class="scatterplot-editor tabbable tabs-left">\n \n <ul class="nav nav-tabs">\n \n <li class="active">\n <a title="Use this tab to change which data are used"\n href="#data-control" data-toggle="tab">Data Controls</a>\n </li>\n <li>\n <a title="Use this tab to change how the chart is drawn"\n href="#chart-control" data-toggle="tab" >Chart Controls</a>\n </li>\n \n <li class="disabled">\n <a title="This tab will display the chart"\n href="#chart-display" data-toggle="tab">Chart</a>\n </li>\n \n <li class="file-controls">\n<!-- <button class="copy-btn btn btn-default"\n title="Save this as a new visualization">Save to new</button>-->\n <button class="save-btn btn btn-default">Save</button>\n </li>\n </ul>\n\n \n <div class="tab-content">\n \n <div id="data-control" class="scatterplot-config-control tab-pane active">\n \n </div>\n \n \n <div id="chart-control" class="scatterplot-config-control tab-pane">\n \n </div>\n\n \n <div id="chart-display" class="scatterplot-display tab-pane"></div>\n\n </div>\n</div>\n'});var ScatterplotConfigEditor=Backbone.View.extend(LoggableMixin).extend({className:"scatterplot-control-form",initialize:function(a){if(this.model||(this.model=new Visualization({type:"scatterplot"})),this.log(this+".initialize, attributes:",a),!a||!a.dataset)throw new Error("ScatterplotConfigEditor requires a dataset");this.dataset=a.dataset,this.log("dataset:",this.dataset),this.display=new ScatterplotDisplay({dataset:a.dataset,model:this.model})},render:function(){this.$el.empty().append(ScatterplotConfigEditor.templates.mainLayout({})),this.model.id&&(this.$el.find(".copy-btn").show(),this.$el.find(".save-btn").text("Update saved")),this.$el.find("[title]").tooltip(),this._render_dataControl(),this._render_chartControls(),this._render_chartDisplay();var a=this.model.get("config");return this.model.id&&_.isFinite(a.xColumn)&&_.isFinite(a.yColumn)&&this.renderChart(),this},_getColumnIndecesByType:function(){var a={numeric:[],text:[],all:[]};return _.each(this.dataset.metadata_column_types||[],function(b,c){"int"===b||"float"===b?a.numeric.push(c):("str"===b||"list"===b)&&a.text.push(c),a.all.push(c)}),a.numeric.length<2&&(a.numeric=[]),a},_render_dataControl:function(a){a=a||this.$el;var b=this,c=this.model.get("config"),d=this._getColumnIndecesByType(),e=a.find(".tab-pane#data-control");return e.html(ScatterplotConfigEditor.templates.dataControl({peek:this.dataset.peek})),e.find(".peek").peekControl({controls:[{label:"X Column",id:"xColumn",selected:c.xColumn,disabled:d.text},{label:"Y Column",id:"yColumn",selected:c.yColumn,disabled:d.text},{label:"ID Column",id:"idColumn",selected:c.idColumn}]}).on("peek-control.change",function(a,c){b.model.set("config",c)}).on("peek-control.rename",function(){}),e.find("[title]").tooltip(),e},_render_chartControls:function(a){function b(){var a=$(this),b=a.slider("value");c.model.set("config",_.object([[a.parent().data("config-key"),b]])),a.siblings(".slider-output").text(b)}a=a||this.$el;var c=this,d=this.model.get("config"),e=a.find("#chart-control");e.html(ScatterplotConfigEditor.templates.chartControl(d));var f={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};e.find(".numeric-slider-input").each(function(){var a=$(this),c=a.attr("data-config-key"),e=_.extend(f[c],{value:d[c],change:b,slide:b});a.find(".slider").slider(e),a.children(".slider-output").text(d[c])});var g=this.dataset.metadata_column_names||[],h=d.xLabel||g[d.xColumn]||"X",i=d.yLabel||g[d.yColumn]||"Y";return e.find('input[name="X-axis-label"]').val(h).on("change",function(){c.model.set("config",{xLabel:$(this).val()})}),e.find('input[name="Y-axis-label"]').val(i).on("change",function(){c.model.set("config",{yLabel:$(this).val()})}),e.find("[title]").tooltip(),e},_render_chartDisplay:function(a){a=a||this.$el;var b=a.find(".tab-pane#chart-display");return this.display.setElement(b),this.display.render(),b.find("[title]").tooltip(),b},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart","click .save-btn":"saveVisualization"},saveVisualization:function(){var a=this;this.model.save().fail(function(b,c,d){console.error(b,c,d),a.trigger("save:error",view),alert("Error loading data:\n"+b.responseText)}).then(function(){a.display.render()})},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.display.fetchData()},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:scatterplot.editor,dataControl:scatterplot.datacontrol,chartControl:scatterplot.chartcontrol};var ScatterplotDisplay=Backbone.View.extend({initialize:function(a){this.data=null,this.dataset=a.dataset,this.lineCount=this.dataset.metadata_data_lines||null},fetchData:function(){this.showLoadingIndicator();var a=this,b=this.model.get("config"),c=jQuery.getJSON("/api/datasets/"+this.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:b.pagination.perPage,offset:b.pagination.currPage*b.pagination.perPage});return c.done(function(b){a.data=b.data,a.trigger("data:fetched",a),a.renderData()}),c.fail(function(b,c,d){console.error(b,c,d),a.trigger("data:error",a),alert("Error loading data:\n"+b.responseText)}),c},showLoadingIndicator:function(){this.$el.find(".scatterplot-data-info").html(['<div class="loading-indicator">','<span class="fa fa-spinner fa-spin"></span>','<span class="loading-indicator-message">loading...</span>',"</div>"].join(""))},template:function(){var a=['<div class="controls clear">','<div class="right">','<p class="scatterplot-data-info"></p>','<button class="stats-toggle-btn">Stats</button>','<button class="rerender-btn">Redraw</button>',"</div>",'<div class="left">','<div class="page-control"></div>',"</div>","</div>","<svg/>",'<div class="stats-display"></div>'].join("");return a},render:function(){return this.$el.addClass("scatterplot-display").html(this.template()),this.data&&this.renderData(),this},renderData:function(){this.renderLeftControls(),this.renderRightControls(),this.renderPlot(this.data),this.getStats()},renderLeftControls:function(){var a=this,b=this.model.get("config");return this.$el.find(".controls .left .page-control").pagination({startingPage:b.pagination.currPage,perPage:b.pagination.perPage,totalDataSize:this.lineCount,currDataSize:this.data.length}).off().on("pagination.page-change",function(c,d){b.pagination.currPage=d,a.model.set("config",{pagination:b.pagination}),a.resetZoom(),a.fetchData()}),this},renderRightControls:function(){var a=this;this.setLineInfo(this.data),this.$el.find(".stats-toggle-btn").off().click(function(){a.toggleStats()}),this.$el.find(".rerender-btn").off().click(function(){a.resetZoom(),a.renderPlot(this.data)})},renderPlot:function(){var a=this,b=this.$el.find("svg");this.toggleStats(!1),b.off().empty().show().on("zoom.scatterplot",function(b,c){a.model.set("config",c)}),scatterplot(b.get(0),this.model.get("config"),this.data)},setLineInfo:function(a,b){if(a){var c=this.model.get("config"),d=this.lineCount||"an unknown total",e=c.pagination.currPage*c.pagination.perPage,f=e+a.length;this.$el.find(".controls p.scatterplot-data-info").text([e+1,"to",f,"of",d].join(" "))}else this.$el.find(".controls p.scatterplot-data-info").html(b||"");return this},resetZoom:function(a,b){return a=void 0!==a?a:1,b=void 0!==b?b:[0,0],this.model.set("config",{scale:a,translate:b}),this},getStats:function(){if(this.data){var a=this,b=this.model.get("config"),c=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js");c.postMessage({data:this.data,keys:[b.xColumn,b.yColumn]}),c.onerror=function(){c.terminate()},c.onmessage=function(b){a.renderStats(b.data)}}},renderStats:function(a){var b=this.model.get("config"),c=this.$el.find(".stats-display"),d=b.x.label,e=b.y.label,f=$("<table/>").addClass("table").append(["<thead><th></th><th>",d,"</th><th>",e,"</th></thead>"].join("")).append(_.map(a,function(a,b){return $(["<tr><td>",b,"</td><td>",a[0],"</td><td>",a[1],"</td></tr>"].join(""))}));c.empty().append(f)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}}),ScatterplotModel=Visualization.extend({defaults:{type:"scatterplot",config:{pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},x:{ticks:10,label:"X"},y:{ticks:10,label:"Y"},datapointSize:4,animDuration:500,scale:1,translate:[0,0]}}});
\ No newline at end of file
+function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){$(".chart-info-box").remove(),q.redraw(),e(),s=d(),$(o.node()).trigger("zoom.scatterplot",{scale:n.scale(),translate:n.translate()})}function g(a,c,d){return c+=8,$(['<div class="chart-info-box" style="position: absolute">',void 0!==b.idColumn?"<div>"+d[b.idColumn]+"</div>":"","<div>",j(d),"</div>","<div>",k(d),"</div>","</div>"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,30]).scale(b.scale||1).translate(b.translate||[0,0]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.xTicks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.yTicks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=6;q.x.label=o.append("text").attr("id","x-axis-label").attr("class","axis-label").text(b.xLabel).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("id","y-axis-label").attr("class","axis-label").text(b.yLabel).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",0);t.transition().duration(b.animDuration).attr("r",b.datapointSize),e(),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.classed("highlight",!0).style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")<b.height&&p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")).attr("y1",+d.attr("cy")+b.datapointSize).attr("x2",d.attr("cx")).attr("y2",b.height).classed("hoverline",!0);var e=this.getBoundingClientRect();$("body").append(g(e.top,e.right,a)),$(o.node()).trigger("mouseover-datapoint.scatterplot",[this,a,c])}),t.on("mouseout",function(){d3.select(this).classed("highlight",!1).style("fill","black").style("fill-opacity",.2),p.selectAll(".hoverline").remove(),$(".chart-info-box").remove()})}this.scatterplot=this.scatterplot||{},this.scatterplot.chartcontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function",i=this.escapeExpression;return g+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<div data-config-key="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">',(f=c.datapointSize)?f=f.call(b,{hash:{},data:e}):(f=b.datapointSize,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n</div>\n\n<div data-config-key="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">',(f=c.width)?f=f.call(b,{hash:{},data:e}):(f=b.width,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">',(f=c.height)?f=f.call(b,{hash:{},data:e}):(f=b.height,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="',(f=c.xLabel)?f=f.call(b,{hash:{},data:e}):(f=b.xLabel,f=typeof f===h?f.apply(b):f),g+=i(f)+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<div data-config-key="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="',(f=c.yLabel)?f=f.call(b,{hash:{},data:e}):(f=b.yLabel,f=typeof f===h?f.apply(b):f),g+=i(f)+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.datacontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function";return g+='<p class="help-text">\n Use the following control to change which columns are used by the chart. Click any cell\n from the last three rows of the table to select the column for the appropriate data.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<ul class="help-text" style="margin-left: 8px">\n <li><b>X Column</b>: which column values will be used for the x axis of the chart.</li>\n <li><b>Y Column</b>: which column values will be used for the y axis of the chart.</li>\n <li><b>ID Column</b>: an additional column value displayed when the user hovers over a data point.\n It may be useful to select unique or categorical identifiers here (such as gene ids).\n </li>\n</ul>\n\n<div class="column-selection">\n <pre class="peek">',(f=c.peek)?f=f.call(b,{hash:{},data:e}):(f=b.peek,f=typeof f===h?f.apply(b):f),(f||0===f)&&(g+=f),g+='</pre>\n</div>\n\n<p class="help-text help-text-small">\n <b>Note</b>: If it can be determined from the dataset\'s filetype that a column is not numeric,\n that column choice may be disabled for either the x or y axis.\n</p>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.editor=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f="";return f+='<div class="scatterplot-editor tabbable tabs-left">\n \n <ul class="nav nav-tabs">\n \n <li class="active">\n <a title="Use this tab to change which data are used"\n href="#data-control" data-toggle="tab">Data Controls</a>\n </li>\n <li>\n <a title="Use this tab to change how the chart is drawn"\n href="#chart-control" data-toggle="tab" >Chart Controls</a>\n </li>\n \n <li class="disabled">\n <a title="This tab will display the chart"\n href="#chart-display" data-toggle="tab">Chart</a>\n </li>\n \n <li class="file-controls">\n<!-- <button class="copy-btn btn btn-default"\n title="Save this as a new visualization">Save to new</button>-->\n <button class="save-btn btn btn-default">Save</button>\n </li>\n </ul>\n\n \n <div class="tab-content">\n \n <div id="data-control" class="scatterplot-config-control tab-pane active">\n \n </div>\n \n \n <div id="chart-control" class="scatterplot-config-control tab-pane">\n \n </div>\n\n \n <div id="chart-display" class="scatterplot-display tab-pane"></div>\n\n </div>\n</div>\n'});var ScatterplotConfigEditor=Backbone.View.extend(LoggableMixin).extend({className:"scatterplot-control-form",initialize:function(a){if(this.model||(this.model=new Visualization({type:"scatterplot"})),this.log(this+".initialize, attributes:",a),!a||!a.dataset)throw new Error("ScatterplotConfigEditor requires a dataset");this.dataset=a.dataset,this.log("dataset:",this.dataset),this.display=new ScatterplotDisplay({dataset:a.dataset,model:this.model})},render:function(){this.$el.empty().append(ScatterplotConfigEditor.templates.mainLayout({})),this.model.id&&(this.$el.find(".copy-btn").show(),this.$el.find(".save-btn").text("Update saved")),this.$el.find("[title]").tooltip(),this._render_dataControl(),this._render_chartControls(),this._render_chartDisplay();var a=this.model.get("config");return this.model.id&&_.isFinite(a.xColumn)&&_.isFinite(a.yColumn)&&this.renderChart(),this},_getColumnIndecesByType:function(){var a={numeric:[],text:[],all:[]};return _.each(this.dataset.metadata_column_types||[],function(b,c){"int"===b||"float"===b?a.numeric.push(c):("str"===b||"list"===b)&&a.text.push(c),a.all.push(c)}),a.numeric.length<2&&(a.numeric=[]),a},_render_dataControl:function(a){a=a||this.$el;var b=this,c=this.model.get("config"),d=this._getColumnIndecesByType(),e=a.find(".tab-pane#data-control");return e.html(ScatterplotConfigEditor.templates.dataControl({peek:this.dataset.peek})),e.find(".peek").peekControl({controls:[{label:"X Column",id:"xColumn",selected:c.xColumn,disabled:d.text},{label:"Y Column",id:"yColumn",selected:c.yColumn,disabled:d.text},{label:"ID Column",id:"idColumn",selected:c.idColumn}]}).on("peek-control.change",function(a,c){b.model.set("config",c)}).on("peek-control.rename",function(){}),e.find("[title]").tooltip(),e},_render_chartControls:function(a){function b(){var a=$(this),b=a.slider("value");c.model.set("config",_.object([[a.parent().data("config-key"),b]])),a.siblings(".slider-output").text(b)}a=a||this.$el;var c=this,d=this.model.get("config"),e=a.find("#chart-control");e.html(ScatterplotConfigEditor.templates.chartControl(d));var f={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};e.find(".numeric-slider-input").each(function(){var a=$(this),c=a.attr("data-config-key"),e=_.extend(f[c],{value:d[c],change:b,slide:b});a.find(".slider").slider(e),a.children(".slider-output").text(d[c])});var g=this.dataset.metadata_column_names||[],h=d.xLabel||g[d.xColumn]||"X",i=d.yLabel||g[d.yColumn]||"Y";return e.find('input[name="X-axis-label"]').val(h).on("change",function(){c.model.set("config",{xLabel:$(this).val()})}),e.find('input[name="Y-axis-label"]').val(i).on("change",function(){c.model.set("config",{yLabel:$(this).val()})}),e.find("[title]").tooltip(),e},_render_chartDisplay:function(a){a=a||this.$el;var b=a.find(".tab-pane#chart-display");return this.display.setElement(b),this.display.render(),b.find("[title]").tooltip(),b},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart","click .save-btn":"saveVisualization"},saveVisualization:function(){var a=this;this.model.save().fail(function(b,c,d){console.error(b,c,d),a.trigger("save:error",view),alert("Error loading data:\n"+b.responseText)}).then(function(){a.display.render()})},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.display.fetchData()},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:scatterplot.editor,dataControl:scatterplot.datacontrol,chartControl:scatterplot.chartcontrol};var ScatterplotDisplay=Backbone.View.extend({initialize:function(a){this.data=null,this.dataset=a.dataset,this.lineCount=this.dataset.metadata_data_lines||null},fetchData:function(){this.showLoadingIndicator();var a=this,b=this.model.get("config"),c=jQuery.getJSON("/api/datasets/"+this.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:b.pagination.perPage,offset:b.pagination.currPage*b.pagination.perPage});return c.done(function(b){a.data=b.data,a.trigger("data:fetched",a),a.renderData()}),c.fail(function(b,c,d){console.error(b,c,d),a.trigger("data:error",a),alert("Error loading data:\n"+b.responseText)}),c},showLoadingIndicator:function(){this.$el.find(".scatterplot-data-info").html(['<div class="loading-indicator">','<span class="fa fa-spinner fa-spin"></span>','<span class="loading-indicator-message">loading...</span>',"</div>"].join(""))},template:function(){var a=['<div class="controls clear">','<div class="right">','<p class="scatterplot-data-info"></p>','<button class="stats-toggle-btn">Stats</button>','<button class="rerender-btn">Redraw</button>',"</div>",'<div class="left">','<div class="page-control"></div>',"</div>","</div>","<svg/>",'<div class="stats-display"></div>'].join("");return a},render:function(){return this.$el.addClass("scatterplot-display").html(this.template()),this.data&&this.renderData(),this},renderData:function(){this.renderLeftControls(),this.renderRightControls(),this.renderPlot(this.data),this.getStats()},renderLeftControls:function(){var a=this,b=this.model.get("config");return this.$el.find(".controls .left .page-control").pagination({startingPage:b.pagination.currPage,perPage:b.pagination.perPage,totalDataSize:this.lineCount,currDataSize:this.data.length}).off().on("pagination.page-change",function(c,d){b.pagination.currPage=d,a.model.set("config",{pagination:b.pagination}),a.resetZoom(),a.fetchData()}),this},renderRightControls:function(){var a=this;this.setLineInfo(this.data),this.$el.find(".stats-toggle-btn").off().click(function(){a.toggleStats()}),this.$el.find(".rerender-btn").off().click(function(){a.resetZoom(),a.renderPlot(this.data)})},renderPlot:function(){var a=this,b=this.$el.find("svg");this.toggleStats(!1),b.off().empty().show().on("zoom.scatterplot",function(b,c){a.model.set("config",c)}),scatterplot(b.get(0),this.model.get("config"),this.data)},setLineInfo:function(a,b){if(a){var c=this.model.get("config"),d=this.lineCount||"an unknown total",e=c.pagination.currPage*c.pagination.perPage,f=e+a.length;this.$el.find(".controls p.scatterplot-data-info").text([e+1,"to",f,"of",d].join(" "))}else this.$el.find(".controls p.scatterplot-data-info").html(b||"");return this},resetZoom:function(a,b){return a=void 0!==a?a:1,b=void 0!==b?b:[0,0],this.model.set("config",{scale:a,translate:b}),this},getStats:function(){if(this.data){var a=this,b=this.model.get("config"),c=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js");c.postMessage({data:this.data,keys:[b.xColumn,b.yColumn]}),c.onerror=function(){c.terminate()},c.onmessage=function(b){a.renderStats(b.data)}}},renderStats:function(a){var b=this.model.get("config"),c=this.$el.find(".stats-display"),d=b.xLabel,e=b.yLabel,f=$("<table/>").addClass("table").append(["<thead><th></th><th>",d,"</th><th>",e,"</th></thead>"].join("")).append(_.map(a,function(a,b){return $(["<tr><td>",b,"</td><td>",a[0],"</td><td>",a[1],"</td></tr>"].join(""))}));c.empty().append(f)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}}),ScatterplotModel=Visualization.extend({defaults:{type:"scatterplot",config:{pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},xTicks:10,xLabel:"X",yTicks:10,yLabel:"Y",datapointSize:4,animDuration:500,scale:1,translate:[0,0]}}});
\ No newline at end of file
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/static/scatterplot.css
--- a/config/plugins/visualizations/scatterplot/static/scatterplot.css
+++ b/config/plugins/visualizations/scatterplot/static/scatterplot.css
@@ -5,10 +5,11 @@
margin-bottom: 16px;
overflow: auto;
background-color: #ebd9b2;
- padding : 8px 12px 8px 12px;
+ padding : 12px 12px 4px 12px;
}
.chart-header h2 {
+ margin-top: 0px;
margin-bottom: 4px;
}
@@ -63,7 +64,7 @@
.file-controls .btn {
height: 24px;
padding: 0px 10px 0px 10px;
- line-height: 24px;
+ line-height: 20px;
}
.file-controls .copy-btn {
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: carlfeberhard: History panels: persist search visibility, improve model switching, consistently apply icon button groups, fix style on only-child buttons, improve docs and private function names
by commits-noreply@bitbucket.org 14 Mar '14
by commits-noreply@bitbucket.org 14 Mar '14
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/707ba3eee2ab/
Changeset: 707ba3eee2ab
User: carlfeberhard
Date: 2014-03-14 17:26:23
Summary: History panels: persist search visibility, improve model switching, consistently apply icon button groups, fix style on only-child buttons, improve docs and private function names
Affected #: 11 files
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/mvc/history/annotated-history-panel.js
--- a/static/scripts/mvc/history/annotated-history-panel.js
+++ b/static/scripts/mvc/history/annotated-history-panel.js
@@ -33,16 +33,23 @@
HDAViewClass : hdaBase.HDABaseView,
// ------------------------------------------------------------------------ panel rendering
+ /** render with history data
+ * In this override:
+ * replace the datasets list with a table,
+ * add the history annotation,
+ * and move the search controls
+ * @returns {jQuery} dom fragment as temporary container to be swapped out later
+ */
renderModel : function( ){
- // huh?
+ // why do we need this here? why isn't className being applied?
this.$el.addClass( this.className );
var $newRender = readonlyPanel.ReadOnlyHistoryPanel.prototype.renderModel.call( this ),
// move datasets from div to table
- $datasetsList = $newRender.find( this.datasetsSelector ),
+ $datasetsList = this.$datasetsList( $newRender ),
$datasetsTable = $( '<table/>' ).addClass( 'datasets-list datasets-table' );
$datasetsTable.append( $datasetsList.children() );
- $newRender.find( this.datasetsSelector ).replaceWith( $datasetsTable );
- //TODO: it's possible to do this with css only, right?
+ $datasetsList.replaceWith( $datasetsTable );
+ //TODO: it's possible to do this with css only, right? display: table-cell, etc.?
// add history annotation under subtitle
$newRender.find( '.history-subtitle' ).after( this.renderHistoryAnnotation() );
@@ -50,11 +57,11 @@
// hide search button, move search bar beneath controls (instead of above title), show, and set up
$newRender.find( '.history-search-btn' ).hide();
$newRender.find( '.history-controls' ).after( $newRender.find( '.history-search-controls' ).show() );
- this.setUpSearchInput( $newRender.find( '.history-search-input' ) );
return $newRender;
},
+ /** render the history's annotation as it's own field */
renderHistoryAnnotation : function(){
var annotation = this.model.get( 'annotation' );
if( !annotation ){ return null; }
@@ -63,10 +70,13 @@
].join( '' ));
},
+ /** Set up/render a view for each HDA to be shown, init with model and listeners.
+ * In this override, add table header cells to indicate the dataset, annotation columns
+ */
renderHdas : function( $whereTo ){
$whereTo = $whereTo || this.$el;
var hdaViews = readonlyPanel.ReadOnlyHistoryPanel.prototype.renderHdas.call( this, $whereTo );
- $whereTo.find( this.datasetsSelector ).prepend( $( '<tr/>' ).addClass( 'headers' ).append([
+ this.$datasetsList( $whereTo ).prepend( $( '<tr/>' ).addClass( 'headers' ).append([
$( '<th/>' ).text( _l( 'Dataset' ) ),
$( '<th/>' ).text( _l( 'Annotation' ) )
]));
@@ -74,6 +84,9 @@
},
// ------------------------------------------------------------------------ hda sub-views
+ /** attach an hdaView to the panel
+ * In this override, wrap the hdaView in a table row and cell, adding a 2nd cell for the hda annotation
+ */
attachHdaView : function( hdaView, $whereTo ){
$whereTo = $whereTo || this.$el;
// build a row around the dataset with the std hdaView in the first cell and the annotation in the next
@@ -87,7 +100,7 @@
.addClass( stateClass? stateClass.replace( '-', '-color-' ): '' ),
$( '<td/>' ).addClass( 'additional-info' ).text( annotation )
]);
- $whereTo.find( this.datasetsSelector ).append( $tr );
+ this.$datasetsList( $whereTo ).append( $tr );
},
// ------------------------------------------------------------------------ panel events
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/mvc/history/current-history-panel.js
--- a/static/scripts/mvc/history/current-history-panel.js
+++ b/static/scripts/mvc/history/current-history-panel.js
@@ -53,10 +53,6 @@
// ......................................................................... SET UP
/** Set up the view, set up storage, bind listeners to HDACollection events
* @param {Object} attributes
- * @config {Object} urlTemplates.hda nested object containing url templates for HDAViews
- * @throws 'needs urlTemplates' if urlTemplates.history or urlTemplates.hda aren't present
- * @see PersistentStorage
- * @see Backbone.View#initialize
*/
initialize : function( attributes ){
attributes = attributes || {};
@@ -68,10 +64,6 @@
}, _.pick( attributes, _.keys( HistoryPanelPrefs.prototype.defaults ) )));
hpanel.HistoryPanel.prototype.initialize.call( this, attributes );
- if( this.model ){
- console.debug( this.model );
- this.model.checkForUpdates();
- }
},
// ------------------------------------------------------------------------ loading history/hda models
@@ -119,10 +111,12 @@
},
/** release/free/shutdown old models and set up panel for new models */
- setModel : function( newHistoryJSON, newHdaJSON, attributes ){
- attributes = attributes || {};
- hpanel.HistoryPanel.prototype.setModel.call( this, newHistoryJSON, newHdaJSON, attributes );
- this.model.checkForUpdates();
+ setModel : function( model, attributes, render ){
+ hpanel.HistoryPanel.prototype.setModel.call( this, model, attributes, render );
+ if( this.model ){
+ this.log( 'checking for updates' );
+ this.model.checkForUpdates();
+ }
return this;
},
@@ -148,7 +142,6 @@
this.removeHdaView( this.hdaViews[ hda.id ] );
}
}, this );
-
},
// ------------------------------------------------------------------------ panel rendering
@@ -172,7 +165,6 @@
// fade out existing, swap with the new, fade in, set up behaviours
$( panel ).queue( 'fx', [
function( next ){
- //panel.$el.fadeTo( panel.fxSpeed, 0.0001, next );
if( speed && panel.$el.is( ':visible' ) ){
panel.$el.fadeOut( speed, next );
} else {
@@ -208,10 +200,19 @@
/** perform additional rendering based on preferences */
renderBasedOnPrefs : function(){
if( this.preferences.get( 'searching' ) ){
- this.showSearchControls( 0 );
+ this.toggleSearchControls( 0, true );
}
},
+ /** In this override, save the search control visibility state to preferences */
+ toggleSearchControls : function( eventOrSpeed, show ){
+ var visible = hpanel.HistoryPanel.prototype.toggleSearchControls.call( this, eventOrSpeed, show );
+ this.preferences.set( 'searching', visible );
+ },
+
+ /** render the tag sub-view controller
+ * In this override, get and set current panel preferences when editor is used
+ */
_renderTags : function( $where ){
var panel = this;
// render tags and show/hide based on preferences
@@ -225,6 +226,9 @@
panel.preferences.set( 'tagsEditorShown', tagsEditor.hidden );
});
},
+ /** render the annotation sub-view controller
+ * In this override, get and set current panel preferences when editor is used
+ */
_renderAnnotation : function( $where ){
var panel = this;
// render annotation and show/hide based on preferences
@@ -300,6 +304,7 @@
}
});
+
//==============================================================================
return {
CurrentHistoryPanel : CurrentHistoryPanel
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/mvc/history/history-panel.js
--- a/static/scripts/mvc/history/history-panel.js
+++ b/static/scripts/mvc/history/history-panel.js
@@ -7,7 +7,7 @@
TODO:
============================================================================= */
-/** @class View/Controller for the history model.
+/** @class Editable View/Controller for the history model.
* @name HistoryPanel
*
* Allows:
@@ -15,8 +15,6 @@
* changing the name
* displaying and editing tags and annotations
* multi-selection and operations on mulitple hdas
- * Does not allow:
- * changing the name
*
* @augments Backbone.View
* @borrows LoggableMixin#logger as #logger
@@ -65,8 +63,8 @@
this.model.on( 'change:nice_size', this.updateHistoryDiskSize, this );
- this.model.hdas.on( 'change:deleted', this.handleHdaDeletionChange, this );
- this.model.hdas.on( 'change:visible', this.handleHdaVisibleChange, this );
+ this.model.hdas.on( 'change:deleted', this._handleHdaDeletionChange, this );
+ this.model.hdas.on( 'change:visible', this._handleHdaVisibleChange, this );
this.model.hdas.on( 'change:purged', function( hda ){
// hafta get the new nice-size w/o the purged hda
this.model.fetch();
@@ -74,8 +72,12 @@
},
// ------------------------------------------------------------------------ panel rendering
- /** render with history data */
+ /** render with history data
+ * In this override, add tags, annotations, and multi select
+ * @returns {jQuery} dom fragment as temporary container to be swapped out later
+ */
renderModel : function( ){
+//TODO: can't call ReadOnlyHistoryPanel?
var $newRender = $( '<div/>' );
$newRender.append( HistoryPanel.templates.historyPanel( this.model.toJSON() ) );
@@ -95,6 +97,7 @@
return $newRender;
},
+ /** render the tags sub-view controller */
_renderTags : function( $where ){
var panel = this;
this.tagsEditor = new TagsEditor({
@@ -115,6 +118,7 @@
}).appendTo( $where.find( '.history-secondary-actions' ) )
});
},
+ /** render the annotation sub-view controller */
_renderAnnotation : function( $where ){
var panel = this;
this.annotationEditor = new AnnotationEditor({
@@ -145,11 +149,12 @@
});
},
- /** Set up HistoryPanel js/widget behaviours */
+ /** Set up HistoryPanel js/widget behaviours
+ * In this override, add the multi select popup menu and make the name editable
+ */
_setUpBehaviours : function( $where ){
- //TODO: these should be either sub-MVs, or handled by events
$where = $where || this.$el;
- $where.find( '[title]' ).tooltip({ placement: 'bottom' });
+ readonlyPanel.ReadOnlyHistoryPanel.prototype._setUpBehaviours.call( this, $where );
// anon users shouldn't have access to any of the following
if( !this.model ){
@@ -184,6 +189,9 @@
});
},
+ /** return a new popup menu for choosing a multi selection action
+ * ajax calls made for multiple datasets are queued
+ */
_setUpDatasetActionsPopup : function( $where ){
var panel = this;
( new PopupMenu( $where.find( '.history-dataset-action-popup-btn' ), [
@@ -226,7 +234,7 @@
/** If this hda is deleted and we're not showing deleted hdas, remove the view
* @param {HistoryDataAssociation} the hda to check
*/
- handleHdaDeletionChange : function( hda ){
+ _handleHdaDeletionChange : function( hda ){
if( hda.get( 'deleted' ) && !this.storage.get( 'show_deleted' ) ){
this.removeHdaView( this.hdaViews[ hda.id ] );
} // otherwise, the hdaView rendering should handle it
@@ -236,7 +244,7 @@
/** If this hda is hidden and we're not showing hidden hdas, remove the view
* @param {HistoryDataAssociation} the hda to check
*/
- handleHdaVisibleChange : function( hda ){
+ _handleHdaVisibleChange : function( hda ){
if( hda.hidden() && !this.storage.get( 'show_hidden' ) ){
this.removeHdaView( this.hdaViews[ hda.id ] );
} // otherwise, the hdaView rendering should handle it
@@ -245,7 +253,7 @@
/** Create an HDA view for the given HDA (but leave attachment for addHdaView above)
* @param {HistoryDatasetAssociation} hda
*/
- createHdaView : function( hda ){
+ _createHdaView : function( hda ){
var hdaId = hda.get( 'id' ),
hdaView = new this.HDAViewClass({
model : hda,
@@ -315,7 +323,7 @@
hdaView.remove();
delete panel.hdaViews[ hdaView.model.id ];
if( _.isEmpty( panel.hdaViews ) ){
- panel.$el.find( panel.emptyMsgSelector ).fadeIn( panel.fxSpeed, function(){
+ panel.$emptyMessage().fadeIn( panel.fxSpeed, function(){
panel.trigger( 'empty-history', panel );
});
}
@@ -337,6 +345,7 @@
},
// ........................................................................ multi-select of hdas
+//TODO: to toggle showOrHide pattern
/** show selectors on all visible hdas and associated controls */
showSelectors : function( speed ){
this.selecting = true;
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/mvc/history/readonly-history-panel.js
--- a/static/scripts/mvc/history/readonly-history-panel.js
+++ b/static/scripts/mvc/history/readonly-history-panel.js
@@ -15,26 +15,43 @@
},
/** add an hda id to the hash of expanded hdas */
addExpandedHda : function( id ){
- this.save( 'expandedHdas', _.extend( this.get( 'expandedHdas' ), _.object([ id ], [ true ]) ) );
+ var key = 'expandedHdas';
+ this.save( key, _.extend( this.get( key ), _.object([ id ], [ true ]) ) );
},
/** remove an hda id from the hash of expanded hdas */
removeExpandedHda : function( id ){
- this.save( 'expandedHdas', _.omit( this.get( 'expandedHdas' ), id ) );
+ var key = 'expandedHdas';
+ this.save( key, _.omit( this.get( key ), id ) );
},
toString : function(){
return 'HistoryPrefs(' + this.id + ')';
}
});
+// class lvl for access w/o instantiation
+HistoryPrefs.storageKeyPrefix = 'history:';
/** key string to store each histories settings under */
HistoryPrefs.historyStorageKey = function historyStorageKey( historyId ){
- // class lvl for access w/o instantiation
if( !historyId ){
throw new Error( 'HistoryPrefs.historyStorageKey needs valid id: ' + historyId );
}
// single point of change
- return ( 'history:' + historyId );
+ return ( HistoryPrefs.storageKeyPrefix + historyId );
};
+/** return the existing storage for the history with the given id (or create one if it doesn't exist) */
+HistoryPrefs.get = function get( historyId ){
+ return new HistoryPrefs({ id: HistoryPrefs.historyStorageKey( historyId ) });
+};
+/** clear all history related items in sessionStorage */
+HistoryPrefs.clearAll = function clearAll( historyId ){
+ for( var key in sessionStorage ){
+ if( key.indexOf( HistoryPrefs.storageKeyPrefix ) === 0 ){
+ sessionStorage.removeItem( key );
+ }
+ }
+};
+
+
/* =============================================================================
TODO:
@@ -44,6 +61,8 @@
*
* Allows:
* changing the loaded history
+ * searching hdas
+ * displaying data, info, and download
* Does not allow:
* changing the name
*
@@ -55,8 +74,8 @@
var ReadOnlyHistoryPanel = Backbone.View.extend( LoggableMixin ).extend(
/** @lends ReadOnlyHistoryPanel.prototype */{
- ///** logger used to record this.log messages, commonly set to console */
- //// comment this out to suppress log output
+ /** logger used to record this.log messages, commonly set to console */
+ // comment this out to suppress log output
//logger : console,
/** class to use for constructing the HDA views */
@@ -68,15 +87,14 @@
/** (in ms) that jquery effects will use */
fxSpeed : 'fast',
- datasetsSelector : '.datasets-list',
- msgsSelector : '.message-container',
- emptyMsgSelector : '.empty-history-message',
+ /** string to display when the model has no hdas */
emptyMsg : _l( 'This history is empty' ),
+ /** string to no hdas match the search terms */
noneFoundMsg : _l( 'No matching datasets found' ),
// ......................................................................... SET UP
/** Set up the view, set up storage, bind listeners to HDACollection events
- * @param {Object} attributes
+ * @param {Object} attributes optional settings for the panel
*/
initialize : function( attributes ){
attributes = attributes || {};
@@ -86,35 +104,34 @@
}
this.log( this + '.initialize:', attributes );
- // ---- set up instance vars
+ // ---- instance vars
// control contents/behavior based on where (and in what context) the panel is being used
/** where should pages from links be displayed? (default to new tab/window) */
this.linkTarget = attributes.linkTarget || '_blank';
/** how quickly should jquery fx run? */
this.fxSpeed = _.has( attributes, 'fxSpeed' )?( attributes.fxSpeed ):( this.fxSpeed );
+ /** filters for displaying hdas */
+ this.filters = [];
+ this.searchFor = '';
+
+ /** a function to locate the container to scroll to effectively scroll the panel */
+//TODO: rename
+ this.findContainerFn = attributes.findContainerFn;
+ // generally this is $el.parent() - but may be $el or $el.parent().parent() depending on the context
+
// ---- sub views and saved elements
/** map of hda model ids to hda views */
this.hdaViews = {};
/** loading indicator */
this.indicator = new LoadingIndicator( this.$el );
- /** filters for displaying hdas */
- this.filters = [];
- this.searchFor = '';
-
+ // ----- set up panel listeners, handle models passed on init, and call any ready functions
this._setUpListeners();
-
- // ---- handle models passed on init
- if( this.model ){
- if( this.logger ){
- this.model.logger = this.logger;
- }
- this._setUpWebStorage( attributes.initiallyExpanded, attributes.show_deleted, attributes.show_hidden );
- this._setUpModelEventHandlers();
- }
+ // don't render when setting the first time
+ var modelOptions = _.pick( attributes, 'initiallyExpanded', 'show_deleted', 'show_hidden' );
+ this.setModel( this.model, modelOptions, false );
//TODO: remove?
- // ---- and any run functions
if( attributes.onready ){
attributes.onready.call( this );
}
@@ -125,25 +142,23 @@
* @fires: empty-history when switching to a history with no HDAs or creating a new history
*/
_setUpListeners : function(){
- // ---- event handlers for self
this.on( 'error', function( model, xhr, options, msg, details ){
this.errorHandler( model, xhr, options, msg, details );
});
this.on( 'loading-history', function(){
// show the loading indicator when loading a new history starts...
- this.showLoadingIndicator( 'loading history...', 40 );
+ this._showLoadingIndicator( 'loading history...', 40 );
});
this.on( 'loading-done', function(){
// ...hiding it again when loading is done (or there's been an error)
- this.hideLoadingIndicator( 40 );
+ this._hideLoadingIndicator( 40 );
if( _.isEmpty( this.hdaViews ) ){
this.trigger( 'empty-history', this );
}
});
- // throw the first render up as a diff namespace using once
- // (for outside consumption)
+ // throw the first render up as a diff namespace using once (for outside consumption)
this.once( 'rendered', function(){
this.trigger( 'rendered:initial', this );
return false;
@@ -155,6 +170,7 @@
this.log( this + '', arguments );
}, this );
}
+ return this;
},
//TODO: see base-mvc
@@ -166,7 +182,7 @@
//},
// ........................................................................ error handling
- /** Event listener to handle errors (from the panel, the history, or the history's HDAs)
+ /** Event handler for errors (from the panel, the history, or the history's HDAs)
* @param {Model or View} model the (Backbone) source of the error
* @param {XMLHTTPRequest} xhr any ajax obj. assoc. with the error
* @param {Object} options the options map commonly used with bbone ajax
@@ -174,7 +190,8 @@
* @param {Object} msg optional object containing error details
*/
errorHandler : function( model, xhr, options, msg, details ){
- var parsed = this._parseErrorMessage( model, xhr, options, msg, details );
+ console.error( model, xhr, options, msg, details );
+//TODO: getting JSON parse errors from jq migrate
// interrupted ajax
if( xhr && xhr.status === 0 && xhr.readyState === 0 ){
@@ -185,8 +202,9 @@
// otherwise, show an error message inside the panel
} else {
+ var parsed = this._parseErrorMessage( model, xhr, options, msg, details );
// it's possible to have a triggered error before the message container is rendered - wait for it to show
- if( !this.$el.find( this.msgsSelector ).is( ':visible' ) ){
+ if( !this.$messages().is( ':visible' ) ){
this.once( 'rendered', function(){
this.displayMessage( 'error', parsed.message, parsed.details );
});
@@ -239,34 +257,35 @@
/** loads a history & hdas w/ details (but does not make them the current history) */
loadHistoryWithHDADetails : function( historyId, attributes, historyFn, hdaFn ){
//console.info( 'loadHistoryWithHDADetails:', historyId, attributes, historyFn, hdaFn );
- var panel = this,
- // will be called to get hda ids that need details from the api
- hdaDetailIds = function( historyData ){
+ var hdaDetailIds = function( historyData ){
+ // will be called to get hda ids that need details from the api
//TODO: non-visible HDAs are getting details loaded... either stop loading them at all or filter ids thru isVisible
- return panel.getExpandedHdaIds( historyData.id );
+ return _.keys( HistoryPrefs.get( historyData.id ).get( 'expandedHdas' ) );
};
return this.loadHistory( historyId, attributes, historyFn, hdaFn, hdaDetailIds );
},
/** loads a history & hdas w/ NO details (but does not make them the current history) */
loadHistory : function( historyId, attributes, historyFn, hdaFn, hdaDetailIds ){
- this.trigger( 'loading-history', this );
+ var panel = this;
attributes = attributes || {};
- var panel = this;
+
+ panel.trigger( 'loading-history', panel );
//console.info( 'loadHistory:', historyId, attributes, historyFn, hdaFn, hdaDetailIds );
var xhr = historyModel.History.getHistoryData( historyId, {
historyFn : historyFn,
hdaFn : hdaFn,
hdaDetailIds : attributes.initiallyExpanded || hdaDetailIds
});
- return this._loadHistoryFromXHR( xhr, attributes )
+
+ return panel._loadHistoryFromXHR( xhr, attributes )
.fail( function( xhr, where, history ){
// throw an error up for the error handler
panel.trigger( 'error', panel, xhr, attributes, _l( 'An error was encountered while ' + where ),
{ historyId: historyId, history: history || {} });
})
.always( function(){
- // bc hideLoadingIndicator relies on this firing
+ // bc _hideLoadingIndicator relies on this firing
panel.trigger( 'loading-done', panel );
});
},
@@ -275,7 +294,7 @@
_loadHistoryFromXHR : function( xhr, attributes ){
var panel = this;
xhr.then( function( historyJSON, hdaJSON ){
- panel.setModel( historyJSON, hdaJSON, attributes );
+ panel.JSONToModel( historyJSON, hdaJSON, attributes );
});
xhr.fail( function( xhr, where ){
// always render - whether we get a model or not
@@ -284,11 +303,59 @@
return xhr;
},
- /** release/free/shutdown old models and set up panel for new models */
- setModel : function( newHistoryJSON, newHdaJSON, attributes ){
+
+ /** create a new history model from JSON and call setModel on it */
+ JSONToModel : function( newHistoryJSON, newHdaJSON, attributes ){
+ this.log( 'JSONToModel:', newHistoryJSON, newHdaJSON, attributes );
+//TODO: Maybe better in History?
attributes = attributes || {};
- //console.info( 'setModel:', newHistoryJSON, newHdaJSON.length, attributes );
+ //this.log( 'JSONToModel:', newHistoryJSON, newHdaJSON.length, attributes );
+ // set up the new model and render
+ if( Galaxy && Galaxy.currUser ){
+//TODO: global
+ newHistoryJSON.user = Galaxy.currUser.toJSON();
+ }
+ var model = new historyModel.History( newHistoryJSON, newHdaJSON, attributes );
+ this.setModel( model );
+ return this;
+ },
+
+ /** release/free/shutdown old models and set up panel for new models
+ * @fires new-model with the panel as parameter
+ */
+ setModel : function( model, attributes, render ){
+ attributes = attributes || {};
+ render = ( render !== undefined )?( render ):( true );
+ this.log( 'setModel:', model, attributes, render );
+
+ this.freeModel();
+ this.selectedHdaIds = [];
+
+ if( model ){
+ // set up the new model with user, logger, storage, events
+ if( Galaxy && Galaxy.currUser ){
+//TODO: global
+ model.user = Galaxy.currUser.toJSON();
+ }
+ this.model = model;
+ if( this.logger ){
+ this.model.logger = this.logger;
+ }
+ this._setUpWebStorage( attributes.initiallyExpanded, attributes.show_deleted, attributes.show_hidden );
+ this._setUpModelEventHandlers();
+ this.trigger( 'new-model', this );
+ }
+
+ if( render ){
+//TODO: remove?
+ this.render();
+ }
+ return this;
+ },
+
+ /** free the current model and all listeners for it, free any hdaViews for the model */
+ freeModel : function(){
// stop/release the previous model, and clear cache to hda sub-views
if( this.model ){
this.model.clearUpdateTimeout();
@@ -297,22 +364,13 @@
//TODO: see base-mvc
//this.model.free();
}
+ this.freeHdaViews();
+ return this;
+ },
+
+ /** free any hdaViews the panel has */
+ freeHdaViews : function(){
this.hdaViews = {};
-
- // set up the new model and render
- if( Galaxy && Galaxy.currUser ){
-//TODO: global
- newHistoryJSON.user = Galaxy.currUser.toJSON();
- }
- this.model = new historyModel.History( newHistoryJSON, newHdaJSON, attributes );
- if( this.logger ){
- this.model.logger = this.logger;
- }
- this._setUpWebStorage( attributes.initiallyExpanded, attributes.show_deleted, attributes.show_hidden );
- this._setUpModelEventHandlers();
- this.selectedHdaIds = [];
- this.trigger( 'new-model', this );
- this.render();
return this;
},
@@ -324,12 +382,11 @@
* @see PersistentStorage
*/
_setUpWebStorage : function( initiallyExpanded, show_deleted, show_hidden ){
- //console.debug( '_setUpWebStorage', initiallyExpanded, show_deleted, show_hidden );
+ //this.log( '_setUpWebStorage', initiallyExpanded, show_deleted, show_hidden );
this.storage = new HistoryPrefs({
id: HistoryPrefs.historyStorageKey( this.model.get( 'id' ) )
});
-//TODO: move into HistoryPrefs
// expanded Hdas is a map of hda.ids -> a boolean repr'ing whether this hda's body is already expanded
// store any pre-expanded ids passed in
if( _.isObject( initiallyExpanded ) ){
@@ -348,34 +405,7 @@
this.trigger( 'new-storage', this.storage, this );
this.log( this + ' (init\'d) storage:', this.storage.get() );
- },
-
-//TODO: move into HistoryPrefs
- /** clear all stored history panel data */
- clearWebStorage : function(){
- for( var key in sessionStorage ){
- if( key.indexOf( 'history:' ) === 0 ){
- sessionStorage.removeItem( key );
- }
- }
- },
-
-//TODO: move into HistoryPrefs
- /** get all stored data as an Object for a history with the given id */
- getStoredOptions : function( historyId ){
- if( !historyId || historyId === 'current' ){
- return ( this.storage )?( this.storage.get() ):( {} );
- }
- //TODO: make storage engine generic
- var item = sessionStorage.getItem( HistoryPrefs.historyStorageKey( historyId ) );
- return ( item === null )?( {} ):( JSON.parse( item ) );
- },
-
-//TODO: move into HistoryPrefs
- /** get an array of expanded hda ids for the given history id */
- getExpandedHdaIds : function( historyId ){
- var expandedHdas = this.getStoredOptions( historyId ).expandedHdas;
- return (( _.isEmpty( expandedHdas ) )?( [] ):( _.keys( expandedHdas ) ));
+ return this;
},
// ------------------------------------------------------------------------ history/hda event listening
@@ -391,6 +421,7 @@
this.model.on( 'error error:hdas', function( model, xhr, options, msg ){
this.errorHandler( model, xhr, options, msg );
}, this );
+ return this;
},
// ------------------------------------------------------------------------ panel rendering
@@ -399,6 +430,7 @@
* @see Backbone.View#render
*/
render : function( speed, callback ){
+ this.log( 'render:', speed, callback );
// send a speed of 0 to have no fade in/out performed
speed = ( speed === undefined )?( this.fxSpeed ):( speed );
var panel = this,
@@ -414,7 +446,6 @@
// fade out existing, swap with the new, fade in, set up behaviours
$( panel ).queue( 'fx', [
function( next ){
- //panel.$el.fadeTo( panel.fxSpeed, 0.0001, next );
if( speed && panel.$el.is( ':visible' ) ){
panel.$el.fadeOut( speed, next );
} else {
@@ -446,7 +477,9 @@
return this;
},
- /** render with no history data */
+ /** render without history data
+ * @returns {jQuery} dom fragment with message container only
+ */
renderWithoutModel : function( ){
// we'll always need the message container
var $newRender = $( '<div/>' ),
@@ -455,8 +488,11 @@
return $newRender.append( $msgContainer );
},
- /** render with history data */
+ /** render with history data
+ * @returns {jQuery} dom fragment as temporary container to be swapped out later
+ */
renderModel : function( ){
+ // tmp div for final swap in render
var $newRender = $( '<div/>' );
// render based on anonymity, set up behaviors
@@ -485,13 +521,77 @@
//TODO: these should be either sub-MVs, or handled by events
$where = $where || this.$el;
$where.find( '[title]' ).tooltip({ placement: 'bottom' });
+ this._setUpSearchInput( $where.find( '.history-search-controls .history-search-input' ) );
+ return this;
+ },
+
+ // ------------------------------------------------------------------------ sub-$element shortcuts
+ /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */
+ $container : function(){
+ return ( this.findContainerFn )?( this.findContainerFn.call( this ) ):( this.$el.parent() );
+ },
+ /** where hdaViews are attached */
+ $datasetsList : function( $where ){
+ return ( $where || this.$el ).find( '.datasets-list' );
+ },
+ /** container where panel messages are attached */
+ $messages : function( $where ){
+ return ( $where || this.$el ).find( '.message-container' );
+ },
+ /** the message displayed when no hdaViews can be shown (no hdas, none matching search) */
+ $emptyMessage : function( $where ){
+ return ( $where || this.$el ).find( '.empty-history-message' );
},
// ------------------------------------------------------------------------ hda sub-views
- /** Create an HDA view for the given HDA (but leave attachment for addHdaView above)
+ /** Set up/render a view for each HDA to be shown, init with model and listeners.
+ * HDA views are cached to the map this.hdaViews (using the model.id as key).
+ * @param {jQuery} $whereTo what dom element to prepend the HDA views to
+ * @returns the number of visible hda views
+ */
+ renderHdas : function( $whereTo ){
+ $whereTo = $whereTo || this.$el;
+ var panel = this,
+ newHdaViews = {},
+ // only render the shown hdas
+ //TODO: switch to more general filtered pattern
+ visibleHdas = this.model.hdas.getVisible(
+ this.storage.get( 'show_deleted' ),
+ this.storage.get( 'show_hidden' ),
+ this.filters
+ );
+ //this.log( 'renderHdas, visibleHdas:', visibleHdas, $whereTo );
+//TODO: prepend to sep div, add as one
+
+ this.$datasetsList( $whereTo ).empty();
+
+ if( visibleHdas.length ){
+ visibleHdas.each( function( hda ){
+ // render it (NOTE: reverse order, newest on top (prepend))
+ var hdaId = hda.get( 'id' ),
+ hdaView = panel._createHdaView( hda );
+ newHdaViews[ hdaId ] = hdaView;
+ if( _.contains( panel.selectedHdaIds, hdaId ) ){
+ hdaView.selected = true;
+ }
+ panel.attachHdaView( hdaView.render(), $whereTo );
+ });
+ panel.$emptyMessage( $whereTo ).hide();
+
+ } else {
+ //this.log( 'emptyMsg:', panel.$emptyMessage( $whereTo ) )
+ panel.$emptyMessage( $whereTo )
+ .text( ( this.model.hdas.length && this.searchFor )?( this.noneFoundMsg ):( this.emptyMsg ) )
+ .show();
+ }
+ this.hdaViews = newHdaViews;
+ return this.hdaViews;
+ },
+
+ /** Create an HDA view for the given HDA and set up listeners (but leave attachment for addHdaView)
* @param {HistoryDatasetAssociation} hda
*/
- createHdaView : function( hda ){
+ _createHdaView : function( hda ){
var hdaId = hda.get( 'id' ),
hdaView = new this.HDAViewClass({
model : hda,
@@ -510,66 +610,26 @@
* @param {HDAView} hdaView HDAView (base or edit) to listen to
*/
_setUpHdaListeners : function( hdaView ){
- var historyView = this;
+ var panel = this;
+ hdaView.on( 'error', function( model, xhr, options, msg ){
+ panel.errorHandler( model, xhr, options, msg );
+ });
// maintain a list of hdas whose bodies are expanded
hdaView.on( 'body-expanded', function( id ){
- historyView.storage.addExpandedHda( id );
+ panel.storage.addExpandedHda( id );
});
hdaView.on( 'body-collapsed', function( id ){
- historyView.storage.removeExpandedHda( id );
+ panel.storage.removeExpandedHda( id );
});
- hdaView.on( 'error', function( model, xhr, options, msg ){
- historyView.errorHandler( model, xhr, options, msg );
- });
+ return this;
},
- /** Set up/render a view for each HDA to be shown, init with model and listeners.
- * HDA views are cached to the map this.hdaViews (using the model.id as key).
- * @param {jQuery} $whereTo what dom element to prepend the HDA views to
- * @returns the number of visible hda views
- */
- renderHdas : function( $whereTo ){
- $whereTo = $whereTo || this.$el;
- var historyView = this,
- newHdaViews = {},
- // only render the shown hdas
- //TODO: switch to more general filtered pattern
- visibleHdas = this.model.hdas.getVisible(
- this.storage.get( 'show_deleted' ),
- this.storage.get( 'show_hidden' ),
- this.filters
- );
- //console.debug( 'renderHdas, visibleHdas:', visibleHdas, $whereTo );
-//TODO: prepend to sep div, add as one
- $whereTo.find( this.datasetsSelector ).empty();
-
- if( visibleHdas.length ){
- visibleHdas.each( function( hda ){
- // render it (NOTE: reverse order, newest on top (prepend))
- var hdaId = hda.get( 'id' ),
- hdaView = historyView.createHdaView( hda );
- newHdaViews[ hdaId ] = hdaView;
- if( _.contains( historyView.selectedHdaIds, hdaId ) ){
- hdaView.selected = true;
- }
- historyView.attachHdaView( hdaView.render(), $whereTo );
- });
- $whereTo.find( this.emptyMsgSelector ).hide();
-
- } else {
- //console.debug( 'emptyMsg:', $whereTo.find( this.emptyMsgSelector ) )
- $whereTo.find( this.emptyMsgSelector )
- .text( ( this.model.hdas.length && this.searchFor )?( this.noneFoundMsg ):( this.emptyMsg ) )
- .show();
- }
- this.hdaViews = newHdaViews;
- return this.hdaViews;
- },
-
+ /** attach an hdaView to the panel */
attachHdaView : function( hdaView, $whereTo ){
$whereTo = $whereTo || this.$el;
- var $datasetsList = $whereTo.find( this.datasetsSelector );
+ var $datasetsList = this.$datasetsList( $whereTo );
$datasetsList.prepend( hdaView.$el );
+ return this;
},
/** Add an hda view to the panel for the given hda
@@ -581,13 +641,13 @@
// don't add the view if it wouldn't be visible accrd. to current settings
if( !hda.isVisible( this.storage.get( 'show_deleted' ), this.storage.get( 'show_hidden' ) ) ){
- return;
+ return panel;
}
// create and prepend to current el, if it was empty fadeout the emptyMsg first
$({}).queue([
function fadeOutEmptyMsg( next ){
- var $emptyMsg = panel.$el.find( panel.emptyMsgSelector );
+ var $emptyMsg = panel.$emptyMessage();
if( $emptyMsg.is( ':visible' ) ){
$emptyMsg.fadeOut( panel.fxSpeed, next );
} else {
@@ -595,16 +655,20 @@
}
},
function createAndPrepend( next ){
+ var hdaView = panel._createHdaView( hda );
+ panel.hdaViews[ hda.id ] = hdaView;
+ hdaView.render().$el.hide();
panel.scrollToTop();
- var $whereTo = panel.$el.find( panel.datasetsSelector ),
- hdaView = panel.createHdaView( hda );
- panel.hdaViews[ hda.id ] = hdaView;
- hdaView.render().$el.hide().prependTo( $whereTo ).slideDown( panel.fxSpeed );
+ panel.attachHdaView( hdaView );
+ hdaView.$el.slideDown( panel.fxSpeed );
}
]);
+ return panel;
},
- /** alias to the model. Updates the hda list only (not the history) */
+ //TODO: removeHdaView?
+
+ /** convenience alias to the model. Updates the hda list only (not the history) */
refreshHdas : function( detailIds, options ){
if( this.model ){
return this.model.refresh( detailIds, options );
@@ -613,11 +677,20 @@
return $.when();
},
+ ///** use underscore's findWhere to find a view where the model matches the terms
+ // * note: finds and returns the _first_ matching
+ // */
+ //findHdaView : function( terms ){
+ // if( !this.model || !this.model.hdas.length ){ return undefined; }
+ // var model = this.model.hdas.findWhere( terms );
+ // return ( model )?( this.hdaViews[ model.id ] ):( undefined );
+ //},
+
// ------------------------------------------------------------------------ panel events
/** event map */
events : {
// allow (error) messages to be clicked away
- //TODO: switch to common close (X) idiom
+//TODO: switch to common close (X) idiom
'click .message-container' : 'clearMessages',
'click .history-search-btn' : 'toggleSearchControls'
},
@@ -628,6 +701,7 @@
item.toggleBodyVisibility( null, false );
});
this.storage.set( 'expandedHdas', {} );
+ return this;
},
/** Handle the user toggling the deleted visibility by:
@@ -654,7 +728,7 @@
return this.storage.get( 'show_hidden' );
},
- // ........................................................................ filters
+ // ........................................................................ hda search & filters
/** render a search input for filtering datasets shown
* (see the search section in the HDA model for implementation of the actual searching)
* return will start the search
@@ -662,12 +736,12 @@
* clicking the clear button will clear the search
* uses searchInput in ui.js
*/
- setUpSearchInput : function( $where ){
+ _setUpSearchInput : function( $where ){
var panel = this,
inputSelector = '.history-search-input';
function onFirstSearch( searchFor ){
- //console.debug( 'onFirstSearch', searchFor, panel );
+ //this.log( 'onFirstSearch', searchFor, panel );
if( panel.model.hdas.haveDetails() ){
panel.searchHdas( searchFor );
return;
@@ -692,33 +766,22 @@
});
return $where;
},
-//TODO: to hidden/shown plugin
- showSearchControls : function( speed ){
- speed = ( speed === undefined )?( this.fxSpeed ):( speed );
+ /** toggle showing/hiding the search controls (rendering first on the initial show)
+ * @param {Event or Number} eventOrSpeed variadic - if number the speed of the show/hide effect
+ * @param {boolean} show force show/hide with T/F
+ */
+ toggleSearchControls : function( eventOrSpeed, show ){
var $searchControls = this.$el.find( '.history-search-controls' ),
- $input = $searchControls.find( '.history-search-input' );
- // if it hasn't been rendered - do it now
- if( !$input.children().size() ){
- this.setUpSearchInput( $input );
+ speed = ( jQuery.type( eventOrSpeed ) === 'number' )?( eventOrSpeed ):( this.fxSpeed );
+ show = ( show !== undefined )?( show ):( !$searchControls.is( ':visible' ) );
+ if( show ){
+ $searchControls.slideDown( speed, function(){
+ $( this ).find( 'input' ).focus();
+ });
+ } else {
+ $searchControls.slideUp( speed );
}
- // then slide open, focusing on the input, and persisting the setting when it's done
- $searchControls.slideDown( speed, function(){
- $( this ).find( 'input' ).focus();
- });
- },
- hideSearchControls : function( speed ){
- speed = ( speed === undefined )?( this.fxSpeed ):( speed );
- this.$el.find( '.history-search-controls' ).slideUp( speed );
- },
-
- /** toggle showing/hiding the search controls (rendering first on the initial show) */
- toggleSearchControls : function( eventOrSpeed ){
- speed = ( jQuery.type( eventOrSpeed ) === 'number' )?( eventOrSpeed ):( this.fxSpeed );
- if( this.$el.find( '.history-search-controls' ).is( ':visible' ) ){
- this.hideSearchControls( speed );
- } else {
- this.showSearchControls( speed );
- }
+ return show;
},
/** filter hda view list to those that contain the searchFor terms
@@ -726,26 +789,28 @@
*/
searchHdas : function( searchFor ){
//note: assumes hda details are loaded
- //console.debug( 'onSearch', searchFor, this );
+ //this.log( 'onSearch', searchFor, this );
var panel = this;
this.searchFor = searchFor;
this.filters = [ function( hda ){ return hda.matchesAll( panel.searchFor ); } ];
this.trigger( 'search:searching', searchFor, this );
this.renderHdas();
+ return this;
},
/** clear the search filters and show all views that are normally shown */
clearHdaSearch : function( searchFor ){
- //console.debug( 'onSearchClear', this );
+ //this.log( 'onSearchClear', this );
this.searchFor = '';
this.filters = [];
this.trigger( 'search:clear', this );
this.renderHdas();
+ return this;
},
// ........................................................................ loading indicator
/** hide the panel and display a loading indicator (in the panel's parent) when history model's are switched */
- showLoadingIndicator : function( msg, speed, callback ){
+ _showLoadingIndicator : function( msg, speed, callback ){
speed = ( speed !== undefined )?( speed ):( this.fxSpeed );
if( !this.indicator ){
this.indicator = new LoadingIndicator( this.$el, this.$el.parent() );
@@ -759,7 +824,7 @@
},
/** hide the loading indicator */
- hideLoadingIndicator : function( speed, callback ){
+ _hideLoadingIndicator : function( speed, callback ){
speed = ( speed !== undefined )?( speed ):( this.fxSpeed );
if( this.indicator ){
this.indicator.hide( speed, callback );
@@ -770,16 +835,17 @@
/** Display a message in the top of the panel.
* @param {String} type type of message ('done', 'error', 'warning')
* @param {String} msg the message to display
+ * @param {Object or HTML} modal contents displayed when the user clicks 'details' in the message
*/
displayMessage : function( type, msg, details ){
//precondition: msgContainer must have been rendered even if there's no model
var panel = this;
- //console.debug( 'displayMessage', type, msg, details );
+ //this.log( 'displayMessage', type, msg, details );
this.scrollToTop();
- var $msgContainer = this.$el.find( this.msgsSelector ),
+ var $msgContainer = this.$messages(),
$msg = $( '<div/>' ).addClass( type + 'message' ).html( msg );
- //console.debug( ' ', $msgContainer );
+ //this.log( ' ', $msgContainer );
if( !_.isEmpty( details ) ){
var $detailsLink = $( '<a href="javascript:void(0)">Details</a>' )
@@ -832,47 +898,25 @@
/** Remove all messages from the panel.
*/
clearMessages : function(){
- var $msgContainer = this.$el.find( this.msgsSelector );
- $msgContainer.empty();
+ this.$messages().empty();
+ return this;
},
// ........................................................................ scrolling
/** get the current scroll position of the panel in its parent */
scrollPosition : function(){
- return this.$el.scrollTop();
+ return this.$container().scrollTop();
},
/** set the current scroll position of the panel in its parent */
scrollTo : function( pos ){
- this.$el.scrollTop( pos );
+ this.$container().scrollTop( pos );
return this;
},
/** Scrolls the panel to the top. */
scrollToTop : function(){
- this.$el.scrollTop( 0 );
- return this;
- },
-
- /** Scrolls the panel (the enclosing container - in gen., the page) so that some object
- * is displayed in the vertical middle.
- * NOTE: if no height is given the panel will scroll to objTop (putting it at the top).
- * @param {Number} top top offset of the object to view
- * @param {Number} height height of the object to view
- * @returns {HistoryPanel} the panel
- */
- scrollIntoView : function( top, height ){
- if( height === undefined ){
- this.scrollTo( top );
- return this;
- }
- // otherwise, place the object in the vertical middle
- var viewport = window,
- $panelContainer = this.$el.parent(),
- containerHeight = $( viewport ).innerHeight(),
- middleOffset = ( containerHeight / 2 ) - ( height / 2 );
-
- this.scrollTo( top - middleOffset );
+ this.$container().scrollTop( 0 );
return this;
},
@@ -885,8 +929,9 @@
if( ( !id ) || ( !this.hdaViews[ id ] ) ){
return this;
}
- var $viewEl = this.hdaViews[ id ].$el;
- this.scrollIntoView( $viewEl.offset().top, $viewEl.outerHeight() );
+ var view = this.hdaViews[ id ];
+ //this.scrollIntoView( $viewEl.offset().top );
+ this.scrollTo( view.el.offsetTop );
return this;
},
@@ -901,18 +946,18 @@
return this.scrollToId( hda.id );
},
+ // ........................................................................ misc
/** Return a string rep of the history */
toString : function(){
return 'ReadOnlyHistoryPanel(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
}
});
-
-
//------------------------------------------------------------------------------ TEMPLATES
ReadOnlyHistoryPanel.templates = {
historyPanel : Handlebars.templates[ 'template-history-historyPanel' ]
};
+
//==============================================================================
return {
ReadOnlyHistoryPanel: ReadOnlyHistoryPanel
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/mvc/visualization/scatterplotControlForm.js
--- a/static/scripts/mvc/visualization/scatterplotControlForm.js
+++ /dev/null
@@ -1,631 +0,0 @@
-/* =============================================================================
-todo:
- I'd like to move the svg creation out of the splot constr. to:
- allow adding splots to an existing canvas
- allow mult. splots sharing a canvas
-
-
- outside this:
- BUG: setting width, height in plot controls doesn't re-interpolate data locations!!
- BUG?: get metadata_column_names (from datatype if necessary)
- BUG: single vis in popupmenu should have tooltip with that name NOT 'Visualizations'
-
- wire label setters, anim setter
-
- TwoVarScatterplot:
- ??: maybe better to do this with a canvas...
- save as visualization
- to seperate file?
- remove underscore dependencies
- add interface to change values (seperate)?
- download svg -> base64 encode
- incorporate glyphs, glyph state renderers
-
- ScatterplotSettingsForm:
- some css bug that lowers the width of settings form when plot-controls tab is open
- causes chart to shift
- what can be abstracted/reused for other graphs?
- avoid direct manipulation of this.plot
- allow option to put plot into seperate tab of interface (for small multiples)
-
- provide callback in view to load data incrementally - for large sets
- paginate
- handle rerender
- use endpoint (here and on the server (fileptr))
- fetch (new?) data
- handle rerender
- use d3.TSV?
- render warning on long data (> maxDataPoints)
- adjust endpoint
-
- selectable list of preset column comparisons (rnaseq etc.)
- how to know what sort of Tabular the data is?
- smarter about headers
- validate columns selection (here or server)
-
- set stats column names by selected columns
- move chart into tabbed area...
-
- Scatterplot.mako:
- multiple plots on one page (small multiples)
- ?? ensure svg styles thru d3 or css?
- d3: configable (easily)
- css: standard - better maintenance
- ? override at config
-
-============================================================================= */
-/**
- * Scatterplot control UI as a backbone view
- * handles:
- * getting the desired data
- * configuring the plot display
- * showing (general) statistics
- *
- * initialize attributes REQUIRES a dataset and an apiDatasetsURL
- */
-var ScatterplotControlForm = Backbone.View.extend( LoggableMixin ).extend({
- //logger : console,
- className : 'scatterplot-control-form',
-
- //NOTE: should include time needed to render
- dataLoadDelay : 4000,
- dataLoadSize : 5000,
-
- loadingIndicatorImage : 'loading_small_white_bg.gif',
- fetchMsg : 'Fetching data...',
- renderMsg : 'Rendering...',
-
- initialize : function( attributes ){
- this.log( this + '.initialize, attributes:', attributes );
-
- this.dataset = null;
- this.chartConfig = null;
- this.chart = null;
- this.loader = null;
-
- // set up refs to the four tab areas
- this.$dataControl = null;
- this.$chartControl = null;
- this.$statsDisplay = null;
- this.$chartDisplay = null;
-
- this.dataFetch = null;
-
- this.initializeFromAttributes( attributes );
- this.initializeChart( attributes );
- this.initializeDataLoader( attributes );
- },
-
- initializeFromAttributes : function( attributes ){
- // required settings: ensure certain vars we need are passed in attributes
- if( !attributes || !attributes.dataset ){
- throw( "ScatterplotView requires a dataset" );
- } else {
- this.dataset = attributes.dataset;
- }
- if( jQuery.type( this.dataset.metadata_column_types ) === 'string' ){
- this.dataset.metadata_column_types = this.dataset.metadata_column_types.split( ', ' );
- }
- this.log( '\t dataset:', this.dataset );
-
- // attempt to get possible headers from the data's first line
- if( this.dataset.comment_lines && this.dataset.comment_lines.length ){
- //TODO:??
- var firstLine = this.dataset.comment_lines[0],
- possibleHeaders = firstLine.split( '\t' );
- if( possibleHeaders.length === this.dataset.metadata_column_types.length ){
- this.possibleHeaders = possibleHeaders;
- }
- }
-
- // passed from mako helper
- //TODO: integrate to galaxyPaths
- //TODO: ?? seems like data loader section would be better
- if( !attributes.apiDatasetsURL ){
- throw( "ScatterplotView requires a apiDatasetsURL" );
- } else {
- this.dataURL = attributes.apiDatasetsURL + '/' + this.dataset.id + '?';
- }
- this.log( '\t dataURL:', this.dataURL );
- },
-
- initializeChart : function( attributes ){
- // set up the basic chart infrastructure and config (if any)
- this.chartConfig = attributes.chartConfig || {};
- //if( this.logger ){ this.chartConfig.debugging = true; }
- this.log( '\t initial chartConfig:', this.chartConfig );
-
- this.chart = new TwoVarScatterplot( this.chartConfig );
- //TODO: remove 2nd ref, use this.chart.config
- this.chartConfig = this.chart.config;
- },
-
- initializeDataLoader : function( attributes ){
- // set up data loader
- var view = this;
- this.loader = new LazyDataLoader({
- //logger : ( this.logger )?( this.logger ):( null ),
- // we'll generate this when columns are chosen
- url : null,
- start : attributes.start || 0,
- //NOTE: metadata_data_lines can be null (so we won't know the total)
- total : attributes.total || this.dataset.metadata_data_lines,
- delay : this.dataLoadDelay,
- size : this.dataLoadSize,
-
- buildUrl : function( start, size ){
- // currently VERY SPECIFIC to using data_providers.py start_val, max_vals params
- return this.url + '&' + jQuery.param({
- start_val: start,
- max_vals: size
- });
- }
- });
- $( this.loader ).bind( 'error', function( event, status, error ){
- view.log( 'ERROR:', status, error );
- alert( 'ERROR fetching data:\n' + status + '\n' + error );
- view.hideLoadingIndicator();
- });
- },
-
- // ------------------------------------------------------------------------- CONTROLS RENDERING
- render : function(){
- this.log( this + '.render' );
-
- // render the tab controls, areas and loading indicator
- this.$el.append( ScatterplotControlForm.templates.mainLayout({
- loadingIndicatorImagePath : galaxy_config.root + 'static/images/' + this.loadingIndicatorImage,
- message : ''
- }));
-
- // render the tab content
- this.$dataControl = this._render_dataControl();
- this.$chartControl = this._render_chartControl();
- this.$statsDisplay = this.$el.find( '.tab-pane#stats-display' );
- this.$chartDisplay = this._render_chartDisplay();
-
- // auto render if given both x, y column choices in query for page
- //TODO:?? add autoRender=1 to query maybe?
- if( this.chartConfig.xColumn && this.chartConfig.yColumn ){
- this.renderChart();
- }
-
- // set up behaviours
- this.$el.find( '[title]' ).tooltip();
-
- // uncomment any of the following to have that tab show on initial load (for testing)
- //this.$el.find( 'ul.nav' ).find( 'a[href="#data-control"]' ).tab( 'show' );
- //this.$el.find( 'ul.nav' ).find( 'a[href="#chart-control"]' ).tab( 'show' );
- //this.$el.find( 'ul.nav' ).find( 'a[href="#stats-display"]' ).tab( 'show' );
- //this.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' );
- return this;
- },
-
- _render_dataControl : function(){
- // controls for which columns are used to plot datapoints (and ids/additional info to attach if desired)
- var view = this,
- allColumns = [],
- numericColumns = [],
- usePossibleHeaders = ( this.possibleHeaders && this.$dataControl )?
- ( this.$dataControl.find( '#first-line-header-checkbox' ).is( ':checked' ) ):( false );
-
- // gather column indeces (from metadata_column_types) and names (from metadata_columnnames)
- _.each( this.dataset.metadata_column_types, function( type, index ){
- // use a 1 based index in names/values within the form (will be dec. when parsed out)
- var oneBasedIndex = index + 1,
- // default name is 'column <index>'...
- name = 'column ' + oneBasedIndex;
-
- // ...but label with the name if available...
- if( view.dataset.metadata_column_names ){
- name = view.dataset.metadata_column_names[ index ];
-
- // ...or, use the first line as headers if the user wants
- } else if( usePossibleHeaders ){
- name = view.possibleHeaders[ index ];
- }
-
- // cache all columns here
- allColumns.push({ index: oneBasedIndex, name: name });
-
- // filter numeric columns to their own list
- if( type === 'int' || type === 'float' ){
- numericColumns.push({ index: oneBasedIndex, name: name });
- }
- });
- //TODO: other vals: max_vals, start_val, pagination (chart-settings)
-
- // render the html
- var $dataControl = this.$el.find( '.tab-pane#data-control' );
- $dataControl.html( ScatterplotControlForm.templates.dataControl({
- allColumns : allColumns,
- numericColumns : numericColumns,
- possibleHeaders : ( this.possibleHeaders )?( this.possibleHeaders.join( ', ' ) ):( '' ),
- usePossibleHeaders : usePossibleHeaders
- }));
-
- if( !this.dataset.metadata_column_names && this.possibleHeaders ){
- $dataControl.find( '#first-line-header' ).show();
- }
-
- // preset to column selectors if they were passed in the config in the query string
- $dataControl.find( '#X-select' ).val( this.chartConfig.xColumn );
- $dataControl.find( '#Y-select' ).val( this.chartConfig.yColumn );
- if( this.chartConfig.idColumn !== undefined ){
- $dataControl.find( '#include-id-checkbox' )
- .attr( 'checked', true ).trigger( 'change' );
- $dataControl.find( '#ID-select' ).val( this.chartConfig.idColumn );
- }
-
- return $dataControl;
- },
-
- _render_chartControl : function(){
- // tab content to control how the chart is rendered (data glyph size, chart size, etc.)
- var view = this,
- $chartControl = this.$el.find( '.tab-pane#chart-control' ),
- // limits for controls (by control/chartConfig id)
- //TODO: move into TwoVarScatterplot
- controlRanges = {
- 'datapointSize' : { min: 2, max: 10, step: 1 },
- 'width' : { min: 200, max: 800, step: 20 },
- 'height' : { min: 200, max: 800, step: 20 }
- };
-
- // render the html
- $chartControl.append( ScatterplotControlForm.templates.chartControl( this.chartConfig ) );
-
- // set up behaviours, js on sliders
- $chartControl.find( '.numeric-slider-input' ).each( function(){
- var $this = $( this ),
- $output = $this.find( '.slider-output' ),
- $slider = $this.find( '.slider' ),
- id = $this.attr( 'id' );
- //chartControl.log( 'slider set up', 'this:', $this, 'slider:', $slider, 'id', id );
-
- // what to do when the slider changes: update display and update chartConfig
- //TODO: move out of loop
- function onSliderChange(){
- var $this = $( this ),
- newValue = $this.slider( 'value' );
- //chartControl.log( 'slider change', 'this:', $this, 'output:', $output, 'value', newValue );
- $output.text( newValue );
- //chartControl.chartConfig[ id ] = newValue;
- }
-
- $slider.slider( _.extend( controlRanges[ id ], {
- value : view.chartConfig[ id ],
- change : onSliderChange,
- slide : onSliderChange
- }));
- });
-
- return $chartControl;
- },
-
- _render_chartDisplay : function(){
- // render the tab content where the chart is displayed (but not the chart itself)
- var $chartDisplay = this.$el.find( '.tab-pane#chart-display' );
- $chartDisplay.append( ScatterplotControlForm.templates.chartDisplay( this.chartConfig ) );
- return $chartDisplay;
- },
-
- // ------------------------------------------------------------------------- EVENTS
- events : {
- 'change #include-id-checkbox' : 'toggleThirdColumnSelector',
- 'change #first-line-header-checkbox' : 'rerenderDataControl',
- 'click #data-control #render-button' : 'renderChart',
- 'click #chart-control #render-button' : 'changeChartSettings'
- },
-
- toggleThirdColumnSelector : function(){
- // show/hide the id selector on the data settings panel
- this.$el.find( 'select[name="ID"]' ).parent().toggle();
- },
-
- rerenderDataControl : function(){
- this.$dataControl = this._render_dataControl();
- },
-
- showLoadingIndicator : function( message, callback ){
- // display the loading indicator over the tab panels if hidden, update message (if passed)
- message = message || '';
- var indicator = this.$el.find( 'div#loading-indicator' );
- messageBox = indicator.find( '.loading-message' );
-
- if( indicator.is( ':visible' ) ){
- if( message ){
- messageBox.fadeOut( 'fast', function(){
- messageBox.text( message );
- messageBox.fadeIn( 'fast', callback );
- });
- } else {
- callback();
- }
-
- } else {
- if( message ){ messageBox.text( message ); }
- indicator.fadeIn( 'fast', callback );
- }
- },
-
- hideLoadingIndicator : function( callback ){
- this.$el.find( 'div#loading-indicator' ).fadeOut( 'fast', callback );
- },
-
- // ------------------------------------------------------------------------- CHART/STATS RENDERING
- renderChart : function(){
- // fetch the data, (re-)render the chart
- this.log( this + '.renderChart' );
-
- //TODO: separate data fetch
-
- // this is a complete re-render, so clear the prev. data
- this.data = null;
- this.meta = null;
-
- // update the chartConfig (here and chart) using chart settings
- //TODO: separate and improve (used in changeChartSettings too)
- _.extend( this.chartConfig, this.getChartSettings() );
- this.log( '\t chartConfig:', this.chartConfig );
- this.chart.updateConfig( this.chartConfig, false );
-
- // build the url with the current data settings
- this.loader.url = this.dataURL + '&' + jQuery.param( this.getDataSettings() );
- this.log( '\t loader: total lines:', this.loader.total, ' url:', this.loader.url );
-
- // bind the new data event to: aggregate data, update the chart and stats with new data
- var view = this;
- $( this.loader ).bind( 'loaded.new', function( event, response ){
- view.log( view + ' loaded.new', response );
-
- // aggregate data and meta
- view.postProcessDataFetchResponse( response );
- view.log( '\t postprocessed data:', view.data );
- view.log( '\t postprocessed meta:', view.meta );
-
- // update the chart and stats
- view.showLoadingIndicator( view.renderMsg, function(){
- view.chart.render( view.data, view.meta );
- view.renderStats( view.data, view.meta );
- view.hideLoadingIndicator();
- });
- });
- // when all data loaded - unbind (or we'll start doubling event handlers)
- $( this.loader ).bind( 'complete', function( event, data ){
- view.log( view + ' complete', data );
- $( view.loader ).unbind();
- });
-
- // begin loading the data, switch to the chart display tab
- view.showLoadingIndicator( view.fetchMsg, function(){
- view.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' );
- view.loader.load();
- });
- },
-
- renderStats : function(){
- this.log( this + '.renderStats' );
- // render the stats table in the stats panel
- //TODO: there's a better way
- this.$statsDisplay.html( ScatterplotControlForm.templates.statsDisplay({
- stats: [
- { name: 'Count', xval: this.meta[0].count, yval: this.meta[1].count },
- { name: 'Min', xval: this.meta[0].min, yval: this.meta[1].min },
- { name: 'Max', xval: this.meta[0].max, yval: this.meta[1].max },
- { name: 'Sum', xval: this.meta[0].sum, yval: this.meta[1].sum },
- { name: 'Mean', xval: this.meta[0].mean, yval: this.meta[1].mean },
- { name: 'Median', xval: this.meta[0].median, yval: this.meta[1].median }
- ]
- }));
- },
-
- changeChartSettings : function(){
- // re-render the chart with new chart settings and OLD data
- var view = this;
- newChartSettings = this.getChartSettings();
-
- // update the chart config from the chartSettings panel controls
- _.extend( this.chartConfig, newChartSettings );
- this.log( 'this.chartConfig:', this.chartConfig );
- this.chart.updateConfig( this.chartConfig, false );
-
- // if there's current data, call chart.render with it (no data fetch)
- if( view.data && view.meta ){
- view.showLoadingIndicator( view.renderMsg, function(){
- view.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' );
- view.chart.render( view.data, view.meta );
- view.hideLoadingIndicator();
- });
-
- // no current data, call renderChart instead (which will fetch data)
- } else {
- this.renderChart();
- }
- },
-
- // ------------------------------------------------------------------------- DATA AGGREGATION
- postProcessDataFetchResponse : function( response ){
- // the loader only returns new data - it's up to this to munge the fetches together properly
- //TODO: we're now storing data in two places: loader and here
- // can't we reduce incoming data into loader.data[0]? are there concurrency problems?
- this.postProcessData( response.data );
- this.postProcessMeta( response.meta );
- },
-
- postProcessData : function( newData ){
- // stack the column data on top of each other into this.data
- //this.log( this + '.postProcessData:', newData );
- var view = this;
-
- // if we already have data: aggregate
- if( view.data ){
- _.each( newData, function( newColData, colIndex ){
- //view.log( colIndex + ' data:', newColData );
- //TODO??: time, space efficiency of this?
- view.data[ colIndex ] = view.data[ colIndex ].concat( newColData );
- });
-
- // otherwise: assign (first load)
- } else {
- view.data = newData;
- }
- },
-
- postProcessMeta : function( newMeta ){
- // munge the meta data (stats) from the server fetches together
- //pre: this.data must be preprocessed (needed for medians)
- //this.log( this + '.postProcessMeta:', newMeta );
- var view = this,
- colTypes = this.dataset.metadata_column_types;
-
- // if we already have meta: aggregate
- if( view.meta ){
- _.each( newMeta, function( newColMeta, colIndex ){
- var colMeta = view.meta[ colIndex ],
- colType = colTypes[ colIndex ];
- //view.log( '\t ' + colIndex + ' postprocessing meta:', newColMeta );
- //view.log( colIndex + ' old meta:',
- // 'min:', colMeta.min,
- // 'max:', colMeta.max,
- // 'sum:', colMeta.sum,
- // 'mean:', colMeta.mean,
- // 'median:', colMeta.median
- //);
-
- //!TODO: at what point are we getting int/float overflow on these?!
- //??: need to be null safe?
- colMeta.count += ( newColMeta.count )?( newColMeta.count ):( 0 );
- //view.log( colIndex, 'count:', colMeta.count );
-
- if( ( colType === 'int' ) || ( colType === 'float' ) ){
- //view.log( colIndex + ' incoming meta:',
- // 'min:', newColMeta.min,
- // 'max:', newColMeta.max,
- // 'sum:', newColMeta.sum,
- // 'mean:', newColMeta.mean,
- // 'median:', newColMeta.median
- //);
-
- colMeta.min = Math.min( newColMeta.min, colMeta.min );
- colMeta.max = Math.max( newColMeta.max, colMeta.max );
- colMeta.sum = newColMeta.sum + colMeta.sum;
- colMeta.mean = ( colMeta.count )?( colMeta.sum / colMeta.count ):( null );
-
- // median's a pain bc of sorting (requires the data as well)
- var sortedCol = view.data[ colIndex ].slice().sort(),
- middleIndex = Math.floor( sortedCol.length / 2 );
-
- if( sortedCol.length % 2 === 0 ){
- colMeta.median = ( ( sortedCol[ middleIndex ] + sortedCol[( middleIndex + 1 )] ) / 2 );
-
- } else {
- colMeta.median = sortedCol[ middleIndex ];
- }
-
- //view.log( colIndex + ' new meta:',
- // 'min:', colMeta.min,
- // 'max:', colMeta.max,
- // 'sum:', colMeta.sum,
- // 'mean:', colMeta.mean,
- // 'median:', colMeta.median
- //);
- }
- });
-
- // otherwise: assign (first load)
- } else {
- view.meta = newMeta;
- //view.log( '\t meta (first load):', view.meta );
- }
- },
-
- // ------------------------------------------------------------------------- GET DATA/CHART SETTINGS
- getDataSettings : function(){
- // parse the column values for both indeces (for the data fetch) and names (for the chart)
- var columnSelections = this.getColumnSelections(),
- columns = [];
- this.log( '\t columnSelections:', columnSelections );
-
- //TODO: validate columns - minimally: we can assume either set by selectors or via a good query string
-
- // get column indices for params, include the desired ID column (if any)
- //NOTE: these are presented in human-readable 1 base index (to match the data.peek) - adjust
- columns = [
- columnSelections.X.colIndex - 1,
- columnSelections.Y.colIndex - 1
- ];
- if( this.$dataControl.find( '#include-id-checkbox' ).attr( 'checked' ) ){
- columns.push( columnSelections.ID.colIndex - 1 );
- }
- //TODO: other vals: max, start, page
-
- var params = {
- data_type : 'raw_data',
- provider : 'column_with_stats',
- columns : '[' + columns + ']'
- };
- this.log( '\t data settings (url params):', params );
- return params;
- },
-
- getColumnSelections : function(){
- // gets the current user-selected values for which columns to fetch from the data settings panel
- // returns a map: { column-select name (eg. X) : { colIndex : column-selector val,
- // colName : selected option text }, ... }
- var selections = {};
- this.$dataControl.find( 'div.column-select select' ).each( function(){
- var $this = $( this ),
- val = $this.val();
- selections[ $this.attr( 'name' ) ] = {
- colIndex : val,
- colName : $this.children( '[value="' + val + '"]' ).text()
- };
- });
- return selections;
- },
-
- getChartSettings : function(){
- // gets the user-selected chartConfig from the chart settings panel
- var settings = {},
- colSelections = this.getColumnSelections();
- //this.log( 'colSelections:', colSelections );
-
- //TODO: simplify with keys and loop
- settings.datapointSize = this.$chartControl.find( '#datapointSize.numeric-slider-input' )
- .find( '.slider' ).slider( 'value' );
- settings.width = this.$chartControl.find( '#width.numeric-slider-input' )
- .find( '.slider' ).slider( 'value' );
- settings.height = this.$chartControl.find( '#height.numeric-slider-input' )
- .find( '.slider' ).slider( 'value' );
-
- // update axes labels using chartSettings inputs (if not at defaults), otherwise the selects' colName
- //TODO: a little confusing
- var chartSettingsXLabel = this.$chartControl.find( 'input#X-axis-label' ).val(),
- chartSettingsYLabel = this.$chartControl.find( 'input#Y-axis-label' ).val();
- settings.xLabel = ( chartSettingsXLabel === 'X' )?
- ( colSelections.X.colName ):( chartSettingsXLabel );
- settings.yLabel = ( chartSettingsYLabel === 'Y' )?
- ( colSelections.Y.colName ):( chartSettingsYLabel );
-
- settings.animDuration = ( this.$chartControl.find( '#animate-chart' ).is( ':checked' ) )?
- ( this.chart.defaults.animDuration ):( 0 );
-
- this.log( '\t chartSettings:', settings );
- return settings;
- },
-
- toString : function(){
- return 'ScatterplotControlForm(' + (( this.dataset )?( this.dataset.id ):( '' )) + ')';
- }
-});
-
-ScatterplotControlForm.templates = {
- mainLayout : Handlebars.templates[ 'template-visualization-scatterplotControlForm' ],
- dataControl : Handlebars.templates[ 'template-visualization-dataControl' ],
- chartControl : Handlebars.templates[ 'template-visualization-chartControl' ],
- statsDisplay : Handlebars.templates[ 'template-visualization-statsDisplay' ],
- chartDisplay : Handlebars.templates[ 'template-visualization-chartDisplay' ]
-};
-
-//==============================================================================
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/packed/mvc/history/annotated-history-panel.js
--- a/static/scripts/packed/mvc/history/annotated-history-panel.js
+++ b/static/scripts/packed/mvc/history/annotated-history-panel.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-model","mvc/dataset/hda-base","mvc/history/readonly-history-panel"],function(c,a,b){var d=b.ReadOnlyHistoryPanel.extend({className:"annotated-history-panel",HDAViewClass:a.HDABaseView,renderModel:function(){this.$el.addClass(this.className);var g=b.ReadOnlyHistoryPanel.prototype.renderModel.call(this),e=g.find(this.datasetsSelector),f=$("<table/>").addClass("datasets-list datasets-table");f.append(e.children());g.find(this.datasetsSelector).replaceWith(f);g.find(".history-subtitle").after(this.renderHistoryAnnotation());g.find(".history-search-btn").hide();g.find(".history-controls").after(g.find(".history-search-controls").show());this.setUpSearchInput(g.find(".history-search-input"));return g},renderHistoryAnnotation:function(){var e=this.model.get("annotation");if(!e){return null}return $(['<div class="history-annotation">',e,"</div>"].join(""))},renderHdas:function(f){f=f||this.$el;var e=b.ReadOnlyHistoryPanel.prototype.renderHdas.call(this,f);f.find(this.datasetsSelector).prepend($("<tr/>").addClass("headers").append([$("<th/>").text(_l("Dataset")),$("<th/>").text(_l("Annotation"))]));return e},attachHdaView:function(h,f){f=f||this.$el;var i=_.find(h.el.classList,function(j){return(/^state\-/).test(j)}),e=h.model.get("annotation")||"",g=$("<tr/>").addClass("dataset-row").append([$("<td/>").addClass("dataset-container").append(h.$el).addClass(i?i.replace("-","-color-"):""),$("<td/>").addClass("additional-info").text(e)]);f.find(this.datasetsSelector).append(g)},events:_.extend(_.clone(b.ReadOnlyHistoryPanel.prototype.events),{"click tr":function(e){$(e.currentTarget).find(".dataset-title-bar").click()},"click .icon-btn":function(e){e.stopPropagation()}}),toString:function(){return"AnnotatedHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{AnnotatedHistoryPanel:d}});
\ No newline at end of file
+define(["mvc/dataset/hda-model","mvc/dataset/hda-base","mvc/history/readonly-history-panel"],function(c,a,b){var d=b.ReadOnlyHistoryPanel.extend({className:"annotated-history-panel",HDAViewClass:a.HDABaseView,renderModel:function(){this.$el.addClass(this.className);var g=b.ReadOnlyHistoryPanel.prototype.renderModel.call(this),e=this.$datasetsList(g),f=$("<table/>").addClass("datasets-list datasets-table");f.append(e.children());e.replaceWith(f);g.find(".history-subtitle").after(this.renderHistoryAnnotation());g.find(".history-search-btn").hide();g.find(".history-controls").after(g.find(".history-search-controls").show());return g},renderHistoryAnnotation:function(){var e=this.model.get("annotation");if(!e){return null}return $(['<div class="history-annotation">',e,"</div>"].join(""))},renderHdas:function(f){f=f||this.$el;var e=b.ReadOnlyHistoryPanel.prototype.renderHdas.call(this,f);this.$datasetsList(f).prepend($("<tr/>").addClass("headers").append([$("<th/>").text(_l("Dataset")),$("<th/>").text(_l("Annotation"))]));return e},attachHdaView:function(h,f){f=f||this.$el;var i=_.find(h.el.classList,function(j){return(/^state\-/).test(j)}),e=h.model.get("annotation")||"",g=$("<tr/>").addClass("dataset-row").append([$("<td/>").addClass("dataset-container").append(h.$el).addClass(i?i.replace("-","-color-"):""),$("<td/>").addClass("additional-info").text(e)]);this.$datasetsList(f).append(g)},events:_.extend(_.clone(b.ReadOnlyHistoryPanel.prototype.events),{"click tr":function(e){$(e.currentTarget).find(".dataset-title-bar").click()},"click .icon-btn":function(e){e.stopPropagation()}}),toString:function(){return"AnnotatedHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{AnnotatedHistoryPanel:d}});
\ No newline at end of file
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/packed/mvc/history/current-history-panel.js
--- a/static/scripts/packed/mvc/history/current-history-panel.js
+++ b/static/scripts/packed/mvc/history/current-history-panel.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-edit","mvc/history/history-panel"],function(b,e){var c=SessionStorageModel.extend({defaults:{searching:false,tagsEditorShown:false,annotationEditorShown:false},toString:function(){return"HistoryPanelPrefs("+JSON.stringify(this.toJSON())+")"}});c.storageKey=function d(){return("history-panel")};var a=e.HistoryPanel.extend({HDAViewClass:b.HDAEditView,emptyMsg:_l("This history is empty. Click 'Get Data' on the left pane to start"),noneFoundMsg:_l("No matching datasets found"),initialize:function(f){f=f||{};this.preferences=new c(_.extend({id:c.storageKey()},_.pick(f,_.keys(c.prototype.defaults))));e.HistoryPanel.prototype.initialize.call(this,f);if(this.model){console.debug(this.model);this.model.checkForUpdates()}},loadCurrentHistory:function(g){var f=this;return this.loadHistoryWithHDADetails("current",g).then(function(i,h){f.trigger("current-history",f)})},switchToHistory:function(i,h){var f=this,g=function(){return jQuery.post(galaxy_config.root+"api/histories/"+i+"/set_as_current")};return this.loadHistoryWithHDADetails(i,h,g).then(function(k,j){f.trigger("switched-history",f)})},createNewHistory:function(h){if(!Galaxy||!Galaxy.currUser||Galaxy.currUser.isAnonymous()){this.displayMessage("error",_l("You must be logged in to create histories"));return $.when()}var f=this,g=function(){return jQuery.post(galaxy_config.root+"api/histories",{current:true})};return this.loadHistory(undefined,h,g).then(function(j,i){f.trigger("new-history",f)})},setModel:function(h,f,g){g=g||{};e.HistoryPanel.prototype.setModel.call(this,h,f,g);this.model.checkForUpdates();return this},_setUpModelEventHandlers:function(){e.HistoryPanel.prototype._setUpModelEventHandlers.call(this);if(Galaxy&&Galaxy.quotaMeter){this.listenTo(this.model,"change:nice_size",function(){Galaxy.quotaMeter.update()})}this.model.hdas.on("state:ready",function(g,h,f){if((!g.get("visible"))&&(!this.storage.get("show_hidden"))){this.removeHdaView(this.hdaViews[g.id])}},this)},render:function(h,i){h=(h===undefined)?(this.fxSpeed):(h);var f=this,g;if(this.model){g=this.renderModel()}else{g=this.renderWithoutModel()}$(f).queue("fx",[function(j){if(h&&f.$el.is(":visible")){f.$el.fadeOut(h,j)}else{j()}},function(j){f.$el.empty();if(g){f.$el.append(g.children());f.renderBasedOnPrefs()}j()},function(j){if(h&&!f.$el.is(":visible")){f.$el.fadeIn(h,j)}else{j()}},function(j){if(i){i.call(this)}f.trigger("rendered",this);j()}]);return this},renderBasedOnPrefs:function(){if(this.preferences.get("searching")){this.showSearchControls(0)}},_renderTags:function(f){var g=this;e.HistoryPanel.prototype._renderTags.call(this,f);if(this.preferences.get("tagsEditorShown")){this.tagsEditor.toggle(true)}this.tagsEditor.on("hiddenUntilActivated:shown hiddenUntilActivated:hidden",function(h){g.preferences.set("tagsEditorShown",h.hidden)})},_renderAnnotation:function(f){var g=this;e.HistoryPanel.prototype._renderAnnotation.call(this,f);if(this.preferences.get("annotationEditorShown")){this.annotationEditor.toggle(true)}this.annotationEditor.on("hiddenUntilActivated:shown hiddenUntilActivated:hidden",function(h){g.preferences.set("annotationEditorShown",h.hidden)})},connectToQuotaMeter:function(f){if(!f){return this}this.listenTo(f,"quota:over",this.showQuotaMessage);this.listenTo(f,"quota:under",this.hideQuotaMessage);this.on("rendered rendered:initial",function(){if(f&&f.isOverQuota()){this.showQuotaMessage()}});return this},showQuotaMessage:function(){var f=this.$el.find(".quota-message");if(f.is(":hidden")){f.slideDown(this.fxSpeed)}},hideQuotaMessage:function(){var f=this.$el.find(".quota-message");if(!f.is(":hidden")){f.slideUp(this.fxSpeed)}},connectToOptionsMenu:function(f){if(!f){return this}this.on("new-storage",function(h,g){if(f&&h){f.findItemByHtml(_l("Include Deleted Datasets")).checked=h.get("show_deleted");f.findItemByHtml(_l("Include Hidden Datasets")).checked=h.get("show_hidden")}});return this},toString:function(){return"CurrentHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{CurrentHistoryPanel:a}});
\ No newline at end of file
+define(["mvc/dataset/hda-edit","mvc/history/history-panel"],function(b,e){var c=SessionStorageModel.extend({defaults:{searching:false,tagsEditorShown:false,annotationEditorShown:false},toString:function(){return"HistoryPanelPrefs("+JSON.stringify(this.toJSON())+")"}});c.storageKey=function d(){return("history-panel")};var a=e.HistoryPanel.extend({HDAViewClass:b.HDAEditView,emptyMsg:_l("This history is empty. Click 'Get Data' on the left pane to start"),noneFoundMsg:_l("No matching datasets found"),initialize:function(f){f=f||{};this.preferences=new c(_.extend({id:c.storageKey()},_.pick(f,_.keys(c.prototype.defaults))));e.HistoryPanel.prototype.initialize.call(this,f)},loadCurrentHistory:function(g){var f=this;return this.loadHistoryWithHDADetails("current",g).then(function(i,h){f.trigger("current-history",f)})},switchToHistory:function(i,h){var f=this,g=function(){return jQuery.post(galaxy_config.root+"api/histories/"+i+"/set_as_current")};return this.loadHistoryWithHDADetails(i,h,g).then(function(k,j){f.trigger("switched-history",f)})},createNewHistory:function(h){if(!Galaxy||!Galaxy.currUser||Galaxy.currUser.isAnonymous()){this.displayMessage("error",_l("You must be logged in to create histories"));return $.when()}var f=this,g=function(){return jQuery.post(galaxy_config.root+"api/histories",{current:true})};return this.loadHistory(undefined,h,g).then(function(j,i){f.trigger("new-history",f)})},setModel:function(g,f,h){e.HistoryPanel.prototype.setModel.call(this,g,f,h);if(this.model){this.log("checking for updates");this.model.checkForUpdates()}return this},_setUpModelEventHandlers:function(){e.HistoryPanel.prototype._setUpModelEventHandlers.call(this);if(Galaxy&&Galaxy.quotaMeter){this.listenTo(this.model,"change:nice_size",function(){Galaxy.quotaMeter.update()})}this.model.hdas.on("state:ready",function(g,h,f){if((!g.get("visible"))&&(!this.storage.get("show_hidden"))){this.removeHdaView(this.hdaViews[g.id])}},this)},render:function(h,i){h=(h===undefined)?(this.fxSpeed):(h);var f=this,g;if(this.model){g=this.renderModel()}else{g=this.renderWithoutModel()}$(f).queue("fx",[function(j){if(h&&f.$el.is(":visible")){f.$el.fadeOut(h,j)}else{j()}},function(j){f.$el.empty();if(g){f.$el.append(g.children());f.renderBasedOnPrefs()}j()},function(j){if(h&&!f.$el.is(":visible")){f.$el.fadeIn(h,j)}else{j()}},function(j){if(i){i.call(this)}f.trigger("rendered",this);j()}]);return this},renderBasedOnPrefs:function(){if(this.preferences.get("searching")){this.toggleSearchControls(0,true)}},toggleSearchControls:function(g,f){var h=e.HistoryPanel.prototype.toggleSearchControls.call(this,g,f);this.preferences.set("searching",h)},_renderTags:function(f){var g=this;e.HistoryPanel.prototype._renderTags.call(this,f);if(this.preferences.get("tagsEditorShown")){this.tagsEditor.toggle(true)}this.tagsEditor.on("hiddenUntilActivated:shown hiddenUntilActivated:hidden",function(h){g.preferences.set("tagsEditorShown",h.hidden)})},_renderAnnotation:function(f){var g=this;e.HistoryPanel.prototype._renderAnnotation.call(this,f);if(this.preferences.get("annotationEditorShown")){this.annotationEditor.toggle(true)}this.annotationEditor.on("hiddenUntilActivated:shown hiddenUntilActivated:hidden",function(h){g.preferences.set("annotationEditorShown",h.hidden)})},connectToQuotaMeter:function(f){if(!f){return this}this.listenTo(f,"quota:over",this.showQuotaMessage);this.listenTo(f,"quota:under",this.hideQuotaMessage);this.on("rendered rendered:initial",function(){if(f&&f.isOverQuota()){this.showQuotaMessage()}});return this},showQuotaMessage:function(){var f=this.$el.find(".quota-message");if(f.is(":hidden")){f.slideDown(this.fxSpeed)}},hideQuotaMessage:function(){var f=this.$el.find(".quota-message");if(!f.is(":hidden")){f.slideUp(this.fxSpeed)}},connectToOptionsMenu:function(f){if(!f){return this}this.on("new-storage",function(h,g){if(f&&h){f.findItemByHtml(_l("Include Deleted Datasets")).checked=h.get("show_deleted");f.findItemByHtml(_l("Include Hidden Datasets")).checked=h.get("show_hidden")}});return this},toString:function(){return"CurrentHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{CurrentHistoryPanel:a}});
\ No newline at end of file
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/packed/mvc/history/history-panel.js
--- a/static/scripts/packed/mvc/history/history-panel.js
+++ b/static/scripts/packed/mvc/history/history-panel.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-model","mvc/dataset/hda-edit","mvc/history/readonly-history-panel"],function(c,a,b){var d=b.ReadOnlyHistoryPanel.extend({HDAViewClass:a.HDAEditView,initialize:function(e){e=e||{};this.selectedHdaIds=[];this.tagsEditor=null;this.annotationEditor=null;this.selecting=e.selecting||false;this.annotationEditorShown=e.annotationEditorShown||false;this.tagsEditorShown=e.tagsEditorShown||false;b.ReadOnlyHistoryPanel.prototype.initialize.call(this,e)},_setUpModelEventHandlers:function(){b.ReadOnlyHistoryPanel.prototype._setUpModelEventHandlers.call(this);this.model.on("change:nice_size",this.updateHistoryDiskSize,this);this.model.hdas.on("change:deleted",this.handleHdaDeletionChange,this);this.model.hdas.on("change:visible",this.handleHdaVisibleChange,this);this.model.hdas.on("change:purged",function(e){this.model.fetch()},this)},renderModel:function(){var e=$("<div/>");e.append(d.templates.historyPanel(this.model.toJSON()));if(Galaxy.currUser.id&&Galaxy.currUser.id===this.model.get("user_id")){this._renderTags(e);this._renderAnnotation(e)}e.find(".history-secondary-actions").prepend(this._renderSelectButton());e.find(".history-dataset-actions").toggle(this.selecting);e.find(".history-secondary-actions").prepend(this._renderSearchButton());this._setUpBehaviours(e);this.renderHdas(e);return e},_renderTags:function(e){var f=this;this.tagsEditor=new TagsEditor({model:this.model,el:e.find(".history-controls .tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){f.toggleHDATagEditors(true,f.fxSpeed)},onhide:function(){f.toggleHDATagEditors(false,f.fxSpeed)},$activator:faIconButton({title:_l("Edit history tags"),classes:"history-tag-btn",faIcon:"fa-tags"}).appendTo(e.find(".history-secondary-actions"))})},_renderAnnotation:function(e){var f=this;this.annotationEditor=new AnnotationEditor({model:this.model,el:e.find(".history-controls .annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){f.toggleHDAAnnotationEditors(true,f.fxSpeed)},onhide:function(){f.toggleHDAAnnotationEditors(false,f.fxSpeed)},$activator:faIconButton({title:_l("Edit history Annotation"),classes:"history-annotate-btn",faIcon:"fa-comment"}).appendTo(e.find(".history-secondary-actions"))})},_renderSelectButton:function(e){return faIconButton({title:_l("Operations on multiple datasets"),classes:"history-select-btn",faIcon:"fa-check-square-o"})},_setUpBehaviours:function(e){e=e||this.$el;e.find("[title]").tooltip({placement:"bottom"});if(!this.model){return}this._setUpDatasetActionsPopup(e);if((!Galaxy.currUser||Galaxy.currUser.isAnonymous())||(Galaxy.currUser.id!==this.model.get("user_id"))){return}var f=this;e.find(".history-name").attr("title",_l("Click to rename history")).tooltip({placement:"bottom"}).make_text_editable({on_finish:function(g){var h=f.model.get("name");if(g&&g!==h){f.$el.find(".history-name").text(g);f.model.save({name:g}).fail(function(){f.$el.find(".history-name").text(f.model.previous("name"))})}else{f.$el.find(".history-name").text(h)}}})},_setUpDatasetActionsPopup:function(e){var f=this;(new PopupMenu(e.find(".history-dataset-action-popup-btn"),[{html:_l("Hide datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype.hide;f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Unhide datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype.unhide;f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Delete datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype["delete"];f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Undelete datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype.undelete;f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Permanently delete datasets"),func:function(){if(confirm(_l("This will permanently remove the data in your datasets. Are you sure?"))){var g=c.HistoryDatasetAssociation.prototype.purge;f.getSelectedHdaCollection().ajaxQueue(g)}}}]))},handleHdaDeletionChange:function(e){if(e.get("deleted")&&!this.storage.get("show_deleted")){this.removeHdaView(this.hdaViews[e.id])}},handleHdaVisibleChange:function(e){if(e.hidden()&&!this.storage.get("show_hidden")){this.removeHdaView(this.hdaViews[e.id])}},createHdaView:function(f){var e=f.get("id"),g=new this.HDAViewClass({model:f,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[e],selectable:this.selecting,hasUser:this.model.ownedByCurrUser(),logger:this.logger,tagsEditorShown:(this.tagsEditor&&!this.tagsEditor.hidden),annotationEditorShown:(this.annotationEditor&&!this.annotationEditor.hidden)});this._setUpHdaListeners(g);return g},_setUpHdaListeners:function(f){var e=this;b.ReadOnlyHistoryPanel.prototype._setUpHdaListeners.call(this,f);f.on("selected",function(g){var h=g.model.get("id");e.selectedHdaIds=_.union(e.selectedHdaIds,[h])});f.on("de-selected",function(g){var h=g.model.get("id");e.selectedHdaIds=_.without(e.selectedHdaIds,h)})},toggleHDATagEditors:function(e){var f=arguments;_.each(this.hdaViews,function(g){if(g.tagsEditor){g.tagsEditor.toggle.apply(g.tagsEditor,f)}})},toggleHDAAnnotationEditors:function(e){var f=arguments;_.each(this.hdaViews,function(g){if(g.annotationEditor){g.annotationEditor.toggle.apply(g.annotationEditor,f)}})},removeHdaView:function(f){if(!f){return}var e=this;f.$el.fadeOut(e.fxSpeed,function(){f.off();f.remove();delete e.hdaViews[f.model.id];if(_.isEmpty(e.hdaViews)){e.$el.find(e.emptyMsgSelector).fadeIn(e.fxSpeed,function(){e.trigger("empty-history",e)})}})},events:_.extend(_.clone(b.ReadOnlyHistoryPanel.prototype.events),{"click .history-select-btn":function(f){this.toggleSelectors(this.fxSpeed)},"click .history-select-all-datasets-btn":"selectAllDatasets","click .history-deselect-all-datasets-btn":"deselectAllDatasets"}),updateHistoryDiskSize:function(){this.$el.find(".history-size").text(this.model.get("nice_size"))},showSelectors:function(e){this.selecting=true;this.$el.find(".history-dataset-actions").slideDown(e);_.each(this.hdaViews,function(f){f.showSelector(e)});this.selectedHdaIds=[]},hideSelectors:function(e){this.selecting=false;this.$el.find(".history-dataset-actions").slideUp(e);_.each(this.hdaViews,function(f){f.hideSelector(e)});this.selectedHdaIds=[]},toggleSelectors:function(e){if(!this.selecting){this.showSelectors(e)}else{this.hideSelectors(e)}},selectAllDatasets:function(e){_.each(this.hdaViews,function(f){f.select(e)})},deselectAllDatasets:function(e){_.each(this.hdaViews,function(f){f.deselect(e)})},getSelectedHdaViews:function(){return _.filter(this.hdaViews,function(e){return e.selected})},getSelectedHdaCollection:function(){return new c.HDACollection(_.map(this.getSelectedHdaViews(),function(e){return e.model}),{historyId:this.model.id})},toString:function(){return"HistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{HistoryPanel:d}});
\ No newline at end of file
+define(["mvc/dataset/hda-model","mvc/dataset/hda-edit","mvc/history/readonly-history-panel"],function(c,a,b){var d=b.ReadOnlyHistoryPanel.extend({HDAViewClass:a.HDAEditView,initialize:function(e){e=e||{};this.selectedHdaIds=[];this.tagsEditor=null;this.annotationEditor=null;this.selecting=e.selecting||false;this.annotationEditorShown=e.annotationEditorShown||false;this.tagsEditorShown=e.tagsEditorShown||false;b.ReadOnlyHistoryPanel.prototype.initialize.call(this,e)},_setUpModelEventHandlers:function(){b.ReadOnlyHistoryPanel.prototype._setUpModelEventHandlers.call(this);this.model.on("change:nice_size",this.updateHistoryDiskSize,this);this.model.hdas.on("change:deleted",this._handleHdaDeletionChange,this);this.model.hdas.on("change:visible",this._handleHdaVisibleChange,this);this.model.hdas.on("change:purged",function(e){this.model.fetch()},this)},renderModel:function(){var e=$("<div/>");e.append(d.templates.historyPanel(this.model.toJSON()));if(Galaxy.currUser.id&&Galaxy.currUser.id===this.model.get("user_id")){this._renderTags(e);this._renderAnnotation(e)}e.find(".history-secondary-actions").prepend(this._renderSelectButton());e.find(".history-dataset-actions").toggle(this.selecting);e.find(".history-secondary-actions").prepend(this._renderSearchButton());this._setUpBehaviours(e);this.renderHdas(e);return e},_renderTags:function(e){var f=this;this.tagsEditor=new TagsEditor({model:this.model,el:e.find(".history-controls .tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){f.toggleHDATagEditors(true,f.fxSpeed)},onhide:function(){f.toggleHDATagEditors(false,f.fxSpeed)},$activator:faIconButton({title:_l("Edit history tags"),classes:"history-tag-btn",faIcon:"fa-tags"}).appendTo(e.find(".history-secondary-actions"))})},_renderAnnotation:function(e){var f=this;this.annotationEditor=new AnnotationEditor({model:this.model,el:e.find(".history-controls .annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){f.toggleHDAAnnotationEditors(true,f.fxSpeed)},onhide:function(){f.toggleHDAAnnotationEditors(false,f.fxSpeed)},$activator:faIconButton({title:_l("Edit history Annotation"),classes:"history-annotate-btn",faIcon:"fa-comment"}).appendTo(e.find(".history-secondary-actions"))})},_renderSelectButton:function(e){return faIconButton({title:_l("Operations on multiple datasets"),classes:"history-select-btn",faIcon:"fa-check-square-o"})},_setUpBehaviours:function(e){e=e||this.$el;b.ReadOnlyHistoryPanel.prototype._setUpBehaviours.call(this,e);if(!this.model){return}this._setUpDatasetActionsPopup(e);if((!Galaxy.currUser||Galaxy.currUser.isAnonymous())||(Galaxy.currUser.id!==this.model.get("user_id"))){return}var f=this;e.find(".history-name").attr("title",_l("Click to rename history")).tooltip({placement:"bottom"}).make_text_editable({on_finish:function(g){var h=f.model.get("name");if(g&&g!==h){f.$el.find(".history-name").text(g);f.model.save({name:g}).fail(function(){f.$el.find(".history-name").text(f.model.previous("name"))})}else{f.$el.find(".history-name").text(h)}}})},_setUpDatasetActionsPopup:function(e){var f=this;(new PopupMenu(e.find(".history-dataset-action-popup-btn"),[{html:_l("Hide datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype.hide;f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Unhide datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype.unhide;f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Delete datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype["delete"];f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Undelete datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype.undelete;f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Permanently delete datasets"),func:function(){if(confirm(_l("This will permanently remove the data in your datasets. Are you sure?"))){var g=c.HistoryDatasetAssociation.prototype.purge;f.getSelectedHdaCollection().ajaxQueue(g)}}}]))},_handleHdaDeletionChange:function(e){if(e.get("deleted")&&!this.storage.get("show_deleted")){this.removeHdaView(this.hdaViews[e.id])}},_handleHdaVisibleChange:function(e){if(e.hidden()&&!this.storage.get("show_hidden")){this.removeHdaView(this.hdaViews[e.id])}},_createHdaView:function(f){var e=f.get("id"),g=new this.HDAViewClass({model:f,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[e],selectable:this.selecting,hasUser:this.model.ownedByCurrUser(),logger:this.logger,tagsEditorShown:(this.tagsEditor&&!this.tagsEditor.hidden),annotationEditorShown:(this.annotationEditor&&!this.annotationEditor.hidden)});this._setUpHdaListeners(g);return g},_setUpHdaListeners:function(f){var e=this;b.ReadOnlyHistoryPanel.prototype._setUpHdaListeners.call(this,f);f.on("selected",function(g){var h=g.model.get("id");e.selectedHdaIds=_.union(e.selectedHdaIds,[h])});f.on("de-selected",function(g){var h=g.model.get("id");e.selectedHdaIds=_.without(e.selectedHdaIds,h)})},toggleHDATagEditors:function(e){var f=arguments;_.each(this.hdaViews,function(g){if(g.tagsEditor){g.tagsEditor.toggle.apply(g.tagsEditor,f)}})},toggleHDAAnnotationEditors:function(e){var f=arguments;_.each(this.hdaViews,function(g){if(g.annotationEditor){g.annotationEditor.toggle.apply(g.annotationEditor,f)}})},removeHdaView:function(f){if(!f){return}var e=this;f.$el.fadeOut(e.fxSpeed,function(){f.off();f.remove();delete e.hdaViews[f.model.id];if(_.isEmpty(e.hdaViews)){e.$emptyMessage().fadeIn(e.fxSpeed,function(){e.trigger("empty-history",e)})}})},events:_.extend(_.clone(b.ReadOnlyHistoryPanel.prototype.events),{"click .history-select-btn":function(f){this.toggleSelectors(this.fxSpeed)},"click .history-select-all-datasets-btn":"selectAllDatasets","click .history-deselect-all-datasets-btn":"deselectAllDatasets"}),updateHistoryDiskSize:function(){this.$el.find(".history-size").text(this.model.get("nice_size"))},showSelectors:function(e){this.selecting=true;this.$el.find(".history-dataset-actions").slideDown(e);_.each(this.hdaViews,function(f){f.showSelector(e)});this.selectedHdaIds=[]},hideSelectors:function(e){this.selecting=false;this.$el.find(".history-dataset-actions").slideUp(e);_.each(this.hdaViews,function(f){f.hideSelector(e)});this.selectedHdaIds=[]},toggleSelectors:function(e){if(!this.selecting){this.showSelectors(e)}else{this.hideSelectors(e)}},selectAllDatasets:function(e){_.each(this.hdaViews,function(f){f.select(e)})},deselectAllDatasets:function(e){_.each(this.hdaViews,function(f){f.deselect(e)})},getSelectedHdaViews:function(){return _.filter(this.hdaViews,function(e){return e.selected})},getSelectedHdaCollection:function(){return new c.HDACollection(_.map(this.getSelectedHdaViews(),function(e){return e.model}),{historyId:this.model.id})},toString:function(){return"HistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{HistoryPanel:d}});
\ No newline at end of file
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/packed/mvc/history/readonly-history-panel.js
--- a/static/scripts/packed/mvc/history/readonly-history-panel.js
+++ b/static/scripts/packed/mvc/history/readonly-history-panel.js
@@ -1,1 +1,1 @@
-define(["mvc/history/history-model","mvc/dataset/hda-base"],function(e,a){var d=SessionStorageModel.extend({defaults:{expandedHdas:{},show_deleted:false,show_hidden:false},addExpandedHda:function(f){this.save("expandedHdas",_.extend(this.get("expandedHdas"),_.object([f],[true])))},removeExpandedHda:function(f){this.save("expandedHdas",_.omit(this.get("expandedHdas"),f))},toString:function(){return"HistoryPrefs("+this.id+")"}});d.historyStorageKey=function c(f){if(!f){throw new Error("HistoryPrefs.historyStorageKey needs valid id: "+f)}return("history:"+f)};var b=Backbone.View.extend(LoggableMixin).extend({HDAViewClass:a.HDABaseView,tagName:"div",className:"history-panel",fxSpeed:"fast",datasetsSelector:".datasets-list",msgsSelector:".message-container",emptyMsgSelector:".empty-history-message",emptyMsg:_l("This history is empty"),noneFoundMsg:_l("No matching datasets found"),initialize:function(f){f=f||{};if(f.logger){this.logger=f.logger}this.log(this+".initialize:",f);this.linkTarget=f.linkTarget||"_blank";this.fxSpeed=_.has(f,"fxSpeed")?(f.fxSpeed):(this.fxSpeed);this.hdaViews={};this.indicator=new LoadingIndicator(this.$el);this.filters=[];this.searchFor="";this._setUpListeners();if(this.model){if(this.logger){this.model.logger=this.logger}this._setUpWebStorage(f.initiallyExpanded,f.show_deleted,f.show_hidden);this._setUpModelEventHandlers()}if(f.onready){f.onready.call(this)}},_setUpListeners:function(){this.on("error",function(g,j,f,i,h){this.errorHandler(g,j,f,i,h)});this.on("loading-history",function(){this.showLoadingIndicator("loading history...",40)});this.on("loading-done",function(){this.hideLoadingIndicator(40);if(_.isEmpty(this.hdaViews)){this.trigger("empty-history",this)}});this.once("rendered",function(){this.trigger("rendered:initial",this);return false});if(this.logger){this.on("all",function(f){this.log(this+"",arguments)},this)}},errorHandler:function(h,k,g,j,i){var f=this._parseErrorMessage(h,k,g,j,i);if(k&&k.status===0&&k.readyState===0){}else{if(k&&k.status===502){}else{if(!this.$el.find(this.msgsSelector).is(":visible")){this.once("rendered",function(){this.displayMessage("error",f.message,f.details)})}else{this.displayMessage("error",f.message,f.details)}}}},_parseErrorMessage:function(i,m,h,l,k){var g=Galaxy.currUser,f={message:this._bePolite(l),details:{user:(g instanceof User)?(g.toJSON()):(g+""),source:(i instanceof Backbone.Model)?(i.toJSON()):(i+""),xhr:m,options:(m)?(_.omit(h,"xhr")):(h)}};_.extend(f.details,k||{});if(m&&_.isFunction(m.getAllResponseHeaders)){var j=m.getAllResponseHeaders();j=_.compact(j.split("\n"));j=_.map(j,function(n){return n.split(": ")});f.details.xhr.responseHeaders=_.object(j)}return f},_bePolite:function(f){f=f||_l("An error occurred while getting updates from the server");return f+". "+_l("Please contact a Galaxy administrator if the problem persists.")},loadHistoryWithHDADetails:function(i,h,g,k){var f=this,j=function(l){return f.getExpandedHdaIds(l.id)};return this.loadHistory(i,h,g,k,j)},loadHistory:function(i,h,g,l,j){this.trigger("loading-history",this);h=h||{};var f=this;var k=e.History.getHistoryData(i,{historyFn:g,hdaFn:l,hdaDetailIds:h.initiallyExpanded||j});return this._loadHistoryFromXHR(k,h).fail(function(o,m,n){f.trigger("error",f,o,h,_l("An error was encountered while "+m),{historyId:i,history:n||{}})}).always(function(){f.trigger("loading-done",f)})},_loadHistoryFromXHR:function(h,g){var f=this;h.then(function(i,j){f.setModel(i,j,g)});h.fail(function(j,i){f.render()});return h},setModel:function(h,f,g){g=g||{};if(this.model){this.model.clearUpdateTimeout();this.stopListening(this.model);this.stopListening(this.model.hdas)}this.hdaViews={};if(Galaxy&&Galaxy.currUser){h.user=Galaxy.currUser.toJSON()}this.model=new e.History(h,f,g);if(this.logger){this.model.logger=this.logger}this._setUpWebStorage(g.initiallyExpanded,g.show_deleted,g.show_hidden);this._setUpModelEventHandlers();this.selectedHdaIds=[];this.trigger("new-model",this);this.render();return this},_setUpWebStorage:function(g,f,h){this.storage=new d({id:d.historyStorageKey(this.model.get("id"))});if(_.isObject(g)){this.storage.set("exandedHdas",g)}if(_.isBoolean(f)){this.storage.set("show_deleted",f)}if(_.isBoolean(h)){this.storage.set("show_hidden",h)}this.trigger("new-storage",this.storage,this);this.log(this+" (init'd) storage:",this.storage.get())},clearWebStorage:function(){for(var f in sessionStorage){if(f.indexOf("history:")===0){sessionStorage.removeItem(f)}}},getStoredOptions:function(g){if(!g||g==="current"){return(this.storage)?(this.storage.get()):({})}var f=sessionStorage.getItem(d.historyStorageKey(g));return(f===null)?({}):(JSON.parse(f))},getExpandedHdaIds:function(f){var g=this.getStoredOptions(f).expandedHdas;return((_.isEmpty(g))?([]):(_.keys(g)))},_setUpModelEventHandlers:function(){this.model.hdas.on("add",this.addHdaView,this);this.model.on("error error:hdas",function(g,i,f,h){this.errorHandler(g,i,f,h)},this)},render:function(h,i){h=(h===undefined)?(this.fxSpeed):(h);var f=this,g;if(this.model){g=this.renderModel()}else{g=this.renderWithoutModel()}$(f).queue("fx",[function(j){if(h&&f.$el.is(":visible")){f.$el.fadeOut(h,j)}else{j()}},function(j){f.$el.empty();if(g){f.$el.append(g.children())}j()},function(j){if(h&&!f.$el.is(":visible")){f.$el.fadeIn(h,j)}else{j()}},function(j){if(i){i.call(this)}f.trigger("rendered",this);j()}]);return this},renderWithoutModel:function(){var f=$("<div/>"),g=$("<div/>").addClass("message-container").css({"margin-left":"4px","margin-right":"4px"});return f.append(g)},renderModel:function(){var f=$("<div/>");f.append(b.templates.historyPanel(this.model.toJSON()));f.find(".history-secondary-actions").prepend(this._renderSearchButton());this._setUpBehaviours(f);this.renderHdas(f);return f},_renderSearchButton:function(f){return faIconButton({title:_l("Search datasets"),classes:"history-search-btn",faIcon:"fa-search"})},_setUpBehaviours:function(f){f=f||this.$el;f.find("[title]").tooltip({placement:"bottom"})},createHdaView:function(g){var f=g.get("id"),h=new this.HDAViewClass({model:g,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[f],hasUser:this.model.ownedByCurrUser(),logger:this.logger});this._setUpHdaListeners(h);return h},_setUpHdaListeners:function(g){var f=this;g.on("body-expanded",function(h){f.storage.addExpandedHda(h)});g.on("body-collapsed",function(h){f.storage.removeExpandedHda(h)});g.on("error",function(i,k,h,j){f.errorHandler(i,k,h,j)})},renderHdas:function(g){g=g||this.$el;var f=this,i={},h=this.model.hdas.getVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"),this.filters);g.find(this.datasetsSelector).empty();if(h.length){h.each(function(k){var j=k.get("id"),l=f.createHdaView(k);i[j]=l;if(_.contains(f.selectedHdaIds,j)){l.selected=true}f.attachHdaView(l.render(),g)});g.find(this.emptyMsgSelector).hide()}else{g.find(this.emptyMsgSelector).text((this.model.hdas.length&&this.searchFor)?(this.noneFoundMsg):(this.emptyMsg)).show()}this.hdaViews=i;return this.hdaViews},attachHdaView:function(h,g){g=g||this.$el;var f=g.find(this.datasetsSelector);f.prepend(h.$el)},addHdaView:function(i){this.log("add."+this,i);var g=this;if(!i.isVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"))){return}$({}).queue([function h(k){var j=g.$el.find(g.emptyMsgSelector);if(j.is(":visible")){j.fadeOut(g.fxSpeed,k)}else{k()}},function f(k){g.scrollToTop();var j=g.$el.find(g.datasetsSelector),l=g.createHdaView(i);g.hdaViews[i.id]=l;l.render().$el.hide().prependTo(j).slideDown(g.fxSpeed)}])},refreshHdas:function(g,f){if(this.model){return this.model.refresh(g,f)}return $.when()},events:{"click .message-container":"clearMessages","click .history-search-btn":"toggleSearchControls"},collapseAllHdaBodies:function(){_.each(this.hdaViews,function(f){f.toggleBodyVisibility(null,false)});this.storage.set("expandedHdas",{})},toggleShowDeleted:function(f){f=(f!==undefined)?(f):(!this.storage.get("show_deleted"));this.storage.set("show_deleted",f);this.renderHdas();return this.storage.get("show_deleted")},toggleShowHidden:function(f){f=(f!==undefined)?(f):(!this.storage.get("show_hidden"));this.storage.set("show_hidden",f);this.renderHdas();return this.storage.get("show_hidden")},setUpSearchInput:function(g){var h=this,i=".history-search-input";function f(j){if(h.model.hdas.haveDetails()){h.searchHdas(j);return}h.$el.find(i).searchInput("toggle-loading");h.model.hdas.fetchAllDetails({silent:true}).always(function(){h.$el.find(i).searchInput("toggle-loading")}).done(function(){h.searchHdas(j)})}g.searchInput({initialVal:h.searchFor,name:"history-search",placeholder:"search datasets",classes:"history-search",onfirstsearch:f,onsearch:_.bind(this.searchHdas,this),onclear:_.bind(this.clearHdaSearch,this)});return g},showSearchControls:function(g){g=(g===undefined)?(this.fxSpeed):(g);var f=this.$el.find(".history-search-controls"),h=f.find(".history-search-input");if(!h.children().size()){this.setUpSearchInput(h)}f.slideDown(g,function(){$(this).find("input").focus()})},hideSearchControls:function(f){f=(f===undefined)?(this.fxSpeed):(f);this.$el.find(".history-search-controls").slideUp(f)},toggleSearchControls:function(f){speed=(jQuery.type(f)==="number")?(f):(this.fxSpeed);if(this.$el.find(".history-search-controls").is(":visible")){this.hideSearchControls(speed)}else{this.showSearchControls(speed)}},searchHdas:function(f){var g=this;this.searchFor=f;this.filters=[function(h){return h.matchesAll(g.searchFor)}];this.trigger("search:searching",f,this);this.renderHdas()},clearHdaSearch:function(f){this.searchFor="";this.filters=[];this.trigger("search:clear",this);this.renderHdas()},showLoadingIndicator:function(g,f,h){f=(f!==undefined)?(f):(this.fxSpeed);if(!this.indicator){this.indicator=new LoadingIndicator(this.$el,this.$el.parent())}if(!this.$el.is(":visible")){this.indicator.show(0,h)}else{this.$el.fadeOut(f);this.indicator.show(g,f,h)}},hideLoadingIndicator:function(f,g){f=(f!==undefined)?(f):(this.fxSpeed);if(this.indicator){this.indicator.hide(f,g)}},displayMessage:function(k,l,j){var h=this;this.scrollToTop();var i=this.$el.find(this.msgsSelector),f=$("<div/>").addClass(k+"message").html(l);if(!_.isEmpty(j)){var g=$('<a href="javascript:void(0)">Details</a>').click(function(){Galaxy.modal.show(h._messageToModalOptions(k,l,j));return false});f.append(" ",g)}return i.html(f)},_messageToModalOptions:function(j,l,i){var f=this,k=$("<div/>"),h={title:"Details"};function g(m){m=_.omit(m,_.functions(m));return["<table>",_.map(m,function(o,n){o=(_.isObject(o))?(g(o)):(o);return'<tr><td style="vertical-align: top; color: grey">'+n+'</td><td style="padding-left: 8px">'+o+"</td></tr>"}).join(""),"</table>"].join("")}if(_.isObject(i)){h.body=k.append(g(i))}else{h.body=k.html(i)}h.buttons={Ok:function(){Galaxy.modal.hide();f.clearMessages()}};return h},clearMessages:function(){var f=this.$el.find(this.msgsSelector);f.empty()},scrollPosition:function(){return this.$el.scrollTop()},scrollTo:function(f){this.$el.scrollTop(f);return this},scrollToTop:function(){this.$el.scrollTop(0);return this},scrollIntoView:function(k,g){if(g===undefined){this.scrollTo(k);return this}var f=window,j=this.$el.parent(),i=$(f).innerHeight(),h=(i/2)-(g/2);this.scrollTo(k-h);return this},scrollToId:function(g){if((!g)||(!this.hdaViews[g])){return this}var f=this.hdaViews[g].$el;this.scrollIntoView(f.offset().top,f.outerHeight());return this},scrollToHid:function(f){var g=this.model.hdas.getByHid(f);if(!g){return this}return this.scrollToId(g.id)},toString:function(){return"ReadOnlyHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});b.templates={historyPanel:Handlebars.templates["template-history-historyPanel"]};return{ReadOnlyHistoryPanel:b}});
\ No newline at end of file
+define(["mvc/history/history-model","mvc/dataset/hda-base"],function(g,a){var f=SessionStorageModel.extend({defaults:{expandedHdas:{},show_deleted:false,show_hidden:false},addExpandedHda:function(i){var h="expandedHdas";this.save(h,_.extend(this.get(h),_.object([i],[true])))},removeExpandedHda:function(i){var h="expandedHdas";this.save(h,_.omit(this.get(h),i))},toString:function(){return"HistoryPrefs("+this.id+")"}});f.storageKeyPrefix="history:";f.historyStorageKey=function e(h){if(!h){throw new Error("HistoryPrefs.historyStorageKey needs valid id: "+h)}return(f.storageKeyPrefix+h)};f.get=function d(h){return new f({id:f.historyStorageKey(h)})};f.clearAll=function c(i){for(var h in sessionStorage){if(h.indexOf(f.storageKeyPrefix)===0){sessionStorage.removeItem(h)}}};var b=Backbone.View.extend(LoggableMixin).extend({HDAViewClass:a.HDABaseView,tagName:"div",className:"history-panel",fxSpeed:"fast",emptyMsg:_l("This history is empty"),noneFoundMsg:_l("No matching datasets found"),initialize:function(h){h=h||{};if(h.logger){this.logger=h.logger}this.log(this+".initialize:",h);this.linkTarget=h.linkTarget||"_blank";this.fxSpeed=_.has(h,"fxSpeed")?(h.fxSpeed):(this.fxSpeed);this.filters=[];this.searchFor="";this.findContainerFn=h.findContainerFn;this.hdaViews={};this.indicator=new LoadingIndicator(this.$el);this._setUpListeners();var i=_.pick(h,"initiallyExpanded","show_deleted","show_hidden");this.setModel(this.model,i,false);if(h.onready){h.onready.call(this)}},_setUpListeners:function(){this.on("error",function(i,l,h,k,j){this.errorHandler(i,l,h,k,j)});this.on("loading-history",function(){this._showLoadingIndicator("loading history...",40)});this.on("loading-done",function(){this._hideLoadingIndicator(40);if(_.isEmpty(this.hdaViews)){this.trigger("empty-history",this)}});this.once("rendered",function(){this.trigger("rendered:initial",this);return false});if(this.logger){this.on("all",function(h){this.log(this+"",arguments)},this)}return this},errorHandler:function(j,m,i,l,k){console.error(j,m,i,l,k);if(m&&m.status===0&&m.readyState===0){}else{if(m&&m.status===502){}else{var h=this._parseErrorMessage(j,m,i,l,k);if(!this.$messages().is(":visible")){this.once("rendered",function(){this.displayMessage("error",h.message,h.details)})}else{this.displayMessage("error",h.message,h.details)}}}},_parseErrorMessage:function(k,o,j,n,m){var i=Galaxy.currUser,h={message:this._bePolite(n),details:{user:(i instanceof User)?(i.toJSON()):(i+""),source:(k instanceof Backbone.Model)?(k.toJSON()):(k+""),xhr:o,options:(o)?(_.omit(j,"xhr")):(j)}};_.extend(h.details,m||{});if(o&&_.isFunction(o.getAllResponseHeaders)){var l=o.getAllResponseHeaders();l=_.compact(l.split("\n"));l=_.map(l,function(p){return p.split(": ")});h.details.xhr.responseHeaders=_.object(l)}return h},_bePolite:function(h){h=h||_l("An error occurred while getting updates from the server");return h+". "+_l("Please contact a Galaxy administrator if the problem persists.")},loadHistoryWithHDADetails:function(j,i,h,l){var k=function(m){return _.keys(f.get(m.id).get("expandedHdas"))};return this.loadHistory(j,i,h,l,k)},loadHistory:function(k,j,i,n,l){var h=this;j=j||{};h.trigger("loading-history",h);var m=g.History.getHistoryData(k,{historyFn:i,hdaFn:n,hdaDetailIds:j.initiallyExpanded||l});return h._loadHistoryFromXHR(m,j).fail(function(q,o,p){h.trigger("error",h,q,j,_l("An error was encountered while "+o),{historyId:k,history:p||{}})}).always(function(){h.trigger("loading-done",h)})},_loadHistoryFromXHR:function(j,i){var h=this;j.then(function(k,l){h.JSONToModel(k,l,i)});j.fail(function(l,k){h.render()});return j},JSONToModel:function(k,h,i){this.log("JSONToModel:",k,h,i);i=i||{};if(Galaxy&&Galaxy.currUser){k.user=Galaxy.currUser.toJSON()}var j=new g.History(k,h,i);this.setModel(j);return this},setModel:function(i,h,j){h=h||{};j=(j!==undefined)?(j):(true);this.log("setModel:",i,h,j);this.freeModel();this.selectedHdaIds=[];if(i){if(Galaxy&&Galaxy.currUser){i.user=Galaxy.currUser.toJSON()}this.model=i;if(this.logger){this.model.logger=this.logger}this._setUpWebStorage(h.initiallyExpanded,h.show_deleted,h.show_hidden);this._setUpModelEventHandlers();this.trigger("new-model",this)}if(j){this.render()}return this},freeModel:function(){if(this.model){this.model.clearUpdateTimeout();this.stopListening(this.model);this.stopListening(this.model.hdas)}this.freeHdaViews();return this},freeHdaViews:function(){this.hdaViews={};return this},_setUpWebStorage:function(i,h,j){this.storage=new f({id:f.historyStorageKey(this.model.get("id"))});if(_.isObject(i)){this.storage.set("exandedHdas",i)}if(_.isBoolean(h)){this.storage.set("show_deleted",h)}if(_.isBoolean(j)){this.storage.set("show_hidden",j)}this.trigger("new-storage",this.storage,this);this.log(this+" (init'd) storage:",this.storage.get());return this},_setUpModelEventHandlers:function(){this.model.hdas.on("add",this.addHdaView,this);this.model.on("error error:hdas",function(i,k,h,j){this.errorHandler(i,k,h,j)},this);return this},render:function(j,k){this.log("render:",j,k);j=(j===undefined)?(this.fxSpeed):(j);var h=this,i;if(this.model){i=this.renderModel()}else{i=this.renderWithoutModel()}$(h).queue("fx",[function(l){if(j&&h.$el.is(":visible")){h.$el.fadeOut(j,l)}else{l()}},function(l){h.$el.empty();if(i){h.$el.append(i.children())}l()},function(l){if(j&&!h.$el.is(":visible")){h.$el.fadeIn(j,l)}else{l()}},function(l){if(k){k.call(this)}h.trigger("rendered",this);l()}]);return this},renderWithoutModel:function(){var h=$("<div/>"),i=$("<div/>").addClass("message-container").css({"margin-left":"4px","margin-right":"4px"});return h.append(i)},renderModel:function(){var h=$("<div/>");h.append(b.templates.historyPanel(this.model.toJSON()));h.find(".history-secondary-actions").prepend(this._renderSearchButton());this._setUpBehaviours(h);this.renderHdas(h);return h},_renderSearchButton:function(h){return faIconButton({title:_l("Search datasets"),classes:"history-search-btn",faIcon:"fa-search"})},_setUpBehaviours:function(h){h=h||this.$el;h.find("[title]").tooltip({placement:"bottom"});this._setUpSearchInput(h.find(".history-search-controls .history-search-input"));return this},$container:function(){return(this.findContainerFn)?(this.findContainerFn.call(this)):(this.$el.parent())},$datasetsList:function(h){return(h||this.$el).find(".datasets-list")},$messages:function(h){return(h||this.$el).find(".message-container")},$emptyMessage:function(h){return(h||this.$el).find(".empty-history-message")},renderHdas:function(i){i=i||this.$el;var h=this,k={},j=this.model.hdas.getVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"),this.filters);this.$datasetsList(i).empty();if(j.length){j.each(function(m){var l=m.get("id"),n=h._createHdaView(m);k[l]=n;if(_.contains(h.selectedHdaIds,l)){n.selected=true}h.attachHdaView(n.render(),i)});h.$emptyMessage(i).hide()}else{h.$emptyMessage(i).text((this.model.hdas.length&&this.searchFor)?(this.noneFoundMsg):(this.emptyMsg)).show()}this.hdaViews=k;return this.hdaViews},_createHdaView:function(i){var h=i.get("id"),j=new this.HDAViewClass({model:i,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[h],hasUser:this.model.ownedByCurrUser(),logger:this.logger});this._setUpHdaListeners(j);return j},_setUpHdaListeners:function(i){var h=this;i.on("error",function(k,m,j,l){h.errorHandler(k,m,j,l)});i.on("body-expanded",function(j){h.storage.addExpandedHda(j)});i.on("body-collapsed",function(j){h.storage.removeExpandedHda(j)});return this},attachHdaView:function(j,i){i=i||this.$el;var h=this.$datasetsList(i);h.prepend(j.$el);return this},addHdaView:function(k){this.log("add."+this,k);var i=this;if(!k.isVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"))){return i}$({}).queue([function j(m){var l=i.$emptyMessage();if(l.is(":visible")){l.fadeOut(i.fxSpeed,m)}else{m()}},function h(l){var m=i._createHdaView(k);i.hdaViews[k.id]=m;m.render().$el.hide();i.scrollToTop();i.attachHdaView(m);m.$el.slideDown(i.fxSpeed)}]);return i},refreshHdas:function(i,h){if(this.model){return this.model.refresh(i,h)}return $.when()},events:{"click .message-container":"clearMessages","click .history-search-btn":"toggleSearchControls"},collapseAllHdaBodies:function(){_.each(this.hdaViews,function(h){h.toggleBodyVisibility(null,false)});this.storage.set("expandedHdas",{});return this},toggleShowDeleted:function(h){h=(h!==undefined)?(h):(!this.storage.get("show_deleted"));this.storage.set("show_deleted",h);this.renderHdas();return this.storage.get("show_deleted")},toggleShowHidden:function(h){h=(h!==undefined)?(h):(!this.storage.get("show_hidden"));this.storage.set("show_hidden",h);this.renderHdas();return this.storage.get("show_hidden")},_setUpSearchInput:function(i){var j=this,k=".history-search-input";function h(l){if(j.model.hdas.haveDetails()){j.searchHdas(l);return}j.$el.find(k).searchInput("toggle-loading");j.model.hdas.fetchAllDetails({silent:true}).always(function(){j.$el.find(k).searchInput("toggle-loading")}).done(function(){j.searchHdas(l)})}i.searchInput({initialVal:j.searchFor,name:"history-search",placeholder:"search datasets",classes:"history-search",onfirstsearch:h,onsearch:_.bind(this.searchHdas,this),onclear:_.bind(this.clearHdaSearch,this)});return i},toggleSearchControls:function(j,h){var i=this.$el.find(".history-search-controls"),k=(jQuery.type(j)==="number")?(j):(this.fxSpeed);h=(h!==undefined)?(h):(!i.is(":visible"));if(h){i.slideDown(k,function(){$(this).find("input").focus()})}else{i.slideUp(k)}return h},searchHdas:function(h){var i=this;this.searchFor=h;this.filters=[function(j){return j.matchesAll(i.searchFor)}];this.trigger("search:searching",h,this);this.renderHdas();return this},clearHdaSearch:function(h){this.searchFor="";this.filters=[];this.trigger("search:clear",this);this.renderHdas();return this},_showLoadingIndicator:function(i,h,j){h=(h!==undefined)?(h):(this.fxSpeed);if(!this.indicator){this.indicator=new LoadingIndicator(this.$el,this.$el.parent())}if(!this.$el.is(":visible")){this.indicator.show(0,j)}else{this.$el.fadeOut(h);this.indicator.show(i,h,j)}},_hideLoadingIndicator:function(h,i){h=(h!==undefined)?(h):(this.fxSpeed);if(this.indicator){this.indicator.hide(h,i)}},displayMessage:function(m,n,l){var j=this;this.scrollToTop();var k=this.$messages(),h=$("<div/>").addClass(m+"message").html(n);if(!_.isEmpty(l)){var i=$('<a href="javascript:void(0)">Details</a>').click(function(){Galaxy.modal.show(j._messageToModalOptions(m,n,l));return false});h.append(" ",i)}return k.html(h)},_messageToModalOptions:function(l,n,k){var h=this,m=$("<div/>"),j={title:"Details"};function i(o){o=_.omit(o,_.functions(o));return["<table>",_.map(o,function(q,p){q=(_.isObject(q))?(i(q)):(q);return'<tr><td style="vertical-align: top; color: grey">'+p+'</td><td style="padding-left: 8px">'+q+"</td></tr>"}).join(""),"</table>"].join("")}if(_.isObject(k)){j.body=m.append(i(k))}else{j.body=m.html(k)}j.buttons={Ok:function(){Galaxy.modal.hide();h.clearMessages()}};return j},clearMessages:function(){this.$messages().empty();return this},scrollPosition:function(){return this.$container().scrollTop()},scrollTo:function(h){this.$container().scrollTop(h);return this},scrollToTop:function(){this.$container().scrollTop(0);return this},scrollToId:function(i){if((!i)||(!this.hdaViews[i])){return this}var h=this.hdaViews[i];this.scrollTo(h.el.offsetTop);return this},scrollToHid:function(h){var i=this.model.hdas.getByHid(h);if(!i){return this}return this.scrollToId(i.id)},toString:function(){return"ReadOnlyHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});b.templates={historyPanel:Handlebars.templates["template-history-historyPanel"]};return{ReadOnlyHistoryPanel:b}});
\ No newline at end of file
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/style/blue/base.css
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -1602,7 +1602,11 @@
.workflow-invocation-complete{border:solid 1px #6A6;border-left-width:5px;margin:10px 0;padding-left:5px}
.icon-btn{display:inline-block;height:22px;width:22px;text-align:center;line-height:19px;font-size:1.2em;border-radius:3px;border:1px solid #bfbfbf;background-color:#f2f2f2;color:#333;cursor:pointer}
.icon-btn:hover{background-color:white;color:maroon}
-.icon-btn.disabled{background-color:transparent;color:#bfbfbf}
+.icon-btn.disabled{background-color:transparent;color:#BBB;border-color:#BBB}
+.icon-btn-group{display:inline-block}.icon-btn-group .icon-btn:not(:last-child){margin:0px;border-radius:0px;border-right:none}
+.icon-btn-group .icon-btn:first-child{margin-right:0px;border-top-left-radius:3px;border-bottom-left-radius:3px}
+.icon-btn-group .icon-btn:last-child{margin-left:0px;border-radius:0px 3px 3px 0px}
+.icon-btn-group .icon-btn:only-child{margin:0px;border-radius:3px}
.search-input .search-query{width:100%;padding-right:24px}
.search-input .search-clear,.search-input .search-loading{position:relative;display:inline-block;float:right;margin-top:-23px;margin-right:4px;font-size:1.4em;line-height:23px;color:grey}
.search-input .search-clear:hover{color:#303030}
@@ -1618,6 +1622,7 @@
.history-panel .dataset .dataset-primary-actions{display:inline-block;float:right;margin:6px 10px 0}.history-panel .dataset .dataset-primary-actions .icon-btn:not(:last-child){margin:0px;border-radius:0px;border-right:none}
.history-panel .dataset .dataset-primary-actions .icon-btn:first-child{margin-right:0px;border-top-left-radius:3px;border-bottom-left-radius:3px}
.history-panel .dataset .dataset-primary-actions .icon-btn:last-child{margin-left:0px;border-radius:0px 3px 3px 0px}
+.history-panel .dataset .dataset-primary-actions .icon-btn:only-child{margin:0px;border-radius:3px}
.history-panel .dataset .dataset-primary-actions .icon-btn{margin-left:2px}
.history-panel .dataset .dataset-body{display:none;padding:0 10px 6px 8px}.history-panel .dataset .dataset-body [class$=messagesmall]{margin:0px 0px 8px 0px}
.history-panel .dataset .dataset-body label{margin:0px;padding:0px;font-weight:normal}
@@ -1632,9 +1637,14 @@
.history-panel .dataset .dataset-body .dataset-actions .left{display:inline-block;float:left}.history-panel .dataset .dataset-body .dataset-actions .left .icon-btn:not(:last-child){margin:0px;border-radius:0px;border-right:none}
.history-panel .dataset .dataset-body .dataset-actions .left .icon-btn:first-child{margin-right:0px;border-top-left-radius:3px;border-bottom-left-radius:3px}
.history-panel .dataset .dataset-body .dataset-actions .left .icon-btn:last-child{margin-left:0px;border-radius:0px 3px 3px 0px}
+.history-panel .dataset .dataset-body .dataset-actions .left .icon-btn:only-child{margin:0px;border-radius:3px}
.history-panel .dataset .dataset-body .dataset-actions .left .icon-btn{margin-right:2px}
.history-panel .dataset .dataset-body .dataset-actions .left:not(:empty){margin-bottom:8px}
-.history-panel .dataset .dataset-body .dataset-actions .right{float:right}.history-panel .dataset .dataset-body .dataset-actions .right .icon-btn{margin-left:2px}
+.history-panel .dataset .dataset-body .dataset-actions .right{display:inline-block;float:right}.history-panel .dataset .dataset-body .dataset-actions .right .icon-btn:not(:last-child){margin:0px;border-radius:0px;border-right:none}
+.history-panel .dataset .dataset-body .dataset-actions .right .icon-btn:first-child{margin-right:0px;border-top-left-radius:3px;border-bottom-left-radius:3px}
+.history-panel .dataset .dataset-body .dataset-actions .right .icon-btn:last-child{margin-left:0px;border-radius:0px 3px 3px 0px}
+.history-panel .dataset .dataset-body .dataset-actions .right .icon-btn:only-child{margin:0px;border-radius:3px}
+.history-panel .dataset .dataset-body .dataset-actions .right .icon-btn{margin-left:2px}
.history-panel .dataset .dataset-body .tags-display{display:none;margin-bottom:8px}.history-panel .dataset .dataset-body .tags-display .select2-container{min-width:0px}.history-panel .dataset .dataset-body .tags-display .select2-container .select2-choices{border-radius:3px}
.history-panel .dataset .dataset-body .annotation-display{display:none;margin-bottom:8px}.history-panel .dataset .dataset-body .annotation-display .annotation{border-radius:3px;border:1px solid rgba(153,153,153,0.30000000000000004);padding:4px;white-space:pre-line}
.history-panel .dataset .dataset-body .annotation-display .annotation:empty{height:22px}
@@ -1643,9 +1653,6 @@
.history-panel .dataset .dataset-body .dataset-display-applications .display-application:last-child{margin-bottom:8px}
.history-panel .dataset .dataset-body .dataset-peek{margin-bottom:8px}.history-panel .dataset .dataset-body .dataset-peek pre.peek{width:100%;margin:0px;border-radius:3px;background:white;color:black;font-size:10px;overflow:auto}.history-panel .dataset .dataset-body .dataset-peek pre.peek th{color:white;background:#5f6990}
.history-panel .dataset .dataset-body .dataset-peek pre.peek table,.history-panel .dataset .dataset-body .dataset-peek pre.peek th,.history-panel .dataset .dataset-body .dataset-peek pre.peek tr,.history-panel .dataset .dataset-body .dataset-peek pre.peek td{font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:10px}
-.history-panel .dataset .icon-btn-group{display:inline-block}.history-panel .dataset .icon-btn-group .icon-btn:not(:last-child){margin:0px;border-radius:0px;border-right:none}
-.history-panel .dataset .icon-btn-group .icon-btn:first-child{margin-right:0px;border-top-left-radius:3px;border-bottom-left-radius:3px}
-.history-panel .dataset .icon-btn-group .icon-btn:last-child{margin-left:0px;border-radius:0px 3px 3px 0px}
.history-panel .dataset .state-icon{font-family:FontAwesome;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-block;vertical-align:middle;width:16px;height:16px;line-height:16px;text-align:center;font-size:16px}
.state-icon-error{background-color:white;border-radius:8px}.state-icon-error:before{font-size:20px;line-height:16px;color:red;content:"\f057"}
.history-panel [class$="messagesmall"]{margin:0px}
@@ -1659,7 +1666,11 @@
.history-panel .history-controls .history-title input{width:100%;margin:-2px 0 -2px -4px;font-weight:bold}
.history-panel .history-controls .history-subtitle{margin-bottom:8px}
.history-panel .history-controls .history-size{float:left}
-.history-panel .history-controls .history-secondary-actions{float:right}.history-panel .history-controls .history-secondary-actions .icon-btn{margin-left:2px}
+.history-panel .history-controls .history-secondary-actions{display:inline-block;float:right}.history-panel .history-controls .history-secondary-actions .icon-btn:not(:last-child){margin:0px;border-radius:0px;border-right:none}
+.history-panel .history-controls .history-secondary-actions .icon-btn:first-child{margin-right:0px;border-top-left-radius:3px;border-bottom-left-radius:3px}
+.history-panel .history-controls .history-secondary-actions .icon-btn:last-child{margin-left:0px;border-radius:0px 3px 3px 0px}
+.history-panel .history-controls .history-secondary-actions .icon-btn:only-child{margin:0px;border-radius:3px}
+.history-panel .history-controls .history-secondary-actions .icon-btn{margin-left:2px}
.history-panel .history-controls .quota-message{display:none;margin:8px 0px 5px 0px}
.history-panel .history-controls .tags-display,.history-panel .history-controls .annotation-display{display:none;margin-bottom:8px}.history-panel .history-controls .tags-display label,.history-panel .history-controls .annotation-display label{display:block;margin:0px;padding:0px;font-weight:normal;color:#555}
.history-panel .history-controls .tags-display label:after,.history-panel .history-controls .annotation-display label:after{content:':'}
This diff is so big that we needed to truncate the remainder.
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: martenson: data libraries: final touch for the library API controller
by commits-noreply@bitbucket.org 14 Mar '14
by commits-noreply@bitbucket.org 14 Mar '14
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/1ed94d89769e/
Changeset: 1ed94d89769e
User: martenson
Date: 2014-03-14 16:43:43
Summary: data libraries: final touch for the library API controller
Affected #: 1 file
diff -r 3fb927653301a0c06a0bf94f2b6bd71b3595ec0d -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce lib/galaxy/webapps/galaxy/api/libraries.py
--- a/lib/galaxy/webapps/galaxy/api/libraries.py
+++ b/lib/galaxy/webapps/galaxy/api/libraries.py
@@ -12,7 +12,6 @@
import logging
log = logging.getLogger( __name__ )
-
class LibrariesController( BaseAPIController ):
@expose_api_anonymous
@@ -29,6 +28,7 @@
:rtype: list
.. seealso:: :attr:`galaxy.model.Library.dict_collection_visible_keys`
+
"""
query = trans.sa_session.query( trans.app.model.Library )
deleted = kwd.get( 'deleted', 'missing' )
@@ -54,7 +54,7 @@
trans.model.Library.table.c.id.in_( accessible_restricted_library_ids ) ) )
libraries = []
for library in query:
- item = self._prepend_folder_prefix( library.to_dict( view='element',
+ item = self.prepend_folder_prefix( library.to_dict( view='element',
value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) )
libraries.append( item )
return libraries
@@ -77,6 +77,8 @@
:rtype: dictionary
.. seealso:: :attr:`galaxy.model.Library.dict_element_visible_keys`
+
+ :raises: MalformedId, ObjectNotFound
"""
library_id = id
deleted = util.string_as_bool( deleted )
@@ -91,7 +93,7 @@
library = None
if not library or not ( trans.user_is_admin() or trans.app.security_agent.can_access_library( trans.get_current_user_roles(), library ) ):
raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id )
- return self._prepend_folder_prefix( library.to_dict( view='element',
+ return self.prepend_folder_prefix( library.to_dict( view='element',
value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) )
@expose_api
@@ -111,6 +113,8 @@
:returns: detailed library information
:rtype: dict
+
+ :raises: ItemAccessibilityException, RequestParameterMissingException
"""
if not trans.user_is_admin():
raise exceptions.ItemAccessibilityException( 'Only administrators can create libraries.' )
@@ -127,7 +131,7 @@
library.root_folder = root_folder
trans.sa_session.add_all( ( library, root_folder ) )
trans.sa_session.flush()
- return self._prepend_folder_prefix( library.to_dict( view='element',
+ return self.prepend_folder_prefix( library.to_dict( view='element',
value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) )
@expose_api
@@ -146,6 +150,8 @@
:returns: detailed library information
:rtype: dict
+
+ :raises: ItemAccessibilityException, MalformedId, ObjectNotFound, RequestParameterInvalidException, RequestParameterMissingException
"""
if not trans.user_is_admin():
raise exceptions.ItemAccessibilityException( 'Only administrators can update libraries.' )
@@ -177,7 +183,7 @@
raise exceptions.RequestParameterMissingException( "You did not specify any payload." )
trans.sa_session.add( library )
trans.sa_session.flush()
- return self._prepend_folder_prefix( library.to_dict( view='element',
+ return self.prepend_folder_prefix( library.to_dict( view='element',
value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) )
@expose_api
@@ -192,13 +198,15 @@
:param id: the encoded id of the library to un/delete
:type id: str
- :param undelete: flag specifying whether the item should be deleted or undeleted, defaults to false:
+ :param undelete: (optional) flag specifying whether the item should be deleted or undeleted, defaults to false:
:type undelete: bool
:returns: detailed library information
:rtype: dictionary
.. seealso:: :attr:`galaxy.model.Library.dict_element_visible_keys`
+
+ :raises: ItemAccessibilityException, MalformedId, ObjectNotFound
"""
undelete = util.string_as_bool( kwd.get( 'undelete', False ) )
if not trans.user_is_admin():
@@ -221,12 +229,21 @@
trans.sa_session.add( library )
trans.sa_session.flush()
- return self._prepend_folder_prefix( library.to_dict( view='element',
+ return self.prepend_folder_prefix( library.to_dict( view='element',
value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) )
- def _prepend_folder_prefix (self, dictionary, type='library' ):
+ def prepend_folder_prefix (self, dictionary, type='library' ):
"""
- In Galaxy folders have 'F' as a prefix to the encoded id to distinguish between folders and libraries
+ prepend_folder_prefix (self, dictionary, type='library' )
+ In Galaxy folders have an 'F' as a prefix to the encoded id to distinguish between folders and libraries
+
+ :param dictionary: a supported object after to_dict containing _encoded_ ids
+ :type dictionary: dictionary
+
+ :param type: string representing the type of dictionary that is passed in, defaults to 'library'
+ :type type: string
+
+ :raises: TypeError, ValueError
"""
if not ( type in [ 'library', 'folder' ] ):
raise TypeError( 'Prepending is not implemented for given type of dictionary.' )
@@ -240,5 +257,5 @@
if return_dict[ 'id' ]:
return_dict[ 'id' ] = 'F' + return_dict[ 'id' ]
else:
- raise ValueError( 'Given folder does not contain id to prepend to.' )
+ raise ValueError( 'Given folder does not contain an id to prepend to.' )
return return_dict
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: jmchilton: Add documentation for using GALAXY_SLOTS with local runner.
by commits-noreply@bitbucket.org 14 Mar '14
by commits-noreply@bitbucket.org 14 Mar '14
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/3fb927653301/
Changeset: 3fb927653301
User: jmchilton
Date: 2014-03-14 14:42:10
Summary: Add documentation for using GALAXY_SLOTS with local runner.
Affected #: 1 file
diff -r e22d4f2b57a8c99cf5b48d36e0ca440934eaa569 -r 3fb927653301a0c06a0bf94f2b6bd71b3595ec0d job_conf.xml.sample_advanced
--- a/job_conf.xml.sample_advanced
+++ b/job_conf.xml.sample_advanced
@@ -57,6 +57,12 @@
should be executed on those remote resources.
--><destination id="local" runner="local"/>
+ <destination id="multicore_local" runner="local">
+ <param id="local_slots">4</param><!-- Specify GALAXY_SLOTS for local jobs. -->
+ <!-- Warning: Local slot count doesn't tie up additional worker threads, to prevent over
+ allocating machine define a second local runner with different name and fewer workers
+ to run this destination. -->
+ </destination><destination id="pbs" runner="pbs" tags="mycluster"/><destination id="pbs_longjobs" runner="pbs" tags="mycluster,longjobs"><!-- Define parameters that are native to the job runner plugin. -->
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: guerler: Charts: Disable box plots
by commits-noreply@bitbucket.org 14 Mar '14
by commits-noreply@bitbucket.org 14 Mar '14
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/e22d4f2b57a8/
Changeset: e22d4f2b57a8
User: guerler
Date: 2014-03-14 05:06:13
Summary: Charts: Disable box plots
Affected #: 3 files
diff -r 97063e8da923eac2f9abc0bf256f4a7c98b0acec -r e22d4f2b57a8c99cf5b48d36e0ca440934eaa569 config/plugins/visualizations/charts/static/build-app.js
--- a/config/plugins/visualizations/charts/static/build-app.js
+++ b/config/plugins/visualizations/charts/static/build-app.js
@@ -3,4 +3,4 @@
// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore may be freely distributed under the MIT license.
-(function(){var e=this,t=e._,n={},r=Array.prototype,i=Object.prototype,s=Function.prototype,o=r.push,u=r.slice,a=r.concat,f=i.toString,l=i.hasOwnProperty,c=r.forEach,h=r.map,p=r.reduce,d=r.reduceRight,v=r.filter,m=r.every,g=r.some,y=r.indexOf,b=r.lastIndexOf,w=Array.isArray,E=Object.keys,S=s.bind,x=function(e){if(e instanceof x)return e;if(!(this instanceof x))return new x(e);this._wrapped=e};typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(exports=module.exports=x),exports._=x):e._=x,x.VERSION="1.4.4";var T=x.each=x.forEach=function(e,t,r){if(e==null)return;if(c&&e.forEach===c)e.forEach(t,r);else if(e.length===+e.length){for(var i=0,s=e.length;i<s;i++)if(t.call(r,e[i],i,e)===n)return}else for(var o in e)if(x.has(e,o)&&t.call(r,e[o],o,e)===n)return};x.map=x.collect=function(e,t,n){var r=[];return e==null?r:h&&e.map===h?e.map(t,n):(T(e,function(e,i,s){r.push(t.call(n,e,i,s))}),r)};var N="Reduce of empty array with no initial value";x.reduce=x.foldl=x.inject=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(p&&e.reduce===p)return r&&(t=x.bind(t,r)),i?e.reduce(t,n):e.reduce(t);T(e,function(e,s,o){i?n=t.call(r,n,e,s,o):(n=e,i=!0)});if(!i)throw new TypeError(N);return n},x.reduceRight=x.foldr=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(d&&e.reduceRight===d)return r&&(t=x.bind(t,r)),i?e.reduceRight(t,n):e.reduceRight(t);var s=e.length;if(s!==+s){var o=x.keys(e);s=o.length}T(e,function(u,a,f){a=o?o[--s]:--s,i?n=t.call(r,n,e[a],a,f):(n=e[a],i=!0)});if(!i)throw new TypeError(N);return n},x.find=x.detect=function(e,t,n){var r;return C(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},x.filter=x.select=function(e,t,n){var r=[];return e==null?r:v&&e.filter===v?e.filter(t,n):(T(e,function(e,i,s){t.call(n,e,i,s)&&r.push(e)}),r)},x.reject=function(e,t,n){return x.filter(e,function(e,r,i){return!t.call(n,e,r,i)},n)},x.every=x.all=function(e,t,r){t||(t=x.identity);var i=!0;return e==null?i:m&&e.every===m?e.every(t,r):(T(e,function(e,s,o){if(!(i=i&&t.call(r,e,s,o)))return n}),!!i)};var C=x.some=x.any=function(e,t,r){t||(t=x.identity);var i=!1;return e==null?i:g&&e.some===g?e.some(t,r):(T(e,function(e,s,o){if(i||(i=t.call(r,e,s,o)))return n}),!!i)};x.contains=x.include=function(e,t){return e==null?!1:y&&e.indexOf===y?e.indexOf(t)!=-1:C(e,function(e){return e===t})},x.invoke=function(e,t){var n=u.call(arguments,2),r=x.isFunction(t);return x.map(e,function(e){return(r?t:e[t]).apply(e,n)})},x.pluck=function(e,t){return x.map(e,function(e){return e[t]})},x.where=function(e,t,n){return x.isEmpty(t)?n?void 0:[]:x[n?"find":"filter"](e,function(e){for(var n in t)if(t[n]!==e[n])return!1;return!0})},x.findWhere=function(e,t){return x.where(e,t,!0)},x.max=function(e,t,n){if(!t&&x.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.max.apply(Math,e);if(!t&&x.isEmpty(e))return-Infinity;var r={computed:-Infinity,value:-Infinity};return T(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o>=r.computed&&(r={value:e,computed:o})}),r.value},x.min=function(e,t,n){if(!t&&x.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.min.apply(Math,e);if(!t&&x.isEmpty(e))return Infinity;var r={computed:Infinity,value:Infinity};return T(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o<r.computed&&(r={value:e,computed:o})}),r.value},x.shuffle=function(e){var t,n=0,r=[];return T(e,function(e){t=x.random(n++),r[n-1]=r[t],r[t]=e}),r};var k=function(e){return x.isFunction(e)?e:function(t){return t[e]}};x.sortBy=function(e,t,n){var r=k(t);return x.pluck(x.map(e,function(e,t,i){return{value:e,index:t,criteria:r.call(n,e,t,i)}}).sort(function(e,t){var n=e.criteria,r=t.criteria;if(n!==r){if(n>r||n===void 0)return 1;if(n<r||r===void 0)return-1}return e.index<t.index?-1:1}),"value")};var L=function(e,t,n,r){var i={},s=k(t==null?x.identity:t);return T(e,function(t,o){var u=s.call(n,t,o,e);r(i,u,t)}),i};x.groupBy=function(e,t,n){return L(e,t,n,function(e,t,n){(x.has(e,t)?e[t]:e[t]=[]).push(n)})},x.countBy=function(e,t,n){return L(e,t,n,function(e,t){x.has(e,t)||(e[t]=0),e[t]++})},x.sortedIndex=function(e,t,n,r){n=n==null?x.identity:k(n);var i=n.call(r,t),s=0,o=e.length;while(s<o){var u=s+o>>>1;n.call(r,e[u])<i?s=u+1:o=u}return s},x.toArray=function(e){return e?x.isArray(e)?u.call(e):e.length===+e.length?x.map(e,x.identity):x.values(e):[]},x.size=function(e){return e==null?0:e.length===+e.length?e.length:x.keys(e).length},x.first=x.head=x.take=function(e,t,n){return e==null?void 0:t!=null&&!n?u.call(e,0,t):e[0]},x.initial=function(e,t,n){return u.call(e,0,e.length-(t==null||n?1:t))},x.last=function(e,t,n){return e==null?void 0:t!=null&&!n?u.call(e,Math.max(e.length-t,0)):e[e.length-1]},x.rest=x.tail=x.drop=function(e,t,n){return u.call(e,t==null||n?1:t)},x.compact=function(e){return x.filter(e,x.identity)};var A=function(e,t,n){return T(e,function(e){x.isArray(e)?t?o.apply(n,e):A(e,t,n):n.push(e)}),n};x.flatten=function(e,t){return A(e,t,[])},x.without=function(e){return x.difference(e,u.call(arguments,1))},x.uniq=x.unique=function(e,t,n,r){x.isFunction(t)&&(r=n,n=t,t=!1);var i=n?x.map(e,n,r):e,s=[],o=[];return T(i,function(n,r){if(t?!r||o[o.length-1]!==n:!x.contains(o,n))o.push(n),s.push(e[r])}),s},x.union=function(){return x.uniq(a.apply(r,arguments))},x.intersection=function(e){var t=u.call(arguments,1);return x.filter(x.uniq(e),function(e){return x.every(t,function(t){return x.indexOf(t,e)>=0})})},x.difference=function(e){var t=a.apply(r,u.call(arguments,1));return x.filter(e,function(e){return!x.contains(t,e)})},x.zip=function(){var e=u.call(arguments),t=x.max(x.pluck(e,"length")),n=new Array(t);for(var r=0;r<t;r++)n[r]=x.pluck(e,""+r);return n},x.unzip=function(e){var t=[];return x.each(e,function(e,n){x.each(e,function(e,r){t.length<=r&&(t[r]=[]),t[r][n]=e})}),t},x.object=function(e,t){if(e==null)return{};var n={};for(var r=0,i=e.length;r<i;r++)t?n[e[r]]=t[r]:n[e[r][0]]=e[r][1];return n},x.indexOf=function(e,t,n){if(e==null)return-1;var r=0,i=e.length;if(n){if(typeof n!="number")return r=x.sortedIndex(e,t),e[r]===t?r:-1;r=n<0?Math.max(0,i+n):n}if(y&&e.indexOf===y)return e.indexOf(t,n);for(;r<i;r++)if(e[r]===t)return r;return-1},x.lastIndexOf=function(e,t,n){if(e==null)return-1;var r=n!=null;if(b&&e.lastIndexOf===b)return r?e.lastIndexOf(t,n):e.lastIndexOf(t);var i=r?n:e.length;while(i--)if(e[i]===t)return i;return-1},x.range=function(e,t,n){arguments.length<=1&&(t=e||0,e=0),n=arguments[2]||1;var r=Math.max(Math.ceil((t-e)/n),0),i=0,s=new Array(r);while(i<r)s[i++]=e,e+=n;return s};var O=function(){};x.bind=function(e,t){var n,r;if(e.bind===S&&S)return S.apply(e,u.call(arguments,1));if(!x.isFunction(e))throw new TypeError;return n=u.call(arguments,2),r=function(){if(this instanceof r){O.prototype=e.prototype;var i=new O;O.prototype=null;var s=e.apply(i,n.concat(u.call(arguments)));return Object(s)===s?s:i}return e.apply(t,n.concat(u.call(arguments)))}},x.partial=function(e){var t=u.call(arguments,1);return function(){return e.apply(this,t.concat(u.call(arguments)))}},x.bindAll=function(e){var t=u.call(arguments,1);if(t.length===0)throw new Error("bindAll must be passed function names");return T(t,function(t){e[t]=x.bind(e[t],e)}),e},x.memoize=function(e,t){var n={};return t||(t=x.identity),function(){var r=t.apply(this,arguments);return x.has(n,r)?n[r]:n[r]=e.apply(this,arguments)}},x.delay=function(e,t){var n=u.call(arguments,2);return setTimeout(function(){return e.apply(null,n)},t)},x.defer=function(e){return x.delay.apply(x,[e,1].concat(u.call(arguments,1)))},x.throttle=function(e,t,n){var r,i,s,o,u=0,a=function(){u=new Date,s=null,o=e.apply(r,i)};return function(){var f=new Date;!u&&n===!1&&(u=f);var l=t-(f-u);return r=this,i=arguments,l<=0?(clearTimeout(s),s=null,u=f,o=e.apply(r,i)):s||(s=setTimeout(a,l)),o}},x.debounce=function(e,t,n){var r,i;return function(){var s=this,o=arguments,u=function(){r=null,n||(i=e.apply(s,o))},a=n&&!r;return clearTimeout(r),r=setTimeout(u,t),a&&(i=e.apply(s,o)),i}},x.once=function(e){var t=!1,n;return function(){return t?n:(t=!0,n=e.apply(this,arguments),e=null,n)}},x.wrap=function(e,t){return function(){var n=[e];return o.apply(n,arguments),t.apply(this,n)}},x.compose=function(){var e=arguments;return function(){var t=arguments;for(var n=e.length-1;n>=0;n--)t=[e[n].apply(this,t)];return t[0]}},x.after=function(e,t){return e<=0?t():function(){if(--e<1)return t.apply(this,arguments)}},x.keys=E||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)x.has(e,n)&&t.push(n);return t},x.values=function(e){var t=[];for(var n in e)x.has(e,n)&&t.push(e[n]);return t},x.pairs=function(e){var t=[];for(var n in e)x.has(e,n)&&t.push([n,e[n]]);return t},x.invert=function(e){var t={};for(var n in e)x.has(e,n)&&(t[e[n]]=n);return t},x.functions=x.methods=function(e){var t=[];for(var n in e)x.isFunction(e[n])&&t.push(n);return t.sort()},x.extend=function(e){return T(u.call(arguments,1),function(t){if(t)for(var n in t)e[n]=t[n]}),e},x.pick=function(e){var t={},n=a.apply(r,u.call(arguments,1));return T(n,function(n){n in e&&(t[n]=e[n])}),t},x.omit=function(e){var t={},n=a.apply(r,u.call(arguments,1));for(var i in e)x.contains(n,i)||(t[i]=e[i]);return t},x.defaults=function(e){return T(u.call(arguments,1),function(t){if(t)for(var n in t)e[n]===void 0&&(e[n]=t[n])}),e},x.clone=function(e){return x.isObject(e)?x.isArray(e)?e.slice():x.extend({},e):e},x.tap=function(e,t){return t(e),e};var M=function(e,t,n,r){if(e===t)return e!==0||1/e==1/t;if(e==null||t==null)return e===t;e instanceof x&&(e=e._wrapped),t instanceof x&&(t=t._wrapped);var i=f.call(e);if(i!=f.call(t))return!1;switch(i){case"[object String]":return e==String(t);case"[object Number]":return e!=+e?t!=+t:e==0?1/e==1/t:e==+t;case"[object Date]":case"[object Boolean]":return+e==+t;case"[object RegExp]":return e.source==t.source&&e.global==t.global&&e.multiline==t.multiline&&e.ignoreCase==t.ignoreCase}if(typeof e!="object"||typeof t!="object")return!1;var s=n.length;while(s--)if(n[s]==e)return r[s]==t;n.push(e),r.push(t);var o=0,u=!0;if(i=="[object Array]"){o=e.length,u=o==t.length;if(u)while(o--)if(!(u=M(e[o],t[o],n,r)))break}else{var a=e.constructor,l=t.constructor;if(a!==l&&!(x.isFunction(a)&&a instanceof a&&x.isFunction(l)&&l instanceof l))return!1;for(var c in e)if(x.has(e,c)){o++;if(!(u=x.has(t,c)&&M(e[c],t[c],n,r)))break}if(u){for(c in t)if(x.has(t,c)&&!(o--))break;u=!o}}return n.pop(),r.pop(),u};x.isEqual=function(e,t){return M(e,t,[],[])},x.isEmpty=function(e){if(e==null)return!0;if(x.isArray(e)||x.isString(e))return e.length===0;for(var t in e)if(x.has(e,t))return!1;return!0},x.isElement=function(e){return!!e&&e.nodeType===1},x.isArray=w||function(e){return f.call(e)=="[object Array]"},x.isObject=function(e){return e===Object(e)},T(["Arguments","Function","String","Number","Date","RegExp"],function(e){x["is"+e]=function(t){return f.call(t)=="[object "+e+"]"}}),x.isArguments(arguments)||(x.isArguments=function(e){return!!e&&!!x.has(e,"callee")}),typeof /./!="function"&&(x.isFunction=function(e){return typeof e=="function"}),x.isFinite=function(e){return isFinite(e)&&!isNaN(parseFloat(e))},x.isNaN=function(e){return x.isNumber(e)&&e!=+e},x.isBoolean=function(e){return e===!0||e===!1||f.call(e)=="[object Boolean]"},x.isNull=function(e){return e===null},x.isUndefined=function(e){return e===void 0},x.has=function(e,t){return l.call(e,t)},x.noConflict=function(){return e._=t,this},x.identity=function(e){return e},x.times=function(e,t,n){var r=Array(e);for(var i=0;i<e;i++)r[i]=t.call(n,i);return r},x.random=function(e,t){return t==null&&(t=e,e=0),e+Math.floor(Math.random()*(t-e+1))};var _={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};_.unescape=x.invert(_.escape);var D={escape:new RegExp("["+x.keys(_.escape).join("")+"]","g"),unescape:new RegExp("("+x.keys(_.unescape).join("|")+")","g")};x.each(["escape","unescape"],function(e){x[e]=function(t){return t==null?"":(""+t).replace(D[e],function(t){return _[e][t]})}}),x.result=function(e,t){if(e==null)return void 0;var n=e[t];return x.isFunction(n)?n.call(e):n},x.mixin=function(e){T(x.functions(e),function(t){var n=x[t]=e[t];x.prototype[t]=function(){var e=[this._wrapped];return o.apply(e,arguments),F.call(this,n.apply(x,e))}})};var P=0;x.uniqueId=function(e){var t=++P+"";return e?e+t:t},x.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var H=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},j=/\\|'|\r|\n|\t|\u2028|\u2029/g;x.template=function(e,t,n){var r;n=x.defaults({},n,x.templateSettings);var i=new RegExp([(n.escape||H).source,(n.interpolate||H).source,(n.evaluate||H).source].join("|")+"|$","g"),s=0,o="__p+='";e.replace(i,function(t,n,r,i,u){return o+=e.slice(s,u).replace(j,function(e){return"\\"+B[e]}),n&&(o+="'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'"),r&&(o+="'+\n((__t=("+r+"))==null?'':__t)+\n'"),i&&(o+="';\n"+i+"\n__p+='"),s=u+t.length,t}),o+="';\n",n.variable||(o="with(obj||{}){\n"+o+"}\n"),o="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+o+"return __p;\n";try{r=new Function(n.variable||"obj","_",o)}catch(u){throw u.source=o,u}if(t)return r(t,x);var a=function(e){return r.call(this,e,x)};return a.source="function("+(n.variable||"obj")+"){\n"+o+"}",a},x.chain=function(e){return x(e).chain()};var F=function(e){return this._chain?x(e).chain():e};x.mixin(x),T(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];x.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),(e=="shift"||e=="splice")&&n.length===0&&delete n[0],F.call(this,n)}}),T(["concat","join","slice"],function(e){var t=r[e];x.prototype[e]=function(){return F.call(this,t.apply(this._wrapped,arguments))}}),x.extend(x.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this),define("libs/underscore",function(e){return function(){var t,n;return t||e._}}(this)),define("utils/utils",["libs/underscore"],function(e){function t(e,t,r){n("GET",e,{},t,r)}function n(e,t,n,r,i){if(e=="GET"||e=="DELETE")t.indexOf("?")==-1?t+="?":t+="&",t+=$.param(n);var s=new XMLHttpRequest;s.open(e,t,!0),s.setRequestHeader("Accept","application/json"),s.setRequestHeader("Cache-Control","no-cache"),s.setRequestHeader("X-Requested-With","XMLHttpRequest"),s.setRequestHeader("Content-Type","application/json"),s.onloadend=function(){var e=s.status;if(e==200){try{response=jQuery.parseJSON(s.responseText)}catch(t){response=s.responseText}r&&r(response)}else i&&i(e)},e=="GET"||e=="DELETE"?s.send():s.send(JSON.stringify(n))}function r(e,t){var n=$('<div class="'+e+'"></div>');n.appendTo(":eq(0)");var r=n.css(t);return n.remove(),r}function i(e){$('link[href^="'+e+'"]').length||$('<link href="'+galaxy_config.root+e+'" rel="stylesheet">').appendTo("head")}function s(t,n){return t?e.defaults(t,n):n}function o(e,t){var n="";if(e>=1e11)e/=1e11,n="TB";else if(e>=1e8)e/=1e8,n="GB";else if(e>=1e5)e/=1e5,n="MB";else if(e>=100)e/=100,n="KB";else{if(!(e>0))return"<strong>-</strong>";e*=10,n="b"}var r=Math.round(e)/10;return t?r+" "+n:"<strong>"+r+"</strong> "+n}function u(){return(new Date).getTime().toString(36)}function a(e){var t=$("<p></p>");return t.append(e),t}function f(){var e=new Date,t=(e.getHours()<10?"0":"")+e.getHours(),n=(e.getMinutes()<10?"0":"")+e.getMinutes(),r=e.getDate()+"/"+(e.getMonth()+1)+"/"+e.getFullYear()+", "+t+":"+n;return r}return{cssLoadFile:i,cssGetAttribute:r,get:t,merge:s,bytesToString:o,uuid:u,time:f,wrap:a,request:n}}),define("mvc/ui/ui-modal",["utils/utils"],function(e){var t=Backbone.View.extend({elMain:"body",optionsDefault:{title:"ui-modal",body:"",backdrop:!0,height:null,width:null,closing_events:!1},buttonList:{},initialize:function(e){e&&this._create(e)},show:function(e){this.initialize(e),this.options.height?(this.$body.css("height",this.options.height),this.$body.css("overflow","hidden")):this.$body.css("max-height",$(window).height()/2),this.options.width&&this.$dialog.css("width",this.options.width),this.visible?this.$el.show():this.$el.fadeIn("fast"),this.visible=!0},hide:function(){this.visible=!1,this.$el.fadeOut("fast")},enableButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).prop("disabled",!1)},disableButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).prop("disabled",!0)},showButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).show()},hideButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).hide()},getButton:function(e){var t=this.buttonList[e];return this.$buttons.find("#"+t)},scrollTop:function(){return this.$body.scrollTop()},_create:function(e){var t=this;this.options=_.defaults(e,this.optionsDefault),this.options.body=="progress"&&(this.options.body=$('<div class="progress progress-striped active"><div class="progress-bar progress-bar-info" style="width:100%"></div></div>')),this.$el&&(this.$el.remove(),$(document).off("keyup")),this.setElement(this._template(this.options.title)),this.$dialog=this.$el.find(".modal-dialog"),this.$body=this.$el.find(".modal-body"),this.$footer=this.$el.find(".modal-footer"),this.$buttons=this.$el.find(".buttons"),this.$backdrop=this.$el.find(".modal-backdrop"),this.$body.html(this.options.body),this.options.backdrop||this.$backdrop.removeClass("in");if(this.options.buttons){this.buttonList={};var n=0;$.each(this.options.buttons,function(e,r){var i="button-"+n++;t.$buttons.append($('<button id="'+i+'"></button>').text(e).click(r)).append(" "),t.buttonList[e]=i})}else this.$footer.hide();$(this.elMain).append($(this.el)),this.options.closing_events&&(this.options.buttons.Pause||$(document).on("keyup",function(e){e.keyCode==27&&t.hide()}),this.$el.find(".modal-backdrop").on("click",function(){t.hide()}))},_template:function(e){return'<div class="modal"><div class="modal-backdrop fade in" style="z-index: -1;"></div><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><button type="button" class="close" style="display: none;">×</button><h4 class="title">'+e+"</h4>"+"</div>"+'<div class="modal-body" style="position: static;"></div>'+'<div class="modal-footer">'+'<div class="buttons" style="float: right;"></div>'+"</div>"+"</div"+"</div>"+"</div>"}});return{View:t}}),define("mvc/ui/ui-portlet",["utils/utils"],function(e){var t=Backbone.View.extend({visible:!1,optionsDefault:{title:"",icon:"",buttons:null,body:null,height:null,operations:null,placement:"bottom",overflow:"auto"},$title:null,$content:null,$buttons:null,$operations:null,initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.$content=this.$el.find("#content"),this.$title=this.$el.find("#title-text"),this.options.height&&(this.$el.find("#body").css("height",this.options.height),this.$el.find("#content").css("overflow",this.options.overflow)),this.$buttons=$(this.el).find("#buttons");if(this.options.buttons){var n=this;$.each(this.options.buttons,function(e,t){t.$el.prop("id",e),n.$buttons.append(t.$el)})}else this.$buttons.remove();this.$operations=$(this.el).find("#operations");if(this.options.operations){var n=this;$.each(this.options.operations,function(e,t){t.$el.prop("id",e),n.$operations.append(t.$el)})}this.options.body&&this.append(this.options.body)},append:function(t){this.$content.append(e.wrap(t))},content:function(){return this.$content},show:function(){this.$el.fadeIn("fast"),this.visible=!0},hide:function(){this.$el.fadeOut("fast"),this.visible=!1},enableButton:function(e){this.$buttons.find("#"+e).prop("disabled",!1)},disableButton:function(e){this.$buttons.find("#"+e).prop("disabled",!0)},hideOperation:function(e){this.$operations.find("#"+e).hide()},showOperation:function(e){this.$operations.find("#"+e).show()},setOperation:function(e,t){var n=this.$operations.find("#"+e);n.off("click"),n.on("click",t)},title:function(e){var t=this.$title;return e&&t.html(e),t.html()},_template:function(e){var t='<div class="toolForm">';if(e.title||e.icon)t+='<div id="title" class="toolFormTitle" style="overflow: hidden;"><div id="operations" style="float: right;"></div><div style="overflow: hidden;">',e.icon&&(t+='<i style="padding-top: 3px; float: left; font-size: 1.2em" class="icon fa '+e.icon+'"> </i>'),t+='<div id="title-text" style="padding-top: 2px; float: left;">'+e.title+"</div>",t+="</div></div>";return t+='<div id="body" class="toolFormBody">',e.placement=="top"&&(t+='<div id="buttons" class="buttons" style="height: 50px; padding: 10px;"></div>'),t+='<div id="content" class="content" style="height: inherit; padding: 10px;"></div>',e.placement=="bottom"&&(t+='<div id="buttons" class="buttons" style="height: 50px; padding: 10px;"></div>'),t+="</div></div>",t}});return{View:t}}),define("plugin/library/ui-select",["utils/utils"],function(e){var t=Backbone.View.extend({optionsDefault:{id:"",cls:"",empty:"No data available",visible:!0,wait:!1},selected:null,initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.$select=this.$el.find("#select"),this.$icon=this.$el.find("#icon"),this.selected=this.options.value;var n=this;this.options.onchange&&this.$select.on("change",function(){n.value(n.$select.val())}),this._refresh(),this.options.visible||this.hide(),this.options.wait?this.wait():this.show()},value:function(e){var t=this.selected;e!==undefined&&(this.selected=e,this.$select.val(e));var n=this.selected;return n&&n!=t&&this.options.onchange&&this.options.onchange(n),n},text:function(){return this.$select.find("option:selected").text()},show:function(){this.$icon.removeClass(),this.$icon.addClass("fa fa-caret-down"),this.$select.show(),this.$el.show()},hide:function(){this.$el.hide()},wait:function(){this.$icon.removeClass(),this.$icon.addClass("fa fa-spinner fa-spin"),this.$select.hide()},disabled:function(){return this.$select.is(":disabled")},enable:function(){this.$select.prop("disabled",!1)},disable:function(){this.$select.prop("disabled",!0)},add:function(e){this.$select.append(this._templateOption(e)),this._refresh()},del:function(e){this.$select.find("option[value="+e+"]").remove(),this.$select.trigger("change"),this._refresh()},update:function(e){this.$select.find("option").remove();for(var t in e)this.$select.append(this._templateOption(e[t]));!this.selected&&e.length>0&&this.value(e[0].value),this._refresh()},_refresh:function(){this.$select.find("option[value=null]").remove();var e=this.$select.find("option").length;e==0?(this.$select.append(this._templateOption({value:"null",label:this.options.empty})),this.disable()):(this.enable(),this.selected&&this.$select.val(this.selected))},_exists:function(e){return 0!=this.$select.find("option[value="+e+"]").length},_templateOption:function(e){return'<option value="'+e.value+'">'+e.label+"</option>"},_template:function(e){var t='<div id="'+e.id+'" class="styled-select">'+'<div class="button">'+'<i id="icon"/>'+"</div>"+'<select id="select" class="select '+e.cls+" "+e.id+'">';for(key in e.data){var n=e.data[key],r="";if(n.value==e.value||n.value=="")r="selected";t+='<option value="'+n.value+'" '+r+">"+n.label+"</option>"}return t+="</select></div>",t}});return{View:t}}),define("plugin/library/ui",["utils/utils","plugin/library/ui-select"],function(e,t){var n=Backbone.View.extend({optionsDefault:{title:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options))},title:function(e){this.$el.find("b").html(e)},_template:function(e){return"<label><b>"+e.title+"</b></label>"},value:function(){return options.title}}),r=Backbone.View.extend({optionsDefault:{id:null,title:"","float":"right",cls:"btn-default",icon:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).on("click",t.onclick),$(this.el).tooltip({title:t.tooltip,placement:"bottom"})},_template:function(e){var t='<button id="'+e.id+'" type="submit" style="margin-right: 5px; float: '+e.float+';" type="button" class="btn '+e.cls+'">';return e.icon&&(t+='<i class="icon fa '+e.icon+'"></i> '),t+=e.title+"</button>",t}}),i=Backbone.View.extend({optionsDefault:{"float":"right",icon:"",tooltip:"",placement:"bottom"},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).tooltip({title:t.tooltip,placement:"bottom"})},_template:function(e){return'<span class="fa '+e.icon+'" style="font-size: 1.2em;"/>'}}),s=Backbone.View.extend({optionsDefault:{title:"",id:null,"float":"right",cls:"icon-btn",icon:"",tooltip:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).on("click",t.onclick),$(this.el).tooltip({title:t.tooltip,placement:"bottom"})},_template:function(e){var t="";e.title&&(t="width: auto;");var n='<div id="'+e.id+'" style="margin-right: 5px; float: '+e.float+"; "+t+'" class="'+e.cls+'">';return e.title?n+='<div style="margin-right: 5px; margin-left: 5px;"><i class="icon fa '+e.icon+'"/> '+'<span style="position: relative; font-size: 0.8em; font-weight: normal; top: -1px;">'+e.title+"</span>"+"</div>":n+='<i class="icon fa '+e.icon+'"/>',n+="</div>",n}}),o=Backbone.View.extend({optionsDefault:{title:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).on("click",t.onclick)},_template:function(e){return'<div><a href="javascript:void(0)">'+e.title+"</a></div>"}}),u=Backbone.View.extend({optionsDefault:{message:"",status:"info",persistent:!1},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement("<div></div>")},update:function(t){this.options=e.merge(t,this.optionsDefault);if(t.message!=""){this.$el.html(this._template(this.options)),this.$el.fadeIn();if(!t.persistent){var n=this;window.setTimeout(function(){n.$el.is(":visible")?n.$el.fadeOut():n.$el.hide()},3e3)}}else this.$el.fadeOut()},_template:function(e){return'<div class="alert alert-'+e.status+'" style="padding: 2px 2px 2px 10px;">'+e.message+"</div>"}}),a=Backbone.View.extend({optionsDefault:{onclick:null,searchword:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options));var n=this;this.options.onclick&&this.$el.on("submit",function(e){var t=n.$el.find("#search");n.options.onclick(t.val())})},_template:function(e){return'<div class="search"><form onsubmit="return false;"><input id="search" class="form-control input-sm" type="text" name="search" placeholder="Search..." value="'+e.searchword+'">'+'<button type="submit" class="btn search-btn">'+'<i class="fa fa-search"></i>'+"</button>"+"</form>"+"</div>"}}),f=Backbone.View.extend({optionsDefault:{title:"Unlabeled",body:null},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.options.body&&this.$el.find(".body").append(this.options.body)},_template:function(e){return'<div id="title" class="title">'+e.title+":"+"</div>"}}),l=Backbone.View.extend({optionsDefault:{id:"",title:"",target:"",href:"",onunload:null,onclick:null,visible:!0,icon:null,tag:""},$menu:null,initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement($(this._template(this.options)));var n=$(this.el).find(".root"),r=this;n.on("click",function(e){e.preventDefault(),r.options.onclick&&r.options.onclick()}),this.options.visible||this.hide()},show:function(){$(this.el).show()},hide:function(){$(this.el).hide()},addMenu:function(t){var n={title:"",target:"",href:"",onclick:null,divider:!1,icon:null};n=e.merge(t,n),this.$menu||($(this.el).append(this._templateMenu()),this.$menu=$(this.el).find(".menu"));var r=$(this._templateMenuItem(n));r.on("click",function(e){e.preventDefault(),n.onclick&&n.onclick()}),this.$menu.append(r),n.divider&&this.$menu.append($(this._templateDivider()))},_templateMenuItem:function(e){var t='<li><a href="'+e.href+'" target="'+e.target+'">';return e.icon&&(t+='<i class="fa '+e.icon+'"></i>'),t+=" "+e.title+"</a>"+"</li>",t},_templateMenu:function(){return'<ul class="menu dropdown-menu pull-right" role="menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(e){var t='<div id="'+e.id+'" class="button-menu btn-group">'+'<button type="button" class="root btn btn-default dropdown-toggle" data-toggle="dropdown">';return e.icon&&(t+='<i class="fa '+e.icon+'"></i>'),"</button></div>",t}}),c=Backbone.View.extend({optionsDefault:{value:"",type:"text",placeholder:"",disabled:!1,visible:!0},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.options.disabled&&this.$el.prop("disabled",!0),this.options.visible||this.$el.hide();var n=this;this.options.onchange&&this.$el.on("input",function(){n.options.onchange()})},value:function(e){return e!==undefined&&this.$el.val(e),this.$el.val()},_template:function(e){return'<input id="'+e.id+'" type="'+e.type+'" value="'+e.value+'" placeholder="'+e.placeholder+'" class="form-control">'}});return{Label:n,Button:r,Icon:i,ButtonIcon:s,Input:c,Anchor:o,Message:u,Searchbox:a,Title:f,Select:t,ButtonMenu:l}}),define("plugin/library/jobs",["utils/utils"],function(e){return Backbone.Model.extend({initialize:function(t,n){this.app=t,this.options=e.merge(n,this.optionsDefault)},submit:function(t,n,r){var i=this,s=t.id,o=t.get("type"),u=this.app.types.get(o);data={tool_id:"rkit",history_id:t.get("history_id"),inputs:{input:t.get("dataset_hid"),module:o,options:n}};var a=t.get("dataset_id_job");a&&e.request("PUT",config.root+"api/histories/"+t.get("history_id")+"/contents/"+a,{deleted:!0}),t.state("submit","Sending job request..."),e.request("POST",config.root+"api/tools",data,function(e){if(!e.outputs||e.outputs.length==0)t.state("failed","Job submission failed. No response.");else{Galaxy&&Galaxy.currHistoryPanel&&Galaxy.currHistoryPanel.refreshHdas();var n=e.outputs[0];t.state("queued","Job has been queued..."),t.set("dataset_id_job",n.id),i._loop(n.id,function(e){switch(e.state){case"ok":return t.state("success","Job completed successfully..."),r(e),!0;case"error":return t.state("failed","Job has failed. Please check the history for details."),!0;case"running":return t.state("running","Job is running..."),!1}})}},function(e){t.state("failed","Job submission failed. Please make sure that 'R-kit' is installed.")})},_loop:function(t,n){var r=this;e.request("GET",config.root+"api/jobs/"+t,{},function(e){n(e)||setTimeout(function(){r._loop(t,n)},r.app.config.get("query_timeout"))})}})}),define("plugin/library/datasets",["utils/utils"],function(e){return Backbone.Collection.extend({list:{},initialize:function(t,n){this.app=t,this.options=e.merge(n,this.optionsDefault)},request:function(t,n,r){var i=this;if(t.groups)this._fetch(t,n);else{var s=this.list[t.id];if(s){n(s);return}e.request("GET",config.root+"api/datasets/"+t.id,{},function(e){switch(e.state){case"error":r&&r(e);break;default:i.list[t.id]=e,n(e)}})}},_fetch:function(t,n){var r=t.start?t.start:0,i=Math.abs(t.end-t.start),s=this.app.config.get("query_limit");if(!i||i>s)i=s;var o="",u={},a=0;for(var f in t.groups){var l=t.groups[f];for(var c in l.columns){var h=l.columns[c];o+=h+",",u[h]=a,a++}}if(a==0){n({});return}o=o.substring(0,o.length-1);var p=t.groups.slice(0);for(var f in p)p[f].values=[];var d=this;e.request("GET",config.root+"api/datasets/"+t.id,{data_type:"raw_data",provider:"dataset-column",limit:i,offset:r,indeces:o},function(e){for(var i in e.data){var s=e.data[i];for(var o in t.groups){var a=t.groups[o],f={x:parseInt(i)+r};for(var l in a.columns){var c=a.columns[l],h=u[c],d=s[h];if(isNaN(d)||!d)d=0;f[l]=d}p[o].values.push(f)}}n(p)})}})}),define("plugin/library/ui-table",["utils/utils"],function(e){return Backbone.View.extend({row:null,row_count:0,optionsDefault:{content:"No content available.",onchange:null,ondblclick:null,onconfirm:null},events:{click:"_onclick",dblclick:"_ondblclick"},first:!0,initialize:function(t){this.options=e.merge(t,this.optionsDefault);var n=$(this._template(t));this.$thead=n.find("thead"),this.$tbody=n.find("tbody"),this.$tmessage=n.find("tmessage"),this.setElement(n),this.row=$("<tr></tr>")},addHeader:function(e){var t=$("<th></th>");t.append(e),this.row.append(t)},appendHeader:function(){this.$thead.append(this.row),this.row=$("<tr></tr>")},add:function(e,t){var n=$("<td></td>");t&&n.css("width",t),n.append(e),this.row.append(n)},append:function(e){this._commit(e)},prepend:function(e){this._commit(e,!0)},remove:function(e){var t=this.$tbody.find("#"+e);t.length>0&&(t.remove(),this.row_count--,this._refresh())},removeAll:function(){this.$tbody.html(""),this.row_count=0,this._refresh()},value:function(e){this.before=this.$tbody.find(".current").attr("id"),e!==undefined&&(this.$tbody.find("tr").removeClass("current"),e&&this.$tbody.find("#"+e).addClass("current"));var t=this.$tbody.find(".current").attr("id");return t===undefined?null:(t!=this.before&&this.options.onchange&&this.options.onchange(e),t)},size:function(){return this.$tbody.find("tr").length},_commit:function(e,t){this.remove(e),this.row.attr("id",e),t?this.$tbody.prepend(this.row):this.$tbody.append(this.row),this.row=$("<tr></tr>"),this.row_count++,this._refresh()},_onclick:function(e){var t=this.value(),n=$(e.target).closest("tr").attr("id");n&&t!=n&&(this.options.onconfirm?this.options.onconfirm(n):this.value(n))},_ondblclick:function(e){var t=this.value();t&&this.options.ondblclick&&this.options.ondblclick(t)},_refresh:function(){this.row_count==0?this.$tmessage.show():this.$tmessage.hide()},_template:function(e){return'<div><table class="grid"><thead></thead><tbody style="cursor: pointer;"></tbody></table><tmessage>'+e.content+"</tmessage>"+"<div>"}})}),define("plugin/models/group",[],function(){return Backbone.Model.extend({defaults:{key:"Data label",date:""},reset:function(){this.clear({silent:!0}).set(this.defaults),this.trigger("reset",this)}})}),define("plugin/views/viewport",["mvc/ui/ui-portlet","plugin/library/ui","utils/utils"],function(e,t,n){return Backbone.View.extend({list:{},optionsDefault:{height:300},initialize:function(r,i){this.app=r,this.options=n.merge(i,this.optionsDefault),this.portlet=new e.View({title:"title",height:this.options.height,overflow:"hidden",operations:{edit:new t.ButtonIcon({icon:"fa-gear",tooltip:"Customize Chart",title:"Customize"})}}),this.setElement(this.portlet.$el);var s=this;this.app.charts.on("remove",function(e){s._removeChart(e.id)}),this.app.charts.on("redraw",function(e){e.ready()?s._refreshChart(e):e.on("change:state",function(){e.ready()&&s._refreshChart(e)})})},showChart:function(e){this.show(),this.hideCharts();var t=this.list[e];if(t){var n=self.app.charts.get(e);this.portlet.title(n.get("title")),this.portlet.setOperation("edit",function(){self.app.chart.copy(n),self.app.charts_view.hide(),self.app.chart_view.$el.show()}),t.$el.show(),$(window).trigger("resize")}},hideCharts:function(){this.$el.find(".item").hide()},show:function(){$(".tooltip").hide(),this.$el.show()},hide:function(){$(".tooltip").hide(),this.$el.hide()},_refreshChart:function(e){var t=this;if(!e.ready()){t.app.log("viewport:_refreshChart()","Invalid attempt to refresh chart before completion.");return}var n=e.id;this._removeChart(n);var r="#"+n,i=$(this._template({id:r}));this.portlet.append(i);var s=d3.select(r+" svg");this.list[n]={svg:s,$el:i},this.showChart(n),e.off("change:state"),e.on("change:state",function(){var t=i.find("#info"),n=t.find("#icon");n.removeClass(),t.show(),t.find("#text").html(e.get("state_info"));var r=e.get("state");switch(r){case"ok":t.hide();break;case"failed":n.addClass("fa fa-warning");break;default:n.addClass("fa fa-spinner fa-spin")}}),e.state("wait","Please wait...");var o=e.get("type"),u=this.app.types.get(o),t=this;require(["plugin/charts/"+o+"/"+o],function(n){var r=new n(t.app,{svg:s}),i=u.mode;i=="execute"?t.app.jobs.submit(e,t._defaultRequestString(e),function(){r.draw(e,t._defaultRequestDictionary(e))}):r.draw(e,t._defaultRequestDictionary(e))})},_removeChart:function(e){var t=this.list[e];t&&(t.svg.remove(),t.$el.remove())},_template:function(e){return'<div id="'+e.id.substr(1)+'" class="item">'+'<span id="info">'+'<span id="icon" style="font-size: 1.2em; display: inline-block;"/>'+'<span id="text" style="position: relative; margin-left: 5px; top: -1px; font-size: 1.0em;"/>'+"</span>"+'<svg style="height: auto;"/>'+"</div>"},_defaultRequestString:function(e){var t=this.app.types.get(e.get("type")),n="",r=0;return e.groups.each(function(e){for(var i in t.columns)n+=i+"_"+ ++r+":"+(parseInt(e.get(i))+1)+", "}),n.substring(0,n.length-2)},_defaultRequestDictionary:function(e){var t=this.app.types.get(e.get("type")),n={id:e.get("dataset_id"),groups:[]},r=0;return e.groups.each(function(e){var i={};for(var s in t.columns)i[s]=e.get(s);n.groups.push({key:++r+":"+e.get("key"),columns:i})}),n}})}),define("plugin/views/charts",["mvc/ui/ui-portlet","plugin/library/ui-table","plugin/library/ui","utils/utils","plugin/models/group","plugin/views/viewport"],function(e,t,n,r,i,s){return Backbone.View.extend({initialize:function(i,o){this.app=i,this.viewport_view=new s(i),this.table=new t({content:"Add charts to this table.",ondblclick:function(e){var t=u.app.charts.get(e);u.app.chart.copy(t),u.hide(),u.app.chart_view.$el.show()},onchange:function(e){var t=u.app.charts.get(e);u.app.config.set("title",t.get("title")),u.viewport_view.showChart(e)}});var u=this;this.portlet=new e.View({icon:"fa-list",title:"List of created charts:",height:100,operations:{"new":new n.ButtonIcon({icon:"fa-magic",tooltip:"Create a new Chart",title:"New",onclick:function(){u.hide(),u.app.chart.reset(),u.app.chart_view.$el.show()}}),"delete":new n.ButtonIcon({icon:"fa-trash-o",tooltip:"Delete this Chart",title:"Delete",onclick:function(){var e=u.table.value();if(!e)return;var t=u.app.charts.get(e);u.app.modal.show({title:"Are you sure?",body:'The selected chart "'+t.get("title")+'" will be irreversibly deleted.',buttons:{Cancel:function(){u.app.modal.hide()},Delete:function(){u.app.modal.hide(),u.app.charts.remove(e)}}})}})}}),this.portlet.append(this.table.$el),this.app.options.config.widget||this.$el.append(this.portlet.$el),this.$el.append(r.wrap("")),this.$el.append(this.viewport_view.$el);var u=this;this.app.charts.on("add",function(e){u._addChart(e)}),this.app.charts.on("remove",function(e){u._removeChart(e)}),this.app.charts.on("change",function(e){u._changeChart(e)})},hide:function(){$(".tooltip").hide(),this.$el.hide()},_addChart:function(e){var t=e.get("title");t==""&&(t="Untitled"),this.table.add(t);var n=this.app.types.get(e.get("type"));this.table.add(n.title),this.table.add("Last change: "+e.get("date")),this.table.prepend(e.get("id")),this.table.value(e.get("id"))},_removeChart:function(e){this.table.remove(e.id),this.table.size()==0?(this.hide(),this.app.chart.reset(),this.app.chart_view.$el.show()):this.table.value(this.app.charts.last().id)},_changeChart:function(e){e.get("type")&&(this._addChart(e),this.table.value(e.id))}})}),define("mvc/ui/ui-tabs",["utils/utils"],function(e){var t=Backbone.View.extend({visible:!1,list:{},$nav:null,$content:null,first_tab:null,optionsDefault:{title_new:"",operations:null,onnew:null},initialize:function(t){this.options=e.merge(t,this.optionsDefault);var n=$(this._template(this.options));this.$nav=n.find(".tab-navigation"),this.$content=n.find(".tab-content"),this.setElement(n),this.list={};var r=this;this.options.operations&&$.each(this.options.operations,function(e,t){t.$el.prop("id",e),r.$nav.append(t.$el)});if(this.options.onnew){var i=$(this._template_tab_new(this.options));this.$nav.append(i),i.tooltip({title:"Add a new tab",placement:"bottom"}),i.on("click",function(e){i.tooltip("hide"),r.options.onnew()})}},add:function(e){var t=e.id,n={$title:$(this._template_tab(e)),$content:$(this._template_tab_content(e)),removable:e.ondel?!0:!1};this.list[t]=n,this.options.onnew?this.$nav.find("#new-tab").before(n.$title):this.$nav.append(n.$title),n.$content.append(e.$el),this.$content.append(n.$content),_.size(this.list)==1&&(n.$title.addClass("active"),n.$content.addClass("active"),this.first_tab=t);if(e.ondel){var r=n.$title.find("#delete");r.tooltip({title:"Delete this tab",placement:"bottom"}),r.on("click",function(){return r.tooltip("destroy"),e.ondel(),!1})}e.onclick&&n.$title.on("click",function(){e.onclick()})},del:function(e){var t=this.list[e];t.$title.remove(),t.$content.remove(),delete t,this.first_tab==e&&(this.first_tab=null),this.first_tab!=null&&this.show(this.first_tab)},delRemovable:function(){for(var e in this.list){var t=this.list[e];t.removable&&this.del(e)}},show:function(e){this.$el.fadeIn("fast"),this.visible=!0,e&&this.list[e].$title.find("a").tab("show")},hide:function(){this.$el.fadeOut("fast"),this.visible=!1},hideOperation:function(e){this.$nav.find("#"+e).hide()},showOperation:function(e){this.$nav.find("#"+e).show()},setOperation:function(e,t){var n=this.$nav.find("#"+e);n.off("click"),n.on("click",t)},title:function(e,t){var n=this.list[e].$title.find("#text");return t&&n.html(t),n.html()},_template:function(e){return'<div class="tabbable tabs-left"><ul class="tab-navigation nav nav-tabs"/><div class="tab-content"/></div>'},_template_tab_new:function(e){return'<li id="new-tab"><a href="javascript:void(0);"><i style="font-size: 0.8em; margin-right: 5px;" class="fa fa-plus-circle"/>'+e.title_new+"</a>"+"</li>"},_template_tab:function(e){var t='<li id="title-'+e.id+'">'+'<a title="" href="#tab-'+e.id+'" data-toggle="tab" data-original-title="">'+'<span id="text">'+e.title+"</span>";return e.ondel&&(t+='<i id="delete" style="font-size: 0.8em; margin-left: 5px; cursor: pointer;" class="fa fa-minus-circle"/>'),t+="</a></li>",t},_template_tab_content:function(e){return'<div id="tab-'+e.id+'" class="tab-pane"/>'}});return{View:t}}),define("plugin/models/groups",["plugin/models/group"],function(e){return Backbone.Collection.extend({model:e})}),define("plugin/models/chart",["plugin/models/groups"],function(e){return Backbone.Model.extend({defaults:{id:null,title:"",type:"",date:null,state:"ok",state_info:""},initialize:function(t){this.groups=new e,this.settings=new Backbone.Model},reset:function(){this.clear({silent:!0}).set(this.defaults),this.groups.reset(),this.settings.clear(),this.trigger("reset",this)},copy:function(e){var t=this;t.clear({silent:!0}).set(this.defaults),t.set(e.attributes),t.settings=e.settings.clone(),t.groups.reset(),e.groups.each(function(e){t.groups.add(e.clone())}),t.trigger("change",t)},state:function(e,t){this.set("state_info",t),this.set("state",e)},ready:function(){return this.get("state")=="ok"||this.get("state")=="failed"}})}),define("plugin/views/group",["plugin/library/ui-table","plugin/library/ui","utils/utils"],function(e,t,n){return Backbone.View.extend({columns:[],initialize:function(r,i){this.app=r;var s=this;this.chart=this.app.chart,this.group=i.group,this.group_key=new t.Input({placeholder:"Data label",onchange:function(){s.group.set("key",s.group_key.value())}}),this.table=new e({content:"No data column."});var o=$("<div/>");o.append(n.wrap((new t.Label({title:"Provide a label:"})).$el)),o.append(n.wrap(this.group_key.$el)),o.append(n.wrap((new t.Label({title:"Select columns:"})).$el)),o.append(n.wrap(this.table.$el)),this.setElement(o);var s=this;this.chart.on("change:dataset_id",function(){s._refreshTable()}),this.chart.on("change:type",function(){s._refreshTable()}),this.group.on("change:key",function(){s._refreshGroupKey()}),this.group.on("change",function(){s._refreshGroup()}),this._refreshTable(),this._refreshGroupKey(),this._refreshGroup()},_refreshTable:function(){var e=this.chart.get("dataset_id"),n=this.chart.get("type");if(!e||!n)return;var r=this,i=this.app.types.get(n);this.table.removeAll();var s={};for(var o in i.columns){var u=this.group.get(o);u||this.group.set(o,0);var a=i.columns[o],f=new t.Select.View({id:"select_"+o,gid:o,onchange:function(e){r.group.set(this.gid,e)},value:u,wait:!0});this.table.add(a.title,"25%"),this.table.add(f.$el),this.table.append(o),s[o]=f}this.app.datasets.request({id:e},function(e){r.columns=[];var t=e.metadata_column_types;for(var n in t)(t[n]=="int"||t[n]=="float")&&r.columns.push({label:"Column: "+(parseInt(n)+1)+" ["+t[n]+"]",value:n});for(var n in s)s[n].update(r.columns),s[n].show()})},_refreshGroup:function(){this.group.set("date",n.time())},_refreshGroupKey:function(){var e=this.group.get("key");e===undefined&&(e=""),this.group_key.value(e)}})}),define("plugin/library/ui-table-form",["plugin/library/ui-table","plugin/library/ui","utils/utils"],function(e,t,n){var r=Backbone.View.extend({initialize:function(r){this.table_title=new t.Label({title:r.title}),this.table=new e({content:r.content});var i=$("<div/>");i.append(n.wrap(this.table_title.$el)),i.append(n.wrap(this.table.$el)),this.setElement(i)},title:function(e){this.table_title.title(e)},update:function(e,t){this.table.removeAll();for(var n in e)this._add(n,e[n],t)},_add:function(e,n,r){var i=null,s=n.type;switch(s){case"text":i=new t.Input({placeholder:n.placeholder,onchange:function(){r.set(e,i.value())}});break;case"select":i=new t.Select.View({data:n.data,onchange:function(){r.set(e,i.value())}});break;case"slider":i=new t.Input({placeholder:n.placeholder,onchange:function(){r.set(e,i.value())}});break;case"separator":i=$("<div/>");break;default:console.log("ui-table-form:_add","Unknown setting type ("+n.type+")");return}if(s!="separator"){r.get(e)||r.set(e,n.init),i.value(r.get(e));var o=$("<div/>");o.append(i.$el),o.append('<div class="toolParamHelp" style="font-size: 0.9em;">'+n.info+"</div>"),this.table.add('<span style="white-space: nowrap;">'+n.title+"</span>","25%"),this.table.add(o)}else this.table.add('<h6 style="white-space: nowrap;">'+n.title+":<h6/>"),this.table.add($("<div/>"));this.table.append(e)}});return{View:r}}),define("plugin/views/settings",["plugin/library/ui","plugin/library/ui-table-form","utils/utils"],function(e,t,n){return Backbone.View.extend({initialize:function(e,n){this.app=e;var r=this;this.chart=this.app.chart,this.form=new t.View({title:"Chart options:",content:"This chart type does not provide any options."}),this.setElement(this.form.$el);var r=this;this.chart.on("change",function(){r._refreshTable()})},_refreshTable:function(){var e=this.chart.get("type");if(!e)return;var t=this.app.types.get(e);this.form.title(t.title+":"),this.form.update(t.settings,this.chart.settings)}})}),define("plugin/views/chart",["mvc/ui/ui-tabs","plugin/library/ui-table","plugin/library/ui","utils/utils","plugin/models/chart","plugin/models/group","plugin/views/group","plugin/views/settings"],function(e,t,n,r,i,s,o,u){return Backbone.View.extend({optionsDefault:{header:!0,content:"No content available."},initialize:function(i,s){this.app=i,this.chart=this.app.chart,this.options=r.merge(s,this.optionsDefault);var o=this;this.table=new t({header:!1,onconfirm:function(e){o.chart.groups.length>0?o.app.modal.show({title:"Switching to another chart type?",body:"If you continue your settings and selections will be reseted.",buttons:{Cancel:function(){o.app.modal.hide()},Continue:function(){o.app.modal.hide(),o.table.value(e)}}}):o.table.value(e)},onchange:function(e){o.chart.groups.reset(),o.chart.settings.clear(),o.chart.set({type:e})},ondblclick:function(e){o.tabs.show("settings")},content:"No chart types available"});var a=0,f=i.types.attributes;for(var l in f){var c=f[l];this.table.add(++a+"."),this.table.add(c.title),this.table.append(l)}this.tabs=new e.View({title_new:"Add Data",onnew:function(){var e=o._addGroupModel();o.tabs.show(e.id)},operations:{save:new n.ButtonIcon({icon:"fa-save",tooltip:"Draw Chart",title:"Draw",onclick:function(){o.chart.groups.length==0&&o._addGroupModel(),o._saveChart(),o.hide(),o.app.charts_view.$el.show()}}),back:new n.ButtonIcon({icon:"fa-caret-left",tooltip:"Return to Viewer",title:"Return",onclick:function(){o.hide(),o.app.charts_view.$el.show()}})}}),this.title=new n.Input({placeholder:"Chart title",onchange:function(){o.app.config.set("title",o.title.value())}});var h=$("<div/>");h.append(r.wrap((new n.Label({title:"Provide a chart title:"})).$el)),h.append(r.wrap(this.title.$el)),h.append(r.wrap((new n.Label({title:"Select a chart type:"})).$el)),h.append(r.wrap(this.table.$el)),this.tabs.add({id:"main",title:"Start",$el:h}),this.settings=new u(this.app),this.tabs.add({id:"settings",title:"Configuration",$el:this.settings.$el}),this.setElement(this.tabs.$el),this.tabs.hideOperation("back");var o=this;this.chart.on("change:title",function(e){o.title.value(e.get("title")),o.app.config.set("title",e.get("title"))}),this.chart.on("change:type",function(e){o.table.value(e.get("type"))}),this.chart.on("reset",function(e){o._resetChart()}),this.app.charts.on("add",function(e){o.tabs.showOperation("back")}),this.app.charts.on("remove",function(e){o.app.charts.length==0&&o.tabs.hideOperation("back")}),this.app.charts.on("reset",function(e){o.tabs.hideOperation("back")}),this.app.chart.groups.on("add",function(e){o._addGroup(e)}),this.app.chart.groups.on("remove",function(e){o._removeGroup(e)}),this.app.chart.groups.on("reset",function(e){o._removeAllGroups()}),this.app.chart.groups.on("change:key",function(e){o._refreshGroupKey()}),this._resetChart()},hide:function(){$(".tooltip").hide(),this.$el.hide()},_refreshGroupKey:function(){var e=this,t=0;this.chart.groups.each(function(n){var r=n.get("key","");r==""&&(r="Chart data"),e.tabs.title(n.id,++t+": "+r)})},_addGroupModel:function(){var e=new s({id:r.uuid()});return this.chart.groups.add(e),e},_addGroup:function(e){var t=this,n=new o(this.app,{group:e}),r=t.chart.groups.length;this.tabs.add({id:e.id,$el:n.$el,ondel:function(){t.chart.groups.remove(e.id)}}),this._refreshGroupKey()},_removeGroup:function(e){this.tabs.del(e.id),this._refreshGroupKey()},_removeAllGroups:function(e){this.tabs.delRemovable()},_resetChart:function(){this.chart.set("id",r.uuid()),this.chart.set("type","bardiagram"),this.chart.set("dataset_id",this.app.options.dataset.id),this.chart.set("dataset_hid",this.app.options.dataset.hid),this.chart.set("history_id",this.app.options.dataset.history_id),this.chart.set("title","New Chart")},_saveChart:function(){this.chart.set({type:this.table.value(),title:this.title.value(),date:r.time()});var e=this.app.charts.get(this.chart.id);e?e.copy(this.chart):(e=this.chart.clone(),e.copy(this.chart),this.app.charts.add(e)),e.trigger("redraw",e)}})}),define("plugin/models/config",[],function(){return Backbone.Model.extend({defaults:{query_limit:1e3,query_timeout:500,title:"Create a new chart"}})}),define("plugin/models/charts",["plugin/models/chart"],function(e){return Backbone.Collection.extend({model:e})}),define("plugin/charts/_nvd3/config",[],function(){return{title:"",columns:{y:{title:"Values for y-axis"}},settings:{separator_label:{title:"X axis",type:"separator"},x_axis_label:{title:"Axis label",info:"Provide a label for the axis.",type:"text",init:"X-axis",placeholder:"Axis label"},x_axis_type:{title:"Axis value type",info:"Select the value type of the axis.",type:"select",init:"f",data:[{label:"-- Do not show values --",value:"hide"},{label:"Float",value:"f"},{label:"Exponent",value:"e"},{label:"Integer",value:"d"},{label:"Percentage",value:"p"},{label:"Rounded",value:"r"},{label:"SI-prefix",value:"s"}]},x_axis_tick:{title:"Axis tick format",info:"Select the tick format for the axis.",type:"select",init:".1",data:[{label:"0.00001",value:".5"},{label:"0.0001",value:".4"},{label:"0.001",value:".3"},{label:"0.01",value:".2"},{label:"0.1",value:".1"},{label:"1",value:"1"}]},separator_tick:{title:"Y axis",type:"separator"},y_axis_label:{title:"Axis label",info:"Provide a label for the axis.",type:"text",init:"Y-axis",placeholder:"Axis label"},y_axis_type:{title:"Axis value type",info:"Select the value type of the axis.",type:"select",init:"f",data:[{label:"-- Do not show values --",value:"hide"},{label:"Float",value:"f"},{label:"Exponent",value:"e"},{label:"Integer",value:"d"},{label:"Percentage",value:"p"},{label:"Rounded",value:"r"},{label:"SI-prefix",value:"s"}]},y_axis_tick:{title:"Axis tick format",info:"Select the tick format for the axis.",type:"select",init:".1",data:[{label:"0.00001",value:".5"},{label:"0.0001",value:".4"},{label:"0.001",value:".3"},{label:"0.01",value:".2"},{label:"0.1",value:".1"},{label:"1",value:"1"}]},separator_legend:{title:"Others",type:"separator"},show_legend:{title:"Show legend",info:"Would you like to add a legend?",type:"select",init:"true",data:[{label:"Yes",value:"true"},{label:"No",value:"false"}]}}}}),define("plugin/charts/bardiagram/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Bar diagram"})}),define("plugin/charts/boxplot/config",[],function(){return $.extend(!0,{},{title:"Boxplot",columns:{e:{title:"Values for y-axis"},r:{title:"Values for y-axis"},s:{title:"Values for y-axis"}}})}),define("plugin/charts/histogram/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Histogram",mode:"execute",columns:{y:{title:"Observations"}},settings:{x_axis_label:{init:"Breaks"},y_axis_label:{init:"Density"},y_axis_tick:{init:".3"},separator_custom:{title:"Advanced",type:"separator"},bin_size:{title:"Number of bins",info:"Provide the number of histogram bins. The parsed data will be evenly distributed into bins according to the minimum and maximum values of the dataset.",type:"slider",init:10,min:10,max:1e3}}})}),define("plugin/charts/horizontal/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Bar diagram (horizontal)",settings:{x_axis_type:{init:"hide"}}})}),define("plugin/charts/line/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Line chart"})}),define("plugin/charts/linewithfocus/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Line with focus"})}),define("plugin/charts/piechart/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Pie chart"})}),define("plugin/charts/scatterplot/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Scatter plot",columns:{x:{title:"Values for x-axis"}}})}),define("plugin/charts/stackedarea/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Stacked area"})}),define("plugin/charts/types",["plugin/charts/bardiagram/config","plugin/charts/boxplot/config","plugin/charts/histogram/config","plugin/charts/horizontal/config","plugin/charts/line/config","plugin/charts/linewithfocus/config","plugin/charts/piechart/config","plugin/charts/scatterplot/config","plugin/charts/stackedarea/config"],function(e,t,n,r,i,s,o,u,a){return Backbone.Model.extend({defaults:{bardiagram:e,boxplot:t,horizontal:r,histogram:n,line:i,linewithfocus:s,piechart:o,scatterplot:u,stackedarea:a}})}),define("plugin/app",["mvc/ui/ui-modal","mvc/ui/ui-portlet","plugin/library/ui","utils/utils","plugin/library/jobs","plugin/library/datasets","plugin/views/charts","plugin/views/chart","plugin/models/config","plugin/models/chart","plugin/models/charts","plugin/charts/types"],function(e,t,n,r,i,s,o,u,a,f,l,c){return Backbone.View.extend({initialize:function(n){this.options=n,Galaxy&&Galaxy.modal?this.modal=Galaxy.modal:this.modal=new e.View,this.config=new a,this.jobs=new i(this),this.types=new c,this.chart=new f,this.charts=new l,this.datasets=new s(this),this.charts_view=new o(this),this.chart_view=new u(this),this.options.config.widget?this.portlet=$("<div></div>"):this.portlet=new t.View({icon:"fa-bar-chart-o"}),this.portlet.append(this.charts_view.$el),this.portlet.append(this.chart_view.$el),this.options.config.widget?this.setElement(this.portlet):this.setElement(this.portlet.$el),this.charts_view.$el.hide();var r=this;this.config.on("change:title",function(){r._refreshTitle()}),this.render()},render:function(){this._refreshTitle()},_refreshTitle:function(){var e=this.config.get("title");e&&(e=" - "+e),this.portlet.title("Charts"+e)},execute:function(e){},onunload:function(){},log:function(e,t){console.log(e+" "+t)}})});
\ No newline at end of file
+(function(){var e=this,t=e._,n={},r=Array.prototype,i=Object.prototype,s=Function.prototype,o=r.push,u=r.slice,a=r.concat,f=i.toString,l=i.hasOwnProperty,c=r.forEach,h=r.map,p=r.reduce,d=r.reduceRight,v=r.filter,m=r.every,g=r.some,y=r.indexOf,b=r.lastIndexOf,w=Array.isArray,E=Object.keys,S=s.bind,x=function(e){if(e instanceof x)return e;if(!(this instanceof x))return new x(e);this._wrapped=e};typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(exports=module.exports=x),exports._=x):e._=x,x.VERSION="1.4.4";var T=x.each=x.forEach=function(e,t,r){if(e==null)return;if(c&&e.forEach===c)e.forEach(t,r);else if(e.length===+e.length){for(var i=0,s=e.length;i<s;i++)if(t.call(r,e[i],i,e)===n)return}else for(var o in e)if(x.has(e,o)&&t.call(r,e[o],o,e)===n)return};x.map=x.collect=function(e,t,n){var r=[];return e==null?r:h&&e.map===h?e.map(t,n):(T(e,function(e,i,s){r.push(t.call(n,e,i,s))}),r)};var N="Reduce of empty array with no initial value";x.reduce=x.foldl=x.inject=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(p&&e.reduce===p)return r&&(t=x.bind(t,r)),i?e.reduce(t,n):e.reduce(t);T(e,function(e,s,o){i?n=t.call(r,n,e,s,o):(n=e,i=!0)});if(!i)throw new TypeError(N);return n},x.reduceRight=x.foldr=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(d&&e.reduceRight===d)return r&&(t=x.bind(t,r)),i?e.reduceRight(t,n):e.reduceRight(t);var s=e.length;if(s!==+s){var o=x.keys(e);s=o.length}T(e,function(u,a,f){a=o?o[--s]:--s,i?n=t.call(r,n,e[a],a,f):(n=e[a],i=!0)});if(!i)throw new TypeError(N);return n},x.find=x.detect=function(e,t,n){var r;return C(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},x.filter=x.select=function(e,t,n){var r=[];return e==null?r:v&&e.filter===v?e.filter(t,n):(T(e,function(e,i,s){t.call(n,e,i,s)&&r.push(e)}),r)},x.reject=function(e,t,n){return x.filter(e,function(e,r,i){return!t.call(n,e,r,i)},n)},x.every=x.all=function(e,t,r){t||(t=x.identity);var i=!0;return e==null?i:m&&e.every===m?e.every(t,r):(T(e,function(e,s,o){if(!(i=i&&t.call(r,e,s,o)))return n}),!!i)};var C=x.some=x.any=function(e,t,r){t||(t=x.identity);var i=!1;return e==null?i:g&&e.some===g?e.some(t,r):(T(e,function(e,s,o){if(i||(i=t.call(r,e,s,o)))return n}),!!i)};x.contains=x.include=function(e,t){return e==null?!1:y&&e.indexOf===y?e.indexOf(t)!=-1:C(e,function(e){return e===t})},x.invoke=function(e,t){var n=u.call(arguments,2),r=x.isFunction(t);return x.map(e,function(e){return(r?t:e[t]).apply(e,n)})},x.pluck=function(e,t){return x.map(e,function(e){return e[t]})},x.where=function(e,t,n){return x.isEmpty(t)?n?void 0:[]:x[n?"find":"filter"](e,function(e){for(var n in t)if(t[n]!==e[n])return!1;return!0})},x.findWhere=function(e,t){return x.where(e,t,!0)},x.max=function(e,t,n){if(!t&&x.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.max.apply(Math,e);if(!t&&x.isEmpty(e))return-Infinity;var r={computed:-Infinity,value:-Infinity};return T(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o>=r.computed&&(r={value:e,computed:o})}),r.value},x.min=function(e,t,n){if(!t&&x.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.min.apply(Math,e);if(!t&&x.isEmpty(e))return Infinity;var r={computed:Infinity,value:Infinity};return T(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o<r.computed&&(r={value:e,computed:o})}),r.value},x.shuffle=function(e){var t,n=0,r=[];return T(e,function(e){t=x.random(n++),r[n-1]=r[t],r[t]=e}),r};var k=function(e){return x.isFunction(e)?e:function(t){return t[e]}};x.sortBy=function(e,t,n){var r=k(t);return x.pluck(x.map(e,function(e,t,i){return{value:e,index:t,criteria:r.call(n,e,t,i)}}).sort(function(e,t){var n=e.criteria,r=t.criteria;if(n!==r){if(n>r||n===void 0)return 1;if(n<r||r===void 0)return-1}return e.index<t.index?-1:1}),"value")};var L=function(e,t,n,r){var i={},s=k(t==null?x.identity:t);return T(e,function(t,o){var u=s.call(n,t,o,e);r(i,u,t)}),i};x.groupBy=function(e,t,n){return L(e,t,n,function(e,t,n){(x.has(e,t)?e[t]:e[t]=[]).push(n)})},x.countBy=function(e,t,n){return L(e,t,n,function(e,t){x.has(e,t)||(e[t]=0),e[t]++})},x.sortedIndex=function(e,t,n,r){n=n==null?x.identity:k(n);var i=n.call(r,t),s=0,o=e.length;while(s<o){var u=s+o>>>1;n.call(r,e[u])<i?s=u+1:o=u}return s},x.toArray=function(e){return e?x.isArray(e)?u.call(e):e.length===+e.length?x.map(e,x.identity):x.values(e):[]},x.size=function(e){return e==null?0:e.length===+e.length?e.length:x.keys(e).length},x.first=x.head=x.take=function(e,t,n){return e==null?void 0:t!=null&&!n?u.call(e,0,t):e[0]},x.initial=function(e,t,n){return u.call(e,0,e.length-(t==null||n?1:t))},x.last=function(e,t,n){return e==null?void 0:t!=null&&!n?u.call(e,Math.max(e.length-t,0)):e[e.length-1]},x.rest=x.tail=x.drop=function(e,t,n){return u.call(e,t==null||n?1:t)},x.compact=function(e){return x.filter(e,x.identity)};var A=function(e,t,n){return T(e,function(e){x.isArray(e)?t?o.apply(n,e):A(e,t,n):n.push(e)}),n};x.flatten=function(e,t){return A(e,t,[])},x.without=function(e){return x.difference(e,u.call(arguments,1))},x.uniq=x.unique=function(e,t,n,r){x.isFunction(t)&&(r=n,n=t,t=!1);var i=n?x.map(e,n,r):e,s=[],o=[];return T(i,function(n,r){if(t?!r||o[o.length-1]!==n:!x.contains(o,n))o.push(n),s.push(e[r])}),s},x.union=function(){return x.uniq(a.apply(r,arguments))},x.intersection=function(e){var t=u.call(arguments,1);return x.filter(x.uniq(e),function(e){return x.every(t,function(t){return x.indexOf(t,e)>=0})})},x.difference=function(e){var t=a.apply(r,u.call(arguments,1));return x.filter(e,function(e){return!x.contains(t,e)})},x.zip=function(){var e=u.call(arguments),t=x.max(x.pluck(e,"length")),n=new Array(t);for(var r=0;r<t;r++)n[r]=x.pluck(e,""+r);return n},x.unzip=function(e){var t=[];return x.each(e,function(e,n){x.each(e,function(e,r){t.length<=r&&(t[r]=[]),t[r][n]=e})}),t},x.object=function(e,t){if(e==null)return{};var n={};for(var r=0,i=e.length;r<i;r++)t?n[e[r]]=t[r]:n[e[r][0]]=e[r][1];return n},x.indexOf=function(e,t,n){if(e==null)return-1;var r=0,i=e.length;if(n){if(typeof n!="number")return r=x.sortedIndex(e,t),e[r]===t?r:-1;r=n<0?Math.max(0,i+n):n}if(y&&e.indexOf===y)return e.indexOf(t,n);for(;r<i;r++)if(e[r]===t)return r;return-1},x.lastIndexOf=function(e,t,n){if(e==null)return-1;var r=n!=null;if(b&&e.lastIndexOf===b)return r?e.lastIndexOf(t,n):e.lastIndexOf(t);var i=r?n:e.length;while(i--)if(e[i]===t)return i;return-1},x.range=function(e,t,n){arguments.length<=1&&(t=e||0,e=0),n=arguments[2]||1;var r=Math.max(Math.ceil((t-e)/n),0),i=0,s=new Array(r);while(i<r)s[i++]=e,e+=n;return s};var O=function(){};x.bind=function(e,t){var n,r;if(e.bind===S&&S)return S.apply(e,u.call(arguments,1));if(!x.isFunction(e))throw new TypeError;return n=u.call(arguments,2),r=function(){if(this instanceof r){O.prototype=e.prototype;var i=new O;O.prototype=null;var s=e.apply(i,n.concat(u.call(arguments)));return Object(s)===s?s:i}return e.apply(t,n.concat(u.call(arguments)))}},x.partial=function(e){var t=u.call(arguments,1);return function(){return e.apply(this,t.concat(u.call(arguments)))}},x.bindAll=function(e){var t=u.call(arguments,1);if(t.length===0)throw new Error("bindAll must be passed function names");return T(t,function(t){e[t]=x.bind(e[t],e)}),e},x.memoize=function(e,t){var n={};return t||(t=x.identity),function(){var r=t.apply(this,arguments);return x.has(n,r)?n[r]:n[r]=e.apply(this,arguments)}},x.delay=function(e,t){var n=u.call(arguments,2);return setTimeout(function(){return e.apply(null,n)},t)},x.defer=function(e){return x.delay.apply(x,[e,1].concat(u.call(arguments,1)))},x.throttle=function(e,t,n){var r,i,s,o,u=0,a=function(){u=new Date,s=null,o=e.apply(r,i)};return function(){var f=new Date;!u&&n===!1&&(u=f);var l=t-(f-u);return r=this,i=arguments,l<=0?(clearTimeout(s),s=null,u=f,o=e.apply(r,i)):s||(s=setTimeout(a,l)),o}},x.debounce=function(e,t,n){var r,i;return function(){var s=this,o=arguments,u=function(){r=null,n||(i=e.apply(s,o))},a=n&&!r;return clearTimeout(r),r=setTimeout(u,t),a&&(i=e.apply(s,o)),i}},x.once=function(e){var t=!1,n;return function(){return t?n:(t=!0,n=e.apply(this,arguments),e=null,n)}},x.wrap=function(e,t){return function(){var n=[e];return o.apply(n,arguments),t.apply(this,n)}},x.compose=function(){var e=arguments;return function(){var t=arguments;for(var n=e.length-1;n>=0;n--)t=[e[n].apply(this,t)];return t[0]}},x.after=function(e,t){return e<=0?t():function(){if(--e<1)return t.apply(this,arguments)}},x.keys=E||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)x.has(e,n)&&t.push(n);return t},x.values=function(e){var t=[];for(var n in e)x.has(e,n)&&t.push(e[n]);return t},x.pairs=function(e){var t=[];for(var n in e)x.has(e,n)&&t.push([n,e[n]]);return t},x.invert=function(e){var t={};for(var n in e)x.has(e,n)&&(t[e[n]]=n);return t},x.functions=x.methods=function(e){var t=[];for(var n in e)x.isFunction(e[n])&&t.push(n);return t.sort()},x.extend=function(e){return T(u.call(arguments,1),function(t){if(t)for(var n in t)e[n]=t[n]}),e},x.pick=function(e){var t={},n=a.apply(r,u.call(arguments,1));return T(n,function(n){n in e&&(t[n]=e[n])}),t},x.omit=function(e){var t={},n=a.apply(r,u.call(arguments,1));for(var i in e)x.contains(n,i)||(t[i]=e[i]);return t},x.defaults=function(e){return T(u.call(arguments,1),function(t){if(t)for(var n in t)e[n]===void 0&&(e[n]=t[n])}),e},x.clone=function(e){return x.isObject(e)?x.isArray(e)?e.slice():x.extend({},e):e},x.tap=function(e,t){return t(e),e};var M=function(e,t,n,r){if(e===t)return e!==0||1/e==1/t;if(e==null||t==null)return e===t;e instanceof x&&(e=e._wrapped),t instanceof x&&(t=t._wrapped);var i=f.call(e);if(i!=f.call(t))return!1;switch(i){case"[object String]":return e==String(t);case"[object Number]":return e!=+e?t!=+t:e==0?1/e==1/t:e==+t;case"[object Date]":case"[object Boolean]":return+e==+t;case"[object RegExp]":return e.source==t.source&&e.global==t.global&&e.multiline==t.multiline&&e.ignoreCase==t.ignoreCase}if(typeof e!="object"||typeof t!="object")return!1;var s=n.length;while(s--)if(n[s]==e)return r[s]==t;n.push(e),r.push(t);var o=0,u=!0;if(i=="[object Array]"){o=e.length,u=o==t.length;if(u)while(o--)if(!(u=M(e[o],t[o],n,r)))break}else{var a=e.constructor,l=t.constructor;if(a!==l&&!(x.isFunction(a)&&a instanceof a&&x.isFunction(l)&&l instanceof l))return!1;for(var c in e)if(x.has(e,c)){o++;if(!(u=x.has(t,c)&&M(e[c],t[c],n,r)))break}if(u){for(c in t)if(x.has(t,c)&&!(o--))break;u=!o}}return n.pop(),r.pop(),u};x.isEqual=function(e,t){return M(e,t,[],[])},x.isEmpty=function(e){if(e==null)return!0;if(x.isArray(e)||x.isString(e))return e.length===0;for(var t in e)if(x.has(e,t))return!1;return!0},x.isElement=function(e){return!!e&&e.nodeType===1},x.isArray=w||function(e){return f.call(e)=="[object Array]"},x.isObject=function(e){return e===Object(e)},T(["Arguments","Function","String","Number","Date","RegExp"],function(e){x["is"+e]=function(t){return f.call(t)=="[object "+e+"]"}}),x.isArguments(arguments)||(x.isArguments=function(e){return!!e&&!!x.has(e,"callee")}),typeof /./!="function"&&(x.isFunction=function(e){return typeof e=="function"}),x.isFinite=function(e){return isFinite(e)&&!isNaN(parseFloat(e))},x.isNaN=function(e){return x.isNumber(e)&&e!=+e},x.isBoolean=function(e){return e===!0||e===!1||f.call(e)=="[object Boolean]"},x.isNull=function(e){return e===null},x.isUndefined=function(e){return e===void 0},x.has=function(e,t){return l.call(e,t)},x.noConflict=function(){return e._=t,this},x.identity=function(e){return e},x.times=function(e,t,n){var r=Array(e);for(var i=0;i<e;i++)r[i]=t.call(n,i);return r},x.random=function(e,t){return t==null&&(t=e,e=0),e+Math.floor(Math.random()*(t-e+1))};var _={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};_.unescape=x.invert(_.escape);var D={escape:new RegExp("["+x.keys(_.escape).join("")+"]","g"),unescape:new RegExp("("+x.keys(_.unescape).join("|")+")","g")};x.each(["escape","unescape"],function(e){x[e]=function(t){return t==null?"":(""+t).replace(D[e],function(t){return _[e][t]})}}),x.result=function(e,t){if(e==null)return void 0;var n=e[t];return x.isFunction(n)?n.call(e):n},x.mixin=function(e){T(x.functions(e),function(t){var n=x[t]=e[t];x.prototype[t]=function(){var e=[this._wrapped];return o.apply(e,arguments),F.call(this,n.apply(x,e))}})};var P=0;x.uniqueId=function(e){var t=++P+"";return e?e+t:t},x.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var H=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},j=/\\|'|\r|\n|\t|\u2028|\u2029/g;x.template=function(e,t,n){var r;n=x.defaults({},n,x.templateSettings);var i=new RegExp([(n.escape||H).source,(n.interpolate||H).source,(n.evaluate||H).source].join("|")+"|$","g"),s=0,o="__p+='";e.replace(i,function(t,n,r,i,u){return o+=e.slice(s,u).replace(j,function(e){return"\\"+B[e]}),n&&(o+="'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'"),r&&(o+="'+\n((__t=("+r+"))==null?'':__t)+\n'"),i&&(o+="';\n"+i+"\n__p+='"),s=u+t.length,t}),o+="';\n",n.variable||(o="with(obj||{}){\n"+o+"}\n"),o="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+o+"return __p;\n";try{r=new Function(n.variable||"obj","_",o)}catch(u){throw u.source=o,u}if(t)return r(t,x);var a=function(e){return r.call(this,e,x)};return a.source="function("+(n.variable||"obj")+"){\n"+o+"}",a},x.chain=function(e){return x(e).chain()};var F=function(e){return this._chain?x(e).chain():e};x.mixin(x),T(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];x.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),(e=="shift"||e=="splice")&&n.length===0&&delete n[0],F.call(this,n)}}),T(["concat","join","slice"],function(e){var t=r[e];x.prototype[e]=function(){return F.call(this,t.apply(this._wrapped,arguments))}}),x.extend(x.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this),define("libs/underscore",function(e){return function(){var t,n;return t||e._}}(this)),define("utils/utils",["libs/underscore"],function(e){function t(e,t,r){n("GET",e,{},t,r)}function n(e,t,n,r,i){if(e=="GET"||e=="DELETE")t.indexOf("?")==-1?t+="?":t+="&",t+=$.param(n);var s=new XMLHttpRequest;s.open(e,t,!0),s.setRequestHeader("Accept","application/json"),s.setRequestHeader("Cache-Control","no-cache"),s.setRequestHeader("X-Requested-With","XMLHttpRequest"),s.setRequestHeader("Content-Type","application/json"),s.onloadend=function(){var e=s.status;if(e==200){try{response=jQuery.parseJSON(s.responseText)}catch(t){response=s.responseText}r&&r(response)}else i&&i(e)},e=="GET"||e=="DELETE"?s.send():s.send(JSON.stringify(n))}function r(e,t){var n=$('<div class="'+e+'"></div>');n.appendTo(":eq(0)");var r=n.css(t);return n.remove(),r}function i(e){$('link[href^="'+e+'"]').length||$('<link href="'+galaxy_config.root+e+'" rel="stylesheet">').appendTo("head")}function s(t,n){return t?e.defaults(t,n):n}function o(e,t){var n="";if(e>=1e11)e/=1e11,n="TB";else if(e>=1e8)e/=1e8,n="GB";else if(e>=1e5)e/=1e5,n="MB";else if(e>=100)e/=100,n="KB";else{if(!(e>0))return"<strong>-</strong>";e*=10,n="b"}var r=Math.round(e)/10;return t?r+" "+n:"<strong>"+r+"</strong> "+n}function u(){return(new Date).getTime().toString(36)}function a(e){var t=$("<p></p>");return t.append(e),t}function f(){var e=new Date,t=(e.getHours()<10?"0":"")+e.getHours(),n=(e.getMinutes()<10?"0":"")+e.getMinutes(),r=e.getDate()+"/"+(e.getMonth()+1)+"/"+e.getFullYear()+", "+t+":"+n;return r}return{cssLoadFile:i,cssGetAttribute:r,get:t,merge:s,bytesToString:o,uuid:u,time:f,wrap:a,request:n}}),define("mvc/ui/ui-modal",["utils/utils"],function(e){var t=Backbone.View.extend({elMain:"body",optionsDefault:{title:"ui-modal",body:"",backdrop:!0,height:null,width:null,closing_events:!1},buttonList:{},initialize:function(e){e&&this._create(e)},show:function(e){this.initialize(e),this.options.height?(this.$body.css("height",this.options.height),this.$body.css("overflow","hidden")):this.$body.css("max-height",$(window).height()/2),this.options.width&&this.$dialog.css("width",this.options.width),this.visible?this.$el.show():this.$el.fadeIn("fast"),this.visible=!0},hide:function(){this.visible=!1,this.$el.fadeOut("fast")},enableButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).prop("disabled",!1)},disableButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).prop("disabled",!0)},showButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).show()},hideButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).hide()},getButton:function(e){var t=this.buttonList[e];return this.$buttons.find("#"+t)},scrollTop:function(){return this.$body.scrollTop()},_create:function(e){var t=this;this.options=_.defaults(e,this.optionsDefault),this.options.body=="progress"&&(this.options.body=$('<div class="progress progress-striped active"><div class="progress-bar progress-bar-info" style="width:100%"></div></div>')),this.$el&&(this.$el.remove(),$(document).off("keyup")),this.setElement(this._template(this.options.title)),this.$dialog=this.$el.find(".modal-dialog"),this.$body=this.$el.find(".modal-body"),this.$footer=this.$el.find(".modal-footer"),this.$buttons=this.$el.find(".buttons"),this.$backdrop=this.$el.find(".modal-backdrop"),this.$body.html(this.options.body),this.options.backdrop||this.$backdrop.removeClass("in");if(this.options.buttons){this.buttonList={};var n=0;$.each(this.options.buttons,function(e,r){var i="button-"+n++;t.$buttons.append($('<button id="'+i+'"></button>').text(e).click(r)).append(" "),t.buttonList[e]=i})}else this.$footer.hide();$(this.elMain).append($(this.el)),this.options.closing_events&&(this.options.buttons.Pause||$(document).on("keyup",function(e){e.keyCode==27&&t.hide()}),this.$el.find(".modal-backdrop").on("click",function(){t.hide()}))},_template:function(e){return'<div class="modal"><div class="modal-backdrop fade in" style="z-index: -1;"></div><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><button type="button" class="close" style="display: none;">×</button><h4 class="title">'+e+"</h4>"+"</div>"+'<div class="modal-body" style="position: static;"></div>'+'<div class="modal-footer">'+'<div class="buttons" style="float: right;"></div>'+"</div>"+"</div"+"</div>"+"</div>"}});return{View:t}}),define("mvc/ui/ui-portlet",["utils/utils"],function(e){var t=Backbone.View.extend({visible:!1,optionsDefault:{title:"",icon:"",buttons:null,body:null,height:null,operations:null,placement:"bottom",overflow:"auto"},$title:null,$content:null,$buttons:null,$operations:null,initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.$content=this.$el.find("#content"),this.$title=this.$el.find("#title-text"),this.options.height&&(this.$el.find("#body").css("height",this.options.height),this.$el.find("#content").css("overflow",this.options.overflow)),this.$buttons=$(this.el).find("#buttons");if(this.options.buttons){var n=this;$.each(this.options.buttons,function(e,t){t.$el.prop("id",e),n.$buttons.append(t.$el)})}else this.$buttons.remove();this.$operations=$(this.el).find("#operations");if(this.options.operations){var n=this;$.each(this.options.operations,function(e,t){t.$el.prop("id",e),n.$operations.append(t.$el)})}this.options.body&&this.append(this.options.body)},append:function(t){this.$content.append(e.wrap(t))},content:function(){return this.$content},show:function(){this.$el.fadeIn("fast"),this.visible=!0},hide:function(){this.$el.fadeOut("fast"),this.visible=!1},enableButton:function(e){this.$buttons.find("#"+e).prop("disabled",!1)},disableButton:function(e){this.$buttons.find("#"+e).prop("disabled",!0)},hideOperation:function(e){this.$operations.find("#"+e).hide()},showOperation:function(e){this.$operations.find("#"+e).show()},setOperation:function(e,t){var n=this.$operations.find("#"+e);n.off("click"),n.on("click",t)},title:function(e){var t=this.$title;return e&&t.html(e),t.html()},_template:function(e){var t='<div class="toolForm">';if(e.title||e.icon)t+='<div id="title" class="toolFormTitle" style="overflow: hidden;"><div id="operations" style="float: right;"></div><div style="overflow: hidden;">',e.icon&&(t+='<i style="padding-top: 3px; float: left; font-size: 1.2em" class="icon fa '+e.icon+'"> </i>'),t+='<div id="title-text" style="padding-top: 2px; float: left;">'+e.title+"</div>",t+="</div></div>";return t+='<div id="body" class="toolFormBody">',e.placement=="top"&&(t+='<div id="buttons" class="buttons" style="height: 50px; padding: 10px;"></div>'),t+='<div id="content" class="content" style="height: inherit; padding: 10px;"></div>',e.placement=="bottom"&&(t+='<div id="buttons" class="buttons" style="height: 50px; padding: 10px;"></div>'),t+="</div></div>",t}});return{View:t}}),define("plugin/library/ui-select",["utils/utils"],function(e){var t=Backbone.View.extend({optionsDefault:{id:"",cls:"",empty:"No data available",visible:!0,wait:!1},selected:null,initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.$select=this.$el.find("#select"),this.$icon=this.$el.find("#icon"),this.selected=this.options.value;var n=this;this.options.onchange&&this.$select.on("change",function(){n.value(n.$select.val())}),this._refresh(),this.options.visible||this.hide(),this.options.wait?this.wait():this.show()},value:function(e){var t=this.selected;e!==undefined&&(this.selected=e,this.$select.val(e));var n=this.selected;return n&&n!=t&&this.options.onchange&&this.options.onchange(n),n},text:function(){return this.$select.find("option:selected").text()},show:function(){this.$icon.removeClass(),this.$icon.addClass("fa fa-caret-down"),this.$select.show(),this.$el.show()},hide:function(){this.$el.hide()},wait:function(){this.$icon.removeClass(),this.$icon.addClass("fa fa-spinner fa-spin"),this.$select.hide()},disabled:function(){return this.$select.is(":disabled")},enable:function(){this.$select.prop("disabled",!1)},disable:function(){this.$select.prop("disabled",!0)},add:function(e){this.$select.append(this._templateOption(e)),this._refresh()},del:function(e){this.$select.find("option[value="+e+"]").remove(),this.$select.trigger("change"),this._refresh()},update:function(e){this.$select.find("option").remove();for(var t in e)this.$select.append(this._templateOption(e[t]));!this.selected&&e.length>0&&this.value(e[0].value),this._refresh()},_refresh:function(){this.$select.find("option[value=null]").remove();var e=this.$select.find("option").length;e==0?(this.$select.append(this._templateOption({value:"null",label:this.options.empty})),this.disable()):(this.enable(),this.selected&&this.$select.val(this.selected))},_exists:function(e){return 0!=this.$select.find("option[value="+e+"]").length},_templateOption:function(e){return'<option value="'+e.value+'">'+e.label+"</option>"},_template:function(e){var t='<div id="'+e.id+'" class="styled-select">'+'<div class="button">'+'<i id="icon"/>'+"</div>"+'<select id="select" class="select '+e.cls+" "+e.id+'">';for(key in e.data){var n=e.data[key],r="";if(n.value==e.value||n.value=="")r="selected";t+='<option value="'+n.value+'" '+r+">"+n.label+"</option>"}return t+="</select></div>",t}});return{View:t}}),define("plugin/library/ui",["utils/utils","plugin/library/ui-select"],function(e,t){var n=Backbone.View.extend({optionsDefault:{title:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options))},title:function(e){this.$el.find("b").html(e)},_template:function(e){return"<label><b>"+e.title+"</b></label>"},value:function(){return options.title}}),r=Backbone.View.extend({optionsDefault:{id:null,title:"","float":"right",cls:"btn-default",icon:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).on("click",t.onclick),$(this.el).tooltip({title:t.tooltip,placement:"bottom"})},_template:function(e){var t='<button id="'+e.id+'" type="submit" style="margin-right: 5px; float: '+e.float+';" type="button" class="btn '+e.cls+'">';return e.icon&&(t+='<i class="icon fa '+e.icon+'"></i> '),t+=e.title+"</button>",t}}),i=Backbone.View.extend({optionsDefault:{"float":"right",icon:"",tooltip:"",placement:"bottom"},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).tooltip({title:t.tooltip,placement:"bottom"})},_template:function(e){return'<span class="fa '+e.icon+'" style="font-size: 1.2em;"/>'}}),s=Backbone.View.extend({optionsDefault:{title:"",id:null,"float":"right",cls:"icon-btn",icon:"",tooltip:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).on("click",t.onclick),$(this.el).tooltip({title:t.tooltip,placement:"bottom"})},_template:function(e){var t="";e.title&&(t="width: auto;");var n='<div id="'+e.id+'" style="margin-right: 5px; float: '+e.float+"; "+t+'" class="'+e.cls+'">';return e.title?n+='<div style="margin-right: 5px; margin-left: 5px;"><i class="icon fa '+e.icon+'"/> '+'<span style="position: relative; font-size: 0.8em; font-weight: normal; top: -1px;">'+e.title+"</span>"+"</div>":n+='<i class="icon fa '+e.icon+'"/>',n+="</div>",n}}),o=Backbone.View.extend({optionsDefault:{title:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).on("click",t.onclick)},_template:function(e){return'<div><a href="javascript:void(0)">'+e.title+"</a></div>"}}),u=Backbone.View.extend({optionsDefault:{message:"",status:"info",persistent:!1},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement("<div></div>")},update:function(t){this.options=e.merge(t,this.optionsDefault);if(t.message!=""){this.$el.html(this._template(this.options)),this.$el.fadeIn();if(!t.persistent){var n=this;window.setTimeout(function(){n.$el.is(":visible")?n.$el.fadeOut():n.$el.hide()},3e3)}}else this.$el.fadeOut()},_template:function(e){return'<div class="alert alert-'+e.status+'" style="padding: 2px 2px 2px 10px;">'+e.message+"</div>"}}),a=Backbone.View.extend({optionsDefault:{onclick:null,searchword:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options));var n=this;this.options.onclick&&this.$el.on("submit",function(e){var t=n.$el.find("#search");n.options.onclick(t.val())})},_template:function(e){return'<div class="search"><form onsubmit="return false;"><input id="search" class="form-control input-sm" type="text" name="search" placeholder="Search..." value="'+e.searchword+'">'+'<button type="submit" class="btn search-btn">'+'<i class="fa fa-search"></i>'+"</button>"+"</form>"+"</div>"}}),f=Backbone.View.extend({optionsDefault:{title:"Unlabeled",body:null},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.options.body&&this.$el.find(".body").append(this.options.body)},_template:function(e){return'<div id="title" class="title">'+e.title+":"+"</div>"}}),l=Backbone.View.extend({optionsDefault:{id:"",title:"",target:"",href:"",onunload:null,onclick:null,visible:!0,icon:null,tag:""},$menu:null,initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement($(this._template(this.options)));var n=$(this.el).find(".root"),r=this;n.on("click",function(e){e.preventDefault(),r.options.onclick&&r.options.onclick()}),this.options.visible||this.hide()},show:function(){$(this.el).show()},hide:function(){$(this.el).hide()},addMenu:function(t){var n={title:"",target:"",href:"",onclick:null,divider:!1,icon:null};n=e.merge(t,n),this.$menu||($(this.el).append(this._templateMenu()),this.$menu=$(this.el).find(".menu"));var r=$(this._templateMenuItem(n));r.on("click",function(e){e.preventDefault(),n.onclick&&n.onclick()}),this.$menu.append(r),n.divider&&this.$menu.append($(this._templateDivider()))},_templateMenuItem:function(e){var t='<li><a href="'+e.href+'" target="'+e.target+'">';return e.icon&&(t+='<i class="fa '+e.icon+'"></i>'),t+=" "+e.title+"</a>"+"</li>",t},_templateMenu:function(){return'<ul class="menu dropdown-menu pull-right" role="menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(e){var t='<div id="'+e.id+'" class="button-menu btn-group">'+'<button type="button" class="root btn btn-default dropdown-toggle" data-toggle="dropdown">';return e.icon&&(t+='<i class="fa '+e.icon+'"></i>'),"</button></div>",t}}),c=Backbone.View.extend({optionsDefault:{value:"",type:"text",placeholder:"",disabled:!1,visible:!0},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.options.disabled&&this.$el.prop("disabled",!0),this.options.visible||this.$el.hide();var n=this;this.options.onchange&&this.$el.on("input",function(){n.options.onchange()})},value:function(e){return e!==undefined&&this.$el.val(e),this.$el.val()},_template:function(e){return'<input id="'+e.id+'" type="'+e.type+'" value="'+e.value+'" placeholder="'+e.placeholder+'" class="form-control">'}});return{Label:n,Button:r,Icon:i,ButtonIcon:s,Input:c,Anchor:o,Message:u,Searchbox:a,Title:f,Select:t,ButtonMenu:l}}),define("plugin/library/jobs",["utils/utils"],function(e){return Backbone.Model.extend({initialize:function(t,n){this.app=t,this.options=e.merge(n,this.optionsDefault)},submit:function(t,n,r){var i=this,s=t.id,o=t.get("type"),u=this.app.types.get(o);data={tool_id:"rkit",history_id:t.get("history_id"),inputs:{input:t.get("dataset_hid"),module:o,options:n}};var a=t.get("dataset_id_job");a&&e.request("PUT",config.root+"api/histories/"+t.get("history_id")+"/contents/"+a,{deleted:!0}),t.state("submit","Sending job request..."),e.request("POST",config.root+"api/tools",data,function(e){if(!e.outputs||e.outputs.length==0)t.state("failed","Job submission failed. No response.");else{Galaxy&&Galaxy.currHistoryPanel&&Galaxy.currHistoryPanel.refreshHdas();var n=e.outputs[0];t.state("queued","Job has been queued..."),t.set("dataset_id_job",n.id),i._loop(n.id,function(e){switch(e.state){case"ok":return t.state("success","Job completed successfully..."),r(e),!0;case"error":return t.state("failed","Job has failed. Please check the history for details."),!0;case"running":return t.state("running","Job is running..."),!1}})}},function(e){t.state("failed","Job submission failed. Please make sure that 'R-kit' is installed.")})},_loop:function(t,n){var r=this;e.request("GET",config.root+"api/jobs/"+t,{},function(e){n(e)||setTimeout(function(){r._loop(t,n)},r.app.config.get("query_timeout"))})}})}),define("plugin/library/datasets",["utils/utils"],function(e){return Backbone.Collection.extend({list:{},initialize:function(t,n){this.app=t,this.options=e.merge(n,this.optionsDefault)},request:function(t,n,r){var i=this;if(t.groups)this._fetch(t,n);else{var s=this.list[t.id];if(s){n(s);return}e.request("GET",config.root+"api/datasets/"+t.id,{},function(e){switch(e.state){case"error":r&&r(e);break;default:i.list[t.id]=e,n(e)}})}},_fetch:function(t,n){var r=t.start?t.start:0,i=Math.abs(t.end-t.start),s=this.app.config.get("query_limit");if(!i||i>s)i=s;var o="",u={},a=0;for(var f in t.groups){var l=t.groups[f];for(var c in l.columns){var h=l.columns[c];o+=h+",",u[h]=a,a++}}if(a==0){n({});return}o=o.substring(0,o.length-1);var p=t.groups.slice(0);for(var f in p)p[f].values=[];var d=this;e.request("GET",config.root+"api/datasets/"+t.id,{data_type:"raw_data",provider:"dataset-column",limit:i,offset:r,indeces:o},function(e){for(var i in e.data){var s=e.data[i];for(var o in t.groups){var a=t.groups[o],f={x:parseInt(i)+r};for(var l in a.columns){var c=a.columns[l],h=u[c],d=s[h];if(isNaN(d)||!d)d=0;f[l]=d}p[o].values.push(f)}}n(p)})}})}),define("plugin/library/ui-table",["utils/utils"],function(e){return Backbone.View.extend({row:null,row_count:0,optionsDefault:{content:"No content available.",onchange:null,ondblclick:null,onconfirm:null},events:{click:"_onclick",dblclick:"_ondblclick"},first:!0,initialize:function(t){this.options=e.merge(t,this.optionsDefault);var n=$(this._template(t));this.$thead=n.find("thead"),this.$tbody=n.find("tbody"),this.$tmessage=n.find("tmessage"),this.setElement(n),this.row=$("<tr></tr>")},addHeader:function(e){var t=$("<th></th>");t.append(e),this.row.append(t)},appendHeader:function(){this.$thead.append(this.row),this.row=$("<tr></tr>")},add:function(e,t){var n=$("<td></td>");t&&n.css("width",t),n.append(e),this.row.append(n)},append:function(e){this._commit(e)},prepend:function(e){this._commit(e,!0)},remove:function(e){var t=this.$tbody.find("#"+e);t.length>0&&(t.remove(),this.row_count--,this._refresh())},removeAll:function(){this.$tbody.html(""),this.row_count=0,this._refresh()},value:function(e){this.before=this.$tbody.find(".current").attr("id"),e!==undefined&&(this.$tbody.find("tr").removeClass("current"),e&&this.$tbody.find("#"+e).addClass("current"));var t=this.$tbody.find(".current").attr("id");return t===undefined?null:(t!=this.before&&this.options.onchange&&this.options.onchange(e),t)},size:function(){return this.$tbody.find("tr").length},_commit:function(e,t){this.remove(e),this.row.attr("id",e),t?this.$tbody.prepend(this.row):this.$tbody.append(this.row),this.row=$("<tr></tr>"),this.row_count++,this._refresh()},_onclick:function(e){var t=this.value(),n=$(e.target).closest("tr").attr("id");n&&t!=n&&(this.options.onconfirm?this.options.onconfirm(n):this.value(n))},_ondblclick:function(e){var t=this.value();t&&this.options.ondblclick&&this.options.ondblclick(t)},_refresh:function(){this.row_count==0?this.$tmessage.show():this.$tmessage.hide()},_template:function(e){return'<div><table class="grid"><thead></thead><tbody style="cursor: pointer;"></tbody></table><tmessage>'+e.content+"</tmessage>"+"<div>"}})}),define("plugin/models/group",[],function(){return Backbone.Model.extend({defaults:{key:"Data label",date:""},reset:function(){this.clear({silent:!0}).set(this.defaults),this.trigger("reset",this)}})}),define("plugin/views/viewport",["mvc/ui/ui-portlet","plugin/library/ui","utils/utils"],function(e,t,n){return Backbone.View.extend({list:{},optionsDefault:{height:300},initialize:function(r,i){this.app=r,this.options=n.merge(i,this.optionsDefault),this.portlet=new e.View({title:"title",height:this.options.height,overflow:"hidden",operations:{edit:new t.ButtonIcon({icon:"fa-gear",tooltip:"Customize Chart",title:"Customize"})}}),this.setElement(this.portlet.$el);var s=this;this.app.charts.on("remove",function(e){s._removeChart(e.id)}),this.app.charts.on("redraw",function(e){e.ready()?s._refreshChart(e):e.on("change:state",function(){e.ready()&&s._refreshChart(e)})})},showChart:function(e){this.show(),this.hideCharts();var t=this.list[e];if(t){var n=self.app.charts.get(e);this.portlet.title(n.get("title")),this.portlet.setOperation("edit",function(){self.app.chart.copy(n),self.app.charts_view.hide(),self.app.chart_view.$el.show()}),t.$el.show(),$(window).trigger("resize")}},hideCharts:function(){this.$el.find(".item").hide()},show:function(){$(".tooltip").hide(),this.$el.show()},hide:function(){$(".tooltip").hide(),this.$el.hide()},_refreshChart:function(e){var t=this;if(!e.ready()){t.app.log("viewport:_refreshChart()","Invalid attempt to refresh chart before completion.");return}var n=e.id;this._removeChart(n);var r="#"+n,i=$(this._template({id:r}));this.portlet.append(i);var s=d3.select(r+" svg");this.list[n]={svg:s,$el:i},this.showChart(n),e.off("change:state"),e.on("change:state",function(){var t=i.find("#info"),n=t.find("#icon");n.removeClass(),t.show(),t.find("#text").html(e.get("state_info"));var r=e.get("state");switch(r){case"ok":t.hide();break;case"failed":n.addClass("fa fa-warning");break;default:n.addClass("fa fa-spinner fa-spin")}}),e.state("wait","Please wait...");var o=e.get("type"),u=this.app.types.get(o),t=this;require(["plugin/charts/"+o+"/"+o],function(n){var r=new n(t.app,{svg:s}),i=u.mode;i=="execute"?t.app.jobs.submit(e,t._defaultRequestString(e),function(){r.draw(e,t._defaultRequestDictionary(e))}):r.draw(e,t._defaultRequestDictionary(e))})},_removeChart:function(e){var t=this.list[e];t&&(t.svg.remove(),t.$el.remove())},_template:function(e){return'<div id="'+e.id.substr(1)+'" class="item">'+'<span id="info">'+'<span id="icon" style="font-size: 1.2em; display: inline-block;"/>'+'<span id="text" style="position: relative; margin-left: 5px; top: -1px; font-size: 1.0em;"/>'+"</span>"+'<svg style="height: auto;"/>'+"</div>"},_defaultRequestString:function(e){var t=this.app.types.get(e.get("type")),n="",r=0;return e.groups.each(function(e){for(var i in t.columns)n+=i+"_"+ ++r+":"+(parseInt(e.get(i))+1)+", "}),n.substring(0,n.length-2)},_defaultRequestDictionary:function(e){var t=this.app.types.get(e.get("type")),n={id:e.get("dataset_id"),groups:[]},r=0;return e.groups.each(function(e){var i={};for(var s in t.columns)i[s]=e.get(s);n.groups.push({key:++r+":"+e.get("key"),columns:i})}),n}})}),define("plugin/views/charts",["mvc/ui/ui-portlet","plugin/library/ui-table","plugin/library/ui","utils/utils","plugin/models/group","plugin/views/viewport"],function(e,t,n,r,i,s){return Backbone.View.extend({initialize:function(i,o){this.app=i,this.viewport_view=new s(i),this.table=new t({content:"Add charts to this table.",ondblclick:function(e){var t=u.app.charts.get(e);u.app.chart.copy(t),u.hide(),u.app.chart_view.$el.show()},onchange:function(e){var t=u.app.charts.get(e);u.app.config.set("title",t.get("title")),u.viewport_view.showChart(e)}});var u=this;this.portlet=new e.View({icon:"fa-list",title:"List of created charts:",height:100,operations:{"new":new n.ButtonIcon({icon:"fa-magic",tooltip:"Create a new Chart",title:"New",onclick:function(){u.hide(),u.app.chart.reset(),u.app.chart_view.$el.show()}}),"delete":new n.ButtonIcon({icon:"fa-trash-o",tooltip:"Delete this Chart",title:"Delete",onclick:function(){var e=u.table.value();if(!e)return;var t=u.app.charts.get(e);u.app.modal.show({title:"Are you sure?",body:'The selected chart "'+t.get("title")+'" will be irreversibly deleted.',buttons:{Cancel:function(){u.app.modal.hide()},Delete:function(){u.app.modal.hide(),u.app.charts.remove(e)}}})}})}}),this.portlet.append(this.table.$el),this.app.options.config.widget||this.$el.append(this.portlet.$el),this.$el.append(r.wrap("")),this.$el.append(this.viewport_view.$el);var u=this;this.app.charts.on("add",function(e){u._addChart(e)}),this.app.charts.on("remove",function(e){u._removeChart(e)}),this.app.charts.on("change",function(e){u._changeChart(e)})},hide:function(){$(".tooltip").hide(),this.$el.hide()},_addChart:function(e){var t=e.get("title");t==""&&(t="Untitled"),this.table.add(t);var n=this.app.types.get(e.get("type"));this.table.add(n.title),this.table.add("Last change: "+e.get("date")),this.table.prepend(e.get("id")),this.table.value(e.get("id"))},_removeChart:function(e){this.table.remove(e.id),this.table.size()==0?(this.hide(),this.app.chart.reset(),this.app.chart_view.$el.show()):this.table.value(this.app.charts.last().id)},_changeChart:function(e){e.get("type")&&(this._addChart(e),this.table.value(e.id))}})}),define("mvc/ui/ui-tabs",["utils/utils"],function(e){var t=Backbone.View.extend({visible:!1,list:{},$nav:null,$content:null,first_tab:null,optionsDefault:{title_new:"",operations:null,onnew:null},initialize:function(t){this.options=e.merge(t,this.optionsDefault);var n=$(this._template(this.options));this.$nav=n.find(".tab-navigation"),this.$content=n.find(".tab-content"),this.setElement(n),this.list={};var r=this;this.options.operations&&$.each(this.options.operations,function(e,t){t.$el.prop("id",e),r.$nav.append(t.$el)});if(this.options.onnew){var i=$(this._template_tab_new(this.options));this.$nav.append(i),i.tooltip({title:"Add a new tab",placement:"bottom"}),i.on("click",function(e){i.tooltip("hide"),r.options.onnew()})}},add:function(e){var t=e.id,n={$title:$(this._template_tab(e)),$content:$(this._template_tab_content(e)),removable:e.ondel?!0:!1};this.list[t]=n,this.options.onnew?this.$nav.find("#new-tab").before(n.$title):this.$nav.append(n.$title),n.$content.append(e.$el),this.$content.append(n.$content),_.size(this.list)==1&&(n.$title.addClass("active"),n.$content.addClass("active"),this.first_tab=t);if(e.ondel){var r=n.$title.find("#delete");r.tooltip({title:"Delete this tab",placement:"bottom"}),r.on("click",function(){return r.tooltip("destroy"),e.ondel(),!1})}e.onclick&&n.$title.on("click",function(){e.onclick()})},del:function(e){var t=this.list[e];t.$title.remove(),t.$content.remove(),delete t,this.first_tab==e&&(this.first_tab=null),this.first_tab!=null&&this.show(this.first_tab)},delRemovable:function(){for(var e in this.list){var t=this.list[e];t.removable&&this.del(e)}},show:function(e){this.$el.fadeIn("fast"),this.visible=!0,e&&this.list[e].$title.find("a").tab("show")},hide:function(){this.$el.fadeOut("fast"),this.visible=!1},hideOperation:function(e){this.$nav.find("#"+e).hide()},showOperation:function(e){this.$nav.find("#"+e).show()},setOperation:function(e,t){var n=this.$nav.find("#"+e);n.off("click"),n.on("click",t)},title:function(e,t){var n=this.list[e].$title.find("#text");return t&&n.html(t),n.html()},_template:function(e){return'<div class="tabbable tabs-left"><ul class="tab-navigation nav nav-tabs"/><div class="tab-content"/></div>'},_template_tab_new:function(e){return'<li id="new-tab"><a href="javascript:void(0);"><i style="font-size: 0.8em; margin-right: 5px;" class="fa fa-plus-circle"/>'+e.title_new+"</a>"+"</li>"},_template_tab:function(e){var t='<li id="title-'+e.id+'">'+'<a title="" href="#tab-'+e.id+'" data-toggle="tab" data-original-title="">'+'<span id="text">'+e.title+"</span>";return e.ondel&&(t+='<i id="delete" style="font-size: 0.8em; margin-left: 5px; cursor: pointer;" class="fa fa-minus-circle"/>'),t+="</a></li>",t},_template_tab_content:function(e){return'<div id="tab-'+e.id+'" class="tab-pane"/>'}});return{View:t}}),define("plugin/models/groups",["plugin/models/group"],function(e){return Backbone.Collection.extend({model:e})}),define("plugin/models/chart",["plugin/models/groups"],function(e){return Backbone.Model.extend({defaults:{id:null,title:"",type:"",date:null,state:"ok",state_info:""},initialize:function(t){this.groups=new e,this.settings=new Backbone.Model},reset:function(){this.clear({silent:!0}).set(this.defaults),this.groups.reset(),this.settings.clear(),this.trigger("reset",this)},copy:function(e){var t=this;t.clear({silent:!0}).set(this.defaults),t.set(e.attributes),t.settings=e.settings.clone(),t.groups.reset(),e.groups.each(function(e){t.groups.add(e.clone())}),t.trigger("change",t)},state:function(e,t){this.set("state_info",t),this.set("state",e)},ready:function(){return this.get("state")=="ok"||this.get("state")=="failed"}})}),define("plugin/views/group",["plugin/library/ui-table","plugin/library/ui","utils/utils"],function(e,t,n){return Backbone.View.extend({columns:[],initialize:function(r,i){this.app=r;var s=this;this.chart=this.app.chart,this.group=i.group,this.group_key=new t.Input({placeholder:"Data label",onchange:function(){s.group.set("key",s.group_key.value())}}),this.table=new e({content:"No data column."});var o=$("<div/>");o.append(n.wrap((new t.Label({title:"Provide a label:"})).$el)),o.append(n.wrap(this.group_key.$el)),o.append(n.wrap((new t.Label({title:"Select columns:"})).$el)),o.append(n.wrap(this.table.$el)),this.setElement(o);var s=this;this.chart.on("change:dataset_id",function(){s._refreshTable()}),this.chart.on("change:type",function(){s._refreshTable()}),this.group.on("change:key",function(){s._refreshGroupKey()}),this.group.on("change",function(){s._refreshGroup()}),this._refreshTable(),this._refreshGroupKey(),this._refreshGroup()},_refreshTable:function(){var e=this.chart.get("dataset_id"),n=this.chart.get("type");if(!e||!n)return;var r=this,i=this.app.types.get(n);this.table.removeAll();var s={};for(var o in i.columns){var u=this.group.get(o);u||this.group.set(o,0);var a=i.columns[o],f=new t.Select.View({id:"select_"+o,gid:o,onchange:function(e){r.group.set(this.gid,e)},value:u,wait:!0});this.table.add(a.title,"25%"),this.table.add(f.$el),this.table.append(o),s[o]=f}this.app.datasets.request({id:e},function(e){r.columns=[];var t=e.metadata_column_types;for(var n in t)(t[n]=="int"||t[n]=="float")&&r.columns.push({label:"Column: "+(parseInt(n)+1)+" ["+t[n]+"]",value:n});for(var n in s)s[n].update(r.columns),s[n].show()})},_refreshGroup:function(){this.group.set("date",n.time())},_refreshGroupKey:function(){var e=this.group.get("key");e===undefined&&(e=""),this.group_key.value(e)}})}),define("plugin/library/ui-table-form",["plugin/library/ui-table","plugin/library/ui","utils/utils"],function(e,t,n){var r=Backbone.View.extend({initialize:function(r){this.table_title=new t.Label({title:r.title}),this.table=new e({content:r.content});var i=$("<div/>");i.append(n.wrap(this.table_title.$el)),i.append(n.wrap(this.table.$el)),this.setElement(i)},title:function(e){this.table_title.title(e)},update:function(e,t){this.table.removeAll();for(var n in e)this._add(n,e[n],t)},_add:function(e,n,r){var i=null,s=n.type;switch(s){case"text":i=new t.Input({placeholder:n.placeholder,onchange:function(){r.set(e,i.value())}});break;case"select":i=new t.Select.View({data:n.data,onchange:function(){r.set(e,i.value())}});break;case"slider":i=new t.Input({placeholder:n.placeholder,onchange:function(){r.set(e,i.value())}});break;case"separator":i=$("<div/>");break;default:console.log("ui-table-form:_add","Unknown setting type ("+n.type+")");return}if(s!="separator"){r.get(e)||r.set(e,n.init),i.value(r.get(e));var o=$("<div/>");o.append(i.$el),o.append('<div class="toolParamHelp" style="font-size: 0.9em;">'+n.info+"</div>"),this.table.add('<span style="white-space: nowrap;">'+n.title+"</span>","25%"),this.table.add(o)}else this.table.add('<h6 style="white-space: nowrap;">'+n.title+":<h6/>"),this.table.add($("<div/>"));this.table.append(e)}});return{View:r}}),define("plugin/views/settings",["plugin/library/ui","plugin/library/ui-table-form","utils/utils"],function(e,t,n){return Backbone.View.extend({initialize:function(e,n){this.app=e;var r=this;this.chart=this.app.chart,this.form=new t.View({title:"Chart options:",content:"This chart type does not provide any options."}),this.setElement(this.form.$el);var r=this;this.chart.on("change",function(){r._refreshTable()})},_refreshTable:function(){var e=this.chart.get("type");if(!e)return;var t=this.app.types.get(e);this.form.title(t.title+":"),this.form.update(t.settings,this.chart.settings)}})}),define("plugin/views/chart",["mvc/ui/ui-tabs","plugin/library/ui-table","plugin/library/ui","utils/utils","plugin/models/chart","plugin/models/group","plugin/views/group","plugin/views/settings"],function(e,t,n,r,i,s,o,u){return Backbone.View.extend({optionsDefault:{header:!0,content:"No content available."},initialize:function(i,s){this.app=i,this.chart=this.app.chart,this.options=r.merge(s,this.optionsDefault);var o=this;this.table=new t({header:!1,onconfirm:function(e){o.chart.groups.length>0?o.app.modal.show({title:"Switching to another chart type?",body:"If you continue your settings and selections will be reseted.",buttons:{Cancel:function(){o.app.modal.hide()},Continue:function(){o.app.modal.hide(),o.table.value(e)}}}):o.table.value(e)},onchange:function(e){o.chart.groups.reset(),o.chart.settings.clear(),o.chart.set({type:e})},ondblclick:function(e){o.tabs.show("settings")},content:"No chart types available"});var a=0,f=i.types.attributes;for(var l in f){var c=f[l];this.table.add(++a+"."),this.table.add(c.title),this.table.append(l)}this.tabs=new e.View({title_new:"Add Data",onnew:function(){var e=o._addGroupModel();o.tabs.show(e.id)},operations:{save:new n.ButtonIcon({icon:"fa-save",tooltip:"Draw Chart",title:"Draw",onclick:function(){o.chart.groups.length==0&&o._addGroupModel(),o._saveChart(),o.hide(),o.app.charts_view.$el.show()}}),back:new n.ButtonIcon({icon:"fa-caret-left",tooltip:"Return to Viewer",title:"Return",onclick:function(){o.hide(),o.app.charts_view.$el.show()}})}}),this.title=new n.Input({placeholder:"Chart title",onchange:function(){o.app.config.set("title",o.title.value())}});var h=$("<div/>");h.append(r.wrap((new n.Label({title:"Provide a chart title:"})).$el)),h.append(r.wrap(this.title.$el)),h.append(r.wrap((new n.Label({title:"Select a chart type:"})).$el)),h.append(r.wrap(this.table.$el)),this.tabs.add({id:"main",title:"Start",$el:h}),this.settings=new u(this.app),this.tabs.add({id:"settings",title:"Configuration",$el:this.settings.$el}),this.setElement(this.tabs.$el),this.tabs.hideOperation("back");var o=this;this.chart.on("change:title",function(e){o.title.value(e.get("title")),o.app.config.set("title",e.get("title"))}),this.chart.on("change:type",function(e){o.table.value(e.get("type"))}),this.chart.on("reset",function(e){o._resetChart()}),this.app.charts.on("add",function(e){o.tabs.showOperation("back")}),this.app.charts.on("remove",function(e){o.app.charts.length==0&&o.tabs.hideOperation("back")}),this.app.charts.on("reset",function(e){o.tabs.hideOperation("back")}),this.app.chart.groups.on("add",function(e){o._addGroup(e)}),this.app.chart.groups.on("remove",function(e){o._removeGroup(e)}),this.app.chart.groups.on("reset",function(e){o._removeAllGroups()}),this.app.chart.groups.on("change:key",function(e){o._refreshGroupKey()}),this._resetChart()},hide:function(){$(".tooltip").hide(),this.$el.hide()},_refreshGroupKey:function(){var e=this,t=0;this.chart.groups.each(function(n){var r=n.get("key","");r==""&&(r="Chart data"),e.tabs.title(n.id,++t+": "+r)})},_addGroupModel:function(){var e=new s({id:r.uuid()});return this.chart.groups.add(e),e},_addGroup:function(e){var t=this,n=new o(this.app,{group:e}),r=t.chart.groups.length;this.tabs.add({id:e.id,$el:n.$el,ondel:function(){t.chart.groups.remove(e.id)}}),this._refreshGroupKey()},_removeGroup:function(e){this.tabs.del(e.id),this._refreshGroupKey()},_removeAllGroups:function(e){this.tabs.delRemovable()},_resetChart:function(){this.chart.set("id",r.uuid()),this.chart.set("type","bardiagram"),this.chart.set("dataset_id",this.app.options.dataset.id),this.chart.set("dataset_hid",this.app.options.dataset.hid),this.chart.set("history_id",this.app.options.dataset.history_id),this.chart.set("title","New Chart")},_saveChart:function(){this.chart.set({type:this.table.value(),title:this.title.value(),date:r.time()});var e=this.app.charts.get(this.chart.id);e?e.copy(this.chart):(e=this.chart.clone(),e.copy(this.chart),this.app.charts.add(e)),e.trigger("redraw",e)}})}),define("plugin/models/config",[],function(){return Backbone.Model.extend({defaults:{query_limit:1e3,query_timeout:500,title:"Create a new chart"}})}),define("plugin/models/charts",["plugin/models/chart"],function(e){return Backbone.Collection.extend({model:e})}),define("plugin/charts/_nvd3/config",[],function(){return{title:"",columns:{y:{title:"Values for y-axis"}},settings:{separator_label:{title:"X axis",type:"separator"},x_axis_label:{title:"Axis label",info:"Provide a label for the axis.",type:"text",init:"X-axis",placeholder:"Axis label"},x_axis_type:{title:"Axis value type",info:"Select the value type of the axis.",type:"select",init:"f",data:[{label:"-- Do not show values --",value:"hide"},{label:"Float",value:"f"},{label:"Exponent",value:"e"},{label:"Integer",value:"d"},{label:"Percentage",value:"p"},{label:"Rounded",value:"r"},{label:"SI-prefix",value:"s"}]},x_axis_tick:{title:"Axis tick format",info:"Select the tick format for the axis.",type:"select",init:".1",data:[{label:"0.00001",value:".5"},{label:"0.0001",value:".4"},{label:"0.001",value:".3"},{label:"0.01",value:".2"},{label:"0.1",value:".1"},{label:"1",value:"1"}]},separator_tick:{title:"Y axis",type:"separator"},y_axis_label:{title:"Axis label",info:"Provide a label for the axis.",type:"text",init:"Y-axis",placeholder:"Axis label"},y_axis_type:{title:"Axis value type",info:"Select the value type of the axis.",type:"select",init:"f",data:[{label:"-- Do not show values --",value:"hide"},{label:"Float",value:"f"},{label:"Exponent",value:"e"},{label:"Integer",value:"d"},{label:"Percentage",value:"p"},{label:"Rounded",value:"r"},{label:"SI-prefix",value:"s"}]},y_axis_tick:{title:"Axis tick format",info:"Select the tick format for the axis.",type:"select",init:".1",data:[{label:"0.00001",value:".5"},{label:"0.0001",value:".4"},{label:"0.001",value:".3"},{label:"0.01",value:".2"},{label:"0.1",value:".1"},{label:"1",value:"1"}]},separator_legend:{title:"Others",type:"separator"},show_legend:{title:"Show legend",info:"Would you like to add a legend?",type:"select",init:"true",data:[{label:"Yes",value:"true"},{label:"No",value:"false"}]}}}}),define("plugin/charts/bardiagram/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Bar diagram"})}),define("plugin/charts/histogram/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Histogram",mode:"execute",columns:{y:{title:"Observations"}},settings:{x_axis_label:{init:"Breaks"},y_axis_label:{init:"Density"},y_axis_tick:{init:".3"},separator_custom:{title:"Advanced",type:"separator"},bin_size:{title:"Number of bins",info:"Provide the number of histogram bins. The parsed data will be evenly distributed into bins according to the minimum and maximum values of the dataset.",type:"slider",init:10,min:10,max:1e3}}})}),define("plugin/charts/horizontal/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Bar diagram (horizontal)",settings:{x_axis_type:{init:"hide"}}})}),define("plugin/charts/line/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Line chart"})}),define("plugin/charts/linewithfocus/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Line with focus"})}),define("plugin/charts/piechart/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Pie chart"})}),define("plugin/charts/scatterplot/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Scatter plot",columns:{x:{title:"Values for x-axis"}}})}),define("plugin/charts/stackedarea/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Stacked area"})}),define("plugin/charts/types",["plugin/charts/bardiagram/config","plugin/charts/histogram/config","plugin/charts/horizontal/config","plugin/charts/line/config","plugin/charts/linewithfocus/config","plugin/charts/piechart/config","plugin/charts/scatterplot/config","plugin/charts/stackedarea/config"],function(e,t,n,r,i,s,o,u){return Backbone.Model.extend({defaults:{bardiagram:e,horizontal:n,histogram:t,line:r,linewithfocus:i,piechart:s,scatterplot:o,stackedarea:u}})}),define("plugin/app",["mvc/ui/ui-modal","mvc/ui/ui-portlet","plugin/library/ui","utils/utils","plugin/library/jobs","plugin/library/datasets","plugin/views/charts","plugin/views/chart","plugin/models/config","plugin/models/chart","plugin/models/charts","plugin/charts/types"],function(e,t,n,r,i,s,o,u,a,f,l,c){return Backbone.View.extend({initialize:function(n){this.options=n,Galaxy&&Galaxy.modal?this.modal=Galaxy.modal:this.modal=new e.View,this.config=new a,this.jobs=new i(this),this.types=new c,this.chart=new f,this.charts=new l,this.datasets=new s(this),this.charts_view=new o(this),this.chart_view=new u(this),this.options.config.widget?this.portlet=$("<div></div>"):this.portlet=new t.View({icon:"fa-bar-chart-o"}),this.portlet.append(this.charts_view.$el),this.portlet.append(this.chart_view.$el),this.options.config.widget?this.setElement(this.portlet):this.setElement(this.portlet.$el),this.charts_view.$el.hide();var r=this;this.config.on("change:title",function(){r._refreshTitle()}),this.render()},render:function(){this._refreshTitle()},_refreshTitle:function(){var e=this.config.get("title");e&&(e=" - "+e),this.portlet.title("Charts"+e)},execute:function(e){},onunload:function(){},log:function(e,t){console.log(e+" "+t)}})});
\ No newline at end of file
diff -r 97063e8da923eac2f9abc0bf256f4a7c98b0acec -r e22d4f2b57a8c99cf5b48d36e0ca440934eaa569 config/plugins/visualizations/charts/static/charts/types.js
--- a/config/plugins/visualizations/charts/static/charts/types.js
+++ b/config/plugins/visualizations/charts/static/charts/types.js
@@ -1,6 +1,6 @@
// dependencies
define(['plugin/charts/bardiagram/config',
- 'plugin/charts/boxplot/config',
+ //'plugin/charts/boxplot/config',
'plugin/charts/histogram/config',
'plugin/charts/horizontal/config',
'plugin/charts/line/config',
@@ -9,7 +9,7 @@
'plugin/charts/scatterplot/config',
'plugin/charts/stackedarea/config',
], function(bardiagram,
- boxplot,
+ //boxplot,
histogram,
horizontal,
line,
@@ -25,7 +25,7 @@
// types
defaults: {
'bardiagram' : bardiagram,
- 'boxplot' : boxplot,
+ //'boxplot' : boxplot,
'horizontal' : horizontal,
'histogram' : histogram,
'line' : line,
diff -r 97063e8da923eac2f9abc0bf256f4a7c98b0acec -r e22d4f2b57a8c99cf5b48d36e0ca440934eaa569 config/plugins/visualizations/charts/templates/charts.mako
--- a/config/plugins/visualizations/charts/templates/charts.mako
+++ b/config/plugins/visualizations/charts/templates/charts.mako
@@ -25,7 +25,7 @@
${h.stylesheet_link( app_root + "plugins/nv.d3.css" )}
## install boxplot module
- ${h.javascript_link( app_root + "plugins/box.js" )}
+ ##${h.javascript_link( app_root + "plugins/box.js" )}
## load merged/minified code
${h.javascript_link( app_root + "build-app.js" )}
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: guerler: Charts: Use grunt for build process
by commits-noreply@bitbucket.org 14 Mar '14
by commits-noreply@bitbucket.org 14 Mar '14
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/97063e8da923/
Changeset: 97063e8da923
User: guerler
Date: 2014-03-14 05:00:35
Summary: Charts: Use grunt for build process
Affected #: 12 files
diff -r 9319b29c6024cd3825c6777f8e55d79ca1264936 -r 97063e8da923eac2f9abc0bf256f4a7c98b0acec config/plugins/visualizations/charts/Gruntfile.js
--- /dev/null
+++ b/config/plugins/visualizations/charts/Gruntfile.js
@@ -0,0 +1,24 @@
+// NOTE: use 'sudo npm install .', then 'grunt' to use this file
+module.exports = function(grunt) {
+ grunt.initConfig({
+ pkg: grunt.file.readJSON('package.json'),
+ requirejs: {
+ compile: {
+ options: {
+ baseUrl : "../../../../static/scripts/",
+ paths : {
+ "plugin": "../../config/plugins/visualizations/charts/static"
+ },
+ shim : {
+ "libs/underscore": { exports: "_" },
+ "libs/backbone/backbone": { exports: "Backbone" }
+ },
+ name : "plugin/app",
+ out : "static/build-app.js",
+ }
+ }
+ }
+ });
+ grunt.loadNpmTasks('grunt-contrib-requirejs');
+ grunt.registerTask( 'default', [ 'requirejs' ]);
+};
diff -r 9319b29c6024cd3825c6777f8e55d79ca1264936 -r 97063e8da923eac2f9abc0bf256f4a7c98b0acec config/plugins/visualizations/charts/build/build.js
--- a/config/plugins/visualizations/charts/build/build.js
+++ /dev/null
@@ -1,12 +0,0 @@
-({
- baseUrl : "../../../../../static/scripts/",
- paths : {
- "plugin": "../../config/plugins/visualizations/charts/static"
- },
- shim : {
- "libs/underscore": { exports: "_" },
- "libs/backbone/backbone": { exports: "Backbone" }
- },
- name : "plugin/app",
- out : "../static/build-app.js",
-})
\ No newline at end of file
diff -r 9319b29c6024cd3825c6777f8e55d79ca1264936 -r 97063e8da923eac2f9abc0bf256f4a7c98b0acec config/plugins/visualizations/charts/build/build.sh
--- a/config/plugins/visualizations/charts/build/build.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/bash
-node r.js -o build.js
\ No newline at end of file
This diff is so big that we needed to truncate the remainder.
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/ac4e18e81ed5/
Changeset: ac4e18e81ed5
Branch: stable
User: dan
Date: 2014-03-13 22:07:23
Summary: Update GenomeSpace exporter directory listing to send the now working/required accept string.
Affected #: 1 file
diff -r 9ed9316ae6fba4fb83050757d9db259569d14de9 -r ac4e18e81ed5d9256cb1f07692da7928dcd7969f tools/genomespace/genomespace_exporter.py
--- a/tools/genomespace/genomespace_exporter.py
+++ b/tools/genomespace/genomespace_exporter.py
@@ -121,7 +121,7 @@
if value:
value = value[0]#single select, only 1 value
def recurse_directory_dict( url_opener, cur_options, url ):
- cur_directory = urllib2.Request( url )#, headers = { 'Content-Type': 'application/json', 'Accept': 'application/text' } ) #apparently http://www.genomespace.org/team/specs/updated-dm-rest-api:"Every HTTP request to the Data Manager should include the Accept header with a preference for the media types application/json and application/text." is not correct
+ cur_directory = urllib2.Request( url, headers = { 'Content-Type': 'application/json', 'Accept': 'application/json, text/plain' } )
cur_directory.get_method = lambda: 'GET'
#get url to upload to
try:
https://bitbucket.org/galaxy/galaxy-central/commits/9319b29c6024/
Changeset: 9319b29c6024
User: dan
Date: 2014-03-13 22:08:18
Summary: merge stable
Affected #: 0 files
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: dan: Update GenomeSpace exporter directory listing to send the now working/required accept string.
by commits-noreply@bitbucket.org 13 Mar '14
by commits-noreply@bitbucket.org 13 Mar '14
13 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/777483d69cd8/
Changeset: 777483d69cd8
User: dan
Date: 2014-03-13 22:07:23
Summary: Update GenomeSpace exporter directory listing to send the now working/required accept string.
Affected #: 1 file
diff -r 5e9c0065ae572bb32ae55591bd5a998abecdf5b5 -r 777483d69cd8d7db7b78c9b519f244e7321433e2 tools/genomespace/genomespace_exporter.py
--- a/tools/genomespace/genomespace_exporter.py
+++ b/tools/genomespace/genomespace_exporter.py
@@ -121,7 +121,7 @@
if value:
value = value[0]#single select, only 1 value
def recurse_directory_dict( url_opener, cur_options, url ):
- cur_directory = urllib2.Request( url )#, headers = { 'Content-Type': 'application/json', 'Accept': 'application/text' } ) #apparently http://www.genomespace.org/team/specs/updated-dm-rest-api:"Every HTTP request to the Data Manager should include the Accept header with a preference for the media types application/json and application/text." is not correct
+ cur_directory = urllib2.Request( url, headers = { 'Content-Type': 'application/json', 'Accept': 'application/json, text/plain' } )
cur_directory.get_method = lambda: 'GET'
#get url to upload to
try:
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: martenson: Closed branch icons for external menu links
by commits-noreply@bitbucket.org 13 Mar '14
by commits-noreply@bitbucket.org 13 Mar '14
13 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/4c68b5fdd12f/
Changeset: 4c68b5fdd12f
Branch: icons for external menu links
User: martenson
Date: 2014-03-13 21:54:19
Summary: Closed branch icons for external menu links
Affected #: 0 files
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/068e49f30717/
Changeset: 068e49f30717
Branch: icons for external menu links
User: martenson
Date: 2014-03-13 21:38:07
Summary: add greyed icons to menu items that open into new window/tab so user will not lose control
Affected #: 2 files
diff -r 54a2ec26ae5f8f2bf78b8b964d4279533e4a905a -r 068e49f3071716f7b259115848bafedb1814dd1d static/scripts/galaxy.masthead.js
--- a/static/scripts/galaxy.masthead.js
+++ b/static/scripts/galaxy.masthead.js
@@ -404,7 +404,13 @@
// fill template header
_templateMenuItem: function (options)
{
- return '<li><a href="' + options.content + '" target="' + options.target + '">' + options.title + '</a></li>';
+ tmpl = '<li><a href="' + options.content + '" target="' + options.target + '">' + options.title;
+ if (options.target === '_blank'){
+ tmpl += ' <span class="fa fa-external-link" style="font-size: 0.8em; color:grey; "></span>';
+ }
+ tmpl += '</a></li>';
+
+ return tmpl
},
// fill template header
diff -r 54a2ec26ae5f8f2bf78b8b964d4279533e4a905a -r 068e49f3071716f7b259115848bafedb1814dd1d static/scripts/packed/galaxy.masthead.js
--- a/static/scripts/packed/galaxy.masthead.js
+++ b/static/scripts/packed/galaxy.masthead.js
@@ -1,1 +1,1 @@
-define([],function(){var a=Backbone.View.extend({el_masthead:"#everything",options:null,$background:null,list:[],initialize:function(e){this.options=e;$("body").off();this.setElement($(this._template(e)));$(this.el_masthead).append($(this.el));this.$background=$(this.el).find("#masthead-background");var d=this;$(window).on("beforeunload",function(){var g="";for(key in d.list){if(d.list[key].options.onunload){var f=d.list[key].options.onunload();if(f){g+=f+" "}}}if(g!=""){return g}})},events:{click:"_click",mousedown:function(d){d.preventDefault()}},append:function(d){return this._add(d,true)},prepend:function(d){return this._add(d,false)},highlight:function(e){var d=$(this.el).find("#"+e+"> li");if(d){d.addClass("active")}},_add:function(g,e){var d=$(this.el).find("#"+g.location);if(d){var f=$(g.el);f.addClass("masthead-item");if(e){d.append(f)}else{d.prepend(f)}this.list.push(g)}return null},_click:function(g){var f=$(this.el).find(".popup");if(f){f.hide()}var d=$(g.target).closest(".masthead-item").find(".popup");if($(g.target).hasClass("head")){d.show();this.$background.show()}else{this.$background.hide()}},_template:function(d){var e=d.brand?("/ "+d.brand):"";return'<div><div id="masthead" class="navbar navbar-fixed-top navbar-inverse"><div style="position: relative; right: -50%; float: left;"><div id="navbar" style="display: block; position: relative; right: 50%;"></div></div><div class="navbar-brand"><a href="'+d.logo_url+'"><img border="0" src="'+galaxy_config.root+'static/images/galaxyIcon_noText.png"><span id="brand"> Galaxy '+e+'</span></a></div><div class="quota-meter-container"></div><div id="iconbar" class="iconbar"></div></div><div id="masthead-background" style="display: none; position: absolute; top: 33px; width: 100%; height: 100%; z-index: 1010"></div></div>'}});var b=Backbone.View.extend({options:{id:"",icon:"fa-cog",tooltip:"",with_number:false,onclick:function(){alert("clicked")},onunload:null,visible:true},location:"iconbar",initialize:function(e){if(e){this.options=_.defaults(e,this.options)}this.setElement($(this._template(this.options)));var d=this;$(this.el).find(".icon").tooltip({title:this.options.tooltip,placement:"bottom"}).on("mouseup",d.options.onclick);if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},icon:function(d){$(this.el).find(".icon").removeClass(this.options.icon).addClass(d);this.options.icon=d},toggle:function(){$(this.el).addClass("toggle")},untoggle:function(){$(this.el).removeClass("toggle")},number:function(d){$(this.el).find(".number").text(d)},_template:function(e){var d='<div id="'+e.id+'" class="symbol"><div class="icon fa fa-2x '+e.icon+'"></div>';if(e.with_number){d+='<div class="number"></div>'}d+="</div>";return d}});var c=Backbone.View.extend({options:{id:"",title:"",target:"_parent",content:"",type:"url",scratchbook:false,onunload:null,visible:true,disabled:false,title_attribute:""},location:"navbar",$menu:null,events:{"click .head":"_head"},initialize:function(d){if(d){this.options=_.defaults(d,this.options)}if(this.options.content&&this.options.content.indexOf("//")===-1){this.options.content=galaxy_config.root+this.options.content}this.setElement($(this._template(this.options)));if(this.options.disabled){$(this.el).find(".root").addClass("disabled");this._attachPopover()}if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},add:function(f){var g={title:"Title",content:"",type:"url",target:"_parent",scratchbook:false,divider:false};if(f){g=_.defaults(f,g)}if(g.content&&g.content.indexOf("//")===-1){g.content=galaxy_config.root+g.content}if(!this.$menu){$(this.el).find(".root").append(this._templateMenu());$(this.el).find(".symbol").addClass("caret");this.$menu=$(this.el).find(".popup")}var e=$(this._templateMenuItem(g));this.$menu.append(e);var d=this;e.on("click",function(h){h.preventDefault();if(d.options.target==="_blank"){return true}Galaxy.frame.add(f)});if(g.divider){this.$menu.append($(this._templateDivider()))}},_head:function(d){d.preventDefault();if(this.options.disabled){return}if(!this.$menu){Galaxy.frame.add(this.options)}},_attachPopover:function(){var d=$(this.el).find(".head");d.popover({html:true,content:'Please <a href="/user/login">log in</a> or <a href="/user/create">register</a> to use this feature.',placement:"bottom"}).on("shown.bs.popover",function(){setTimeout(function(){d.popover("hide")},5000)})},_templateMenuItem:function(d){return'<li><a href="'+d.content+'" target="'+d.target+'">'+d.title+"</a></li>"},_templateMenu:function(){return'<ul class="popup dropdown-menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(e){var d='<ul id="'+e.id+'" class="nav navbar-nav" border="0" cellspacing="0"><li class="root dropdown" style=""><a class="head dropdown-toggle" data-toggle="dropdown" target="'+e.target+'" href="'+e.content+'" title="'+e.title_attribute+'">'+e.title+'<b class="symbol"></b></a></li></ul>';return d}});return{GalaxyMasthead:a,GalaxyMastheadTab:c,GalaxyMastheadIcon:b}});
\ No newline at end of file
+define([],function(){var a=Backbone.View.extend({el_masthead:"#everything",options:null,$background:null,list:[],initialize:function(e){this.options=e;$("body").off();this.setElement($(this._template(e)));$(this.el_masthead).append($(this.el));this.$background=$(this.el).find("#masthead-background");var d=this;$(window).on("beforeunload",function(){var g="";for(key in d.list){if(d.list[key].options.onunload){var f=d.list[key].options.onunload();if(f){g+=f+" "}}}if(g!=""){return g}})},events:{click:"_click",mousedown:function(d){d.preventDefault()}},append:function(d){return this._add(d,true)},prepend:function(d){return this._add(d,false)},highlight:function(e){var d=$(this.el).find("#"+e+"> li");if(d){d.addClass("active")}},_add:function(g,e){var d=$(this.el).find("#"+g.location);if(d){var f=$(g.el);f.addClass("masthead-item");if(e){d.append(f)}else{d.prepend(f)}this.list.push(g)}return null},_click:function(g){var f=$(this.el).find(".popup");if(f){f.hide()}var d=$(g.target).closest(".masthead-item").find(".popup");if($(g.target).hasClass("head")){d.show();this.$background.show()}else{this.$background.hide()}},_template:function(d){var e=d.brand?("/ "+d.brand):"";return'<div><div id="masthead" class="navbar navbar-fixed-top navbar-inverse"><div style="position: relative; right: -50%; float: left;"><div id="navbar" style="display: block; position: relative; right: 50%;"></div></div><div class="navbar-brand"><a href="'+d.logo_url+'"><img border="0" src="'+galaxy_config.root+'static/images/galaxyIcon_noText.png"><span id="brand"> Galaxy '+e+'</span></a></div><div class="quota-meter-container"></div><div id="iconbar" class="iconbar"></div></div><div id="masthead-background" style="display: none; position: absolute; top: 33px; width: 100%; height: 100%; z-index: 1010"></div></div>'}});var b=Backbone.View.extend({options:{id:"",icon:"fa-cog",tooltip:"",with_number:false,onclick:function(){alert("clicked")},onunload:null,visible:true},location:"iconbar",initialize:function(e){if(e){this.options=_.defaults(e,this.options)}this.setElement($(this._template(this.options)));var d=this;$(this.el).find(".icon").tooltip({title:this.options.tooltip,placement:"bottom"}).on("mouseup",d.options.onclick);if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},icon:function(d){$(this.el).find(".icon").removeClass(this.options.icon).addClass(d);this.options.icon=d},toggle:function(){$(this.el).addClass("toggle")},untoggle:function(){$(this.el).removeClass("toggle")},number:function(d){$(this.el).find(".number").text(d)},_template:function(e){var d='<div id="'+e.id+'" class="symbol"><div class="icon fa fa-2x '+e.icon+'"></div>';if(e.with_number){d+='<div class="number"></div>'}d+="</div>";return d}});var c=Backbone.View.extend({options:{id:"",title:"",target:"_parent",content:"",type:"url",scratchbook:false,onunload:null,visible:true,disabled:false,title_attribute:""},location:"navbar",$menu:null,events:{"click .head":"_head"},initialize:function(d){if(d){this.options=_.defaults(d,this.options)}if(this.options.content&&this.options.content.indexOf("//")===-1){this.options.content=galaxy_config.root+this.options.content}this.setElement($(this._template(this.options)));if(this.options.disabled){$(this.el).find(".root").addClass("disabled");this._attachPopover()}if(!this.options.visible){this.hide()}},show:function(){$(this.el).css({visibility:"visible"})},hide:function(){$(this.el).css({visibility:"hidden"})},add:function(f){var g={title:"Title",content:"",type:"url",target:"_parent",scratchbook:false,divider:false};if(f){g=_.defaults(f,g)}if(g.content&&g.content.indexOf("//")===-1){g.content=galaxy_config.root+g.content}if(!this.$menu){$(this.el).find(".root").append(this._templateMenu());$(this.el).find(".symbol").addClass("caret");this.$menu=$(this.el).find(".popup")}var e=$(this._templateMenuItem(g));this.$menu.append(e);var d=this;e.on("click",function(h){h.preventDefault();if(d.options.target==="_blank"){return true}Galaxy.frame.add(f)});if(g.divider){this.$menu.append($(this._templateDivider()))}},_head:function(d){d.preventDefault();if(this.options.disabled){return}if(!this.$menu){Galaxy.frame.add(this.options)}},_attachPopover:function(){var d=$(this.el).find(".head");d.popover({html:true,content:'Please <a href="/user/login">log in</a> or <a href="/user/create">register</a> to use this feature.',placement:"bottom"}).on("shown.bs.popover",function(){setTimeout(function(){d.popover("hide")},5000)})},_templateMenuItem:function(d){tmpl='<li><a href="'+d.content+'" target="'+d.target+'">'+d.title;if(d.target==="_blank"){tmpl+=' <span class="fa fa-external-link" style="font-size: 0.8em; color:grey; "></span>'}tmpl+="</a></li>";return tmpl},_templateMenu:function(){return'<ul class="popup dropdown-menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(e){var d='<ul id="'+e.id+'" class="nav navbar-nav" border="0" cellspacing="0"><li class="root dropdown" style=""><a class="head dropdown-toggle" data-toggle="dropdown" target="'+e.target+'" href="'+e.content+'" title="'+e.title_attribute+'">'+e.title+'<b class="symbol"></b></a></li></ul>';return d}});return{GalaxyMasthead:a,GalaxyMastheadTab:c,GalaxyMastheadIcon:b}});
\ No newline at end of file
https://bitbucket.org/galaxy/galaxy-central/commits/5e9c0065ae57/
Changeset: 5e9c0065ae57
User: martenson
Date: 2014-03-13 21:50:27
Summary: data libraries: initial take on showing libraries to anonyms and filter on deleted libraries
plus some newly packed script that wasn't packed in error
Affected #: 4 files
diff -r 54a2ec26ae5f8f2bf78b8b964d4279533e4a905a -r 5e9c0065ae572bb32ae55591bd5a998abecdf5b5 lib/galaxy/webapps/galaxy/api/libraries.py
--- a/lib/galaxy/webapps/galaxy/api/libraries.py
+++ b/lib/galaxy/webapps/galaxy/api/libraries.py
@@ -2,10 +2,10 @@
API operations on a data library.
"""
from galaxy import util
-from galaxy.util import string_as_bool
from galaxy import web
from galaxy import exceptions
from galaxy.web import _future_expose_api as expose_api
+from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous
from galaxy.model.orm import and_, not_, or_
from galaxy.web.base.controller import BaseAPIController, url_for
@@ -15,17 +15,15 @@
class LibrariesController( BaseAPIController ):
- @expose_api
- def index( self, trans, deleted='False', **kwd ):
+ @expose_api_anonymous
+ def index( self, trans, **kwd ):
"""
- index( self, trans, deleted='False', **kwd )
+ index( self, trans, **kwd )
* GET /api/libraries:
- Returns a list of summary data for all libraries that are ``non-deleted``
- * GET /api/libraries/deleted:
- Returns a list of summary data for ``deleted`` libraries.
+ Returns a list of summary data for all libraries.
- :param deleted: if True, show only ``deleted`` libraries, if False or nonpresent show only ``non-deleted``
- :type deleted: boolean
+ :param deleted: if True, show only ``deleted`` libraries, if False show only ``non-deleted``
+ :type deleted: boolean (optional)
:returns: list of dictionaries containing library information
:rtype: list
@@ -33,11 +31,17 @@
.. seealso:: :attr:`galaxy.model.Library.dict_collection_visible_keys`
"""
query = trans.sa_session.query( trans.app.model.Library )
- deleted = util.string_as_bool( deleted )
- if deleted:
- query = query.filter( trans.app.model.Library.table.c.deleted == True )
- else:
- query = query.filter( trans.app.model.Library.table.c.deleted == False )
+ deleted = kwd.get( 'deleted', 'missing' )
+ try:
+ deleted = util.asbool( deleted )
+ if deleted:
+ query = query.filter( trans.app.model.Library.table.c.deleted == True )
+ else:
+ query = query.filter( trans.app.model.Library.table.c.deleted == False )
+ except ValueError:
+ # given value wasn't true/false so we don't filter on this parameter at all
+ pass
+
current_user_role_ids = [ role.id for role in trans.get_current_user_roles() ]
library_access_action = trans.app.security_agent.permitted_actions.LIBRARY_ACCESS.action
restricted_library_ids = [ lp.library_id for lp in ( trans.sa_session.query( trans.model.LibraryPermissions )
@@ -196,7 +200,7 @@
.. seealso:: :attr:`galaxy.model.Library.dict_element_visible_keys`
"""
- undelete = string_as_bool( kwd.get( 'undelete', False ) )
+ undelete = util.string_as_bool( kwd.get( 'undelete', False ) )
if not trans.user_is_admin():
raise exceptions.ItemAccessibilityException( 'Only administrators can delete and undelete libraries.' )
try:
diff -r 54a2ec26ae5f8f2bf78b8b964d4279533e4a905a -r 5e9c0065ae572bb32ae55591bd5a998abecdf5b5 static/scripts/galaxy.library.js
--- a/static/scripts/galaxy.library.js
+++ b/static/scripts/galaxy.library.js
@@ -817,20 +817,31 @@
$("#center").css('overflow','auto');
},
- render: function (models) {
+ /** Renders the libraries table either from the object's own collection, or from a given array of library models,
+ or renders an empty list in case no data is present. */
+ render: function (options) {
var template = this.templateLibraryList();
var libraries_to_render = null;
+ var include_deleted = false;
+ var models = null
+ if (typeof options !== 'undefined'){
+ include_deleted = typeof options.with_deleted !== 'undefined' ? options.with_deleted : false;
+ models = typeof options.models !== 'undefined' ? options.models : null;
+ }
- if (this.collection !== null && typeof models === 'undefined'){
- libraries_to_render = this.collection.models;
+ if (this.collection !== null && models === null){
+ if (include_deleted){ // show all the libraries
+ libraries_to_render = this.collection.models;
+ } else{ // show only undeleted libraries
+ libraries_to_render = this.collection.where({deleted: false});;
+ }
} else if (models !== null){
libraries_to_render = models;
} else {
libraries_to_render = [];
- }
-
+ }
+
this.$el.html(template({libraries: libraries_to_render, order: this.collection.sort_order}));
-
},
/** Sorts the underlying collection according to the parameters received through URL.
@@ -867,6 +878,9 @@
tmpl_array.push('');
tmpl_array.push('<h3>Data Libraries Beta Test. This is work in progress. Please report problems & ideas via <a href="mailto:galaxy-bugs@bx.psu.edu?Subject=DataLibrariesBeta_Feedback" target="_blank">email</a> and <a href="https://trello.com/c/nwYQNFPK/56-data-library-ui-progressive-display-of-fol…" target="_blank">Trello</a>.</h3>');
tmpl_array.push('<a href="" id="create_new_library_btn" class="btn btn-primary file ">New Library</a>');
+ tmpl_array.push('<% if(libraries.length === 0) { %>');
+ tmpl_array.push("<div>I see no libraries. Why don't you create one?</div>");
+ tmpl_array.push('<% } else{ %>');
tmpl_array.push('<table class="library_table table table-condensed">');
tmpl_array.push(' <thead>');
tmpl_array.push(' <th><a title="Click to reverse order" href="#sort/name/<% if(order==="desc"||order===null){print("asc")}else{print("desc")} %>">name</a><span title="Sorted alphabetically" class="fa fa-sort-alpha-<%- order %>"></span></th>');
@@ -883,6 +897,7 @@
tmpl_array.push(' <% }); %>');
tmpl_array.push(' </tbody>');
tmpl_array.push('</table>');
+ tmpl_array.push('<% }%>');
tmpl_array.push('</div>');
return _.template(tmpl_array.join(''));
diff -r 54a2ec26ae5f8f2bf78b8b964d4279533e4a905a -r 5e9c0065ae572bb32ae55591bd5a998abecdf5b5 static/scripts/packed/galaxy.library.js
--- a/static/scripts/packed/galaxy.library.js
+++ b/static/scripts/packed/galaxy.library.js
@@ -1,1 +1,1 @@
-var view=null;var library_router=null;var responses=[];define(["galaxy.masthead","utils/utils","libs/toastr"],function(k,h,m){var f=Backbone.Model.extend({urlRoot:"/api/libraries"});var c=Backbone.Model.extend({urlRoot:"/api/folders"});var n=Backbone.Collection.extend({url:"/api/libraries",model:f,sort_key:"name",sort_order:null,});var i=Backbone.Model.extend({urlRoot:"/api/libraries/datasets"});var d=Backbone.Collection.extend({model:i});var e=Backbone.Model.extend({defaults:{folder:new d(),full_path:"unknown",urlRoot:"/api/folders/",id:"unknown"},parse:function(q){this.full_path=q[0].full_path;this.get("folder").reset(q[1].folder_contents);return q}});var b=Backbone.Model.extend({urlRoot:"/api/histories/"});var j=Backbone.Model.extend({url:"/api/histories/"});var o=Backbone.Collection.extend({url:"/api/histories",model:j});var l=Backbone.View.extend({el:"#center",progress:0,progressStep:1,lastSelectedHistory:"",modal:null,folders:null,initialize:function(){this.folders=[];this.queue=jQuery.Deferred();this.queue.resolve()},templateFolder:function(){var q=[];q.push('<div class="library_container" style="width: 90%; margin: auto; margin-top: 2em; ">');q.push('<h3>Data Libraries Beta Test. This is work in progress. Please report problems & ideas via <a href="mailto:galaxy-bugs@bx.psu.edu?Subject=DataLibrariesBeta_Feedback" target="_blank">email</a> and <a href="https://trello.com/c/nwYQNFPK/56-data-library-ui-progressive-display-of-fol…" target="_blank">Trello</a>.</h3>');q.push('<div id="library_folder_toolbar" >');q.push(' <button title="Create New Folder" id="toolbtn_create_folder" class="btn btn-primary" type="button"><span class="fa fa-plus"></span><span class="fa fa-folder-close"></span> folder</button>');q.push(' <button title="Import selected datasets into history" id="toolbtn_bulk_import" class="btn btn-primary" style="display: none; margin-left: 0.5em;" type="button"><span class="fa fa-external-link"></span> to history</button>');q.push(' <div id="toolbtn_dl" class="btn-group" style="margin-left: 0.5em; display: none; ">');q.push(' <button title="Download selected datasets" id="drop_toggle" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">');q.push(' <span class="fa fa-download"></span> download <span class="caret"></span>');q.push(" </button>");q.push(' <ul class="dropdown-menu" role="menu">');q.push(' <li><a href="#/folders/<%= id %>/download/tgz">.tar.gz</a></li>');q.push(' <li><a href="#/folders/<%= id %>/download/tbz">.tar.bz</a></li>');q.push(' <li><a href="#/folders/<%= id %>/download/zip">.zip</a></li>');q.push(" </ul>");q.push(" </div>");q.push("</div>");q.push('<ol class="breadcrumb">');q.push(' <li><a title="Return to the list of libraries" href="#">Libraries</a></li>');q.push(" <% _.each(path, function(path_item) { %>");q.push(" <% if (path_item[0] != id) { %>");q.push(' <li><a title="Return to this folder" href="#/folders/<%- path_item[0] %>"><%- path_item[1] %></a></li> ');q.push("<% } else { %>");q.push(' <li class="active"><span title="You are in this folder"><%- path_item[1] %></span></li>');q.push(" <% } %>");q.push(" <% }); %>");q.push("</ol>");q.push('<table id="folder_table" class="library_table table table-condensed">');q.push(" <thead>");q.push(' <th class="button_heading"></th>');q.push(' <th style="text-align: center; width: 20px; "><input id="select-all-checkboxes" style="margin: 0;" type="checkbox"></th>');q.push(" <th>name</th>");q.push(" <th>data type</th>");q.push(" <th>size</th>");q.push(" <th>date (UTC)</th>");q.push(" </thead>");q.push(" <tbody>");q.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>');q.push(" <td></td>");q.push(" <td></td>");q.push(" <td></td>");q.push(" <td></td>");q.push(" <td></td>");q.push(" </tr>");q.push(" <% _.each(items, function(content_item) { %>");q.push(' <% if (content_item.get("type") === "folder") { %>');q.push(' <tr class="folder_row light" id="<%- content_item.id %>">');q.push(" <td>");q.push(' <span title="Folder" class="fa fa-folder-o"></span>');q.push(" </td>");q.push(" <td></td>");q.push(" <td>");q.push(' <a href="#folders/<%- content_item.id %>"><%- content_item.get("name") %></a>');q.push(' <% if (content_item.get("item_count") === 0) { %>');q.push(' <span class="muted">(empty folder)</span>');q.push(" <% } %>");q.push(" </td>");q.push(" <td>folder</td>");q.push(' <td><%= _.escape(content_item.get("item_count")) %> item(s)</td>');q.push(' <td><%= _.escape(content_item.get("time_updated")) %></td>');q.push(" </tr>");q.push(" <% } else { %>");q.push(' <tr class="dataset_row light" id="<%- content_item.id %>">');q.push(" <td>");q.push(' <span title="Dataset" class="fa fa-file-o"></span>');q.push(" </td>");q.push(' <td style="text-align: center; "><input style="margin: 0;" type="checkbox"></td>');q.push(' <td><a href="#" class="library-dataset"><%- content_item.get("name") %><a></td>');q.push(' <td><%= _.escape(content_item.get("data_type")) %></td>');q.push(' <td><%= _.escape(content_item.get("readable_size")) %></td>');q.push(' <td><%= _.escape(content_item.get("time_updated")) %></td>');q.push(" </tr>");q.push(" <% } %> ");q.push(" <% }); %>");q.push(" ");q.push(" </tbody>");q.push("</table>");q.push("</div>");return q.join("")},templateDatasetModal:function(){var q=[];q.push('<div class="modal_table">');q.push(' <table class="table table-striped table-condensed">');q.push(" <tr>");q.push(' <th scope="row" id="id_row" data-id="<%= _.escape(item.get("ldda_id")) %>">Name</th>');q.push(' <td><%= _.escape(item.get("name")) %></td>');q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Data type</th>');q.push(' <td><%= _.escape(item.get("data_type")) %></td>');q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Genome build</th>');q.push(' <td><%= _.escape(item.get("genome_build")) %></td>');q.push(" </tr>");q.push(' <th scope="row">Size</th>');q.push(" <td><%= _.escape(size) %></td>");q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Date uploaded (UTC)</th>');q.push(' <td><%= _.escape(item.get("date_uploaded")) %></td>');q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Uploaded by</th>');q.push(' <td><%= _.escape(item.get("uploaded_by")) %></td>');q.push(" </tr>");q.push(' <tr scope="row">');q.push(' <th scope="row">Data Lines</th>');q.push(' <td scope="row"><%= _.escape(item.get("metadata_data_lines")) %></td>');q.push(" </tr>");q.push(' <th scope="row">Comment Lines</th>');q.push(' <% if (item.get("metadata_comment_lines") === "") { %>');q.push(' <td scope="row"><%= _.escape(item.get("metadata_comment_lines")) %></td>');q.push(" <% } else { %>");q.push(' <td scope="row">unknown</td>');q.push(" <% } %>");q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Number of Columns</th>');q.push(' <td scope="row"><%= _.escape(item.get("metadata_columns")) %></td>');q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Column Types</th>');q.push(' <td scope="row"><%= _.escape(item.get("metadata_column_types")) %></td>');q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Miscellaneous information</th>');q.push(' <td scope="row"><%= _.escape(item.get("misc_blurb")) %></td>');q.push(" </tr>");q.push(" </table>");q.push(' <pre class="peek">');q.push(" </pre>");q.push("</div>");return q.join("")},templateHistorySelectInModal:function(){var q=[];q.push('<span id="history_modal_combo" style="width:90%; margin-left: 1em; margin-right: 1em; ">');q.push("Select history: ");q.push('<select id="dataset_import_single" name="dataset_import_single" style="width:50%; margin-bottom: 1em; "> ');q.push(" <% _.each(histories, function(history) { %>");q.push(' <option value="<%= _.escape(history.get("id")) %>"><%= _.escape(history.get("name")) %></option>');q.push(" <% }); %>");q.push("</select>");q.push("</span>");return q.join("")},templateBulkImportInModal:function(){var q=[];q.push('<span id="history_modal_combo_bulk" style="width:90%; margin-left: 1em; margin-right: 1em; ">');q.push("Select history: ");q.push('<select id="dataset_import_bulk" name="dataset_import_bulk" style="width:50%; margin-bottom: 1em; "> ');q.push(" <% _.each(histories, function(history) { %>");q.push(' <option value="<%= _.escape(history.get("id")) %>"><%= _.escape(history.get("name")) %></option>');q.push(" <% }); %>");q.push("</select>");q.push("</span>");return q.join("")},templateProgressBar:function(){var q=[];q.push('<div class="import_text">');q.push("Importing selected datasets to history <b><%= _.escape(history_name) %></b>");q.push("</div>");q.push('<div class="progress">');q.push(' <div class="progress-bar progress-bar-import" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 00%;">');q.push(' <span class="completion_span">0% Complete</span>');q.push(" </div>");q.push("</div>");q.push("");return q.join("")},templateNewFolderInModal:function(){tmpl_array=[];tmpl_array.push('<div id="new_folder_modal">');tmpl_array.push("<form>");tmpl_array.push('<input type="text" name="Name" value="" placeholder="Name">');tmpl_array.push('<input type="text" name="Description" value="" placeholder="Description">');tmpl_array.push("</form>");tmpl_array.push("</div>");return tmpl_array.join("")},events:{"click #select-all-checkboxes":"selectAll","click #toolbtn_bulk_import":"modalBulkImport","click #toolbtn_dl":"bulkDownload","click #toolbtn_create_folder":"createFolderFromModal","click .library-dataset":"showDatasetDetails","click .dataset_row":"selectClickedRow"},render:function(q){$("#center").css("overflow","auto");view=this;var s=this;var r=new e({id:q.id});r.url=r.attributes.urlRoot+q.id+"/contents";r.fetch({success:function(t){for(var v=0;v<r.attributes.folder.models.length;v++){var u=r.attributes.folder.models[v];if(u.get("type")==="file"){u.set("readable_size",s.size_to_string(u.get("file_size")))}}var x=r.full_path;var y;if(x.length===1){y=0}else{y=x[x.length-2][0]}var w=_.template(s.templateFolder(),{path:r.full_path,items:r.attributes.folder.models,id:q.id,upper_folder_id:y});s.$el.html(w)},error:function(){m.error("An error occured :(")}})},size_to_string:function(q){var r="";if(q>=100000000000){q=q/100000000000;r="TB"}else{if(q>=100000000){q=q/100000000;r="GB"}else{if(q>=100000){q=q/100000;r="MB"}else{if(q>=100){q=q/100;r="KB"}else{q=q*10;r="b"}}}}return(Math.round(q)/10)+r},showDatasetDetails:function(t){t.preventDefault();var u=$(t.target).parent().parent().parent().attr("id");if(typeof u==="undefined"){u=$(t.target).parent().attr("id")}if(typeof u==="undefined"){u=$(t.target).parent().parent().attr("id")}var s=new i();var r=new o();s.id=u;var q=this;s.fetch({success:function(v){r.fetch({success:function(w){q.renderModalAfterFetch(v,w)},error:function(){m.error("An error occured during fetching histories:(");q.renderModalAfterFetch(v)}})},error:function(){m.error("An error occured during loading dataset details :(")}})},renderModalAfterFetch:function(v,s){var t=this.size_to_string(v.get("file_size"));var u=_.template(this.templateDatasetModal(),{item:v,size:t});var r=this;this.modal=Galaxy.modal;this.modal.show({closing_events:true,title:"Dataset Details",body:u,buttons:{Import:function(){r.importCurrentIntoHistory()},Download:function(){r.downloadCurrent()},Close:function(){r.modal.hide()}}});$(".peek").html(v.get("peek"));if(typeof history.models!==undefined){var q=_.template(this.templateHistorySelectInModal(),{histories:s.models});$(this.modal.elMain).find(".buttons").prepend(q);if(r.lastSelectedHistory.length>0){$(this.modal.elMain).find("#dataset_import_single").val(r.lastSelectedHistory)}}},downloadCurrent:function(){this.modal.disableButton("Import");this.modal.disableButton("Download");var q=[];q.push($("#id_row").attr("data-id"));var r="/api/libraries/datasets/download/uncompressed";var s={ldda_ids:q};folderContentView.processDownload(r,s);this.modal.enableButton("Import");this.modal.enableButton("Download")},importCurrentIntoHistory:function(){this.modal.disableButton("Import");this.modal.disableButton("Download");var s=$(this.modal.elMain).find("select[name=dataset_import_single] option:selected").val();this.lastSelectedHistory=s;var q=$("#id_row").attr("data-id");var t=new b();var r=this;t.url=t.urlRoot+s+"/contents";t.save({content:q,source:"library"},{success:function(){m.success("Dataset imported");r.modal.enableButton("Import");r.modal.enableButton("Download")},error:function(){m.error("An error occured! Dataset not imported. Please try again.");r.modal.enableButton("Import");r.modal.enableButton("Download")}})},selectAll:function(r){var q=r.target.checked;that=this;$(":checkbox").each(function(){this.checked=q;$row=$(this.parentElement.parentElement);(q)?that.makeDarkRow($row):that.makeWhiteRow($row)});this.checkTools()},selectClickedRow:function(r){var t="";var q;var s;if(r.target.localName==="input"){t=r.target;q=$(r.target.parentElement.parentElement);s="input"}else{if(r.target.localName==="td"){t=$("#"+r.target.parentElement.id).find(":checkbox")[0];q=$(r.target.parentElement);s="td"}}if(t.checked){if(s==="td"){t.checked="";this.makeWhiteRow(q)}else{if(s==="input"){this.makeDarkRow(q)}}}else{if(s==="td"){t.checked="selected";this.makeDarkRow(q)}else{if(s==="input"){this.makeWhiteRow(q)}}}this.checkTools()},makeDarkRow:function(q){q.removeClass("light");q.find("a").removeClass("light");q.addClass("dark");q.find("a").addClass("dark");q.find("span").removeClass("fa-file-o");q.find("span").addClass("fa-file")},makeWhiteRow:function(q){q.removeClass("dark");q.find("a").removeClass("dark");q.addClass("light");q.find("a").addClass("light");q.find("span").addClass("fa-file-o");q.find("span").removeClass("fa-file")},checkTools:function(){var q=$("#folder_table").find(":checked");if(q.length>0){$("#toolbtn_bulk_import").show();$("#toolbtn_dl").show()}else{$("#toolbtn_bulk_import").hide();$("#toolbtn_dl").hide()}},modalBulkImport:function(){var r=this;var q=new o();q.fetch({success:function(s){var t=_.template(r.templateBulkImportInModal(),{histories:s.models});r.modal=Galaxy.modal;r.modal.show({closing_events:true,title:"Import into History",body:t,buttons:{Import:function(){r.importAllIntoHistory()},Close:function(){r.modal.hide()}}})},error:function(){m.error("An error occured :(")}})},importAllIntoHistory:function(){this.modal.disableButton("Import");var s=$("select[name=dataset_import_bulk] option:selected").val();var w=$("select[name=dataset_import_bulk] option:selected").text();var y=[];$("#folder_table").find(":checked").each(function(){if(this.parentElement.parentElement.id!=""){y.push(this.parentElement.parentElement.id)}});var x=_.template(this.templateProgressBar(),{history_name:w});$(this.modal.elMain).find(".modal-body").html(x);var t=100/y.length;this.initProgress(t);var q=[];for(var r=y.length-1;r>=0;r--){library_dataset_id=y[r];var u=new b();var v=this;u.url=u.urlRoot+s+"/contents";u.content=library_dataset_id;u.source="library";q.push(u)}this.chainCall(q)},chainCall:function(r){var q=this;var s=r.pop();if(typeof s==="undefined"){m.success("All datasets imported");this.modal.hide();return}var t=$.when(s.save({content:s.content,source:s.source})).done(function(u){q.updateProgress();responses.push(u);q.chainCall(r)})},initProgress:function(q){this.progress=0;this.progressStep=q},updateProgress:function(){this.progress+=this.progressStep;$(".progress-bar-import").width(Math.round(this.progress)+"%");txt_representation=Math.round(this.progress)+"% Complete";$(".completion_span").text(txt_representation)},download:function(q,u){var s=[];$("#folder_table").find(":checked").each(function(){if(this.parentElement.parentElement.id!=""){s.push(this.parentElement.parentElement.id)}});var r="/api/libraries/datasets/download/"+u;var t={ldda_ids:s};this.processDownload(r,t,"get")},processDownload:function(r,s,t){if(r&&s){s=typeof s=="string"?s:$.param(s);var q="";$.each(s.split("&"),function(){var u=this.split("=");q+='<input type="hidden" name="'+u[0]+'" value="'+u[1]+'" />'});$('<form action="'+r+'" method="'+(t||"post")+'">'+q+"</form>").appendTo("body").submit().remove();m.info("Your download will begin soon")}},createFolderFromModal:function(){event.preventDefault();event.stopPropagation();var q=this;this.modal=Galaxy.modal;this.modal.show({closing_events:true,title:"Create New Folder",body:this.templateNewFolderInModal(),buttons:{Create:function(){q.create_new_folder_event()},Close:function(){q.modal.hide();q.modal=null}}})},create_new_folder_event:function(){var q=this.serialize_new_folder();if(this.validate_new_folder(q)){var s=new c();url_items=Backbone.history.fragment.split("/");current_folder_id=url_items[url_items.length-1];s.url=s.urlRoot+"/"+current_folder_id;var r=this;s.save(q,{success:function(t){r.modal.hide();m.success("Folder created");r.render({id:current_folder_id})},error:function(){m.error("An error occured :(")}})}else{m.error("Folder's name is missing")}return false},serialize_new_folder:function(){return{name:$("input[name='Name']").val(),description:$("input[name='Description']").val()}},validate_new_folder:function(q){return q.name!==""}});var a=Backbone.View.extend({el:"#center",events:{"click #create_new_library_btn":"show_library_modal"},modal:null,collection:null,initialize:function(){var q=this;this.collection=new n();this.collection.fetch({success:function(r){q.render()},error:function(s,r){if(r.statusCode().status===403){m.info("Please log in first. Redirecting to login page in 3s.");setTimeout(that.redirectToLogin,3000)}else{m.error("An error occured. Please try again.")}}});$("#center").css("overflow","auto")},render:function(s){var q=this.templateLibraryList();var r=null;if(this.collection!==null&&typeof s==="undefined"){r=this.collection.models}else{if(s!==null){r=s}else{r=[]}}this.$el.html(q({libraries:r,order:this.collection.sort_order}))},sortLibraries:function(r,q){if(r==="name"){if(q==="asc"){this.collection.sort_order="asc";this.collection.comparator=function(t,s){if(t.get("name").toLowerCase()>s.get("name").toLowerCase()){return 1}if(s.get("name").toLowerCase()>t.get("name").toLowerCase()){return -1}return 0}}else{if(q==="desc"){this.collection.sort_order="desc";this.collection.comparator=function(t,s){if(t.get("name").toLowerCase()>s.get("name").toLowerCase()){return -1}if(s.get("name").toLowerCase()>t.get("name").toLowerCase()){return 1}return 0}}}this.collection.sort()}},templateLibraryList:function(){tmpl_array=[];tmpl_array.push('<div class="library_container" style="width: 90%; margin: auto; margin-top: 2em; overflow: auto !important; ">');tmpl_array.push("");tmpl_array.push('<h3>Data Libraries Beta Test. This is work in progress. Please report problems & ideas via <a href="mailto:galaxy-bugs@bx.psu.edu?Subject=DataLibrariesBeta_Feedback" target="_blank">email</a> and <a href="https://trello.com/c/nwYQNFPK/56-data-library-ui-progressive-display-of-fol…" target="_blank">Trello</a>.</h3>');tmpl_array.push('<a href="" id="create_new_library_btn" class="btn btn-primary file ">New Library</a>');tmpl_array.push('<table class="library_table table table-condensed">');tmpl_array.push(" <thead>");tmpl_array.push(' <th><a title="Click to reverse order" href="#sort/name/<% if(order==="desc"||order===null){print("asc")}else{print("desc")} %>">name</a><span title="Sorted alphabetically" class="fa fa-sort-alpha-<%- order %>"></span></th>');tmpl_array.push(" <th>description</th>");tmpl_array.push(" <th>synopsis</th> ");tmpl_array.push(" </thead>");tmpl_array.push(" <tbody>");tmpl_array.push(" <% _.each(libraries, function(library) { %>");tmpl_array.push(" <tr>");tmpl_array.push(' <td><a href="#folders/<%- library.get("root_folder_id") %>"><%- library.get("name") %></a></td>');tmpl_array.push(' <td><%= _.escape(library.get("description")) %></td>');tmpl_array.push(' <td><%= _.escape(library.get("synopsis")) %></td>');tmpl_array.push(" </tr>");tmpl_array.push(" <% }); %>");tmpl_array.push(" </tbody>");tmpl_array.push("</table>");tmpl_array.push("</div>");return _.template(tmpl_array.join(""))},templateNewLibraryInModal:function(){tmpl_array=[];tmpl_array.push('<div id="new_library_modal">');tmpl_array.push(" <form>");tmpl_array.push(' <input type="text" name="Name" value="" placeholder="Name">');tmpl_array.push(' <input type="text" name="Description" value="" placeholder="Description">');tmpl_array.push(' <input type="text" name="Synopsis" value="" placeholder="Synopsis">');tmpl_array.push(" </form>");tmpl_array.push("</div>");return tmpl_array.join("")},redirectToHome:function(){window.location="../"},redirectToLogin:function(){window.location="/user/login"},show_library_modal:function(r){r.preventDefault();r.stopPropagation();var q=this;this.modal=Galaxy.modal;this.modal.show({closing_events:true,title:"Create New Library",body:this.templateNewLibraryInModal(),buttons:{Create:function(){q.create_new_library_event()},Close:function(){q.modal.hide()}}})},create_new_library_event:function(){var s=this.serialize_new_library();if(this.validate_new_library(s)){var r=new f();var q=this;r.save(s,{success:function(t){q.collection.add(t);q.modal.hide();q.clear_library_modal();q.render();m.success("Library created")},error:function(){m.error("An error occured :(")}})}else{m.error("Library's name is missing")}return false},clear_library_modal:function(){$("input[name='Name']").val("");$("input[name='Description']").val("");$("input[name='Synopsis']").val("")},serialize_new_library:function(){return{name:$("input[name='Name']").val(),description:$("input[name='Description']").val(),synopsis:$("input[name='Synopsis']").val()}},validate_new_library:function(q){return q.name!==""}});var p=Backbone.Router.extend({routes:{"":"libraries","sort/:sort_by/:order":"sort_libraries","folders/:id":"folder_content","folders/:folder_id/download/:format":"download"}});var g=Backbone.View.extend({initialize:function(){galaxyLibraryview=new a();library_router=new p();folderContentView=new l();library_router.on("route:libraries",function(){galaxyLibraryview.render()});library_router.on("route:sort_libraries",function(r,q){galaxyLibraryview.sortLibraries(r,q);galaxyLibraryview.render()});library_router.on("route:folder_content",function(q){folderContentView.render({id:q})});library_router.on("route:download",function(q,r){if($("#center").find(":checked").length===0){library_router.navigate("folders/"+q,{trigger:true,replace:true})}else{folderContentView.download(q,r);library_router.navigate("folders/"+q,{trigger:false,replace:true})}});Backbone.history.start({pushState:false})}});return{GalaxyApp:g}});
\ No newline at end of file
+var view=null;var library_router=null;var responses=[];define(["galaxy.masthead","utils/utils","libs/toastr"],function(k,h,m){var f=Backbone.Model.extend({urlRoot:"/api/libraries"});var c=Backbone.Model.extend({urlRoot:"/api/folders"});var n=Backbone.Collection.extend({url:"/api/libraries",model:f,sort_key:"name",sort_order:null,});var i=Backbone.Model.extend({urlRoot:"/api/libraries/datasets"});var d=Backbone.Collection.extend({model:i});var e=Backbone.Model.extend({defaults:{folder:new d(),full_path:"unknown",urlRoot:"/api/folders/",id:"unknown"},parse:function(q){this.full_path=q[0].full_path;this.get("folder").reset(q[1].folder_contents);return q}});var b=Backbone.Model.extend({urlRoot:"/api/histories/"});var j=Backbone.Model.extend({url:"/api/histories/"});var o=Backbone.Collection.extend({url:"/api/histories",model:j});var l=Backbone.View.extend({el:"#center",progress:0,progressStep:1,lastSelectedHistory:"",modal:null,folders:null,initialize:function(){this.folders=[];this.queue=jQuery.Deferred();this.queue.resolve()},templateFolder:function(){var q=[];q.push('<div class="library_container" style="width: 90%; margin: auto; margin-top: 2em; ">');q.push('<h3>Data Libraries Beta Test. This is work in progress. Please report problems & ideas via <a href="mailto:galaxy-bugs@bx.psu.edu?Subject=DataLibrariesBeta_Feedback" target="_blank">email</a> and <a href="https://trello.com/c/nwYQNFPK/56-data-library-ui-progressive-display-of-fol…" target="_blank">Trello</a>.</h3>');q.push('<div id="library_folder_toolbar" >');q.push(' <button title="Create New Folder" id="toolbtn_create_folder" class="btn btn-primary" type="button"><span class="fa fa-plus"></span><span class="fa fa-folder-close"></span> folder</button>');q.push(' <button title="Import selected datasets into history" id="toolbtn_bulk_import" class="btn btn-primary" style="display: none; margin-left: 0.5em;" type="button"><span class="fa fa-external-link"></span> to history</button>');q.push(' <div id="toolbtn_dl" class="btn-group" style="margin-left: 0.5em; display: none; ">');q.push(' <button title="Download selected datasets" id="drop_toggle" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">');q.push(' <span class="fa fa-download"></span> download <span class="caret"></span>');q.push(" </button>");q.push(' <ul class="dropdown-menu" role="menu">');q.push(' <li><a href="#/folders/<%= id %>/download/tgz">.tar.gz</a></li>');q.push(' <li><a href="#/folders/<%= id %>/download/tbz">.tar.bz</a></li>');q.push(' <li><a href="#/folders/<%= id %>/download/zip">.zip</a></li>');q.push(" </ul>");q.push(" </div>");q.push("</div>");q.push('<ol class="breadcrumb">');q.push(' <li><a title="Return to the list of libraries" href="#">Libraries</a></li>');q.push(" <% _.each(path, function(path_item) { %>");q.push(" <% if (path_item[0] != id) { %>");q.push(' <li><a title="Return to this folder" href="#/folders/<%- path_item[0] %>"><%- path_item[1] %></a></li> ');q.push("<% } else { %>");q.push(' <li class="active"><span title="You are in this folder"><%- path_item[1] %></span></li>');q.push(" <% } %>");q.push(" <% }); %>");q.push("</ol>");q.push('<table id="folder_table" class="library_table table table-condensed">');q.push(" <thead>");q.push(' <th class="button_heading"></th>');q.push(' <th style="text-align: center; width: 20px; "><input id="select-all-checkboxes" style="margin: 0;" type="checkbox"></th>');q.push(" <th>name</th>");q.push(" <th>data type</th>");q.push(" <th>size</th>");q.push(" <th>date (UTC)</th>");q.push(" </thead>");q.push(" <tbody>");q.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>');q.push(" <td></td>");q.push(" <td></td>");q.push(" <td></td>");q.push(" <td></td>");q.push(" <td></td>");q.push(" </tr>");q.push(" <% _.each(items, function(content_item) { %>");q.push(' <% if (content_item.get("type") === "folder") { %>');q.push(' <tr class="folder_row light" id="<%- content_item.id %>">');q.push(" <td>");q.push(' <span title="Folder" class="fa fa-folder-o"></span>');q.push(" </td>");q.push(" <td></td>");q.push(" <td>");q.push(' <a href="#folders/<%- content_item.id %>"><%- content_item.get("name") %></a>');q.push(' <% if (content_item.get("item_count") === 0) { %>');q.push(' <span class="muted">(empty folder)</span>');q.push(" <% } %>");q.push(" </td>");q.push(" <td>folder</td>");q.push(' <td><%= _.escape(content_item.get("item_count")) %> item(s)</td>');q.push(' <td><%= _.escape(content_item.get("time_updated")) %></td>');q.push(" </tr>");q.push(" <% } else { %>");q.push(' <tr class="dataset_row light" id="<%- content_item.id %>">');q.push(" <td>");q.push(' <span title="Dataset" class="fa fa-file-o"></span>');q.push(" </td>");q.push(' <td style="text-align: center; "><input style="margin: 0;" type="checkbox"></td>');q.push(' <td><a href="#" class="library-dataset"><%- content_item.get("name") %><a></td>');q.push(' <td><%= _.escape(content_item.get("data_type")) %></td>');q.push(' <td><%= _.escape(content_item.get("readable_size")) %></td>');q.push(' <td><%= _.escape(content_item.get("time_updated")) %></td>');q.push(" </tr>");q.push(" <% } %> ");q.push(" <% }); %>");q.push(" ");q.push(" </tbody>");q.push("</table>");q.push("</div>");return q.join("")},templateDatasetModal:function(){var q=[];q.push('<div class="modal_table">');q.push(' <table class="table table-striped table-condensed">');q.push(" <tr>");q.push(' <th scope="row" id="id_row" data-id="<%= _.escape(item.get("ldda_id")) %>">Name</th>');q.push(' <td><%= _.escape(item.get("name")) %></td>');q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Data type</th>');q.push(' <td><%= _.escape(item.get("data_type")) %></td>');q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Genome build</th>');q.push(' <td><%= _.escape(item.get("genome_build")) %></td>');q.push(" </tr>");q.push(' <th scope="row">Size</th>');q.push(" <td><%= _.escape(size) %></td>");q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Date uploaded (UTC)</th>');q.push(' <td><%= _.escape(item.get("date_uploaded")) %></td>');q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Uploaded by</th>');q.push(' <td><%= _.escape(item.get("uploaded_by")) %></td>');q.push(" </tr>");q.push(' <tr scope="row">');q.push(' <th scope="row">Data Lines</th>');q.push(' <td scope="row"><%= _.escape(item.get("metadata_data_lines")) %></td>');q.push(" </tr>");q.push(' <th scope="row">Comment Lines</th>');q.push(' <% if (item.get("metadata_comment_lines") === "") { %>');q.push(' <td scope="row"><%= _.escape(item.get("metadata_comment_lines")) %></td>');q.push(" <% } else { %>");q.push(' <td scope="row">unknown</td>');q.push(" <% } %>");q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Number of Columns</th>');q.push(' <td scope="row"><%= _.escape(item.get("metadata_columns")) %></td>');q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Column Types</th>');q.push(' <td scope="row"><%= _.escape(item.get("metadata_column_types")) %></td>');q.push(" </tr>");q.push(" <tr>");q.push(' <th scope="row">Miscellaneous information</th>');q.push(' <td scope="row"><%= _.escape(item.get("misc_blurb")) %></td>');q.push(" </tr>");q.push(" </table>");q.push(' <pre class="peek">');q.push(" </pre>");q.push("</div>");return q.join("")},templateHistorySelectInModal:function(){var q=[];q.push('<span id="history_modal_combo" style="width:90%; margin-left: 1em; margin-right: 1em; ">');q.push("Select history: ");q.push('<select id="dataset_import_single" name="dataset_import_single" style="width:50%; margin-bottom: 1em; "> ');q.push(" <% _.each(histories, function(history) { %>");q.push(' <option value="<%= _.escape(history.get("id")) %>"><%= _.escape(history.get("name")) %></option>');q.push(" <% }); %>");q.push("</select>");q.push("</span>");return q.join("")},templateBulkImportInModal:function(){var q=[];q.push('<span id="history_modal_combo_bulk" style="width:90%; margin-left: 1em; margin-right: 1em; ">');q.push("Select history: ");q.push('<select id="dataset_import_bulk" name="dataset_import_bulk" style="width:50%; margin-bottom: 1em; "> ');q.push(" <% _.each(histories, function(history) { %>");q.push(' <option value="<%= _.escape(history.get("id")) %>"><%= _.escape(history.get("name")) %></option>');q.push(" <% }); %>");q.push("</select>");q.push("</span>");return q.join("")},templateProgressBar:function(){var q=[];q.push('<div class="import_text">');q.push("Importing selected datasets to history <b><%= _.escape(history_name) %></b>");q.push("</div>");q.push('<div class="progress">');q.push(' <div class="progress-bar progress-bar-import" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 00%;">');q.push(' <span class="completion_span">0% Complete</span>');q.push(" </div>");q.push("</div>");q.push("");return q.join("")},templateNewFolderInModal:function(){tmpl_array=[];tmpl_array.push('<div id="new_folder_modal">');tmpl_array.push("<form>");tmpl_array.push('<input type="text" name="Name" value="" placeholder="Name">');tmpl_array.push('<input type="text" name="Description" value="" placeholder="Description">');tmpl_array.push("</form>");tmpl_array.push("</div>");return tmpl_array.join("")},events:{"click #select-all-checkboxes":"selectAll","click #toolbtn_bulk_import":"modalBulkImport","click #toolbtn_dl":"bulkDownload","click #toolbtn_create_folder":"createFolderFromModal","click .library-dataset":"showDatasetDetails","click .dataset_row":"selectClickedRow"},render:function(q){$("#center").css("overflow","auto");view=this;var s=this;var r=new e({id:q.id});r.url=r.attributes.urlRoot+q.id+"/contents";r.fetch({success:function(t){for(var v=0;v<r.attributes.folder.models.length;v++){var u=r.attributes.folder.models[v];if(u.get("type")==="file"){u.set("readable_size",s.size_to_string(u.get("file_size")))}}var x=r.full_path;var y;if(x.length===1){y=0}else{y=x[x.length-2][0]}var w=_.template(s.templateFolder(),{path:r.full_path,items:r.attributes.folder.models,id:q.id,upper_folder_id:y});s.$el.html(w)},error:function(){m.error("An error occured :(")}})},size_to_string:function(q){var r="";if(q>=100000000000){q=q/100000000000;r="TB"}else{if(q>=100000000){q=q/100000000;r="GB"}else{if(q>=100000){q=q/100000;r="MB"}else{if(q>=100){q=q/100;r="KB"}else{q=q*10;r="b"}}}}return(Math.round(q)/10)+r},showDatasetDetails:function(t){t.preventDefault();var u=$(t.target).parent().parent().parent().attr("id");if(typeof u==="undefined"){u=$(t.target).parent().attr("id")}if(typeof u==="undefined"){u=$(t.target).parent().parent().attr("id")}var s=new i();var r=new o();s.id=u;var q=this;s.fetch({success:function(v){r.fetch({success:function(w){q.renderModalAfterFetch(v,w)},error:function(){m.error("An error occured during fetching histories:(");q.renderModalAfterFetch(v)}})},error:function(){m.error("An error occured during loading dataset details :(")}})},renderModalAfterFetch:function(v,s){var t=this.size_to_string(v.get("file_size"));var u=_.template(this.templateDatasetModal(),{item:v,size:t});var r=this;this.modal=Galaxy.modal;this.modal.show({closing_events:true,title:"Dataset Details",body:u,buttons:{Import:function(){r.importCurrentIntoHistory()},Download:function(){r.downloadCurrent()},Close:function(){r.modal.hide()}}});$(".peek").html(v.get("peek"));if(typeof history.models!==undefined){var q=_.template(this.templateHistorySelectInModal(),{histories:s.models});$(this.modal.elMain).find(".buttons").prepend(q);if(r.lastSelectedHistory.length>0){$(this.modal.elMain).find("#dataset_import_single").val(r.lastSelectedHistory)}}},downloadCurrent:function(){this.modal.disableButton("Import");this.modal.disableButton("Download");var q=[];q.push($("#id_row").attr("data-id"));var r="/api/libraries/datasets/download/uncompressed";var s={ldda_ids:q};folderContentView.processDownload(r,s);this.modal.enableButton("Import");this.modal.enableButton("Download")},importCurrentIntoHistory:function(){this.modal.disableButton("Import");this.modal.disableButton("Download");var s=$(this.modal.elMain).find("select[name=dataset_import_single] option:selected").val();this.lastSelectedHistory=s;var q=$("#id_row").attr("data-id");var t=new b();var r=this;t.url=t.urlRoot+s+"/contents";t.save({content:q,source:"library"},{success:function(){m.success("Dataset imported");r.modal.enableButton("Import");r.modal.enableButton("Download")},error:function(){m.error("An error occured! Dataset not imported. Please try again.");r.modal.enableButton("Import");r.modal.enableButton("Download")}})},selectAll:function(r){var q=r.target.checked;that=this;$(":checkbox").each(function(){this.checked=q;$row=$(this.parentElement.parentElement);(q)?that.makeDarkRow($row):that.makeWhiteRow($row)});this.checkTools()},selectClickedRow:function(r){var t="";var q;var s;if(r.target.localName==="input"){t=r.target;q=$(r.target.parentElement.parentElement);s="input"}else{if(r.target.localName==="td"){t=$("#"+r.target.parentElement.id).find(":checkbox")[0];q=$(r.target.parentElement);s="td"}}if(t.checked){if(s==="td"){t.checked="";this.makeWhiteRow(q)}else{if(s==="input"){this.makeDarkRow(q)}}}else{if(s==="td"){t.checked="selected";this.makeDarkRow(q)}else{if(s==="input"){this.makeWhiteRow(q)}}}this.checkTools()},makeDarkRow:function(q){q.removeClass("light");q.find("a").removeClass("light");q.addClass("dark");q.find("a").addClass("dark");q.find("span").removeClass("fa-file-o");q.find("span").addClass("fa-file")},makeWhiteRow:function(q){q.removeClass("dark");q.find("a").removeClass("dark");q.addClass("light");q.find("a").addClass("light");q.find("span").addClass("fa-file-o");q.find("span").removeClass("fa-file")},checkTools:function(){var q=$("#folder_table").find(":checked");if(q.length>0){$("#toolbtn_bulk_import").show();$("#toolbtn_dl").show()}else{$("#toolbtn_bulk_import").hide();$("#toolbtn_dl").hide()}},modalBulkImport:function(){var r=this;var q=new o();q.fetch({success:function(s){var t=_.template(r.templateBulkImportInModal(),{histories:s.models});r.modal=Galaxy.modal;r.modal.show({closing_events:true,title:"Import into History",body:t,buttons:{Import:function(){r.importAllIntoHistory()},Close:function(){r.modal.hide()}}})},error:function(){m.error("An error occured :(")}})},importAllIntoHistory:function(){this.modal.disableButton("Import");var s=$("select[name=dataset_import_bulk] option:selected").val();var w=$("select[name=dataset_import_bulk] option:selected").text();var y=[];$("#folder_table").find(":checked").each(function(){if(this.parentElement.parentElement.id!=""){y.push(this.parentElement.parentElement.id)}});var x=_.template(this.templateProgressBar(),{history_name:w});$(this.modal.elMain).find(".modal-body").html(x);var t=100/y.length;this.initProgress(t);var q=[];for(var r=y.length-1;r>=0;r--){library_dataset_id=y[r];var u=new b();var v=this;u.url=u.urlRoot+s+"/contents";u.content=library_dataset_id;u.source="library";q.push(u)}this.chainCall(q)},chainCall:function(r){var q=this;var s=r.pop();if(typeof s==="undefined"){m.success("All datasets imported");this.modal.hide();return}var t=$.when(s.save({content:s.content,source:s.source})).done(function(u){q.updateProgress();responses.push(u);q.chainCall(r)})},initProgress:function(q){this.progress=0;this.progressStep=q},updateProgress:function(){this.progress+=this.progressStep;$(".progress-bar-import").width(Math.round(this.progress)+"%");txt_representation=Math.round(this.progress)+"% Complete";$(".completion_span").text(txt_representation)},download:function(q,u){var s=[];$("#folder_table").find(":checked").each(function(){if(this.parentElement.parentElement.id!=""){s.push(this.parentElement.parentElement.id)}});var r="/api/libraries/datasets/download/"+u;var t={ldda_ids:s};this.processDownload(r,t,"get")},processDownload:function(r,s,t){if(r&&s){s=typeof s=="string"?s:$.param(s);var q="";$.each(s.split("&"),function(){var u=this.split("=");q+='<input type="hidden" name="'+u[0]+'" value="'+u[1]+'" />'});$('<form action="'+r+'" method="'+(t||"post")+'">'+q+"</form>").appendTo("body").submit().remove();m.info("Your download will begin soon")}},createFolderFromModal:function(){event.preventDefault();event.stopPropagation();var q=this;this.modal=Galaxy.modal;this.modal.show({closing_events:true,title:"Create New Folder",body:this.templateNewFolderInModal(),buttons:{Create:function(){q.create_new_folder_event()},Close:function(){q.modal.hide();q.modal=null}}})},create_new_folder_event:function(){var q=this.serialize_new_folder();if(this.validate_new_folder(q)){var s=new c();url_items=Backbone.history.fragment.split("/");current_folder_id=url_items[url_items.length-1];s.url=s.urlRoot+"/"+current_folder_id;var r=this;s.save(q,{success:function(t){r.modal.hide();m.success("Folder created");r.render({id:current_folder_id})},error:function(){m.error("An error occured :(")}})}else{m.error("Folder's name is missing")}return false},serialize_new_folder:function(){return{name:$("input[name='Name']").val(),description:$("input[name='Description']").val()}},validate_new_folder:function(q){return q.name!==""}});var a=Backbone.View.extend({el:"#center",events:{"click #create_new_library_btn":"show_library_modal"},modal:null,collection:null,initialize:function(){var q=this;this.collection=new n();this.collection.fetch({success:function(r){q.render()},error:function(s,r){if(r.statusCode().status===403){m.info("Please log in first. Redirecting to login page in 3s.");setTimeout(that.redirectToLogin,3000)}else{m.error("An error occured. Please try again.")}}});$("#center").css("overflow","auto")},render:function(r){var s=this.templateLibraryList();var t=null;var q=false;var u=null;if(typeof r!=="undefined"){q=typeof r.with_deleted!=="undefined"?r.with_deleted:false;u=typeof r.models!=="undefined"?r.models:null}if(this.collection!==null&&u===null){if(q){t=this.collection.models}else{t=this.collection.where({deleted:false})}}else{if(u!==null){t=u}else{t=[]}}this.$el.html(s({libraries:t,order:this.collection.sort_order}))},sortLibraries:function(r,q){if(r==="name"){if(q==="asc"){this.collection.sort_order="asc";this.collection.comparator=function(t,s){if(t.get("name").toLowerCase()>s.get("name").toLowerCase()){return 1}if(s.get("name").toLowerCase()>t.get("name").toLowerCase()){return -1}return 0}}else{if(q==="desc"){this.collection.sort_order="desc";this.collection.comparator=function(t,s){if(t.get("name").toLowerCase()>s.get("name").toLowerCase()){return -1}if(s.get("name").toLowerCase()>t.get("name").toLowerCase()){return 1}return 0}}}this.collection.sort()}},templateLibraryList:function(){tmpl_array=[];tmpl_array.push('<div class="library_container" style="width: 90%; margin: auto; margin-top: 2em; overflow: auto !important; ">');tmpl_array.push("");tmpl_array.push('<h3>Data Libraries Beta Test. This is work in progress. Please report problems & ideas via <a href="mailto:galaxy-bugs@bx.psu.edu?Subject=DataLibrariesBeta_Feedback" target="_blank">email</a> and <a href="https://trello.com/c/nwYQNFPK/56-data-library-ui-progressive-display-of-fol…" target="_blank">Trello</a>.</h3>');tmpl_array.push('<a href="" id="create_new_library_btn" class="btn btn-primary file ">New Library</a>');tmpl_array.push("<% if(libraries.length === 0) { %>");tmpl_array.push("<div>I see no libraries. Why don't you create one?</div>");tmpl_array.push("<% } else{ %>");tmpl_array.push('<table class="library_table table table-condensed">');tmpl_array.push(" <thead>");tmpl_array.push(' <th><a title="Click to reverse order" href="#sort/name/<% if(order==="desc"||order===null){print("asc")}else{print("desc")} %>">name</a><span title="Sorted alphabetically" class="fa fa-sort-alpha-<%- order %>"></span></th>');tmpl_array.push(" <th>description</th>");tmpl_array.push(" <th>synopsis</th> ");tmpl_array.push(" </thead>");tmpl_array.push(" <tbody>");tmpl_array.push(" <% _.each(libraries, function(library) { %>");tmpl_array.push(" <tr>");tmpl_array.push(' <td><a href="#folders/<%- library.get("root_folder_id") %>"><%- library.get("name") %></a></td>');tmpl_array.push(' <td><%= _.escape(library.get("description")) %></td>');tmpl_array.push(' <td><%= _.escape(library.get("synopsis")) %></td>');tmpl_array.push(" </tr>");tmpl_array.push(" <% }); %>");tmpl_array.push(" </tbody>");tmpl_array.push("</table>");tmpl_array.push("<% }%>");tmpl_array.push("</div>");return _.template(tmpl_array.join(""))},templateNewLibraryInModal:function(){tmpl_array=[];tmpl_array.push('<div id="new_library_modal">');tmpl_array.push(" <form>");tmpl_array.push(' <input type="text" name="Name" value="" placeholder="Name">');tmpl_array.push(' <input type="text" name="Description" value="" placeholder="Description">');tmpl_array.push(' <input type="text" name="Synopsis" value="" placeholder="Synopsis">');tmpl_array.push(" </form>");tmpl_array.push("</div>");return tmpl_array.join("")},redirectToHome:function(){window.location="../"},redirectToLogin:function(){window.location="/user/login"},show_library_modal:function(r){r.preventDefault();r.stopPropagation();var q=this;this.modal=Galaxy.modal;this.modal.show({closing_events:true,title:"Create New Library",body:this.templateNewLibraryInModal(),buttons:{Create:function(){q.create_new_library_event()},Close:function(){q.modal.hide()}}})},create_new_library_event:function(){var s=this.serialize_new_library();if(this.validate_new_library(s)){var r=new f();var q=this;r.save(s,{success:function(t){q.collection.add(t);q.modal.hide();q.clear_library_modal();q.render();m.success("Library created")},error:function(){m.error("An error occured :(")}})}else{m.error("Library's name is missing")}return false},clear_library_modal:function(){$("input[name='Name']").val("");$("input[name='Description']").val("");$("input[name='Synopsis']").val("")},serialize_new_library:function(){return{name:$("input[name='Name']").val(),description:$("input[name='Description']").val(),synopsis:$("input[name='Synopsis']").val()}},validate_new_library:function(q){return q.name!==""}});var p=Backbone.Router.extend({routes:{"":"libraries","sort/:sort_by/:order":"sort_libraries","folders/:id":"folder_content","folders/:folder_id/download/:format":"download"}});var g=Backbone.View.extend({initialize:function(){galaxyLibraryview=new a();library_router=new p();folderContentView=new l();library_router.on("route:libraries",function(){galaxyLibraryview.render()});library_router.on("route:sort_libraries",function(r,q){galaxyLibraryview.sortLibraries(r,q);galaxyLibraryview.render()});library_router.on("route:folder_content",function(q){folderContentView.render({id:q})});library_router.on("route:download",function(q,r){if($("#center").find(":checked").length===0){library_router.navigate("folders/"+q,{trigger:true,replace:true})}else{folderContentView.download(q,r);library_router.navigate("folders/"+q,{trigger:false,replace:true})}});Backbone.history.start({pushState:false})}});return{GalaxyApp:g}});
\ No newline at end of file
diff -r 54a2ec26ae5f8f2bf78b8b964d4279533e4a905a -r 5e9c0065ae572bb32ae55591bd5a998abecdf5b5 static/scripts/packed/mvc/ui/ui-portlet.js
--- a/static/scripts/packed/mvc/ui/ui-portlet.js
+++ b/static/scripts/packed/mvc/ui/ui-portlet.js
@@ -1,1 +1,1 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({visible:false,optionsDefault:{title:"",icon:"",buttons:null,body:null,height:null,operations:null,placement:"bottom",overflow:"auto"},$title:null,$content:null,$buttons:null,$operations:null,initialize:function(d){this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.$content=this.$el.find("#content");this.$title=this.$el.find("#title-text");if(this.options.height){this.$el.find("#body").css("height",this.options.height);this.$el.find("#content").css("overflow",this.options.overflow)}this.$buttons=$(this.el).find("#buttons");if(this.options.buttons){var c=this;$.each(this.options.buttons,function(e,f){f.$el.prop("id",e);c.$buttons.append(f.$el)})}else{this.$buttons.remove()}this.$operations=$(this.el).find("#operations");if(this.options.operations){var c=this;$.each(this.options.operations,function(e,f){f.$el.prop("id",e);c.$operations.append(f.$el)})}if(this.options.body){this.append(this.options.body)}},append:function(c){this.$content.append(a.wrap(c))},content:function(){return this.$content},show:function(){this.$el.fadeIn("fast");this.visible=true},hide:function(){this.$el.fadeOut("fast");this.visible=false},enableButton:function(c){this.$buttons.find("#"+c).prop("disabled",false)},disableButton:function(c){this.$buttons.find("#"+c).prop("disabled",true)},hideOperation:function(c){this.$operations.find("#"+c).hide()},showOperation:function(c){this.$operations.find("#"+c).show()},setOperation:function(e,d){var c=this.$operations.find("#"+e);c.off("click");c.on("click",d)},title:function(d){var c=this.$title;if(d){c.html(d)}return c.html()},_template:function(d){var c='<div class="toolForm">';if(d.title||d.icon){c+='<div id="title" class="toolFormTitle" style="overflow:hidden; padding-bottom: 1px;"><div id="operations" style="float: right;"></div><div style="overflow: hidden; margin-bottom: 4px;">';if(d.icon){c+='<i style="padding-top: 3px; float: left; font-size: 1.2em" class="icon fa '+d.icon+'"> </i>'}c+='<div id="title-text" style="padding-top: 2px; float: left;">'+d.title+"</div>";c+="</div></div>"}c+='<div id="body" class="toolFormBody">';if(d.placement=="top"){c+='<div id="buttons" class="buttons" style="height: 50px; padding: 10px;"></div>'}c+='<div id="content" class="content" style="height: inherit; padding: 10px;"></div>';if(d.placement=="bottom"){c+='<div id="buttons" class="buttons" style="height: 50px; padding: 10px;"></div>'}c+="</div></div>";return c}});return{View:b}});
\ No newline at end of file
+define(["utils/utils"],function(a){var b=Backbone.View.extend({visible:false,optionsDefault:{title:"",icon:"",buttons:null,body:null,height:null,operations:null,placement:"bottom",overflow:"auto"},$title:null,$content:null,$buttons:null,$operations:null,initialize:function(d){this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.$content=this.$el.find("#content");this.$title=this.$el.find("#title-text");if(this.options.height){this.$el.find("#body").css("height",this.options.height);this.$el.find("#content").css("overflow",this.options.overflow)}this.$buttons=$(this.el).find("#buttons");if(this.options.buttons){var c=this;$.each(this.options.buttons,function(e,f){f.$el.prop("id",e);c.$buttons.append(f.$el)})}else{this.$buttons.remove()}this.$operations=$(this.el).find("#operations");if(this.options.operations){var c=this;$.each(this.options.operations,function(e,f){f.$el.prop("id",e);c.$operations.append(f.$el)})}if(this.options.body){this.append(this.options.body)}},append:function(c){this.$content.append(a.wrap(c))},content:function(){return this.$content},show:function(){this.$el.fadeIn("fast");this.visible=true},hide:function(){this.$el.fadeOut("fast");this.visible=false},enableButton:function(c){this.$buttons.find("#"+c).prop("disabled",false)},disableButton:function(c){this.$buttons.find("#"+c).prop("disabled",true)},hideOperation:function(c){this.$operations.find("#"+c).hide()},showOperation:function(c){this.$operations.find("#"+c).show()},setOperation:function(e,d){var c=this.$operations.find("#"+e);c.off("click");c.on("click",d)},title:function(d){var c=this.$title;if(d){c.html(d)}return c.html()},_template:function(d){var c='<div class="toolForm">';if(d.title||d.icon){c+='<div id="title" class="toolFormTitle" style="overflow:hidden; padding-bottom: 1px;"><div id="operations" style="float: right;"></div><div style="overflow: hidden; margin-bottom: 4px;">';if(d.icon){c+='<i style="padding-top: 3px; float: left; font-size: 1.2em" class="icon fa '+d.icon+'"> </i>'}c+='<div id="title-text" style="padding-top: 1px; float: left;">'+d.title+"</div>";c+="</div></div>"}c+='<div id="body" class="toolFormBody">';if(d.placement=="top"){c+='<div id="buttons" class="buttons" style="height: 50px; padding: 10px;"></div>'}c+='<div id="content" class="content" style="height: inherit; padding: 10px;"></div>';if(d.placement=="bottom"){c+='<div id="buttons" class="buttons" style="height: 50px; padding: 10px;"></div>'}c+="</div></div>";return c}});return{View:b}});
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
6 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/ed45cbef6347/
Changeset: ed45cbef6347
Branch: model-info
User: Kyle Ellrott
Date: 2014-02-26 23:28:40
Summary: Patch to expose the actual dataset id in the LDDA and HDA to_dict calls (in addition to the instance id).
Affected #: 1 file
diff -r 1c91163d6c768452d106bdc8d843406fd7696ac7 -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -1823,6 +1823,7 @@
rval = dict( id = hda.id,
hda_ldda = 'hda',
uuid = ( lambda uuid: str( uuid ) if uuid else None )( hda.dataset.uuid ),
+ dataset_id = hda.dataset.id,
hid = hda.hid,
file_ext = hda.ext,
peek = ( lambda hda: hda.display_peek() if hda.peek and hda.peek != 'no peek' else None )( hda ),
@@ -2251,6 +2252,7 @@
file_size = 0
rval = dict( id = ldda.id,
hda_ldda = 'ldda',
+ dataset_id = ldda.dataset.id,
model_class = self.__class__.__name__,
name = ldda.name,
deleted = ldda.deleted,
https://bitbucket.org/galaxy/galaxy-central/commits/95050c5d33b9/
Changeset: 95050c5d33b9
Branch: model-info
User: kellrott
Date: 2014-03-04 22:04:28
Summary: Default merge
Affected #: 69 files
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa config/plugins/visualizations/charts/static/app.js
--- a/config/plugins/visualizations/charts/static/app.js
+++ b/config/plugins/visualizations/charts/static/app.js
@@ -61,9 +61,6 @@
// events
var self = this;
- this.config.on('change:current_view', function() {
- self._showCurrent();
- });
this.config.on('change:title', function() {
self._refreshTitle();
});
@@ -86,11 +83,6 @@
this.portlet.title('Charts' + title);
},
- // current view
- _showCurrent: function() {
-
- },
-
// execute command
execute: function(options) {
},
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa config/plugins/visualizations/charts/static/library/table.js
--- a/config/plugins/visualizations/charts/static/library/table.js
+++ b/config/plugins/visualizations/charts/static/library/table.js
@@ -33,7 +33,15 @@
this.options = Utils.merge(options, this.optionsDefault);
// create new element
- this.setElement(this._template(options));
+ var $el = $(this._template(options));
+
+ // link sub-elements
+ this.$thead = $el.find('thead');
+ this.$tbody = $el.find('tbody');
+ this.$tmessage = $el.find('tmessage');
+
+ // set element
+ this.setElement($el);
// initialize row
this.row = $('<tr></tr>');
@@ -49,7 +57,7 @@
// header
appendHeader: function() {
// append header row
- $(this.el).find('thead').append(this.row);
+ this.$thead.append(this.row);
// row
this.row = $('<tr></tr>');
@@ -74,14 +82,14 @@
// remove
remove: function(id) {
- $(this.el).find('#' + id).remove();
+ this.$tbody.find('#' + id).remove();
this.row_count--;
this._refresh();
},
// remove
removeAll: function() {
- $(this.el).find('tbody').html('');
+ this.$tbody.html('');
this.row_count = 0;
this._refresh();
},
@@ -89,18 +97,18 @@
// value
value: function(new_value) {
// get current id/value
- this.before = this.$el.find('.current').attr('id');
+ this.before = this.$tbody.find('.current').attr('id');
// check if new_value is defined
if (new_value !== undefined) {
- this.$el.find('tr').removeClass('current');
+ this.$tbody.find('tr').removeClass('current');
if (new_value) {
- this.$el.find('#' + new_value).addClass('current');
+ this.$tbody.find('#' + new_value).addClass('current');
}
}
// get current id/value
- var after = this.$el.find('.current').attr('id');
+ var after = this.$tbody.find('.current').attr('id');
if(after === undefined) {
return null;
} else {
@@ -114,6 +122,11 @@
}
},
+ // size
+ size: function() {
+ return this.$tbody.find('tr').length;
+ },
+
// commit
_commit: function(id, prepend) {
// add
@@ -121,9 +134,9 @@
// add row
if (prepend) {
- $(this.el).find('tbody').prepend(this.row);
+ this.$tbody.prepend(this.row);
} else {
- $(this.el).find('tbody').append(this.row);
+ this.$tbody.append(this.row);
}
// row
@@ -161,9 +174,9 @@
// refresh
_refresh: function() {
if (this.row_count == 0) {
- this.$el.find('tmessage').show();
+ this.$tmessage.show();
} else {
- this.$el.find('tmessage').hide();
+ this.$tmessage.hide();
}
},
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa config/plugins/visualizations/charts/static/library/ui.js
--- a/config/plugins/visualizations/charts/static/library/ui.js
+++ b/config/plugins/visualizations/charts/static/library/ui.js
@@ -111,7 +111,7 @@
if (options.title) {
str += '<div style="margin-right: 5px; margin-left: 5px;">' +
'<i class="icon fa ' + options.icon + '"/> ' +
- '<span style="position: relative; font-size: 0.8em; top: -1px;">' + options.title + '</span>' +
+ '<span style="position: relative; font-size: 0.8em; font-weight: normal; top: -1px;">' + options.title + '</span>' +
'</div>';
} else {
str += '<i class="icon fa ' + options.icon + '"/>';
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa config/plugins/visualizations/charts/static/views/chart.js
--- a/config/plugins/visualizations/charts/static/views/chart.js
+++ b/config/plugins/visualizations/charts/static/views/chart.js
@@ -96,6 +96,10 @@
// save chart
self._saveChart();
+
+ // show viewport
+ self.hide();
+ self.app.charts_view.$el.show();
}
}),
'back' : new Ui.ButtonIcon({
@@ -103,7 +107,7 @@
tooltip : 'Return to Viewer',
title : 'Return',
onclick : function() {
- self.$el.hide();
+ self.hide();
self.app.charts_view.$el.show();
}
})
@@ -161,7 +165,7 @@
self.tabs.showOperation('back');
});
this.app.charts.on('remove', function(chart) {
- if (self.app.charts.length == 1) {
+ if (self.app.charts.length == 0) {
self.tabs.hideOperation('back');
}
});
@@ -187,6 +191,12 @@
this._resetChart();
},
+ // hide
+ hide: function() {
+ $('.tooltip').hide();
+ this.$el.hide();
+ },
+
// update
_refreshLabels: function() {
var self = this;
@@ -275,12 +285,6 @@
// update chart model
current.copy(this.chart);
-
- // hide
- this.$el.hide();
-
- // update main
- this.app.charts_view.$el.show();
}
});
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa config/plugins/visualizations/charts/static/views/charts.js
--- a/config/plugins/visualizations/charts/static/views/charts.js
+++ b/config/plugins/visualizations/charts/static/views/charts.js
@@ -19,7 +19,7 @@
self.app.chart.copy(chart);
// hide this element
- self.$el.hide();
+ self.hide();
// show chart view
self.app.chart_view.$el.show();
@@ -44,17 +44,19 @@
height : 100,
operations : {
'new' : new Ui.ButtonIcon({
- icon : 'fa-plus',
- tooltip: 'Create',
+ icon : 'fa-magic',
+ tooltip: 'Create a new Chart',
+ title: 'New',
onclick: function() {
- self.$el.hide();
+ self.hide();
self.app.chart.reset();
self.app.chart_view.$el.show();
}
}),
'delete' : new Ui.ButtonIcon({
- icon : 'fa-minus',
- tooltip: 'Delete',
+ icon : 'fa-trash-o',
+ tooltip: 'Delete this Chart',
+ title: 'Delete',
onclick: function() {
// check if element has been selected
@@ -109,6 +111,12 @@
});
},
+ // hide
+ hide: function() {
+ $('.tooltip').hide();
+ this.$el.hide();
+ },
+
// append
append : function($el) {
this.$el.append(Utils.wrap(''));
@@ -134,6 +142,15 @@
_removeChart: function(chart) {
// remove from to table
this.table.remove(chart.id);
+
+ // check if table is empty
+ if (this.table.size() == 0) {
+ this.hide();
+ this.app.chart_view.$el.show();
+ } else {
+ // select available chart
+ this.table.value(this.app.charts.last().id);
+ }
}
});
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa config/plugins/visualizations/charts/static/views/viewport.js
--- a/config/plugins/visualizations/charts/static/views/viewport.js
+++ b/config/plugins/visualizations/charts/static/views/viewport.js
@@ -31,7 +31,8 @@
operations : {
'edit' : new Ui.ButtonIcon({
icon : 'fa-gear',
- tooltip : 'Configure'
+ tooltip : 'Customize Chart',
+ title : 'Customize'
})
}
});
@@ -80,7 +81,7 @@
self.app.chart.copy(chart);
// show edit
- self.app.charts_view.$el.hide();
+ self.app.charts_view.hide();
self.app.chart_view.$el.show();
});
@@ -89,6 +90,12 @@
}
},
+ // hide
+ hide: function() {
+ $('.tooltip').hide();
+ this.$el.hide();
+ },
+
// add
_addChart: function(chart) {
// link this
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/config.py
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -207,6 +207,7 @@
self.log_actions = string_as_bool( kwargs.get( 'log_actions', 'False' ) )
self.log_events = string_as_bool( kwargs.get( 'log_events', 'False' ) )
self.sanitize_all_html = string_as_bool( kwargs.get( 'sanitize_all_html', True ) )
+ self.serve_xss_vulnerable_mimetypes = string_as_bool( kwargs.get( 'serve_xss_vulnerable_mimetypes', False ) )
self.enable_old_display_applications = string_as_bool( kwargs.get( "enable_old_display_applications", "True" ) )
self.ucsc_display_sites = kwargs.get( 'ucsc_display_sites', "main,test,archaea,ucla" ).lower().split(",")
self.gbrowse_display_sites = kwargs.get( 'gbrowse_display_sites', "modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225" ).lower().split(",")
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/datatypes/data.py
--- a/lib/galaxy/datatypes/data.py
+++ b/lib/galaxy/datatypes/data.py
@@ -21,6 +21,12 @@
eggs.require( "Paste" )
import paste
+XSS_VULNERABLE_MIME_TYPES = [
+ 'image/svg+xml', # Unfiltered by Galaxy and may contain JS that would be executed by some browsers.
+ 'application/xml', # Some browsers will evalute SVG embedded JS in such XML documents.
+]
+DEFAULT_MIME_TYPE = 'text/plain' # Vulnerable mime types will be replaced with this.
+
log = logging.getLogger(__name__)
comptypes=[] # Is this being used anywhere, why was this here? -JohnC
@@ -334,11 +340,12 @@
mime = trans.app.datatypes_registry.get_mimetype_by_extension( ".".split( file_path )[-1] )
except:
mime = "text/plain"
- trans.response.set_content_type( mime )
+ self._clean_and_set_mime_type( trans, mime )
return open( file_path )
else:
return trans.show_error_message( "Could not find '%s' on the extra files path %s." % ( filename, file_path ) )
- trans.response.set_content_type(data.get_mime())
+ self._clean_and_set_mime_type( trans, data.get_mime() )
+
trans.log_event( "Display dataset id: %s" % str( data.id ) )
from galaxy import datatypes #DBTODO REMOVE THIS AT REFACTOR
if to_ext or isinstance(data.datatype, datatypes.binary.Binary): # Saving the file, or binary file
@@ -624,6 +631,12 @@
dataset_source = dataproviders.dataset.DatasetDataProvider( dataset )
return dataproviders.chunk.Base64ChunkDataProvider( dataset_source, **settings )
+ def _clean_and_set_mime_type(self, trans, mime):
+ if mime.lower() in XSS_VULNERABLE_MIME_TYPES:
+ if not getattr( trans.app.config, "serve_xss_vulnerable_mimetypes", True ):
+ mime = DEFAULT_MIME_TYPE
+ trans.response.set_content_type( mime )
+
@dataproviders.decorators.has_dataproviders
class Text( Data ):
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/jobs/__init__.py
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -181,7 +181,7 @@
if tools is not None:
for tool in self.__findall_with_required(tools, 'tool'):
# There can be multiple definitions with identical ids, but different params
- id = tool.get('id').lower()
+ id = tool.get('id').lower().rstrip('/')
if id not in self.tools:
self.tools[id] = list()
self.tools[id].append(JobToolConfiguration(**dict(tool.items())))
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/jobs/runners/cli_job/torque.py
--- a/lib/galaxy/jobs/runners/cli_job/torque.py
+++ b/lib/galaxy/jobs/runners/cli_job/torque.py
@@ -131,4 +131,5 @@
def __get_job_state(self, state):
return { 'E' : job_states.RUNNING,
'R' : job_states.RUNNING,
- 'Q' : job_states.QUEUED }.get(state, state)
+ 'Q' : job_states.QUEUED,
+ 'C' : job_states.OK }.get(state, state)
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -19,6 +19,7 @@
import time
from string import Template
from itertools import ifilter
+from itertools import chain
import galaxy.datatypes
import galaxy.datatypes.registry
@@ -964,24 +965,37 @@
"""
Fetch filtered list of contents of history.
"""
- python_filter = None
+ default_contents_types = [
+ 'dataset',
+ ]
+ types = kwds.get('types', default_contents_types)
+ iters = []
+ if 'dataset' in types:
+ iters.append( self.__dataset_contents_iter( **kwds ) )
+ return galaxy.util.merge_sorted_iterables( operator.attrgetter( "hid" ), *iters )
+
+ def __dataset_contents_iter(self, **kwds):
+ return self.__filter_contents( HistoryDatasetAssociation, **kwds )
+
+ def __filter_contents( self, content_class, **kwds ):
db_session = object_session( self )
assert db_session != None
- query = db_session.query( HistoryDatasetAssociation ).filter( HistoryDatasetAssociation.table.c.history_id == self.id )
- query = query.order_by( HistoryDatasetAssociation.table.c.hid.asc() )
+ query = db_session.query( content_class ).filter( content_class.table.c.history_id == self.id )
+ query = query.order_by( content_class.table.c.hid.asc() )
+ python_filter = None
deleted = galaxy.util.string_as_bool_or_none( kwds.get( 'deleted', None ) )
if deleted is not None:
- query = query.filter( HistoryDatasetAssociation.deleted == deleted )
+ query = query.filter( content_class.deleted == deleted )
visible = galaxy.util.string_as_bool_or_none( kwds.get( 'visible', None ) )
if visible is not None:
- query = query.filter( HistoryDatasetAssociation.visible == visible )
+ query = query.filter( content_class.visible == visible )
if 'ids' in kwds:
ids = kwds['ids']
max_in_filter_length = kwds.get('max_in_filter_length', MAX_IN_FILTER_LENGTH)
if len(ids) < max_in_filter_length:
- query = query.filter( HistoryDatasetAssociation.id.in_(ids) )
+ query = query.filter( content_class.id.in_(ids) )
else:
- python_filter = lambda hda: hda.id in ids
+ python_filter = lambda content: content.id in ids
if python_filter:
return ifilter(python_filter, query)
else:
@@ -2128,6 +2142,7 @@
parent_library_id = self.folder.parent_library.id,
folder_id = self.folder_id,
model_class = self.__class__.__name__,
+ state = ldda.state,
name = ldda.name,
file_name = ldda.file_name,
uploaded_by = ldda.user.email,
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -1644,18 +1644,7 @@
# Also collect children via association object
dataset_collector( hda.children, hid )
dataset_collector( history.active_datasets_children_and_roles, None )
-
- set_selected = field.get_selected( return_label=True, return_value=True, multi=False ) is not None
- # Ensure than an item is always selected
- if self.optional:
- if set_selected:
- field.add_option( "Selection is Optional", 'None', False )
- else:
- field.add_option( "Selection is Optional", 'None', True )
- elif not set_selected and bool( field.options ):
- # Select the last item
- a, b, c = field.options[-1]
- field.options[-1] = a, b, True
+ self._ensure_selection( field )
return field
def get_initial_value( self, trans, context, history=None ):
@@ -1835,6 +1824,19 @@
assert history is not None, "%s requires a history" % class_name
return history
+ def _ensure_selection( self, field ):
+ set_selected = field.get_selected( return_label=True, return_value=True, multi=False ) is not None
+ # Ensure than an item is always selected
+ if self.optional:
+ if set_selected:
+ field.add_option( "Selection is Optional", 'None', False )
+ else:
+ field.add_option( "Selection is Optional", 'None', True )
+ elif not set_selected and bool( field.options ):
+ # Select the last item
+ a, b, c = field.options[-1]
+ field.options[-1] = a, b, True
+
class HiddenDataToolParameter( HiddenToolParameter, DataToolParameter ):
"""
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/util/__init__.py
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -427,6 +427,59 @@
return os.path.commonprefix( [ file, directory ] ) == directory
+def merge_sorted_iterables( operator, *iterables ):
+ """
+
+ >>> operator = lambda x: x
+ >>> list( merge_sorted_iterables( operator, [1,2,3], [4,5] ) )
+ [1, 2, 3, 4, 5]
+ >>> list( merge_sorted_iterables( operator, [4, 5], [1,2,3] ) )
+ [1, 2, 3, 4, 5]
+ >>> list( merge_sorted_iterables( operator, [1, 4, 5], [2], [3] ) )
+ [1, 2, 3, 4, 5]
+ """
+ first_iterable = iterables[ 0 ]
+ if len( iterables ) == 1:
+ for el in first_iterable:
+ yield el
+ else:
+ for el in __merge_two_sorted_iterables(
+ operator,
+ iter( first_iterable ),
+ merge_sorted_iterables( operator, *iterables[ 1: ] )
+ ):
+ yield el
+
+
+def __merge_two_sorted_iterables( operator, iterable1, iterable2 ):
+ unset = object()
+ continue_merge = True
+ next_1 = unset
+ next_2 = unset
+ while continue_merge:
+ try:
+ if next_1 is unset:
+ next_1 = next( iterable1 )
+ if next_2 is unset:
+ next_2 = next( iterable2 )
+ if operator( next_2 ) < operator( next_1 ):
+ yield next_2
+ next_2 = unset
+ else:
+ yield next_1
+ next_1 = unset
+ except StopIteration:
+ continue_merge = False
+ if next_1 is not unset:
+ yield next_1
+ if next_2 is not unset:
+ yield next_2
+ for el in iterable1:
+ yield el
+ for el in iterable2:
+ yield el
+
+
class Params( object ):
"""
Stores and 'sanitizes' parameters. Alphanumeric characters and the
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/visualization/genomes.py
--- a/lib/galaxy/visualization/genomes.py
+++ b/lib/galaxy/visualization/genomes.py
@@ -210,7 +210,7 @@
rval = self.genomes[ dbkey ]
return rval
- def get_dbkeys( self, trans, chrom_info=False ):
+ def get_dbkeys( self, trans, chrom_info=False, **kwd ):
""" Returns all known dbkeys. If chrom_info is True, only dbkeys with
chromosome lengths are returned. """
dbkeys = []
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/webapps/galaxy/api/history_contents.py
--- a/lib/galaxy/webapps/galaxy/api/history_contents.py
+++ b/lib/galaxy/webapps/galaxy/api/history_contents.py
@@ -33,6 +33,9 @@
:param history_id: encoded id string of the HDA's History
:type ids: str
:param ids: (optional) a comma separated list of encoded `HDA` ids
+ :param types: (optional) kinds of contents to index (currently just
+ dataset, but dataset_collection will be added shortly).
+ :type types: str
:rtype: list
:returns: dictionaries containing summary or detailed HDA information
@@ -51,7 +54,16 @@
else:
history = self.get_history( trans, history_id, check_ownership=True, check_accessible=True )
- contents_kwds = {}
+ # Allow passing in type or types - for continuity rest of methods
+ # take in type - but this one can be passed multiple types and
+ # type=dataset,dataset_collection is a bit silly.
+ types = kwd.get( 'type', kwd.get( 'types', None ) ) or []
+ if types:
+ types = util.listify(types)
+ else:
+ types = [ 'dataset' ]
+
+ contents_kwds = {'types': types}
if ids:
ids = map( lambda id: trans.security.decode_id( id ), ids.split( ',' ) )
contents_kwds[ 'ids' ] = ids
@@ -66,13 +78,14 @@
if details and details != 'all':
details = util.listify( details )
- for hda in history.contents_iter( **contents_kwds ):
- encoded_hda_id = trans.security.encode_id( hda.id )
- detailed = details == 'all' or ( encoded_hda_id in details )
- if detailed:
- rval.append( self._detailed_hda_dict( trans, hda ) )
- else:
- rval.append( self._summary_hda_dict( trans, history_id, hda ) )
+ for content in history.contents_iter( **contents_kwds ):
+ if isinstance(content, trans.app.model.HistoryDatasetAssociation):
+ encoded_content_id = trans.security.encode_id( content.id )
+ detailed = details == 'all' or ( encoded_content_id in details )
+ if detailed:
+ rval.append( self._detailed_hda_dict( trans, content ) )
+ else:
+ rval.append( self._summary_hda_dict( trans, history_id, content ) )
except Exception, e:
# for errors that are not specific to one hda (history lookup or summary list)
rval = "Error in history API at listing contents: " + str( e )
@@ -138,6 +151,13 @@
:returns: dictionary containing detailed HDA information
.. seealso:: :func:`galaxy.web.base.controller.UsesHistoryDatasetAssociationMixin.get_hda_dict`
"""
+ contents_type = kwd.get('type', 'dataset')
+ if contents_type == 'dataset':
+ return self.__show_dataset( trans, id, history_id, **kwd )
+ else:
+ return self.__handle_unknown_contents_type( trans, contents_type )
+
+ def __show_dataset( self, trans, id, history_id, **kwd ):
try:
hda = self.get_history_dataset_association_from_ids( trans, id, history_id )
hda_dict = self.get_hda_dict( trans, hda )
@@ -178,11 +198,6 @@
#TODO: copy existing, accessible hda - dataset controller, copy_datasets
#TODO: convert existing, accessible hda - model.DatasetInstance(or hda.datatype).get_converter_types
# check parameters
- source = payload.get('source', None)
- content = payload.get('content', None)
- if source not in ['library', 'hda'] or content is None:
- trans.response.status = 400
- return "Please define the source ('library' or 'hda') and the content."
# retrieve history
try:
history = self.get_history( trans, history_id, check_ownership=True, check_accessible=False )
@@ -190,6 +205,18 @@
# no way to tell if it failed bc of perms or other (all MessageExceptions)
trans.response.status = 500
return str( e )
+ type = payload.get('type', 'dataset')
+ if type == 'dataset':
+ return self.__create_dataset( trans, history, payload, **kwd )
+ else:
+ return self.__handle_unknown_contents_type( trans, type )
+
+ def __create_dataset( self, trans, history, payload, **kwd ):
+ source = payload.get('source', None)
+ content = payload.get('content', None)
+ if source not in ['library', 'hda'] or content is None:
+ trans.response.status = 400
+ return "Please define the source ('library' or 'hda') and the content."
# copy from library dataset
if source == 'library':
# get library data set
@@ -221,7 +248,7 @@
return str( msg_exc )
except Exception, exc:
trans.response.status = 500
- log.exception( "history: %s, source: %s, content: %s", history_id, source, content )
+ log.exception( "history: %s, source: %s, content: %s", trans.security.encode_id(history.id), source, content )
return str( exc )
data_copy=hda.copy( copy_children=True )
result=history.add_dataset( data_copy )
@@ -255,6 +282,13 @@
any values that were different from the original and, therefore, updated
"""
#TODO: PUT /api/histories/{encoded_history_id} payload = { rating: rating } (w/ no security checks)
+ contents_type = kwd.get('type', 'dataset')
+ if contents_type == "dataset":
+ return self.__update_dataset( trans, history_id, id, payload, **kwd )
+ else:
+ return self.__handle_unknown_contents_type( contents_type )
+
+ def __update_dataset( self, trans, history_id, id, payload, **kwd ):
changed = {}
try:
# anon user
@@ -323,6 +357,13 @@
* deleted: if the history was marked as deleted,
* purged: if the history was purged
"""
+ contents_type = kwd.get('type', 'dataset')
+ if contents_type == "dataset":
+ return self.__delete_dataset( trans, history_id, id, purge=purge, **kwd )
+ else:
+ return self.__handle_unknown_contents_type( trans, contents_type )
+
+ def __delete_dataset( self, trans, history_id, id, purge, **kwd ):
# get purge from the query or from the request body payload (a request body is optional here)
purge = util.string_as_bool( purge )
if kwd.get( 'payload', None ):
@@ -407,3 +448,8 @@
pass
#log.warn( 'unknown key: %s', str( key ) )
return validated_payload
+
+ def __handle_unknown_contents_type( self, trans, contents_type ):
+ # TODO: raise a message exception instead of setting status and returning dict.
+ trans.response.status = 400
+ return { 'error': 'Unknown contents type %s' % type }
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/webapps/galaxy/api/jobs.py
--- a/lib/galaxy/webapps/galaxy/api/jobs.py
+++ b/lib/galaxy/webapps/galaxy/api/jobs.py
@@ -12,6 +12,7 @@
from galaxy.web.base.controller import UsesHistoryDatasetAssociationMixin
from galaxy.web.base.controller import UsesLibraryMixinItems
from galaxy import exceptions
+from galaxy import util
import logging
log = logging.getLogger( __name__ )
@@ -22,7 +23,7 @@
@expose_api
def index( self, trans, **kwd ):
"""
- index( trans, state=None )
+ index( trans, state=None, tool_id=None, history_id=None )
* GET /api/jobs:
return jobs for current user
@@ -31,6 +32,12 @@
Valid Galaxy job states include:
'new', 'upload', 'waiting', 'queued', 'running', 'ok', 'error', 'paused', 'deleted', 'deleted_new'
+ :type tool_id: string or list
+ :param tool_id: limit listing of jobs to those that match one of the included tool_ids. If none, all are returned.
+
+ :type history_id: string
+ :param history_id: limit listing of jobs to those that match the history_id. If none, all are returned.
+
:rtype: list
:returns: list of dictionaries containing summary job information
"""
@@ -39,14 +46,30 @@
query = trans.sa_session.query( trans.app.model.Job ).filter(
trans.app.model.Job.user == trans.user
)
- if state is not None:
- if isinstance( state, basestring ):
- query = query.filter( trans.app.model.Job.state == state )
- elif isinstance( state, list ):
- t = []
- for s in state:
- t.append( trans.app.model.Job.state == s )
- query = query.filter( or_( *t ) )
+
+ def build_and_apply_filters( query, objects, filter_func ):
+ if objects is not None:
+ if isinstance( objects, basestring ):
+ query = query.filter( filter_func( objects ) )
+ elif isinstance( objects, list ):
+ t = []
+ for obj in objects:
+ t.append( filter_func( obj ) )
+ query = query.filter( or_( *t ) )
+ return query
+
+ query = build_and_apply_filters( query, state, lambda s: trans.app.model.Job.state == s )
+
+ query = build_and_apply_filters( query, kwd.get( 'tool_id', None ), lambda t: trans.app.model.Job.tool_id == t )
+ query = build_and_apply_filters( query, kwd.get( 'tool_id_like', None ), lambda t: trans.app.model.Job.tool_id.like(t) )
+
+ history_id = kwd.get( 'history_id', None )
+ if history_id is not None:
+ try:
+ decoded_history_id = trans.security.decode_id(history_id)
+ query = query.filter( trans.app.model.Job.history_id == decoded_history_id )
+ except:
+ raise exceptions.ObjectAttributeInvalidException()
out = []
for job in query.order_by(
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/webapps/galaxy/api/libraries.py
--- a/lib/galaxy/webapps/galaxy/api/libraries.py
+++ b/lib/galaxy/webapps/galaxy/api/libraries.py
@@ -130,6 +130,7 @@
new_library['description'] = description
new_library['synopsis'] = synopsis
new_library['id'] = encoded_id
+ new_library['root_folder_id'] = trans.security.encode_id( root_folder.id )
return new_library
def edit( self, trans, encoded_id, payload, **kwd ):
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/webapps/galaxy/api/workflows.py
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -79,7 +79,7 @@
user=trans.user ).join( 'stored_workflow' ).filter(
trans.app.model.StoredWorkflow.deleted == False ).order_by(
desc( trans.app.model.StoredWorkflow.update_time ) ).all():
- item = wf_sa.stored_workflow.to_dict( value_mapper={ 'id': trans.security.encode_id })
+ item = wf_sa.stored_workflow.to_dict( value_mapper={ 'id': trans.security.encode_id } )
encoded_id = trans.security.encode_id(wf_sa.stored_workflow.id)
item['url'] = url_for( 'workflow', id=encoded_id )
rval.append(item)
@@ -127,6 +127,8 @@
steps[step.id] = {'id': step.id,
'type': step.type,
'tool_id': step.tool_id,
+ 'tool_version': step.tool_version,
+ 'tool_inputs': step.tool_inputs,
'input_steps': {}}
for conn in step.input_connections:
steps[step.id]['input_steps'][conn.input_name] = {'source_step': conn.output_step_id,
@@ -139,17 +141,38 @@
"""
POST /api/workflows
- We're not creating workflows from the api. Just execute for now.
+ We're not creating workflows from the api. Just execute for now.
- However, we will import them if installed_repository_file is specified
+ However, we will import them if installed_repository_file is specified.
+
+ :param installed_repository_file The path of a workflow to import. Either workflow_id or installed_repository_file must be specified
+ :type installed_repository_file str
+
+ :param workflow_id: an existing workflow id. Either workflow_id or installed_repository_file must be specified
+ :type workflow_id: str
+
+ :param parameters: See _update_step_parameters()
+ :type parameters: dict
+
+ :param ds_map: A dictionary mapping each input step id to a dictionary with 2 keys: 'src' (which can be 'ldda', 'ld' or 'hda') and 'id' (which should be the id of a LibraryDatasetDatasetAssociation, LibraryDataset or HistoryDatasetAssociation respectively)
+ :type ds_map: dict
+
+ :param no_add_to_history: if present in the payload with any value, the input datasets will not be added to the selected history
+ :type no_add_to_history: str
+
+ :param history: Either the name of a new history or "hist_id=HIST_ID" where HIST_ID is the id of an existing history
+ :type history: str
+
+ :param replacement_params: A dictionary used when renaming datasets
+ :type replacement_params: dict
"""
# Pull parameters out of payload.
- workflow_id = payload['workflow_id']
+ workflow_id = payload.get('workflow_id', None)
param_map = payload.get('parameters', {})
- ds_map = payload['ds_map']
+ ds_map = payload.get('ds_map', {})
add_to_history = 'no_add_to_history' not in payload
- history_param = payload['history']
+ history_param = payload.get('history', '')
# Get/create workflow.
if not workflow_id:
@@ -175,6 +198,20 @@
return("Workflow is not owned by or shared with current user")
workflow = stored_workflow.latest_workflow
+ # Sanity checks.
+ if not workflow:
+ trans.response.status = 400
+ return "Workflow not found."
+ if len( workflow.steps ) == 0:
+ trans.response.status = 400
+ return "Workflow cannot be run because it does not have any steps"
+ if workflow.has_cycles:
+ trans.response.status = 400
+ return "Workflow cannot be run because it contains cycles"
+ if workflow.has_errors:
+ trans.response.status = 400
+ return "Workflow cannot be run because of validation errors in some steps"
+
# Get target history.
if history_param.startswith('hist_id='):
#Passing an existing history to use.
@@ -210,7 +247,7 @@
else:
trans.response.status = 400
return "Unknown dataset source '%s' specified." % ds_map[k]['src']
- if add_to_history and hda.history != history:
+ if add_to_history and hda.history != history:
hda = hda.copy()
history.add_dataset(hda)
ds_map[k]['hda'] = hda
@@ -218,22 +255,7 @@
trans.response.status = 400
return "Invalid Dataset '%s' Specified" % ds_map[k]['id']
- # Sanity checks.
- if not workflow:
- trans.response.status = 400
- return "Workflow not found."
- if len( workflow.steps ) == 0:
- trans.response.status = 400
- return "Workflow cannot be run because it does not have any steps"
- if workflow.has_cycles:
- trans.response.status = 400
- return "Workflow cannot be run because it contains cycles"
- if workflow.has_errors:
- trans.response.status = 400
- return "Workflow cannot be run because of validation errors in some steps"
-
# Build the state for each step
- rval = {}
for step in workflow.steps:
step_errors = None
input_connections_by_name = {}
@@ -260,7 +282,7 @@
trans.response.status = 400
return "Workflow cannot be run because of step upgrade messages: %s" % step.upgrade_messages
else:
- # This is an input step. Make sure we have an available input.
+ # This is an input step. Make sure we have an available input.
if step.type == 'data_input' and str(step.id) not in ds_map:
trans.response.status = 400
return "Workflow cannot be run because an expected input step '%s' has no input dataset." % step.id
@@ -268,12 +290,7 @@
step.state = step.module.get_runtime_state()
# Run each step, connecting outputs to inputs
- outputs = util.odict.odict()
- rval['history'] = trans.security.encode_id(history.id)
- rval['outputs'] = []
-
replacement_dict = payload.get('replacement_params', {})
-
outputs = invoke(
trans=trans,
workflow=workflow,
@@ -285,6 +302,9 @@
# Build legacy output - should probably include more information from
# outputs.
+ rval = {}
+ rval['history'] = trans.security.encode_id(history.id)
+ rval['outputs'] = []
for step in workflow.steps:
if step.type == 'tool' or step.type is None:
for v in outputs[ step.id ].itervalues():
@@ -387,7 +407,7 @@
"""
# Pull parameters out of payload.
workflow_id = payload.get('workflow_id', None)
- if workflow_id == None:
+ if workflow_id is None:
raise exceptions.ObjectAttributeMissingException( "Missing required parameter 'workflow_id'." )
try:
stored_workflow = self.get_stored_workflow( trans, workflow_id, check_ownership=False )
@@ -452,7 +472,7 @@
if trans.sa_session.query(trans.app.model.StoredWorkflowUserShareAssociation).filter_by(user=trans.user, stored_workflow=stored_workflow).count() == 0:
raise exceptions.ItemOwnershipException()
results = trans.sa_session.query(self.app.model.WorkflowInvocation).filter(self.app.model.WorkflowInvocation.workflow_id==stored_workflow.latest_workflow_id)
- results = results.filter(self.app.model.WorkflowInvocation.id == trans.security.decode_id(usage_id))
+ results = results.filter(self.app.model.WorkflowInvocation.id == trans.security.decode_id(usage_id))
out = results.first()
if out is not None:
return self.encode_all_ids( trans, out.to_dict('element'), True)
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/webapps/galaxy/buildapp.py
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -75,6 +75,19 @@
webapp.add_api_controllers( 'galaxy.webapps.galaxy.api', app )
+ valid_history_contents_types = [
+ 'dataset',
+ ]
+ # This must come before history contents below.
+ # Accesss HDA details via histories/:history_id/contents/datasets/:hda_id
+ webapp.mapper.resource( "typed_content",
+ "{type:%s}s" % "|".join( valid_history_contents_types ),
+ name_prefix="history_content_",
+ controller='history_contents',
+ path_prefix='/api/histories/:history_id/contents',
+ parent_resources=dict( member_name='history', collection_name='histories' ),
+ )
+ # Legacy access to HDA details via histories/:history_id/contents/:hda_id
webapp.mapper.resource( 'content',
'contents',
controller='history_contents',
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/webapps/galaxy/controllers/biostar.py
--- a/lib/galaxy/webapps/galaxy/controllers/biostar.py
+++ b/lib/galaxy/webapps/galaxy/controllers/biostar.py
@@ -7,6 +7,7 @@
import base64
from galaxy.util import json
import hmac
+import urlparse
# Slugifying from Armin Ronacher (http://flask.pocoo.org/snippets/5/)
@@ -15,6 +16,12 @@
_punct_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?(a)\[\\\]^_`{|},.]+')
+BIOSTAR_ACTIONS = {
+ None: '',
+ 'new': 'p/new/post/',
+ 'show_tag_galaxy': 't/galaxy/'
+}
+
def slugify(text, delim=u'-'):
"""Generates an slightly worse ASCII-only slug."""
@@ -28,12 +35,9 @@
# Biostar requires all keys to be present, so we start with a template
DEFAULT_PAYLOAD = {
- 'email': "",
- 'title': "",
- 'tags': 'galaxy',
- 'tool_name': '',
- 'tool_version': '',
- 'tool_id': ''
+ 'title': '',
+ 'tag_val': 'galaxy',
+ 'content': '',
}
@@ -49,10 +53,32 @@
def tag_for_tool( tool ):
"""
- Generate a reasonavle biostar tag for a tool.
+ Generate a reasonable biostar tag for a tool.
"""
return slugify( unicode( tool.name ) )
+def determine_cookie_domain( galaxy_hostname, biostar_hostname ):
+ if galaxy_hostname == biostar_hostname:
+ return galaxy_hostname
+
+ sub_biostar_hostname = biostar_hostname.split( '.', 1 )[-1]
+ if sub_biostar_hostname == galaxy_hostname:
+ return galaxy_hostname
+
+ sub_galaxy_hostname = galaxy_hostname.split( '.', 1 )[-1]
+ if sub_biostar_hostname == sub_galaxy_hostname:
+ return sub_galaxy_hostname
+
+ return galaxy_hostname
+
+def create_cookie( trans, key_name, key, email ):
+ digest = hmac.new( key, email ).hexdigest()
+ value = "%s:%s" % (email, digest)
+ trans.set_cookie( value, name=key_name, path='/', age=90, version='1' )
+ #We need to explicitly set the domain here, in order to allow for biostar in a subdomain to work
+ galaxy_hostname = urlparse.urlsplit( url_for( '/', qualified=True ) ).hostname
+ biostar_hostname = urlparse.urlsplit( trans.app.config.biostar_url ).hostname
+ trans.response.cookies[ key_name ][ 'domain' ] = determine_cookie_domain( galaxy_hostname, biostar_hostname )
class BiostarController( BaseUIController ):
"""
@@ -65,26 +91,22 @@
Generate a redirect to a Biostar site using external authentication to
pass Galaxy user information and information about a specific tool.
"""
- payload = payload or {}
# Ensure biostar integration is enabled
if not trans.app.config.biostar_url:
return error( "Biostar integration is not enabled" )
+ if biostar_action not in BIOSTAR_ACTIONS:
+ return error( "Invalid action specified (%s)." % ( biostar_action ) )
+
# Start building up the payload
+ payload = payload or {}
payload = dict( DEFAULT_PAYLOAD, **payload )
# Do the best we can of providing user information for the payload
if trans.user:
- payload['username'] = "user-" + trans.security.encode_id( trans.user.id )
- payload['email'] = trans.user.email
- if trans.user.username:
- payload['display_name'] = trans.user.username
- else:
- payload['display_name'] = trans.user.email.split( "@" )[0]
+ email = trans.user.email
else:
- encoded = trans.security.encode_id( trans.galaxy_session.id )
- payload['username'] = "anon-" + encoded
- payload['display_name'] = "Anonymous Galaxy User"
- data, digest = encode_data( trans.app.config.biostar_key, payload )
- return trans.response.send_redirect( url_for( trans.app.config.biostar_url, data=data, digest=digest, name=trans.app.config.biostar_key_name, action=biostar_action ) )
+ email = "anon-%s" % ( trans.security.encode_id( trans.galaxy_session.id ) )
+ create_cookie( trans, trans.app.config.biostar_key_name, trans.app.config.biostar_key, email )
+ return trans.response.send_redirect( url_for( urlparse.urljoin( trans.app.config.biostar_url, BIOSTAR_ACTIONS[ biostar_action ] ), **payload ) )
@web.expose
def biostar_question_redirect( self, trans, payload=None ):
@@ -111,9 +133,8 @@
if not tool:
return error( "No tool found matching '%s'" % tool_id )
# Tool specific information for payload
- payload = { 'tool_name': tool.name,
- 'tool_version': tool.version,
- 'tool_id': tool.id,
- 'tags': 'galaxy ' + tag_for_tool( tool ) }
+ payload = { 'title':'Need help with "%s" tool' % ( tool.name ),
+ 'content': '<br /><hr /><p>Tool name: %s</br>Tool version: %s</br>Tool ID: %s</p>' % ( tool.name, tool.version, tool.id ),
+ 'tag_val': ','.join( [ 'galaxy', tag_for_tool( tool ) ] ) }
# Pass on to regular question method
return self.biostar_question_redirect( trans, payload )
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/webapps/galaxy/controllers/tag.py
--- a/lib/galaxy/webapps/galaxy/controllers/tag.py
+++ b/lib/galaxy/webapps/galaxy/controllers/tag.py
@@ -89,6 +89,7 @@
item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) )
user = trans.user
item_class = self.get_class( item_class )
+ q = '' if q is None else q
q = q.encode( 'utf-8' )
if q.find( ":" ) == -1:
return self._get_tag_autocomplete_names( trans, q, limit, timestamp, user, item, item_class )
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/galaxy/webapps/galaxy/controllers/workflow.py
--- a/lib/galaxy/webapps/galaxy/controllers/workflow.py
+++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py
@@ -1557,23 +1557,28 @@
m.stored_workflow = q.get( id )
user.stored_workflow_menu_entries.append( m )
sess.flush()
- return trans.show_message( "Menu updated", refresh_frames=['tools'] )
+ message = "Menu updated"
+ refresh_frames = ['tools']
else:
- user = trans.get_user()
- ids_in_menu = set( [ x.stored_workflow_id for x in user.stored_workflow_menu_entries ] )
- workflows = trans.sa_session.query( model.StoredWorkflow ) \
- .filter_by( user=user, deleted=False ) \
- .order_by( desc( model.StoredWorkflow.table.c.update_time ) ) \
- .all()
- shared_by_others = trans.sa_session \
- .query( model.StoredWorkflowUserShareAssociation ) \
- .filter_by( user=user ) \
- .filter( model.StoredWorkflow.deleted == False ) \
- .all()
- return trans.fill_template( "workflow/configure_menu.mako",
- workflows=workflows,
- shared_by_others=shared_by_others,
- ids_in_menu=ids_in_menu )
+ message = None
+ refresh_frames = []
+ user = trans.get_user()
+ ids_in_menu = set( [ x.stored_workflow_id for x in user.stored_workflow_menu_entries ] )
+ workflows = trans.sa_session.query( model.StoredWorkflow ) \
+ .filter_by( user=user, deleted=False ) \
+ .order_by( desc( model.StoredWorkflow.table.c.update_time ) ) \
+ .all()
+ shared_by_others = trans.sa_session \
+ .query( model.StoredWorkflowUserShareAssociation ) \
+ .filter_by( user=user ) \
+ .filter( model.StoredWorkflow.deleted == False ) \
+ .all()
+ return trans.fill_template( "workflow/configure_menu.mako",
+ workflows=workflows,
+ shared_by_others=shared_by_others,
+ ids_in_menu=ids_in_menu,
+ message=message,
+ refresh_frames=['tools'] )
def _workflow_to_svg_canvas( self, trans, stored ):
workflow = stored.latest_workflow
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
--- a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
@@ -3,16 +3,21 @@
import logging
import os
+import Queue
import shutil
+import stat
+import subprocess
import sys
import tempfile
import td_common_util
+import threading
import time
import shlex
-from subprocess import PIPE, Popen
-from threading import Thread
from contextlib import contextmanager
+from galaxy.util import DATABASE_MAX_STRING_SIZE
+from galaxy.util import DATABASE_MAX_STRING_SIZE_PRETTY
+from galaxy.util import shrink_string_by_size
from galaxy.util import unicodify
from galaxy.util.template import fill_template
from galaxy import eggs
@@ -25,7 +30,6 @@
from fabric.api import env
from fabric.api import hide
from fabric.api import lcd
-from fabric.api import local
from fabric.api import settings
from fabric.api import prefix
from fabric.operations import _AttributeString
@@ -33,20 +37,46 @@
log = logging.getLogger( __name__ )
INSTALLATION_LOG = 'INSTALLATION.log'
-NO_CMD_OUTPUT_TIMEOUT = 600
VIRTUALENV_URL = 'https://pypi.python.org/packages/source/v/virtualenv/virtualenv-1.9.1.tar.gz'
+class AsynchronousReader( threading.Thread ):
+ """
+ A helper class to implement asynchronous reading of a stream in a separate thread. Read lines are pushed
+ onto a queue to be consumed in another thread.
+ """
+
+ def __init__( self, fd, queue ):
+ threading.Thread.__init__( self )
+ self._fd = fd
+ self._queue = queue
+ self.lines = []
+
+ def run( self ):
+ """Read lines and put them on the queue."""
+ thread_lock = threading.Lock()
+ thread_lock.acquire()
+ for line in iter( self._fd.readline, '' ):
+ stripped_line = line.rstrip()
+ self.lines.append( stripped_line )
+ self._queue.put( stripped_line )
+ thread_lock.release()
+
+ def installation_complete( self ):
+ """Make sure there is more installation and compilation logging content expected."""
+ return not self.is_alive() and self._queue.empty()
+
+
class EnvFileBuilder( object ):
def __init__( self, install_dir ):
self.install_dir = install_dir
self.return_code = 0
- def append_line( self, skip_if_contained=True, make_executable=True, **kwd ):
+ def append_line( self, make_executable=True, **kwd ):
env_var_dict = dict( **kwd )
env_entry, env_file = self.create_or_update_env_shell_file( self.install_dir, env_var_dict )
- return_code = file_append( env_entry, env_file, skip_if_contained=skip_if_contained, make_executable=make_executable )
+ return_code = file_append( env_entry, env_file, make_executable=make_executable )
self.return_code = self.return_code or return_code
return self.return_code
@@ -128,62 +158,93 @@
if int( version.split( "." )[ 0 ] ) < 1:
raise NotImplementedError( "Install Fabric version 1.0 or later." )
+def close_file_descriptor( fd ):
+ """Attempt to close a file descriptor."""
+ start_timer = time.time()
+ error = ''
+ while True:
+ try:
+ fd.close()
+ break
+ except IOError, e:
+ # Undoubtedly close() was called during a concurrent operation on the same file object.
+ log.debug( 'Error closing file descriptor: %s' % str( e ) )
+ time.sleep( .5 )
+ current_wait_time = time.time() - start_timer
+ if current_wait_time >= 600:
+ error = 'Error closing file descriptor: %s' % str( e )
+ break
+ return error
+
def enqueue_output( stdout, stdout_queue, stderr, stderr_queue ):
- '''
- This method places streamed stdout and stderr into a threaded IPC queue target, defined as follows:
-
- stdio_thread = Thread( target=enqueue_output, args=( process_handle.stdout, stdout_queue, process_handle.stderr, stderr_queue ) )
-
- When input is received, it logs that input to the defined logger, and saves it to that thread's queue. The calling thread
- can then retrieve that data using thread.stdout and thread.stderr.
- '''
+ """
+ This method places streamed stdout and stderr into a threaded IPC queue target. Received data
+ is printed and saved to that thread's queue. The calling thread can then retrieve the data using
+ thread.stdout and thread.stderr.
+ """
stdout_logger = logging.getLogger( 'fabric_util.STDOUT' )
stderr_logger = logging.getLogger( 'fabric_util.STDERR' )
- for line in iter( stdout.readline, b'' ):
+ for line in iter( stdout.readline, '' ):
output = line.rstrip()
stdout_logger.debug( output )
stdout_queue.put( output )
- stdout.close()
stdout_queue.put( None )
- for line in iter( stderr.readline, b'' ):
+ for line in iter( stderr.readline, '' ):
output = line.rstrip()
stderr_logger.debug( output )
stderr_queue.put( output )
- stderr.close()
stderr_queue.put( None )
-def file_append( text, file_path, skip_if_contained=True, make_executable=True ):
- '''
- Append a line to a file unless skip_if_contained is True and the line already exists in the file. This method creates the file
- if it doesn't exist. If make_executable is True, the permissions on the file are set to executable by the owner. This method
- is similar to a local version of fabric.contrib.files.append.
- '''
- if not os.path.exists( file_path ):
- local( 'touch %s' % file_path )
+def file_append( text, file_path, make_executable=True ):
+ """
+ Append a line to a file unless the line already exists in the file. This method creates the file if
+ it doesn't exist. If make_executable is True, the permissions on the file are set to executable by
+ the owner.
+ """
+ file_dir = os.path.dirname( file_path )
+ if not os.path.exists( file_dir ):
+ try:
+ os.makedirs( file_dir )
+ except Exception, e:
+ log.exception( str( e ) )
+ return 1
+ if os.path.exists( file_path ):
+ try:
+ new_env_file_contents = []
+ env_file_contents = file( file_path, 'r' ).readlines()
+ # Clean out blank lines from the env.sh file.
+ for line in env_file_contents:
+ line = line.rstrip()
+ if line:
+ new_env_file_contents.append( line )
+ env_file_contents = new_env_file_contents
+ except Exception, e:
+ log.exception( str( e ) )
+ return 1
+ else:
+ env_file_handle = open( file_path, 'w' )
+ env_file_handle.close()
+ env_file_contents = []
if make_executable:
- # Explicitly set the file to the received mode if valid.
- with settings( hide( 'everything' ), warn_only=True ):
- local( 'chmod +x %s' % file_path )
+ # Explicitly set the file's executable bits.
+ try:
+ os.chmod( file_path, int( '111', base=8) | os.stat( file_path )[ stat.ST_MODE ] )
+ except Exception, e:
+ log.exception( str( e ) )
+ return 1
return_code = 0
# Convert the received text to a list, in order to support adding one or more lines to the file.
if isinstance( text, basestring ):
text = [ text ]
for line in text:
- # Build a regex to search for the relevant line in env.sh.
- regex = td_common_util.egrep_escape( line )
- if skip_if_contained:
- # If the line exists in the file, egrep will return a success.
- with settings( hide( 'everything' ), warn_only=True ):
- egrep_cmd = 'egrep "^%s$" %s' % ( regex, file_path )
- contains_line = local( egrep_cmd ).succeeded
- if contains_line:
- continue
- # Append the current line to the file, escaping any single quotes in the line.
- line = line.replace( "'", r"'\\''" )
- return_code = local( "echo '%s' >> %s" % ( line, file_path ) ).return_code
- if return_code:
- # Return upon the first error encountered.
- return return_code
+ line = line.rstrip()
+ if line not in env_file_contents:
+ env_file_contents.append( line )
+ try:
+ file( file_path, 'w' ).write( '\n'.join( env_file_contents ) )
+ except Exception, e:
+ log.exception( str( e ) )
+ return 1
return return_code
def filter_actions_after_binary_installation( actions ):
@@ -202,23 +263,122 @@
def handle_command( app, tool_dependency, install_dir, cmd, return_output=False ):
context = app.install_model.context
- with settings( warn_only=True ):
- output = local( cmd, capture=True )
+ output = handle_complex_command( cmd )
log_results( cmd, output, os.path.join( install_dir, INSTALLATION_LOG ) )
- if output.return_code:
+ stdout = output.stdout
+ stderr = output.stderr
+ if len( stdout ) > DATABASE_MAX_STRING_SIZE:
+ print "Length of stdout > %s, so only a portion will be saved in the database." % str( DATABASE_MAX_STRING_SIZE_PRETTY )
+ stdout = shrink_string_by_size( stdout, DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True )
+ if len( stderr ) > DATABASE_MAX_STRING_SIZE:
+ print "Length of stderr > %s, so only a portion will be saved in the database." % str( DATABASE_MAX_STRING_SIZE_PRETTY )
+ stderr = shrink_string_by_size( stderr, DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True )
+ if output.return_code not in [ 0 ]:
tool_dependency.status = app.install_model.ToolDependency.installation_status.ERROR
- if output.stderr:
- tool_dependency.error_message = unicodify( str( output.stderr )[ :32768 ] )
- elif output.stdout:
- tool_dependency.error_message = unicodify( str( output.stdout )[ :32768 ] )
+ if stderr:
+ tool_dependency.error_message = unicodify( stderr )
+ elif stdout:
+ tool_dependency.error_message = unicodify( stdout )
else:
- tool_dependency.error_message = "Unknown error occurred executing shell command %s, return_code: %s" % ( str( cmd ), str( output.return_code ) )
+ # We have a problem if there was no stdout and no stderr.
+ tool_dependency.error_message = "Unknown error occurred executing shell command %s, return_code: %s" % \
+ ( str( cmd ), str( output.return_code ) )
context.add( tool_dependency )
context.flush()
if return_output:
return output
return output.return_code
+def handle_complex_command( command ):
+ """
+ Wrap subprocess.Popen in such a way that the stderr and stdout from running a shell command will
+ be captured and logged in nearly real time. This is similar to fabric.local, but allows us to
+ retain control over the process. This method is named "complex" because it uses queues and
+ threads to execute a command while capturing and displaying the output.
+ """
+ wrapped_command = shlex.split( '/bin/sh -c "%s"' % str( command ) )
+ # Launch the command as subprocess. A bufsize of 1 means line buffered.
+ process_handle = subprocess.Popen( wrapped_command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ bufsize=1,
+ close_fds=False,
+ cwd=state.env[ 'lcwd' ] )
+ pid = process_handle.pid
+ # Launch the asynchronous readers of the process' stdout and stderr.
+ stdout_queue = Queue.Queue()
+ stdout_reader = AsynchronousReader( process_handle.stdout, stdout_queue )
+ stdout_reader.start()
+ stderr_queue = Queue.Queue()
+ stderr_reader = AsynchronousReader( process_handle.stderr, stderr_queue )
+ stderr_reader.start()
+ # Place streamed stdout and stderr into a threaded IPC queue target so it can
+ # be printed and stored for later retrieval when generating the INSTALLATION.log.
+ stdio_thread = threading.Thread( target=enqueue_output,
+ args=( process_handle.stdout,
+ stdout_queue,
+ process_handle.stderr,
+ stderr_queue ) )
+ thread_lock = threading.Lock()
+ thread_lock.acquire()
+ stdio_thread.start()
+ # Check the queues for output until there is nothing more to get.
+ start_timer = time.time()
+ while not stdout_reader.installation_complete() or not stderr_reader.installation_complete():
+ # Show what we received from standard output.
+ while not stdout_queue.empty():
+ try:
+ line = stdout_queue.get()
+ except Queue.Empty:
+ line = None
+ break
+ if line:
+ print line
+ start_timer = time.time()
+ else:
+ break
+ # Show what we received from standard error.
+ while not stderr_queue.empty():
+ try:
+ line = stderr_queue.get()
+ except Queue.Empty:
+ line = None
+ break
+ if line:
+ print line
+ start_timer = time.time()
+ else:
+ stderr_queue.task_done()
+ break
+ # Sleep a bit before asking the readers again.
+ time.sleep( .1 )
+ current_wait_time = time.time() - start_timer
+ if stdout_queue.empty() and stderr_queue.empty() and current_wait_time > td_common_util.NO_OUTPUT_TIMEOUT:
+ err_msg = "\nShutting down process id %s because it generated no output for the defined timeout period of %.1f seconds.\n" % \
+ ( pid, td_common_util.NO_OUTPUT_TIMEOUT )
+ stderr_reader.lines.append( err_msg )
+ process_handle.kill()
+ break
+ thread_lock.release()
+ # Wait until each of the threads we've started terminate. The following calls will block each thread
+ # until it terminates either normally, through an unhandled exception, or until the timeout occurs.
+ stdio_thread.join( td_common_util.NO_OUTPUT_TIMEOUT )
+ stdout_reader.join( td_common_util.NO_OUTPUT_TIMEOUT )
+ stderr_reader.join( td_common_util.NO_OUTPUT_TIMEOUT )
+ # Close subprocess' file descriptors.
+ error = close_file_descriptor( process_handle.stdout )
+ error = close_file_descriptor( process_handle.stderr )
+ stdout = '\n'.join( stdout_reader.lines )
+ stderr = '\n'.join( stderr_reader.lines )
+ # Handle error condition (deal with stdout being None, too)
+ output = _AttributeString( stdout.strip() if stdout else "" )
+ errors = _AttributeString( stderr.strip() if stderr else "" )
+ # Make sure the process has finished.
+ process_handle.poll()
+ output.return_code = process_handle.returncode
+ output.stderr = errors
+ return output
+
def handle_environment_variables( app, tool_dependency, install_dir, env_var_dict, set_prior_environment_commands ):
"""
This method works with with a combination of three tool dependency definition tag sets, which are defined in the tool_dependencies.xml file in the
@@ -447,7 +607,7 @@
with settings( warn_only=True ):
for tarball_name in tarball_names:
cmd = '''PATH=$PATH:$R_HOME/bin; export PATH; R_LIBS=$INSTALL_DIR; export R_LIBS;
- Rscript -e "install.packages(c('%s'),lib='$INSTALL_DIR', repos=NULL, dependencies=FALSE)"''' % ( str( tarball_name ) )
+ Rscript -e \\"install.packages(c('%s'),lib='$INSTALL_DIR', repos=NULL, dependencies=FALSE)\\"''' % ( str( tarball_name ) )
cmd = install_environment.build_command( td_common_util.evaluate_template( cmd, install_dir ) )
return_code = handle_command( app, tool_dependency, install_dir, cmd )
if return_code:
@@ -591,8 +751,6 @@
# </action>
filtered_actions = [ a for a in actions ]
dir = install_dir
- # We need to be careful in determining if the value of dir is a valid directory because we're dealing with 2 environments, the fabric local
- # environment and the python environment. Checking the path as follows should work.
full_path_to_dir = os.path.abspath( os.path.join( work_dir, dir ) )
if not os.path.exists( full_path_to_dir ):
os.makedirs( full_path_to_dir )
@@ -763,8 +921,7 @@
def log_results( command, fabric_AttributeString, file_path ):
"""
- Write attributes of fabric.operations._AttributeString (which is the output of executing command using fabric's local() method)
- to a specified log file.
+ Write attributes of fabric.operations._AttributeString to a specified log file.
"""
if os.path.exists( file_path ):
logfile = open( file_path, 'ab' )
@@ -785,33 +942,10 @@
work_dir = tempfile.mkdtemp( prefix="tmp-toolshed-mtd" )
yield work_dir
if os.path.exists( work_dir ):
- local( 'rm -rf %s' % work_dir )
-
-def run_local_command( command, capture_output=True, stream_output=True ):
- # TODO: Overhaul this method.
- import Queue
- wrapped_command = shlex.split( "/bin/sh -c '%s'" % command )
- stdout_queue = Queue()
- stderr_queue = Queue()
- process_handle = Popen( wrapped_command, stdout=PIPE, stderr=PIPE, bufsize=1, close_fds=False, cwd=state.env[ 'lcwd' ] )
- stdio_thread = Thread( target=enqueue_output, args=( process_handle.stdout, stdout_queue, process_handle.stderr, stderr_queue ) )
- stdio_thread.daemon = True
- stdio_thread.start()
- stdout, stderr = wait_for_process( process_handle, stream_output, stdout_queue, stderr_queue )
- # Handle error condition (deal with stdout being None, too)
- output = _AttributeString( stdout.strip() if stdout else "" )
- errors = _AttributeString( stderr.strip() if stderr else "" )
- output.failed = False
- output.return_code = process_handle.returncode
- output.stderr = errors
- if process_handle.returncode not in env.ok_ret_codes:
- output.failed = True
- message = "Encountered an error (return code %s) while executing '%s'" % ( process_handle.returncode, command )
- log.error( message )
- output.succeeded = not output.failed
- # If we were capturing, this will be a string; otherwise it will be None.
- if capture_output:
- return output
+ try:
+ shutil.rmtree( work_dir )
+ except Exception, e:
+ log.exception( str( e ) )
def set_galaxy_environment( galaxy_user, tool_dependency_dir, host='localhost', shell='/bin/bash -l -c' ):
"""General Galaxy environment configuration. This method is not currently used."""
@@ -820,52 +954,4 @@
env.host_string = host
env.shell = shell
env.use_sudo = False
- env.safe_cmd = local
return env
-
-def wait_for_process( process_handle, stream_output, stdout_queue, stderr_queue ):
- # TODO: Overhaul this method.
- import Queue
- pid = process_handle.pid
- standard_out = []
- standard_err = []
- process_handle.wait()
- # Generate stdout.
- while True:
- try:
- line = stdout_queue.get( timeout=NO_CMD_OUTPUT_TIMEOUT )
- except Queue.Empty:
- err_msg = "\nShutting down process id %s because it generated no stdout for the defined timout period of %d seconds.\n" % \
- ( pid, NO_CMD_OUTPUT_TIMEOUT )
- print err_msg
- standard_out.append( err_msg )
- stdout_queue.task_done()
- process_handle.kill()
- break
- else:
- if line:
- standard_out.append( line )
- else:
- stdout_queue.task_done()
- break
- # Generate stderr.
- while True:
- try:
- line = stderr_queue.get( timeout=NO_CMD_OUTPUT_TIMEOUT )
- except Queue.Empty:
- err_msg = "\nShutting down process id %s because it generated no stderr for the defined timout period of %d seconds.\n" % \
- ( pid, NO_CMD_OUTPUT_TIMEOUT )
- print err_msg
- standard_err.append( err_msg )
- stderr_queue.task_done()
- process_handle.kill()
- break
- else:
- if line:
- standard_err.append( line )
- else:
- stderr_queue.task_done()
- break
- stdout = '\n'.join( standard_out )
- stderr = '\n'.join( standard_err )
- return stdout, stderr
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/tool_shed/galaxy_install/tool_dependencies/install_util.py
--- a/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py
@@ -350,7 +350,7 @@
tool_dependency_type='package' )
if not can_install_tool_dependency:
log.debug( "Tool dependency %s version %s cannot be installed (it was probably previously installed), so returning it." % \
- ( str( tool_dependency.name, str( tool_dependency.version ) ) ) )
+ ( str( tool_dependency.name ), str( tool_dependency.version ) ) )
return tool_dependency
else:
can_install_tool_dependency = True
@@ -884,7 +884,7 @@
if env_var_version == '1.0':
# Create this tool dependency's env.sh file.
env_file_builder = fabric_util.EnvFileBuilder( install_dir )
- return_code = env_file_builder.append_line( skip_if_contained=True, make_executable=True, **env_var_dict )
+ return_code = env_file_builder.append_line( make_executable=True, **env_var_dict )
if return_code:
error_message = 'Error creating env.sh file for tool dependency %s, return_code: %s' % \
( str( tool_dependency.name ), str( return_code ) )
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/tool_shed/galaxy_install/tool_dependencies/td_common_util.py
--- a/lib/tool_shed/galaxy_install/tool_dependencies/td_common_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/td_common_util.py
@@ -4,6 +4,7 @@
import shutil
import sys
import tarfile
+import time
import traceback
import urllib2
import zipfile
@@ -13,9 +14,12 @@
log = logging.getLogger( __name__ )
+# Set no activity timeout to 20 minutes.
+NO_OUTPUT_TIMEOUT = 1200.0
+
class CompressedFile( object ):
-
+
def __init__( self, file_path, mode='r' ):
if istar( file_path ):
self.file_type = 'tar'
@@ -30,7 +34,7 @@
self.archive = getattr( self, method )( file_path, mode )
else:
raise NameError( 'File type %s specified, no open method found.' % self.file_type )
-
+
def extract( self, path ):
'''Determine the path to which the archive should be extracted.'''
contents = self.getmembers()
@@ -58,36 +62,36 @@
os.makedirs( extraction_path )
self.archive.extractall( os.path.join( extraction_path ) )
return os.path.abspath( extraction_path )
-
+
def getmembers_tar( self ):
return self.archive.getmembers()
-
+
def getmembers_zip( self ):
return self.archive.infolist()
-
+
def getname_tar( self, item ):
return item.name
-
+
def getname_zip( self, item ):
return item.filename
-
+
def getmember( self, name ):
for member in self.getmembers():
if self.getname( member ) == name:
return member
-
+
def getmembers( self ):
return getattr( self, 'getmembers_%s' % self.type )()
-
+
def getname( self, member ):
return getattr( self, 'getname_%s' % self.type )( member )
-
+
def isdir( self, member ):
return getattr( self, 'isdir_%s' % self.type )( member )
-
+
def isdir_tar( self, member ):
return member.isdir()
-
+
def isdir_zip( self, member ):
if member.filename.endswith( os.sep ):
return True
@@ -97,7 +101,7 @@
if not self.isdir( member ):
return True
return False
-
+
def open_tar( self, filepath, mode ):
return tarfile.open( filepath, mode, errorlevel=0 )
@@ -146,9 +150,7 @@
return None
def download_binary( url, work_dir ):
- '''
- Download a pre-compiled binary from the specified URL.
- '''
+ """Download a pre-compiled binary from the specified URL."""
downloaded_filename = os.path.split( url )[ -1 ]
dir = url_download( work_dir, downloaded_filename, url, extract=False )
return downloaded_filename
@@ -179,7 +181,6 @@
return os.path.abspath( os.path.join( root, name ) )
return None
-
def get_env_shell_file_paths( app, elem ):
# Currently only the following tag set is supported.
# <repository toolshed="http://localhost:9009/" name="package_numpy_1_7" owner="test" changeset_revision="c84c6a8be056">
@@ -455,6 +456,8 @@
file_path = os.path.join( install_dir, downloaded_file_name )
src = None
dst = None
+ # Set a timer so we don't sit here forever.
+ start_time = time.time()
try:
src = urllib2.urlopen( download_url )
dst = open( file_path, 'wb' )
@@ -464,8 +467,14 @@
dst.write( chunk )
else:
break
- except:
- raise
+ time_taken = time.time() - start_time
+ if time_taken > NO_OUTPUT_TIMEOUT:
+ err_msg = 'Downloading from URL %s took longer than the defined timeout period of %.1f seconds.' % \
+ ( str( download_url ), NO_OUTPUT_TIMEOUT )
+ raise Exception( err_msg )
+ except Exception, e:
+ err_msg = err_msg = 'Error downloading from URL\n%s:\n%s' % ( str( download_url ), str( e ) )
+ raise Exception( err_msg )
finally:
if src:
src.close()
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/tool_shed/util/commit_util.py
--- a/lib/tool_shed/util/commit_util.py
+++ b/lib/tool_shed/util/commit_util.py
@@ -274,7 +274,11 @@
return False, None, error_message
def handle_repository_dependency_elem( trans, elem, unpopulate=False ):
+ """Populate or unpopulate repository tags."""
# <repository name="molecule_datatypes" owner="test" changeset_revision="1a070566e9c6" />
+ # <repository changeset_revision="xxx" name="package_xorg_macros_1_17_1" owner="test" toolshed="yyy">
+ # <package name="xorg_macros" version="1.17.1" />
+ # </repository>
error_message = ''
name = elem.get( 'name' )
owner = elem.get( 'owner' )
@@ -285,6 +289,23 @@
revised = False
toolshed = elem.get( 'toolshed' )
changeset_revision = elem.get( 'changeset_revision' )
+ sub_elems = [ child_elem for child_elem in list( elem ) ]
+ if len( sub_elems ) > 0:
+ # At this point, a <repository> tag will point only to a package.
+ # <package name="xorg_macros" version="1.17.1" />
+ # Coerce the list to an odict().
+ sub_elements = odict()
+ packages = []
+ for sub_elem in sub_elems:
+ sub_elem_type = sub_elem.tag
+ sub_elem_name = sub_elem.get( 'name' )
+ sub_elem_version = sub_elem.get( 'version' )
+ if sub_elem_type and sub_elem_name and sub_elem_version:
+ packages.append( ( sub_elem_name, sub_elem_version ) )
+ sub_elements[ 'packages' ] = packages
+ else:
+ # Set to None.
+ sub_elements = None
if unpopulate:
# We're exporting the repository, so eliminate all toolshed and changeset_revision attributes from the <repository> tag.
if toolshed or changeset_revision:
@@ -292,7 +313,7 @@
attributes[ 'name' ] = name
attributes[ 'owner' ] = owner
attributes[ 'prior_installation_required' ] = elem.get( 'prior_installation_required', 'False' )
- elem = xml_util.create_element( 'repository', attributes=attributes, sub_elements=None )
+ elem = xml_util.create_element( 'repository', attributes=attributes, sub_elements=sub_elements )
revised = True
return revised, elem, error_message
# From here on we're populating the toolshed and changeset_revisions if necessary.
@@ -321,10 +342,12 @@
return revised, elem, error_message
def handle_repository_dependency_sub_elem( trans, package_altered, altered, actions_elem, action_index, action_elem, unpopulate=False ):
- # This method populates the toolshed and changeset_revision attributes for each of the following.
- # <action type="set_environment_for_install">
- # <action type="setup_r_environment">
- # <action type="setup_ruby_environment">
+ """
+ Populate or unpopulate the toolshed and changeset_revision attributes for each of the following tag sets.
+ <action type="set_environment_for_install">
+ <action type="setup_r_environment">
+ <action type="setup_ruby_environment">
+ """
error_message = ''
for repo_index, repo_elem in enumerate( action_elem ):
# Make sure to skip comments and tags that are not <repository>.
@@ -343,7 +366,8 @@
def handle_tool_dependencies_definition( trans, tool_dependencies_config, unpopulate=False ):
"""
- Populate or unpopulate the tooshed and changeset_revision attributes of each <repository> tag defined within a tool_dependencies.xml file.
+ Populate or unpopulate the tooshed and changeset_revision attributes of each <repository>
+ tag defined within a tool_dependencies.xml file.
"""
altered = False
error_message = ''
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/tool_shed/util/tool_util.py
--- a/lib/tool_shed/util/tool_util.py
+++ b/lib/tool_shed/util/tool_util.py
@@ -1017,7 +1017,8 @@
replacement_tool_version = None
# Since we are going to remove the tool from the section, replace it with the newest loaded version of the tool.
for available_tool_version in available_tool_versions:
- if available_tool_version.id in tool_section.elems.keys():
+ available_tool_section_id, available_tool_section_name = available_tool_version.get_panel_section()
+ if available_tool_version.id in tool_section.elems.keys() or section_key == available_tool_section_id:
replacement_tool_key = 'tool_%s' % str( available_tool_version.id )
replacement_tool_version = available_tool_version
break
@@ -1057,7 +1058,8 @@
replacement_tool_version = None
# Since we are going to remove the tool from the section, replace it with the newest loaded version of the tool.
for available_tool_version in available_tool_versions:
- if available_tool_version.id in trans.app.toolbox.tool_panel.keys():
+ available_tool_section_id, available_tool_section_name = available_tool_version.get_panel_section()
+ if available_tool_version.id in trans.app.toolbox.tool_panel.keys() or not available_tool_section_id:
replacement_tool_key = 'tool_%s' % str( available_tool_version.id )
replacement_tool_version = available_tool_version
break
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa lib/tool_shed/util/xml_util.py
--- a/lib/tool_shed/util/xml_util.py
+++ b/lib/tool_shed/util/xml_util.py
@@ -47,7 +47,7 @@
def create_element( tag, attributes=None, sub_elements=None ):
"""
Create a new element whose tag is the value of the received tag, and whose attributes are all
- key / value pairs in the received the attributes and sub_elements.
+ key / value pairs in the received attributes and sub_elements.
"""
if tag:
elem = XmlET.Element( tag )
@@ -56,14 +56,22 @@
for k, v in attributes.items():
elem.set( k, v )
if sub_elements:
- # The received attributes is an odict as well. These handle information that tends to be
+ # The received attributes is an odict. These handle information that tends to be
# long text including paragraphs (e.g., description and long_description.
for k, v in sub_elements.items():
# Don't include fields that are blank.
if v:
- sub_elem = XmlET.SubElement( elem, k )
- if isinstance( v, list ):
- # If the sub_elem is a list, then it must be a list of tuples where the first
+ if k == 'packages':
+ # The received sub_elements is an odict whose key is 'packages' and whose
+ # value is a list of ( name, version ) tuples.
+ for v_tuple in v:
+ sub_elem = XmlET.SubElement( elem, 'package' )
+ sub_elem_name, sub_elem_version = v_tuple
+ sub_elem.set( 'name', sub_elem_name )
+ sub_elem.set( 'version', sub_elem_version )
+ elif isinstance( v, list ):
+ sub_elem = XmlET.SubElement( elem, k )
+ # If v is a list, then it must be a list of tuples where the first
# item is the tag and the second item is the text value.
for v_tuple in v:
if len( v_tuple ) == 2:
@@ -74,6 +82,7 @@
v_elem = XmlET.SubElement( sub_elem, v_tag )
v_elem.text = v_text
else:
+ sub_elem = XmlET.SubElement( elem, k )
sub_elem.text = v
return elem
return None
diff -r ed45cbef6347a1803ecb53cb99c8c07bad1d14aa -r 95050c5d33b95fd5479a10679875e51609ff8bfa static/scripts/galaxy.pages.js
--- a/static/scripts/galaxy.pages.js
+++ b/static/scripts/galaxy.pages.js
@@ -452,14 +452,14 @@
// item_class='History').
var item_elt_id = item_info.iclass + "-" + item_id;
var item_embed_html =
- "<p><div id='" + item_elt_id + "' class='embedded-item " + item_info.singular.toLowerCase() +
+ "<div id='" + item_elt_id + "' class='embedded-item " + item_info.singular.toLowerCase() +
" placeholder'> \
<p class='title'>Embedded Galaxy " + item_info.singular + " '" + item_name + "'</p> \
<p class='content'> \
[Do not edit this block; Galaxy will fill it in with the annotated " +
item_info.singular.toLowerCase() + " when it is displayed.] \
</p> \
- </div></p>";
+ </div>";
// Insert embedded item into document.
wym.insert(" "); // Needed to prevent insertion from occurring in child element in webkit browsers.
@@ -468,18 +468,18 @@
// TODO: can we fix this?
// Due to oddities of wym.insert() [likely due to inserting a <div> and/or a complete paragraph], an
// empty paragraph (or two!) may be included either before an embedded item. Remove these paragraphs.
- $("#" + item_elt_id, wym._doc.body).each( function() {
- // Remove previous empty paragraphs.
- var removing = true;
- while (removing)
- {
- var prev_elt = $(this).prev();
- if ( prev_elt.length != 0 && jQuery.trim(prev_elt.text()) == "" )
- prev_elt.remove();
- else
- removing = false;
- }
- });
+ //$("#" + item_elt_id, wym._doc.body).each( function() {
+ // // Remove previous empty paragraphs.
+ // var removing = true;
+ // while (removing)
+ // {
+ // var prev_elt = $(this).prev();
+ // if ( prev_elt.length != 0 && jQuery.trim(prev_elt.text()) == "" )
+ // prev_elt.remove();
+ // else
+ // removing = false;
+ // }
+ //});
});
hide_modal();
@@ -706,4 +706,4 @@
// editor.dialog(Galaxy.DIALOG_EMBED_PAGE);
//}
});
-});
\ No newline at end of file
+});
This diff is so big that we needed to truncate the remainder.
https://bitbucket.org/galaxy/galaxy-central/commits/4b2ac910ca82/
Changeset: 4b2ac910ca82
Branch: model-info
User: kellrott
Date: 2014-03-04 22:40:14
Summary: Moving unique dataset id from dataset_id to uuid
Affected #: 1 file
diff -r 95050c5d33b95fd5479a10679875e51609ff8bfa -r 4b2ac910ca82deda745ade68f162c9d2ca8aee1c lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -17,6 +17,7 @@
import json
import socket
import time
+from uuid import UUID, uuid4
from string import Template
from itertools import ifilter
from itertools import chain
@@ -1172,7 +1173,7 @@
file_path = "/tmp/"
object_store = None # This get initialized in mapping.py (method init) by app.py
engine = None
- def __init__( self, id=None, state=None, external_filename=None, extra_files_path=None, file_size=None, purgable=True ):
+ def __init__( self, id=None, state=None, external_filename=None, extra_files_path=None, file_size=None, purgable=True, uuid=None ):
self.id = id
self.state = state
self.deleted = False
@@ -1181,7 +1182,11 @@
self.external_filename = external_filename
self._extra_files_path = extra_files_path
self.file_size = file_size
- self.uuid = None
+ if uuid is None:
+ self.uuid = uuid4()
+ else:
+ self.uuid = UUID(str(uuid))
+ print "dataset uuid", self.uuid
def get_file_name( self ):
if not self.external_filename:
@@ -1837,7 +1842,6 @@
rval = dict( id = hda.id,
hda_ldda = 'hda',
uuid = ( lambda uuid: str( uuid ) if uuid else None )( hda.dataset.uuid ),
- dataset_id = hda.dataset.id,
hid = hda.hid,
file_ext = hda.ext,
peek = ( lambda hda: hda.display_peek() if hda.peek and hda.peek != 'no peek' else None )( hda ),
@@ -2267,7 +2271,6 @@
file_size = 0
rval = dict( id = ldda.id,
hda_ldda = 'ldda',
- dataset_id = ldda.dataset.id,
model_class = self.__class__.__name__,
name = ldda.name,
deleted = ldda.deleted,
https://bitbucket.org/galaxy/galaxy-central/commits/b3a0ba812dba/
Changeset: b3a0ba812dba
Branch: model-info
User: kellrott
Date: 2014-03-05 00:46:10
Summary: Adding script to generate UUIDs for datasets where the value is null. This is to backfill previously generated records (from now on UUID will be generated during creation)
Affected #: 2 files
diff -r 4b2ac910ca82deda745ade68f162c9d2ca8aee1c -r b3a0ba812dba3208b5e24a625b0133d1456a7b1b scripts/cleanup_datasets/populate_uuid.py
--- /dev/null
+++ b/scripts/cleanup_datasets/populate_uuid.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+"""
+Populates blank uuid fields in datasets with randomly generated values
+
+Going forward, these ids will be generated for all new datasets. This
+script fixes datasets that were generated before the change.
+"""
+
+import sys, os, ConfigParser
+import galaxy.app
+from galaxy.util.bunch import Bunch
+import galaxy.datatypes.tabular
+from galaxy.model.orm.scripts import get_config
+from galaxy import eggs
+from galaxy.model import mapping
+import uuid
+
+eggs.require( "SQLAlchemy" )
+
+from sqlalchemy import *
+
+assert sys.version_info[:2] >= ( 2, 4 )
+
+def main():
+ ini_file = sys.argv.pop(1)
+ config = get_config(ini_file)
+
+ model = mapping.init( ini_file, config['db_url'], create_tables = False )
+
+ for row in model.context.query( model.Dataset ):
+ if row.uuid is None:
+ row.uuid = uuid.uuid4()
+ print "Setting dataset:", row.id, " UUID to ", row.uuid
+ model.context.flush()
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff -r 4b2ac910ca82deda745ade68f162c9d2ca8aee1c -r b3a0ba812dba3208b5e24a625b0133d1456a7b1b scripts/cleanup_datasets/populate_uuid.sh
--- /dev/null
+++ b/scripts/cleanup_datasets/populate_uuid.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+cd `dirname $0`/../..
+export PYTHONPATH=./lib/
+python ./scripts/cleanup_datasets/populate_uuid.py ./universe_wsgi.ini $@
\ No newline at end of file
https://bitbucket.org/galaxy/galaxy-central/commits/61719e07f398/
Changeset: 61719e07f398
Branch: model-info
User: kellrott
Date: 2014-03-05 00:50:40
Summary: Deleting rouge print statement
Affected #: 1 file
diff -r b3a0ba812dba3208b5e24a625b0133d1456a7b1b -r 61719e07f39897f017cb5036f96ffbb7081a5c25 lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -1186,7 +1186,6 @@
self.uuid = uuid4()
else:
self.uuid = UUID(str(uuid))
- print "dataset uuid", self.uuid
def get_file_name( self ):
if not self.external_filename:
https://bitbucket.org/galaxy/galaxy-central/commits/54a2ec26ae5f/
Changeset: 54a2ec26ae5f
User: dannon
Date: 2014-03-13 18:48:00
Summary: Merged in kellrott/galaxy-central/model-info (pull request #338)
Patch to expose the actual dataset id in the LDDA and HDA to_dict calls (in addition to the instance id).
Affected #: 3 files
diff -r b194d6a4797fc91a8ad9a1901465865d884d612d -r 54a2ec26ae5f8f2bf78b8b964d4279533e4a905a lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -17,6 +17,7 @@
import json
import socket
import time
+from uuid import UUID, uuid4
from string import Template
from itertools import ifilter
from itertools import chain
@@ -1172,7 +1173,7 @@
file_path = "/tmp/"
object_store = None # This get initialized in mapping.py (method init) by app.py
engine = None
- def __init__( self, id=None, state=None, external_filename=None, extra_files_path=None, file_size=None, purgable=True ):
+ def __init__( self, id=None, state=None, external_filename=None, extra_files_path=None, file_size=None, purgable=True, uuid=None ):
self.id = id
self.state = state
self.deleted = False
@@ -1181,7 +1182,10 @@
self.external_filename = external_filename
self._extra_files_path = extra_files_path
self.file_size = file_size
- self.uuid = None
+ if uuid is None:
+ self.uuid = uuid4()
+ else:
+ self.uuid = UUID(str(uuid))
def get_file_name( self ):
if not self.external_filename:
diff -r b194d6a4797fc91a8ad9a1901465865d884d612d -r 54a2ec26ae5f8f2bf78b8b964d4279533e4a905a scripts/cleanup_datasets/populate_uuid.py
--- /dev/null
+++ b/scripts/cleanup_datasets/populate_uuid.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+"""
+Populates blank uuid fields in datasets with randomly generated values
+
+Going forward, these ids will be generated for all new datasets. This
+script fixes datasets that were generated before the change.
+"""
+
+import sys, os, ConfigParser
+import galaxy.app
+from galaxy.util.bunch import Bunch
+import galaxy.datatypes.tabular
+from galaxy.model.orm.scripts import get_config
+from galaxy import eggs
+from galaxy.model import mapping
+import uuid
+
+eggs.require( "SQLAlchemy" )
+
+from sqlalchemy import *
+
+assert sys.version_info[:2] >= ( 2, 4 )
+
+def main():
+ ini_file = sys.argv.pop(1)
+ config = get_config(ini_file)
+
+ model = mapping.init( ini_file, config['db_url'], create_tables = False )
+
+ for row in model.context.query( model.Dataset ):
+ if row.uuid is None:
+ row.uuid = uuid.uuid4()
+ print "Setting dataset:", row.id, " UUID to ", row.uuid
+ model.context.flush()
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff -r b194d6a4797fc91a8ad9a1901465865d884d612d -r 54a2ec26ae5f8f2bf78b8b964d4279533e4a905a scripts/cleanup_datasets/populate_uuid.sh
--- /dev/null
+++ b/scripts/cleanup_datasets/populate_uuid.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+cd `dirname $0`/../..
+export PYTHONPATH=./lib/
+python ./scripts/cleanup_datasets/populate_uuid.py ./universe_wsgi.ini $@
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/9ed9316ae6fb/
Changeset: 9ed9316ae6fb
Branch: stable
User: greg
Date: 2014-03-11 14:32:05
Summary: Fix for displaying the status of an installed repository that cannot be determined.
Affected #: 1 file
diff -r 1bbc2b265fba40a40ebab9cd03d91a3a7e7d2e18 -r 9ed9316ae6fba4fb83050757d9db259569d14de9 lib/tool_shed/util/shed_util_common.py
--- a/lib/tool_shed/util/shed_util_common.py
+++ b/lib/tool_shed/util/shed_util_common.py
@@ -1424,7 +1424,7 @@
bgcolor = trans.install_model.ToolShedRepository.states.ERROR
else:
bgcolor = trans.install_model.ToolShedRepository.states.WARNING
- status_label = '%s, unknown status' % status_label
+ status_label = 'unknown status'
return '<div class="count-box state-color-%s">%s</div>' % ( bgcolor, status_label )
def get_updated_changeset_revisions( trans, name, owner, changeset_revision ):
https://bitbucket.org/galaxy/galaxy-central/commits/b194d6a4797f/
Changeset: b194d6a4797f
User: davebgx
Date: 2014-03-11 15:31:42
Summary: Merge stable.
Affected #: 0 files
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