galaxy-commits
Threads by month
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- 15302 discussions
galaxy-dist commit 0736d9af9e6e: Allow access to API Key creation when use_remote_user is true.
by commits-noreply@bitbucket.org 30 Jul '10
by commits-noreply@bitbucket.org 30 Jul '10
30 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Nate Coraor <nate(a)bx.psu.edu>
# Date 1280416177 14400
# Node ID 0736d9af9e6e7facef8fad93baa3a303e0cf9526
# Parent d27912281a74ba74a8df6f5d72db7c553ca846c5
Allow access to API Key creation when use_remote_user is true.
--- /dev/null
+++ b/templates/webapps/galaxy/user/api_keys.mako
@@ -0,0 +1,35 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+%if message:
+ ${render_msg( message, status )}
+%endif
+
+<div class="toolForm">
+ <form name="user_api_keys" id="user_api_keys" action="${h.url_for( controller='user', action='api_keys' )}" method="post" >
+ <div class="toolFormTitle">Web API Key</div>
+ <div class="toolFormBody">
+ <div class="form-row">
+ <label>Current API key:</label>
+ %if user.api_keys:
+ ${user.api_keys[0].key}
+ %else:
+ none set
+ %endif
+ </div>
+ <div class="form-row">
+ <input type="submit" name="new_api_key_button" value="Generate a new key now"/>
+ %if user.api_keys:
+ (invalidates old key)
+ %endif
+ <div class="toolParamHelp" style="clear: both;">
+ An API key will allow you to access Galaxy via its web
+ API (documentation forthcoming). Please note that
+ <strong>this key acts as an alternate means to access
+ your account, and should be treated with the same care
+ as your login password</strong>.
+ </div>
+ </div>
+ </div>
+ </form>
+</div>
--- a/lib/galaxy/web/framework/middleware/remoteuser.py
+++ b/lib/galaxy/web/framework/middleware/remoteuser.py
@@ -95,6 +95,8 @@ class RemoteUser( object ):
return self.error( start_response, title, message )
if path_info.startswith( '/user/create' ) and environ[ 'HTTP_REMOTE_USER' ] in self.admin_users:
pass # admins can create users
+ elif path_info.startswith( '/user/api_keys' ):
+ pass
elif path_info.startswith( '/user' ):
title = "Access to Galaxy user controls is disabled"
message = """
--- a/lib/galaxy/web/controllers/user.py
+++ b/lib/galaxy/web/controllers/user.py
@@ -1003,24 +1003,20 @@ class User( BaseController ):
@web.expose
- def new_api_key( self, trans, **kwd ):
+ def api_keys( self, trans, **kwd ):
params = util.Params( kwd )
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
- admin_view = util.string_as_bool( params.get( 'admin_view', False ) )
error = ''
- user = trans.sa_session.query( trans.app.model.User ).get( int( params.get( 'user_id', None ) ) )
if params.get( 'new_api_key_button', None ) == 'Generate a new key now':
new_key = trans.app.model.APIKeys()
- new_key.user_id = user.id
+ new_key.user_id = trans.user.id
new_key.key = trans.app.security.get_new_guid()
trans.sa_session.add( new_key )
trans.sa_session.flush()
message = "Generated a new web API key"
status = "done"
- return trans.response.send_redirect( web.url_for( controller='user',
- action='show_info',
- admin_view=admin_view,
- user_id=user.id,
- message=message,
- status=status ) )
+ return trans.fill_template( 'webapps/galaxy/user/api_keys.mako',
+ user=trans.user,
+ message=message,
+ status=status )
--- a/templates/webapps/galaxy/base_panels.mako
+++ b/templates/webapps/galaxy/base_panels.mako
@@ -130,6 +130,9 @@
%if app.config.get_bool( 'enable_pages', False ):
<li><a href="${h.url_for( controller='/page', action='list' )}">Pages</a></li>
%endif
+ %if app.config.enable_api:
+ <li><a target="galaxy_main" href="${h.url_for( controller='/user', action='api_keys' )}">API Keys</a></li>
+ %endif
</ul></div></td>
--- a/templates/webapps/galaxy/user/info.mako
+++ b/templates/webapps/galaxy/user/info.mako
@@ -120,36 +120,3 @@
</div></form></div>
-
-<p/>
-
-%if trans.app.config.enable_api:
- <div class="toolForm">
- <form name="user_api_keys" id="user_api_keys" action="${h.url_for( controller='user', action='new_api_key', user_id=user.id, admin_view=admin_view )}" method="post" >
- <div class="toolFormTitle">Web API Key</div>
- <div class="toolFormBody">
- <div class="form-row">
- <label>Current API key:</label>
- %if user.api_keys:
- ${user.api_keys[0].key}
- %else:
- none set
- %endif
- </div>
- <div class="form-row">
- <input type="submit" name="new_api_key_button" value="Generate a new key now"/>
- %if user.api_keys:
- (invalidates old key)
- %endif
- <div class="toolParamHelp" style="clear: both;">
- An API key will allow you to access Galaxy via its web
- API (documentation forthcoming). Please note that
- <strong>this key acts as an alternate means to access
- your account, and should be treated with the same care
- as your login password</strong>.
- </div>
- </div>
- </div>
- </form>
- </div>
-%endif
--- a/templates/user/index.mako
+++ b/templates/user/index.mako
@@ -12,6 +12,9 @@
%if webapp == 'galaxy':
<li><a href="${h.url_for( controller='user', action='show_info' )}">${_('Manage your information')}</a></li><li><a href="${h.url_for( controller='user', action='set_default_permissions' )}">${_('Change default permissions')}</a> for new histories</li>
+ %if trans.app.config.enable_api:
+ <li><a href="${h.url_for( controller='user', action='api_keys' )}">${_('Manage your API Keys')}</a> for new histories</li>
+ %endif
%else:
<li><a href="${h.url_for( controller='user', action='show_info', webapp='community' )}">${_('Manage your information')}</a></li>
%endif
1
0
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User rc
# Date 1280415308 14400
# Node ID d27912281a74ba74a8df6f5d72db7c553ca846c5
# Parent af48a13e46b9ab0136bcbf14508bd9bb044ad257
lims:
- added a new table 'sample_dataset' to store sample datasets & their info when they are transfered from the sequencer
- the datasets transfer page now uses a grid to facilitate bulk renaming
- bulk renaming possible to fix the problem with the way SOLiD generates datasets
- the remote file browser is now independent of a specific sample, the user may select any sample when transferring datasets from the sequencer
--- a/templates/requests/common/sample_datasets.mako
+++ b/templates/requests/common/sample_datasets.mako
@@ -1,5 +1,5 @@
<%def name="render_sample_datasets( cntrller, sample )">
- <a href="${h.url_for(controller='requests_common', cntrller=cntrller, action='show_datatx_page', sample_id=trans.security.encode_id(sample.id))}">${sample.transferred_dataset_files()}/${len(sample.dataset_files)}</a>
+ <a href="${h.url_for(controller='requests_common', cntrller=cntrller, action='show_datatx_page', sample_id=trans.security.encode_id(sample.id))}">${sample.transferred_dataset_files()}/${len(sample.datasets)}</a></%def>
--- /dev/null
+++ b/templates/admin/requests/get_data.mako
@@ -0,0 +1,144 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+
+<script type="text/javascript">
+$(document).ready(function(){
+ //hide the all of the element with class msg_body
+ $(".msg_body").hide();
+ //toggle the componenet with class msg_body
+ $(".msg_head").click(function(){
+ $(this).next(".msg_body").slideToggle(450);
+ });
+});
+
+
+
+
+</script>
+
+<script type="text/javascript">
+ function display_file_details(request_id, folder_path)
+ {
+ var w = document.get_data.files_list.selectedIndex;
+ var selected_value = document.get_data.files_list.options[w].value;
+ var cell = $("#file_details");
+ if(selected_value.charAt(selected_value.length-1) != '/')
+ {
+ // Make ajax call
+ $.ajax( {
+ type: "POST",
+ url: "${h.url_for( controller='requests_admin', action='get_file_details' )}",
+ dataType: "json",
+ data: { id: request_id, folder_path: document.get_data.folder_path.value+selected_value },
+ success : function ( data ) {
+ cell.html( '<label>'+data+'</label>' )
+ }
+ });
+ }
+ else
+ {
+ cell.html( '' )
+ }
+
+
+ }
+</script>
+
+<script type="text/javascript">
+ function open_folder1(request_id, folder_path)
+ {
+ var w = document.get_data.files_list.selectedIndex;
+ var selected_value = document.get_data.files_list.options[w].value;
+ var cell = $("#file_details");
+ if(selected_value.charAt(selected_value.length-1) == '/')
+ {
+ document.get_data.folder_path.value = document.get_data.folder_path.value+selected_value
+ // Make ajax call
+ $.ajax( {
+ type: "POST",
+ url: "${h.url_for( controller='requests_admin', action='open_folder' )}",
+ dataType: "json",
+ data: { id: request_id, folder_path: document.get_data.folder_path.value },
+ success : function ( data ) {
+ document.get_data.files_list.options.length = 0
+ for(i=0; i<data.length; i++)
+ {
+ var newOpt = new Option(data[i], data[i]);
+ document.get_data.files_list.options[i] = newOpt;
+ }
+ //cell.html( '<label>'+data+'</label>' )
+
+ }
+ });
+ }
+ else
+ {
+ cell.html( '' )
+ }
+ }
+</script>
+
+
+<style type="text/css">
+.msg_head {
+ padding: 0px 0px;
+ cursor: pointer;
+}
+
+}
+</style>
+
+%if message:
+ ${render_msg( message, status )}
+%endif
+<br/>
+<br/>
+<ul class="manage-table-actions">
+ <li>
+ <a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_request_types', operation='view', id=trans.security.encode_id(request.type.id) )}">
+ <span>Sequencer information</span></a>
+ </li>
+ <li>
+ <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='show', id=trans.security.encode_id(request.id) )}">
+ <span>Browse this request</span></a>
+ </li>
+</ul>
+
+<form name="get_data" id="get_data" action="${h.url_for( controller='requests_admin', cntrller=cntrller, action='get_data', request_id=request.id)}" method="post" >
+ <div class="toolFormTitle">Select files for transfer</div>
+ <div class="toolForm">
+ <div class="form-row">
+ <label>Sample:</label>
+ ${samples_selectbox.get_html()}
+ <div class="toolParamHelp" style="clear: both;">
+ Select the sample with which you want to associate the dataset(s)
+ </div>
+ <br/>
+ <label>Folder path on the sequencer:</label>
+ <input type="text" name="folder_path" value="${folder_path}" size="100"/>
+ <input type="submit" name="browse_button" value="List contents"/>
+ ##<input type="submit" name="open_folder" value="Open folder"/>
+ <input type="submit" name="folder_up" value="Up"/>
+ </div>
+ <div class="form-row">
+ <select name="files_list" id="files_list" style="max-width: 60%; width: 98%; height: 150px; font-size: 100%;" ondblclick="open_folder1(${request.id}, '${folder_path}')" onChange="display_file_details(${request.id}, '${folder_path}')" multiple>
+ %for index, f in enumerate(files):
+ <option value="${f}">${f}</option>
+ %endfor
+ </select>
+ <br/>
+ <div id="file_details" class="toolParamHelp" style="clear: both;">
+
+ </div>
+ </div>
+ <div class="form-row">
+<!-- <div class="toolParamHelp" style="clear: both;">
+ After selecting dataset(s), be sure to click on the <b>Start transfer</b> button.
+ Once the transfer is complete the dataset(s) will show up on this page.
+ </div>-->
+ <input type="submit" name="select_show_datasets_button" value="Select & show datasets"/>
+ <input type="submit" name="select_more_button" value="Select more"/>
+ </div>
+ </div>
+</form>
--- /dev/null
+++ b/templates/admin/requests/rename_datasets.mako
@@ -0,0 +1,66 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+%if message:
+ ${render_msg( message, status )}
+%endif
+<h3>Rename datasets for Sample "${sample.name}"</h3>
+<br/>
+${render_msg('A dataset can be renamed only if its status is "Not Started"', 'ok')}
+
+<ul class="manage-table-actions">
+ <li>
+ <a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_datasets', sample_id=sample.id )}">
+ <span>Browse datasets</span></a>
+ </li>
+ <li>
+ <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='show', id=trans.security.encode_id(sample.request.id) )}">
+ <span>Browse this request</span></a>
+ </li>
+</ul>
+
+<form name="rename_datasets" id="rename_datasets" action="${h.url_for( controller='requests_admin', action='rename_datasets', id_list=id_list, sample_id=trans.security.encode_id(sample.id))}" method="post" >
+ <table class="grid">
+ <thead>
+ <tr>
+ <th>Prepend directory name</th>
+ <th>Name</th>
+ <th>Path on Sequencer</th>
+ </tr>
+ <thead>
+ <tbody>
+ %for id in id_list:
+ <%
+ sample_dataset = trans.sa_session.query( trans.app.model.SampleDataset ).get( trans.security.decode_id(id) )
+ import os
+ id = trans.security.decode_id(id)
+ %>
+ %if sample_dataset.status == trans.app.model.Sample.transfer_status.NOT_STARTED:
+ <tr>
+ <td>
+ <select name="prepend_${sample_dataset.id}" last_selected_value="2">
+ <option value="None" selected></option>
+ %for option_index, option in enumerate(sample_dataset.file_path.split(os.sep)[:-1]):
+ %if option.strip():
+ <option value="${option}">${option}</option>
+ %endif
+ %endfor
+ </select>
+ </td>
+ <td>
+ <input type="text" name="name_${sample_dataset.id}" value="${sample_dataset.name}" size="100"/>
+ </td>
+ <td>
+ ${sample_dataset.file_path}
+ </td>
+ </tr>
+ %endif
+ %endfor
+ </tbody>
+ </table>
+ <br/>
+ <div class="form-row">
+ <input type="submit" name="save_button" value="Save"/>
+ <input type="submit" name="cancel_button" value="Close"/>
+ </div>
+</form>
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -1584,7 +1584,7 @@ class Sample( object ):
COMPLETE = 'Complete',
ERROR = 'Error')
def __init__(self, name=None, desc=None, request=None, form_values=None,
- bar_code=None, library=None, folder=None, dataset_files=None):
+ bar_code=None, library=None, folder=None):
self.name = name
self.desc = desc
self.request = request
@@ -1592,27 +1592,26 @@ class Sample( object ):
self.bar_code = bar_code
self.library = library
self.folder = folder
- self.dataset_files = dataset_files
def current_state(self):
if self.events:
return self.events[0].state
return None
def untransferred_dataset_files(self):
count = 0
- for df in self.dataset_files:
- if df['status'] == self.transfer_status.NOT_STARTED:
+ for df in self.datasets:
+ if df.status == self.transfer_status.NOT_STARTED:
count = count + 1
return count
def inprogress_dataset_files(self):
count = 0
- for df in self.dataset_files:
- if df['status'] not in [self.transfer_status.NOT_STARTED, self.transfer_status.COMPLETE]:
+ for df in self.datasets:
+ if df.status not in [self.transfer_status.NOT_STARTED, self.transfer_status.COMPLETE]:
count = count + 1
return count
def transferred_dataset_files(self):
count = 0
- for df in self.dataset_files:
- if df['status'] == self.transfer_status.COMPLETE:
+ for df in self.datasets:
+ if df.status == self.transfer_status.COMPLETE:
count = count + 1
return count
def dataset_size(self, filepath):
@@ -1639,6 +1638,16 @@ class SampleEvent( object ):
self.state = sample_state
self.comment = comment
+class SampleDataset( object ):
+ def __init__(self, sample=None, name=None, file_path=None,
+ status=None, error_msg=None, size=None):
+ self.sample = sample
+ self.name = name
+ self.file_path = file_path
+ self.status = status
+ self.error_msg = error_msg
+ self.size = size
+
class UserAddress( object ):
def __init__(self, user=None, desc=None, name=None, institution=None,
address=None, city=None, state=None, postal_code=None,
--- a/lib/galaxy/web/controllers/requests_common.py
+++ b/lib/galaxy/web/controllers/requests_common.py
@@ -34,7 +34,7 @@ class RequestsCommon( BaseController ):
if sample.current_state().name != state:
rval[id] = {
"state": sample.current_state().name,
- "datasets": len(sample.dataset_files),
+ "datasets": len(sample.datasets),
"html_state": unicode( trans.fill_template( "requests/common/sample_state.mako", sample=sample, cntrller=cntrller ), 'utf-8' ),
"html_datasets": unicode( trans.fill_template( "requests/common/sample_datasets.mako", trans=trans, sample=sample, cntrller=cntrller ), 'utf-8' )
}
@@ -518,7 +518,6 @@ class RequestsCommon( BaseController ):
barcode=s.bar_code,
library=s.library,
folder=s.folder,
- dataset_files=s.dataset_files,
field_values=s.values.content,
lib_widget=lib_widget,
folder_widget=folder_widget))
@@ -530,7 +529,6 @@ class RequestsCommon( BaseController ):
barcode='',
library=None,
folder=None,
- dataset_files=[],
field_values=['' for field in request.type.sample_form.fields],
lib_widget=lib_widget,
folder_widget=folder_widget))
@@ -792,8 +790,7 @@ class RequestsCommon( BaseController ):
request, form_values,
current_samples[sample_index]['barcode'],
current_samples[sample_index]['library'],
- current_samples[sample_index]['folder'],
- dataset_files=[])
+ current_samples[sample_index]['folder'])
trans.sa_session.add( s )
trans.sa_session.flush()
@@ -1078,12 +1075,17 @@ class RequestsCommon( BaseController ):
operation='show',
status='error',
message="Set a data library and folder for <b>%s</b> to transfer dataset(s)." % sample.name,
- id=trans.security.encode_id(sample.request.id) ) )
+ id=trans.security.encode_id(sample.request.id) ) )
+ if cntrller == 'requests_admin':
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='manage_datasets',
+ sample_id=sample.id) )
+
if params.get( 'folder_path', '' ):
folder_path = util.restore_text( params.get( 'folder_path', '' ) )
else:
- if len(sample.dataset_files):
- folder_path = os.path.dirname(sample.dataset_files[-1]['filepath'][:-1])
+ if len(sample.datasets):
+ folder_path = os.path.dirname(sample.datasets[-1]['filepath'][:-1])
else:
folder_path = util.restore_text( sample.request.type.datatx_info.get('data_dir', '') )
if folder_path and folder_path[-1] != os.sep:
@@ -1094,6 +1096,6 @@ class RequestsCommon( BaseController ):
message = 'The sequencer login information is incomplete. Click on the <b>Sequencer information</b> to add login details.'
return trans.fill_template( '/requests/common/get_data.mako',
cntrller=cntrller, sample=sample,
- dataset_files=sample.dataset_files,
+ dataset_files=sample.datasets,
message=message, status=status, files=[],
folder_path=folder_path )
--- a/scripts/galaxy_messaging/server/data_transfer.py
+++ b/scripts/galaxy_messaging/server/data_transfer.py
@@ -70,12 +70,12 @@ class DataTransfer(object):
self.config_id_secret = config_id_secret
count=0
while True:
- index = self.get_value_index(self.dom, 'index', count)
+ dataset_id = self.get_value_index(self.dom, 'dataset_id', count)
file = self.get_value_index(self.dom, 'file', count)
name = self.get_value_index(self.dom, 'name', count)
if file:
self.dataset_files.append(dict(name=name,
- index=int(index),
+ dataset_id=int(dataset_id),
file=file))
else:
break
@@ -149,7 +149,7 @@ class DataTransfer(object):
def print_ticks(d):
pass
for i, df in enumerate(self.dataset_files):
- self.update_status(Sample.transfer_status.TRANSFERRING, df['index'])
+ self.update_status(Sample.transfer_status.TRANSFERRING, df['dataset_id'])
try:
cmd = "scp %s@%s:'%s' '%s/%s'" % ( self.username,
self.host,
@@ -168,7 +168,7 @@ class DataTransfer(object):
raise Exception(msg)
except Exception, e:
msg = traceback.format_exc()
- self.update_status('Error', df['index'], msg)
+ self.update_status('Error', df['dataset_id'], msg)
def add_to_library(self):
@@ -189,29 +189,17 @@ class DataTransfer(object):
log.debug(e)
self.error_and_exit(str(e))
- def update_status(self, status, dataset_index='All', msg=''):
+ def update_status(self, status, dataset_id='All', msg=''):
'''
Update the data transfer status for this dataset in the database
'''
try:
- log.debug('Setting status "%s" for dataset "%s" of sample "%s"' % ( status, str(dataset_index), str(self.sample_id) ) )
- df = from_json_string(self.galaxydb.get_sample_dataset_files(self.sample_id))
- if dataset_index == 'All':
+ log.debug('Setting status "%s" for dataset "%s" of sample "%s"' % ( status, str(dataset_id), str(self.sample_id) ) )
+ if dataset_id == 'All':
for dataset in self.dataset_files:
- df[dataset['index']]['status'] = status
- if status == 'Error':
- df[dataset['index']]['error_msg'] = msg
- else:
- df[dataset['index']]['error_msg'] = ''
-
+ self.galaxydb.set_sample_dataset_status(dataset['dataset_id'], status, msg)
else:
- df[dataset_index]['status'] = status
- if status == 'Error':
- df[dataset_index]['error_msg'] = msg
- else:
- df[dataset_index]['error_msg'] = ''
-
- self.galaxydb.set_sample_dataset_files(self.sample_id, to_json_string(df))
+ self.galaxydb.set_sample_dataset_status(dataset_id, status, msg)
log.debug('done.')
except:
log.error(traceback.format_exc())
@@ -229,12 +217,12 @@ class DataTransfer(object):
rc = rc + node.data
return rc
- def get_value_index(self, dom, tag_name, index):
+ def get_value_index(self, dom, tag_name, dataset_id):
'''
This method extracts the tag value from the xml message
'''
try:
- nodelist = dom.getElementsByTagName(tag_name)[index].childNodes
+ nodelist = dom.getElementsByTagName(tag_name)[dataset_id].childNodes
except:
return None
rc = ""
--- a/scripts/galaxy_messaging/server/galaxydb_interface.py
+++ b/scripts/galaxy_messaging/server/galaxydb_interface.py
@@ -28,11 +28,12 @@ class GalaxyDbInterface(object):
def __init__(self, dbstr):
self.dbstr = dbstr
self.db_engine = create_engine(self.dbstr)
- self.db_engine.echo = False
+ self.db_engine.echo = True
self.metadata = MetaData(self.db_engine)
self.session = sessionmaker(bind=self.db_engine)
self.event_table = Table('sample_event', self.metadata, autoload=True )
self.sample_table = Table('sample', self.metadata, autoload=True )
+ self.sample_dataset_table = Table('sample_dataset', self.metadata, autoload=True )
self.request_table = Table('request', self.metadata, autoload=True )
self.request_event_table = Table('request_event', self.metadata, autoload=True )
self.state_table = Table('sample_state', self.metadata, autoload=True )
@@ -105,7 +106,7 @@ class GalaxyDbInterface(object):
create_time=datetime.utcnow(),
sample_id=sample_id,
sample_state_id=int(new_state_id),
- comment='bar code scanner')
+ comment='Update by barcode scan')
# if all the samples for this request are in the final state
# then change the request state to 'Complete'
result = select(columns=[self.sample_table.c.id],
@@ -126,23 +127,22 @@ class GalaxyDbInterface(object):
request_id=self.request_id,
state=request_state,
comment='All samples of this request have finished processing.')
-
- def get_sample_dataset_files(self, sample_id):
- subsubquery = select(columns=[self.sample_table.c.dataset_files],
- whereclause=self.sample_table.c.id==sample_id)
- return subsubquery.execute().fetchall()[0][0]
-
- def set_sample_dataset_files(self, sample_id, value):
- u = self.sample_table.update(whereclause=self.sample_table.c.id==sample_id)
- u.execute(dataset_files=value)
-
+ def set_sample_dataset_status(self, id, new_status, msg=None):
+ u = self.sample_dataset_table.update(whereclause=self.sample_dataset_table.c.id==int(id))
+ u.execute(status=new_status)
+ if new_status == 'Error':
+ u.execute(error_msg=msg)
+ else:
+ u.execute(error_msg='')
+ return
+
if __name__ == '__main__':
print '''This file should not be run directly. To start the Galaxy AMQP Listener:
%sh run_galaxy_listener.sh'''
- dbstr = 'postgres://postgres:postgres@localhost/galaxy_uft'
+ dbstr = 'postgres://postgres:postgres@localhost/g2'
parser = optparse.OptionParser()
parser.add_option('-n', '--name', help='name of the sample field', dest='name', \
--- /dev/null
+++ b/templates/admin/requests/datasets_grid.mako
@@ -0,0 +1,32 @@
+
+
+
+<%def name="custom_javascripts()">
+ <script type="text/javascript">
+ $("#select-dataset-action-button").bind( "click", function(e) {
+ alert('afdhblvi')
+ $.ajax({
+ url: "${h.url_for( controller='requests_admin', action='remote_file_browser' )}",
+ data: {id: 6},
+ error: function() { alert( "Couldn't create new browser" ) },
+ success: function(form_html) {
+ show_modal("Select file", form_html, {
+ "Cancel": function() { window.location = "${h.url_for( controller='requests_admin', action='list' )}"; },
+ "Continue": function() { $(document).trigger("convert_dbkeys"); continue_fn(); }
+ });
+ $("#new-title").focus();
+ replace_big_select_inputs();
+ }
+ });
+ }
+ </script>
+</%def>
+
+<%def name="javascripts()">
+ ${h.js( "galaxy.base", "galaxy.panels", "json2", "jquery", "jquery.event.drag", "jquery.autocomplete", "jquery.mousewheel", "trackster", "ui.core", "ui.sortable" )}
+ ${self.custom_javascripts()}
+ ${parent.javascripts()}
+</%def>
+
+<%inherit file="/grid_base.mako"/>
+
--- a/lib/galaxy/model/mapping.py
+++ b/lib/galaxy/model/mapping.py
@@ -574,7 +574,6 @@ Sample.table = Table('sample', metadata,
Column( "bar_code", TrimmedString( 255 ), index=True ),
Column( "library_id", Integer, ForeignKey( "library.id" ), index=True ),
Column( "folder_id", Integer, ForeignKey( "library_folder.id" ), index=True ),
- Column( "dataset_files", JSONType() ),
Column( "deleted", Boolean, index=True, default=False ) )
SampleState.table = Table('sample_state', metadata,
@@ -593,6 +592,17 @@ SampleEvent.table = Table('sample_event'
Column( "sample_state_id", Integer, ForeignKey( "sample_state.id" ), index=True ),
Column( "comment", TEXT ) )
+SampleDataset.table = Table('sample_dataset', metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "sample_id", Integer, ForeignKey( "sample.id" ), index=True ),
+ Column( "name", TrimmedString( 255 ), nullable=False ),
+ Column( "file_path", TrimmedString( 255 ), nullable=False ),
+ Column( "status", TrimmedString( 255 ), nullable=False ),
+ Column( "error_msg", TEXT ),
+ Column( "size", TrimmedString( 255 ) ) )
+
Page.table = Table( "page", metadata,
Column( "id", Integer, primary_key=True ),
Column( "create_time", DateTime, default=now ),
@@ -792,6 +802,8 @@ assign_mapper( context, Sample, Sample.t
properties=dict(
events=relation( SampleEvent, backref="sample",
order_by=desc(SampleEvent.table.c.update_time) ),
+ datasets=relation( SampleDataset, backref="sample",
+ order_by=desc(SampleDataset.table.c.update_time) ),
values=relation( FormValues,
primaryjoin=( Sample.table.c.form_values_id == FormValues.table.c.id ) ),
request=relation( Request,
@@ -865,6 +877,9 @@ assign_mapper( context, SampleEvent, Sam
assign_mapper( context, SampleState, SampleState.table,
properties=None )
+assign_mapper( context, SampleDataset, SampleDataset.table,
+ properties=None )
+
assign_mapper( context, UserAddress, UserAddress.table,
properties=dict(
user=relation( User,
--- a/templates/display_common.mako
+++ b/templates/display_common.mako
@@ -79,6 +79,8 @@
class_plural = "Libraries"
elif a_class == model.HistoryDatasetAssociation:
class_plural = "Datasets"
+ elif a_class == model.SampleDataset:
+ class_plural = "Sample Datasets"
elif a_class == model.FormDefinitionCurrent:
class_plural = "Forms"
else:
--- a/templates/requests/common/get_data.mako
+++ b/templates/requests/common/get_data.mako
@@ -94,8 +94,7 @@
</style>
-<h2>Data transfer from Sequencer</h2>
-<h3>Sample "${sample.name}" of Request "${sample.request.name}"</h3>
+<h2>Datasets of Sample "${sample.name}"</h2><br/><br/>
--- a/templates/requests/common/show_request.mako
+++ b/templates/requests/common/show_request.mako
@@ -147,30 +147,29 @@ function showContent(vThis)
</div><ul class="manage-table-actions">
-
- %if request.unsubmitted() and request.samples:
- <li>
+ <li><a class="action-button" id="seqreq-${request.id}-popup" class="menubutton">Sequencing Request Actions</a></li>
+ <div popupmenu="seqreq-${request.id}-popup">
+ %if request.unsubmitted() and request.samples:
<a class="action-button" confirm="More samples cannot be added to this request once it is submitted. Click OK to submit." href="${h.url_for( controller=cntrller, action='list', operation='Submit', id=trans.security.encode_id(request.id) )}">
- <span>Submit request</span></a>
- </li>
- %endif
- %if cntrller == 'requests_admin' and trans.user_is_admin():
- %if request.submitted():
- <li>
- <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='reject', id=trans.security.encode_id(request.id))}">
- <span>Reject request</span></a>
- </li>
+ <span>Submit</span></a>
%endif
- %endif
- <li>
+ <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='Edit', id=trans.security.encode_id(request.id))}">
+ <span>Edit</span></a><a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='events', id=trans.security.encode_id(request.id) )}"><span>History</span></a>
- </li>
+ %if cntrller == 'requests_admin' and trans.user_is_admin():
+ %if request.submitted():
+ <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='reject', id=trans.security.encode_id(request.id))}">
+ <span>Reject</span></a>
+ <a class="action-button" href="${h.url_for( controller='requests_admin', action='get_data', show_page=True, request_id=request.id)}">
+ <span>Select dataset(s) to transfer</span></a>
+ %endif
+ %endif
+ </div><li><a class="action-button" href="${h.url_for( controller=cntrller, action='list')}"><span>Browse requests</span></a></li>
-
</ul>
@@ -396,12 +395,21 @@ function showContent(vThis)
<td>${info['name']}</td><td>${info['barcode']}</td>
%if sample.request.unsubmitted():
- <td>Unsubmitted</td>
+ ##<td>Unsubmitted</td>
+ <td>
+ <div id="history-name-container">
+ <div id="history-name" class="tooltip editable-text" title="Click to rename history">Unsubmitted</div>
+ </div>
+ </td>
%else:
<td id="sampleState-${sample.id}">${render_sample_state( cntrller, sample )}</td>
%endif
%if info['library']:
- <td><a href="${h.url_for( controller='library_common', action='browse_library', cntrller='library', id=trans.security.encode_id( info['library'].id ) )}">${info['library'].name}</a></td>
+ %if cntrller == 'requests':
+ <td><a href="${h.url_for( controller='library_common', action='browse_library', cntrller='library', id=trans.security.encode_id( info['library'].id ) )}">${info['library'].name}</a></td>
+ %elif cntrller == 'requests_admin':
+ <td><a href="${h.url_for( controller='library_common', action='browse_library', cntrller='library_admin', id=trans.security.encode_id( info['library'].id ) )}">${info['library'].name}</a></td>
+ %endif
%else:
<td></td>
%endif
--- a/templates/admin/requests/dataset.mako
+++ b/templates/admin/requests/dataset.mako
@@ -12,60 +12,44 @@
<ul class="manage-table-actions"><li><a class="action-button" href="${h.url_for( controller='requests_common', action='show_datatx_page', cntrller='requests_admin', sample_id=trans.security.encode_id(sample.id) )}">
- <span>Dataset transfer page</span></a>
+ <span>Browse datasets</span></a></li></ul><div class="toolForm">
- <div class="toolFormTitle">Dataset details</div>
+ <div class="toolFormTitle">Dataset Information</div><div class="toolFormBody">
- <form name="dataset_details" action="${h.url_for( controller='requests_admin', action='dataset_details', save_changes=True, sample_id=trans.security.encode_id(sample.id), dataset_index=dataset_index )}" method="post" >
- <%
- dataset = sample.dataset_files[dataset_index]
- %><div class="form-row"><label>Name:</label><div style="float: left; width: 250px; margin-right: 10px;">
- %if dataset['status'] in [sample.transfer_status.IN_QUEUE, sample.transfer_status.NOT_STARTED]:
- <input type="text" name="name" value="${dataset['name']}" size="60"/>
- %else:
- ${dataset['name']}
- %endif
-
+ ${sample_dataset.name}
</div><div style="clear: both"></div></div><div class="form-row"><label>File on the Sequencer:</label><div style="float: left; width: 250px; margin-right: 10px;">
- ${dataset['filepath']}
- ##<input type="text" name="filepath" value="${dataset['filepath']}" size="100" readonly/>
+ ${sample_dataset.file_path}
</div><div style="clear: both"></div></div><div class="form-row"><label>Size:</label><div style="float: left; width: 250px; margin-right: 10px;">
- ${dataset.get('size', 'Unknown')}
+ ${sample_dataset.size}
</div><div style="clear: both"></div></div><div class="form-row"><label>Transfer status:</label><div style="float: left; width: 250px; margin-right: 10px;">
- ${dataset['status']}
+ ${sample_dataset.status}
<br/>
- %if dataset['status'] == sample.transfer_status.ERROR:
- ${dataset['error_msg']}
+ %if sample_dataset.status == sample.transfer_status.ERROR:
+ ${sample_dataset.error_msg}
%endif
</div><div style="clear: both"></div></div>
- %if dataset['status'] in [sample.transfer_status.IN_QUEUE, sample.transfer_status.NOT_STARTED]:
- <div class="form-row">
- <input type="submit" name="save" value="Save"/>
- </div>
- %endif
- </form></div></div>
--- a/lib/galaxy/web/controllers/requests_admin.py
+++ b/lib/galaxy/web/controllers/requests_admin.py
@@ -146,7 +146,7 @@ class RequestsGrid( grids.Grid ):
#
-# ---- Request Type Gridr ------------------------------------------------------
+# ---- Request Type Grid ------------------------------------------------------
#
class RequestTypeGrid( grids.Grid ):
# Custom column types
@@ -207,6 +207,57 @@ class RequestTypeGrid( grids.Grid ):
action='create_request_type' ) )
]
+
+# ---- Data Transfer Grid ------------------------------------------------------
+#
+class DataTransferGrid( grids.Grid ):
+ # Custom column types
+ class NameColumn( grids.TextColumn ):
+ def get_value(self, trans, grid, sample_dataset):
+ return sample_dataset.name
+ class SizeColumn( grids.TextColumn ):
+ def get_value(self, trans, grid, sample_dataset):
+ return sample_dataset.size
+ class StatusColumn( grids.TextColumn ):
+ def get_value(self, trans, grid, sample_dataset):
+ return sample_dataset.status
+ # Grid definition
+ title = "Sample Datasets"
+ template = "admin/requests/datasets_grid.mako"
+ model_class = model.SampleDataset
+ default_sort_key = "-create_time"
+ num_rows_per_page = 50
+ preserve_state = True
+ use_paging = True
+ #default_filter = dict( deleted="False" )
+ columns = [
+ NameColumn( "Name",
+ #key="name",
+ model_class=model.SampleDataset,
+ link=( lambda item: dict( operation="view", id=item.id ) ),
+ attach_popup=True,
+ filterable="advanced" ),
+ SizeColumn( "Size",
+ #key='size',
+ model_class=model.SampleDataset,
+ filterable="advanced" ),
+ StatusColumn( "Status",
+ #key='status',
+ model_class=model.SampleDataset,
+ filterable="advanced" ),
+ ]
+ columns.append( grids.MulticolFilterColumn( "Search",
+ cols_to_filter=[ columns[0] ],
+ key="free-text-search",
+ visible=False,
+ filterable="standard" ) )
+ operations = [
+ grids.GridOperation( "Start Transfer", allow_multiple=True, condition=( lambda item: item.status in [model.Sample.transfer_status.NOT_STARTED] ) ),
+ grids.GridOperation( "Rename", allow_multiple=True, allow_popup=False, condition=( lambda item: item.status in [model.Sample.transfer_status.NOT_STARTED] ) ),
+ grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: item.status in [model.Sample.transfer_status.NOT_STARTED] ) ),
+ ]
+ def apply_query_filter( self, trans, query, **kwd ):
+ return query.filter_by( sample_id=kwd['sample_id'] )
#
# ---- Request Controller ------------------------------------------------------
#
@@ -214,6 +265,7 @@ class RequestTypeGrid( grids.Grid ):
class RequestsAdmin( BaseController ):
request_grid = RequestsGrid()
requesttype_grid = RequestTypeGrid()
+ datatx_grid = DataTransferGrid()
@web.expose
@@ -280,11 +332,11 @@ class RequestsAdmin( BaseController ):
# Avoid caching
trans.response.headers['Pragma'] = 'no-cache'
trans.response.headers['Expires'] = '0'
- sample = trans.sa_session.query( self.app.model.Sample ).get( int(id) )
- datatx_info = sample.request.type.datatx_info
+ request = trans.sa_session.query( self.app.model.Request ).get( int(id) )
+ datatx_info = request.type.datatx_info
cmd = 'ssh %s@%s "ls -oghp \'%s\'"' % ( datatx_info['username'],
- datatx_info['host'],
- folder_path )
+ datatx_info['host'],
+ folder_path )
output = pexpect.run(cmd, events={'.ssword:*': datatx_info['password']+'\r\n',
pexpect.TIMEOUT:print_ticks},
timeout=10)
@@ -297,8 +349,8 @@ class RequestsAdmin( BaseController ):
# Avoid caching
trans.response.headers['Pragma'] = 'no-cache'
trans.response.headers['Expires'] = '0'
- sample = trans.sa_session.query( self.app.model.Sample ).get( int(id) )
- return self.__get_files(trans, sample, folder_path)
+ request = trans.sa_session.query( self.app.model.Request ).get( int(id) )
+ return self.__get_files(trans, request.type, folder_path)
def __reject_request(self, trans, **kwd):
try:
@@ -522,12 +574,120 @@ class RequestsAdmin( BaseController ):
#
# Data transfer from sequencer
#
+
+ @web.expose
+ @web.require_admin
+ def manage_datasets( self, trans, **kwd ):
+ if 'operation' in kwd:
+ operation = kwd['operation'].lower()
+ if not kwd.get( 'id', None ):
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='list',
+ status='error',
+ message="Invalid sample dataset ID") )
+ if operation == "view":
+ sample_dataset = trans.sa_session.query( trans.app.model.SampleDataset ).get( trans.security.decode_id(kwd['id']) )
+ return trans.fill_template( '/admin/requests/dataset.mako',
+ sample=sample_dataset.sample,
+ sample_dataset=sample_dataset)
- def __get_files(self, trans, sample, folder_path):
+ elif operation == "delete":
+ id_list = util.listify( kwd['id'] )
+ for id in id_list:
+ sample_dataset = trans.sa_session.query( trans.app.model.SampleDataset ).get( trans.security.decode_id(id) )
+ sample_id = sample_dataset.sample_id
+ trans.sa_session.delete( sample_dataset )
+ trans.sa_session.flush()
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='manage_datasets',
+ sample_id=sample_id,
+ status='done',
+ message="%i dataset(s) have been removed." % len(id_list)) )
+
+ elif operation == "rename":
+ id_list = util.listify( kwd['id'] )
+ sample_dataset = trans.sa_session.query( trans.app.model.SampleDataset ).get( trans.security.decode_id(id_list[0]) )
+ return trans.fill_template( '/admin/requests/rename_datasets.mako',
+ sample=sample_dataset.sample,
+ id_list=id_list )
+ elif operation == "start transfer":
+ id_list = util.listify( kwd['id'] )
+ sample_dataset = trans.sa_session.query( trans.app.model.SampleDataset ).get( trans.security.decode_id(id_list[0]) )
+ self.__start_datatx(trans, sample_dataset.sample, id_list)
+
+
+ # Render the grid view
+ try:
+ sample = trans.sa_session.query( trans.app.model.Sample ).get( kwd['sample_id'] )
+ except:
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='list',
+ status='error',
+ message="Invalid sample ID" ) )
+ self.datatx_grid.title = 'Datasets of Sample "%s"' % sample.name
+ self.datatx_grid.global_actions = [
+ grids.GridAction( "Refresh",
+ dict( controller='requests_admin',
+ action='manage_datasets',
+ sample_id=sample.id) ),
+ grids.GridAction( "Select Datasets",
+ dict(controller='requests_admin',
+ action='get_data',
+ request_id=sample.request.id,
+ folder_path=sample.request.type.datatx_info['data_dir'],
+ sample_id=sample.id,
+ show_page=True)),
+ grids.GridAction( 'Data Library "%s"' % sample.library.name,
+ dict(controller='library_common',
+ action='browse_library',
+ cntrller='library_admin',
+ id=trans.security.encode_id( sample.library.id))),
+ grids.GridAction( "Browse this request",
+ dict( controller='requests_admin',
+ action='list',
+ operation='show',
+ id=trans.security.encode_id(sample.request.id)))]
+ return self.datatx_grid( trans, **kwd )
+
+ @web.expose
+ @web.require_admin
+ def rename_datasets( self, trans, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ try:
+ sample = trans.sa_session.query( trans.app.model.Sample ).get( trans.security.decode_id(kwd['sample_id']))
+ except:
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='list',
+ status='error',
+ message="Invalid sample ID" ) )
+ if params.get( 'save_button', False ):
+ id_list = util.listify( kwd['id_list'] )
+ for id in id_list:
+ sample_dataset = trans.sa_session.query( trans.app.model.SampleDataset ).get( trans.security.decode_id(id) )
+ prepend = util.restore_text( params.get( 'prepend_%i' % sample_dataset.id, '' ) )
+ name = util.restore_text( params.get( 'name_%i' % sample_dataset.id, sample_dataset.name ) )
+ if prepend == 'None':
+ sample_dataset.name = name
+ else:
+ sample_dataset.name = prepend+'_'+name
+ trans.sa_session.add( sample_dataset )
+ trans.sa_session.flush()
+ return trans.fill_template( '/admin/requests/rename_datasets.mako',
+ sample=sample, id_list=id_list,
+ message='Changes saved successfully.',
+ status='done' )
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='manage_datasets',
+ sample_id=sample.id) )
+
+
+ def __get_files(self, trans, request_type, folder_path):
'''
This method retrieves the filenames to be transfer from the remote host.
'''
- datatx_info = sample.request.type.datatx_info
+ datatx_info = request_type.datatx_info
if not datatx_info['host'] or not datatx_info['username'] or not datatx_info['password']:
message = "Error in sequencer login information."
return trans.response.send_redirect( web.url_for( controller='requests_common',
@@ -555,123 +715,152 @@ class RequestsAdmin( BaseController ):
return output.splitlines()
- def __get_files_in_dir(self, trans, sample, folder_path):
- tmpfiles = self.__get_files(trans, sample, folder_path)
- for tf in tmpfiles:
- if tf[-1] == os.sep:
- self.__get_files_in_dir(trans, sample, os.path.join(folder_path, tf))
+# def __get_files_in_dir(self, trans, sample, folder_path):
+# tmpfiles = self.__get_files(trans, sample, folder_path)
+# for tf in tmpfiles:
+# if tf[-1] == os.sep:
+# self.__get_files_in_dir(trans, sample, os.path.join(folder_path, tf))
+# else:
+# sample.dataset_files.append([os.path.join(folder_path, tf),
+# sample.transfer_status.NOT_STARTED])
+# trans.sa_session.add( sample )
+# trans.sa_session.flush()
+# return
+
+
+ def __samples_selectbox(self, trans, request, sample_id=None):
+ samples_selectbox = SelectField('sample_id')
+ for i, s in enumerate(request.samples):
+ if str(s.id) == sample_id:
+ samples_selectbox.add_option(s.name, s.id, selected=True)
else:
- sample.dataset_files.append([os.path.join(folder_path, tf),
- sample.transfer_status.NOT_STARTED])
- trans.sa_session.add( sample )
- trans.sa_session.flush()
- return
+ samples_selectbox.add_option(s.name, s.id)
+ return samples_selectbox
+
@web.expose
@web.require_admin
def get_data(self, trans, **kwd):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
try:
- sample = trans.sa_session.query( trans.app.model.Sample ).get( kwd['sample_id'] )
+ request = trans.sa_session.query( trans.app.model.Request ).get( kwd['request_id'] )
except:
return trans.response.send_redirect( web.url_for( controller='requests_admin',
action='list',
status='error',
- message="Invalid sample ID" ) )
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
+ message="Invalid request ID" ) )
+ files_list = util.listify( params.get( 'files_list', '' ) )
folder_path = util.restore_text( params.get( 'folder_path',
- sample.request.type.datatx_info['data_dir'] ) )
- files_list = util.listify( params.get( 'files_list', '' ) )
- if params.get( 'start_transfer_button', False ) == 'True':
- return self.__start_datatx(trans, sample)
+ request.type.datatx_info['data_dir'] ) )
+ sbox = self.__samples_selectbox(trans, request, kwd.get('sample_id', None))
if not folder_path:
- return trans.fill_template( '/requests/common/get_data.mako',
- cntrller='requests_admin',
- sample=sample, files=[],
- dataset_files=sample.dataset_files,
+ return trans.fill_template( '/admin/requests/get_data.mako',
+ cntrller='requests_admin', request=request,
+ samples_selectbox=sbox, files=[],
folder_path=folder_path )
if folder_path[-1] != os.sep:
folder_path = folder_path+os.sep
- if params.get( 'browse_button', False ):
+ if params.get( 'show_page', False ):
+ if kwd.get('sample_id', None):
+ sample = trans.sa_session.query( trans.app.model.Sample ).get( kwd['sample_id'] )
+ if sample.datasets:
+ folder_path = os.path.dirname(sample.datasets[-1].file_path)
+ return trans.fill_template( '/admin/requests/get_data.mako',
+ cntrller='requests_admin', request=request,
+ samples_selectbox=sbox, files=[],
+ folder_path=folder_path,
+ status=status, message=message )
+ elif params.get( 'browse_button', False ):
# get the filenames from the remote host
- files = self.__get_files(trans, sample, folder_path)
+ files = self.__get_files(trans, request.type, folder_path)
if folder_path[-1] != os.sep:
folder_path += os.sep
- return trans.fill_template( '/requests/common/get_data.mako',
- cntrller='requests_admin',
- sample=sample, files=files,
- dataset_files=sample.dataset_files,
- folder_path=folder_path )
+ return trans.fill_template( '/admin/requests/get_data.mako',
+ cntrller='requests_admin', request=request,
+ samples_selectbox=sbox, files=files,
+ folder_path=folder_path,
+ status=status, message=message )
elif params.get( 'folder_up', False ):
if folder_path[-1] == os.sep:
folder_path = os.path.dirname(folder_path[:-1])
# get the filenames from the remote host
- files = self.__get_files(trans, sample, folder_path)
+ files = self.__get_files(trans, request.type, folder_path)
if folder_path[-1] != os.sep:
folder_path += os.sep
- return trans.fill_template( '/requests/common/get_data.mako',
- cntrller='requests_admin',
- sample=sample, files=files,
- dataset_files=sample.dataset_files,
- folder_path=folder_path )
+ return trans.fill_template( '/admin/requests/get_data.mako',
+ cntrller='requests_admin',request=request,
+ samples_selectbox=sbox, files=files,
+ folder_path=folder_path,
+ status=status, message=message )
elif params.get( 'open_folder', False ):
if len(files_list) == 1:
folder_path = os.path.join(folder_path, files_list[0])
# get the filenames from the remote host
- files = self.__get_files(trans, sample, folder_path)
+ files = self.__get_files(trans, request.type, folder_path)
if folder_path[-1] != os.sep:
folder_path += os.sep
- return trans.fill_template( '/requests/common/get_data.mako',
- cntrller='requests_admin',
- sample=sample, files=files,
- dataset_files=sample.dataset_files,
- folder_path=folder_path )
- elif params.get( 'remove_dataset_button', False ):
- # get the filenames from the remote host
- files = self.__get_files(trans, sample, folder_path)
- dataset_index = int(params.get( 'dataset_index', 0 ))
- del sample.dataset_files[dataset_index]
- trans.sa_session.add( sample )
- trans.sa_session.flush()
- return trans.fill_template( '/requests/common/get_data.mako',
- cntrller='requests_admin',
- sample=sample, files=files,
- dataset_files=sample.dataset_files,
- folder_path=folder_path)
- elif params.get( 'select_files_button', False ):
- folder_files = []
- if len(files_list):
- for f in files_list:
- filepath = os.path.join(folder_path, f)
- if f[-1] == os.sep:
- # the selected item is a folder so transfer all the
- # folder contents
- # FIXME
- #self.__get_files_in_dir(trans, sample, filepath)
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='get_data',
- sample_id=sample.id,
- folder_path=folder_path,
- open_folder=True))
- else:
- sample.dataset_files.append(dict(filepath=filepath,
- status=sample.transfer_status.NOT_STARTED,
- name=self.__dataset_name(sample, filepath.split('/')[-1]),
- error_msg='',
- size=sample.dataset_size(filepath)))
- trans.sa_session.add( sample )
- trans.sa_session.flush()
+ return trans.fill_template( '/admin/requests/get_data.mako',
+ cntrller='requests_admin', request=request,
+ samples_selectbox=sbox, files=files,
+ folder_path=folder_path,
+ status=status, message=message )
+ elif params.get( 'select_show_datasets_button', False ):
+ sample = trans.sa_session.query( trans.app.model.Sample ).get( kwd['sample_id'] )
+ retval = self.__save_sample_datasets(trans, sample, files_list, folder_path)
+ if retval: message='The dataset(s) %s have been selected for sample <b>%s</b>' %(str(retval)[1:-1].replace("'", ""), sample.name)
+ else: message = None
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='manage_datasets',
+ sample_id=sample.id,
+ status='done',
+ message=message) )
+ elif params.get( 'select_more_button', False ):
+ sample = trans.sa_session.query( trans.app.model.Sample ).get( kwd['sample_id'] )
+ retval = self.__save_sample_datasets(trans, sample, files_list, folder_path)
+ if retval: message='The dataset(s) %s have been selected for sample <b>%s</b>' %(str(retval)[1:-1].replace("'", ""), sample.name)
+ else: message = None
return trans.response.send_redirect( web.url_for( controller='requests_admin',
action='get_data',
+ request_id=sample.request.id,
+ folder_path=folder_path,
sample_id=sample.id,
- folder_path=folder_path,
- open_folder=True))
-
- return trans.response.send_redirect( web.url_for( controller='requests_common',
- cntrller='requests_admin' ,
- action='show_datatx_page',
- sample_id=trans.security.encode_id(sample.id),
- folder_path=folder_path))
+ open_folder=True,
+ status='done',
+ message=message))
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='get_data',
+ request_id=sample.request.id,
+ folder_path=folder_path,
+ show_page=True))
+
+ def __save_sample_datasets(self, trans, sample, files_list, folder_path):
+ files = []
+ if len(files_list):
+ for f in files_list:
+ filepath = os.path.join(folder_path, f)
+ if f[-1] == os.sep:
+ # the selected item is a folder so transfer all the
+ # folder contents
+ # FIXME
+ #self.__get_files_in_dir(trans, sample, filepath)
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='get_data',
+ request=sample.request,
+ folder_path=folder_path,
+ open_folder=True))
+ else:
+ sample_dataset = trans.app.model.SampleDataset( sample=sample,
+ file_path=filepath,
+ status=sample.transfer_status.NOT_STARTED,
+ name=self.__dataset_name(sample, filepath.split('/')[-1]),
+ error_msg='',
+ size=sample.dataset_size(filepath))
+ trans.sa_session.add( sample_dataset )
+ trans.sa_session.flush()
+ files.append(str(sample_dataset.name))
+ return files
+
def __dataset_name(self, sample, filepath):
name = filepath.split('/')[-1]
@@ -735,7 +924,7 @@ class RequestsAdmin( BaseController ):
trans.sa_session.flush()
return datatx_user
- def __send_message(self, trans, datatx_info, sample):
+ def __send_message(self, trans, datatx_info, sample, id_list):
'''
This method creates the xml message and sends it to the rabbitmq server
'''
@@ -752,20 +941,20 @@ class RequestsAdmin( BaseController ):
</data_transfer>'''
dataset_xml = \
'''<dataset>
- <index>%(INDEX)s</index>
+ <dataset_id>%(ID)s</dataset_id><name>%(NAME)s</name><file>%(FILE)s</file></dataset>'''
datasets = ''
- for index, dataset in enumerate(sample.dataset_files):
- if dataset['status'] == sample.transfer_status.NOT_STARTED:
- datasets = datasets + dataset_xml % dict(INDEX=str(index),
- NAME=dataset['name'],
- FILE=dataset['filepath'])
- sample.dataset_files[index]['status'] = sample.transfer_status.IN_QUEUE
-
- trans.sa_session.add( sample )
- trans.sa_session.flush()
+ for id in id_list:
+ sample_dataset = trans.sa_session.query( trans.app.model.SampleDataset ).get( trans.security.decode_id(id) )
+ if sample_dataset.status == sample.transfer_status.NOT_STARTED:
+ datasets = datasets + dataset_xml % dict(ID=str(sample_dataset.id),
+ NAME=sample_dataset.name,
+ FILE=sample_dataset.file_path)
+ sample_dataset.status = sample.transfer_status.IN_QUEUE
+ trans.sa_session.add( sample_dataset )
+ trans.sa_session.flush()
data = xml % dict(DATA_HOST=datatx_info['host'],
DATA_USER=datatx_info['username'],
DATA_PASSWORD=datatx_info['password'],
@@ -790,7 +979,7 @@ class RequestsAdmin( BaseController ):
chan.close()
conn.close()
- def __start_datatx(self, trans, sample):
+ def __start_datatx(self, trans, sample, id_list):
# data transfer user
datatx_user = self.__setup_datatx_user(trans, sample.library, sample.folder)
# validate sequecer information
@@ -799,46 +988,19 @@ class RequestsAdmin( BaseController ):
not datatx_info['username'] or \
not datatx_info['password']:
message = "Error in sequencer login information."
- return trans.response.send_redirect( web.url_for( controller='requests_common',
- cntrller='requests_admin' ,
- action='show_datatx_page',
- sample_id=trans.security.encode_id(sample.id),
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='manage_datasets',
+ sample_id=sample.id,
status='error',
- message=message))
- self.__send_message(trans, datatx_info, sample)
- return trans.response.send_redirect( web.url_for( controller='requests_common',
- cntrller='requests_admin' ,
- action='show_datatx_page',
- sample_id=trans.security.encode_id(sample.id),
- folder_path=datatx_info['data_dir']))
- @web.expose
- @web.require_admin
- def dataset_details( self, trans, **kwd ):
- try:
- sample = trans.sa_session.query( trans.app.model.Sample ).get( trans.security.decode_id(kwd['sample_id']) )
- except:
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- status='error',
- message="Invalid sample ID" ) )
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- dataset_index = int( params.get( 'dataset_index', '' ) )
- if params.get('save', '') == 'Save':
- sample.dataset_files[dataset_index]['name'] = util.restore_text( params.get( 'name',
- sample.dataset_files[dataset_index]['name'] ) )
- trans.sa_session.add( sample )
- trans.sa_session.flush()
- status = 'done'
- message = 'Saved the changes made to the dataset.'
- return trans.fill_template( '/admin/requests/dataset.mako',
- sample=sample,
- dataset_index=dataset_index,
- message=message,
- status=status)
+ message=message) )
+ self.__send_message(trans, datatx_info, sample, id_list)
+ message="%i dataset(s) have been queued for transfer from the sequencer. Click on <b>Refresh</b> button above to get the latest transfer status." % len(id_list)
+ return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ action='manage_datasets',
+ sample_id=sample.id,
+ status='done',
+ message=message) )
-#
##
#### Request Type Stuff ###################################################
##
1
0
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Kanwei Li <kanwei(a)gmail.com>
# Date 1280357492 14400
# Node ID 75e99661d24caaeda1381b14cd062ffdcf7c3ecd
# Parent fd9514c5349fadaf31abec04fd8725cc50a4213f
trackster:
- Fix Reference track incorrect display offset
- When no tracks exist in editor, show suggestion to add tracks
- Clicking "Visualize in Trackster" in history now shows a grid of current browsers, but actual adding is still work in progress
--- a/lib/galaxy/web/controllers/tracks.py
+++ b/lib/galaxy/web/controllers/tracks.py
@@ -87,6 +87,35 @@ class DatasetSelectionGrid( grids.Grid )
.filter( model.History.deleted == False ) \
.filter( model.HistoryDatasetAssociation.deleted == False )
+class TracksterSelectionGrid( grids.Grid ):
+# class DbKeyColumn( grids.GridColumn ):
+# def filter( self, trans, user, query, dbkey ):
+# """ Filter by dbkey. """
+# # use raw SQL b/c metadata is a BLOB
+# dbkey = dbkey.replace("'", "\\'")
+# return query.filter( or_( "metadata like '%%\"dbkey\": [\"%s\"]%%'" % dbkey, "metadata like '%%\"dbkey\": \"%s\"%%'" % dbkey ) )
+
+ # Grid definition.
+ title = "Insert into visualization"
+ template = "/tracks/add_to_viz.mako"
+ async_template = "/page/select_items_grid_async.mako"
+ model_class = model.Visualization
+ default_filter = { "deleted" : "False" , "shared" : "All" }
+ default_sort_key = "title"
+ use_async = True
+ use_paging = False
+ columns = [
+ grids.TextColumn( "Title", key="title", model_class=model.Visualization )
+ ]
+ columns.append(
+ grids.MulticolFilterColumn( "Search", cols_to_filter=[ columns[0] ],
+ key="free-text-search", visible=False, filterable="standard" )
+ )
+
+ def build_initial_query( self, trans, **kwargs ):
+ return trans.sa_session.query( self.model_class )
+ def apply_query_filter( self, trans, query, **kwargs ):
+ return query.filter( self.model_class.user_id == trans.user.id )
class TracksController( BaseController, UsesVisualization ):
"""
@@ -138,7 +167,7 @@ class TracksController( BaseController,
@web.expose
@web.require_login()
- def browser(self, trans, id, chrom=""):
+ def browser(self, trans, id, chrom="", **kwargs):
"""
Display browser for the datasets listed in `dataset_ids`.
"""
@@ -149,7 +178,7 @@ class TracksController( BaseController,
# Set config chrom.
viz_config[ 'chrom' ] = chrom
- return trans.fill_template( 'tracks/browser.mako', config=viz_config )
+ return trans.fill_template( 'tracks/browser.mako', config=viz_config, add_dataset=kwargs.get("dataset_id", None) )
@web.json
def chroms(self, trans, vis_id=None, dbkey=None ):
@@ -288,10 +317,6 @@ class TracksController( BaseController,
data = data['data']
return { 'dataset_type': dataset_type, 'extra_info': extra_info, 'data': data, 'message': message }
- @web.expose
- def list_tracks( self, trans, hid ):
- return None
-
@web.json
def save( self, trans, **kwargs ):
session = trans.sa_session
@@ -328,6 +353,7 @@ class TracksController( BaseController,
return trans.security.encode_id(vis.id)
data_grid = DatasetSelectionGrid()
+ tracks_grid = TracksterSelectionGrid()
@web.expose
@web.require_login( "see all available datasets" )
@@ -336,3 +362,8 @@ class TracksController( BaseController,
# Render the list view
return self.data_grid( trans, **kwargs )
+
+ @web.expose
+ def list_tracks( self, trans, **kwargs ):
+ return self.tracks_grid( trans, **kwargs )
+
--- /dev/null
+++ b/templates/tracks/add_to_viz.mako
@@ -0,0 +1,13 @@
+## Template generates a grid that enables user to add tracks
+<%namespace file="../grid_base.mako" import="*" />
+
+${stylesheets()}
+${grid_javascripts()}
+${render_grid_header( grid, False )}
+${render_grid_table( grid, show_item_checkboxes=True )}
+
+## Initialize the grid.
+<script type="text/javascript">
+ init_grid_elements();
+ init_grid_controls();
+</script>
--- a/static/scripts/packed/galaxy.base.js
+++ b/static/scripts/packed/galaxy.base.js
@@ -1,1 +1,1 @@
-$.fn.makeAbsolute=function(a){return this.each(function(){var b=$(this);var c=b.position();b.css({position:"absolute",marginLeft:0,marginTop:0,top:c.top,left:c.left,right:$(window).width()-(c.left+b.width())});if(a){b.remove().appendTo("body")}})};function ensure_popup_helper(){if($("#popup-helper").length===0){$("<div id='popup-helper'/>").css({background:"white",opacity:0,zIndex:15000,position:"absolute",top:0,left:0,width:"100%",height:"100%"}).appendTo("body").hide()}}function attach_popupmenu(b,d){var a=function(){d.unbind().hide();$("#popup-helper").unbind("click.popupmenu").hide()};var c=function(g){$("#popup-helper").bind("click.popupmenu",a).show();d.click(a).css({left:0,top:-1000}).show();var f=g.pageX-d.width()/2;f=Math.min(f,$(document).scrollLeft()+$(window).width()-$(d).width()-20);f=Math.max(f,$(document).scrollLeft()+20);d.css({top:g.pageY-5,left:f});return false};$(b).click(c)}function make_popupmenu(c,b){ensure_popup_helper();var a=$("<ul id='"+c.attr("id")
+"-menu'></ul>");$.each(b,function(f,e){if(e){$("<li/>").html(f).click(e).appendTo(a)}else{$("<li class='head'/>").html(f).appendTo(a)}});var d=$("<div class='popmenu-wrapper'>");d.append(a).append("<div class='overlay-border'>").css("position","absolute").appendTo("body").hide();attach_popupmenu(c,d)}function make_popup_menus(){jQuery("div[popupmenu]").each(function(){var c={};$(this).find("a").each(function(){var b=$(this).attr("confirm"),d=$(this).attr("href"),e=$(this).attr("target");c[$(this).text()]=function(){if(!b||confirm(b)){var g=window;if(e=="_parent"){g=window.parent}else{if(e=="_top"){g=window.top}}g.location=d}}});var a=$("#"+$(this).attr("popupmenu"));make_popupmenu(a,c);$(this).remove();a.addClass("popup").show()})}function array_length(b){if(b.length){return b.length}var c=0;for(var a in b){c++}return c}function naturalSort(i,g){var n=/(-?[0-9\.]+)/g,j=i.toString().toLowerCase()||"",f=g.toString().toLowerCase()||"",k=String.fromCharCode(0),l=j.replace(n,k+"
$1"+k).split(k),e=f.replace(n,k+"$1"+k).split(k),d=(new Date(j)).getTime(),m=d?(new Date(f)).getTime():null;if(m){if(d<m){return -1}else{if(d>m){return 1}}}for(var h=0,c=Math.max(l.length,e.length);h<c;h++){oFxNcL=parseFloat(l[h])||l[h];oFyNcL=parseFloat(e[h])||e[h];if(oFxNcL<oFyNcL){return -1}else{if(oFxNcL>oFyNcL){return 1}}}return 0}function replace_big_select_inputs(a,b){if(!jQuery().autocomplete){return}if(a===undefined){a=20}if(b===undefined){b=3000}$("select").each(function(){var e=$(this);var h=e.find("option").length;if((h<a)||(h>b)){return}if(e.attr("multiple")==true){return}if(e.hasClass("no-autocomplete")){return}var l=e.attr("value");var c=$("<input type='text' class='text-and-autocomplete-select'></input>");c.attr("size",40);c.attr("name",e.attr("name"));c.attr("id",e.attr("id"));c.click(function(){var m=$(this).val();$(this).val("Loading...");$(this).showAllInCache();$(this).val(m);$(this).select()});var f=[];var i={};e.children("option").each(function(){var n
=$(this).text();var m=$(this).attr("value");f.push(n);i[n]=m;i[m]=m;if(m==l){c.attr("value",n)}});if(l==""||l=="?"){c.attr("value","Click to Search or Select")}if(e.attr("name")=="dbkey"){f=f.sort(naturalSort)}var g={selectFirst:false,autoFill:false,mustMatch:false,matchContains:true,max:b,minChars:0,hideForLessThanMinChars:false};c.autocomplete(f,g);e.replaceWith(c);var k=function(){var n=c.attr("value");var m=i[n];if(m!==null&&m!==undefined){c.attr("value",m)}else{if(l!=""){c.attr("value",l)}else{c.attr("value","?")}}};c.parents("form").submit(function(){k()});$(document).bind("convert_dbkeys",function(){k()});if(e.attr("refresh_on_change")=="true"){var d=e.attr("refresh_on_change_values");if(d!==undefined){d=d.split(",")}var j=function(){var o=c.attr("value");var n=i[o];if(n!==null&&n!==undefined){refresh=false;if(d!==undefined){for(var m=0;m<d.length;m++){if(n==d[m]){refresh=true;break}}}else{refresh=true}if(refresh){c.attr("value",n);c.parents("form").submit()}}};c.bind
("result",j);c.keyup(function(m){if(m.keyCode===13){j()}});c.keydown(function(m){if(m.keyCode===13){return false}})}})}function async_save_text(d,f,e,a,c,h,i,g,b){if(c===undefined){c=30}if(i===undefined){i=4}$("#"+d).live("click",function(){if($("#renaming-active").length>0){return}var l=$("#"+f),k=l.text(),j;if(h){j=$("<textarea></textarea>").attr({rows:i,cols:c}).text(k)}else{j=$("<input type='text'></input>").attr({value:k,size:c})}j.attr("id","renaming-active");j.blur(function(){$(this).remove();l.show();if(b){b(j)}});j.keyup(function(n){if(n.keyCode===27){$(this).trigger("blur")}else{if(n.keyCode===13){var m={};m[a]=$(this).val();$(this).trigger("blur");$.ajax({url:e,data:m,error:function(){alert("Text editing for elt "+f+" failed")},success:function(o){l.text(o);if(b){b(j)}}})}}});if(g){g(j)}l.hide();j.insertAfter(l);j.focus();j.select();return})}function init_history_items(d,a,c){var b=function(){try{var e=$.jStore.store("history_expand_state");if(e){for(var g in e){$
("#"+g+" div.historyItemBody").show()}}}catch(f){$.jStore.remove("history_expand_state")}if($.browser.mozilla){$("div.historyItemBody").each(function(){if(!$(this).is(":visible")){$(this).find("pre.peek").css("overflow","hidden")}})}d.each(function(){var j=this.id;var h=$(this).children("div.historyItemBody");var i=h.find("pre.peek");$(this).find(".historyItemTitleBar > .historyItemTitle").wrap("<a href='javascript:void(0);'></a>").click(function(){if(h.is(":visible")){if($.browser.mozilla){i.css("overflow","hidden")}h.slideUp("fast");if(!c){var k=$.jStore.store("history_expand_state");if(k){delete k[j];$.jStore.store("history_expand_state",k)}}}else{h.slideDown("fast",function(){if($.browser.mozilla){i.css("overflow","auto")}});if(!c){var k=$.jStore.store("history_expand_state");if(k===undefined){k={}}k[j]=true;$.jStore.store("history_expand_state",k)}}return false})});$("#top-links > a.toggle").click(function(){var h=$.jStore.store("history_expand_state");if(h===undefined)
{h={}}$("div.historyItemBody:visible").each(function(){if($.browser.mozilla){$(this).find("pre.peek").css("overflow","hidden")}$(this).slideUp("fast");if(h){delete h[$(this).parent().attr("id")]}});$.jStore.store("history_expand_state",h)}).show()};if(a){b()}else{$.jStore.init("galaxy");$.jStore.engineReady(function(){b()})}}function commatize(b){b+="";var a=/(\d+)(\d{3})/;while(a.test(b)){b=b.replace(a,"$1,$2")}return b}function reset_tool_search(a){var c=$("#galaxy_tools").contents();if(c.length==0){c=$(document)}$(this).removeClass("search_active");c.find(".toolTitle").removeClass("search_match");c.find(".toolSectionBody").hide();c.find(".toolTitle").show();c.find(".toolPanelLabel").show();c.find(".toolSectionWrapper").each(function(){if($(this).attr("id")!="recently_used_wrapper"){$(this).show()}else{if($(this).hasClass("user_pref_visible")){$(this).show()}}});c.find("#search-no-results").hide();c.find("#search-spinner").hide();if(a){var b=c.find("#tool-search-query");b.
val("search tools");b.css("font-style","italic")}}function GalaxyAsync(a){this.url_dict={};this.log_action=(a===undefined?false:a)}GalaxyAsync.prototype.set_func_url=function(a,b){this.url_dict[a]=b};GalaxyAsync.prototype.set_user_pref=function(a,b){var c=this.url_dict[arguments.callee];if(c===undefined){return false}$.ajax({url:c,data:{pref_name:a,pref_value:b},error:function(){return false},success:function(){return true}})};GalaxyAsync.prototype.log_user_action=function(c,b,d){if(!this.log_action){return}var a=this.url_dict[arguments.callee];if(a===undefined){return false}$.ajax({url:a,data:{action:c,context:b,params:d},error:function(){return false},success:function(){return true}})};$(document).ready(function(){$("a[confirm]").click(function(){return confirm($(this).attr("confirm"))});if($.fn.tipsy){$(".tooltip").tipsy({gravity:"s"})}make_popup_menus();replace_big_select_inputs(20,1500)});
+$.fn.makeAbsolute=function(a){return this.each(function(){var b=$(this);var c=b.position();b.css({position:"absolute",marginLeft:0,marginTop:0,top:c.top,left:c.left,right:$(window).width()-(c.left+b.width())});if(a){b.remove().appendTo("body")}})};function ensure_popup_helper(){if($("#popup-helper").length===0){$("<div id='popup-helper'/>").css({background:"white",opacity:0,zIndex:15000,position:"absolute",top:0,left:0,width:"100%",height:"100%"}).appendTo("body").hide()}}function attach_popupmenu(b,d){var a=function(){d.unbind().hide();$("#popup-helper").unbind("click.popupmenu").hide()};var c=function(g){$("#popup-helper").bind("click.popupmenu",a).show();d.click(a).css({left:0,top:-1000}).show();var f=g.pageX-d.width()/2;f=Math.min(f,$(document).scrollLeft()+$(window).width()-$(d).width()-20);f=Math.max(f,$(document).scrollLeft()+20);d.css({top:g.pageY-5,left:f});return false};$(b).click(c)}function make_popupmenu(c,b){ensure_popup_helper();var a=$("<ul id='"+c.attr("id")
+"-menu'></ul>");$.each(b,function(f,e){if(e){$("<li/>").html(f).click(e).appendTo(a)}else{$("<li class='head'/>").html(f).appendTo(a)}});var d=$("<div class='popmenu-wrapper'>");d.append(a).append("<div class='overlay-border'>").css("position","absolute").appendTo("body").hide();attach_popupmenu(c,d)}function make_popup_menus(){jQuery("div[popupmenu]").each(function(){var c={};$(this).find("a").each(function(){var b=$(this).attr("confirm"),d=$(this).attr("href"),e=$(this).attr("target");c[$(this).text()]=function(){if(!b||confirm(b)){var g=window;if(e=="_parent"){g=window.parent}else{if(e=="_top"){g=window.top}}g.location=d}}});var a=$("#"+$(this).attr("popupmenu"));make_popupmenu(a,c);$(this).remove();a.addClass("popup").show()})}function array_length(b){if(b.length){return b.length}var c=0;for(var a in b){c++}return c}function naturalSort(i,g){var n=/(-?[0-9\.]+)/g,j=i.toString().toLowerCase()||"",f=g.toString().toLowerCase()||"",k=String.fromCharCode(0),l=j.replace(n,k+"
$1"+k).split(k),e=f.replace(n,k+"$1"+k).split(k),d=(new Date(j)).getTime(),m=d?(new Date(f)).getTime():null;if(m){if(d<m){return -1}else{if(d>m){return 1}}}for(var h=0,c=Math.max(l.length,e.length);h<c;h++){oFxNcL=parseFloat(l[h])||l[h];oFyNcL=parseFloat(e[h])||e[h];if(oFxNcL<oFyNcL){return -1}else{if(oFxNcL>oFyNcL){return 1}}}return 0}function replace_big_select_inputs(a,b){if(!jQuery().autocomplete){return}if(a===undefined){a=20}if(b===undefined){b=3000}$("select").each(function(){var e=$(this);var h=e.find("option").length;if((h<a)||(h>b)){return}if(e.attr("multiple")==true){return}if(e.hasClass("no-autocomplete")){return}var l=e.attr("value");var c=$("<input type='text' class='text-and-autocomplete-select'></input>");c.attr("size",40);c.attr("name",e.attr("name"));c.attr("id",e.attr("id"));c.click(function(){var m=$(this).val();$(this).val("Loading...");$(this).showAllInCache();$(this).val(m);$(this).select()});var f=[];var i={};e.children("option").each(function(){var n
=$(this).text();var m=$(this).attr("value");f.push(n);i[n]=m;i[m]=m;if(m==l){c.attr("value",n)}});if(l==""||l=="?"){c.attr("value","Click to Search or Select")}if(e.attr("name")=="dbkey"){f=f.sort(naturalSort)}var g={selectFirst:false,autoFill:false,mustMatch:false,matchContains:true,max:b,minChars:0,hideForLessThanMinChars:false};c.autocomplete(f,g);e.replaceWith(c);var k=function(){var n=c.attr("value");var m=i[n];if(m!==null&&m!==undefined){c.attr("value",m)}else{if(l!=""){c.attr("value",l)}else{c.attr("value","?")}}};c.parents("form").submit(function(){k()});$(document).bind("convert_dbkeys",function(){k()});if(e.attr("refresh_on_change")=="true"){var d=e.attr("refresh_on_change_values");if(d!==undefined){d=d.split(",")}var j=function(){var o=c.attr("value");var n=i[o];if(n!==null&&n!==undefined){refresh=false;if(d!==undefined){for(var m=0;m<d.length;m++){if(n==d[m]){refresh=true;break}}}else{refresh=true}if(refresh){c.attr("value",n);c.parents("form").submit()}}};c.bind
("result",j);c.keyup(function(m){if(m.keyCode===13){j()}});c.keydown(function(m){if(m.keyCode===13){return false}})}})}function async_save_text(d,f,e,a,c,h,i,g,b){if(c===undefined){c=30}if(i===undefined){i=4}$("#"+d).live("click",function(){if($("#renaming-active").length>0){return}var l=$("#"+f),k=l.text(),j;if(h){j=$("<textarea></textarea>").attr({rows:i,cols:c}).text(k)}else{j=$("<input type='text'></input>").attr({value:k,size:c})}j.attr("id","renaming-active");j.blur(function(){$(this).remove();l.show();if(b){b(j)}});j.keyup(function(n){if(n.keyCode===27){$(this).trigger("blur")}else{if(n.keyCode===13){var m={};m[a]=$(this).val();$(this).trigger("blur");$.ajax({url:e,data:m,error:function(){alert("Text editing for elt "+f+" failed")},success:function(o){l.text(o);if(b){b(j)}}})}}});if(g){g(j)}l.hide();j.insertAfter(l);j.focus();j.select();return})}function init_history_items(d,a,c){var b=function(){try{var e=$.jStore.store("history_expand_state");if(e){for(var g in e){$
("#"+g+" div.historyItemBody").show()}}}catch(f){$.jStore.remove("history_expand_state")}if($.browser.mozilla){$("div.historyItemBody").each(function(){if(!$(this).is(":visible")){$(this).find("pre.peek").css("overflow","hidden")}})}d.each(function(){var j=this.id;var h=$(this).children("div.historyItemBody");var i=h.find("pre.peek");$(this).find(".historyItemTitleBar > .historyItemTitle").wrap("<a href='javascript:void(0);'></a>").click(function(){if(h.is(":visible")){if($.browser.mozilla){i.css("overflow","hidden")}h.slideUp("fast");if(!c){var k=$.jStore.store("history_expand_state");if(k){delete k[j];$.jStore.store("history_expand_state",k)}}}else{h.slideDown("fast",function(){if($.browser.mozilla){i.css("overflow","auto")}});if(!c){var k=$.jStore.store("history_expand_state");if(k===undefined){k={}}k[j]=true;$.jStore.store("history_expand_state",k)}}return false})});$("#top-links > a.toggle").click(function(){var h=$.jStore.store("history_expand_state");if(h===undefined)
{h={}}$("div.historyItemBody:visible").each(function(){if($.browser.mozilla){$(this).find("pre.peek").css("overflow","hidden")}$(this).slideUp("fast");if(h){delete h[$(this).parent().attr("id")]}});$.jStore.store("history_expand_state",h)}).show()};if(a){b()}else{$.jStore.init("galaxy");$.jStore.engineReady(function(){b()})}}function commatize(b){b+="";var a=/(\d+)(\d{3})/;while(a.test(b)){b=b.replace(a,"$1,$2")}return b}function reset_tool_search(a){var c=$("#galaxy_tools").contents();if(c.length==0){c=$(document)}$(this).removeClass("search_active");c.find(".toolTitle").removeClass("search_match");c.find(".toolSectionBody").hide();c.find(".toolTitle").show();c.find(".toolPanelLabel").show();c.find(".toolSectionWrapper").each(function(){if($(this).attr("id")!="recently_used_wrapper"){$(this).show()}else{if($(this).hasClass("user_pref_visible")){$(this).show()}}});c.find("#search-no-results").hide();c.find("#search-spinner").hide();if(a){var b=c.find("#tool-search-query");b.
val("search tools");b.css("font-style","italic")}}function GalaxyAsync(a){this.url_dict={};this.log_action=(a===undefined?false:a)}GalaxyAsync.prototype.set_func_url=function(a,b){this.url_dict[a]=b};GalaxyAsync.prototype.set_user_pref=function(a,b){var c=this.url_dict[arguments.callee];if(c===undefined){return false}$.ajax({url:c,data:{pref_name:a,pref_value:b},error:function(){return false},success:function(){return true}})};GalaxyAsync.prototype.log_user_action=function(c,b,d){if(!this.log_action){return}var a=this.url_dict[arguments.callee];if(a===undefined){return false}$.ajax({url:a,data:{action:c,context:b,params:d},error:function(){return false},success:function(){return true}})};$(".trackster-add").live("click",function(){var b=this,a=$(this);$.ajax({url:a.attr("data-url"),data:{"f-dbkey":"hi"},dataType:"html",error:function(){alert("Could not add this dataset to browser.")},success:function(c){var d=window.parent;d.show_modal("Add to Browser:",c,{"Insert Dataset In
to":function(){$(d.document).find("input[name=id]:checked").each(function(){var e=$(this).val();d.location=a.attr("action-url")+"&id="+e});d.hide_modal()},Cancel:function(){d.hide_modal()}})}})});$(document).ready(function(){$("a[confirm]").click(function(){return confirm($(this).attr("confirm"))});if($.fn.tipsy){$(".tooltip").tipsy({gravity:"s"})}make_popup_menus();replace_big_select_inputs(20,1500)});
--- a/static/scripts/packed/trackster.js
+++ b/static/scripts/packed/trackster.js
@@ -1,1 +1,1 @@
-var DENSITY=200,FEATURE_LEVELS=10,DATA_ERROR="There was an error in indexing this dataset. ",DATA_NOCONVERTER="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",DATA_NONE="No data for this chrom/contig.",DATA_PENDING="Currently indexing... please wait",DATA_LOADING="Loading data...",CACHED_TILES_FEATURE=10,CACHED_TILES_LINE=30,CACHED_DATA=5,CONTEXT=$("<canvas></canvas>").get(0).getContext("2d"),PX_PER_CHAR=CONTEXT.measureText("A").width,RIGHT_STRAND,LEFT_STRAND;var right_img=new Image();right_img.src=image_path+"/visualization/strand_right.png";right_img.onload=function(){RIGHT_STRAND=CONTEXT.createPattern(right_img,"repeat")};var left_img=new Image();left_img.src=image_path+"/visualization/strand_left.png";left_img.onload=function(){LEFT_STRAND=CONTEXT.createPattern(left_img,"repeat")};var right_img_inv=new Image();right_img_inv.src=image_path+"/visualization/strand_right_inv.png";right_img_inv.onload=function(){RIGHT_STRAND_INV=CONT
EXT.createPattern(right_img_inv,"repeat")};var left_img_inv=new Image();left_img_inv.src=image_path+"/visualization/strand_left_inv.png";left_img_inv.onload=function(){LEFT_STRAND_INV=CONTEXT.createPattern(left_img_inv,"repeat")};var Cache=function(a){this.num_elements=a;this.clear()};$.extend(Cache.prototype,{get:function(b){var a=this.key_ary.indexOf(b);if(a!=-1){this.key_ary.splice(a,1);this.key_ary.push(b)}return this.obj_cache[b]},set:function(b,c){if(!this.obj_cache[b]){if(this.key_ary.length>=this.num_elements){var a=this.key_ary.shift();delete this.obj_cache[a]}this.key_ary.push(b)}this.obj_cache[b]=c;return c},clear:function(){this.obj_cache={};this.key_ary=[]}});var View=function(a,c,e,d,b){this.container=a;this.vis_id=d;this.dbkey=b;this.title=e;this.chrom=c;this.tracks=[];this.label_tracks=[];this.max_low=0;this.max_high=0;this.track_id_counter=0;this.zoom_factor=3;this.min_separation=30;this.has_changes=false;this.init();this.reset()};$.extend(View.prototype,{in
it:function(){var b=this.container,a=this;this.top_labeltrack=$("<div/>").addClass("top-labeltrack").appendTo(b);this.content_div=$("<div/>").addClass("content").css("position","relative").appendTo(b);this.intro_div=$("<div/>").addClass("intro").text("Select a chrom from the dropdown below").hide().appendTo(b);this.viewport_container=$("<div/>").addClass("viewport-container").addClass("viewport-container").appendTo(this.content_div);this.viewport=$("<div/>").addClass("viewport").appendTo(this.viewport_container);this.nav_container=$("<div/>").addClass("nav-container").appendTo(b);this.nav_labeltrack=$("<div/>").addClass("nav-labeltrack").appendTo(this.nav_container);this.nav=$("<div/>").addClass("nav").appendTo(this.nav_container);this.overview=$("<div/>").addClass("overview").appendTo(this.nav);this.overview_viewport=$("<div/>").addClass("overview-viewport").appendTo(this.overview);this.overview_box=$("<div/>").addClass("overview-box").appendTo(this.overview_viewport);this.
nav_controls=$("<div/>").addClass("nav-controls").appendTo(this.nav);this.chrom_form=$("<form/>").attr("action",function(){void (0)}).appendTo(this.nav_controls);this.chrom_select=$("<select/>").attr({name:"chrom"}).css("width","15em").addClass("no-autocomplete").append("<option value=''>Loading</option>").appendTo(this.chrom_form);this.low_input=$("<input/>").addClass("low").css("width","10em").appendTo(this.chrom_form);$("<span/>").text(" - ").appendTo(this.chrom_form);this.high_input=$("<input/>").addClass("high").css("width","10em").appendTo(this.chrom_form);if(this.vis_id!==undefined){this.hidden_input=$("<input/>").attr("type","hidden").val(this.vis_id).appendTo(this.chrom_form)}this.zi_link=$("<a/>").click(function(){a.zoom_in();a.redraw()}).html('<img src="'+image_path+'/fugue/magnifier-zoom.png" />').appendTo(this.chrom_form);this.zo_link=$("<a/>").click(function(){a.zoom_out();a.redraw()}).html('<img src="'+image_path+'/fugue/magnifier-zoom-out.png" />').appendTo(t
his.chrom_form);$.ajax({url:chrom_url,data:(this.vis_id!==undefined?{vis_id:this.vis_id}:{dbkey:this.dbkey}),dataType:"json",success:function(c){if(c.reference){a.add_label_track(new ReferenceTrack(a))}a.chrom_data=c.chrom_info;var f='<option value="">Select Chrom/Contig</option>';for(i in a.chrom_data){var e=a.chrom_data[i]["chrom"];f+='<option value="'+e+'">'+e+"</option>"}a.chrom_select.html(f);var d=function(){if(a.chrom_select.val()===""){a.intro_div.show();a.content_div.hide()}else{a.intro_div.hide();a.content_div.show()}a.chrom=a.chrom_select.val();var h=$.grep(a.chrom_data,function(k,l){return k.chrom===a.chrom})[0];a.max_high=(h!==undefined?h.len:0);a.reset();a.redraw(true);for(var j in a.tracks){var g=a.tracks[j];if(g.init){g.init()}}a.redraw()};a.chrom_select.bind("change",d);d()},error:function(){alert("Could not load chroms for this dbkey:",a.dbkey)}});this.content_div.bind("dblclick",function(c){a.zoom_in(c.pageX,this.viewport_container)});this.overview_box.bin
d("dragstart",function(c){this.current_x=c.offsetX}).bind("drag",function(c){var f=c.offsetX-this.current_x;this.current_x=c.offsetX;var d=Math.round(f/a.viewport_container.width()*(a.max_high-a.max_low));a.move_delta(-d)});this.viewport_container.bind("dragstart",function(c){this.original_low=a.low;this.current_height=c.clientY;this.current_x=c.offsetX}).bind("drag",function(f){var c=$(this);var h=f.offsetX-this.current_x;var d=c.scrollTop()-(f.clientY-this.current_height);c.scrollTop(d);this.current_height=f.clientY;this.current_x=f.offsetX;var g=Math.round(h/a.viewport_container.width()*(a.high-a.low));a.move_delta(g)});this.top_labeltrack.bind("dragstart",function(c){this.drag_origin_x=c.clientX;this.drag_origin_pos=c.clientX/a.viewport_container.width()*(a.high-a.low)+a.low;this.drag_div=$("<div />").css({height:a.viewport_container.height(),top:"0px",position:"absolute","background-color":"#cfc",border:"1px solid #6a6",opacity:0.5,"z-index":1000}).appendTo($(this))}).b
ind("drag",function(h){var d=Math.min(h.clientX,this.drag_origin_x)-a.container.offset().left,c=Math.max(h.clientX,this.drag_origin_x)-a.container.offset().left,g=(a.high-a.low),f=a.viewport_container.width();a.low_input.val(commatize(Math.round(d/f*g)+a.low));a.high_input.val(commatize(Math.round(c/f*g)+a.low));this.drag_div.css({left:d+"px",width:(c-d)+"px"})}).bind("dragend",function(j){var d=Math.min(j.clientX,this.drag_origin_x),c=Math.max(j.clientX,this.drag_origin_x),g=(a.high-a.low),f=a.viewport_container.width(),h=a.low;a.low=Math.round(d/f*g)+h;a.high=Math.round(c/f*g)+h;this.drag_div.remove();a.redraw()});this.add_label_track(new LabelTrack(this,this.top_labeltrack));this.add_label_track(new LabelTrack(this,this.nav_labeltrack))},move_delta:function(c){var a=this;var b=a.high-a.low;if(a.low-c<a.max_low){a.low=a.max_low;a.high=a.max_low+b}else{if(a.high-c>a.max_high){a.high=a.max_high;a.low=a.max_high-b}else{a.high-=c;a.low-=c}}a.redraw()},add_track:function(a){a.v
iew=this;a.track_id=this.track_id_counter;this.tracks.push(a);if(a.init){a.init()}a.container_div.attr("id","track_"+a.track_id);this.track_id_counter+=1},add_label_track:function(a){a.view=this;this.label_tracks.push(a)},remove_track:function(a){this.has_changes=true;a.container_div.fadeOut("slow",function(){$(this).remove()});delete this.tracks[this.tracks.indexOf(a)]},update_options:function(){this.has_changes=true;var b=$("ul#sortable-ul").sortable("toArray");for(var c in b){var e=b[c].split("_li")[0].split("track_")[1];this.viewport.append($("#track_"+e))}for(var d in view.tracks){var a=view.tracks[d];if(a&&a.update_options){a.update_options(d)}}},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},redraw:function(f){var d=this.high-this.low,b=this.low,e=this.high;if(b<this.max_low){b=this.max_low}if(e>this.max_high){e=this.max_high}if(this.high!==0&&d<this.min_separation){e=b+this.min_separation}this.low=
Math.floor(b);this.high=Math.ceil(e);this.resolution=Math.pow(10,Math.ceil(Math.log((this.high-this.low)/200)/Math.LN10));this.zoom_res=Math.pow(FEATURE_LEVELS,Math.max(0,Math.ceil(Math.log(this.resolution,FEATURE_LEVELS)/Math.log(FEATURE_LEVELS))));this.overview_box.css({left:(this.low/(this.max_high-this.max_low))*this.overview_viewport.width(),width:Math.max(12,(this.high-this.low)/(this.max_high-this.max_low)*this.overview_viewport.width())}).show();this.low_input.val(commatize(this.low));this.high_input.val(commatize(this.high));if(!f){for(var c=0,a=this.tracks.length;c<a;c++){if(this.tracks[c]&&this.tracks[c].enabled){this.tracks[c].draw()}}for(var c=0,a=this.label_tracks.length;c<a;c++){this.label_tracks[c].draw()}}},zoom_in:function(b,c){if(this.max_high===0||this.high-this.low<this.min_separation){return}var d=this.high-this.low,e=d/2+this.low,a=(d/this.zoom_factor)/2;if(b){e=b/this.viewport_container.width()*(this.high-this.low)+this.low}this.low=Math.round(e-a);th
is.high=Math.round(e+a);this.redraw()},zoom_out:function(){if(this.max_high===0){return}var b=this.high-this.low,c=b/2+this.low,a=(b*this.zoom_factor)/2;this.low=Math.round(c-a);this.high=Math.round(c+a);this.redraw()}});var Track=function(b,a,c){this.name=b;this.parent_element=c;this.view=a;this.init_global()};$.extend(Track.prototype,{init_global:function(){this.header_div=$("<div class='track-header'>").text(this.name);this.content_div=$("<div class='track-content'>");this.container_div=$("<div />").addClass("track").append(this.header_div).append(this.content_div);this.parent_element.append(this.container_div)},init_each:function(c,b){var a=this;a.enabled=false;a.data_queue={};a.tile_cache.clear();a.data_cache.clear();a.content_div.css("height","auto");if(!a.content_div.text()){a.content_div.text(DATA_LOADING)}a.container_div.removeClass("nodata error pending");if(a.view.chrom){$.getJSON(data_url,c,function(d){if(!d||d==="error"||d.kind==="error"){a.container_div.addClas
s("error");a.content_div.text(DATA_ERROR);if(d.message){var f=a.view.tracks.indexOf(a);var e=$("<a href='javascript:void(0);'></a>").attr("id",f+"_error");e.text("Click to view error");$("#"+f+"_error").live("click",function(){show_modal("Trackster Error","<pre>"+d.message+"</pre>",{Close:hide_modal})});a.content_div.append(e)}}else{if(d==="no converter"){a.container_div.addClass("error");a.content_div.text(DATA_NOCONVERTER)}else{if(d.data!==undefined&&(d.data===null||d.data.length===0)){a.container_div.addClass("nodata");a.content_div.text(DATA_NONE)}else{if(d==="pending"){a.container_div.addClass("pending");a.content_div.text(DATA_PENDING);setTimeout(function(){a.init()},5000)}else{a.content_div.text("");a.content_div.css("height",a.height_px+"px");a.enabled=true;b(d);a.draw()}}}}})}else{a.container_div.addClass("nodata");a.content_div.text(DATA_NONE)}}});var TiledTrack=function(){this.left_offset=200};$.extend(TiledTrack.prototype,Track.prototype,{draw:function(){var j=th
is.view.low,e=this.view.high,f=e-j,d=this.view.resolution;var l=$("<div style='position: relative;'></div>"),m=this.content_div.width()/f,h;this.content_div.children(":first").remove();this.content_div.append(l),this.max_height=0;var a=Math.floor(j/d/DENSITY);while((a*DENSITY*d)<e){var k=this.content_div.width()+"_"+m+"_"+a;var c=this.tile_cache.get(k);if(c){var g=a*DENSITY*d;var b=(g-j)*m;if(this.left_offset){b-=this.left_offset}c.css({left:b});l.append(c);this.max_height=Math.max(this.max_height,c.height());this.content_div.css("height",this.max_height+"px")}else{this.delayed_draw(this,k,j,e,a,d,l,m)}a+=1}},delayed_draw:function(c,e,a,f,b,d,g,h){setTimeout(function(){if(!(a>c.view.high||f<c.view.low)){tile_element=c.draw_tile(d,b,g,h);if(tile_element){c.tile_cache.set(e,tile_element);c.max_height=Math.max(c.max_height,tile_element.height());c.content_div.css("height",c.max_height+"px")}}},50)}});var LabelTrack=function(a,b){Track.call(this,null,a,b);this.track_type="LabelT
rack";this.hidden=true;this.container_div.addClass("label-track")};$.extend(LabelTrack.prototype,Track.prototype,{draw:function(){var c=this.view,d=c.high-c.low,g=Math.floor(Math.pow(10,Math.floor(Math.log(d)/Math.log(10)))),a=Math.floor(c.low/g)*g,e=this.content_div.width(),b=$("<div style='position: relative; height: 1.3em;'></div>");while(a<c.high){var f=(a-c.low)/d*e;b.append($("<div class='label'>"+commatize(a)+"</div>").css({position:"absolute",left:f-1}));a+=g}this.content_div.children(":first").remove();this.content_div.append(b)}});var ReferenceTrack=function(a){this.track_type="ReferenceTrack";Track.call(this,null,a,a.top_labeltrack);TiledTrack.call(this);this.hidden=true;this.height_px=12;this.container_div.addClass("reference-track");this.dummy_canvas=$("<canvas></canvas>").get(0).getContext("2d");this.data_queue={};this.data_cache=new Cache(CACHED_DATA);this.tile_cache=new Cache(CACHED_TILES_LINE)};$.extend(ReferenceTrack.prototype,TiledTrack.prototype,{get_data
:function(d,b){var c=this,a=b*DENSITY*d,f=(b+1)*DENSITY*d,e=d+"_"+b;if(!c.data_queue[e]){c.data_queue[e]=true;$.ajax({url:reference_url,dataType:"json",data:{chrom:this.view.chrom,low:a,high:f,dbkey:this.view.dbkey},success:function(g){c.data_cache.set(e,g);delete c.data_queue[e];c.draw()},error:function(h,g,j){console.log(h,g,j)}})}},draw_tile:function(f,b,k,o){var g=b*DENSITY*f,d=DENSITY*f,e=$("<canvas class='tile'></canvas>"),n=e.get(0).getContext("2d"),j=f+"_"+b;if(o>PX_PER_CHAR){if(this.data_cache.get(j)===undefined){this.get_data(f,b);return}var m=this.data_cache.get(j);if(m===null){this.content_div.css("height","0px");return}e.get(0).width=Math.ceil(d*o+this.left_offset);e.get(0).height=this.height_px;e.css({position:"absolute",top:0,left:(g-this.view.low)*o+this.left_offset});for(var h=0,l=m.length;h<l;h++){var a=Math.round(h*o);n.fillText(m[h],a+this.left_offset,10)}k.append(e);return e}this.content_div.css("height","0px")}});var LineTrack=function(d,b,a,c){this.tra
ck_type="LineTrack";Track.call(this,d,b,b.viewport_container);TiledTrack.call(this);this.height_px=100;this.dataset_id=a;this.data_cache=new Cache(CACHED_DATA);this.tile_cache=new Cache(CACHED_TILES_LINE);this.prefs={min_value:undefined,max_value:undefined,mode:"Line"};if(c.min_value!==undefined){this.prefs.min_value=c.min_value}if(c.max_value!==undefined){this.prefs.max_value=c.max_value}if(c.mode!==undefined){this.prefs.mode=c.mode}};$.extend(LineTrack.prototype,TiledTrack.prototype,{init:function(){var a=this,b=a.view.tracks.indexOf(a);a.vertical_range=undefined;this.init_each({stats:true,chrom:a.view.chrom,low:null,high:null,dataset_id:a.dataset_id},function(c){a.container_div.addClass("line-track");data=c.data;if(isNaN(parseFloat(a.prefs.min_value))||isNaN(parseFloat(a.prefs.max_value))){a.prefs.min_value=data.min;a.prefs.max_value=data.max;$("#track_"+b+"_minval").val(a.prefs.min_value);$("#track_"+b+"_maxval").val(a.prefs.max_value)}a.vertical_range=a.prefs.max_value-
a.prefs.min_value;a.total_frequency=data.total_frequency;$("#linetrack_"+b+"_minval").remove();$("#linetrack_"+b+"_maxval").remove();var e=$("<div />").addClass("yaxislabel").attr("id","linetrack_"+b+"_minval").text(a.prefs.min_value);var d=$("<div />").addClass("yaxislabel").attr("id","linetrack_"+b+"_maxval").text(a.prefs.max_value);d.css({position:"relative",top:"25px",left:"10px"});d.prependTo(a.container_div);e.css({position:"relative",top:a.height_px+55+"px",left:"10px"});e.prependTo(a.container_div)})},get_data:function(d,b){var c=this,a=b*DENSITY*d,f=(b+1)*DENSITY*d,e=d+"_"+b;if(!c.data_queue[e]){c.data_queue[e]=true;$.ajax({url:data_url,dataType:"json",data:{chrom:this.view.chrom,low:a,high:f,dataset_id:this.dataset_id,resolution:this.view.resolution},success:function(g){data=g.data;c.data_cache.set(e,data);delete c.data_queue[e];c.draw()},error:function(h,g,j){console.log(h,g,j)}})}},draw_tile:function(p,r,c,e){if(this.vertical_range===undefined){return}var s=r*DEN
SITY*p,a=DENSITY*p,b=$("<canvas class='tile'></canvas>"),v=p+"_"+r;if(this.data_cache.get(v)===undefined){this.get_data(p,r);return}var j=this.data_cache.get(v);if(j===null){return}b.css({position:"absolute",top:0,left:(s-this.view.low)*e});b.get(0).width=Math.ceil(a*e+this.left_offset);b.get(0).height=this.height_px;var o=b.get(0).getContext("2d"),k=false,l=this.prefs.min_value,g=this.prefs.max_value,n=this.vertical_range,t=this.total_frequency,d=this.height_px,m=this.prefs.mode;o.beginPath();if(data.length>1){var f=Math.ceil((data[1][0]-data[0][0])*e)}else{var f=10}var u,h;for(var q=0;q<data.length;q++){u=(data[q][0]-s)*e;h=data[q][1];if(m=="Intensity"){if(h===null){continue}if(h<=l){h=l}else{if(h>=g){h=g}}h=255-Math.floor((h-l)/n*255);o.fillStyle="rgb("+h+","+h+","+h+")";o.fillRect(u,0,f,this.height_px)}else{if(h===null){if(k&&m==="Filled"){o.lineTo(u,d)}k=false;continue}else{if(h<=l){h=l}else{if(h>=g){h=g}}h=Math.round(d-(h-l)/n*d);if(k){o.lineTo(u,h)}else{k=true;if(m===
"Filled"){o.moveTo(u,d);o.lineTo(u,h)}else{o.moveTo(u,h)}}}}}if(m==="Filled"){if(k){o.lineTo(u,d)}o.fill()}else{o.stroke()}c.append(b);return b},gen_options:function(o){var a=$("<div />").addClass("form-row");var h="track_"+o+"_minval",m=$("<label></label>").attr("for",h).text("Min value:"),b=(this.prefs.min_value===undefined?"":this.prefs.min_value),n=$("<input></input>").attr("id",h).val(b),l="track_"+o+"_maxval",g=$("<label></label>").attr("for",l).text("Max value:"),k=(this.prefs.max_value===undefined?"":this.prefs.max_value),f=$("<input></input>").attr("id",l).val(k),e="track_"+o+"_mode",d=$("<label></label>").attr("for",e).text("Display mode:"),j=(this.prefs.mode===undefined?"Line":this.prefs.mode),c=$('<select id="'+e+'"><option value="Line" id="mode_Line">Line</option><option value="Filled" id="mode_Filled">Filled</option><option value="Intensity" id="mode_Intensity">Intensity</option></select>');c.children("#mode_"+j).attr("selected","selected");return a.append(m).a
ppend(n).append(g).append(f).append(d).append(c)},update_options:function(d){var a=$("#track_"+d+"_minval").val(),c=$("#track_"+d+"_maxval").val(),b=$("#track_"+d+"_mode option:selected").val();if(a!==this.prefs.min_value||c!==this.prefs.max_value||b!==this.prefs.mode){this.prefs.min_value=parseFloat(a);this.prefs.max_value=parseFloat(c);this.prefs.mode=b;this.vertical_range=this.prefs.max_value-this.prefs.min_value;$("#linetrack_"+d+"_minval").text(this.prefs.min_value);$("#linetrack_"+d+"_maxval").text(this.prefs.max_value);this.tile_cache.clear();this.draw()}}});var FeatureTrack=function(d,b,a,c){this.track_type="FeatureTrack";Track.call(this,d,b,b.viewport_container);TiledTrack.call(this);this.height_px=0;this.container_div.addClass("feature-track");this.dataset_id=a;this.zo_slots={};this.show_labels_scale=0.001;this.showing_details=false;this.vertical_detail_px=10;this.vertical_nodetail_px=3;this.default_font="9px Monaco, Lucida Console, monospace";this.inc_slots={};thi
s.data_queue={};this.s_e_by_tile={};this.tile_cache=new Cache(CACHED_TILES_FEATURE);this.data_cache=new Cache(20);this.prefs={block_color:"black",label_color:"black",show_counts:false};if(c.block_color!==undefined){this.prefs.block_color=c.block_color}if(c.label_color!==undefined){this.prefs.label_color=c.label_color}if(c.show_counts!==undefined){this.prefs.show_counts=c.show_counts}};$.extend(FeatureTrack.prototype,TiledTrack.prototype,{init:function(){var a=this,b=a.view.max_low+"_"+a.view.max_high;a.mode="Auto";if(a.mode_div){a.mode_div.remove()}this.init_each({low:a.view.max_low,high:a.view.max_high,dataset_id:a.dataset_id,chrom:a.view.chrom,resolution:this.view.resolution},function(d){a.mode_div=$("<div class='right-float menubutton popup' />").text("Display Mode");a.header_div.append(a.mode_div);a.mode="Auto";var c=function(e){a.mode_div.text(e);a.mode=e;a.tile_cache.clear();a.draw()};make_popupmenu(a.mode_div,{Auto:function(){c("Auto")},Dense:function(){c("Dense")},Sq
uish:function(){c("Squish")},Pack:function(){c("Pack")}});a.data_cache.set(b,d);a.draw()})},get_data:function(a,d){var b=this,c=a+"_"+d;if(!b.data_queue[c]){b.data_queue[c]=true;$.getJSON(data_url,{chrom:b.view.chrom,low:a,high:d,dataset_id:b.dataset_id,resolution:this.view.resolution,mode:this.mode},function(e){b.data_cache.set(c,e);delete b.data_queue[c];b.draw()})}},incremental_slots:function(a,h,c,r){if(!this.inc_slots[a]){this.inc_slots[a]={};this.inc_slots[a].w_scale=1/a;this.inc_slots[a].mode=r;this.s_e_by_tile[a]={}}var n=this.inc_slots[a].w_scale,z=[],l=0,b=$("<canvas></canvas>").get(0).getContext("2d"),o=this.view.max_low;var B=[];if(this.inc_slots[a].mode!==r){delete this.inc_slots[a];this.inc_slots[a]={mode:r,w_scale:n};delete this.s_e_by_tile[a];this.s_e_by_tile[a]={}}for(var w=0,x=h.length;w<x;w++){var g=h[w],m=g[0];if(this.inc_slots[a][m]!==undefined){l=Math.max(l,this.inc_slots[a][m]);B.push(this.inc_slots[a][m])}else{z.push(w)}}for(var w=0,x=z.length;w<x;w++
){var g=h[z[w]],m=g[0],s=g[1],d=g[2],q=g[3],e=Math.floor((s-o)*n),f=Math.ceil((d-o)*n);if(q!==undefined&&!c){var t=b.measureText(q).width;if(e-t<0){f+=t}else{e-=t}}var v=0;while(true){var p=true;if(this.s_e_by_tile[a][v]!==undefined){for(var u=0,A=this.s_e_by_tile[a][v].length;u<A;u++){var y=this.s_e_by_tile[a][v][u];if(f>y[0]&&e<y[1]){p=false;break}}}if(p){if(this.s_e_by_tile[a][v]===undefined){this.s_e_by_tile[a][v]=[]}this.s_e_by_tile[a][v].push([e,f]);this.inc_slots[a][m]=v;l=Math.max(l,v);break}v++}}return l},rect_or_text:function(n,o,f,m,b,d,k,e,h){n.textAlign="center";var j=Math.round(o/2);if((this.mode==="Pack"||this.mode==="Auto")&&d!==undefined&&o>PX_PER_CHAR){n.fillStyle=this.prefs.block_color;n.fillRect(k,h+1,e,9);n.fillStyle="#eee";for(var g=0,l=d.length;g<l;g++){if(b+g>=f&&b+g<=m){var a=Math.floor(Math.max(0,(b+g-f)*o));n.fillText(d[g],a+this.left_offset+j,h+9)}}}else{n.fillStyle=this.prefs.block_color;n.fillRect(k,h+4,e,3)}},draw_tile:function(X,h,n,ak){var E=
h*DENSITY*X,ad=(h+1)*DENSITY*X,D=DENSITY*X;var ae=E+"_"+ad;var z=this.data_cache.get(ae);if(z===undefined){this.data_queue[[E,ad]]=true;this.get_data(E,ad);return}var a=Math.ceil(D*ak),L=$("<canvas class='tile'></canvas>"),Z=this.prefs.label_color,f=this.prefs.block_color,m=this.mode,V=(m==="Squish")||(m==="Dense")&&(m!=="Pack")||(m==="Auto"&&(z.extra_info==="no_detail")),P=this.left_offset,aj,s,al;if(z.dataset_type==="summary_tree"){s=30}else{if(m==="Dense"){s=15;al=10}else{al=(V?this.vertical_nodetail_px:this.vertical_detail_px);s=this.incremental_slots(this.view.zoom_res,z.data,V,m)*al+15;aj=this.inc_slots[this.view.zoom_res]}}L.css({position:"absolute",top:0,left:(E-this.view.low)*ak-P});L.get(0).width=a+P;L.get(0).height=s;n.parent().css("height",Math.max(this.height_px,s)+"px");var A=L.get(0).getContext("2d");A.fillStyle=f;A.font=this.default_font;A.textAlign="right";if(z.dataset_type=="summary_tree"){var K,H=55,ac=255-H,g=ac*2/3,R=z.data,C=z.max,l=z.avg;if(R.length>2)
{var b=Math.ceil((R[1][0]-R[0][0])*ak)}else{var b=50}for(var ag=0,w=R.length;ag<w;ag++){var T=Math.ceil((R[ag][0]-E)*ak);var S=R[ag][1];if(!S){continue}K=Math.floor(ac-(S/C)*ac);A.fillStyle="rgb("+K+","+K+","+K+")";A.fillRect(T+P,0,b,20);if(this.prefs.show_counts){if(K>g){A.fillStyle="black"}else{A.fillStyle="#ddd"}A.textAlign="center";A.fillText(R[ag][1],T+P+(b/2),12)}}n.append(L);return L}var ai=z.data;var af=0;for(var ag=0,w=ai.length;ag<w;ag++){var M=ai[ag],J=M[0],ah=M[1],U=M[2],F=M[3];if(ah<=ad&&U>=E){var W=Math.floor(Math.max(0,(ah-E)*ak)),B=Math.ceil(Math.min(a,Math.max(0,(U-E)*ak))),Q=(m==="Dense"?0:aj[J]*al);if(z.dataset_type==="bai"){A.fillStyle=f;if(M[4] instanceof Array){var t=Math.floor(Math.max(0,(M[4][0]-E)*ak)),I=Math.ceil(Math.min(a,Math.max(0,(M[4][1]-E)*ak))),r=Math.floor(Math.max(0,(M[5][0]-E)*ak)),p=Math.ceil(Math.min(a,Math.max(0,(M[5][1]-E)*ak)));if(M[4][1]>=E&&M[4][0]<=ad){this.rect_or_text(A,ak,E,ad,M[4][0],M[4][2],t+P,I-t,Q)}if(M[5][1]>=E&&M[5][0]<=
ad){this.rect_or_text(A,ak,E,ad,M[5][0],M[5][2],r+P,p-r,Q)}if(r>I){A.fillStyle="#999";A.fillRect(I+P,Q+5,r-I,1)}}else{A.fillStyle=f;this.rect_or_text(A,ak,E,ad,ah,F,W+P,B-W,Q)}if(m!=="Dense"&&!V&&ah>E){A.fillStyle=this.prefs.label_color;if(h===0&&W-A.measureText(F).width<0){A.textAlign="left";A.fillText(J,B+2+P,Q+8)}else{A.textAlign="right";A.fillText(J,W-2+P,Q+8)}A.fillStyle=f}}else{if(z.dataset_type==="interval_index"){if(V){A.fillRect(W+P,Q+5,B-W,1)}else{var v=M[4],O=M[5],Y=M[6],e=M[7];var u,aa,G=null,am=null;if(O&&Y){G=Math.floor(Math.max(0,(O-E)*ak));am=Math.ceil(Math.min(a,Math.max(0,(Y-E)*ak)))}if(m!=="Dense"&&F!==undefined&&ah>E){A.fillStyle=Z;if(h===0&&W-A.measureText(F).width<0){A.textAlign="left";A.fillText(F,B+2+P,Q+8)}else{A.textAlign="right";A.fillText(F,W-2+P,Q+8)}A.fillStyle=f}if(e){if(v){if(v=="+"){A.fillStyle=RIGHT_STRAND}else{if(v=="-"){A.fillStyle=LEFT_STRAND}}A.fillRect(W+P,Q,B-W,10);A.fillStyle=f}for(var ae=0,d=e.length;ae<d;ae++){var o=e[ae],c=Math.flo
or(Math.max(0,(o[0]-E)*ak)),N=Math.ceil(Math.min(a,Math.max((o[1]-E)*ak)));if(c>N){continue}u=5;aa=3;A.fillRect(c+P,Q+aa,N-c,u);if(G!==undefined&&!(c>am||N<G)){u=9;aa=1;var ab=Math.max(c,G),q=Math.min(N,am);A.fillRect(ab+P,Q+aa,q-ab,u)}}}else{u=9;aa=1;A.fillRect(W+P,Q+aa,B-W,u);if(M.strand){if(M.strand=="+"){A.fillStyle=RIGHT_STRAND_INV}else{if(M.strand=="-"){A.fillStyle=LEFT_STRAND_INV}}A.fillRect(W+P,Q,B-W,10);A.fillStyle=prefs.block_color}}}}}af++}}n.append(L);return L},gen_options:function(j){var a=$("<div />").addClass("form-row");var e="track_"+j+"_block_color",l=$("<label />").attr("for",e).text("Block color:"),m=$("<input />").attr("id",e).attr("name",e).val(this.prefs.block_color),k="track_"+j+"_label_color",g=$("<label />").attr("for",k).text("Text color:"),h=$("<input />").attr("id",k).attr("name",k).val(this.prefs.label_color),f="track_"+j+"_show_count",c=$("<label />").attr("for",f).text("Show summary counts"),b=$('<input type="checkbox" style="float:left;"></in
put>').attr("id",f).attr("name",f).attr("checked",this.prefs.show_counts),d=$("<div />").append(b).append(c);return a.append(l).append(m).append(g).append(h).append(d)},update_options:function(e){var b=$("#track_"+e+"_block_color").val(),d=$("#track_"+e+"_label_color").val(),c=$("#track_"+e+"_mode option:selected").val(),a=$("#track_"+e+"_show_count").attr("checked");if(b!==this.prefs.block_color||d!==this.prefs.label_color||a!==this.prefs.show_counts){this.prefs.block_color=b;this.prefs.label_color=d;this.prefs.show_counts=a;this.tile_cache.clear();this.draw()}}});var ReadTrack=function(d,b,a,c){FeatureTrack.call(this,d,b,a,c);this.track_type="ReadTrack";this.vertical_detail_px=10;this.vertical_nodetail_px=5};$.extend(ReadTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype,{});
+var DENSITY=200,FEATURE_LEVELS=10,DATA_ERROR="There was an error in indexing this dataset. ",DATA_NOCONVERTER="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",DATA_NONE="No data for this chrom/contig.",DATA_PENDING="Currently indexing... please wait",DATA_LOADING="Loading data...",CACHED_TILES_FEATURE=10,CACHED_TILES_LINE=30,CACHED_DATA=5,CONTEXT=$("<canvas></canvas>").get(0).getContext("2d"),PX_PER_CHAR=CONTEXT.measureText("A").width,RIGHT_STRAND,LEFT_STRAND;var right_img=new Image();right_img.src=image_path+"/visualization/strand_right.png";right_img.onload=function(){RIGHT_STRAND=CONTEXT.createPattern(right_img,"repeat")};var left_img=new Image();left_img.src=image_path+"/visualization/strand_left.png";left_img.onload=function(){LEFT_STRAND=CONTEXT.createPattern(left_img,"repeat")};var right_img_inv=new Image();right_img_inv.src=image_path+"/visualization/strand_right_inv.png";right_img_inv.onload=function(){RIGHT_STRAND_INV=CONT
EXT.createPattern(right_img_inv,"repeat")};var left_img_inv=new Image();left_img_inv.src=image_path+"/visualization/strand_left_inv.png";left_img_inv.onload=function(){LEFT_STRAND_INV=CONTEXT.createPattern(left_img_inv,"repeat")};var Cache=function(a){this.num_elements=a;this.clear()};$.extend(Cache.prototype,{get:function(b){var a=this.key_ary.indexOf(b);if(a!=-1){this.key_ary.splice(a,1);this.key_ary.push(b)}return this.obj_cache[b]},set:function(b,c){if(!this.obj_cache[b]){if(this.key_ary.length>=this.num_elements){var a=this.key_ary.shift();delete this.obj_cache[a]}this.key_ary.push(b)}this.obj_cache[b]=c;return c},clear:function(){this.obj_cache={};this.key_ary=[]}});var View=function(a,c,e,d,b){this.container=a;this.vis_id=d;this.dbkey=b;this.title=e;this.chrom=c;this.tracks=[];this.label_tracks=[];this.max_low=0;this.max_high=0;this.num_tracks=0;this.track_id_counter=0;this.zoom_factor=3;this.min_separation=30;this.has_changes=false;this.init();this.reset()};$.extend(
View.prototype,{init:function(){var b=this.container,a=this;this.top_labeltrack=$("<div/>").addClass("top-labeltrack").appendTo(b);this.content_div=$("<div/>").addClass("content").css("position","relative").appendTo(b);this.intro_div=$("<div/>").addClass("intro").text("Select a chrom from the dropdown below").hide().appendTo(b);this.viewport_container=$("<div/>").addClass("viewport-container").addClass("viewport-container").appendTo(this.content_div);this.viewport=$("<div/>").addClass("viewport").appendTo(this.viewport_container);this.nav_container=$("<div/>").addClass("nav-container").appendTo(b);this.nav_labeltrack=$("<div/>").addClass("nav-labeltrack").appendTo(this.nav_container);this.nav=$("<div/>").addClass("nav").appendTo(this.nav_container);this.overview=$("<div/>").addClass("overview").appendTo(this.nav);this.overview_viewport=$("<div/>").addClass("overview-viewport").appendTo(this.overview);this.overview_box=$("<div/>").addClass("overview-box").appendTo(this.overvi
ew_viewport);this.nav_controls=$("<div/>").addClass("nav-controls").appendTo(this.nav);this.chrom_form=$("<form/>").attr("action",function(){void (0)}).appendTo(this.nav_controls);this.chrom_select=$("<select/>").attr({name:"chrom"}).css("width","15em").addClass("no-autocomplete").append("<option value=''>Loading</option>").appendTo(this.chrom_form);this.low_input=$("<input/>").addClass("low").css("width","10em").appendTo(this.chrom_form);$("<span/>").text(" - ").appendTo(this.chrom_form);this.high_input=$("<input/>").addClass("high").css("width","10em").appendTo(this.chrom_form);if(this.vis_id!==undefined){this.hidden_input=$("<input/>").attr("type","hidden").val(this.vis_id).appendTo(this.chrom_form)}this.zi_link=$("<a/>").click(function(){a.zoom_in();a.redraw()}).html('<img src="'+image_path+'/fugue/magnifier-zoom.png" />').appendTo(this.chrom_form);this.zo_link=$("<a/>").click(function(){a.zoom_out();a.redraw()}).html('<img src="'+image_path+'/fugue/magnifier-zoom-out.pn
g" />').appendTo(this.chrom_form);$.ajax({url:chrom_url,data:(this.vis_id!==undefined?{vis_id:this.vis_id}:{dbkey:this.dbkey}),dataType:"json",success:function(c){if(c.reference){a.add_label_track(new ReferenceTrack(a))}a.chrom_data=c.chrom_info;var f='<option value="">Select Chrom/Contig</option>';for(i in a.chrom_data){var e=a.chrom_data[i]["chrom"];f+='<option value="'+e+'">'+e+"</option>"}a.chrom_select.html(f);var d=function(){if(a.chrom_select.val()===""){a.intro_div.show();a.content_div.hide()}else{a.intro_div.hide();a.content_div.show()}a.chrom=a.chrom_select.val();var h=$.grep(a.chrom_data,function(k,l){return k.chrom===a.chrom})[0];a.max_high=(h!==undefined?h.len:0);a.reset();a.redraw(true);for(var j in a.tracks){var g=a.tracks[j];if(g.init){g.init()}}a.redraw()};a.chrom_select.bind("change",d);d()},error:function(){alert("Could not load chroms for this dbkey:",a.dbkey)}});this.content_div.bind("dblclick",function(c){a.zoom_in(c.pageX,this.viewport_container)});thi
s.overview_box.bind("dragstart",function(c){this.current_x=c.offsetX}).bind("drag",function(c){var f=c.offsetX-this.current_x;this.current_x=c.offsetX;var d=Math.round(f/a.viewport_container.width()*(a.max_high-a.max_low));a.move_delta(-d)});this.viewport_container.bind("dragstart",function(c){this.original_low=a.low;this.current_height=c.clientY;this.current_x=c.offsetX}).bind("drag",function(f){var c=$(this);var h=f.offsetX-this.current_x;var d=c.scrollTop()-(f.clientY-this.current_height);c.scrollTop(d);this.current_height=f.clientY;this.current_x=f.offsetX;var g=Math.round(h/a.viewport_container.width()*(a.high-a.low));a.move_delta(g)});this.top_labeltrack.bind("dragstart",function(c){this.drag_origin_x=c.clientX;this.drag_origin_pos=c.clientX/a.viewport_container.width()*(a.high-a.low)+a.low;this.drag_div=$("<div />").css({height:a.viewport_container.height(),top:"0px",position:"absolute","background-color":"#cfc",border:"1px solid #6a6",opacity:0.5,"z-index":1000}).app
endTo($(this))}).bind("drag",function(h){var d=Math.min(h.clientX,this.drag_origin_x)-a.container.offset().left,c=Math.max(h.clientX,this.drag_origin_x)-a.container.offset().left,g=(a.high-a.low),f=a.viewport_container.width();a.low_input.val(commatize(Math.round(d/f*g)+a.low));a.high_input.val(commatize(Math.round(c/f*g)+a.low));this.drag_div.css({left:d+"px",width:(c-d)+"px"})}).bind("dragend",function(j){var d=Math.min(j.clientX,this.drag_origin_x),c=Math.max(j.clientX,this.drag_origin_x),g=(a.high-a.low),f=a.viewport_container.width(),h=a.low;a.low=Math.round(d/f*g)+h;a.high=Math.round(c/f*g)+h;this.drag_div.remove();a.redraw()});this.add_label_track(new LabelTrack(this,this.top_labeltrack));this.add_label_track(new LabelTrack(this,this.nav_labeltrack))},move_delta:function(c){var a=this;var b=a.high-a.low;if(a.low-c<a.max_low){a.low=a.max_low;a.high=a.max_low+b}else{if(a.high-c>a.max_high){a.high=a.max_high;a.low=a.max_high-b}else{a.high-=c;a.low-=c}}a.redraw()},add_tra
ck:function(a){a.view=this;a.track_id=this.track_id_counter;this.tracks.push(a);if(a.init){a.init()}a.container_div.attr("id","track_"+a.track_id);this.track_id_counter+=1;this.num_tracks+=1},add_label_track:function(a){a.view=this;this.label_tracks.push(a)},remove_track:function(a){this.has_changes=true;a.container_div.fadeOut("slow",function(){$(this).remove()});delete this.tracks[this.tracks.indexOf(a)];this.num_tracks-=1},update_options:function(){this.has_changes=true;var b=$("ul#sortable-ul").sortable("toArray");for(var c in b){var e=b[c].split("_li")[0].split("track_")[1];this.viewport.append($("#track_"+e))}for(var d in view.tracks){var a=view.tracks[d];if(a&&a.update_options){a.update_options(d)}}},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},redraw:function(f){var d=this.high-this.low,b=this.low,e=this.high;if(b<this.max_low){b=this.max_low}if(e>this.max_high){e=this.max_high}if(this.high!==0&&
d<this.min_separation){e=b+this.min_separation}this.low=Math.floor(b);this.high=Math.ceil(e);this.resolution=Math.pow(10,Math.ceil(Math.log((this.high-this.low)/200)/Math.LN10));this.zoom_res=Math.pow(FEATURE_LEVELS,Math.max(0,Math.ceil(Math.log(this.resolution,FEATURE_LEVELS)/Math.log(FEATURE_LEVELS))));this.overview_box.css({left:(this.low/(this.max_high-this.max_low))*this.overview_viewport.width(),width:Math.max(12,(this.high-this.low)/(this.max_high-this.max_low)*this.overview_viewport.width())}).show();this.low_input.val(commatize(this.low));this.high_input.val(commatize(this.high));if(!f){for(var c=0,a=this.tracks.length;c<a;c++){if(this.tracks[c]&&this.tracks[c].enabled){this.tracks[c].draw()}}for(var c=0,a=this.label_tracks.length;c<a;c++){this.label_tracks[c].draw()}}},zoom_in:function(b,c){if(this.max_high===0||this.high-this.low<this.min_separation){return}var d=this.high-this.low,e=d/2+this.low,a=(d/this.zoom_factor)/2;if(b){e=b/this.viewport_container.width()*(
this.high-this.low)+this.low}this.low=Math.round(e-a);this.high=Math.round(e+a);this.redraw()},zoom_out:function(){if(this.max_high===0){return}var b=this.high-this.low,c=b/2+this.low,a=(b*this.zoom_factor)/2;this.low=Math.round(c-a);this.high=Math.round(c+a);this.redraw()}});var Track=function(b,a,c){this.name=b;this.parent_element=c;this.view=a;this.init_global()};$.extend(Track.prototype,{init_global:function(){this.header_div=$("<div class='track-header'>").text(this.name);this.content_div=$("<div class='track-content'>");this.container_div=$("<div />").addClass("track").append(this.header_div).append(this.content_div);this.parent_element.append(this.container_div)},init_each:function(c,b){var a=this;a.enabled=false;a.data_queue={};a.tile_cache.clear();a.data_cache.clear();a.content_div.css("height","auto");if(!a.content_div.text()){a.content_div.text(DATA_LOADING)}a.container_div.removeClass("nodata error pending");if(a.view.chrom){$.getJSON(data_url,c,function(d){if(!d
||d==="error"||d.kind==="error"){a.container_div.addClass("error");a.content_div.text(DATA_ERROR);if(d.message){var f=a.view.tracks.indexOf(a);var e=$("<a href='javascript:void(0);'></a>").attr("id",f+"_error");e.text("Click to view error");$("#"+f+"_error").live("click",function(){show_modal("Trackster Error","<pre>"+d.message+"</pre>",{Close:hide_modal})});a.content_div.append(e)}}else{if(d==="no converter"){a.container_div.addClass("error");a.content_div.text(DATA_NOCONVERTER)}else{if(d.data!==undefined&&(d.data===null||d.data.length===0)){a.container_div.addClass("nodata");a.content_div.text(DATA_NONE)}else{if(d==="pending"){a.container_div.addClass("pending");a.content_div.text(DATA_PENDING);setTimeout(function(){a.init()},5000)}else{a.content_div.text("");a.content_div.css("height",a.height_px+"px");a.enabled=true;b(d);a.draw()}}}}})}else{a.container_div.addClass("nodata");a.content_div.text(DATA_NONE)}}});var TiledTrack=function(){this.left_offset=200};$.extend(TiledT
rack.prototype,Track.prototype,{draw:function(){var j=this.view.low,e=this.view.high,f=e-j,d=this.view.resolution;var l=$("<div style='position: relative;'></div>"),m=this.content_div.width()/f,h;this.content_div.children(":first").remove();this.content_div.append(l),this.max_height=0;var a=Math.floor(j/d/DENSITY);while((a*DENSITY*d)<e){var k=this.content_div.width()+"_"+m+"_"+a;var c=this.tile_cache.get(k);if(c){var g=a*DENSITY*d;var b=(g-j)*m;if(this.left_offset){b-=this.left_offset}c.css({left:b});l.append(c);this.max_height=Math.max(this.max_height,c.height());this.content_div.css("height",this.max_height+"px")}else{this.delayed_draw(this,k,j,e,a,d,l,m)}a+=1}},delayed_draw:function(c,e,a,f,b,d,g,h){setTimeout(function(){if(!(a>c.view.high||f<c.view.low)){tile_element=c.draw_tile(d,b,g,h);if(tile_element){c.tile_cache.set(e,tile_element);c.max_height=Math.max(c.max_height,tile_element.height());c.content_div.css("height",c.max_height+"px")}}},50)}});var LabelTrack=functio
n(a,b){Track.call(this,null,a,b);this.track_type="LabelTrack";this.hidden=true;this.container_div.addClass("label-track")};$.extend(LabelTrack.prototype,Track.prototype,{draw:function(){var c=this.view,d=c.high-c.low,g=Math.floor(Math.pow(10,Math.floor(Math.log(d)/Math.log(10)))),a=Math.floor(c.low/g)*g,e=this.content_div.width(),b=$("<div style='position: relative; height: 1.3em;'></div>");while(a<c.high){var f=(a-c.low)/d*e;b.append($("<div class='label'>"+commatize(a)+"</div>").css({position:"absolute",left:f-1}));a+=g}this.content_div.children(":first").remove();this.content_div.append(b)}});var ReferenceTrack=function(a){this.track_type="ReferenceTrack";Track.call(this,null,a,a.top_labeltrack);TiledTrack.call(this);this.hidden=true;this.height_px=12;this.container_div.addClass("reference-track");this.dummy_canvas=$("<canvas></canvas>").get(0).getContext("2d");this.data_queue={};this.data_cache=new Cache(CACHED_DATA);this.tile_cache=new Cache(CACHED_TILES_LINE)};$.extend
(ReferenceTrack.prototype,TiledTrack.prototype,{get_data:function(d,b){var c=this,a=b*DENSITY*d,f=(b+1)*DENSITY*d,e=d+"_"+b;if(!c.data_queue[e]){c.data_queue[e]=true;$.ajax({url:reference_url,dataType:"json",data:{chrom:this.view.chrom,low:a,high:f,dbkey:this.view.dbkey},success:function(g){c.data_cache.set(e,g);delete c.data_queue[e];c.draw()},error:function(h,g,j){console.log(h,g,j)}})}},draw_tile:function(f,b,k,o){var g=b*DENSITY*f,d=DENSITY*f,e=$("<canvas class='tile'></canvas>"),n=e.get(0).getContext("2d"),j=f+"_"+b;if(o>PX_PER_CHAR){if(this.data_cache.get(j)===undefined){this.get_data(f,b);return}var m=this.data_cache.get(j);if(m===null){this.content_div.css("height","0px");return}e.get(0).width=Math.ceil(d*o+this.left_offset);e.get(0).height=this.height_px;e.css({position:"absolute",top:0,left:(g-this.view.low)*o-this.left_offset});for(var h=0,l=m.length;h<l;h++){var a=Math.round(h*o);n.fillText(m[h],a+this.left_offset,10)}k.append(e);return e}this.content_div.css("he
ight","0px")}});var LineTrack=function(d,b,a,c){this.track_type="LineTrack";Track.call(this,d,b,b.viewport_container);TiledTrack.call(this);this.height_px=100;this.dataset_id=a;this.data_cache=new Cache(CACHED_DATA);this.tile_cache=new Cache(CACHED_TILES_LINE);this.prefs={min_value:undefined,max_value:undefined,mode:"Line"};if(c.min_value!==undefined){this.prefs.min_value=c.min_value}if(c.max_value!==undefined){this.prefs.max_value=c.max_value}if(c.mode!==undefined){this.prefs.mode=c.mode}};$.extend(LineTrack.prototype,TiledTrack.prototype,{init:function(){var a=this,b=a.view.tracks.indexOf(a);a.vertical_range=undefined;this.init_each({stats:true,chrom:a.view.chrom,low:null,high:null,dataset_id:a.dataset_id},function(c){a.container_div.addClass("line-track");data=c.data;if(isNaN(parseFloat(a.prefs.min_value))||isNaN(parseFloat(a.prefs.max_value))){a.prefs.min_value=data.min;a.prefs.max_value=data.max;$("#track_"+b+"_minval").val(a.prefs.min_value);$("#track_"+b+"_maxval").va
l(a.prefs.max_value)}a.vertical_range=a.prefs.max_value-a.prefs.min_value;a.total_frequency=data.total_frequency;$("#linetrack_"+b+"_minval").remove();$("#linetrack_"+b+"_maxval").remove();var e=$("<div />").addClass("yaxislabel").attr("id","linetrack_"+b+"_minval").text(a.prefs.min_value);var d=$("<div />").addClass("yaxislabel").attr("id","linetrack_"+b+"_maxval").text(a.prefs.max_value);d.css({position:"relative",top:"25px",left:"10px"});d.prependTo(a.container_div);e.css({position:"relative",top:a.height_px+55+"px",left:"10px"});e.prependTo(a.container_div)})},get_data:function(d,b){var c=this,a=b*DENSITY*d,f=(b+1)*DENSITY*d,e=d+"_"+b;if(!c.data_queue[e]){c.data_queue[e]=true;$.ajax({url:data_url,dataType:"json",data:{chrom:this.view.chrom,low:a,high:f,dataset_id:this.dataset_id,resolution:this.view.resolution},success:function(g){data=g.data;c.data_cache.set(e,data);delete c.data_queue[e];c.draw()},error:function(h,g,j){console.log(h,g,j)}})}},draw_tile:function(p,r,c,e
){if(this.vertical_range===undefined){return}var s=r*DENSITY*p,a=DENSITY*p,b=$("<canvas class='tile'></canvas>"),v=p+"_"+r;if(this.data_cache.get(v)===undefined){this.get_data(p,r);return}var j=this.data_cache.get(v);if(j===null){return}b.css({position:"absolute",top:0,left:(s-this.view.low)*e});b.get(0).width=Math.ceil(a*e+this.left_offset);b.get(0).height=this.height_px;var o=b.get(0).getContext("2d"),k=false,l=this.prefs.min_value,g=this.prefs.max_value,n=this.vertical_range,t=this.total_frequency,d=this.height_px,m=this.prefs.mode;o.beginPath();if(data.length>1){var f=Math.ceil((data[1][0]-data[0][0])*e)}else{var f=10}var u,h;for(var q=0;q<data.length;q++){u=(data[q][0]-s)*e;h=data[q][1];if(m=="Intensity"){if(h===null){continue}if(h<=l){h=l}else{if(h>=g){h=g}}h=255-Math.floor((h-l)/n*255);o.fillStyle="rgb("+h+","+h+","+h+")";o.fillRect(u,0,f,this.height_px)}else{if(h===null){if(k&&m==="Filled"){o.lineTo(u,d)}k=false;continue}else{if(h<=l){h=l}else{if(h>=g){h=g}}h=Math.ro
und(d-(h-l)/n*d);if(k){o.lineTo(u,h)}else{k=true;if(m==="Filled"){o.moveTo(u,d);o.lineTo(u,h)}else{o.moveTo(u,h)}}}}}if(m==="Filled"){if(k){o.lineTo(u,d)}o.fill()}else{o.stroke()}c.append(b);return b},gen_options:function(o){var a=$("<div />").addClass("form-row");var h="track_"+o+"_minval",m=$("<label></label>").attr("for",h).text("Min value:"),b=(this.prefs.min_value===undefined?"":this.prefs.min_value),n=$("<input></input>").attr("id",h).val(b),l="track_"+o+"_maxval",g=$("<label></label>").attr("for",l).text("Max value:"),k=(this.prefs.max_value===undefined?"":this.prefs.max_value),f=$("<input></input>").attr("id",l).val(k),e="track_"+o+"_mode",d=$("<label></label>").attr("for",e).text("Display mode:"),j=(this.prefs.mode===undefined?"Line":this.prefs.mode),c=$('<select id="'+e+'"><option value="Line" id="mode_Line">Line</option><option value="Filled" id="mode_Filled">Filled</option><option value="Intensity" id="mode_Intensity">Intensity</option></select>');c.children("#mo
de_"+j).attr("selected","selected");return a.append(m).append(n).append(g).append(f).append(d).append(c)},update_options:function(d){var a=$("#track_"+d+"_minval").val(),c=$("#track_"+d+"_maxval").val(),b=$("#track_"+d+"_mode option:selected").val();if(a!==this.prefs.min_value||c!==this.prefs.max_value||b!==this.prefs.mode){this.prefs.min_value=parseFloat(a);this.prefs.max_value=parseFloat(c);this.prefs.mode=b;this.vertical_range=this.prefs.max_value-this.prefs.min_value;$("#linetrack_"+d+"_minval").text(this.prefs.min_value);$("#linetrack_"+d+"_maxval").text(this.prefs.max_value);this.tile_cache.clear();this.draw()}}});var FeatureTrack=function(d,b,a,c){this.track_type="FeatureTrack";Track.call(this,d,b,b.viewport_container);TiledTrack.call(this);this.height_px=0;this.container_div.addClass("feature-track");this.dataset_id=a;this.zo_slots={};this.show_labels_scale=0.001;this.showing_details=false;this.vertical_detail_px=10;this.vertical_nodetail_px=3;this.default_font="9px
Monaco, Lucida Console, monospace";this.inc_slots={};this.data_queue={};this.s_e_by_tile={};this.tile_cache=new Cache(CACHED_TILES_FEATURE);this.data_cache=new Cache(20);this.prefs={block_color:"black",label_color:"black",show_counts:false};if(c.block_color!==undefined){this.prefs.block_color=c.block_color}if(c.label_color!==undefined){this.prefs.label_color=c.label_color}if(c.show_counts!==undefined){this.prefs.show_counts=c.show_counts}};$.extend(FeatureTrack.prototype,TiledTrack.prototype,{init:function(){var a=this,b=a.view.max_low+"_"+a.view.max_high;a.mode="Auto";if(a.mode_div){a.mode_div.remove()}this.init_each({low:a.view.max_low,high:a.view.max_high,dataset_id:a.dataset_id,chrom:a.view.chrom,resolution:this.view.resolution},function(d){a.mode_div=$("<div class='right-float menubutton popup' />").text("Display Mode");a.header_div.append(a.mode_div);a.mode="Auto";var c=function(e){a.mode_div.text(e);a.mode=e;a.tile_cache.clear();a.draw()};make_popupmenu(a.mode_div,{Au
to:function(){c("Auto")},Dense:function(){c("Dense")},Squish:function(){c("Squish")},Pack:function(){c("Pack")}});a.data_cache.set(b,d);a.draw()})},get_data:function(a,d){var b=this,c=a+"_"+d;if(!b.data_queue[c]){b.data_queue[c]=true;$.getJSON(data_url,{chrom:b.view.chrom,low:a,high:d,dataset_id:b.dataset_id,resolution:this.view.resolution,mode:this.mode},function(e){b.data_cache.set(c,e);delete b.data_queue[c];b.draw()})}},incremental_slots:function(a,h,c,r){if(!this.inc_slots[a]){this.inc_slots[a]={};this.inc_slots[a].w_scale=1/a;this.inc_slots[a].mode=r;this.s_e_by_tile[a]={}}var n=this.inc_slots[a].w_scale,z=[],l=0,b=$("<canvas></canvas>").get(0).getContext("2d"),o=this.view.max_low;var B=[];if(this.inc_slots[a].mode!==r){delete this.inc_slots[a];this.inc_slots[a]={mode:r,w_scale:n};delete this.s_e_by_tile[a];this.s_e_by_tile[a]={}}for(var w=0,x=h.length;w<x;w++){var g=h[w],m=g[0];if(this.inc_slots[a][m]!==undefined){l=Math.max(l,this.inc_slots[a][m]);B.push(this.inc_slo
ts[a][m])}else{z.push(w)}}for(var w=0,x=z.length;w<x;w++){var g=h[z[w]],m=g[0],s=g[1],d=g[2],q=g[3],e=Math.floor((s-o)*n),f=Math.ceil((d-o)*n);if(q!==undefined&&!c){var t=b.measureText(q).width;if(e-t<0){f+=t}else{e-=t}}var v=0;while(true){var p=true;if(this.s_e_by_tile[a][v]!==undefined){for(var u=0,A=this.s_e_by_tile[a][v].length;u<A;u++){var y=this.s_e_by_tile[a][v][u];if(f>y[0]&&e<y[1]){p=false;break}}}if(p){if(this.s_e_by_tile[a][v]===undefined){this.s_e_by_tile[a][v]=[]}this.s_e_by_tile[a][v].push([e,f]);this.inc_slots[a][m]=v;l=Math.max(l,v);break}v++}}return l},rect_or_text:function(n,o,f,m,b,d,k,e,h){n.textAlign="center";var j=Math.round(o/2);if((this.mode==="Pack"||this.mode==="Auto")&&d!==undefined&&o>PX_PER_CHAR){n.fillStyle=this.prefs.block_color;n.fillRect(k,h+1,e,9);n.fillStyle="#eee";for(var g=0,l=d.length;g<l;g++){if(b+g>=f&&b+g<=m){var a=Math.floor(Math.max(0,(b+g-f)*o));n.fillText(d[g],a+this.left_offset+j,h+9)}}}else{n.fillStyle=this.prefs.block_color;n.f
illRect(k,h+4,e,3)}},draw_tile:function(X,h,n,ak){var E=h*DENSITY*X,ad=(h+1)*DENSITY*X,D=DENSITY*X;var ae=E+"_"+ad;var z=this.data_cache.get(ae);if(z===undefined){this.data_queue[[E,ad]]=true;this.get_data(E,ad);return}var a=Math.ceil(D*ak),L=$("<canvas class='tile'></canvas>"),Z=this.prefs.label_color,f=this.prefs.block_color,m=this.mode,V=(m==="Squish")||(m==="Dense")&&(m!=="Pack")||(m==="Auto"&&(z.extra_info==="no_detail")),P=this.left_offset,aj,s,al;if(z.dataset_type==="summary_tree"){s=30}else{if(m==="Dense"){s=15;al=10}else{al=(V?this.vertical_nodetail_px:this.vertical_detail_px);s=this.incremental_slots(this.view.zoom_res,z.data,V,m)*al+15;aj=this.inc_slots[this.view.zoom_res]}}L.css({position:"absolute",top:0,left:(E-this.view.low)*ak-P});L.get(0).width=a+P;L.get(0).height=s;n.parent().css("height",Math.max(this.height_px,s)+"px");var A=L.get(0).getContext("2d");A.fillStyle=f;A.font=this.default_font;A.textAlign="right";if(z.dataset_type=="summary_tree"){var K,H=55,a
c=255-H,g=ac*2/3,R=z.data,C=z.max,l=z.avg;if(R.length>2){var b=Math.ceil((R[1][0]-R[0][0])*ak)}else{var b=50}for(var ag=0,w=R.length;ag<w;ag++){var T=Math.ceil((R[ag][0]-E)*ak);var S=R[ag][1];if(!S){continue}K=Math.floor(ac-(S/C)*ac);A.fillStyle="rgb("+K+","+K+","+K+")";A.fillRect(T+P,0,b,20);if(this.prefs.show_counts){if(K>g){A.fillStyle="black"}else{A.fillStyle="#ddd"}A.textAlign="center";A.fillText(R[ag][1],T+P+(b/2),12)}}n.append(L);return L}var ai=z.data;var af=0;for(var ag=0,w=ai.length;ag<w;ag++){var M=ai[ag],J=M[0],ah=M[1],U=M[2],F=M[3];if(ah<=ad&&U>=E){var W=Math.floor(Math.max(0,(ah-E)*ak)),B=Math.ceil(Math.min(a,Math.max(0,(U-E)*ak))),Q=(m==="Dense"?0:aj[J]*al);if(z.dataset_type==="bai"){A.fillStyle=f;if(M[4] instanceof Array){var t=Math.floor(Math.max(0,(M[4][0]-E)*ak)),I=Math.ceil(Math.min(a,Math.max(0,(M[4][1]-E)*ak))),r=Math.floor(Math.max(0,(M[5][0]-E)*ak)),p=Math.ceil(Math.min(a,Math.max(0,(M[5][1]-E)*ak)));if(M[4][1]>=E&&M[4][0]<=ad){this.rect_or_text(A,ak,
E,ad,M[4][0],M[4][2],t+P,I-t,Q)}if(M[5][1]>=E&&M[5][0]<=ad){this.rect_or_text(A,ak,E,ad,M[5][0],M[5][2],r+P,p-r,Q)}if(r>I){A.fillStyle="#999";A.fillRect(I+P,Q+5,r-I,1)}}else{A.fillStyle=f;this.rect_or_text(A,ak,E,ad,ah,F,W+P,B-W,Q)}if(m!=="Dense"&&!V&&ah>E){A.fillStyle=this.prefs.label_color;if(h===0&&W-A.measureText(F).width<0){A.textAlign="left";A.fillText(J,B+2+P,Q+8)}else{A.textAlign="right";A.fillText(J,W-2+P,Q+8)}A.fillStyle=f}}else{if(z.dataset_type==="interval_index"){if(V){A.fillRect(W+P,Q+5,B-W,1)}else{var v=M[4],O=M[5],Y=M[6],e=M[7];var u,aa,G=null,am=null;if(O&&Y){G=Math.floor(Math.max(0,(O-E)*ak));am=Math.ceil(Math.min(a,Math.max(0,(Y-E)*ak)))}if(m!=="Dense"&&F!==undefined&&ah>E){A.fillStyle=Z;if(h===0&&W-A.measureText(F).width<0){A.textAlign="left";A.fillText(F,B+2+P,Q+8)}else{A.textAlign="right";A.fillText(F,W-2+P,Q+8)}A.fillStyle=f}if(e){if(v){if(v=="+"){A.fillStyle=RIGHT_STRAND}else{if(v=="-"){A.fillStyle=LEFT_STRAND}}A.fillRect(W+P,Q,B-W,10);A.fillStyle=f}f
or(var ae=0,d=e.length;ae<d;ae++){var o=e[ae],c=Math.floor(Math.max(0,(o[0]-E)*ak)),N=Math.ceil(Math.min(a,Math.max((o[1]-E)*ak)));if(c>N){continue}u=5;aa=3;A.fillRect(c+P,Q+aa,N-c,u);if(G!==undefined&&!(c>am||N<G)){u=9;aa=1;var ab=Math.max(c,G),q=Math.min(N,am);A.fillRect(ab+P,Q+aa,q-ab,u)}}}else{u=9;aa=1;A.fillRect(W+P,Q+aa,B-W,u);if(M.strand){if(M.strand=="+"){A.fillStyle=RIGHT_STRAND_INV}else{if(M.strand=="-"){A.fillStyle=LEFT_STRAND_INV}}A.fillRect(W+P,Q,B-W,10);A.fillStyle=prefs.block_color}}}}}af++}}n.append(L);return L},gen_options:function(j){var a=$("<div />").addClass("form-row");var e="track_"+j+"_block_color",l=$("<label />").attr("for",e).text("Block color:"),m=$("<input />").attr("id",e).attr("name",e).val(this.prefs.block_color),k="track_"+j+"_label_color",g=$("<label />").attr("for",k).text("Text color:"),h=$("<input />").attr("id",k).attr("name",k).val(this.prefs.label_color),f="track_"+j+"_show_count",c=$("<label />").attr("for",f).text("Show summary count
s"),b=$('<input type="checkbox" style="float:left;"></input>').attr("id",f).attr("name",f).attr("checked",this.prefs.show_counts),d=$("<div />").append(b).append(c);return a.append(l).append(m).append(g).append(h).append(d)},update_options:function(e){var b=$("#track_"+e+"_block_color").val(),d=$("#track_"+e+"_label_color").val(),c=$("#track_"+e+"_mode option:selected").val(),a=$("#track_"+e+"_show_count").attr("checked");if(b!==this.prefs.block_color||d!==this.prefs.label_color||a!==this.prefs.show_counts){this.prefs.block_color=b;this.prefs.label_color=d;this.prefs.show_counts=a;this.tile_cache.clear();this.draw()}}});var ReadTrack=function(d,b,a,c){FeatureTrack.call(this,d,b,a,c);this.track_type="ReadTrack";this.vertical_detail_px=10;this.vertical_nodetail_px=5};$.extend(ReadTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype,{});
--- a/static/scripts/trackster.js
+++ b/static/scripts/trackster.js
@@ -79,6 +79,7 @@ var View = function( container, chrom, t
this.label_tracks = [];
this.max_low = 0;
this.max_high = 0;
+ this.num_tracks = 0;
this.track_id_counter = 0;
this.zoom_factor = 3;
this.min_separation = 30;
@@ -267,6 +268,7 @@ var View = function( container, chrom, t
if (track.init) { track.init(); }
track.container_div.attr('id', 'track_' + track.track_id);
this.track_id_counter += 1;
+ this.num_tracks += 1;
},
add_label_track: function (label_track) {
label_track.view = this;
@@ -276,6 +278,7 @@ var View = function( container, chrom, t
this.has_changes = true;
track.container_div.fadeOut('slow', function() { $(this).remove(); });
delete this.tracks[this.tracks.indexOf(track)];
+ this.num_tracks -= 1;
},
update_options: function() {
this.has_changes = true;
@@ -570,7 +573,7 @@ var ReferenceTrack = function (view) {
canvas.css( {
position: "absolute",
top: 0,
- left: ( tile_low - this.view.low ) * w_scale + this.left_offset
+ left: ( tile_low - this.view.low ) * w_scale - this.left_offset
});
for (var c = 0, str_len = seq.length; c < str_len; c++) {
--- a/static/scripts/galaxy.base.js
+++ b/static/scripts/galaxy.base.js
@@ -553,6 +553,34 @@ GalaxyAsync.prototype.log_user_action =
});
};
+// Add to trackster browser functionality
+$(".trackster-add").live("click", function() {
+ var dataset = this,
+ dataset_jquery = $(this);
+ $.ajax({
+ url: dataset_jquery.attr("data-url"),
+ data: { "f-dbkey": "hi" },
+ dataType: "html",
+ error: function() { alert( "Could not add this dataset to browser." ); },
+ success: function(table_html) {
+ var parent = window.parent;
+ parent.show_modal("Add to Browser:", table_html, {
+ "Insert Dataset Into": function() {
+ $(parent.document).find('input[name=id]:checked').each(function() {
+ var vis_id = $(this).val();
+ parent.location = dataset_jquery.attr("action-url") + "&id=" + vis_id;
+ });
+ parent.hide_modal();
+ },
+ "Cancel": function() {
+ parent.hide_modal();
+ }
+ });
+ }
+ });
+});
+
+
$(document).ready( function() {
// Links with confirmation
$( "a[confirm]" ).click( function() {
--- a/templates/tracks/new_browser.mako
+++ b/templates/tracks/new_browser.mako
@@ -17,4 +17,8 @@
</div><div style="clear: both;"></div></div>
+ <div class="form-row">
+ Is your build not listed here?
+ <a href="${h.url_for( controller='user', action='dbkeys', panels=True )}">Add a Custom Build</a>
+ </div></form>
--- a/templates/tracks/browser.mako
+++ b/templates/tracks/browser.mako
@@ -56,6 +56,9 @@
</div><form action="#" onsubmit="view.update_options();return false;"><div id="show-hide-move">
+ <div id="no-tracks" class="warningmessage" style="margin: 5px; display:none;">
+ There are currently no tracks in this browser. Add tracks via the button above.
+ </div><ul id="sortable-ul"></ul></div><input type="submit" id="refresh-button" value="Refresh" style="display:none" />
@@ -123,6 +126,14 @@
// Execute initializer for EDITOR specific javascript
function init() {
+ %if add_dataset:
+ console.log("Adding dataset");
+ ## Code for adding new dataset
+ %endif
+
+ if (view.num_tracks === 0) {
+ $("#no-tracks").show();
+ }
$("#title").text(view.title + " (" + view.dbkey + ")");
$("ul#sortable-ul").sortable({
update: function(event, ui) {
@@ -161,6 +172,7 @@
view.add_track(new_track);
view.has_changes = true;
+ $("#no-tracks").hide();
sidebar_box(new_track);
}
});
@@ -231,6 +243,9 @@
del_icon.bind("click", function() {
$("#track_" + track_id + "_li").fadeOut('slow', function() { $("#track_" + track_id + "_li").remove(); });
view.remove_track(track);
+ if (view.num_tracks === 0) {
+ $("#no-tracks").show();
+ }
});
icon_div.append(edit_icon).append(del_icon);
title.append(label).prepend(icon_div);
--- a/templates/root/history_common.mako
+++ b/templates/root/history_common.mako
@@ -119,7 +119,8 @@
%if for_editing:
<a href="${h.url_for( controller='tool_runner', action='rerun', id=data.id )}" target="galaxy_main" title="Run this job again" class="icon-button arrow-circle tooltip"></a>
%if app.config.get_bool( 'enable_tracks', False ) and data.ext in app.datatypes_registry.get_available_tracks():
- <a class="icon-button vis-chart tooltip trackster" title="Visualize in Trackster" id="visualize_${hid}"></a>
+ <a data-url="${h.url_for( controller='tracks', action='list_tracks' )}" class="icon-button vis-chart tooltip trackster-add"
+ action-url="${h.url_for( controller='tracks', action='browser', dataset_id=dataset_id)}" title="Visualize in Trackster"></a>
%endif
%if trans.user:
<div style="float: right">
1
0
galaxy-dist commit 9780508ee0c3: Allow interval to bedstrict converter to work on bed files that may have e.g. a 'track' line.
by commits-noreply@bitbucket.org 30 Jul '10
by commits-noreply@bitbucket.org 30 Jul '10
30 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Dan Blankenberg <dan(a)bx.psu.edu>
# Date 1280371288 14400
# Node ID 9780508ee0c31b132158842afcae6c5ef90028a7
# Parent 75e99661d24caaeda1381b14cd062ffdcf7c3ecd
Allow interval to bedstrict converter to work on bed files that may have e.g. a 'track' line.
--- a/lib/galaxy/datatypes/converters/interval_to_bedstrict_converter.py
+++ b/lib/galaxy/datatypes/converters/interval_to_bedstrict_converter.py
@@ -81,8 +81,8 @@ def __main__():
first_skipped_line = count + 1
continue
fields = line.split('\t')
- assert len( fields ) >= 3, 'A BED file requires at least 3 columns' #we can't fix this
try:
+ assert len( fields ) >= 3, 'A BED file requires at least 3 columns' #we can't fix this
if len(fields) > 12:
strict_bed = False
break
1
0
galaxy-dist commit af48a13e46b9: Fix a bug in the sge runner's stop_job and job finishing logic.
by commits-noreply@bitbucket.org 30 Jul '10
by commits-noreply@bitbucket.org 30 Jul '10
30 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Nate Coraor <nate(a)bx.psu.edu>
# Date 1280411093 14400
# Node ID af48a13e46b9ab0136bcbf14508bd9bb044ad257
# Parent 9780508ee0c31b132158842afcae6c5ef90028a7
Fix a bug in the sge runner's stop_job and job finishing logic.
--- a/lib/galaxy/jobs/runners/sge.py
+++ b/lib/galaxy/jobs/runners/sge.py
@@ -69,6 +69,7 @@ class SGEJobRunner( object ):
if DRMAA is None:
raise Exception( "SGEJobRunner requires DRMAA_python which was not found" )
self.app = app
+ self.sa_session = app.model.context
# 'watched' and 'queue' are both used to keep track of jobs to watch.
# 'queue' is used to add new watched jobs, and can be called from
# any thread (usually by the 'queue_job' method). 'watched' must only
@@ -291,13 +292,9 @@ class SGEJobRunner( object ):
if state == DRMAA.Session.RUNNING and not sge_job_state.running:
sge_job_state.running = True
sge_job_state.job_wrapper.change_state( model.Job.states.RUNNING )
- if state == DRMAA.Session.DONE:
+ if state in ( DRMAA.Session.DONE, DRMAA.Session.FAILED ):
self.work_queue.put( ( 'finish', sge_job_state ) )
continue
- if state == DRMAA.Session.FAILED:
- sge_job_state.fail_message = "Cluster could not complete job"
- self.work_queue.put( ( 'fail', sge_job_state ) )
- continue
sge_job_state.old_state = state
new_watched.append( sge_job_state )
# Replace the watch list with the updated version
1
0
galaxy-dist commit fd9514c5349f: The DRMAA job runner, which should be compatible with all DRMs which support DRMAA (most notably, LSF and PBS Pro). Minor testing with LSF has been done, and this code is based on the SGE runner code which has been in service for a long time, but it should not be considered production.
by commits-noreply@bitbucket.org 30 Jul '10
by commits-noreply@bitbucket.org 30 Jul '10
30 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Nate Coraor <nate(a)bx.psu.edu>
# Date 1280348685 14400
# Node ID fd9514c5349fadaf31abec04fd8725cc50a4213f
# Parent 34ee69d9ba931b99a935b4ba7b38288160d68d31
The DRMAA job runner, which should be compatible with all DRMs which support DRMAA (most notably, LSF and PBS Pro). Minor testing with LSF has been done, and this code is based on the SGE runner code which has been in service for a long time, but it should not be considered production.
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -737,6 +737,9 @@ class DefaultJobDispatcher( object ):
elif runner_name == "sge":
import runners.sge
self.job_runners[runner_name] = runners.sge.SGEJobRunner( app )
+ elif runner_name == "drmaa":
+ import runners.drmaa
+ self.job_runners[runner_name] = runners.drmaa.DRMAAJobRunner( app )
else:
log.error( "Unable to start unknown job runner: %s" %runner_name )
--- a/eggs.ini
+++ b/eggs.ini
@@ -31,6 +31,7 @@ amqplib = 0.6.1
Beaker = 1.4
decorator = 3.1.2
docutils = 0.4
+drmaa = 0.4b3
elementtree = 1.2.6_20050316
GeneTrack = 2.0.0_beta_1
lrucache = 0.2
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -151,7 +151,7 @@ class Configuration( object ):
raise ConfigurationError("File not found: %s" % path )
# Check job runners so the admin can scramble dependent egg.
if self.start_job_runners is not None:
- runner_to_egg = dict( pbs = 'pbs_python', sge = 'DRMAA_python' )
+ runner_to_egg = dict( pbs = 'pbs_python', sge = 'DRMAA_python', drmaa = 'drmaa' )
for runner in self.start_job_runners.split( ',' ):
try:
pkg_resources.require( runner_to_egg[runner] )
--- /dev/null
+++ b/lib/galaxy/jobs/runners/drmaa.py
@@ -0,0 +1,347 @@
+import os, logging, threading, time
+from Queue import Queue, Empty
+
+from galaxy import model
+from paste.deploy.converters import asbool
+
+import pkg_resources
+
+try:
+ pkg_resources.require( "drmaa" )
+ drmaa = __import__( "drmaa" )
+except Exception, e:
+ drmaa = str( e )
+
+log = logging.getLogger( __name__ )
+
+if type( drmaa ) != str:
+ drmaa_state = {
+ drmaa.JobState.UNDETERMINED: 'process status cannot be determined',
+ drmaa.JobState.QUEUED_ACTIVE: 'job is queued and active',
+ drmaa.JobState.SYSTEM_ON_HOLD: 'job is queued and in system hold',
+ drmaa.JobState.USER_ON_HOLD: 'job is queued and in user hold',
+ drmaa.JobState.USER_SYSTEM_ON_HOLD: 'job is queued and in user and system hold',
+ drmaa.JobState.RUNNING: 'job is running',
+ drmaa.JobState.SYSTEM_SUSPENDED: 'job is system suspended',
+ drmaa.JobState.USER_SUSPENDED: 'job is user suspended',
+ drmaa.JobState.DONE: 'job finished normally',
+ drmaa.JobState.FAILED: 'job finished, but failed',
+ }
+
+drm_template = """#!/bin/sh
+#$ -S /bin/sh
+GALAXY_LIB="%s"
+if [ "$GALAXY_LIB" != "None" ]; then
+ if [ -n "$PYTHONPATH" ]; then
+ PYTHONPATH="$GALAXY_LIB:$PYTHONPATH"
+ else
+ PYTHONPATH="$GALAXY_LIB"
+ fi
+ export PYTHONPATH
+fi
+cd %s
+%s
+"""
+
+class DRMAAJobState( object ):
+ def __init__( self ):
+ """
+ Encapsulates state related to a job that is being run via the DRM and
+ that we need to monitor.
+ """
+ self.job_wrapper = None
+ self.job_id = None
+ self.old_state = None
+ self.running = False
+ self.job_file = None
+ self.ofile = None
+ self.efile = None
+ self.runner_url = None
+
+class DRMAAJobRunner( object ):
+ """
+ Job runner backed by a finite pool of worker threads. FIFO scheduling
+ """
+ STOP_SIGNAL = object()
+ def __init__( self, app ):
+ """Initialize this job runner and start the monitor thread"""
+ # Check if drmaa was importable, fail if not
+ if type( drmaa ) == str:
+ raise Exception( "DRMAAJobRunner requires drmaa module which could not be loaded: %s" % drmaa )
+ self.app = app
+ self.sa_session = app.model.context
+ # 'watched' and 'queue' are both used to keep track of jobs to watch.
+ # 'queue' is used to add new watched jobs, and can be called from
+ # any thread (usually by the 'queue_job' method). 'watched' must only
+ # be modified by the monitor thread, which will move items from 'queue'
+ # to 'watched' and then manage the watched jobs.
+ self.watched = []
+ self.monitor_queue = Queue()
+ self.ds = drmaa.Session()
+ self.ds.initialize()
+ self.monitor_thread = threading.Thread( target=self.monitor )
+ self.monitor_thread.start()
+ self.work_queue = Queue()
+ self.work_threads = []
+ nworkers = app.config.cluster_job_queue_workers
+ for i in range( nworkers ):
+ worker = threading.Thread( target=self.run_next )
+ worker.start()
+ self.work_threads.append( worker )
+ log.debug( "%d workers ready" % nworkers )
+
+ def get_native_spec( self, url ):
+ """Get any native DRM arguments specified by the site configuration"""
+ try:
+ return url.split('/')[2] or None
+ except:
+ return None
+
+ def run_next( self ):
+ """
+ Run the next item in the queue (a job waiting to run or finish )
+ """
+ while 1:
+ ( op, obj ) = self.work_queue.get()
+ if op is self.STOP_SIGNAL:
+ return
+ try:
+ if op == 'queue':
+ self.queue_job( obj )
+ elif op == 'finish':
+ self.finish_job( obj )
+ elif op == 'fail':
+ self.fail_job( obj )
+ except:
+ log.exception( "Uncaught exception %sing job" % op )
+
+ def queue_job( self, job_wrapper ):
+ """Create job script and submit it to the DRM"""
+
+ try:
+ job_wrapper.prepare()
+ command_line = job_wrapper.get_command_line()
+ except:
+ job_wrapper.fail( "failure preparing job", exception=True )
+ log.exception("failure running job %d" % job_wrapper.job_id)
+ return
+
+ runner_url = job_wrapper.tool.job_runner
+
+ # This is silly, why would we queue a job with no command line?
+ if not command_line:
+ job_wrapper.finish( '', '' )
+ return
+
+ # Check for deletion before we change state
+ if job_wrapper.get_state() == model.Job.states.DELETED:
+ log.debug( "Job %s deleted by user before it entered the queue" % job_wrapper.job_id )
+ job_wrapper.cleanup()
+ return
+
+ # Change to queued state immediately
+ job_wrapper.change_state( model.Job.states.QUEUED )
+
+ # define job attributes
+ ofile = "%s/database/pbs/%s.o" % (os.getcwd(), job_wrapper.job_id)
+ efile = "%s/database/pbs/%s.e" % (os.getcwd(), job_wrapper.job_id)
+ jt = self.ds.createJobTemplate()
+ jt.remoteCommand = "%s/database/pbs/galaxy_%s.sh" % (os.getcwd(), job_wrapper.job_id)
+ jt.outputPath = ":%s" % ofile
+ jt.errorPath = ":%s" % efile
+ native_spec = self.get_native_spec( runner_url )
+ if native_spec is not None:
+ jt.nativeSpecification = native_spec
+
+ script = drm_template % (job_wrapper.galaxy_lib_dir, os.path.abspath( job_wrapper.working_directory ), command_line)
+ if self.app.config.set_metadata_externally:
+ script += "cd %s\n" % os.path.abspath( os.getcwd() )
+ script += "%s\n" % job_wrapper.setup_external_metadata( exec_dir = os.path.abspath( os.getcwd() ),
+ tmp_dir = self.app.config.new_file_path,
+ dataset_files_path = self.app.model.Dataset.file_path,
+ output_fnames = job_wrapper.get_output_fnames(),
+ set_extension = False,
+ kwds = { 'overwrite' : False } ) #we don't want to overwrite metadata that was copied over in init_meta(), as per established behavior
+ fh = file( jt.remoteCommand, "w" )
+ fh.write( script )
+ fh.close()
+ os.chmod( jt.remoteCommand, 0750 )
+
+ # job was deleted while we were preparing it
+ if job_wrapper.get_state() == model.Job.states.DELETED:
+ log.debug( "Job %s deleted by user before it entered the queue" % job_wrapper.job_id )
+ self.cleanup( ( ofile, efile, jt.remoteCommand ) )
+ job_wrapper.cleanup()
+ return
+
+ galaxy_job_id = job_wrapper.job_id
+ log.debug("(%s) submitting file %s" % ( galaxy_job_id, jt.remoteCommand ) )
+ log.debug("(%s) command is: %s" % ( galaxy_job_id, command_line ) )
+ # runJob will raise if there's a submit problem
+ job_id = self.ds.runJob(jt)
+ log.info("(%s) queued as %s" % ( galaxy_job_id, job_id ) )
+
+ # store runner information for tracking if Galaxy restarts
+ job_wrapper.set_runner( runner_url, job_id )
+
+ # Store DRM related state information for job
+ drm_job_state = DRMAAJobState()
+ drm_job_state.job_wrapper = job_wrapper
+ drm_job_state.job_id = job_id
+ drm_job_state.ofile = ofile
+ drm_job_state.efile = efile
+ drm_job_state.job_file = jt.remoteCommand
+ drm_job_state.old_state = 'new'
+ drm_job_state.running = False
+ drm_job_state.runner_url = runner_url
+
+ # delete the job template
+ self.ds.deleteJobTemplate( jt )
+
+ # Add to our 'queue' of jobs to monitor
+ self.monitor_queue.put( drm_job_state )
+
+ def monitor( self ):
+ """
+ Watches jobs currently in the PBS queue and deals with state changes
+ (queued to running) and job completion
+ """
+ while 1:
+ # Take any new watched jobs and put them on the monitor list
+ try:
+ while 1:
+ drm_job_state = self.monitor_queue.get_nowait()
+ if drm_job_state is self.STOP_SIGNAL:
+ # TODO: This is where any cleanup would occur
+ self.ds.exit()
+ return
+ self.watched.append( drm_job_state )
+ except Empty:
+ pass
+ # Iterate over the list of watched jobs and check state
+ self.check_watched_items()
+ # Sleep a bit before the next state check
+ time.sleep( 1 )
+
+ def check_watched_items( self ):
+ """
+ Called by the monitor thread to look at each watched job and deal
+ with state changes.
+ """
+ new_watched = []
+ for drm_job_state in self.watched:
+ job_id = drm_job_state.job_id
+ galaxy_job_id = drm_job_state.job_wrapper.job_id
+ old_state = drm_job_state.old_state
+ try:
+ state = self.ds.jobStatus( job_id )
+ except drmaa.InvalidJobException:
+ # we should only get here if an orphaned job was put into the queue at app startup
+ log.debug("(%s/%s) job left DRM queue" % ( galaxy_job_id, job_id ) )
+ self.work_queue.put( ( 'finish', drm_job_state ) )
+ continue
+ except Exception, e:
+ # so we don't kill the monitor thread
+ log.exception("(%s/%s) Unable to check job status" % ( galaxy_job_id, job_id ) )
+ log.warning("(%s/%s) job will now be errored" % ( galaxy_job_id, job_id ) )
+ drm_job_state.fail_message = "Cluster could not complete job"
+ self.work_queue.put( ( 'fail', drm_job_state ) )
+ continue
+ if state != old_state:
+ log.debug("(%s/%s) state change: %s" % ( galaxy_job_id, job_id, drmaa_state[state] ) )
+ if state == drmaa.JobState.RUNNING and not drm_job_state.running:
+ drm_job_state.running = True
+ drm_job_state.job_wrapper.change_state( model.Job.states.RUNNING )
+ if state in ( drmaa.JobState.DONE, drmaa.JobState.FAILED ):
+ self.work_queue.put( ( 'finish', drm_job_state ) )
+ continue
+ drm_job_state.old_state = state
+ new_watched.append( drm_job_state )
+ # Replace the watch list with the updated version
+ self.watched = new_watched
+
+ def finish_job( self, drm_job_state ):
+ """
+ Get the output/error for a finished job, pass to `job_wrapper.finish`
+ and cleanup all the DRM temporary files.
+ """
+ ofile = drm_job_state.ofile
+ efile = drm_job_state.efile
+ job_file = drm_job_state.job_file
+ # collect the output
+ try:
+ ofh = file(ofile, "r")
+ efh = file(efile, "r")
+ stdout = ofh.read()
+ stderr = efh.read()
+ except:
+ stdout = ''
+ stderr = 'Job output not returned from cluster'
+ log.debug(stderr)
+
+ try:
+ drm_job_state.job_wrapper.finish( stdout, stderr )
+ except:
+ log.exception("Job wrapper finish method failed")
+
+ # clean up the drm files
+ self.cleanup( ( ofile, efile, job_file ) )
+
+ def fail_job( self, drm_job_state ):
+ """
+ Seperated out so we can use the worker threads for it.
+ """
+ self.stop_job( self.sa_session.query( self.app.model.Job ).get( drm_job_state.job_wrapper.job_id ) )
+ drm_job_state.job_wrapper.fail( drm_job_state.fail_message )
+ self.cleanup( ( drm_job_state.ofile, drm_job_state.efile, drm_job_state.job_file ) )
+
+ def cleanup( self, files ):
+ if not asbool( self.app.config.get( 'debug', False ) ):
+ for file in files:
+ if os.access( file, os.R_OK ):
+ os.unlink( file )
+
+ def put( self, job_wrapper ):
+ """Add a job to the queue (by job identifier)"""
+ # Change to queued state before handing to worker thread so the runner won't pick it up again
+ job_wrapper.change_state( model.Job.states.QUEUED )
+ self.work_queue.put( ( 'queue', job_wrapper ) )
+
+ def shutdown( self ):
+ """Attempts to gracefully shut down the monitor thread"""
+ log.info( "sending stop signal to worker threads" )
+ self.monitor_queue.put( self.STOP_SIGNAL )
+ for i in range( len( self.work_threads ) ):
+ self.work_queue.put( ( self.STOP_SIGNAL, None ) )
+ log.info( "drmaa job runner stopped" )
+
+ def stop_job( self, job ):
+ """Attempts to delete a job from the DRM queue"""
+ try:
+ self.ds.control( job.job_runner_external_id, drmaa.JobControlAction.TERMINATE )
+ log.debug( "(%s/%s) Removed from DRM queue at user's request" % ( job.id, job.job_runner_external_id ) )
+ except drmaa.InvalidJobException:
+ log.debug( "(%s/%s) User killed running job, but it was already dead" % ( job.id, job.job_runner_external_id ) )
+ except Exception, e:
+ log.debug( "(%s/%s) User killed running job, but error encountered removing from DRM queue: %s" % ( job.id, job.job_runner_external_id, e ) )
+
+ def recover( self, job, job_wrapper ):
+ """Recovers jobs stuck in the queued/running state when Galaxy started"""
+ drm_job_state = DRMAAJobState()
+ drm_job_state.ofile = "%s/database/pbs/%s.o" % (os.getcwd(), job.id)
+ drm_job_state.efile = "%s/database/pbs/%s.e" % (os.getcwd(), job.id)
+ drm_job_state.job_file = "%s/database/pbs/galaxy_%s.sh" % (os.getcwd(), job.id)
+ drm_job_state.job_id = str( job.job_runner_external_id )
+ drm_job_state.runner_url = job_wrapper.tool.job_runner
+ job_wrapper.command_line = job.command_line
+ drm_job_state.job_wrapper = job_wrapper
+ if job.state == model.Job.states.RUNNING:
+ log.debug( "(%s/%s) is still in running state, adding to the DRM queue" % ( job.id, job.job_runner_external_id ) )
+ drm_job_state.old_state = drmaa.JobState.RUNNING
+ drm_job_state.running = True
+ self.monitor_queue.put( drm_job_state )
+ elif job.state == model.Job.states.QUEUED:
+ log.debug( "(%s/%s) is still in DRM queued state, adding to the DRM queue" % ( job.id, job.job_runner_external_id ) )
+ drm_job_state.old_state = drmaa.JobState.QUEUED_ACTIVE
+ drm_job_state.running = False
+ self.monitor_queue.put( drm_job_state )
--- a/lib/galaxy/eggs/__init__.py
+++ b/lib/galaxy/eggs/__init__.py
@@ -314,6 +314,7 @@ class GalaxyConfig( object ):
return { "psycopg2": lambda: self.config.get( "app:main", "database_connection" ).startswith( "postgres://" ),
"MySQL_python": lambda: self.config.get( "app:main", "database_connection" ).startswith( "mysql://" ),
"DRMAA_python": lambda: "sge" in self.config.get( "app:main", "start_job_runners" ).split(","),
+ "drmaa": lambda: ( "drmaa" in self.config.get( "app:main", "start_job_runners" ).split(",") ) and sys.version_info[:2] >= ( 2, 5 ),
"pbs_python": lambda: "pbs" in self.config.get( "app:main", "start_job_runners" ).split(","),
"threadframe": lambda: self.config.get( "app:main", "use_heartbeat" ),
"guppy": lambda: self.config.get( "app:main", "use_memdump" ),
1
0
galaxy-dist commit 34ee69d9ba93: Fixes and code cleanup for the Galaxy tool shed. Fixes include improved mappers and more optimal methods for defining the latest version of a tool and the latest version of tools by state.
by commits-noreply@bitbucket.org 30 Jul '10
by commits-noreply@bitbucket.org 30 Jul '10
30 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Greg Von Kuster <greg(a)bx.psu.edu>
# Date 1280346733 14400
# Node ID 34ee69d9ba931b99a935b4ba7b38288160d68d31
# Parent 00672de098618096b9a11f7ad4983eb1c4faa32a
Fixes and code cleanup for the Galaxy tool shed. Fixes include improved mappers and more optimal methods for defining the latest version of a tool and the latest version of tools by state.
--- a/lib/galaxy/webapps/community/controllers/common.py
+++ b/lib/galaxy/webapps/community/controllers/common.py
@@ -96,7 +96,7 @@ class ToolListGrid( grids.Grid ):
ToolCategoryColumn( "Category",
key="category",
model_class=model.Category,
- visible=False ),
+ visible=False )
]
columns.append( grids.MulticolFilterColumn( "Search",
cols_to_filter=[ columns[0], columns[1], columns[2] ],
@@ -123,6 +123,14 @@ class CategoryListGrid( grids.Grid ):
class DescriptionColumn( grids.TextColumn ):
def get_value( self, trans, grid, category ):
return category.description
+ class ToolsColumn( grids.TextColumn ):
+ def get_value( self, trans, grid, category ):
+ if category.tools:
+ viewable_tools = 0
+ for tca in category.tools:
+ viewable_tools += 1
+ return viewable_tools
+ return 0
# Grid definition
webapp = "community"
@@ -148,13 +156,17 @@ class CategoryListGrid( grids.Grid ):
grids.DeletedColumn( "Deleted",
key="deleted",
visible=False,
- filterable="advanced" )
+ filterable="advanced" ),
+ ToolsColumn( "Tools",
+ model_class=model.Tool,
+ attach_popup=False )
]
columns.append( grids.MulticolFilterColumn( "Search",
cols_to_filter=[ columns[0], columns[1] ],
key="free-text-search",
visible=False,
filterable="standard" ) )
+
# Override these
global_actions = []
operations = []
@@ -240,7 +252,7 @@ class CommonController( BaseController )
out_categories.append( ( category.id, category.name ) )
if tool.is_rejected:
# Include the comments regarding the reason for rejection
- reason_for_rejection = get_most_recent_event( tool ).comment
+ reason_for_rejection = tool.latest_event.comment
else:
reason_for_rejection = ''
can_approve_or_reject = trans.app.security_agent.can_approve_or_reject( trans.user, trans.user_is_admin(), cntrller, tool )
@@ -294,7 +306,7 @@ class CommonController( BaseController )
tool_file_contents = tarfile.open( tool.file_name, 'r' ).getnames()
if tool.is_rejected:
# Include the comments regarding the reason for rejection
- reason_for_rejection = get_most_recent_event( tool ).comment
+ reason_for_rejection = tool.latest_event.comment
else:
reason_for_rejection = ''
return trans.fill_template( '/webapps/community/tool/view_tool.mako',
@@ -429,15 +441,15 @@ class CommonController( BaseController )
def get_versions( item ):
"""Get all versions of item"""
- versions = [item]
+ versions = [ item ]
this_item = item
while item.newer_version:
versions.insert( 0, item.newer_version )
item = item.newer_version
item = this_item
while item.older_version:
- versions.append( item.older_version[0] )
- item = item.older_version[0]
+ versions.append( item.older_version[ 0 ] )
+ item = item.older_version[ 0 ]
return versions
def get_categories( trans ):
"""Get all categories from the database"""
@@ -450,22 +462,21 @@ def get_category( trans, id ):
def get_tool( trans, id ):
"""Get a tool from the database"""
return trans.sa_session.query( trans.model.Tool ).get( trans.app.security.decode_id( id ) )
-def get_tools( trans ):
+def get_latest_versions_of_tools( trans ):
"""Get only the latest version of each tool from the database"""
return trans.sa_session.query( trans.model.Tool ) \
.filter( trans.model.Tool.newer_version_id == None ) \
.order_by( trans.model.Tool.name )
+def get_approved_tools( trans ):
+ """Get the tools from the database whose state is APPROVED"""
+ approved_tools = []
+ for tool in get_latest_versions_of_tools( trans ):
+ if tool.state == trans.model.Tool.states.APPROVED:
+ approved_tools.append( tool )
+ return approved_tools
def get_event( trans, id ):
"""Get an event from the databse"""
return trans.sa_session.query( trans.model.Event ).get( trans.security.decode_id( id ) )
-def get_most_recent_event( item ):
- """Get the most recent event for item"""
- if item.events:
- # Sort the events in ascending order by update_time
- events = model.sort_by_attr( [ item_event_assoc.event for item_event_assoc in item.events ], 'update_time' )
- # Get the last event that occurred
- return events[-1]
- return None
def get_user( trans, id ):
"""Get a user from the database"""
return trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( id ) )
--- a/templates/webapps/community/index.mako
+++ b/templates/webapps/community/index.mako
@@ -81,7 +81,7 @@
<div class="toolSectionBody"><div class="toolSectionBg"><div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='tool', action='browse_categories', webapp='community' )}">Browse by category</a></div>
- <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='tool', action='browse_tools', webapp='community' )}">Browse all tools</a></div>
+ <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='tool', action='browse_tools', operation='approved_tools', webapp='community' )}">Browse all tools</a></div>
%if trans.user:
<div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='tool', action='browse_tools', operation='my_tools', webapp='community' )}">Browse your tools</a></div>
%endif
--- a/lib/galaxy/webapps/community/model/__init__.py
+++ b/lib/galaxy/webapps/community/model/__init__.py
@@ -10,7 +10,6 @@ from galaxy import util
from galaxy.util.hash_util import *
from galaxy.web.form_builder import *
log = logging.getLogger( __name__ )
-from sqlalchemy.orm import object_session
class User( object ):
def __init__( self, email=None, password=None ):
@@ -137,22 +136,17 @@ class Tool( object ):
self.suite = datatype_bunch.suite
@property
def state( self ):
+ latest_event = self.latest_event
+ if latest_event:
+ return latest_event.state
+ return None
+ @property
+ def latest_event( self ):
if self.events:
- # Sort the events in ascending order by update_time
- events = sort_by_attr( [ tca.event for tca in self.events ], 'update_time' )
- # Get the last event that occurred
- return events[-1].state
+ events = [ tea.event for tea in self.events ]
+ # Get the last event that occurred ( events mapper is sorted descending )
+ return events[0]
return None
- def last_comment( self ):
- if self.events:
- # Sort the events in ascending order by update_time
- events = sort_by_attr( [ tca.event for tca in self.events ], 'update_time' )
- # Get the last event that occurred
- if events[-1].comment:
- return events[-1].comment
- else:
- return ''
- return 'No comment'
# Tool states
@property
def is_new( self ):
--- a/lib/galaxy/webapps/community/model/mapping.py
+++ b/lib/galaxy/webapps/community/model/mapping.py
@@ -14,8 +14,6 @@ from galaxy.model.orm.ext.assignmapper i
from galaxy.model.custom_types import *
from galaxy.util.bunch import Bunch
from galaxy.webapps.community.security import CommunityRBACAgent
-from sqlalchemy.orm.collections import attribute_mapped_collection
-from sqlalchemy.ext.associationproxy import association_proxy
metadata = MetaData()
context = Session = scoped_session( sessionmaker( autoflush=False, autocommit=True ) )
@@ -216,7 +214,12 @@ assign_mapper( context, ToolAnnotationAs
assign_mapper( context, Tool, Tool.table,
properties = dict(
categories=relation( ToolCategoryAssociation ),
- events=relation( ToolEventAssociation ),
+ events=relation( ToolEventAssociation, secondary=Event.table,
+ primaryjoin=( Tool.table.c.id==ToolEventAssociation.table.c.tool_id ),
+ secondaryjoin=( ToolEventAssociation.table.c.event_id==Event.table.c.id ),
+ order_by=desc( Event.table.c.update_time ),
+ viewonly=True,
+ uselist=True ),
user=relation( User.mapper ),
older_version=relation(
Tool,
--- a/lib/galaxy/webapps/community/controllers/tool.py
+++ b/lib/galaxy/webapps/community/controllers/tool.py
@@ -8,45 +8,55 @@ from common import *
log = logging.getLogger( __name__ )
+class StateColumn( grids.TextColumn ):
+ def get_value( self, trans, grid, tool ):
+ state = tool.state
+ if state == trans.model.Tool.states.APPROVED:
+ state_color = 'ok'
+ elif state == trans.model.Tool.states.REJECTED:
+ state_color = 'error'
+ elif state == trans.model.Tool.states.ARCHIVED:
+ state_color = 'upload'
+ else:
+ state_color = state
+ return '<div class="count-box state-color-%s">%s</div>' % ( state_color, state )
+class ToolStateColumn( grids.StateColumn ):
+ def filter( self, trans, user, query, column_filter ):
+ """Modify query to filter self.model_class by state."""
+ if column_filter == "All":
+ pass
+ elif column_filter in [ v for k, v in self.model_class.states.items() ]:
+ # Get all of the latest Events associated with the current version of each tool
+ latest_event_id_for_current_versions_of_tools = [ tool.latest_event.id for tool in get_latest_versions_of_tools( trans ) ]
+ # Filter query by the latest state for the current version of each tool
+ return query.filter( and_( model.Event.table.c.state == column_filter,
+ model.Event.table.c.id.in_( latest_event_id_for_current_versions_of_tools ) ) )
+ return query
+
class ApprovedToolListGrid( ToolListGrid ):
- def apply_query_filter( self, trans, query, **kwargs ):
- return query.filter( model.Event.table.c.state == 'approved' )
-
-class MyToolsListGrid( ToolListGrid ):
- class StateColumn( grids.TextColumn ):
- def get_value( self, trans, grid, tool ):
- state = tool.state
- if state == 'approved':
- state_color = 'ok'
- elif state == 'rejected':
- state_color = 'error'
- elif state == 'archived':
- state_color = 'upload'
- else:
- state_color = state
- return '<div class="count-box state-color-%s">%s</div>' % ( state_color, state )
- class ToolStateColumn( grids.StateColumn ):
- def filter( self, trans, user, query, column_filter ):
- """Modify query to filter self.model_class by state."""
- if column_filter == "All":
- pass
- elif column_filter in [ v for k, v in self.model_class.states.items() ]:
- # Get all of the latest ToolEventAssociation ids
- tea_ids = [ tea_id_tup[0] for tea_id_tup in trans.sa_session.query( func.max( model.ToolEventAssociation.table.c.id ) ) \
- .group_by( model.ToolEventAssociation.table.c.tool_id ) ]
- # Get all of the Event ids associated with the latest ToolEventAssociation ids
- event_ids = [ event_id_tup[0] for event_id_tup in trans.sa_session.query( model.ToolEventAssociation.table.c.event_id ) \
- .filter( model.ToolEventAssociation.table.c.id.in_( tea_ids ) ) ]
- # Filter our query by state and event ids
- return query.filter( and_( model.Event.table.c.state == column_filter,
- model.Event.table.c.id.in_( event_ids ) ) )
- return query
-
columns = [ col for col in ToolListGrid.columns ]
columns.append(
StateColumn( "Status",
model_class=model.Tool,
link=( lambda item: dict( operation="tools_by_state", id=item.id, webapp="community" ) ),
+ visible=False,
+ attach_popup=False )
+ )
+ columns.append(
+ ToolStateColumn( "State",
+ key="state",
+ model_class=model.Tool,
+ visible=False,
+ filterable="advanced" )
+ )
+
+class MyToolsListGrid( ApprovedToolListGrid ):
+ columns = [ col for col in ToolListGrid.columns ]
+ columns.append(
+ StateColumn( "Status",
+ model_class=model.Tool,
+ link=( lambda item: dict( operation="tools_by_state", id=item.id, webapp="community" ) ),
+ visible=True,
attach_popup=False )
)
columns.append(
@@ -58,6 +68,10 @@ class MyToolsListGrid( ToolListGrid ):
)
class ToolCategoryListGrid( CategoryListGrid ):
+ """
+ Replaces the tools column in the Category grid with a similar column,
+ but displaying the number of APPROVED tools in the category.
+ """
class ToolsColumn( grids.TextColumn ):
def get_value( self, trans, grid, category ):
if category.tools:
@@ -69,7 +83,10 @@ class ToolCategoryListGrid( CategoryList
return viewable_tools
return 0
- columns = [ col for col in CategoryListGrid.columns ]
+ columns = []
+ for col in CategoryListGrid.columns:
+ if not isinstance( col, CategoryListGrid.ToolsColumn ):
+ columns.append( col )
columns.append(
ToolsColumn( "Tools",
model_class=model.Tool,
@@ -146,6 +163,14 @@ class ToolController( BaseController ):
del kwd[ k ]
kwd[ 'f-email' ] = trans.user.email
return self.my_tools_list_grid( trans, **kwd )
+ elif operation == "approved_tools":
+ # Eliminate the current filters if any exist.
+ for k, v in kwd.items():
+ if k.startswith( 'f-' ):
+ del kwd[ k ]
+ # Make sure only the latest version of a tool whose state is APPROVED are displayed.
+ kwd[ 'f-state' ] = trans.model.Tool.states.APPROVED
+ return self.tool_list_grid( trans, **kwd )
elif operation == "tools_by_category":
# Eliminate the current filters if any exist.
for k, v in kwd.items():
@@ -154,6 +179,8 @@ class ToolController( BaseController ):
category_id = kwd.get( 'id', None )
category = get_category( trans, category_id )
kwd[ 'f-category' ] = category.name
+ # Make sure only the latest version of a tool whose state is APPROVED are displayed.
+ kwd[ 'f-state' ] = trans.model.Tool.states.APPROVED
# Render the list view
return self.tool_list_grid( trans, **kwd )
@web.expose
--- a/lib/galaxy/webapps/community/datatypes/__init__.py
+++ b/lib/galaxy/webapps/community/datatypes/__init__.py
@@ -69,7 +69,7 @@ class Tool( object ):
raise DatatypeVerificationError( 'The archive is not a readable tar file.' )
if not xml_files:
# Make sure we're not uploading a tool suite
- if filter( lambda x: x.lower() == 'suite_config.xml', tar.getnames() ):
+ if filter( lambda x: x.lower().find( 'suite_config.xml' ) >= 0, tar.getnames() ):
raise DatatypeVerificationError( 'The archive includes a suite_config.xml file, so set the upload type to "Tool Suite".' )
xml_files = filter( lambda x: x.lower().endswith( '.xml' ), tar.getnames() )
if not xml_files:
--- a/lib/galaxy/webapps/community/controllers/admin.py
+++ b/lib/galaxy/webapps/community/controllers/admin.py
@@ -2,7 +2,7 @@ from galaxy.web.base.controller import *
from galaxy.webapps.community import model
from galaxy.model.orm import *
from galaxy.web.framework.helpers import time_ago, iff, grids
-from common import ToolListGrid, CategoryListGrid, get_category, get_tools, get_event, get_tool, get_versions
+from common import ToolListGrid, CategoryListGrid, get_category, get_event, get_tool, get_versions
import logging
log = logging.getLogger( __name__ )
@@ -791,38 +791,3 @@ class AdminController( BaseController, A
action='manage_categories',
message=util.sanitize_text( message ),
status='done' ) )
-
-## ---- Utility methods -------------------------------------------------------
-
-def get_tools_by_state( trans, state ):
- # TODO: write this as a query using eagerload - will be much faster.
- ids = []
- if state == trans.model.Tool.states.NEW:
- for tool in get_tools( trans ):
- if tool.is_new:
- ids.append( tool.id )
- elif state == trans.model.Tool.states.ERROR:
- for tool in get_tools( trans ):
- if tool.is_error:
- ids.append( tool.id )
- elif state == trans.model.Tool.states.DELETED:
- for tool in get_tools( trans ):
- if tool.is_deleted:
- ids.append( tool.id )
- elif state == trans.model.Tool.states.WAITING:
- for tool in get_tools( trans ):
- if tool.is_waiting:
- ids.append( tool.id )
- elif state == trans.model.Tool.states.APPROVED:
- for tool in get_tools( trans ):
- if tool.is_approved:
- ids.append( tool.id )
- elif state == trans.model.Tool.states.REJECTED:
- for tool in get_tools( trans ):
- if tool.is_rejected:
- ids.append( tool.id )
- elif state == trans.model.Tool.states.ARCHIVED:
- for tool in get_tools( trans ):
- if tool.is_archived:
- ids.append( tool.id )
- return ids
1
0
galaxy-dist commit 00672de09861: Bug fix for calculating viewport when displaying interval files at Ensembl browser.
by commits-noreply@bitbucket.org 30 Jul '10
by commits-noreply@bitbucket.org 30 Jul '10
30 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Dan Blankenberg <dan(a)bx.psu.edu>
# Date 1280343810 14400
# Node ID 00672de098618096b9a11f7ad4983eb1c4faa32a
# Parent 453e4edfab90db13efd08af64eeb3e7c64c22880
Bug fix for calculating viewport when displaying interval files at Ensembl browser.
--- a/display_applications/ensembl/ensembl_interval_as_bed.xml
+++ b/display_applications/ensembl/ensembl_interval_as_bed.xml
@@ -31,7 +31,7 @@
#if $chrom.startswith( 'chr' ):
#set $chrom = $chrom[3:]
#end if
-${chrom}:${start + 1}-${end}
+${chrom}:${int( start ) + 1}-${end}
#else:
##default view is of '1'
1
@@ -71,7 +71,7 @@ 1
#if $chrom.startswith( 'chr' ):
#set $chrom = $chrom[3:]
#end if
- &chr=${chrom}&start=${start + 1}&end=${end}
+ &chr=${chrom}&start=${int( start ) + 1}&end=${end}
#end if
</param></dynamic_links>
1
0
galaxy-dist commit 453e4edfab90: Have check_galaxy.py script get twill from galaxy.eggs instead of directly from the (defunct) eggs/py-(version) directory
by commits-noreply@bitbucket.org 30 Jul '10
by commits-noreply@bitbucket.org 30 Jul '10
30 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Nate Coraor <nate(a)bx.psu.edu>
# Date 1280336236 14400
# Node ID 453e4edfab90db13efd08af64eeb3e7c64c22880
# Parent 04dd5e51cf19b8c85e824457b045cc6af945dd4e
Have check_galaxy.py script get twill from galaxy.eggs instead of directly from the (defunct) eggs/py-(version) directory
--- a/scripts/check_galaxy.py
+++ b/scripts/check_galaxy.py
@@ -90,9 +90,8 @@ except:
# find/import twill
lib_dir = os.path.join( scripts_dir, "..", "lib" )
-eggs_dir = os.path.join( scripts_dir, "..", "eggs", "py%s-noplatform" %sys.version[:3] )
sys.path.append( lib_dir )
-sys.path.append( eggs_dir )
+from galaxy import eggs
import pkg_resources
pkg_resources.require( "twill" )
import twill
1
0
galaxy-dist commit 04dd5e51cf19: Display rerun button for history items when dataset is empty.
by commits-noreply@bitbucket.org 30 Jul '10
by commits-noreply@bitbucket.org 30 Jul '10
30 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Dan Blankenberg <dan(a)bx.psu.edu>
# Date 1280327687 14400
# Node ID 04dd5e51cf19b8c85e824457b045cc6af945dd4e
# Parent 5401c7d2e13bbd214e1a424e71c4fbcbac7cf919
Display rerun button for history items when dataset is empty.
--- a/templates/root/history_common.mako
+++ b/templates/root/history_common.mako
@@ -158,6 +158,8 @@
<a target="${link_app.url.get( 'target_frame', '_blank' )}" href="${link_app.get_display_url( data, trans )}">${_(link_app.name)}</a>
%endfor
%endfor
+ %elif for_editing:
+ <a href="${h.url_for( controller='tool_runner', action='rerun', id=data.id )}" target="galaxy_main" title="Run this job again" class="icon-button arrow-circle tooltip"></a>
%endif
</div>
1
0