galaxy-commits
Threads by month
- ----- 2025 -----
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- 15302 discussions

galaxy-dist commit b841989e2662: Move trackster.css to common stylesheet folder, fix small error in color
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Kanwei Li <kanwei(a)gmail.com>
# Date 1278095180 14400
# Node ID b841989e2662b204fcb4c73931ab3a2b08ef35a0
# Parent b68cbc002e8aabb1750d1c82607430124065736f
Move trackster.css to common stylesheet folder, fix small error in color
--- a/static/june_2007_style/make_style.py
+++ b/static/june_2007_style/make_style.py
@@ -20,13 +20,14 @@ def run( cmd ):
templates = [ ( "base.css.tmpl", "base.css" ),
( "panel_layout.css.tmpl", "panel_layout.css" ),
- ( "masthead.css.tmpl", "masthead.css"),
- ( "library.css.tmpl", "library.css"),
+ ( "masthead.css.tmpl", "masthead.css" ),
+ ( "library.css.tmpl", "library.css" ),
( "history.css.tmpl", "history.css" ),
( "tool_menu.css.tmpl", "tool_menu.css" ),
( "iphone.css.tmpl", "iphone.css" ),
( "reset.css.tmpl", "reset.css" ),
- ( "autocomplete_tagging.css.tmpl", "autocomplete_tagging.css") ]
+ ( "autocomplete_tagging.css.tmpl", "autocomplete_tagging.css" ),
+ ( "trackster.css.tmpl", "trackster.css" ) ]
# TODO: Are these images still being used? If not, clean this code up!
images = [
--- a/templates/display_base.mako
+++ b/templates/display_base.mako
@@ -71,8 +71,7 @@
<%def name="stylesheets()">
${parent.stylesheets()}
- ${h.css( "autocomplete_tagging", "embed_item" )}
- <link rel="stylesheet" type="text/css" href="${h.url_for('/static/trackster.css')}" />
+ ${h.css( "autocomplete_tagging", "embed_item", "trackster" )}
<style type="text/css">
.page-body {
Binary file static/june_2007_style/blue/fugue.png has changed
Binary file static/june_2007_style/blue/history-buttons.png has changed
--- /dev/null
+++ b/static/june_2007_style/trackster.css
@@ -0,0 +1,125 @@
+.viewport-container {
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+/*canvas{
+ border-left: 1px solid green;
+ border-right: 1px solid red; } /* debugging */
+.nav {
+ padding: 0 0;
+ color:#333;font-weight:bold;
+}
+
+.nav-controls {
+ text-align: center;
+ background:#cccccc;
+ background-image:url(style/panel_header_bg.png);
+ background-position:top center;
+ background-repeat:repeat-x;
+ padding: 2px 0;
+}
+.nav-controls input {
+ margin: 0 5px;
+}
+.nav-controls a {
+ padding: 0 0.4em;
+}
+
+.overview {
+ width: 100%;
+ margin: 0px;
+ color: white;
+ margin-top: -6px;
+ margin-bottom: -4px;
+}
+
+.overview-viewport {
+ position: relative;
+ height: 14px;
+/* border-top: solid #666 1px;*/
+/* border-bottom: solid #aaa 1px;*/
+ background: white;
+ border-top: solid gray 1px;
+ border-bottom: solid gray 1px;
+ margin: 5px 0;
+}
+.overview-box {
+ position: absolute;
+ margin-top: 0px;
+ height: 14px;
+ background: #ddd url(images/visualization/draggable_horizontal.png) center center no-repeat;
+ /*border-style: outset;*/
+}
+
+.viewport {
+/* overflow-x: hidden;*/
+ background-color: #fff;
+/* overflow: scroll;*/
+/* border-bottom: 2px solid black;*/
+}
+
+.viewport-canvas {
+ width: 100%;
+ height: 100px;
+}
+
+.yaxislabel {
+ color: #777;
+}
+/* Line track needs borders to show range */
+.line-track .track-content {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+}
+
+.track {
+ /* border-top: solid #DDDDDD 1px; */
+ /* border-bottom: solid #DDDDDD 1px; */
+ background: white;
+}
+
+.track-header {
+ text-align: center;
+ padding: 4px;
+ color: #666;
+}
+
+.track-content {
+ overflow: hidden;
+ text-align: center;
+}
+
+.track.error {
+ background-color: #ECB4AF;
+}
+.track.nodata {
+ background-color: #ddd;
+}
+
+.loading {
+ min-height: 100px;
+}
+
+.label-track {
+ /* font-weight: bold; */
+ /* font-size: 10px; */
+}
+.label-track .label {
+ border-left: solid #999 1px;
+ padding: 1px;
+ display: inline-block;
+}
+.right-float {
+ float: right;
+ margin-left: 5px;
+}
+
+.top-labeltrack {
+ border-bottom: solid #999 1px;
+}
+
+.nav-labeltrack {
+ border-top: solid #999 1px;
+ border-bottom: solid #999 1px;
+}
--- a/static/june_2007_style/blue/base.css
+++ b/static/june_2007_style/blue/base.css
@@ -73,7 +73,7 @@ span.toolParameterExpandableCollapsable{
ul.toolParameterExpandableCollapsable{list-style:none;}
ul.manage-table-actions{float:right;margin-top:-2.5em;}
ul.manage-table-actions li{display:block;float:left;margin-left:0.5em;}
-.state-color-new{border-color:#A86030;background:FFB030;}
+.state-color-new{border-color:#A86030;background:#FFB030;}
.state-color-upload{border-color:#6666AA;background:#CCCCFF;}
.state-color-waiting{border-color:#A86030;background:#E8C060;}
.state-color-queued{border-color:#888888;background:#EEEEEE;}
--- a/templates/tracks/browser.mako
+++ b/templates/tracks/browser.mako
@@ -12,8 +12,8 @@
<%def name="stylesheets()">
${parent.stylesheets()}
-${h.css( "history", "autocomplete_tagging" )}
-<link rel="stylesheet" type="text/css" href="${h.url_for('/static/trackster.css')}" />
+${h.css( "history", "autocomplete_tagging", "trackster" )}
+
<style type="text/css">
#center {
overflow: auto;
--- /dev/null
+++ b/static/june_2007_style/trackster.css.tmpl
@@ -0,0 +1,125 @@
+.viewport-container {
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+/*canvas{
+ border-left: 1px solid green;
+ border-right: 1px solid red; } /* debugging */
+.nav {
+ padding: 0 0;
+ color:#333;font-weight:bold;
+}
+
+.nav-controls {
+ text-align: center;
+ background:#cccccc;
+ background-image:url(panel_header_bg.png);
+ background-position:top center;
+ background-repeat:repeat-x;
+ padding: 2px 0;
+}
+.nav-controls input {
+ margin: 0 5px;
+}
+.nav-controls a {
+ padding: 0 0.4em;
+}
+
+.overview {
+ width: 100%;
+ margin: 0px;
+ color: white;
+ margin-top: -6px;
+ margin-bottom: -4px;
+}
+
+.overview-viewport {
+ position: relative;
+ height: 14px;
+/* border-top: solid #666 1px;*/
+/* border-bottom: solid #aaa 1px;*/
+ background: white;
+ border-top: solid gray 1px;
+ border-bottom: solid gray 1px;
+ margin: 5px 0;
+}
+.overview-box {
+ position: absolute;
+ margin-top: 0px;
+ height: 14px;
+ background: #ddd url(../images/visualization/draggable_horizontal.png) center center no-repeat;
+ /*border-style: outset;*/
+}
+
+.viewport {
+/* overflow-x: hidden;*/
+ background-color: #fff;
+/* overflow: scroll;*/
+/* border-bottom: 2px solid black;*/
+}
+
+.viewport-canvas {
+ width: 100%;
+ height: 100px;
+}
+
+.yaxislabel {
+ color: #777;
+}
+/* Line track needs borders to show range */
+.line-track .track-content {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+}
+
+.track {
+ /* border-top: solid #DDDDDD 1px; */
+ /* border-bottom: solid #DDDDDD 1px; */
+ background: white;
+}
+
+.track-header {
+ text-align: center;
+ padding: 4px;
+ color: #666;
+}
+
+.track-content {
+ overflow: hidden;
+ text-align: center;
+}
+
+.track.error {
+ background-color: #ECB4AF;
+}
+.track.nodata {
+ background-color: #ddd;
+}
+
+.loading {
+ min-height: 100px;
+}
+
+.label-track {
+ /* font-weight: bold; */
+ /* font-size: 10px; */
+}
+.label-track .label {
+ border-left: solid #999 1px;
+ padding: 1px;
+ display: inline-block;
+}
+.right-float {
+ float: right;
+ margin-left: 5px;
+}
+
+.top-labeltrack {
+ border-bottom: solid #999 1px;
+}
+
+.nav-labeltrack {
+ border-top: solid #999 1px;
+ border-bottom: solid #999 1px;
+}
Binary file static/june_2007_style/blue/history-states.png has changed
--- a/static/trackster.css
+++ /dev/null
@@ -1,125 +0,0 @@
-.viewport-container {
- overflow-x: hidden;
- overflow-y: auto;
-}
-
-/*canvas{
- border-left: 1px solid green;
- border-right: 1px solid red; } /* debugging */
-.nav {
- padding: 0 0;
- color:#333;font-weight:bold;
-}
-
-.nav-controls {
- text-align: center;
- background:#cccccc;
- background-image:url(style/panel_header_bg.png);
- background-position:top center;
- background-repeat:repeat-x;
- padding: 2px 0;
-}
-.nav-controls input {
- margin: 0 5px;
-}
-.nav-controls a {
- padding: 0 0.4em;
-}
-
-.overview {
- width: 100%;
- margin: 0px;
- color: white;
- margin-top: -6px;
- margin-bottom: -4px;
-}
-
-.overview-viewport {
- position: relative;
- height: 14px;
-/* border-top: solid #666 1px;*/
-/* border-bottom: solid #aaa 1px;*/
- background: white;
- border-top: solid gray 1px;
- border-bottom: solid gray 1px;
- margin: 5px 0;
-}
-.overview-box {
- position: absolute;
- margin-top: 0px;
- height: 14px;
- background: #ddd url(images/visualization/draggable_horizontal.png) center center no-repeat;
- /*border-style: outset;*/
-}
-
-.viewport {
-/* overflow-x: hidden;*/
- background-color: #fff;
-/* overflow: scroll;*/
-/* border-bottom: 2px solid black;*/
-}
-
-.viewport-canvas {
- width: 100%;
- height: 100px;
-}
-
-.yaxislabel {
- color: #777;
-}
-/* Line track needs borders to show range */
-.line-track .track-content {
- border-top: 1px solid #ddd;
- border-bottom: 1px solid #ddd;
-}
-
-.track {
- /* border-top: solid #DDDDDD 1px; */
- /* border-bottom: solid #DDDDDD 1px; */
- background: white;
-}
-
-.track-header {
- text-align: center;
- padding: 4px;
- color: #666;
-}
-
-.track-content {
- overflow: hidden;
- text-align: center;
-}
-
-.track.error {
- background-color: #ECB4AF;
-}
-.track.nodata {
- background-color: #ddd;
-}
-
-.loading {
- min-height: 100px;
-}
-
-.label-track {
- /* font-weight: bold; */
- /* font-size: 10px; */
-}
-.label-track .label {
- border-left: solid #999 1px;
- padding: 1px;
- display: inline-block;
-}
-.right-float {
- float: right;
- margin-left: 5px;
-}
-
-.top-labeltrack {
- border-bottom: solid #999 1px;
-}
-
-.nav-labeltrack {
- border-top: solid #999 1px;
- border-bottom: solid #999 1px;
-}
--- a/static/june_2007_style/blue_colors.ini
+++ b/static/june_2007_style/blue_colors.ini
@@ -35,7 +35,7 @@ footer_title_bg=#023858
footer_title_hatch=#000000
# History - these are actually Job states (used in the reports webapp), but the same styles are used for Tool states in the community webapp
history_new_border=#A86030
-history_new_bg=FFB030
+history_new_bg=#FFB030
history_upload_border=#990099
history_upload_bg=#D090D0
history_waiting_border=#A86030
1
0

galaxy-dist commit 660a65d7c929: Add a few missing options to the sample config.
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 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 1278099417 14400
# Node ID 660a65d7c9294284faaf6bc7b7c3a52d92d8c8d4
# Parent b841989e2662b204fcb4c73931ab3a2b08ef35a0
Add a few missing options to the sample config.
--- a/universe_wsgi.ini.sample
+++ b/universe_wsgi.ini.sample
@@ -75,6 +75,12 @@ paste.app_factory = galaxy.web.buildapp:
# [filter:proxy-prefix] section above.
#filter-with = proxy-prefix
+# If proxy-prefix is enabled and you're running more than one Galaxy instance
+# behind one hostname, you will want to set this to the same path as the prefix
+# in the filter above. This value becomes the "path" attribute set in the
+# cookie so the cookies from each instance will not clobber each other.
+#cookie_path = None
+
# -- Database
# By default, Galaxy uses a SQLite database at 'database/universe.sqlite'. You
@@ -93,6 +99,15 @@ paste.app_factory = galaxy.web.buildapp:
# you will want to set this to some positive value (7200 should work).
#database_engine_option_pool_recycle = -1
+# If large database query results are causing memory or response time issues in
+# the Galaxy process, leave the result on the server instead. This option is
+# only available for PostgreSQL and is highly recommended.
+#database_engine_option_server_side_cursors = False
+
+# Create only one connection to the database per thread, to reduce the
+# connection overhead. Recommended when not using SQLite:
+#database_engine_option_strategy = threadlocal
+
# -- Files and directories
# Dataset files are stored in this directory.
1
0

galaxy-dist commit b68cbc002e8a: Remove txtseq and binseq from datatypes.
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 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 1278088028 14400
# Node ID b68cbc002e8aabb1750d1c82607430124065736f
# Parent fad6bc5478fa9a4ac4e9d368bcfae1651528deac
Remove txtseq and binseq from datatypes.
--- a/lib/galaxy/datatypes/binary.py
+++ b/lib/galaxy/datatypes/binary.py
@@ -141,28 +141,6 @@ class Bam( Binary ):
def get_track_type( self ):
return "ReadTrack", {"data": "bai", "index": "summary_tree"}
-class Binseq( Binary ):
- """Class describing a zip archive of binary sequence files"""
- file_ext = "binseq.zip"
-
- def set_peek( self, dataset, is_multi_byte=False ):
- if not dataset.dataset.purged:
- zip_file = zipfile.ZipFile( dataset.file_name, "r" )
- num_files = len( zip_file.namelist() )
- dataset.peek = "Archive of %s binary sequence files" % ( str( num_files ) )
- dataset.blurb = data.nice_size( dataset.get_size() )
- else:
- dataset.peek = 'file does not exist'
- dataset.blurb = 'file purged from disk'
- def display_peek( self, dataset ):
- try:
- return dataset.peek
- except:
- return "Binary sequence file archive (%s)" % ( data.nice_size( dataset.get_size() ) )
- def get_mime( self ):
- """Returns the mime type of the datatype"""
- return 'application/zip'
-
class Scf( Binary ):
"""Class describing an scf binary sequence file"""
file_ext = "scf"
--- a/lib/galaxy/datatypes/data.py
+++ b/lib/galaxy/datatypes/data.py
@@ -414,27 +414,6 @@ class Text( Data ):
dataset.peek = 'file does not exist'
dataset.blurb = 'file purged from disk'
-class Txtseq( Data ):
- """Class describing a zip archive of text sequence files"""
- file_ext = "txtseq.zip"
- def set_peek( self, dataset, is_multi_byte=False ):
- if not dataset.dataset.purged:
- zip_file = zipfile.ZipFile( dataset.file_name, "r" )
- num_files = len( zip_file.namelist() )
- dataset.peek = "Archive of %s text sequence files" % ( str( num_files ) )
- dataset.blurb = data.nice_size( dataset.get_size() )
- else:
- dataset.peek = 'file does not exist'
- dataset.blurb = 'file purged from disk'
- def display_peek(self, dataset):
- try:
- return dataset.peek
- except:
- return "Text sequence file archive (%s)" % ( data.nice_size( dataset.get_size() ) )
- def get_mime(self):
- """Returns the mime type of the datatype"""
- return 'application/zip'
-
class Newick( Text ):
pass
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -150,7 +150,6 @@ class Registry( object ):
'axt' : sequence.Axt(),
'bam' : binary.Bam(),
'bed' : interval.Bed(),
- 'binseq.zip' : binary.Binseq(),
'blastxml' : xml.BlastXml(),
'coverage' : coverage.LastzCoverage(),
'customtrack' : interval.CustomTrack(),
@@ -176,7 +175,6 @@ class Registry( object ):
'tabular' : tabular.Tabular(),
'taxonomy' : tabular.Taxonomy(),
'txt' : data.Text(),
- 'txtseq.zip' : data.Txtseq(),
'wig' : interval.Wiggle()
}
self.mimetypes_by_extension = {
@@ -184,7 +182,6 @@ class Registry( object ):
'axt' : 'text/plain',
'bam' : 'application/octet-stream',
'bed' : 'text/plain',
- 'binseq.zip' : 'application/zip',
'blastxml' : 'text/plain',
'customtrack' : 'text/plain',
'csfasta' : 'text/plain',
@@ -208,7 +205,6 @@ class Registry( object ):
'tabular' : 'text/plain',
'taxonomy' : 'text/plain',
'txt' : 'text/plain',
- 'txtseq.zip' : 'application/zip',
'wig' : 'text/plain'
}
# Default values - the order in which we attempt to determine data types is critical
1
0

galaxy-dist commit e5c40bd9c179: Set a default (database/universe.sqlite) in manage_db.py in case the deprecated database_file is unset
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 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 1278099445 14400
# Node ID e5c40bd9c179059344645a9171b7172d74c64a25
# Parent 660a65d7c9294284faaf6bc7b7c3a52d92d8c8d4
Set a default (database/universe.sqlite) in manage_db.py in case the deprecated database_file is unset
--- a/scripts/manage_db.py
+++ b/scripts/manage_db.py
@@ -23,7 +23,7 @@ if cp.has_option( "app:main", "database_
elif cp.has_option( "app:main", "database_file" ):
db_url = "sqlite:///%s?isolation_level=IMMEDIATE" % cp.get( "app:main", "database_file" )
else:
- db_url = None
+ db_url = "sqlite:///./database/universe.sqlite?isolation_level=IMMEDIATE"
dialect_to_egg = {
"sqlite" : "pysqlite>=2",
1
0

galaxy-dist commit 8f2e44ed27f6: Community webapp features and fixes
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 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 1278081407 14400
# Node ID 8f2e44ed27f6a17586854c8e423d2b7b42ca0ae1
# Parent 692458d15b9211ba7e6569e07b350ef2c3c9757a
Community webapp features and fixes
Fixes:
1. Can no longer upload a tool with a different tool_id than the one being replaced when uploading a new version of an existing tool
2. Fixed a bug in the base Admin controller that threw an exception rather than displaying a user's tool when clicking in grid links
3. Fixed bugs in the ToolListGrid class of the common controller that resulted in incorrect query filters being applied
4. Fixed a bug in the ToolListGrid initial query that did not return rows if tools were not associated with categories
New features:
0. Added stringent security to the templates and methods for performing actions
1. When an admin rejects a tool, they are required to add a reason for the rejection.  The user that uploaded the tool can then make a correction and re-submit it for approval.
2. A new tool menu option is available for viewing the history of a tool.
3. Significantly improved GUI flow
4. An admin is now allowed to purge a tool, which removes records from the database, so should be used only when absolutely necessary.
5. Made the User Description field required when the tool is submitted for approval.
6. Tools can now be downloaded from the admin view as well as the tool view.
--- a/lib/galaxy/webapps/community/security/__init__.py
+++ b/lib/galaxy/webapps/community/security/__init__.py
@@ -6,6 +6,7 @@ from datetime import datetime, timedelta
from galaxy.util.bunch import Bunch
from galaxy.util import listify
from galaxy.model.orm import *
+from galaxy.webapps.community.controllers.common import get_versions
log = logging.getLogger(__name__)
@@ -163,16 +164,83 @@ class CommunityRBACAgent( RBACAgent ):
self.sa_session.refresh( tool )
for category in categories:
self.associate_components( tool=tool, category=category )
- def can_edit_item( self, user, item ):
- # We currently assume the current user can edit the item if they are the owner (i.e., they
- # uploaded the item), and the item is in a NEW state.
- return user and user==item.user and item.is_new()
- def can_upload_new_version( self, user, item, versions ):
+ def can_approve_or_reject( self, user, user_is_admin, cntrller, item ):
+ # The current user can approve or reject the item if the user
+ # is an admin, and the item's state is WAITING.
+ return user and user_is_admin and cntrller=='admin' and item.is_waiting()
+ def can_delete( self, user, user_is_admin, cntrller, item ):
+ # The current user can delete the item if they are an admin or if they uploaded the
+ # item and in either case the item's state is not DELETED.
+ if user and user_is_admin and cntrller == 'admin':
+ can_delete = not item.is_deleted()
+ elif cntrller in [ 'tool' ]:
+ can_delete = user==item.user and not item.is_deleted()
+ else:
+ can_delete = False
+ return can_delete
+ def can_download( self, user, user_is_admin, cntrller, item ):
+ # The current user can download the item if they are an admin or if the
+ # item's state is not one of: NEW, WAITING.
+ if user and user_is_admin and cntrller == 'admin':
+ return True
+ elif cntrller in [ 'tool' ]:
+ can_download = not( item.is_new() or item.is_waiting() )
+ else:
+ can_download = False
+ return can_download
+ def can_edit( self, user, user_is_admin, cntrller, item ):
+ # The current user can edit the item if they are an admin or if they uploaded the item
+ # and the item's state is one of: NEW, REJECTED.
+ if user and user_is_admin and cntrller == 'admin':
+ return True
+ if cntrller in [ 'tool' ]:
+ return user and user==item.user and ( item.is_new() or item.is_rejected() )
+ return False
+ def can_purge( self, user, user_is_admin, cntrller ):
+ # The current user can purge the item if they are an admin.
+ return user and user_is_admin and cntrller == 'admin'
+ def can_upload_new_version( self, user, item ):
+ # The current user can upload a new version as long as the item's state is not NEW or WAITING.
+ if not user:
+ return False
+ versions = get_versions( item )
state_ok = True
for version in versions:
if version.is_new() or version.is_waiting():
state_ok = False
- return user and user==item.user and state_ok
+ break
+ return state_ok
+ def can_view( self, user, user_is_admin, cntrller, item ):
+ # The current user can view the item if they are an admin or if they uploaded the item
+ # or if the item's state is APPROVED.
+ if user and user_is_admin and cntrller == 'admin':
+ return True
+ if cntrller in [ 'tool' ] and item.is_approved():
+ return True
+ return user and user==item.user
+ def get_all_action_permissions( self, user, user_is_admin, cntrller, item ):
+ """Get all permitted actions on item for the current user"""
+ can_edit = self.can_edit( cntrller, user, user_is_admin, item )
+ can_view = self.can_view( cntrller, user, user_is_admin, item )
+ can_upload_new_version = self.can_upload_new_version( user, item )
+ visible_versions = self.get_visible_versions( user, user_is_admin, cntrller, item )
+ can_approve_or_reject = self.can_approve_or_reject( user, user_is_admin, cntrller, item )
+ can_delete = self.can_delete( user, user_is_admin, cntrller, item )
+ return can_edit, can_view, can_upload_new_version, can_delete, visible_versions, can_approve_or_reject
+ def get_visible_versions( self, user, user_is_admin, cntrller, item ):
+ # All previous versions of item can be displayed if the current user is an admin
+ # or they uploaded item. Otherwise, only versions whose state is APPROVED or
+ # ARCHIVED will be displayed.
+ if user and user_is_admin and cntrller == 'admin':
+ visible_versions = get_versions( item )
+ elif cntrller in [ 'tool' ]:
+ visible_versions = []
+ for version in get_versions( item ):
+ if version.is_approved() or version.is_archived() or version.user == user:
+ visible_versions.append( version )
+ else:
+ visible_versions = []
+ return visible_versions
def get_permitted_actions( filter=None ):
'''Utility method to return a subset of RBACAgent's permitted actions'''
--- a/lib/galaxy/web/base/controller.py
+++ b/lib/galaxy/web/base/controller.py
@@ -1251,7 +1251,9 @@ class Admin( object ):
# sort filter instead of this class's.
kwargs[ 'user_id' ] = kwargs[ 'id' ]
kwargs[ 'sort' ] = 'name'
- return self.browse_tools( trans, **kwargs )
+ return trans.response.send_redirect( web.url_for( controller='admin',
+ action='browse_tools',
+ **kwargs ) )
# Render the list view
return self.user_list_grid( trans, **kwargs )
@web.expose
--- a/lib/galaxy/webapps/community/controllers/common.py
+++ b/lib/galaxy/webapps/community/controllers/common.py
@@ -22,14 +22,15 @@ class ToolListGrid( grids.Grid ):
return tool.description
class CategoryColumn( grids.TextColumn ):
def get_value( self, trans, grid, tool ):
+ rval = '<ul>'
if tool.categories:
- rval = '<ul>'
for tca in tool.categories:
rval += '<li><a href="browse_tools?operation=tools_by_category&id=%s&webapp=community">%s</a></li>' \
% ( trans.security.encode_id( tca.category.id ), tca.category.name )
- rval += '</ul>'
- return rval
- return 'not set'
+ else:
+ rval += '<li>not set</li>'
+ rval += '</ul>'
+ return rval
class ToolCategoryColumn( grids.GridColumn ):
def filter( self, trans, user, query, column_filter ):
"""Modify query to filter by category."""
@@ -55,9 +56,9 @@ class ToolListGrid( grids.Grid ):
columns = [
NameColumn( "Name",
key="name",
- link=( lambda item: dict( operation="View Tool", id=item.id, webapp="community" ) ),
+ link=( lambda item: dict( operation="view_tool", id=item.id, webapp="community" ) ),
model_class=model.Tool,
- attach_popup=True
+ attach_popup=False
),
VersionColumn( "Version",
key="version",
@@ -93,12 +94,7 @@ class ToolListGrid( grids.Grid ):
key="free-text-search",
visible=False,
filterable="standard" ) )
- operations = [
- grids.GridOperation( "Download tool",
- condition=( lambda item: not item.deleted ),
- allow_multiple=False,
- url_args=dict( controller="tool", action="download_tool", cntrller="tool", webapp="community" ) )
- ]
+ operations = []
standard_filters = []
default_filter = {}
num_rows_per_page = 50
@@ -108,8 +104,8 @@ class ToolListGrid( grids.Grid ):
return trans.sa_session.query( self.model_class ) \
.join( model.ToolEventAssociation.table ) \
.join( model.Event.table ) \
- .join( model.ToolCategoryAssociation.table ) \
- .join( model.Category.table )
+ .outerjoin( model.ToolCategoryAssociation.table ) \
+ .outerjoin( model.Category.table )
class CategoryListGrid( grids.Grid ):
class NameColumn( grids.TextColumn ):
@@ -171,6 +167,12 @@ class CommonController( BaseController )
message='Select a tool to edit',
status='error' ) )
tool = get_tool( trans, id )
+ can_edit = trans.app.security_agent.can_edit( trans.user, trans.user_is_admin(), cntrller, tool )
+ if not can_edit:
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='browse_tools',
+ message='You are not allowed to edit this tool',
+ status='error' ) )
if params.get( 'edit_tool_button', False ):
if params.get( 'in_categories', False ):
in_categories = [ trans.sa_session.query( trans.app.model.Category ).get( x ) for x in util.listify( params.in_categories ) ]
@@ -185,7 +187,7 @@ class CommonController( BaseController )
tool.user_description = ''
trans.sa_session.add( tool )
trans.sa_session.flush()
- message="Tool '%s' description and category associations have been saved" % tool.name
+ message = "Tool '%s' description and category associations have been saved" % tool.name
return trans.response.send_redirect( web.url_for( controller='common',
action='edit_tool',
cntrller=cntrller,
@@ -193,31 +195,33 @@ class CommonController( BaseController )
message=message,
status='done' ) )
elif params.get( 'approval_button', False ):
- if params.get( 'in_categories', False ):
- in_categories = [ trans.sa_session.query( trans.app.model.Category ).get( x ) for x in util.listify( params.in_categories ) ]
- trans.app.security_agent.set_entity_category_associations( tools=[ tool ], categories=in_categories )
- else:
- # There must not be any categories associated with the tool
- trans.app.security_agent.set_entity_category_associations( tools=[ tool ], categories=[] )
user_description = util.restore_text( params.get( 'user_description', '' ) )
if user_description:
tool.user_description = user_description
+ if params.get( 'in_categories', False ):
+ in_categories = [ trans.sa_session.query( trans.app.model.Category ).get( x ) for x in util.listify( params.in_categories ) ]
+ trans.app.security_agent.set_entity_category_associations( tools=[ tool ], categories=in_categories )
+ else:
+ # There must not be any categories associated with the tool
+ trans.app.security_agent.set_entity_category_associations( tools=[ tool ], categories=[] )
+ trans.sa_session.add( tool )
+ trans.sa_session.flush()
+ # Move the state from NEW to WAITING
+ event = trans.app.model.Event( state=trans.app.model.Tool.states.WAITING )
+ tea = trans.app.model.ToolEventAssociation( tool, event )
+ trans.sa_session.add_all( ( event, tea ) )
+ trans.sa_session.flush()
+ message = "Tool '%s' has been submitted for approval and can no longer be modified" % ( tool.name )
+ return trans.response.send_redirect( web.url_for( controller='common',
+ action='view_tool',
+ cntrller=cntrller,
+ id=id,
+ message=message,
+ status='done' ) )
else:
- tool.user_description = ''
- trans.sa_session.add( tool )
- trans.sa_session.flush()
- # Move the state from NEW to WAITING
- event = trans.app.model.Event( state=trans.app.model.Tool.states.WAITING )
- tea = trans.app.model.ToolEventAssociation( tool, event )
- trans.sa_session.add_all( ( event, tea ) )
- trans.sa_session.flush()
- message = "Tool '%s' has been submitted for approval and can no longer be modified" % ( tool.name )
- return trans.response.send_redirect( web.url_for( controller='common',
- action='view_tool',
- cntrller=cntrller,
- id=id,
- message=message,
- status='done' ) )
+ # The user_description field is required when submitting for approval
+ message = 'A user description is required prior to approval.'
+ status = 'error'
in_categories = []
out_categories = []
for category in get_categories( trans ):
@@ -225,12 +229,31 @@ class CommonController( BaseController )
in_categories.append( ( category.id, category.name ) )
else:
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
+ else:
+ reason_for_rejection = ''
+ can_approve_or_reject = trans.app.security_agent.can_approve_or_reject( trans.user, trans.user_is_admin(), cntrller, tool )
+ can_delete = trans.app.security_agent.can_delete( trans.user, trans.user_is_admin(), cntrller, tool )
+ can_download = trans.app.security_agent.can_download( trans.user, trans.user_is_admin(), cntrller, tool )
+ can_purge = trans.app.security_agent.can_purge( trans.user, trans.user_is_admin(), cntrller )
+ can_upload_new_version = trans.app.security_agent.can_upload_new_version( trans.user, tool )
+ can_view = trans.app.security_agent.can_view( trans.user, trans.user_is_admin(), cntrller, tool )
return trans.fill_template( '/webapps/community/tool/edit_tool.mako',
cntrller=cntrller,
tool=tool,
id=id,
in_categories=in_categories,
out_categories=out_categories,
+ can_approve_or_reject=can_approve_or_reject,
+ can_delete=can_delete,
+ can_download=can_download,
+ can_edit=can_edit,
+ can_purge=can_purge,
+ can_upload_new_version=can_upload_new_version,
+ can_view=can_view,
+ reason_for_rejection=reason_for_rejection,
message=message,
status=status )
@web.expose
@@ -245,15 +268,40 @@ class CommonController( BaseController )
message='Select a tool to view',
status='error' ) )
tool = get_tool( trans, id )
+ can_view = trans.app.security_agent.can_view( trans.user, trans.user_is_admin(), cntrller, tool )
+ if not can_view:
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='browse_tools',
+ message='You are not allowed to view this tool',
+ status='error' ) )
+ can_approve_or_reject = trans.app.security_agent.can_approve_or_reject( trans.user, trans.user_is_admin(), cntrller, tool )
+ can_delete = trans.app.security_agent.can_delete( trans.user, trans.user_is_admin(), cntrller, tool )
+ can_download = trans.app.security_agent.can_download( trans.user, trans.user_is_admin(), cntrller, tool )
+ can_edit = trans.app.security_agent.can_edit( trans.user, trans.user_is_admin(), cntrller, tool )
+ can_purge = trans.app.security_agent.can_purge( trans.user, trans.user_is_admin(), cntrller )
+ can_upload_new_version = trans.app.security_agent.can_upload_new_version( trans.user, tool )
+ visible_versions = trans.app.security_agent.get_visible_versions( trans.user, trans.user_is_admin(), cntrller, tool )
categories = [ tca.category for tca in tool.categories ]
tool_file_contents = tarfile.open( tool.file_name, 'r' ).getnames()
- versions = get_versions( trans, tool )
+ if tool.is_rejected():
+ # Include the comments regarding the reason for rejection
+ reason_for_rejection = get_most_recent_event( tool ).comment
+ else:
+ reason_for_rejection = ''
return trans.fill_template( '/webapps/community/tool/view_tool.mako',
tool=tool,
tool_file_contents=tool_file_contents,
- versions=versions,
categories=categories,
cntrller=cntrller,
+ can_approve_or_reject=can_approve_or_reject,
+ can_delete=can_delete,
+ can_download=can_download,
+ can_edit=can_edit,
+ can_purge=can_purge,
+ can_upload_new_version=can_upload_new_version,
+ can_view=can_view,
+ visible_versions=visible_versions,
+ reason_for_rejection=reason_for_rejection,
message=message,
status=status )
@web.expose
@@ -267,6 +315,11 @@ class CommonController( BaseController )
status='error'
else:
tool = get_tool( trans, id )
+ if not trans.app.security_agent.can_delete( trans.user, trans.user_is_admin(), cntrller, tool ):
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='browse_tools',
+ message='You are not allowed to delete this tool',
+ status='error' ) )
# Create a new event
event = trans.model.Event( state=trans.model.Tool.states.DELETED )
# Flush so we can get an event id
@@ -279,13 +332,32 @@ class CommonController( BaseController )
trans.sa_session.add_all( ( tool, tea ) )
trans.sa_session.flush()
# TODO: What if the tool has versions, should they all be deleted?
- message = "Tool '%s' has been marked deleted"
+ message = "Tool '%s' has been marked deleted" % tool.name
status = 'done'
return trans.response.send_redirect( web.url_for( controller=cntrller,
action='browse_tools',
message=message,
status=status ) )
@web.expose
+ def download_tool( self, trans, cntrller, **kwd ):
+ params = util.Params( kwd )
+ id = params.get( 'id', None )
+ if not id:
+ return trans.response.send_redirect( web.url_for( controller='tool',
+ action='browse_tools',
+ message='Select a tool to download',
+ status='error' ) )
+ tool = get_tool( trans, id )
+ if not trans.app.security_agent.can_download( trans.user, trans.user_is_admin(), cntrller, tool ):
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='browse_tools',
+ message='You are not allowed to download this tool',
+ status='error' ) )
+ trans.response.set_content_type( tool.mimetype )
+ trans.response.headers['Content-Length'] = int( os.stat( tool.file_name ).st_size )
+ trans.response.headers['Content-Disposition'] = 'attachment; filename=%s' % tool.download_file_name
+ return open( tool.file_name )
+ @web.expose
def upload_new_tool_version( self, trans, cntrller, **kwd ):
params = util.Params( kwd )
message = util.restore_text( params.get( 'message', '' ) )
@@ -294,27 +366,69 @@ class CommonController( BaseController )
if not id:
return trans.response.send_redirect( web.url_for( controller=cntrller,
action='browse_tools',
- message='Select a tool to to upload a new version',
+ message='Select a tool to upload a new version',
status='error' ) )
tool = get_tool( trans, id )
+ if not trans.app.security_agent.can_upload_new_version( trans.user, tool ):
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='browse_tools',
+ message='You are not allowed to upload a new version of this tool',
+ status='error' ) )
return trans.response.send_redirect( web.url_for( controller='upload',
action='upload',
message=message,
status=status,
replace_id=id ) )
+ @web.expose
+ @web.require_login( "view tool history" )
+ def view_tool_history( self, trans, cntrller, **kwd ):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ id = params.get( 'id', None )
+ if not id:
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='browse_tools',
+ message='Select a tool to view events',
+ status='error' ) )
+ tool = get_tool( trans, id )
+ can_view = trans.app.security_agent.can_view( trans.user, trans.user_is_admin(), cntrller, tool )
+ if not can_view:
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='browse_tools',
+ message="You are not allowed to view this tool's history",
+ status='error' ) )
+ can_approve_or_reject = trans.app.security_agent.can_approve_or_reject( trans.user, trans.user_is_admin(), cntrller, tool )
+ can_edit = trans.app.security_agent.can_edit( trans.user, trans.user_is_admin(), cntrller, tool )
+ can_delete = trans.app.security_agent.can_delete( trans.user, trans.user_is_admin(), cntrller, tool )
+ can_download = trans.app.security_agent.can_download( trans.user, trans.user_is_admin(), cntrller, tool )
+ events = [ tea.event for tea in tool.events ]
+ events = [ ( event.state, time_ago( event.update_time ), event.comment ) for event in events ]
+ return trans.fill_template( '/webapps/community/common/view_tool_history.mako',
+ cntrller=cntrller,
+ events=events,
+ tool=tool,
+ can_approve_or_reject=can_approve_or_reject,
+ can_edit=can_edit,
+ can_delete=can_delete,
+ can_download=can_download,
+ can_view=can_view,
+ message=message,
+ status=status )
## ---- Utility methods -------------------------------------------------------
-def get_versions( trans, tool ):
- versions = [tool]
- this_tool = tool
- while tool.newer_version:
- versions.insert( 0, tool.newer_version )
- tool = tool.newer_version
- tool = this_tool
- while tool.older_version:
- versions.append( tool.older_version[0] )
- tool = tool.older_version[0]
+def get_versions( item ):
+ """Get all versions of 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]
return versions
def get_categories( trans ):
"""Get all categories from the database"""
@@ -322,15 +436,27 @@ def get_categories( trans ):
.filter( trans.model.Category.table.c.deleted==False ) \
.order_by( trans.model.Category.table.c.name ).all()
def get_category( trans, id ):
+ """Get a category from the database"""
return trans.sa_session.query( trans.model.Category ).get( trans.security.decode_id( 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 ):
- # Return only the latest version of each tool
+ """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_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 ) )
--- /dev/null
+++ b/templates/webapps/community/admin/reject_tool.mako
@@ -0,0 +1,70 @@
+<%namespace file="/message.mako" import="render_msg" />
+
+<%!
+ def inherit(context):
+ if context.get('use_panels'):
+ return '/webapps/community/base_panels.mako'
+ else:
+ return '/base.mako'
+%>
+<%inherit file="${inherit(context)}"/>
+
+<%def name="title()">Reject Tool</%def>
+
+<h2>Reject Tool</h2>
+
+<ul class="manage-table-actions">
+ <li><a class="action-button" id="tool-${tool.id}-popup" class="menubutton">Tool Actions</a></li>
+ <div popupmenu="tool-${tool.id}-popup">
+ <a class="action-button" href="${h.url_for( controller='common', action='view_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">View tool</a>
+ <a class="action-button" href="${h.url_for( controller='common', action='view_tool_history', id=trans.security.encode_id( tool.id ), cntrller=cntrller )}">Tool history</a>
+ <a class="action-button" href="${h.url_for( controller='common', action='download_tool', id=trans.security.encode_id( tool.id ), cntrller=cntrller )}">Download tool</a>
+ </div>
+</ul>
+
+%if message:
+ ${render_msg( message, status )}
+%endif
+
+<div class="toolForm">
+ <div class="toolFormTitle">${tool.name}</div>
+ <form name="reject_tool" action="${h.url_for( controller='admin', action='reject_tool', id=trans.security.encode_id( tool.id ) )}" method="post" >
+ <div class="form-row">
+ <label>Tool id:</label>
+ ${tool.tool_id}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>Version:</label>
+ ${tool.version}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>Description:</label>
+ ${tool.description}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>User description:</label>
+ ${tool.user_description}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>Uploaded by:</label>
+ ${tool.user.username}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>Reason for rejection</label>
+ <textarea name="comments" rows="5" cols="40"></textarea>
+ <div class="toolParamHelp" style="clear: both;">
+ Required
+ </div>
+ </div>
+ <div class="form-row">
+ <input type="submit" name="reject_button" value="Reject"/>
+ <input type="submit" name="cancel_reject_button" value="Cancel"/>
+ </div>
+ </form>
+ </div>
+</div>
--- a/lib/galaxy/webapps/community/controllers/upload.py
+++ b/lib/galaxy/webapps/community/controllers/upload.py
@@ -33,7 +33,6 @@ class UploadController( BaseController )
message='No categories have been configured in this instance of the Galaxy Community. An administrator needs to create some via the Administrator control panel before anything can be uploaded',
status='error' ) )
if params.get( 'upload_button', False ):
-
url_paste = params.get( 'url', '' ).strip()
file_data = params.get( 'file_data', '' )
if file_data == '' and url_paste == '':
@@ -66,19 +65,24 @@ class UploadController( BaseController )
obj = datatype.create_model_object( meta )
trans.sa_session.add( obj )
if isinstance( obj, trans.app.model.Tool ):
- existing = trans.sa_session.query( trans.app.model.Tool ).filter_by( tool_id = meta.id ).first()
- if existing and replace_id is None:
+ existing = trans.sa_session.query( trans.app.model.Tool ) \
+ .filter_by( tool_id = meta.id ) \
+ .first()
+ if replace_id:
+ replace_version = trans.sa_session.query( trans.app.model.Tool ).get( trans.security.decode_id( replace_id ) )
+ if existing and not replace_id:
raise UploadError( 'A tool with the same ID already exists. If you are trying to update this tool to a new version, please use the upload form on the "Edit Tool" page. Otherwise, please choose a new ID.' )
- elif existing:
- replace_version = trans.sa_session.query( trans.app.model.Tool ).get( trans.security.decode_id( replace_id ) )
+ elif replace_id and not existing:
+ raise UploadError( 'Tool ids must match when uploading a new version of a tool. The new tool id does not match the old tool id (%s). Check the tool XML files.' % str( replace_version.tool_id ) )
+ elif existing and replace_id:
if replace_version.newer_version:
# If the user has picked an old version, switch to the newest version
- replace_version = get_versions( trans, replace_version )[0]
+ replace_version = get_versions( replace_version )[0]
if replace_version.tool_id != meta.id:
- raise UploadError( 'The new tool id (%s) does not match the old tool id (%s). Check the tool XML file' % ( meta.id, replace_version.tool_id ) )
- for old_version in get_versions( trans, replace_version ):
+ raise UploadError( 'Tool ids must match when uploading a new version of a tool. The new tool id (%s) does not match the old tool id (%s). Check the tool XML files.' % ( str( meta.id ), str( replace_version.tool_id ) ) )
+ for old_version in get_versions( replace_version ):
if old_version.version == meta.version:
- raise UploadError( 'The new version (%s) matches an old version. Check your version in the tool XML file' % meta.version )
+ raise UploadError( 'The new version (%s) matches an old version. Check your version in the tool XML file.' % str( meta.version ) )
if old_version.is_new():
raise UploadError( 'There is an existing version of this tool which has not yet been submitted for approval, so either <a href="%s">submit or delete it</a> before uploading a new version.' % url_for( controller='common',
action='view_tool',
@@ -86,7 +90,7 @@ class UploadController( BaseController )
id=trans.security.encode_id( old_version.id ) ) )
if old_version.is_waiting():
raise UploadError( 'There is an existing version of this tool which is waiting for administrative approval, so contact an administrator for help.' )
- # Defer setting the id since the newer version id doesn't exist until the new Tool object is flushed
+ # Defer setting the id since the newer version id doesn't exist until the new Tool object is flushed
if category_ids:
for category_id in category_ids:
category = trans.app.model.Category.get( trans.security.decode_id( category_id ) )
@@ -123,7 +127,7 @@ class UploadController( BaseController )
elif replace_id is not None:
replace_version = trans.sa_session.query( trans.app.model.Tool ).get( int( trans.app.security.decode_id( replace_id ) ) )
old_version = None
- for old_version in get_versions( trans, replace_version ):
+ for old_version in get_versions( replace_version ):
if old_version.is_new():
message = 'There is an existing version of this tool which has not been submitted for approval, so either submit or delete it before uploading a new version.'
break
--- a/templates/webapps/community/category/create_category.mako
+++ b/templates/webapps/community/category/create_category.mako
@@ -19,13 +19,12 @@
<div class="toolFormBody"><form name="create_category_form" id="create_category_form" action="${h.url_for( action='create_category' )}" method="post" ><div class="form-row">
- <input name="webapp" type="hidden" value="${webapp}" size=40"/><label>Name:</label>
- <input name="name" type="textfield" value="" size=40"/>
+ <input name="name" type="textfield" value="${name}" size=40"/></div><div class="form-row"><label>Description:</label>
- <input name="description" type="textfield" value="" size=40"/>
+ <input name="description" type="textfield" value="${description}" size=40"/></div><div class="form-row"><input type="submit" name="create_category_button" value="Save"/>
--- a/templates/webapps/community/tool/edit_tool.mako
+++ b/templates/webapps/community/tool/edit_tool.mako
@@ -1,6 +1,10 @@
<%inherit file="/base.mako"/><%namespace file="/message.mako" import="render_msg" />
+<%
+ from galaxy.web.framework.helpers import time_ago
+%>
+
<%!
def inherit(context):
if context.get('use_panels'):
@@ -17,7 +21,7 @@
$("input:text:first").focus();
})
function confirmSubmit() {
- if ( confirm( "After you have submitted your tool to be published, you will no longer be able to modify it. Click OK to submit it." ) ) {
+ if ( confirm( "Make sure you have filled in the User Description field. After you have submitted your tool to be published, you will no longer be able to modify it. Click OK to submit it." ) ) {
return true;
} else {
return false;
@@ -52,28 +56,57 @@
<%def name="title()">Edit Tool</%def>
-<h2>Edit Tool: ${tool.name} <em>${tool.description}</em></h2>
+<h2>Edit Tool</h2>
+
+${tool.get_state_message()}
+<p/>
+
+<ul class="manage-table-actions">
+ %if can_approve_or_reject:
+ <li><a class="action-button" href="${h.url_for( controller='admin', action='set_tool_state', state=trans.model.Tool.states.APPROVED, id=trans.security.encode_id( tool.id ), cntrller=cntrller )}">Approve</a></li>
+ <li><a class="action-button" href="${h.url_for( controller='admin', action='set_tool_state', state=trans.model.Tool.states.REJECTED, id=trans.security.encode_id( tool.id ), cntrller=cntrller )}">Reject</a></li>
+ %endif
+ <li><a class="action-button" id="tool-${tool.id}-popup" class="menubutton">Tool Actions</a></li>
+ <div popupmenu="tool-${tool.id}-popup">
+ %if can_view:
+ <a class="action-button" href="${h.url_for( controller='common', action='view_tool_history', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Tool history</a>
+ <a class="action-button" href="${h.url_for( controller='common', action='view_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">View tool</a>
+ %endif
+ %if can_download:
+ <a class="action-button" href="${h.url_for( controller='common', action='download_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Download tool</a>
+ %endif
+ %if can_delete:
+ <a class="action-button" href="${h.url_for( controller='common', action='delete_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}" confirm="Are you sure you want to delete this tool?">Delete tool</a>
+ %endif
+ %if can_upload_new_version:
+ <a class="action-button" href="${h.url_for( controller='common', action='upload_new_tool_version', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Upload a new version</a>
+ %endif
+ %if can_purge:
+ <li><a class="action-button" href="${h.url_for( controller='admin', action='purge_tool', id=trans.security.encode_id( tool.id ), cntrller=cntrller )}" confirm="Purging removes records from the database, are you sure you want to purge this tool?">Purge tool</a></li>
+ %endif
+ </div>
+</ul>
%if message:
${render_msg( message, status )}
%endif
-%if cntrller == 'admin' or trans.user == tool.user:
+%if can_edit:
<form id="edit_tool" name="edit_tool" action="${h.url_for( controller='common', action='edit_tool' )}" method="post">
+ %if tool.is_rejected():
+ <div class="toolForm">
+ <div class="toolFormTitle">Reason for rejection</div>
+ <div class="toolFormBody">
+ <div class="form-row">
+ ${reason_for_rejection}
+ <div style="clear: both"></div>
+ </div>
+ </div>
+ </div>
+ <p/>
+ %endif
<div class="toolForm">
- <div class="toolFormTitle">${tool.name}
- %if not tool.deleted:
- <a id="tool-${tool.id}-popup" class="popup-arrow" style="display: none;">▼</a>
- <div popupmenu="tool-${tool.id}-popup">
- <a class="action-button" href="${h.url_for( controller='common', action='view_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">View information</a>
- <a class="action-button" href="${h.url_for( controller='tool', action='download_tool', id=trans.app.security.encode_id( tool.id ) )}">Download tool</a>
- <a class="action-button" href="${h.url_for( controller='common', action='delete_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Delete tool</a>
- %if not tool.is_new() and not tool.is_waiting():
- <a class="action-button" href="${h.url_for( controller='common', action='upload_new_tool_version', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Upload a new version</a>
- %endif
- </div>
- %endif
- </div>
+ <div class="toolFormTitle">${tool.name}</div><div class="toolFormBody"><input type="hidden" name="id" value="${trans.app.security.encode_id( tool.id )}"/><input type="hidden" name="cntrller" value="${cntrller}"/>
@@ -89,11 +122,27 @@
</div><div class="form-row"><label>Description:</label>
+ ${tool.description}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>User Description:</label>
%if tool.user_description:
<div class="form-row-input"><pre><textarea name="user_description" rows="5" cols="35">${tool.user_description}</textarea></pre></div>
%else:
<div class="form-row-input"><textarea name="user_description" rows="5" cols="35"></textarea></div>
%endif
+ <div class="toolParamHelp" style="clear: both;">Required when submitting for approval</div>
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>Uploaded by:</label>
+ ${tool.user.username}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>Date uploaded:</label>
+ ${time_ago( tool.create_time )}
<div style="clear: both"></div></div></div>
@@ -120,7 +169,7 @@
</div></div><p/>
- %if tool.is_new():
+ %if tool.is_new() or tool.is_rejected():
<div class="toolForm"><div class="toolFormTitle">Get approval for publishing</div><div class="toolFormBody">
--- /dev/null
+++ b/templates/webapps/community/common/view_tool_history.mako
@@ -0,0 +1,92 @@
+<%namespace file="/message.mako" import="render_msg" />
+
+<%
+ if cntrller in [ 'tool' ] and can_edit:
+ menu_label = 'Edit information or submit for approval'
+ else:
+ menu_label = 'Edit information'
+%>
+
+<%!
+ def inherit(context):
+ if context.get('use_panels'):
+ return '/webapps/community/base_panels.mako'
+ else:
+ return '/base.mako'
+%>
+<%inherit file="${inherit(context)}"/>
+
+<h2>Tool history</h2>
+<ul class="manage-table-actions">
+ %if can_approve_or_reject:
+ <li><a class="action-button" href="${h.url_for( controller='admin', action='set_tool_state', state=trans.model.Tool.states.APPROVED, id=trans.security.encode_id( tool.id ), cntrller=cntrller )}">Approve</a></li>
+ <li><a class="action-button" href="${h.url_for( controller='admin', action='set_tool_state', state=trans.model.Tool.states.REJECTED, id=trans.security.encode_id( tool.id ), cntrller=cntrller )}">Reject</a></li>
+ %endif
+ <li><a class="action-button" id="tool-${tool.id}-popup" class="menubutton">Tool Actions</a></li>
+ <div popupmenu="tool-${tool.id}-popup">
+ %if can_edit:
+ <a class="action-button" href="${h.url_for( controller='common', action='edit_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">${menu_label}</a>
+ %endif
+ %if can_view:
+ <a class="action-button" href="${h.url_for( controller='common', action='view_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">View tool</a>
+ %endif
+ %if can_delete:
+ <a class="action-button" href="${h.url_for( controller='common', action='delete_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}" confirm="Are you sure you want to delete this tool?">Delete tool</a>
+ %endif
+ %if can_download:
+ <a class="action-button" href="${h.url_for( controller='common', action='download_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Download tool</a>
+ %endif
+ </div>
+</ul>
+
+%if message:
+ ${render_msg( message, status )}
+%endif
+
+<div class="toolForm">
+ <div class="toolFormTitle">${tool.name}</div>
+ <div class="form-row">
+ <label>Tool id:</label>
+ ${tool.tool_id}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>Version:</label>
+ ${tool.version}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>Description:</label>
+ ${tool.description}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>User description:</label>
+ ${tool.user_description}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>Uploaded by:</label>
+ ${tool.user.username}
+ <div style="clear: both"></div>
+ </div>
+</div>
+<p/>
+<table class="grid">
+ <thead>
+ <tr>
+ <th>State</th>
+ <th>Last Update</th>
+ <th>Comments</th>
+ </tr>
+ </thead>
+ <tbody>
+ %for state, updated, comments in events:
+ <tr class="libraryRow libraryOrFolderRow" id="libraryRow">
+ <td><b><a>${state}</a></b></td>
+ <td><a>${updated}</a></td>
+ <td><a>${comments}</a></td>
+ </tr>
+ %endfor
+ </tbody>
+</table>
--- a/lib/galaxy/webapps/community/model/__init__.py
+++ b/lib/galaxy/webapps/community/model/__init__.py
@@ -164,6 +164,21 @@ class Tool( object ):
return self.state() == self.states.REJECTED
def is_archived( self ):
return self.state() == self.states.ARCHIVED
+ def get_state_message( self ):
+ if self.is_new():
+ return '<font color="red"><b><i>This is an unsubmitted version of this tool</i></b></font>'
+ if self.is_error():
+ return '<font color="red"><b><i>This tool is in an error state</i></b></font>'
+ if self.is_deleted():
+ return '<font color="red"><b><i>This is a deleted version of this tool</i></b></font>'
+ if self.is_waiting():
+ return '<font color="red"><b><i>This version of this tool is awaiting administrative approval</i></b></font>'
+ if self.is_approved():
+ return '<b><i>This is the latest approved version of this tool</i></b>'
+ if self.is_rejected():
+ return '<font color="red"><b><i>This version of this tool has been rejected by an administrator</i></b></font>'
+ if self.is_archived():
+ return '<font color="red"><b><i>This is an archived version of this tool</i></b></font>'
@property
def extension( self ):
# if instantiated via a query, this unmapped property won't exist
@@ -240,7 +255,6 @@ class ToolAnnotationAssociation( object
pass
## ---- Utility methods -------------------------------------------------------
-
def sort_by_attr( seq, attr ):
"""
Sort the sequence of objects by object's attribute
@@ -256,7 +270,6 @@ def sort_by_attr( seq, attr ):
intermed = map( None, map( getattr, seq, ( attr, ) * len( seq ) ), xrange( len( seq ) ), seq )
intermed.sort()
return map( operator.getitem, intermed, ( -1, ) * len( intermed ) )
-
def directory_hash_id( id ):
s = str( id )
l = len( s )
@@ -269,5 +282,3 @@ def directory_hash_id( id ):
padded = padded[:-3]
# Break into chunks of three
return [ padded[i*3:(i+1)*3] for i in range( len( padded ) // 3 ) ]
-
-
--- a/templates/webapps/community/tool/view_tool.mako
+++ b/templates/webapps/community/tool/view_tool.mako
@@ -3,19 +3,11 @@
<%
from galaxy.web.framework.helpers import time_ago
from urllib import quote_plus
-
- menu_label = 'Edit information'
-
- if cntrller in [ 'tool' ]:
- can_edit = trans.app.security_agent.can_edit_item( trans.user, tool )
- if can_edit:
- menu_label = 'Edit information or submit for approval'
- can_upload_new_version = trans.app.security_agent.can_upload_new_version( trans.user, tool, versions )
- visible_versions = []
- for version in versions:
- if version.is_approved() or version.is_archived() or version.user == trans.user:
- visible_versions.append( version )
+ if cntrller in [ 'tool' ] and can_edit:
+ menu_label = 'Edit information or submit for approval'
+ else:
+ menu_label = 'Edit information'
%><%!
@@ -57,127 +49,133 @@
<%def name="title()">View Tool</%def>
-<h2>View Tool: ${tool.name} <em>${tool.description}</em></h2>
+<h2>View Tool</h2>
-%if tool.is_approved():
- <b><i>This is the latest approved version of this tool</i></b>
-%elif tool.is_deleted():
- <font color="red"><b><i>This is a deleted version of this tool</i></b></font>
-%elif tool.is_archived():
- <font color="red"><b><i>This is an archived version of this tool</i></b></font>
-%elif tool.is_new():
- <font color="red"><b><i>This is an unsubmitted version of this tool</i></b></font>
-%elif tool.is_waiting():
- <font color="red"><b><i>This version of this tool is awaiting administrative approval</i></b></font>
-%elif tool.is_rejected():
- <font color="red"><b><i>This version of this tool has been rejected by an administrator</i></b></font>
-%endif
+${tool.get_state_message()}
<p/>
-%if cntrller=='admin' and tool.is_waiting():
- <p>
- <ul class="manage-table-actions">
- <li><a class="action-button" href="${h.url_for( controller='admin', action='set_tool_state', state=trans.model.Tool.states.APPROVED, id=trans.security.encode_id( tool.id ), cntrller=cntrller )}"><span>Approve</span></a></li>
- <li><a class="action-button" href="${h.url_for( controller='admin', action='set_tool_state', state=trans.model.Tool.states.REJECTED, id=trans.security.encode_id( tool.id ), cntrller=cntrller )}"><span>Reject</span></a></li>
- </ul>
- </p>
-%endif
+<ul class="manage-table-actions">
+ %if can_approve_or_reject:
+ <li><a class="action-button" href="${h.url_for( controller='admin', action='set_tool_state', state=trans.model.Tool.states.APPROVED, id=trans.security.encode_id( tool.id ), cntrller=cntrller )}">Approve</a></li>
+ <li><a class="action-button" href="${h.url_for( controller='admin', action='set_tool_state', state=trans.model.Tool.states.REJECTED, id=trans.security.encode_id( tool.id ), cntrller=cntrller )}">Reject</a></li>
+ %endif
+ <li><a class="action-button" id="tool-${tool.id}-popup" class="menubutton">Tool Actions</a></li>
+ <div popupmenu="tool-${tool.id}-popup">
+ %if can_edit:
+ <a class="action-button" href="${h.url_for( controller='common', action='edit_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">${menu_label}</a>
+ %endif
+ <a class="action-button" href="${h.url_for( controller='common', action='view_tool_history', id=trans.security.encode_id( tool.id ), cntrller=cntrller )}">Tool history</a>
+ %if can_download:
+ <a class="action-button" href="${h.url_for( controller='common', action='download_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Download tool</a>
+ %endif
+ %if can_delete:
+ <a class="action-button" href="${h.url_for( controller='common', action='delete_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}" confirm="Are you sure you want to delete this tool?">Delete tool</a>
+ %endif
+ %if can_upload_new_version:
+ <a class="action-button" href="${h.url_for( controller='common', action='upload_new_tool_version', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Upload a new version</a>
+ %endif
+ %if can_purge:
+ <li><a class="action-button" href="${h.url_for( controller='admin', action='purge_tool', id=trans.security.encode_id( tool.id ), cntrller=cntrller )}" confirm="Purging removes records from the database, are you sure you want to purge this tool?">Purge tool</a></li>
+ %endif
+ </div>
+</ul>
%if message:
${render_msg( message, status )}
%endif
-<div class="toolForm">
- <div class="toolFormTitle">${tool.name}
- %if not tool.deleted:
- <a id="tool-${tool.id}-popup" class="popup-arrow" style="display: none;">▼</a>
- <div popupmenu="tool-${tool.id}-popup">
- %if cntrller=='admin' or can_edit:
- <a class="action-button" href="${h.url_for( controller='common', action='edit_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">${menu_label}</a>
- %endif
- <a class="action-button" href="${h.url_for( controller='tool', action='download_tool', id=trans.app.security.encode_id( tool.id ) )}">Download tool</a>
- %if cntrller=='admin' or trans.user==tool.user:
- <a class="action-button" href="${h.url_for( controller='common', action='delete_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Delete tool</a>
- %endif
- %if cntrller=='admin' or can_upload_new_version:
- <a class="action-button" href="${h.url_for( controller='common', action='upload_new_tool_version', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Upload a new version</a>
- %endif
+%if can_view:
+ %if tool.is_rejected():
+ <div class="toolForm">
+ <div class="toolFormTitle">Reason for rejection</div>
+ <div class="toolFormBody">
+ <div class="form-row">
+ ${reason_for_rejection}
+ <div style="clear: both"></div>
+ </div></div>
- %endif
- </div>
- <div class="toolFormBody">
- <div class="form-row">
- <label>Tool Id:</label>
- ${tool.tool_id}
- <div style="clear: both"></div></div>
- <div class="form-row">
- <label>Version:</label>
- ${tool.version}
- <div style="clear: both"></div>
- </div>
- <div class="form-row">
- <label>Description:</label>
- %if tool.user_description:
- <pre>${tool.user_description}</pre>
- %endif
- <div style="clear: both"></div>
- </div>
- <div class="form-row">
- <label>Uploaded by:</label>
- ${tool.user.username}
- <div style="clear: both"></div>
- </div>
- <div class="form-row">
- <label>Date uploaded:</label>
- ${time_ago( tool.create_time )}
- <div style="clear: both"></div>
- </div>
- <div class="form-row">
- <label>Categories:</label>
- %if categories:
- <ul>
- %for category in categories:
- <li>${category.name}</li>
- %endfor
- </ul>
- %else:
- none set
- %endif
- <div style="clear: both"></div>
- </div>
- %if len( visible_versions ) > 1:
+ <p/>
+ %endif
+ <div class="toolForm">
+ <div class="toolFormTitle">${tool.name}</div>
+ <div class="toolFormBody"><div class="form-row">
- <label>All Versions:</label>
- <ul>
- %for version in visible_versions:
- %if version == tool:
- <li><strong>${version.version} (this version)</strong></li>
- %else:
- <li><a href="${h.url_for( controller='common', action='view_tool', id=trans.app.security.encode_id( version.id ), cntrller=cntrller )}">${version.version}</a></li>
- %endif
- %endfor
- </ul>
+ <label>Tool Id:</label>
+ ${tool.tool_id}
<div style="clear: both"></div></div>
- %endif
- </div>
-</div>
-
-<p/>
-
-<div class="toolForm">
- <div class="toolFormTitle">Tool Contents</div>
- <div class="toolFormBody">
- <div class="form-row">
- <ul class="toolFile">
- <li><a href="${h.url_for( controller='tool', action='download_tool', id=trans.app.security.encode_id( tool.id ) )}">${tool.download_file_name}</a></li>
- <ul class="fileBrowser">
- %for name in tool_file_contents:
- <li><a href="${h.url_for( controller='tool', action='view_tool_file', id=trans.app.security.encode_id( tool.id ), file_name=quote_plus( name ) )}">${name}</a></li>
- %endfor
- </ul>
- </ul>
+ <div class="form-row">
+ <label>Version:</label>
+ ${tool.version}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>Description:</label>
+ ${tool.description}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>User Description:</label>
+ %if tool.user_description:
+ <pre>${tool.user_description}</pre>
+ %endif
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>Uploaded by:</label>
+ ${tool.user.username}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>Date uploaded:</label>
+ ${time_ago( tool.create_time )}
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <label>Categories:</label>
+ %if categories:
+ <ul>
+ %for category in categories:
+ <li>${category.name}</li>
+ %endfor
+ </ul>
+ %else:
+ none set
+ %endif
+ <div style="clear: both"></div>
+ </div>
+ %if len( visible_versions ) > 1:
+ <div class="form-row">
+ <label>All Versions:</label>
+ <ul>
+ %for version in visible_versions:
+ %if version == tool:
+ <li><strong>${version.version} (this version)</strong></li>
+ %else:
+ <li><a href="${h.url_for( controller='common', action='view_tool', id=trans.app.security.encode_id( version.id ), cntrller=cntrller )}">${version.version}</a></li>
+ %endif
+ %endfor
+ </ul>
+ <div style="clear: both"></div>
+ </div>
+ %endif
</div></div>
-</div>
+ <p/>
+ <div class="toolForm">
+ <div class="toolFormTitle">Tool Contents</div>
+ <div class="toolFormBody">
+ <div class="form-row">
+ <ul class="toolFile">
+ <li><a href="${h.url_for( controller='tool', action='download_tool', id=trans.app.security.encode_id( tool.id ) )}">${tool.download_file_name}</a></li>
+ <ul class="fileBrowser">
+ %for name in tool_file_contents:
+ <li><a href="${h.url_for( controller='tool', action='view_tool_file', id=trans.app.security.encode_id( tool.id ), file_name=quote_plus( name ) )}">${name}</a></li>
+ %endfor
+ </ul>
+ </ul>
+ </div>
+ </div>
+ </div>
+%endif
--- a/templates/webapps/community/category/edit_category.mako
+++ b/templates/webapps/community/category/edit_category.mako
@@ -10,7 +10,6 @@
<div class="toolFormBody"><form name="library" action="${h.url_for( controller='admin', action='edit_category' )}" method="post" ><div class="form-row">
- <input name="webapp" type="hidden" value="${webapp}" size=40"/><label>Name:</label><div style="float: left; width: 250px; margin-right: 10px;"><input type="text" name="name" value="${category.name}" size="40"/>
--- a/lib/galaxy/webapps/community/controllers/tool.py
+++ b/lib/galaxy/webapps/community/controllers/tool.py
@@ -97,7 +97,9 @@ class ToolController( BaseController ):
for k, v in kwd.items():
if k.startswith( 'f-' ):
del kwd[ k ]
- return self.browse_tools( trans, **kwd )
+ return trans.response.send_redirect( web.url_for( controller='tool',
+ action='browse_tools',
+ **kwd ) )
# Render the list view
return self.category_list_grid( trans, **kwd )
@web.expose
@@ -107,19 +109,20 @@ class ToolController( BaseController ):
# to take this approach because the "-" character is illegal in HTTP requests.
if 'operation' in kwd:
operation = kwd['operation'].lower()
- if operation == "view tool":
+ if operation == "view_tool":
return trans.response.send_redirect( web.url_for( controller='common',
action='view_tool',
cntrller='tool',
**kwd ) )
- elif operation == "edit tool":
+ elif operation == "edit_tool":
return trans.response.send_redirect( web.url_for( controller='common',
action='edit_tool',
cntrller='tool',
**kwd ) )
elif operation == "download tool":
- return trans.response.send_redirect( web.url_for( controller='tool',
+ return trans.response.send_redirect( web.url_for( controller='common',
action='download_tool',
+ cntrller='tool',
**kwd ) )
elif operation == "tools_by_user":
# Eliminate the current filters if any exist.
@@ -154,20 +157,6 @@ class ToolController( BaseController ):
# Render the list view
return self.tool_list_grid( trans, **kwd )
@web.expose
- def download_tool( self, trans, **kwd ):
- params = util.Params( kwd )
- id = params.get( 'id', None )
- if not id:
- return trans.response.send_redirect( web.url_for( controller='tool',
- action='browse_tools',
- message='Select a tool to download',
- status='error' ) )
- tool = get_tool( trans, id )
- trans.response.set_content_type( tool.mimetype )
- trans.response.headers['Content-Length'] = int( os.stat( tool.file_name ).st_size )
- trans.response.headers['Content-Disposition'] = 'attachment; filename=%s' % tool.download_file_name
- return open( tool.file_name )
- @web.expose
def view_tool_file( self, trans, **kwd ):
params = util.Params( kwd )
id = params.get( 'id', None )
--- a/lib/galaxy/webapps/community/controllers/admin.py
+++ b/lib/galaxy/webapps/community/controllers/admin.py
@@ -399,16 +399,21 @@ class AdminController( BaseController, A
# to take this approach because the "-" character is illegal in HTTP requests.
if 'operation' in kwd:
operation = kwd['operation'].lower()
- if operation == "edit tool":
+ if operation == "edit_tool":
return trans.response.send_redirect( web.url_for( controller='common',
action='edit_tool',
cntrller='admin',
**kwd ) )
- elif operation == "view tool":
+ elif operation == "view_tool":
return trans.response.send_redirect( web.url_for( controller='common',
action='view_tool',
cntrller='admin',
**kwd ) )
+ elif operation == 'tool_history':
+ return trans.response.send_redirect( web.url_for( controller='common',
+ cntrller='admin',
+ action='events',
+ **kwd ) )
elif operation == "tools_by_user":
# Eliminate the current filters if any exist.
for k, v in kwd.items():
@@ -457,7 +462,9 @@ class AdminController( BaseController, A
for k, v in kwd.items():
if k.startswith( 'f-' ):
del kwd[ k ]
- return self.browse_tools( trans, **kwd )
+ return trans.response.send_redirect( web.url_for( controller='admin',
+ action='browse_tools',
+ **kwd ) )
# Render the list view
return self.category_list_grid( trans, **kwd )
@web.expose
@@ -481,16 +488,26 @@ class AdminController( BaseController, A
@web.require_admin
def create_category( self, trans, **kwd ):
params = util.Params( kwd )
- webapp = params.get( 'webapp', 'community' )
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
if params.get( 'create_category_button', False ):
name = util.restore_text( params.name )
description = util.restore_text( params.description )
+ error = False
if not name or not description:
- message = "Enter a valid name and a description"
- elif trans.sa_session.query( trans.app.model.Category ).filter( trans.app.model.Category.table.c.name==name ).first():
- message = "A category with that name already exists"
+ message = 'Enter a valid name and a description'
+ error = True
+ elif trans.sa_session.query( trans.app.model.Category ) \
+ .filter( trans.app.model.Category.table.c.name==name ) \
+ .first():
+ message = 'A category with that name already exists'
+ error = True
+ if error:
+ return trans.fill_template( '/webapps/community/category/create_category.mako',
+ name=name,
+ description=description,
+ message=message,
+ status='error' )
else:
# Create the category
category = trans.app.model.Category( name=name, description=description )
@@ -499,26 +516,27 @@ class AdminController( BaseController, A
trans.sa_session.flush()
trans.response.send_redirect( web.url_for( controller='admin',
action='manage_categories',
- webapp=webapp,
message=util.sanitize_text( message ),
status='done' ) )
trans.response.send_redirect( web.url_for( controller='admin',
action='create_category',
- webapp=webapp,
message=util.sanitize_text( message ),
status='error' ) )
+ else:
+ name = ''
+ description = ''
return trans.fill_template( '/webapps/community/category/create_category.mako',
- webapp=webapp,
+ name=name,
+ description=description,
message=message,
status=status )
@web.expose
@web.require_admin
def set_tool_state( self, trans, state, **kwd ):
params = util.Params( kwd )
- webapp = params.get( 'webapp', 'galaxy' )
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
- redirect = params.get( 'no_redirect', True )
+ comments = util.restore_text( params.get( 'comments', '' ) )
id = params.get( 'id', None )
if not id:
message = "No tool id received for setting status"
@@ -526,32 +544,130 @@ class AdminController( BaseController, A
else:
tool = get_tool( trans, id )
if state == trans.app.model.Tool.states.APPROVED:
- # If we're approving a tool, all previous versions must be set to archived
- for version in get_versions( trans, tool ):
+ # If we're approving a tool, all previously approved versions must be set to archived
+ for version in get_versions( tool ):
+ # TODO: get latest approved version instead of all versions
if version != tool and version.is_approved():
- self.set_tool_state( trans,
- trans.app.model.Tool.states.ARCHIVED,
- id=trans.security.encode_id( version.id ),
- redirect='False' )
- event = trans.model.Event( state )
- # Flush so we an get an id
- trans.sa_session.add( event )
- trans.sa_session.flush()
- tea = trans.model.ToolEventAssociation( tool, event )
- trans.sa_session.add( tea )
- trans.sa_session.flush()
+ # Create an event with state ARCHIVED for the previously approved version of this tool
+ self.__create_tool_event( trans,
+ version,
+ trans.app.model.Tool.states.ARCHIVED )
+ # Create an event with state APPROVED for this tool
+ self.__create_tool_event( trans, tool, state, comments )
+ elif state == trans.app.model.Tool.states.REJECTED:
+ # If we're rejecting a tool, comments about why are necessary.
+ return trans.fill_template( '/webapps/community/admin/reject_tool.mako',
+ tool=tool,
+ cntrller='admin' )
message = "State of tool '%s' is now %s" % ( tool.name, state )
- if redirect:
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='browse_tools',
+ message=message,
+ status=status ) )
+ @web.expose
+ @web.require_admin
+ def reject_tool( self, trans, **kwd ):
+ params = util.Params( kwd )
+ if params.get( 'cancel_reject_button', False ):
+ # Fix up the keyword dict to include params to view the current tool
+ # since that is the page from which we originated.
+ del kwd[ 'cancel_reject_button' ]
+ del kwd[ 'comments' ]
+ kwd[ 'webapp' ] = 'community'
+ kwd[ 'operation' ] = 'view_tool'
+ message = 'Tool rejection cancelled'
+ status = 'done'
+ return trans.response.send_redirect( web.url_for( controller='admin',
+ action='browse_tools',
+ message=message,
+ status=status,
+ **kwd ) )
+ id = params.get( 'id', None )
+ if not id:
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='browse_tools',
+ message='No tool id received for rejecting',
+ status='error' ) )
+ tool = get_tool( trans, id )
+ if not trans.app.security_agent.can_approve_or_reject( trans.user, trans.user_is_admin(), 'admin', tool ):
+ return trans.response.send_redirect( web.url_for( controller='admin',
+ action='browse_tools',
+ message='You are not allowed to reject this tool',
+ status='error' ) )
+ # Comments are required when rejecting a tool.
+ comments = util.restore_text( params.get( 'comments', '' ) )
+ if not comments:
+ message = 'The reason for rejection is required when rejecting a tool.'
+ return trans.fill_template( '/webapps/community/admin/reject_tool.mako',
+ tool=tool,
+ cntrller='admin',
+ message=message,
+ status='error' )
+ # Create an event with state REJECTED for this tool
+ self.__create_tool_event( trans, tool, trans.app.model.Tool.states.REJECTED, comments )
+ message = 'The tool "%s" has been rejected.' % tool.name
+ return trans.response.send_redirect( web.url_for( controller='admin',
+ action='browse_tools',
+ operation='tools_by_state',
+ state='rejected',
+ message=message,
+ status='done' ) )
+ def __create_tool_event( self, trans, tool, state, comments='' ):
+ event = trans.model.Event( state, comments )
+ # Flush so we can get an id
+ trans.sa_session.add( event )
+ trans.sa_session.flush()
+ tea = trans.model.ToolEventAssociation( tool, event )
+ trans.sa_session.add( tea )
+ trans.sa_session.flush()
+ @web.expose
+ @web.require_admin
+ def purge_tool( self, trans, **kwd ):
+ # This method completely removes a tool record and all associated foreign key rows
+ # from the database, so it must be used carefully.
+ # This method should only be called for a tool that has previously been deleted.
+ # Purging a deleted tool deletes all of the following from the database:
+ # - ToolCategoryAssociations
+ # - ToolEventAssociations and associated Events
+ # TODO: when we add tagging for tools, we'll have to purge them as well
+ params = util.Params( kwd )
+ id = kwd.get( 'id', None )
+ if not id:
+ message = "No tool ids received for purging"
trans.response.send_redirect( web.url_for( controller='admin',
action='browse_tools',
- webapp=webapp,
- message=message,
- status=status ) )
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ ids = util.listify( id )
+ message = "Purged %d tools: " % len( ids )
+ for tool_id in ids:
+ tool = get_tool( trans, tool_id )
+ message += " %s " % tool.name
+ if not tool.deleted:
+ message = "Tool '%s' has not been deleted, so it cannot be purged." % tool.name
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='browse_tools',
+ message=util.sanitize_text( message ),
+ status='error' ) )
+ # Delete ToolCategoryAssociations
+ for tca in tool.categories:
+ trans.sa_session.delete( tca )
+ # Delete ToolEventAssociations and associated events
+ for tea in tool.events:
+ event = tea.event
+ trans.sa_session.delete( event )
+ trans.sa_session.delete( tea )
+ # Delete the tool
+ trans.sa_session.delete( tool )
+ trans.sa_session.flush()
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='browse_tools',
+ message=util.sanitize_text( message ),
+ status='done' ) )
@web.expose
@web.require_admin
def edit_category( self, trans, **kwd ):
params = util.Params( kwd )
- webapp = params.get( 'webapp', 'galaxy' )
message = util.restore_text( params.get( 'message', '' ) )
status = params.get( 'status', 'done' )
id = params.get( 'id', None )
@@ -559,7 +675,6 @@ class AdminController( BaseController, A
message = "No category ids received for editing"
trans.response.send_redirect( web.url_for( controller='admin',
action='manage_categories',
- webapp=webapp,
message=message,
status='error' ) )
category = get_category( trans, id )
@@ -582,25 +697,21 @@ class AdminController( BaseController, A
message = "The information has been saved for category '%s'" % ( category.name )
return trans.response.send_redirect( web.url_for( controller='admin',
action='manage_categories',
- webapp=webapp,
message=util.sanitize_text( message ),
status='done' ) )
return trans.fill_template( '/webapps/community/category/edit_category.mako',
category=category,
- webapp=webapp,
message=message,
status=status )
@web.expose
@web.require_admin
def mark_category_deleted( self, trans, **kwd ):
params = util.Params( kwd )
- webapp = params.get( 'webapp', 'galaxy' )
id = kwd.get( 'id', None )
if not id:
message = "No category ids received for deleting"
trans.response.send_redirect( web.url_for( controller='admin',
action='manage_categories',
- webapp=webapp,
message=message,
status='error' ) )
ids = util.listify( id )
@@ -613,20 +724,17 @@ class AdminController( BaseController, A
message += " %s " % category.name
trans.response.send_redirect( web.url_for( controller='admin',
action='manage_categories',
- webapp=webapp,
message=util.sanitize_text( message ),
status='done' ) )
@web.expose
@web.require_admin
def undelete_category( self, trans, **kwd ):
params = util.Params( kwd )
- webapp = params.get( 'webapp', 'galaxy' )
id = kwd.get( 'id', None )
if not id:
message = "No category ids received for undeleting"
trans.response.send_redirect( web.url_for( controller='admin',
action='manage_categories',
- webapp=webapp,
message=message,
status='error' ) )
ids = util.listify( id )
@@ -638,7 +746,6 @@ class AdminController( BaseController, A
message = "Category '%s' has not been deleted, so it cannot be undeleted." % category.name
trans.response.send_redirect( web.url_for( controller='admin',
action='manage_categories',
- webapp=webapp,
message=util.sanitize_text( message ),
status='error' ) )
category.deleted = False
@@ -649,7 +756,6 @@ class AdminController( BaseController, A
message = "Undeleted %d categories: %s" % ( count, undeleted_categories )
trans.response.send_redirect( web.url_for( controller='admin',
action='manage_categories',
- webapp=webapp,
message=util.sanitize_text( message ),
status='done' ) )
@web.expose
@@ -659,13 +765,11 @@ class AdminController( BaseController, A
# Purging a deleted Category deletes all of the following from the database:
# - ToolCategoryAssociations where category_id == Category.id
params = util.Params( kwd )
- webapp = params.get( 'webapp', 'galaxy' )
id = kwd.get( 'id', None )
if not id:
message = "No category ids received for purging"
trans.response.send_redirect( web.url_for( controller='admin',
action='manage_categories',
- webapp=webapp,
message=util.sanitize_text( message ),
status='error' ) )
ids = util.listify( id )
@@ -676,7 +780,6 @@ class AdminController( BaseController, A
message = "Category '%s' has not been deleted, so it cannot be purged." % category.name
trans.response.send_redirect( web.url_for( controller='admin',
action='manage_categories',
- webapp=webapp,
message=util.sanitize_text( message ),
status='error' ) )
# Delete ToolCategoryAssociations
@@ -686,7 +789,6 @@ class AdminController( BaseController, A
message += " %s " % category.name
trans.response.send_redirect( web.url_for( controller='admin',
action='manage_categories',
- webapp=webapp,
message=util.sanitize_text( message ),
status='done' ) )
1
0

galaxy-dist commit 692458d15b92: trackster: Track browsers can now be embedded in Galaxy Pages as multiple browsers can now exist at the same time after refactoring. Display code is now separated from editor code. Some scrolling bugs have been fixed as well.
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Kanwei Li <kanwei(a)gmail.com>
# Date 1278029069 14400
# Node ID 692458d15b9211ba7e6569e07b350ef2c3c9757a
# Parent e7678fd94340e969552c8cea825bc2a2cbace68e
trackster: Track browsers can now be embedded in Galaxy Pages as multiple browsers can now exist at the same time after refactoring. Display code is now separated from editor code. Some scrolling bugs have been fixed as well.
--- /dev/null
+++ b/templates/visualization/item_content.mako
@@ -0,0 +1,3 @@
+<%namespace file="/visualization/display.mako" import="*" />
+
+${render_item( item, item_data )}
--- a/templates/visualization/display.mako
+++ b/templates/visualization/display.mako
@@ -1,169 +1,23 @@
<%inherit file="/display_base.mako"/><%def name="javascripts()">
+ <% config = item_data %>
${parent.javascripts()}
- ${h.js( "jquery.event.drag", "jquery.autocomplete", "jquery.mousewheel", "trackster", "ui.core", "ui.sortable" )}
+ ${h.js( "jquery.event.drag", "jquery.autocomplete", "jquery.mousewheel", "trackster" )}
- ## HACK: set config as item_data.
- <% config = item_data %>
-
- ## TODO: Copied from browser.mako -- probably should create JS file for visualization code and include visualization JS above.
<script type="text/javascript">
-
- ## JG: add controller name.
- var data_url = "${h.url_for( controller='/tracks', action='data' )}";
var view;
-
- $(function() {
-
- %if config:
- view = new View( "${config.get('chrom')}", "${config.get('title') | h}", "${config.get('vis_id')}", "${config.get('dbkey')}" );
- %for track in config.get('tracks'):
- view.add_track(
- new ${track["track_type"]}( "${track['name'] | h}", ${track['dataset_id']}, ${track['prefs']} )
- );
- %endfor
- init();
- %else:
- continue_fn = function() {
- view = new View( undefined, $("#new-title").val(), undefined, $("#new-dbkey").val() );
- init();
- hide_modal();
- };
- $.ajax({
- url: "${h.url_for( action='new_browser' )}",
- data: {},
- error: function() { alert( "Couldn't create new browser" ) },
- success: function(form_html) {
- show_modal("New Track Browser", form_html, {
- "Cancel": function() { window.location = "/"; },
- "Continue": function() { $(document).trigger("convert_dbkeys"); continue_fn(); }
- });
- $("#new-title").focus();
- replace_big_select_inputs();
- }
- });
- %endif
-
- // Execute this when everything is ready
- function init() {
- $("ul#sortable-ul").sortable({
- update: function(event, ui) {
- for (var track_id in view.tracks) {
- var track = view.tracks[track_id];
- }
- }
- });
-
- $(document).bind( "redraw", function( e ) {
- view.redraw();
- });
-
- $("#content").bind("mousewheel", function( e, delta ) {
- if (delta > 0) {
- view.zoom_in(e.pageX, $("#viewport-container"));
- } else {
- view.zoom_out();
- }
- e.preventDefault();
- });
-
- $("#content").bind("dblclick", function( e ) {
- view.zoom_in(e.pageX, $("#viewport-container"));
- });
-
- // To let the overview box be draggable
- $("#overview-box").bind("dragstart", function( e ) {
- this.current_x = e.offsetX;
- }).bind("drag", function( e ) {
- var delta = e.offsetX - this.current_x;
- this.current_x = e.offsetX;
-
- var delta_chrom = Math.round(delta / $(document).width() * view.span);
- view.center += delta_chrom;
- view.redraw();
- });
-
- // To adjust the size of the viewport to fit the fixed-height footer
- var refresh = function( e ) {
- $("#viewport-container").height( $(window).height() - 120 );
- $("#nav-container").width( $("#center").width() );
- view.redraw();
- };
- $(window).bind( "resize", function(e) { refresh(e); } );
- $("#right-border").bind( "click", function(e) { refresh(e); } );
- $("#right-border").bind( "dragend", function(e) { refresh(e); } );
- $(window).trigger( "resize" );
-
- $("#viewport-container").bind( "dragstart", function( e ) {
- this.original_low = view.low;
- this.current_height = e.clientY;
- this.current_x = e.offsetX;
- }).bind( "drag", function( e ) {
- var container = $(this);
- var delta = e.offsetX - this.current_x;
- var new_scroll = container.scrollTop() - (e.clientY - this.current_height);
- if ( new_scroll < container.get(0).scrollHeight - container.height() ) {
- container.scrollTop(new_scroll);
- }
- this.current_height = e.clientY;
- this.current_x = e.offsetX;
-
- var delta_chrom = Math.round(delta / $(document).width() * (view.high - view.low));
- view.center -= delta_chrom;
- view.redraw();
- });
-
- ## JG: Removed 'add-track' init code.
-
- ## JG: Removed 'save-button' init code
-
- view.add_label_track( new LabelTrack( $("#top-labeltrack" ) ) );
- view.add_label_track( new LabelTrack( $("#nav-labeltrack" ) ) );
-
- $.ajax({
- ## JG: added controller name
- url: "${h.url_for( controller='/tracks', action='chroms' )}",
- data: { vis_id: view.vis_id },
- dataType: "json",
- success: function ( data ) {
- view.chrom_data = data;
- var chrom_options = '<option value="">Select Chrom/Contig</option>';
- for (i in data) {
- var chrom = data[i]['chrom']
- chrom_options += '<option value="' + chrom + '">' + chrom + '</option>';
- }
- $("#chrom").html(chrom_options);
- $("#chrom").bind( "change", function () {
- view.chrom = $("#chrom").val();
- var found = $.grep(view.chrom_data, function(v, i) {
- return v.chrom === view.chrom;
- })[0];
- view.max_high = found.len;
- view.reset();
- view.redraw(true);
-
- for (var track_id in view.tracks) {
- var track = view.tracks[track_id];
- if (track.init) {
- track.init();
- }
- }
- view.redraw();
- });
- },
- error: function() {
- alert( "Could not load chroms for this dbkey:", view.dbkey );
- }
- });
-
- ## JG: Removed function sidebar_box() and sidebar init code.
-
- $(window).trigger("resize");
- };
-
- });
-
+ // To adjust the size of the viewport to fit the fixed-height footer
+ var refresh = function( e ) {
+ if (view !== undefined) {
+ view.viewport_container.height( $(window).height() - 100 );
+ view.nav_container.width( $("#center").width() );
+ view.redraw();
+ }
+ };
+ $(window).bind( "resize", function(e) { refresh(e); } );
+ $("#right-border").bind( "click dragend", function(e) { refresh(e); } );
+ $(window).trigger( "resize" );
</script></%def>
@@ -171,60 +25,45 @@
<%def name="stylesheets()">
${parent.stylesheets()}
- ${h.css( "history" )}
- <link rel="stylesheet" type="text/css" href="${h.url_for('/static/trackster.css')}" />
-
<style type="text/css">
- ul#sortable-ul {
- list-style: none;
- padding: 0;
- margin: 5px;
+ .nav-container {
+ position: fixed;
+ width: 100%;
+ left: 0;
+ bottom: 0;
}
- ul#sortable-ul li {
- display: block;
- margin: 5px 0;
- background: #eee;
+ .page-body {
+ padding: 0px;
}
+
</style></%def>
+<%def name="render_item_header( item )">
+ ## Don't need to show header
+</%def>
+
<%def name="render_item_links( visualization )">
- ## TODO
+
</%def><%def name="render_item( visualization, config )">
- <br><br>
- ## Copied from center_panel() in browser.mako -- probably need to create visualization_common.mako to render view.
- <div id="content">
- <div id="top-labeltrack"></div>
- <div id="viewport-container" style="overflow-x: hidden; overflow-y: auto;">
- <div id="viewport"></div>
- </div>
- </div>
- <div id="nav-container" style="width:100%;">
- <div id="nav-labeltrack"></div>
- <div id="nav">
- <div id="overview">
- <div id="overview-viewport">
- <div id="overview-box"></div>
- </div>
- </div>
- <div id="nav-controls">
- <form action="#">
- <select id="chrom" name="chrom" style="width: 15em;">
- <option value="">Loading</option>
- </select>
- <input id="low" size="12" />:<input id="high" size="12" />
- <input type="hidden" name="id" value="${config.get('vis_id', '')}" />
- <a href="#" onclick="javascript:view.zoom_in();view.redraw();">
- <img src="${h.url_for('/static/images/fugue/magnifier-zoom.png')}" />
- </a>
- <a href="#" onclick="javascript:view.zoom_out();view.redraw();">
- <img src="${h.url_for('/static/images/fugue/magnifier-zoom-out.png')}" />
- </a>
- </form>
- <div id="debug" style="float: right"></div>
- </div>
- </div>
- </div>
+ <div id="${visualization.id}"></div>
+
+ <script type="text/javascript">
+
+ var data_url = "${h.url_for( controller='/tracks', action='data' )}",
+ reference_url = "${h.url_for( controller='/tracks', action='reference' )}",
+ chrom_url = "${h.url_for( controller='/tracks', action='chroms' )}",
+ view;
+
+ var container_element = $("#${visualization.id}");
+ view = new View( container_element, "${config.get('chrom')}", "${config.get('title') | h}", "${config.get('vis_id')}", "${config.get('dbkey')}" );
+ %for track in config.get('tracks'):
+ view.add_track(
+ new ${track["track_type"]}( "${track['name'] | h}", view, ${track['dataset_id']}, ${track['prefs']} )
+ );
+ %endfor
+
+ </script></%def>
--- a/templates/display_base.mako
+++ b/templates/display_base.mako
@@ -32,7 +32,7 @@
<%def name="javascripts()">
${parent.javascripts()}
- ${h.js( "jquery", "jquery.tipsy", "galaxy.base", "json2", "class", "jquery.jstore", "jquery.autocomplete", "autocomplete_tagging" )}
+ ${h.js( "jquery", "jquery.tipsy", "galaxy.base", "json2", "class", "jquery.jstore", "jquery.autocomplete", "autocomplete_tagging", "trackster" )}
<script type="text/javascript">
@@ -72,6 +72,8 @@
<%def name="stylesheets()">
${parent.stylesheets()}
${h.css( "autocomplete_tagging", "embed_item" )}
+ <link rel="stylesheet" type="text/css" href="${h.url_for('/static/trackster.css')}" />
+
<style type="text/css">
.page-body {
padding: 10px;
--- 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}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.key
Code===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()}}}c
atch(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").ea
ch(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","i
talic")}}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}})};$(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="/static/images/visualization/strand_right.png";right_img.onload=function(){RIGHT_STRAND=CONTEXT.createPattern(right_img,"repeat")};var left_img=new Image();left_img.src="/static/images/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="/static/images/visualization/strand_right_inv.png";right_img_inv.onload=function(){RIGHT_STRAND
_INV=CONTEXT.createPattern(right_img_inv,"repeat")};var left_img_inv=new Image();left_img_inv.src="/static/images/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(b,d,c,a){this.vis_id=c;this.dbkey=a;this.title=d;this.chrom=b;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.reset()};$.extend(View.prototype,{add_track: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},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];$("#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;$(".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(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))));$("#overview-box").css({left:(this.low/(this.max_high-this.max_low))*$("#overview-viewport").width(),width:Math.max(12,(this.high-this.low)/(this.max_high-this.max_low)*$("#overview-viewport").width())}).show();$("#low").val(commatize(this.low));$("#high").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/c.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(a,b){this.name=a;this.parent_element=b;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();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(TiledTrack.prototype,Track.prototype,{draw:function(){var i=this.view.low,e=this.view.high,f=e-i,d=this.view.resolution;var k=$("<div style='position: relative;'></div>"),l=this
.content_div.width()/f,h;this.content_div.children(":first").remove();this.content_div.append(k),this.max_height=0;var a=Math.floor(i/d/DENSITY);while((a*DENSITY*d)<e){var j=this.content_div.width()+"_"+l+"_"+a;var c=this.tile_cache.get(j);if(c){var g=a*DENSITY*d;var b=(g-i)*l;if(this.left_offset){b-=this.left_offset}c.css({left:b});k.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,j,i,e,a,d,k,l)}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){Track.call(this,null,a);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(){this.track_type="ReferenceTrack";Track.call(this,null,$("#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,i){console.log(h,g,i)}})}},draw_tile:function(f,b,j,n){var g=b*DENSITY*f,d=DENSITY*f,e=$("<canvas class='tile'></canvas>"),m=e.get(0).getContext("2d"),i=f+"_"+b;if(n>PX_PER_CHAR){if(this.data_cache.get(i)===undefined){this.get_data(f,b);return}var l=this.data_cache.get(i);if(l===null){return}e.get(0).width=Math.ceil(d*n+this.left_offset);e.get(0).height=this.height_px;e.css({position:"absolute",top:0,left:(g-this.view.low)*n+this.left_offset});for(var h=0,k=l.length;h<k;h++){var a=Math.round(h*n);m.fillText(l[h],a+this.left_offset,10)}j.append(e);return e}}});var LineTrack=function(c,a,b){this.track_type="LineTrack";Track.call(this,c,$("#viewport"));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_LI
NE);this.prefs={min_value:undefined,max_value:undefined,mode:"Line"};if(b.min_value!==undefined){this.prefs.min_value=b.min_value}if(b.max_value!==undefined){this.prefs.max_value=b.max_value}if(b.mode!==undefined){this.prefs.mode=b.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,i){console.log(h,g,i)}})}},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:"abs
olute",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(n){var a=$("<div />").addClass("for
m-row");var h="track_"+n+"_minval",l=$("<label></label>").attr("for",h).text("Min value:"),b=(this.prefs.min_value===undefined?"":this.prefs.min_value),m=$("<input></input>").attr("id",h).val(b),k="track_"+n+"_maxval",g=$("<label></label>").attr("for",k).text("Max value:"),j=(this.prefs.max_value===undefined?"":this.prefs.max_value),f=$("<input></input>").attr("id",k).val(j),e="track_"+n+"_mode",d=$("<label></label>").attr("for",e).text("Display mode:"),i=(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_"+i).attr("selected","selected");return a.append(l).append(m).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(c,a,b){this.track_type="FeatureTrack";Track.call(this,c,$("#viewport"));TiledTrack.call(this);this.height_px=100;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(b.block_color!==undef
ined){this.prefs.block_color=b.block_color}if(b.label_color!==undefined){this.prefs.label_color=b.label_color}if(b.show_counts!==undefined){this.prefs.show_counts=b.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")},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.vie
w.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(m,n,f,l,b,d,j,e,h){m.textAlign="center";var i=Math.round(n/2);if((this.mode==="Pack"||this.mode==="Auto")&&d!==undefined&&n>PX_PER_CHAR){m.fillStyle=this.prefs.block_color;m.fillRect(j,h+1,e,9);m.fillStyle="#eee";for(var g=0,k=d.length;g<k;g++){if(b+g>=f&&b+g<=l){var a=Math.floor(Math.max(0,(b+g-f)*n));m.fillText(d[g],a+this.left_offset+i,h+9)}}}else{m.fillStyle=this.prefs.block_color;m.fillRect(j,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.fillS
tyle=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.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(i){var a=$("<div />").addClass("form-row");var e="track_"+i+"_block_color",k=$("<label />").attr("for",e).text("Block color:"),l=$("<input />").attr("id",e).attr("name",e).val(this.prefs.block_color),j="track_"+i+"_label_color",g=$("<label />").attr("for",j).text("Text color:"),h=$("<input />").attr("id",j).attr("name",j).val(this.prefs.label_color),f="track_"+i+"_show_count",c=$("<label />").attr("for",f).text("Show summary counts"),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(k).append(l).append(g).append(h).append(d)},update_options:function(e){var b=$("#tra
ck_"+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(c,a,b){FeatureTrack.call(this,c,a,b);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="/static/images/visualization/strand_right.png";right_img.onload=function(){RIGHT_STRAND=CONTEXT.createPattern(right_img,"repeat")};var left_img=new Image();left_img.src="/static/images/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="/static/images/visualization/strand_right_inv.png";right_img_inv.onload=function(){RIGHT_STRAND
_INV=CONTEXT.createPattern(right_img_inv,"repeat")};var left_img_inv=new Image();left_img_inv.src="/static/images/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.p
rototype,{init:function(){var c=this.container,a=this;this.content_div=$("<div/>").addClass("content").css("position","relative").appendTo(c);this.top_labeltrack=$("<div/>").addClass("top-labeltrack").appendTo(this.content_div);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(c);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);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="/images/fugue/magnifier-zoom.png" />').appendTo(this.chrom_form);this.zo_link=$("<a/>").click(function(){a.zoom_out();a.redraw()}).html('<img src="/images/fugue/magnifier-zoom-out.png" />').appendTo(this.chrom_form);var b=(this.vis_id!==undefined?{vis_id:this.vis_id}:{dbkey:this.dbkey});$.ajax({url:chrom_url,data:b,dataType:
"json",success:function(d){if(d.reference){a.add_label_track(new ReferenceTrack(a))}a.chrom_data=d.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);a.chrom_select.bind("change",function(){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.len;a.reset();a.redraw(true);for(var j in a.tracks){var g=a.tracks[j];if(g.init){g.init()}}a.redraw()})},error:function(){alert("Could not load chroms for this dbkey:",a.dbkey)}});this.content_div.bind("mousewheel",function(d,f){if(Math.abs(f)<0.5){return}if(f>0){a.zoom_in(d.pageX,this.viewport_container)}else{a.zoom_out()}d.preventDefault()});this.content_div.bind("dblclick",function(d){a.zoom_in(d.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(d){this.current_x=d.offsetX}).bind("drag",function(d){var g=d.offsetX-this
.current_x;this.current_x=d.offsetX;var f=Math.round(g/a.viewport_container.width()*(a.high-a.low));a.move_delta(-2*f)});this.viewport_container.bind("dragstart",function(d){this.original_low=a.low;this.current_height=d.clientY;this.current_x=d.offsetX}).bind("drag",function(g){var d=$(this);var j=g.offsetX-this.current_x;var f=d.scrollTop()-(g.clientY-this.current_height);if(f<d.get(0).scrollHeight-d.height()){d.scrollTop(f)}this.current_height=g.clientY;this.current_x=g.offsetX;var h=Math.round(j/a.viewport_container.width()*(a.high-a.low));a.move_delta(h)});this.top_labeltrack.bind("dragstart",function(d){this.drag_origin_x=d.clientX;this.drag_origin_pos=d.clientX/a.viewport_container.width()*(a.high-a.low)+a.low;this.drag_div=$("<div />").css({height:a.content_div.height(),top:"0px",position:"absolute","background-color":"#cfc",border:"1px solid #6a6",opacity:0.5}).appendTo($(this))}).bind("drag",function(j){var f=Math.min(j.clientX,this.drag_origin_x),d=Math.max(j.clien
tX,this.drag_origin_x),h=(a.high-a.low),g=a.viewport_container.width();a.low_input.val(commatize(Math.round(f/g*h)+a.low));a.high_input.val(commatize(Math.round(d/g*h)+a.low));this.drag_div.css({left:f+"px",width:(d-f)+"px"})}).bind("dragend",function(k){var f=Math.min(k.clientX,this.drag_origin_x),d=Math.max(k.clientX,this.drag_origin_x),h=(a.high-a.low),g=a.viewport_container.width(),j=a.low;a.low=Math.round(f/g*h)+j;a.high=Math.round(d/g*h)+j;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.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.trac
k_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(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();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(TiledTrack.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();thi
s.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="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,Ma
th.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.nav_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,hi
gh: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){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}}});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").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 />").addClas
s("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.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>").at
tr("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).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.pre
fs.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.labe
l_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")},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,resolut
ion: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_t
ile[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_coun
ts){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.measureTex
t(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.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.fillRec
t(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;"></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
@@ -69,7 +69,8 @@ var Cache = function( num_elements ) {
}
});
-var View = function( chrom, title, vis_id, dbkey ) {
+var View = function( container, chrom, title, vis_id, dbkey ) {
+ this.container = container;
this.vis_id = vis_id;
this.dbkey = dbkey;
this.title = title;
@@ -82,22 +83,184 @@ var View = function( chrom, title, vis_i
this.zoom_factor = 3;
this.min_separation = 30;
this.has_changes = false;
+ this.init();
this.reset();
};
$.extend( View.prototype, {
- add_track: function ( track ) {
+ init: function() {
+ // Create DOM elements
+ var parent_element = this.container,
+ view = this;
+
+ this.content_div = $("<div/>").addClass("content").css("position", "relative").appendTo(parent_element);
+ this.top_labeltrack = $("<div/>").addClass("top-labeltrack").appendTo(this.content_div);
+ 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(parent_element);
+ 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);
+ this.hidden_input = $("<input/>").attr("type", "hidden").val(this.vis_id).appendTo(this.chrom_form);
+ this.zi_link = $("<a/>").click(function() { view.zoom_in(); view.redraw() }).html('<img src="/images/fugue/magnifier-zoom.png" />').appendTo(this.chrom_form);
+ this.zo_link = $("<a/>").click(function() { view.zoom_out(); view.redraw() }).html('<img src="/images/fugue/magnifier-zoom-out.png" />').appendTo(this.chrom_form);;
+
+ var data_d = (this.vis_id !== undefined ? { vis_id: this.vis_id } : { dbkey: this.dbkey });
+
+ $.ajax({
+ url: chrom_url,
+ data: data_d,
+ dataType: "json",
+ success: function ( result ) {
+ if (result['reference']) {
+ view.add_label_track( new ReferenceTrack(view) );
+ }
+ view.chrom_data = result['chrom_info'];
+ var chrom_options = '<option value="">Select Chrom/Contig</option>';
+ for (i in view.chrom_data) {
+ var chrom = view.chrom_data[i]['chrom'];
+ chrom_options += '<option value="' + chrom + '">' + chrom + '</option>';
+ }
+ view.chrom_select.html(chrom_options);
+ view.chrom_select.bind( "change", function () {
+ view.chrom = view.chrom_select.val();
+ var found = $.grep(view.chrom_data, function(v, i) {
+ return v.chrom === view.chrom;
+ })[0];
+ view.max_high = found.len;
+ view.reset();
+ view.redraw(true);
+
+ for (var track_id in view.tracks) {
+ var track = view.tracks[track_id];
+ if (track.init) {
+ track.init();
+ }
+ }
+ view.redraw();
+ });
+ },
+ error: function() {
+ alert( "Could not load chroms for this dbkey:", view.dbkey );
+ }
+ });
+
+ this.content_div.bind("mousewheel", function( e, delta ) {
+ if (Math.abs(delta) < 0.5) {
+ return;
+ }
+ if (delta > 0) {
+ view.zoom_in(e.pageX, this.viewport_container);
+ } else {
+ view.zoom_out();
+ }
+ e.preventDefault();
+ });
+
+ this.content_div.bind("dblclick", function( e ) {
+ view.zoom_in(e.pageX, this.viewport_container);
+ });
+
+ // To let the overview box be draggable
+ this.overview_box.bind("dragstart", function( e ) {
+ this.current_x = e.offsetX;
+ }).bind("drag", function( e ) {
+ var delta = e.offsetX - this.current_x;
+ this.current_x = e.offsetX;
+
+ var delta_chrom = Math.round(delta / view.viewport_container.width() * (view.high - view.low) );
+ view.move_delta(-2*delta_chrom);
+ });
+
+ this.viewport_container.bind( "dragstart", function( e ) {
+ this.original_low = view.low;
+ this.current_height = e.clientY;
+ this.current_x = e.offsetX;
+ }).bind( "drag", function( e ) {
+ var container = $(this);
+ var delta = e.offsetX - this.current_x;
+ var new_scroll = container.scrollTop() - (e.clientY - this.current_height);
+ if ( new_scroll < container.get(0).scrollHeight - container.height() ) {
+ container.scrollTop(new_scroll);
+ }
+ this.current_height = e.clientY;
+ this.current_x = e.offsetX;
+
+ var delta_chrom = Math.round(delta / view.viewport_container.width() * (view.high - view.low));
+ view.move_delta(delta_chrom);
+ });
+
+ this.top_labeltrack.bind( "dragstart", function(e) {
+ this.drag_origin_x = e.clientX;
+ this.drag_origin_pos = e.clientX / view.viewport_container.width() * (view.high - view.low) + view.low;
+ this.drag_div = $("<div />").css( {
+ "height": view.content_div.height(), "top": "0px", "position": "absolute",
+ "background-color": "#cfc", "border": "1px solid #6a6", "opacity": 0.5
+ } ).appendTo( $(this) );
+ }).bind( "drag", function(e) {
+ var min = Math.min(e.clientX, this.drag_origin_x),
+ max = Math.max(e.clientX, this.drag_origin_x),
+ span = (view.high - view.low),
+ width = view.viewport_container.width();
+
+ view.low_input.val(commatize(Math.round(min / width * span) + view.low));
+ view.high_input.val(commatize(Math.round(max / width * span) + view.low));
+ this.drag_div.css( { "left": min + "px", "width": (max - min) + "px" } );
+ }).bind( "dragend", function(e) {
+ var min = Math.min(e.clientX, this.drag_origin_x),
+ max = Math.max(e.clientX, this.drag_origin_x),
+ span = (view.high - view.low),
+ width = view.viewport_container.width(),
+ old_low = view.low;
+
+ view.low = Math.round(min / width * span) + old_low;
+ view.high = Math.round(max / width * span) + old_low;
+ this.drag_div.remove();
+ view.redraw();
+ });
+
+ this.add_label_track( new LabelTrack( this, this.top_labeltrack ) );
+ this.add_label_track( new LabelTrack( this, this.nav_labeltrack ) );
+
+ },
+ move_delta: function(delta_chrom) {
+ var view = this;
+ var current_chrom_span = view.high - view.low;
+ // Check for left and right boundaries
+ if (view.low - delta_chrom < view.max_low) {
+ view.low = view.max_low;
+ view.high = view.max_low + current_chrom_span;
+ } else if (view.high - delta_chrom > view.max_high) {
+ view.high = view.max_high;
+ view.low = view.max_high - current_chrom_span;
+ } else {
+ view.high -= delta_chrom;
+ view.low -= delta_chrom;
+ }
+ view.redraw();
+ },
+ add_track: function(track) {
track.view = this;
track.track_id = this.track_id_counter;
- this.tracks.push( track );
+ this.tracks.push(track);
if (track.init) { track.init(); }
track.container_div.attr('id', 'track_' + track.track_id);
this.track_id_counter += 1;
},
- add_label_track: function ( label_track ) {
+ add_label_track: function (label_track) {
label_track.view = this;
- this.label_tracks.push( label_track );
+ this.label_tracks.push(label_track);
},
- remove_track: function( track ) {
+ remove_track: function(track) {
this.has_changes = true;
track.container_div.fadeOut('slow', function() { $(this).remove(); });
delete this.tracks[this.tracks.indexOf(track)];
@@ -107,7 +270,7 @@ var View = function( chrom, title, vis_i
var sorted = $("ul#sortable-ul").sortable('toArray');
for (var id_i in sorted) {
var id = sorted[id_i].split("_li")[0].split("track_")[1];
- $("#viewport").append( $("#track_" + id) );
+ this.viewport.append( $("#track_" + id) );
}
for (var track_id in view.tracks) {
@@ -120,7 +283,7 @@ var View = function( chrom, title, vis_i
reset: function() {
this.low = this.max_low;
this.high = this.max_high;
- $(".yaxislabel").remove();
+ this.viewport_container.find(".yaxislabel").remove();
},
redraw: function(nodraw) {
var span = this.high - this.low,
@@ -144,20 +307,20 @@ var View = function( chrom, title, vis_i
this.zoom_res = Math.pow( FEATURE_LEVELS, Math.max(0,Math.ceil( Math.log( this.resolution, FEATURE_LEVELS ) / Math.log(FEATURE_LEVELS) )));
// Overview
- $("#overview-box").css( {
- left: ( this.low / (this.max_high - this.max_low) ) * $("#overview-viewport").width(),
+ this.overview_box.css( {
+ left: ( this.low / (this.max_high - this.max_low) ) * this.overview_viewport.width(),
// Minimum width for usability
- width: Math.max( 12, (this.high - this.low)/(this.max_high - this.max_low) * $("#overview-viewport").width() )
+ width: Math.max( 12, (this.high - this.low)/(this.max_high - this.max_low) * this.overview_viewport.width() )
}).show();
- $("#low").val( commatize(this.low) );
- $("#high").val( commatize(this.high) );
+ this.low_input.val( commatize(this.low) );
+ this.high_input.val( commatize(this.high) );
if (!nodraw) {
- for ( var i = 0, len = this.tracks.length; i < len; i++ ) {
+ for (var i = 0, len = this.tracks.length; i < len; i++) {
if (this.tracks[i] && this.tracks[i].enabled) {
this.tracks[i].draw();
}
}
- for ( var i = 0, len = this.label_tracks.length; i < len; i++ ) {
+ for (var i = 0, len = this.label_tracks.length; i < len; i++) {
this.label_tracks[i].draw();
}
}
@@ -170,7 +333,7 @@ var View = function( chrom, title, vis_i
cur_center = span / 2 + this.low,
new_half = (span / this.zoom_factor) / 2;
if (point) {
- cur_center = point / container.width() * (this.high - this.low) + this.low;
+ cur_center = point / this.viewport_container.width() * (this.high - this.low) + this.low;
}
this.low = Math.round(cur_center - new_half);
this.high = Math.round(cur_center + new_half);
@@ -189,9 +352,10 @@ var View = function( chrom, title, vis_i
}
});
-var Track = function ( name, parent_element ) {
+var Track = function (name, view, parent_element) {
this.name = name;
this.parent_element = parent_element;
+ this.view = view;
this.init_global();
};
$.extend( Track.prototype, {
@@ -307,8 +471,8 @@ var TiledTrack = function() {
}
});
-var LabelTrack = function ( parent_element ) {
- Track.call( this, null, parent_element );
+var LabelTrack = function (view, parent_element) {
+ Track.call( this, null, view, parent_element );
this.track_type = "LabelTrack";
this.hidden = true;
this.container_div.addClass( "label-track" );
@@ -335,9 +499,9 @@ var LabelTrack = function ( parent_eleme
}
});
-var ReferenceTrack = function () {
+var ReferenceTrack = function (view) {
this.track_type = "ReferenceTrack";
- Track.call( this, null, $("#top-labeltrack") );
+ Track.call( this, null, view, view.nav_labeltrack );
TiledTrack.call( this );
this.hidden = true;
@@ -404,9 +568,9 @@ var ReferenceTrack = function () {
}
});
-var LineTrack = function ( name, dataset_id, prefs ) {
+var LineTrack = function ( name, view, dataset_id, prefs ) {
this.track_type = "LineTrack";
- Track.call( this, name, $("#viewport") );
+ Track.call( this, name, view, view.viewport_container );
TiledTrack.call( this );
this.height_px = 100;
@@ -624,12 +788,12 @@ var LineTrack = function ( name, dataset
}
});
-var FeatureTrack = function ( name, dataset_id, prefs ) {
+var FeatureTrack = function ( name, view, dataset_id, prefs ) {
this.track_type = "FeatureTrack";
- Track.call( this, name, $("#viewport") );
+ Track.call( this, name, view, view.viewport_container );
TiledTrack.call( this );
- this.height_px = 100;
+ this.height_px = 0;
this.container_div.addClass( "feature-track" );
this.dataset_id = dataset_id;
this.zo_slots = {};
@@ -1065,8 +1229,8 @@ var FeatureTrack = function ( name, data
}
});
-var ReadTrack = function ( name, dataset_id, prefs ) {
- FeatureTrack.call( this, name, dataset_id, prefs );
+var ReadTrack = function ( name, view, dataset_id, prefs ) {
+ FeatureTrack.call( this, name, view, dataset_id, prefs );
this.track_type = "ReadTrack";
this.vertical_detail_px = 10;
this.vertical_nodetail_px = 5;
--- a/static/scripts/galaxy.base.js
+++ b/static/scripts/galaxy.base.js
@@ -156,12 +156,18 @@ function replace_big_select_inputs(min_l
var select_elt = $(this);
// Make sure that options is within range.
var num_options = select_elt.find('option').length;
- if ( (num_options < min_length) || (num_options > max_length) )
+ if ( (num_options < min_length) || (num_options > max_length) ) {
return;
+ }
// Skip multi-select because widget cannot handle multi-select.
- if (select_elt.attr('multiple') == true)
+ if (select_elt.attr('multiple') == true) {
return;
+ }
+
+ if (select_elt.hasClass("no-autocomplete")) {
+ return;
+ }
// Replace select with text + autocomplete.
var start_value = select_elt.attr('value');
--- a/lib/galaxy/web/controllers/visualization.py
+++ b/lib/galaxy/web/controllers/visualization.py
@@ -283,7 +283,16 @@ class VisualizationController( BaseContr
@web.require_login("get item content asynchronously")
def get_item_content_async( self, trans, id ):
""" Returns item content in HTML format. """
- return "TODO: visualization content"
+
+ # Get visualization, making sure it's accessible.
+ visualization = self.get_visualization( trans, id, False, True)
+ if visualization is None:
+ raise web.httpexceptions.HTTPNotFound()
+
+ # Return content.
+ visualization_config = self.get_visualization_config( trans, visualization )
+ return trans.fill_template_mako( "visualization/item_content.mako", encoded_id=trans.security.encode_id(visualization.id),
+ item=visualization, item_data=visualization_config, content_only=True )
@web.expose
@web.require_login( "create visualizations" )
--- a/templates/tracks/browser.mako
+++ b/templates/tracks/browser.mako
@@ -15,6 +15,9 @@
${h.css( "history", "autocomplete_tagging" )}
<link rel="stylesheet" type="text/css" href="${h.url_for('/static/trackster.css')}" /><style type="text/css">
+ #center {
+ overflow: auto;
+ }
ul#sortable-ul {
list-style: none;
padding: 0;
@@ -25,6 +28,12 @@
margin: 5px 0;
background: #eee;
}
+ .nav-container {
+ position: fixed;
+ width: 100%;
+ left: 0;
+ bottom: 0;
+ }
</style></%def>
@@ -36,37 +45,7 @@
<a id="refresh-button" class="panel-header-button right-float" href="javascript:void(0);" onclick="view.update_options();return false;">Refresh</a></div></div>
-<div id="content">
- <div id="top-labeltrack" style="position: relative;"></div>
- <div id="viewport-container" style="overflow-x: hidden; overflow-y: auto;">
- <div id="viewport"></div>
- </div>
-</div>
-<div id="nav-container" style="width:100%;">
- <div id="nav-labeltrack"></div>
- <div id="nav">
- <div id="overview">
- <div id="overview-viewport">
- <div id="overview-box"></div>
- </div>
- </div>
- <div id="nav-controls">
- <form action="#">
- <select id="chrom" name="chrom" style="width: 15em;">
- <option value="">Loading</option>
- </select>
- <input id="low" size="12" />:<input id="high" size="12" />
- <input type="hidden" name="id" value="${config.get('vis_id', '')}" />
- <a href="#" onclick="javascript:view.zoom_in();view.redraw();">
- <img src="${h.url_for('/static/images/fugue/magnifier-zoom.png')}" />
- </a>
- <a href="#" onclick="javascript:view.zoom_out();view.redraw();">
- <img src="${h.url_for('/static/images/fugue/magnifier-zoom-out.png')}" />
- </a>
- </form>
- </div>
- </div>
-</div>
+
</%def><%def name="right_panel()">
@@ -86,21 +65,22 @@
<%def name="javascripts()">
${parent.javascripts()}
-${h.js( 'galaxy.base', 'galaxy.panels', "json2", "jquery", "jquery.event.drag", "jquery.autocomplete", "jquery.mousewheel", "trackster", "ui.core", "ui.sortable" )}
+${h.js( "galaxy.base", "galaxy.panels", "json2", "jquery", "jquery.event.drag", "jquery.autocomplete", "jquery.mousewheel", "trackster", "ui.core", "ui.sortable" )}
<script type="text/javascript">
- var data_url = "${h.url_for( action='data' )}";
- var reference_url = "${h.url_for( action='reference' )}";
- var view;
+ var data_url = "${h.url_for( action='data' )}",
+ reference_url = "${h.url_for( action='reference' )}",
+ chrom_url = "${h.url_for( action='chroms' )}",
+ view;
$(function() {
%if config:
- view = new View( "${config.get('chrom')}", "${config.get('title') | h}", "${config.get('vis_id')}", "${config.get('dbkey')}" );
+ view = new View( $("#center"), "${config.get('chrom')}", "${config.get('title') | h}", "${config.get('vis_id')}", "${config.get('dbkey')}" );
%for track in config.get('tracks'):
- view.add_track(
- new ${track["track_type"]}( "${track['name'] | h}", ${track['dataset_id']}, ${track['prefs']} )
+ view.add_track(
+ new ${track["track_type"]}( "${track['name'] | h}", view, ${track['dataset_id']}, ${track['prefs']} )
);
%endfor
init();
@@ -124,14 +104,22 @@
}
});
%endif
+
+ $(document).bind( "redraw", function( e ) {
+ view.redraw();
+ });
+
+ // To adjust the size of the viewport to fit the fixed-height footer
+ var refresh = function( e ) {
+ view.viewport_container.height( $(window).height() - 100 );
+ view.nav_container.width( $("#center").width() );
+ view.redraw();
+ };
+ $(window).bind( "resize", function(e) { refresh(e); } );
+ $("#right-border").bind( "click dragend", function(e) { refresh(e); } );
+ $(window).trigger( "resize" );
- window.onbeforeunload = function() {
- if ( view.has_changes ) {
- return "There are unsaved changes to your visualization which will be lost.";
- }
- };
-
- // Execute this when everything is ready
+ // Execute initializer for EDITOR specific javascript
function init() {
$("#title").text(view.title + " (" + view.dbkey + ")");
$("ul#sortable-ul").sortable({
@@ -142,68 +130,11 @@
}
});
- $(document).bind( "redraw", function( e ) {
- view.redraw();
- });
-
- $("#content").bind("mousewheel", function( e, delta ) {
- if (Math.abs(delta) < 0.5) {
- return;
+ window.onbeforeunload = function() {
+ if ( view.has_changes ) {
+ return "There are unsaved changes to your visualization which will be lost.";
}
- if (delta > 0) {
- view.zoom_in(e.pageX, $("#viewport-container"));
- } else {
- view.zoom_out();
- }
- e.preventDefault();
- });
-
- $("#content").bind("dblclick", function( e ) {
- view.zoom_in(e.pageX, $("#viewport-container"));
- });
-
- // To let the overview box be draggable
- $("#overview-box").bind("dragstart", function( e ) {
- this.current_x = e.offsetX;
- }).bind("drag", function( e ) {
- var delta = e.offsetX - this.current_x;
- this.current_x = e.offsetX;
-
- var delta_chrom = Math.round(delta / $(document).width() * view.span);
- view.high += delta_chrom;
- view.low += delta_chrom;
- view.redraw();
- });
-
- // To adjust the size of the viewport to fit the fixed-height footer
- var refresh = function( e ) {
- $("#viewport-container").height( $(window).height() - 100 );
- $("#nav-container").width( $("#center").width() );
- view.redraw();
};
- $(window).bind( "resize", function(e) { refresh(e); } );
- $("#right-border").bind( "click dragend", function(e) { refresh(e); } );
- $(window).trigger( "resize" );
-
- $("#viewport-container").bind( "dragstart", function( e ) {
- this.original_low = view.low;
- this.current_height = e.clientY;
- this.current_x = e.offsetX;
- }).bind( "drag", function( e ) {
- var container = $(this);
- var delta = e.offsetX - this.current_x;
- var new_scroll = container.scrollTop() - (e.clientY - this.current_height);
- if ( new_scroll < container.get(0).scrollHeight - container.height() ) {
- container.scrollTop(new_scroll);
- }
- this.current_height = e.clientY;
- this.current_x = e.offsetX;
-
- var delta_chrom = Math.round(delta / $("#viewport-container").width() * (view.high - view.low));
- view.high -= delta_chrom;
- view.low -= delta_chrom;
- view.redraw();
- });
// Use a popup grid to add more tracks
$("#add-track").bind( "click", function(e) {
@@ -222,20 +153,10 @@
dataType: "json",
error: function() {},
success: function(track_data) {
- var new_track;
- var td = track_data;
- switch(track_data.track_type) {
- case "LineTrack":
- new_track = new LineTrack( track_data.name, track_data.dataset_id, track_data.prefs );
- break;
- case "FeatureTrack":
- new_track = new FeatureTrack( track_data.name, track_data.dataset_id, track_data.prefs );
- break;
- case "ReadTrack":
- new_track = new ReadTrack( track_data.name, track_data.dataset_id, track_data.prefs );
- break;
- }
- view.add_track(new_track);
+ var td = track_data,
+ track_types = { "LineTrack": LineTrack, "FeatureTrack": FeatureTrack, "ReadTrack": ReadTrack };
+
+ view.add_track(new track_types[track_data.track_type]( track_data.name, view, track_data.dataset_id, track_data.prefs) );
view.has_changes = true;
sidebar_box(new_track);
}
@@ -288,80 +209,6 @@
});
});
- view.add_label_track( new LabelTrack( $("#top-labeltrack") ) );
- view.add_label_track( new LabelTrack( $("#nav-labeltrack") ) );
-
- $("#top-labeltrack").bind( "dragstart", function(e) {
- this.drag_origin_x = e.clientX;
- this.drag_origin_pos = e.clientX / $("#viewport-container").width() * (view.high - view.low) + view.low;
- this.drag_div = $("<div />").css( {
- "height": $("#viewport-container").height(), "top": "0px", "position": "absolute",
- "background-color": "#cfc", "border": "1px solid #6a6", "opacity": 0.5
- } ).appendTo( $(this) );
- }).bind( "drag", function(e) {
- var min = Math.min(e.clientX, this.drag_origin_x),
- max = Math.max(e.clientX, this.drag_origin_x),
- span = (view.high - view.low),
- width = $("#viewport-container").width();
-
- $("#low").val(commatize(Math.round(min / width * span) + view.low));
- $("#high").val(commatize(Math.round(max / width * span) + view.low));
- this.drag_div.css( { "left": min + "px", "width": (max - min) + "px" } );
- }).bind( "dragend", function(e) {
- var min = Math.min(e.clientX, this.drag_origin_x),
- max = Math.max(e.clientX, this.drag_origin_x),
- span = (view.high - view.low),
- width = $("#viewport-container").width(),
- old_low = view.low;
-
- view.low = Math.round(min / width * span) + old_low;
- view.high = Math.round(max / width * span) + old_low;
- this.drag_div.remove();
- view.redraw();
- });
-
- $.ajax({
- url: "${h.url_for( action='chroms' )}",
- %if config.get('vis_id'):
- data: { vis_id: view.vis_id },
- %else:
- data: { dbkey: view.dbkey },
- %endif
- dataType: "json",
- success: function ( result ) {
- if (result['reference']) {
- view.add_label_track( new ReferenceTrack() );
- }
- view.chrom_data = result['chrom_info'];
- var chrom_options = '<option value="">Select Chrom/Contig</option>';
- for (i in view.chrom_data) {
- var chrom = view.chrom_data[i]['chrom'];
- chrom_options += '<option value="' + chrom + '">' + chrom + '</option>';
- }
- $("#chrom").html(chrom_options);
- $("#chrom").bind( "change", function () {
- view.chrom = $("#chrom").val();
- var found = $.grep(view.chrom_data, function(v, i) {
- return v.chrom === view.chrom;
- })[0];
- view.max_high = found.len;
- view.reset();
- view.redraw(true);
-
- for (var track_id in view.tracks) {
- var track = view.tracks[track_id];
- if (track.init) {
- track.init();
- }
- }
- view.redraw();
- });
- },
- error: function() {
- alert( "Could not load chroms for this dbkey:", view.dbkey );
- }
- });
-
function sidebar_box(track) {
if (!track.hidden) {
var track_id = track.track_id,
--- a/templates/history/display.mako
+++ b/templates/history/display.mako
@@ -39,8 +39,8 @@
<%def name="render_item_links( history )"><a
- href="${h.url_for( controller='/history', action='imp', id=trans.security.encode_id(history.id) )}"
- class="icon-button import"
+ href="${h.url_for( controller='/history', action='imp', id=trans.security.encode_id(history.id) )}"
+ class="icon-button import"
## Needed to overwide initial width so that link is floated left appropriately.
style="width: 100%"
title="Import history">Import history</a>
--- a/static/trackster.css
+++ b/static/trackster.css
@@ -1,39 +1,32 @@
-#content {
- width: 100%;
-}
-
-#center {
- overflow: auto;
-}
-
-#nav-container {
- position: fixed;
- left: 0;
- bottom: 0;
+.viewport-container {
+ overflow-x: hidden;
+ overflow-y: auto;
}
/*canvas{
border-left: 1px solid green;
border-right: 1px solid red; } /* debugging */
-#nav {
+.nav {
padding: 0 0;
color:#333;font-weight:bold;
}
-#nav-controls {
+.nav-controls {
text-align: center;
background:#cccccc;
background-image:url(style/panel_header_bg.png);
background-position:top center;
background-repeat:repeat-x;
- padding-bottom: 5px;
+ padding: 2px 0;
+}
+.nav-controls input {
+ margin: 0 5px;
+}
+.nav-controls a {
+ padding: 0 0.4em;
}
-#nav-controls a {
- padding: 5px 0.4em;
-}
-
-#overview {
+.overview {
width: 100%;
margin: 0px;
color: white;
@@ -41,7 +34,7 @@
margin-bottom: -4px;
}
-#overview-viewport {
+.overview-viewport {
position: relative;
height: 14px;
/* border-top: solid #666 1px;*/
@@ -51,7 +44,7 @@
border-bottom: solid gray 1px;
margin: 5px 0;
}
-#overview-box {
+.overview-box {
position: absolute;
margin-top: 0px;
height: 14px;
@@ -59,14 +52,14 @@
/*border-style: outset;*/
}
-#viewport {
+.viewport {
/* overflow-x: hidden;*/
background-color: #fff;
/* overflow: scroll;*/
/* border-bottom: 2px solid black;*/
}
-#viewport-canvas {
+.viewport-canvas {
width: 100%;
height: 100px;
}
@@ -122,11 +115,11 @@
margin-left: 5px;
}
-#top-labeltrack {
+.top-labeltrack {
border-bottom: solid #999 1px;
}
-#nav-labeltrack {
+.nav-labeltrack {
border-top: solid #999 1px;
border-bottom: solid #999 1px;
}
1
0

galaxy-dist commit fad6bc5478fa: Add new state colors to blue_colors.ini so that it is synced with the styles, and clean up and add comments to make_style.py.
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 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 1278087396 14400
# Node ID fad6bc5478fa9a4ac4e9d368bcfae1651528deac
# Parent 8f2e44ed27f6a17586854c8e423d2b7b42ca0ae1
Add new state colors to blue_colors.ini so that it is synced with the styles, and clean up and add comments to make_style.py.
--- a/static/june_2007_style/blue/base.css
+++ b/static/june_2007_style/blue/base.css
@@ -73,8 +73,8 @@ span.toolParameterExpandableCollapsable{
ul.toolParameterExpandableCollapsable{list-style:none;}
ul.manage-table-actions{float:right;margin-top:-2.5em;}
ul.manage-table-actions li{display:block;float:left;margin-left:0.5em;}
-.state-color-new{border-color:#A86030;background:#FFB030;}
-.state-color-upload{border-color:#990099;background:#D090D0;}
+.state-color-new{border-color:#A86030;background:FFB030;}
+.state-color-upload{border-color:#6666AA;background:#CCCCFF;}
.state-color-waiting{border-color:#A86030;background:#E8C060;}
.state-color-queued{border-color:#888888;background:#EEEEEE;}
.state-color-running{border-color:#AAAA66;background:#FFFFCC;}
--- a/static/june_2007_style/blue_colors.ini
+++ b/static/june_2007_style/blue_colors.ini
@@ -33,7 +33,15 @@ table_border=#d8b365
footer_bg=#023858
footer_title_bg=#023858
footer_title_hatch=#000000
-# History
+# History - these are actually Job states (used in the reports webapp), but the same styles are used for Tool states in the community webapp
+history_new_border=#A86030
+history_new_bg=FFB030
+history_upload_border=#990099
+history_upload_bg=#D090D0
+history_waiting_border=#A86030
+history_waiting_bg=#E8C060
+history_deleted_border=#330066
+history_deleted_bg=#3399FF
history_error_border=#AA6666
history_error_bg=#FFCCCC
history_running_border=#AAAA66
--- a/static/june_2007_style/process_css.py
+++ b/static/june_2007_style/process_css.py
@@ -9,9 +9,18 @@ CSS processor for Galaxy style sheets. S
"""
-import sys, string, os.path
+import sys, string, os.path, os
+
+new_path = [ os.path.join( os.getcwd(), '..', '..', "lib" ) ]
+new_path.extend( sys.path[1:] ) # remove scripts/ from the path
+sys.path = new_path
+
+from galaxy import eggs
+import pkg_resources
+from galaxy.util.odict import odict
+
from pyparsing import *
-from odict import odict
+#from odict import odict
try:
import Image
--- a/static/june_2007_style/make_style.py
+++ b/static/june_2007_style/make_style.py
@@ -1,13 +1,6 @@
#!/usr/bin/env python
-#from galaxy import eggs
-#import pkg_resources
-#pkg_resources.require("Cheetah")
-
import sys, string, os.path, tempfile, subprocess
-#from galaxy import eggs
-# import pkg_resources
-# pkg_resources.require( "Cheetah" )
# from Cheetah.Template import Template
from subprocess import Popen, PIPE
@@ -15,7 +8,12 @@ from subprocess import Popen, PIPE
assert sys.version_info[:2] >= ( 2, 4 )
# To create a new style ( this is an example ):
-# python make_style.py blue_colors.ini blue
+# In case you have not yet installed required packages:
+# % sudo easy_install pyparsing
+# % sudo easy_install http://effbot.org/downloads/Imaging-1.1.7.tar.gz
+# When you have the above installed, add whatever new style you want to /static/june_2007_style/blue_colors.ini and then:
+# % cd ~/static/june_2007_style/
+# % python make_style.py blue_colors.ini blue
def run( cmd ):
return Popen( cmd, stdout=PIPE).communicate()[0]
@@ -29,7 +27,8 @@ templates = [ ( "base.css.tmpl", "base.c
( "iphone.css.tmpl", "iphone.css" ),
( "reset.css.tmpl", "reset.css" ),
( "autocomplete_tagging.css.tmpl", "autocomplete_tagging.css") ]
-
+
+# TODO: Are these images still being used? If not, clean this code up!
images = [
( "./gradient.py 9 30 $panel_header_bg_top - $panel_header_bg_bottom 0 0 $panel_header_bg_bottom 1 1", "panel_header_bg.png" ),
( "./gradient.py 9 30 $panel_header_bg_bottom - $panel_header_bg_top 0 0 $panel_header_bg_top 1 1", "panel_header_bg_pressed.png" ),
@@ -49,6 +48,7 @@ images = [
( "./circle.py 12 #FFFFFF #D8B365 none> workflow_circle_drag.png" ),
]
+# TODO: Are these shared_images still being used? If not, clean this code up!
shared_images = [
# Dialog boxes
( "ok_large.png", "done_message_bg", "done_message_icon.png" ),
1
0

galaxy-dist commit e7678fd94340: Restructure and document the sample config file.
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 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 1277996648 14400
# Node ID e7678fd94340e969552c8cea825bc2a2cbace68e
# Parent 9dc53a421c64732b11637f6e5698f82b0196f772
Restructure and document the sample config file.
--- a/lib/galaxy/web/buildapp.py
+++ b/lib/galaxy/web/buildapp.py
@@ -154,13 +154,13 @@ def wrap_in_middleware( app, global_conf
# performance
if debug:
# Middleware to check for WSGI compliance
- if asbool( conf.get( 'use_lint', True ) ):
+ if asbool( conf.get( 'use_lint', False ) ):
from paste import lint
app = lint.make_middleware( app, conf )
log.debug( "Enabling 'lint' middleware" )
# Middleware to run the python profiler on each request
if asbool( conf.get( 'use_profile', False ) ):
- import profile
+ from paste.debug import profile
app = profile.ProfileMiddleware( app, conf )
log.debug( "Enabling 'profile' middleware" )
# Middleware that intercepts print statements and shows them on the
--- a/universe_wsgi.ini.sample
+++ b/universe_wsgi.ini.sample
@@ -1,120 +1,173 @@
+#
+# Galaxy is configured by default to be useable in a single-user development
+# environment. To tune the application for a multi-user production
+# environment, see the documentation at:
+#
+# http://bitbucket.org/galaxy/galaxy-central/wiki/Config/ProductionServer
+#
+
+# Throughout this sample configuration file, except where stated otherwise,
+# uncommented values override the default if left unset, whereas commented
+# values are set to the default value.
+# examples of many of these options are explained in more detail in the wiki:
+#
+# Config hackers are encouraged to check there before asking for help.
+
# ---- HTTP Server ----------------------------------------------------------
+# Configuration of the internal HTTP server.
+
[server:main]
+# The internal HTTP server to use. Currently only Paste is provided. This
+# option is required.
use = egg:Paste#http
-port = 8080
-host = 127.0.0.1
-use_threadpool = true
-threadpool_workers = 10
-# ---- HTTP gzip compression ----
-# If planning to run Galaxy as a production service, we recommend running Galaxy
-# through a proxy and enabling gzip compression there (instructions at
-# http://bitbucket.org/galaxy/galaxy-central/wiki/Config/ProductionServer )
-# but you may also turn on Paste's built-in gzip compressor by uncommenting the following lines
-# and also the 'filter-with = gzip' line under [app:main]. This will reduce network traffic
-# and should speed up the interface, especially the visualization module.
-# [filter:gzip]
-# use = egg:Paste#gzip
+# The port on which to listen.
+#port = 8080
-# ---- Galaxy Web Interface -------------------------------------------------
+# The address on which to listen. By default, only listen to localhost (Galaxy
+# will not be accessible over the network). Use '0.0.0.0' to listen on all
+# available network interfaces.
+#host = 127.0.0.1
+
+# Use a threadpool for the web server instead of creating a thread for each
+# request.
+use_threadpool = True
+
+# Number of threads in the web server thread pool.
+#threadpool_workers = 10
+
+# ---- Filters --------------------------------------------------------------
+
+# Filters sit between Galaxy and the HTTP server.
+
+# These filters are disabled by default. They can be enabled with
+# 'filter-with' in the [app:main] section below.
+
+# Define the gzip filter.
+[filter:gzip]
+use = egg:Paste#gzip
+
+# Define the proxy-prefix filter.
+[filter:proxy-prefix]
+use = egg:PasteDeploy#prefix
+prefix = /galaxy
+
+# ---- Galaxy ---------------------------------------------------------------
+
+# Configuration of the Galaxy application.
+
[app:main]
-# Uncomment following line to enable Paste gzip compression
-# filter-with = gzip
+# -- Application and filtering
-# Uncomment following line below to enable visualization module
-# enable_tracks = True
-
-# Specifies the factory for the universe WSGI application
+# The factory for the WSGI application. This should not be changed.
paste.app_factory = galaxy.web.buildapp:app_factory
-# By default, Galaxy uses a SQLite database found here
-database_file = database/universe.sqlite
+# If not running behind a proxy server, you may want to enable gzip compression
+# to decrease the size of data transferred over the network. If using a proxy
+# server, please enable gzip compression there instead.
+#filter-with = gzip
-# You may use a SQLAlchemy connection string to specify an external database
-# instead. PostgreSQL and MySQL are supported.
-#database_connection = postgres:///galaxy
-#database_engine_option_echo = true
-#database_engine_option_echo_pool = true
-#database_engine_option_pool_size = 10
-#database_engine_option_max_overflow = 20
+# If running behind a proxy server and Galaxy is served from a subdirectory,
+# enable the proxy-prefix filter and set the prefix in the
+# [filter:proxy-prefix] section above.
+#filter-with = proxy-prefix
-# If using MySQL, see:
-# http://rapd.wordpress.com/2008/03/02/sqlalchemy-sqlerror-operationalerror-2…
-# To handle this issue, try the following setting:
-#database_engine_option_pool_recycle = 7200
+# -- Database
-# Where dataset files are saved
-file_path = database/files
-# Temporary storage for additional datasets, this should be shared through the cluster
-new_file_path = database/tmp
+# By default, Galaxy uses a SQLite database at 'database/universe.sqlite'. You
+# may use a SQLAlchemy connection string to specify an external database
+# instead. This string takes many options which are explained in detail in the
+# config file documentation.
+#database_connection = sqlite:///./database/universe.sqlite?isolation_level=IMMEDIATE
-# Tools
-tool_config_file = tool_conf.xml
-tool_path = tools
-tool_data_path = tool-data
+# If the server logs errors about not having enough database pool connections,
+# you will want to increase these values, or consider running more Galaxy
+# processes.
+#database_engine_option_pool_size = 5
+#database_engine_option_max_overflow = 10
-# Datatype converters
-datatype_converters_config_file = datatype_converters_conf.xml
-datatype_converters_path = %(here)s/lib/galaxy/datatypes/converters
+# If using MySQL and the server logs the error "MySQL server has gone away",
+# you will want to set this to some positive value (7200 should work).
+#database_engine_option_pool_recycle = -1
-# Datatype indexers
-datatype_indexers_path = %(here)s/lib/galaxy/datatypes/indexers
-# Metadata
-set_metadata_externally = False
+# -- Files and directories
-# Session support (beaker)
-use_beaker_session = True
-session_type = file
-session_data_dir = %(here)s/database/beaker_sessions
-session_key = galaxysessions
-session_secret = changethisinproduction
+# Dataset files are stored in this directory.
+#file_path = database/files
-# Galaxy session security
-id_secret = changethisinproductiontoo
+# Temporary files are stored in this directory.
+#new_file_path = database/tmp
-# Directories of files contained in the following directory can be uploaded to
-# a library from the Admin view
-#library_import_dir = /var/opt/galaxy/import
+# Tool config file, defines what tools are available in Galaxy.
+#tool_config_file = tool_conf.xml
-# The following can be configured to allow non-admin users to upload a
-# directory of files. The configured directory must contain sub-directories
-# named the same as the non-admin user's Galaxy login ( email ). The non-admin
-# user is restricted to uploading files or sub-directories of files contained
-# in their directory.
-#user_library_import_dir = /var/opt/galaxy/import/users
+# Path to the directory containing the tools defined in the config.
+#tool_path = tools
-# The admin library upload tool may contain a box allowing admins to paste
-# filesystem paths to files and directories to add to a library. Set to True
-# to enable. Please note the security implication that this will give Galaxy
-# Admins access to anything your Galaxy user has access to.
-#allow_library_path_paste = False
+# Directory where data used by tools is located, see the samples in that
+# directory and the wiki for help:
+# http://bitbucket.org/galaxy/galaxy-central/wiki/DataIntegration
+#tool_data_path = tool-data
-# path to sendmail
-sendmail_path = /usr/sbin/sendmail
+# Datatypes config file, defines what data (file) types are available in
+# Galaxy.
+#datatypes_config_file = datatypes_conf.xml
-# Address to join mailing list
-mailing_join_addr = galaxy-user-join(a)bx.psu.edu
+# -- Mail and notification
-# For use by 'report this error' link on error-state datasets
-#smtp_server = smtp.example.org
-#error_email_to = galaxy-bugs(a)example.org
+# Galaxy sends mail for various things: Subscribing users to the mailing list
+# if they request it, emailing password resets, notification from the Galaxy
+# Sample Tracking system, and reporting dataset errors. To do this, it needs
+# to send mail through an SMTP server, which you may define here.
+#smtp_server = None
-# Use the new iframe / javascript based layout
-use_new_layout = true
+# On the user registration form, users may choose to join the mailing list.
+# This is the address of the list they'll be subscribed to.
+#mailing_join_addr = galaxy-user-join(a)bx.psu.edu
-# Comma separated list of UCSC / gbrowse / GeneTrack browsers to use for viewing
-ucsc_display_sites = main,test,archaea,ucla
-gbrowse_display_sites = wormbase,tair,modencode_worm,modencode_fly
-# Define your GeneTrack servers in tool-data/shared/genetrack/genetrack_sites.txt
-#genetrack_display_sites =
+# Datasets in an error state include a link to report the error. Those reports
+# will be sent to this address. Error reports are disabled if no address is set.
+#error_email_to = None
-# Enable the (experimental! beta!) Web API. Documentation forthcoming.
-#enable_api = False
+# -- Display sites
-# Serving static files (needed if running standalone)
+# Galaxy can display data at various external browsers. These options specify
+# which browsers should be available. URLs and builds available at these
+# browsers are defined in the specifield files.
+
+# UCSC browsers: tool-data/shared/ucsc/ucsc_build_sites.txt
+#ucsc_display_sites = main,test,archaea,ucla
+
+# GBrowse servers: tool-data/shared/gbrowse/gbrowse_build_sites.txt
+#gbrowse_display_sites = wormbase,tair,modencode_worm,modencode_fly
+
+# GeneTrack servers: tool-data/shared/genetrack/genetrack_sites.txt
+#genetrack_display_sites = main,test
+
+# -- UI Localization
+
+# Append "/{brand}" to the "Galaxy" text in the masthead.
+#brand = None
+
+# The URL linked by the "Galaxy/brand" text.
+#logo_url = /
+
+# The URL linked by the "Galaxy Wiki" link in the "Help" menu.
+#wiki_url = http://bitbucket.org/galaxy/galaxy-central/wiki
+
+# The URL linked by the "Email comments..." link in the "Help" menu.
+#bugs_email = None
+
+# The URL linked by the "How to Cite..." link in the "Help" menu.
+#citation_url = http://bitbucket.org/galaxy/galaxy-central/wiki/Citations
+
+# Serve static content, which must be enabled if you're not serving it via a
+# proxy server. These options should be self explanatory and so are not
+# documented individually. You can use these paths (or ones in the proxy
+# server) to point to your own styles.
static_enabled = True
static_cache_time = 360
static_dir = %(here)s/static/
@@ -123,94 +176,142 @@ static_favicon_dir = %(here)s/static/fav
static_scripts_dir = %(here)s/static/scripts/
static_style_dir = %(here)s/static/june_2007_style/blue
-## Leave these commented out for the defaults at the main galaxy site
-## Uncomment and adjust these to change locally configurable items in the masthead
-## for a local mirror where you are doing private software development
-##
-## Brand: appends "/[brand]" to the "Galaxy" text in the masthead
-## logo_url: replaces the default "Galaxy + brand" link url
-## wiki_url: replaces the default galaxy main wiki
-## bugs_email: replaces the default galaxy bugs email list
-##citation_url: point to a URL listing citations
-#brand = Private local mirror
-#logo_url = /
-#wiki_url = /path/to/my/local/wiki
-#bugs_email = mailto:galaxy-bugs@example.org
-#citation_url = /path/to/my/citations
+# -- Logging and Debugging
-# ---- Logging and Debugging ------------------------------------------------
+# Verbosity of console log messages. Acceptable values can be found here:
+# http://docs.python.org/library/logging.html#logging-levels
+#log_level = DEBUG
-# Verbosity of log messages
-log_level = DEBUG
+# Print database operations to the server log (warning, quite verbose!).
+#database_engine_option_echo = False
-# Log memory usage
-log_memory_usage = False
+# Print database pool operations to the server log (warning, quite verbose!).
+#database_engine_option_echo_pool = False
-# Log events
-log_events = True
+# Turn on logging of application events and some user events to the database.
+#log_events = True
-# Log user actions
-log_actions = True
+# Turn on logging of user actions to the database. Actions currently logged are
+# grid views, tool searches, and use of "recently" used tools menu. The
+# log_events and log_actions functionality will eventually be merged.
+#log_actions = True
-# Configuration for debugging middleware
+# Debug enables access to various config options useful for development and
+# debugging: use_lint, use_profile, use_printdebug and use_interactive. It
+# also causes the files used by PBS/SGE (submission script, output, and error)
+# to remain on disk after the job is complete. Debug mode is disabled if
+# commented, but is uncommented by default in the sample config.
debug = True
-use_lint = False
-# Interactive debugging - NEVER enable this on a public site
+# Check for WSGI compliance.
+#use_lint = False
+
+# Run the Python profiler on each request.
+#use_profile = False
+
+# Intercept print statements and show them on the returned page.
+#use_printdebug = True
+
+# Enable live debugging in your browser. This should NEVER be enabled on a
+# public site. Enabled in the sample config for development.
use_interactive = True
-# Write thread status periodically to 'heartbeat.log' (careful, uses disk space rapidly!)
+# Write thread status periodically to 'heartbeat.log', (careful, uses disk
+# space rapidly!). Useful to determine why your processes may be consuming a
+# lot of CPU.
#use_heartbeat = False
-# Enable the memory debugging interface (careful, negatively impacts server performance)
+# Enable the memory debugging interface (careful, negatively impacts server
+# performance).
#use_memdump = False
-# Profiling middleware (cProfile based)
-#use_profile = False
+# -- Data Libraries
-# ---- Users and Security ---------------------------------------------------
+# These library upload options are described in much more detail in the wiki:
+# http://bitbucket.org/galaxy/galaxy-central/wiki/DataLibraries/UploadingFiles
+
+# Add an option to the library upload form which allows administrators to
+# upload a directory of files.
+#library_import_dir = None
+
+# Add an option to the library upload form which allows authorized
+# non-administrators to upload a directory of files. The configured directory
+# must contain sub-directories named the same as the non-admin user's Galaxy
+# login ( email ). The non-admin user is restricted to uploading files or
+# sub-directories of files contained in their directory.
+#user_library_import_dir = None
+
+# Add an option to the admin library upload tool allowing admins to paste
+# filesystem paths to files and directories in a box, and these paths will be
+# added to a library. Set to True to enable. Please note the security
+# implication that this will give Galaxy Admins access to anything your Galaxy
+# user has access to.
+#allow_library_path_paste = False
+
+# -- Users and Security
+
+# Galaxy encodes various internal values when these values will be output in
+# some format (for example, in a URL or cookie). You should set a key to be
+# used by the algorithm that encodes and decodes these values. It can be any
+# string. If left unchanged, anyone could construct a cookie that would grant
+# them access to others' sessions.
+#id_secret = USING THE DEFAULT IS NOT SECURE!
# User authentication can be delegated to an upstream proxy server (usually
-# Apache). This is explained on the Galaxy wiki:
-#
-# http://g2.trac.bx.psu.edu/wiki/HowToInstall/ApacheProxy
-
-# Use user provided in an upstream server's $REMOTE_USER variable
+# Apache). The upstream proxy should set a REMOTE_USER header in the request.
+# Enabling remote user disables regular logins. For more information, see:
+# http://bitbucket.org/galaxy/galaxy-central/wiki/Config/ApacheProxy
#use_remote_user = False
# If use_remote_user is enabled and your external authentication
-# method just returns bare usernames, set a default mail domain
-#remote_user_maildomain = example.org
+# method just returns bare usernames, set a default mail domain to be appended
+# to usernames, to become your Galaxy usernames (email addresses).
+#remote_user_maildomain = None
-# If use_remote_user is enabled, set a logout anchor href option
-#remote_user_logout_href = /logout
+# If use_remote_user is enabled, you can set this to a URL that will log your
+# users out.
+#remote_user_logout_href = None
-# this should be a comma-separated list of valid Galaxy users
-#admin_users = user1@example.org,user2@example.org
+# Administrative users - set this to a comma-separated list of valid Galaxy
+# users (email addresses). These users will have access to the Admin section
+# of the server, and will have access to create users, groups, roles,
+# libraries, and more. For more information, see:
+# http://bitbucket.org/galaxy/galaxy-central/wiki/Admin/AdminInterface
+#admin_users = None
-# Force everyone to log in (disable anonymous access)
+# Force everyone to log in (disable anonymous access).
#require_login = False
-# Can users register new accounts?
+# Allow unregistered users to create new accounts (otherwise, they will have to
+# be created by an admin).
#allow_user_creation = True
-# Can an admin user delete user accounts?
+# Allow administrators to delete accounts.
#allow_user_deletion = False
-# Should default dataset access permissions be private for new users; default is False (datasets are public)
-new_user_dataset_access_role_default_private = False
+# By default, users' data will be public, but setting this to True will cause
+# it to be private. Does not affect existing users and data, only ones created
+# after this option is set. Users may still change their default back to
+# public.
+#new_user_dataset_access_role_default_private = False
-# ---- Job Execution --------------------------------------------------------
+# -- Beta features
+
+# Enable Galaxy's built-in visualization module, Trackster.
+#enable_tracks = False
+
+# Enable the (experimental! beta!) Web API. Documentation forthcoming.
+#enable_api = False
+
+# -- Job Execution
# If running multiple Galaxy processes, one can be designated as the job
# runner. For more information, see:
# http://bitbucket.org/galaxy/galaxy-central/wiki/Config/LoadBalancing
#enable_job_running = True
-# Number of concurrent jobs to run (local job runner)
-#local_job_queue_workers = 5
-
-# Should jobs be tracked through the database, rather than in memory
+# Should jobs be tracked through the database, rather than in memory.
+# Necessary if you're running the load balanced setup.
#track_jobs_in_database = False
# Enable job recovery (if Galaxy is restarted while cluster jobs are running,
@@ -218,47 +319,50 @@ new_user_dataset_access_role_default_pri
# running more than one Galaxy server using the same database.
#enable_job_recovery = True
-# Job queue cleanup interval in minutes. Currently only used by RoundRobin
-job_queue_cleanup_interval = 30
+# Setting metadata on job outputs to in a separate process (or if using a
+# cluster, on the cluster). Thanks to Python's Global Interpreter Lock and the
+# hefty expense that setting metadata incurs, your Galaxy process may become
+# unresponsive when this operation occurs internally.
+#set_metadata_externally = False
+
+# Number of concurrent jobs to run (local job runner)
+#local_job_queue_workers = 5
# Jobs can be killed after a certain amount of execution time. Format is in
-# hh:mm:ss. Currently only implemented for PBS. Leave commented for
-# unlimited.
-#job_walltime = 10:00:00
+# hh:mm:ss. Currently only implemented for PBS.
+#job_walltime = None
-# Clustering Galaxy is not a straightforward process and requires a lot of
-# pre-configuration. See the ClusteringGalaxy Wiki before attempting to set
-# any of these options:
-#
-# http://g2.trac.bx.psu.edu/wiki/ClusteringGalaxy
-#
-# If running normally (without a cluster), do not change anything in this
-# section.
+# Clustering Galaxy is not a straightforward process and requires some
+# pre-configuration. See the the wiki before attempting to set any of these
+# options:
+# http://bitbucket.org/galaxy/galaxy-central/wiki/Config/Cluster
# Comma-separated list of job runners to start. local is always started. If
# left commented, no jobs will be run on the cluster, even if a cluster URL is
# explicitly defined in the [galaxy:tool_runners] section below. The runners
# currently available are 'pbs' and 'sge'.
-#start_job_runners = pbs
+#start_job_runners = None
# The URL for the default runner to use when a tool doesn't explicity define a
-# runner below. For help on the cluster URL format, see the ClusteringGalaxy
-# Wiki. Leave commented if not using a cluster job runner.
-#default_cluster_job_runner = pbs:///
+# runner below.
+#default_cluster_job_runner = local:///
# The cluster runners have their own thread pools used to prepare and finish
-# jobs (so that these operations do not block normal queue operation). The
-# value here is the number of worker threads available to each runner.
+# jobs (so that these sometimes lengthy operations do not block normal queue
+# operation). The value here is the number of worker threads available to each
+# started runner.
#cluster_job_queue_workers = 3
-# The PBS options are described in detail in the Galaxy Configuration section of
-# the ClusteringGalaxy Wiki, and are only necessary when using file staging.
+# These options are only used when using file staging with PBS.
#pbs_application_server =
#pbs_stage_path =
#pbs_dataset_server =
+# ---- Tool Job Runners -----------------------------------------------------
+
# Individual per-tool job runner overrides. If not listed here, a tool will
# run with the runner defined with default_cluster_job_runner.
+
[galaxy:tool_runners]
biomart = local:///
@@ -270,8 +374,8 @@ ucsc_table_direct_archaea1 = local:///
ucsc_table_direct_test1 = local:///
upload1 = local:///
+# ---- Galaxy Message Queue -------------------------------------------------
-# Galaxy Message Queue
# Galaxy uses AMQ protocol to receive messages from external sources like
# bar code scanners. Galaxy has been tested against RabbitMQ AMQP implementation.
# For Galaxy to receive messages from a message queue the RabbitMQ server has
@@ -279,6 +383,7 @@ upload1 = local:///
# and 'port' fields should point to where the RabbitMQ server is running.
[galaxy_amqp]
+
#host = 127.0.0.1
#port = 5672
#userid = galaxy
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -23,6 +23,7 @@ class ConfigurationError( Exception ):
pass
class Configuration( object ):
+ deprecated_options = ( 'database_file', )
def __init__( self, **kwargs ):
self.config_dict = kwargs
self.root = kwargs.get( 'root_dir', '.' )
@@ -31,7 +32,7 @@ class Configuration( object ):
os.umask( self.umask ) # can't get w/o set, so set it back
self.gid = os.getgid() # if running under newgrp(1) we'll need to fix the group of data created on the cluster
# Database related configuration
- self.database = resolve_path( kwargs.get( "database_file", "database/universe.d" ), self.root )
+ self.database = resolve_path( kwargs.get( "database_file", "database/universe.sqlite" ), self.root )
self.database_connection = kwargs.get( "database_connection", False )
self.database_engine_options = get_database_engine_options( kwargs )
self.database_create_tables = string_as_bool( kwargs.get( "database_create_tables", "True" ) )
@@ -68,7 +69,6 @@ class Configuration( object ):
self.output_size_limit = int( kwargs.get( 'output_size_limit', 0 ) )
self.job_walltime = kwargs.get( 'job_walltime', None )
self.admin_users = kwargs.get( "admin_users", "" )
- self.sendmail_path = kwargs.get('sendmail_path',"/usr/sbin/sendmail")
self.mailing_join_addr = kwargs.get('mailing_join_addr',"galaxy-user-join(a)bx.psu.edu")
self.error_email_to = kwargs.get( 'error_email_to', None )
self.smtp_server = kwargs.get( 'smtp_server', None )
@@ -80,8 +80,8 @@ class Configuration( object ):
self.pbs_stage_path = kwargs.get('pbs_stage_path', "" )
self.use_heartbeat = string_as_bool( kwargs.get( 'use_heartbeat', 'False' ) )
self.use_memdump = string_as_bool( kwargs.get( 'use_memdump', 'False' ) )
- self.log_actions = string_as_bool( kwargs.get( 'log_actions', 'False' ) )
- self.log_events = string_as_bool( kwargs.get( 'log_events', 'False' ) )
+ self.log_actions = string_as_bool( kwargs.get( 'log_actions', 'True' ) )
+ self.log_events = string_as_bool( kwargs.get( 'log_events', 'True' ) )
self.ucsc_display_sites = kwargs.get( 'ucsc_display_sites', "main,test,archaea,ucla" ).lower().split(",")
self.gbrowse_display_sites = kwargs.get( 'gbrowse_display_sites', "wormbase,tair,modencode_worm,modencode_fly" ).lower().split(",")
self.genetrack_display_sites = kwargs.get( 'genetrack_display_sites', "main,test" ).lower().split(",")
@@ -159,7 +159,11 @@ class Configuration( object ):
raise eggs.EggNotFetchable( 'You must scramble the %s egg to use the %s job runner. Instructions are available at:\n http://bitbucket.org/galaxy/galaxy-central/wiki/Config/Cluster' % ( runner_to_egg[runner], runner ) )
except KeyError:
raise Exception( 'No such job runner: %s. Please double-check the value of start_job_runners in universe_wsgi.ini' % runner )
-
+ # Check for deprecated options.
+ for key in self.config_dict.keys():
+ if key in self.deprecated_options:
+ log.warning( "Config option '%s' is deprecated and will be removed in a future release. Please consult the latest version of the sample configuration file." % key )
+
def is_admin_user( self,user ):
"""
Determine if the provided user is listed in `admin_users`.
1
0

16 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 1277995420 14400
# Node ID 9dc53a421c64732b11637f6e5698f82b0196f772
# Parent 65618fab28b83ccfd2b0575e8ecabd1e9cd5815a
Fix user tests.
--- a/templates/webapps/galaxy/user/info.mako
+++ b/templates/webapps/galaxy/user/info.mako
@@ -69,7 +69,7 @@
<p/><div class="toolForm">
- <form name="user_info" id="user_info" action="${h.url_for( controller='user', action='new_address', user_id=user.id, admin_view=admin_view )}" method="post" >
+ <form name="user_addresses" id="user_addresses" action="${h.url_for( controller='user', action='new_address', user_id=user.id, admin_view=admin_view )}" method="post" ><div class="toolFormTitle">User Addresses</div><div class="toolFormBody">
%if user.addresses:
@@ -125,7 +125,7 @@
%if trans.app.config.enable_api:
<div class="toolForm">
- <form name="user_info" id="user_info" action="${h.url_for( controller='user', action='new_api_key', user_id=user.id, admin_view=admin_view )}" method="post" >
+ <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">
--- a/templates/user/reset_password.mako
+++ b/templates/user/reset_password.mako
@@ -6,7 +6,7 @@
%endif
<div class="toolForm">
- <div class="toolFormTitle">Login</div>
+ <div class="toolFormTitle">Reset Password</div><form name="reset_password" id="reset_password" action="${h.url_for( controller='user', action='reset_password' )}" method="post" ><div class="form-row"><label>Email:</label>
--- a/test/base/twilltestcase.py
+++ b/test/base/twilltestcase.py
@@ -824,8 +824,8 @@ class TwillTestCase( unittest.TestCase )
self.home()
self.visit_url( "%s/user/show_info" % self.url )
self.check_page_for_string( "Manage User Information" )
- tc.fv( "1", "email", new_email )
- tc.fv( "1", "username", new_username )
+ tc.fv( "login_info", "email", new_email )
+ tc.fv( "login_info", "username", new_username )
tc.submit( "login_info_button" )
if check_str1:
self.check_page_for_string( check_str1 )
@@ -833,9 +833,9 @@ class TwillTestCase( unittest.TestCase )
self.home()
self.visit_page( "user/show_info" )
self.check_page_for_string( "Manage User Information" )
- tc.fv( "2", "current", password )
- tc.fv( "2", "password", new_password )
- tc.fv( "2", "confirm", new_password )
+ tc.fv( "change_password", "current", password )
+ tc.fv( "change_password", "password", new_password )
+ tc.fv( "change_password", "confirm", new_password )
tc.submit( "change_password_button" )
self.check_page_for_string( 'The password has been changed.' )
def edit_user_info( self, info_values ):
@@ -843,7 +843,7 @@ class TwillTestCase( unittest.TestCase )
self.visit_page( "user/show_info" )
self.check_page_for_string( "Manage User Information" )
for index, info_value in enumerate(info_values):
- tc.fv( "3", "field_%i" % index, info_value )
+ tc.fv( "user_info", "field_%i" % index, info_value )
tc.submit( "edit_user_info_button" )
self.check_page_for_string( "The user information has been updated with the changes." )
for value in info_values:
1
0

galaxy-dist commit 65618fab28b8: lims: removed the db engine echo option in the data transfer
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User rc
# Date 1277991101 14400
# Node ID 65618fab28b83ccfd2b0575e8ecabd1e9cd5815a
# Parent b9bb8c131cd1a3364d3960640c724d2a786636ef
lims: removed the db engine echo option in the data transfer
--- a/scripts/galaxy_messaging/server/galaxydb_interface.py
+++ b/scripts/galaxy_messaging/server/galaxydb_interface.py
@@ -28,7 +28,7 @@ class GalaxyDbInterface(object):
def __init__(self, dbstr):
self.dbstr = dbstr
self.db_engine = create_engine(self.dbstr)
- self.db_engine.echo = True
+ self.db_engine.echo = False
self.metadata = MetaData(self.db_engine)
self.session = sessionmaker(bind=self.db_engine)
self.event_table = Table('sample_event', self.metadata, autoload=True )
1
0

galaxy-dist commit 99f33c73bf81: Removed binseq.zip test and replace it with a zip test.
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 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 1277989166 14400
# Node ID 99f33c73bf815468400426870da113991e459efc
# Parent e110168a83c91f14a2923149e195aa4e8855fde5
Removed binseq.zip test and replace it with a zip test.
--- a/test/functional/test_get_data.py
+++ b/test/functional/test_get_data.py
@@ -90,35 +90,36 @@ class UploadData( TwillTestCase ):
self.check_history_for_string( "File Format' to 'Scf' when uploading scf files" )
self.delete_history( id=self.security.encode_id( history.id ) )
def test_0025_upload_file( self ):
- """Test uploading 1.scf.zip, manually setting the file format"""
+ """Test uploading 4.bed.zip, manually setting the file format"""
self.check_history_for_string( 'Your history is empty' )
history = sa_session.query( galaxy.model.History ) \
.filter( and_( galaxy.model.History.table.c.deleted==False,
galaxy.model.History.table.c.user_id==admin_user.id ) ) \
.order_by( desc( galaxy.model.History.table.c.create_time ) ) \
.first()
- self.upload_file( '1.scf.zip', ftype='binseq.zip' )
+ self.upload_file( '4.bed.zip', ftype='bed' )
hda = sa_session.query( galaxy.model.HistoryDatasetAssociation ) \
.order_by( desc( galaxy.model.HistoryDatasetAssociation.table.c.create_time ) ) \
.first()
assert hda is not None, "Problem retrieving hda from database"
- self.verify_dataset_correctness( '1.scf.zip', hid=str( hda.hid ) )
- self.check_history_for_string( "Archive of 1 binary sequence files</pre>" )
+ self.verify_dataset_correctness( '4.bed', hid=str( hda.hid ) )
+ self.check_history_for_string( "<th>1.Chrom</th><th>2.Start</th><th>3.End</th>" )
self.delete_history( id=self.security.encode_id( history.id ) )
def test_0030_upload_file( self ):
- """Test uploading 1.scf.zip, NOT setting the file format"""
+ """Test uploading 4.bed.zip, NOT setting the file format"""
self.check_history_for_string( 'Your history is empty' )
history = sa_session.query( galaxy.model.History ) \
.filter( and_( galaxy.model.History.table.c.deleted==False,
galaxy.model.History.table.c.user_id==admin_user.id ) ) \
.order_by( desc( galaxy.model.History.table.c.create_time ) ) \
.first()
- self.upload_file( '1.scf.zip' )
+ self.upload_file( '4.bed.zip' )
hda = sa_session.query( galaxy.model.HistoryDatasetAssociation ) \
.order_by( desc( galaxy.model.HistoryDatasetAssociation.table.c.create_time ) ) \
.first()
assert hda is not None, "Problem retrieving hda from database"
- self.check_history_for_string( "'File Format' for archive consisting of binary files - use 'Binseq.zip'" )
+ self.verify_dataset_correctness( '4.bed', hid=str( hda.hid ) )
+ self.check_history_for_string( "<th>1.Chrom</th><th>2.Start</th><th>3.End</th>" )
self.delete_history( id=self.security.encode_id( history.id ) )
def test_0035_upload_file( self ):
"""Test uploading 1.sam NOT setting the file format"""
Binary file test-data/1.scf.zip has changed
Binary file test-data/4.bed.zip has changed
1
0

galaxy-dist commit b9bb8c131cd1: Merge, since I apparently keep forgetting to push my commits.
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 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 1277989841 14400
# Node ID b9bb8c131cd1a3364d3960640c724d2a786636ef
# Parent 99f33c73bf815468400426870da113991e459efc
# Parent 10027d1e6dc31200319f9d90a4d8233170393f2b
Merge, since I apparently keep forgetting to push my commits.
--- a/lib/galaxy/web/controllers/requests_admin.py
+++ b/lib/galaxy/web/controllers/requests_admin.py
@@ -4,9 +4,10 @@ from galaxy.model.orm import *
from galaxy.datatypes import sniff
from galaxy import model, util
from galaxy.util.streamball import StreamBall
-import logging, tempfile, zipfile, tarfile, os, sys, subprocess
+import logging, tempfile, zipfile, tarfile, os, sys, subprocess, smtplib, socket
from galaxy.web.form_builder import *
from datetime import datetime, timedelta
+from email.MIMEText import MIMEText
from galaxy.web.controllers.forms import get_all_forms
from sqlalchemy.sql.expression import func, and_
from sqlalchemy.sql import select
@@ -474,13 +475,21 @@ class RequestsAdmin( BaseController ):
trans.sa_session.flush()
# now that the request is complete send the email notification to the
# the user
- if request.notify:
- mail = os.popen("%s -t" % trans.app.config.sendmail_path, 'w')
- subject = "Galaxy Sample Tracking: '%s' sequencing request in complete." % request.name
- body = "The '%s' sequencing request (type: %s) is now complete. Datasets from all the samples are now available for analysis or download from the respective data libraries in Galaxy." % (request.name, request.type.name)
- email_content = "To: %s\nFrom: no-reply(a)nowhere.edu\nSubject: %s\n\n%s" % (request.user.email, subject, body)
- mail.write( email_content )
- x = mail.close()
+ if request.notify and trans.app.config.smtp_server is not None:
+ host = trans.request.host.split(':')[0]
+ if host == 'localhost':
+ host = socket.getfqdn()
+ msg = MIMEText( "The '%s' sequencing request (type: %s) is now complete. Datasets from all the samples are now available for analysis or download from the respective data libraries in Galaxy." % ( request.name, request.type.name ) )
+ to = msg[ 'To' ] = request.user.email
+ frm = msg[ 'From' ] = 'galaxy-no-reply@' + host
+ msg[ 'Subject' ] = "Galaxy Sample Tracking: '%s' sequencing request in complete." % request.name
+ try:
+ s = smtplib.SMTP()
+ s.connect( trans.app.config.smtp_server )
+ s.sendmail( frm, [ to ], msg.as_string() )
+ s.close()
+ except:
+ pass
@web.expose
@web.require_admin
1
0

galaxy-dist commit e110168a83c9: lims: added option to rename sequencer datasets by prepending the experiment name.
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User rc
# Date 1277922255 14400
# Node ID e110168a83c91f14a2923149e195aa4e8855fde5
# Parent 428ce38ba8e92af0d2099a381e38863e09b46cd1
lims: added option to rename sequencer datasets by prepending the experiment name.
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -1554,6 +1554,7 @@ class RequestEvent( object ):
class RequestType( object ):
rename_dataset_options = Bunch( NO = 'Do not rename',
SAMPLE_NAME = 'Preprend sample name',
+ EXPERIMENT_NAME = 'Prepend experiment name',
EXPERIMENT_AND_SAMPLE_NAME = 'Prepend experiment and sample name')
permitted_actions = get_permitted_actions( filter='REQUEST_TYPE' )
def __init__(self, name=None, desc=None, request_form=None, sample_form=None,
--- a/lib/galaxy/web/controllers/requests_admin.py
+++ b/lib/galaxy/web/controllers/requests_admin.py
@@ -673,6 +673,8 @@ class RequestsAdmin( BaseController ):
return sample.name+'_'+name
elif opt == sample.request.type.rename_dataset_options.EXPERIMENT_AND_SAMPLE_NAME:
return sample.request.name+'_'+sample.name+'_'+name
+ elif opt == sample.request.type.rename_dataset_options.EXPERIMENT_NAME:
+ return sample.request.name+'_'+name
def __setup_datatx_user(self, trans, library, folder):
'''
This method sets up the datatx user:
1
0

galaxy-dist commit 428ce38ba8e9: Fixed issue #345 - Incorporated Brad's patch which handles skipping of order checking for join version >=7, which would otherwise end in an error. Also modified sort command to better handle cases with duplicate keys. A new test-case to test these scenarios has been added as well.
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User guru
# Date 1277915778 14400
# Node ID 428ce38ba8e92af0d2099a381e38863e09b46cd1
# Parent ddddd07724227dadeb37ffa2528a87e361787d8f
Fixed issue #345 - Incorporated Brad's patch which handles skipping of order checking for join version >=7, which would otherwise end in an error. Also modified sort command to better handle cases with duplicate keys. A new test-case to test these scenarios has been added as well.
--- a/tools/filters/joinWrapper.py
+++ b/tools/filters/joinWrapper.py
@@ -3,7 +3,7 @@
"""
This tool provides the UNIX "join" functionality.
"""
-import sys, os, tempfile
+import sys, os, tempfile, subprocess
def stop_err(msg):
sys.stderr.write(msg)
@@ -22,8 +22,8 @@ def main():
try:
#Sort the two files based on specified fields
- os.system("sort -t $'\t' -k %d -o %s %s" %(field1, tmpfile1.name, infile1))
- os.system("sort -t $'\t' -k %d -o %s %s" %(field2, tmpfile2.name, infile2))
+ os.system("sort -t $'\t' -k %d,%d -o %s %s" %(field1, field1, tmpfile1.name, infile1))
+ os.system("sort -t $'\t' -k %d,%d -o %s %s" %(field2, field2, tmpfile2.name, infile2))
except Exception, exc:
stop_err( 'Initialization error -> %s' %str(exc) )
@@ -39,10 +39,28 @@ def main():
option = option + ",1." + str(j)
break
+ #check if join has --version option. BSD join doens't have this option, while GNU join does.
+ #The return value in the latter case will be 0, and non-zero in the latter case.
+ ret = subprocess.call('join --version 2>/dev/null', shell=True)
+ # check if we are a version later than 7 of join. If so, we want to skip
+ # checking the order since join will raise an error with duplicated items in
+ # the two files being joined.
+ if ret == 0:
+ cl = subprocess.Popen(["join", "--version"], stdout=subprocess.PIPE)
+ (stdout, _) = cl.communicate()
+ version_line = stdout.split("\n")[0]
+ (version, _) = version_line.split()[-1].split(".")
+ if int(version) >= 7:
+ flags = "--nocheck-order"
+ else:
+ flags = ""
+ else:
+ flags = ""
+
if mode == "V":
- cmdline = "join -t $'\t' -v 1 -o %s -1 %d -2 %d %s %s > %s" %(option, field1, field2, tmpfile1.name, tmpfile2.name, outfile)
+ cmdline = "join %s -t $'\t' -v 1 -o %s -1 %d -2 %d %s %s > %s" %(flags, option, field1, field2, tmpfile1.name, tmpfile2.name, outfile)
else:
- cmdline = "join -t $'\t' -o %s -1 %d -2 %d %s %s > %s" %(option, field1, field2, tmpfile1.name, tmpfile2.name, outfile)
+ cmdline = "join %s -t $'\t' -o %s -1 %d -2 %d %s %s > %s" %(flags, option, field1, field2, tmpfile1.name, tmpfile2.name, outfile)
try:
os.system(cmdline)
--- /dev/null
+++ b/test-data/fs-compare-2.dat
@@ -0,0 +1,40 @@
+chr10 55251623 55253124 CCDS7248.1_cds_0_0_chr10_55251624_r 0 -
+chr11 116124407 116124501 CCDS8374.1_cds_0_0_chr11_116124408_r 0 -
+chr11 116206508 116206563 CCDS8377.1_cds_0_0_chr11_116206509_f 0 +
+chr11 116211733 116212337 CCDS8378.1_cds_0_0_chr11_116211734_r 0 -
+chr11 1812377 1812407 CCDS7726.1_cds_0_0_chr11_1812378_f 0 +
+chr12 38440094 38440321 CCDS8736.1_cds_0_0_chr12_38440095_r 0 -
+chr13 112381694 112381953 CCDS9526.1_cds_0_0_chr13_112381695_f 0 +
+chr14 98710240 98712285 CCDS9949.1_cds_0_0_chr14_98710241_r 0 -
+chr15 41486872 41487060 CCDS10096.1_cds_0_0_chr15_41486873_r 0 -
+chr15 41673708 41673857 CCDS10097.1_cds_0_0_chr15_41673709_f 0 +
+chr15 41679161 41679250 CCDS10098.1_cds_0_0_chr15_41679162_r 0 -
+chr15 41826029 41826196 CCDS10101.1_cds_0_0_chr15_41826030_f 0 +
+chr16 142908 143003 CCDS10397.1_cds_0_0_chr16_142909_f 0 +
+chr16 179963 180135 CCDS10401.1_cds_0_0_chr16_179964_r 0 -
+chr16 244413 244681 CCDS10402.1_cds_0_0_chr16_244414_f 0 +
+chr16 259268 259383 CCDS10403.1_cds_0_0_chr16_259269_r 0 -
+chr18 23786114 23786321 CCDS11891.1_cds_0_0_chr18_23786115_r 0 -
+chr18 59406881 59407046 CCDS11985.1_cds_0_0_chr18_59406882_f 0 +
+chr18 59455932 59456337 CCDS11986.1_cds_0_0_chr18_59455933_r 0 -
+chr18 59600586 59600754 CCDS11988.1_cds_0_0_chr18_59600587_f 0 +
+chr19 59068595 59069564 CCDS12866.1_cds_0_0_chr19_59068596_f 0 +
+chr19 59236026 59236146 CCDS12872.1_cds_0_0_chr19_59236027_r 0 -
+chr19 59297998 59298008 CCDS12877.1_cds_0_0_chr19_59297999_f 0 +
+chr19 59302168 59302288 CCDS12878.1_cds_0_0_chr19_59302169_r 0 -
+chr20 33330413 33330423 CCDS13249.1_cds_0_0_chr20_33330414_r 0 -
+chr20 33513606 33513792 CCDS13255.1_cds_0_0_chr20_33513607_f 0 +
+chr20 33579500 33579527 CCDS13256.1_cds_0_0_chr20_33579501_r 0 -
+chr20 33593260 33593348 CCDS13257.1_cds_0_0_chr20_33593261_f 0 +
+chr21 32707032 32707192 CCDS13614.1_cds_0_0_chr21_32707033_f 0 +
+chr21 32869641 32870022 CCDS13615.1_cds_0_0_chr21_32869642_r 0 -
+chr21 33321040 33322012 CCDS13620.1_cds_0_0_chr21_33321041_f 0 +
+chr21 33744994 33745040 CCDS13625.1_cds_0_0_chr21_33744995_r 0 -
+chr22 30120223 30120265 CCDS13897.1_cds_0_0_chr22_30120224_f 0 +
+chr22 30160419 30160661 CCDS13898.1_cds_0_0_chr22_30160420_r 0 -
+chr22 30665273 30665360 CCDS13901.1_cds_0_0_chr22_30665274_f 0 +
+chr22 30939054 30939266 CCDS13903.1_cds_0_0_chr22_30939055_r 0 -
+chrX 122745047 122745924 CCDS14606.1_cds_0_0_chrX_122745048_f 0 +
+chrX 152648964 152649196 CCDS14733.1_cds_0_0_chrX_152648965_r 0 -
+chrX 152691446 152691471 CCDS14735.1_cds_0_0_chrX_152691447_f 0 +
+chrX 152694029 152694263 CCDS14736.1_cds_0_0_chrX_152694030_r 0 -
--- a/tools/filters/compare.xml
+++ b/tools/filters/compare.xml
@@ -1,4 +1,4 @@
-<tool id="comp1" name="Compare two Queries" version="1.0.1">
+<tool id="comp1" name="Compare two Queries" version="1.0.2"><description>to find common or distinct rows</description><command interpreter="python">joinWrapper.py $input1 $input2 $field1 $field2 $mode $out_file1</command><inputs>
@@ -27,6 +27,15 @@
<param name="mode" value="N"/><output name="out_file1" file="fs-compare.dat"/></test>
+ <!--test case with duplicated key values-->
+ <test>
+ <param name="input1" value="1.bed"/>
+ <param name="input2" value="3.bed"/>
+ <param name="field1" value="1"/>
+ <param name="field2" value="1"/>
+ <param name="mode" value="V"/>
+ <output name="out_file1" file="fs-compare-2.dat"/>
+ </test></tests><help>
1
0

galaxy-dist commit ddddd0772422: Remove outdated about.rst/html.
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 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 1277910554 14400
# Node ID ddddd07724227dadeb37ffa2528a87e361787d8f
# Parent c1172bae7b0fc41e4bd52bfe73c130309c2387fe
Remove outdated about.rst/html.
--- a/static/about.html
+++ /dev/null
@@ -1,78 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-<meta name="generator" content="Docutils 0.3.9: http://docutils.sourceforge.net/" />
-<title>ABOUT GALAXY</title>
-<link rel="stylesheet" href="/static/style/base.css" type="text/css" />
-<link rel="stylesheet" href="/static/style/help.css" type="text/css" />
-</head>
-<body>
-<div class="document" id="about-galaxy">
-<h1 class="title">ABOUT GALAXY</h1>
-<div class="section" id="people">
-<h2><a name="people">People:</a></h2>
-<ul class="simple">
-<li>Istvan Albert - <em>Co-PI</em></li>
-<li>Daniel Blankenberg - <em>Graduate student</em></li>
-<li>Richard Burhans - <em>Sysadmin</em></li>
-<li>Laura Elnitski - <em>Co-PI</em></li>
-<li>Belinda Giardine - <em>Programmer</em></li>
-<li>Ross Hardison - <em>Co-PI</em></li>
-<li>Jim Kent - <em>Co-PI</em></li>
-<li>David King - <em>Graduate student</em></li>
-<li>Webb Miller - <em>Co-PI</em></li>
-<li>Anton Nekrutenko - <em>PI</em></li>
-<li>Cathy Riemer - <em>Programmer</em></li>
-<li>Prachi Shah - <em>Programmer</em></li>
-<li>Ian Schenck - <em>Undergraduate student</em></li>
-<li>James Taylor - <em>Graduate student</em></li>
-<li>Yi Zhang - <em>Programmer</em></li>
-</ul>
-</div>
-<hr class="docutils" />
-<div class="section" id="development">
-<h2><a name="development">Development:</a></h2>
-<p><strong>Galaxy core</strong> - <em>Istvan Albert</em> and <em>James Taylor</em></p>
-<p><strong>Interface</strong> - <em>James Taylor</em>, <em>Istvan Albert</em>, <em>Anton Nekrutenko</em></p>
-<p><strong>Tools</strong> -</p>
-<ul class="simple">
-<li>Data sources - <em>Istvan Albert</em></li>
-<li>EMBOSS, Phylip, mafs - <em>Daniel Blankenberg</em></li>
-<li>Text Tools, Sequence Extractors - <em>Richard Burhans</em>, <em>Anton Nekrutenko</em>, <em>Ian Schenck</em></li>
-<li>Operations - <em>James Taylor</em>, <em>Yi Zhang</em></li>
-<li>Statistics, Graphs - <em>Istvan Albert</em>, <em>David King</em></li>
-</ul>
-<p><strong>Project management</strong> - <em>Anton Nekrutenko</em></p>
-<p>Current version of Galaxy uses many ideas developed by <em>Laura Elnitski</em>, <em>Belinda Giardine</em>, and <em>Cathy Riemer</em> in Galaxy1 and GALA.</p>
-</div>
-<hr class="docutils" />
-<div class="section" id="special-thanks-to">
-<h2><a name="special-thanks-to">Special thanks to:</a></h2>
-<ul class="simple">
-<li>David Haussler</li>
-<li>Ewan Birney</li>
-<li>Hiram Clawson</li>
-<li>Angie Hinrichs</li>
-<li>Darin London</li>
-<li>Members of Hardison, Makova, Miller, and Nekrutenko Labs</li>
-</ul>
-</div>
-<hr class="docutils" />
-<div class="section" id="technology">
-<h2><a name="technology">Technology:</a></h2>
-<p>Galaxy core is written entirely in Python. It uses the following open source technologies:</p>
-<ul class="simple">
-<li>web-server <a class="reference" href="http://www.cherrypy.org/">CherryPy</a></li>
-<li>database <a class="reference" href="http://www.sleepycat.com/products/db.shtml">BerkelyDB</a></li>
-<li>XML library <a class="reference" href="http://effbot.org/zone/element-index.htm">ElementTree</a></li>
-<li>HTML templating library <a class="reference" href="http://www.cheetahtemplate.org/">Cheetah</a></li>
-</ul>
-<p>Galaxy API generated from source code can be found <a class="reference" href="http://www.bx.psu.edu/trac/local/docs/index.html">here</a></p>
-<hr class="docutils" />
-<p><a class="reference" href="http://www.bx.psu.edu">The Center for Comparative Genomics and Bioinformatics</a> at <a class="reference" href="http://www.psu.edu">Penn State</a> | 2005</p>
-</div>
-</div>
-</body>
-</html>
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,6 @@
static: static/welcome.html
static: static/help.html
static: static/galaxyIndex.html
-static: static/about.html
%.html : %.rst
./modules/rst2html.py --stylesheet="/static/help.css" --initial-header-level=2 < $< > $@
--- a/static/about.rst
+++ /dev/null
@@ -1,82 +0,0 @@
-ABOUT GALAXY
-============
-
-People:
--------
-
-* Istvan Albert - *Co-PI*
-* Daniel Blankenberg - *Graduate student*
-* Richard Burhans - *Sysadmin*
-* Laura Elnitski - *Co-PI*
-* Belinda Giardine - *Programmer*
-* Ross Hardison - *Co-PI*
-* Jim Kent - *Co-PI*
-* David King - *Graduate student*
-* Webb Miller - *Co-PI*
-* Anton Nekrutenko - *PI*
-* Cathy Riemer - *Programmer*
-* Prachi Shah - *Programmer*
-* Ian Schenck - *Undergraduate student*
-* James Taylor - *Graduate student*
-* Yi Zhang - *Programmer*
-
------
-
-Development:
-------------
-
-**Galaxy core** - *Istvan Albert* and *James Taylor*
-
-**Interface** - *James Taylor*, *Istvan Albert*, *Anton Nekrutenko*
-
-**Tools** -
-
-* Data sources - *Istvan Albert*
-* EMBOSS, Phylip, mafs - *Daniel Blankenberg*
-* Text Tools, Sequence Extractors - *Richard Burhans*, *Anton Nekrutenko*, *Ian Schenck*
-* Operations - *James Taylor*, *Yi Zhang*
-* Statistics, Graphs - *Istvan Albert*, *David King*
-
-**Project management** - *Anton Nekrutenko*
-
-Current version of Galaxy uses many ideas developed by *Laura Elnitski*, *Belinda Giardine*, and *Cathy Riemer* in Galaxy1 and GALA.
-
------
-
-Special thanks to:
-------------------
-
-* David Haussler
-* Ewan Birney
-* Hiram Clawson
-* Angie Hinrichs
-* Darin London
-* Members of Hardison, Makova, Miller, and Nekrutenko Labs
-
------
-
-Technology:
------------
-
-Galaxy core is written entirely in Python. It uses the following open source technologies:
-
-* web-server `CherryPy`__
-* database `BerkelyDB`__
-* XML library `ElementTree`__
-* HTML templating library `Cheetah`__
-
-Galaxy API generated from source code can be found `here`__
-
-.. __: http://www.cherrypy.org/
-.. __: http://www.sleepycat.com/products/db.shtml
-.. __: http://effbot.org/zone/element-index.htm
-.. __: http://www.cheetahtemplate.org/
-.. __: http://www.bx.psu.edu/trac/local/docs/index.html
-
------
-
-`The Center for Comparative Genomics and Bioinformatics`__ at `Penn State`__ | 2005
-
-.. __: http://www.bx.psu.edu
-.. __: http://www.psu.edu
-
1
0

galaxy-dist commit c1172bae7b0f: lims: requests/request_admin controller refactor
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User rc
# Date 1277909210 14400
# Node ID c1172bae7b0fc41e4bd52bfe73c130309c2387fe
# Parent 9289b4d7fa4d1d1b630c044dfbc102cf870f62e3
lims: requests/request_admin controller refactor
A new controller called requests_common now handles all common tasks like create/edit/delete requests & samples. The requests controller has only the grid definition and requests_admin controller has the request_type code and the sequencer data transfer code.
Also fixed a form importer bug in forms.py
--- /dev/null
+++ b/templates/requests/common/sample_events.mako
@@ -0,0 +1,65 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+<%def name="title()">Events for Sample ${sample.name}</%def>
+
+<h2>Events for Sample "${sample.name}"</h2>
+<ul class="manage-table-actions">
+ <li>
+ <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='show', id=trans.security.encode_id(sample.request.id) )}">
+ <span>Browse this request</span></a>
+ </li>
+</ul>
+<h3>User: ${sample.request.user.email}</h3>
+
+%if message:
+ ${render_msg( message, status )}
+%endif
+
+<div class="toolForm">
+ <table class="grid">
+ <thead>
+ <tr>
+ <th>State</th>
+ <th>Description</th>
+ <th>Last Update</th>
+ <th>Comments</th>
+ </tr>
+ </thead>
+ <tbody>
+ %for state, desc, updated, comments in events_list:
+ <tr class="libraryRow libraryOrFolderRow" id="libraryRow">
+ <td><b><a>${state}</a></b></td>
+ <td><a>${desc}</a></td>
+ <td><a>${updated}</a></td>
+ <td><a>${comments}</a></td>
+ </tr>
+ %endfor
+ </tbody>
+ </table>
+</div>
+%if cntrller == 'requests_admin' and trans.user_is_admin():
+ <div class="toolForm">
+ <div class="toolFormTitle">Change current state</div>
+ <div class="toolFormBody">
+ <form name="event" action="${h.url_for( controller='requests_admin', action='save_state', new=True, sample_id=sample.id)}" method="post" >
+ %for w in widgets:
+ <div class="form-row">
+ <label>
+ ${w[0]}
+ </label>
+ ${w[1].get_html()}
+ %if w[0] == 'Comments':
+ <div class="toolParamHelp" style="clear: both;">
+ Optional
+ </div>
+ %endif
+ </div>
+ %endfor
+ <div class="form-row">
+ <input type="submit" name="add_event_button" value="Save"/>
+ </div>
+ </form>
+ </div>
+ </div>
+%endif
--- a/test/functional/test_forms_and_requests.py
+++ b/test/functional/test_forms_and_requests.py
@@ -189,7 +189,7 @@ class TestFormsAndRequests( TwillTestCas
# Make sure the request_type is not accessible by regular_user2 since regular_user2 does not have Role1.
self.logout()
self.login( email=regular_user2.email )
- self.visit_url( '%s/requests/new?create=True&select_request_type=%i' % (self.url, request_type.id) )
+ self.visit_url( '%s/requests_common/new?cntrller=requests&select_request_type=True' % self.url )
try:
self.check_page_for_string( 'There are no request types created for a new request.' )
raise AssertionError, 'The request_type %s is accessible by %s when it should be restricted' % ( request_type.name, regular_user2.email )
@@ -304,12 +304,12 @@ class TestFormsAndRequests( TwillTestCas
self.logout()
self.login( email='test(a)bx.psu.edu' )
self.check_request_admin_grid(state=request_one.states.SUBMITTED, request_name=request_one.name)
- self.visit_url( "%s/requests_admin/list?sort=-create_time&operation=show_request&id=%s" \
+ self.visit_url( "%s/requests_admin/list?operation=show&id=%s" \
% ( self.url, self.security.encode_id( request_one.id ) ))
self.check_page_for_string( 'Sequencing Request "%s"' % request_one.name )
# set bar codes for the samples
bar_codes = [ '1234567890', '0987654321' ]
- self.add_bar_codes( request_one.id, request_one.name, bar_codes )
+ self.add_bar_codes( request_one.id, request_one.name, bar_codes, request_one.samples )
# change the states of all the samples of this request
for sample in request_one.samples:
self.change_sample_state( sample.name, sample.id, request_type.states[1].id, request_type.states[1].name )
@@ -328,11 +328,12 @@ class TestFormsAndRequests( TwillTestCas
self.login( email='test(a)bx.psu.edu' )
request_name = "RequestTwo"
# simulate request creation
- url_str = '%s/requests_admin/new?create=True&create_request_button=Save&select_request_type=%i&select_user=%i&name=%s&library_id=%i&folder_id=%i&refresh=True&field_2=%s&field_0=%s&field_1=%i' \
- % ( self.url, request_type.id, regular_user.id, request_name, library_one.id, library_one.root_folder.id, "field_2_value", 'option1', user_address.id )
+ url_str = '%s/requests_common/new?cntrller=requests_admin&create_request_button=Save&select_request_type=%i&select_user=%i&name=%s&refresh=True&field_2=%s&field_0=%s&field_1=%i' \
+ % ( self.url, request_type.id, regular_user.id, request_name, "field_2_value", 'option1', user_address.id )
+ print url_str
self.home()
self.visit_url( url_str )
- self.check_page_for_string( "The new request named %s has been created" % request_name )
+ self.check_page_for_string( "The new request named <b>%s</b> has been created" % request_name )
global request_two
request_two = sa_session.query( galaxy.model.Request ) \
.filter( and_( galaxy.model.Request.table.c.name==request_name,
@@ -370,44 +371,44 @@ class TestFormsAndRequests( TwillTestCas
# check if the request's state is now set to 'submitted'
assert request_two.state is not request_two.states.REJECTED, "The state of the request '%s' should be set to '%s'" \
% ( request_two.name, request_two.states.REJECTED )
- def test_055_reset_data_for_later_test_runs( self ):
- """Reseting data to enable later test runs to pass"""
- # Logged in as admin_user
- # remove the request_type permissions
- rt_actions = sa_session.query( galaxy.model.RequestTypePermissions ) \
- .filter(and_(galaxy.model.RequestTypePermissions.table.c.request_type_id==request_type.id) ) \
- .order_by( desc( galaxy.model.RequestTypePermissions.table.c.create_time ) ) \
- .all()
- for a in rt_actions:
- sa_session.delete( a )
- sa_session.flush()
- ##################
- # Purge all libraries
- ##################
- for library in [ library_one ]:
- self.delete_library_item( 'library_admin',
- self.security.encode_id( library.id ),
- self.security.encode_id( library.id ),
- library.name,
- item_type='library' )
- self.purge_library( self.security.encode_id( library.id ), library.name )
- ##################
- # Eliminate all non-private roles
- ##################
- for role in [ role_one, role_two ]:
- self.mark_role_deleted( self.security.encode_id( role.id ), role.name )
- self.purge_role( self.security.encode_id( role.id ), role.name )
- # Manually delete the role from the database
- sa_session.refresh( role )
- sa_session.delete( role )
- sa_session.flush()
- ##################
- # Eliminate all groups
- ##################
- for group in [ group_one ]:
- self.mark_group_deleted( self.security.encode_id( group.id ), group.name )
- self.purge_group( self.security.encode_id( group.id ), group.name )
- # Manually delete the group from the database
- refresh( group )
- sa_session.delete( group )
- sa_session.flush()
+# def test_055_reset_data_for_later_test_runs( self ):
+# """Reseting data to enable later test runs to pass"""
+# # Logged in as admin_user
+# # remove the request_type permissions
+# rt_actions = sa_session.query( galaxy.model.RequestTypePermissions ) \
+# .filter(and_(galaxy.model.RequestTypePermissions.table.c.request_type_id==request_type.id) ) \
+# .order_by( desc( galaxy.model.RequestTypePermissions.table.c.create_time ) ) \
+# .all()
+# for a in rt_actions:
+# sa_session.delete( a )
+# sa_session.flush()
+# ##################
+# # Purge all libraries
+# ##################
+# for library in [ library_one ]:
+# self.delete_library_item( 'library_admin',
+# self.security.encode_id( library.id ),
+# self.security.encode_id( library.id ),
+# library.name,
+# item_type='library' )
+# self.purge_library( self.security.encode_id( library.id ), library.name )
+# ##################
+# # Eliminate all non-private roles
+# ##################
+# for role in [ role_one, role_two ]:
+# self.mark_role_deleted( self.security.encode_id( role.id ), role.name )
+# self.purge_role( self.security.encode_id( role.id ), role.name )
+# # Manually delete the role from the database
+# sa_session.refresh( role )
+# sa_session.delete( role )
+# sa_session.flush()
+# ##################
+# # Eliminate all groups
+# ##################
+# for group in [ group_one ]:
+# self.mark_group_deleted( self.security.encode_id( group.id ), group.name )
+# self.purge_group( self.security.encode_id( group.id ), group.name )
+# # Manually delete the group from the database
+# refresh( group )
+# sa_session.delete( group )
+# sa_session.flush()
--- a/templates/admin/requests/show_request.mako
+++ /dev/null
@@ -1,528 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-<%namespace file="/requests/sample_state.mako" import="render_sample_state" />
-<%namespace file="/requests/sample_datasets.mako" import="render_sample_datasets" />
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-<script type="text/javascript">
-$( function() {
- $( "select[refresh_on_change='true']").change( function() {
- var refresh = false;
- var refresh_on_change_values = $( this )[0].attributes.getNamedItem( 'refresh_on_change_values' )
- if ( refresh_on_change_values ) {
- refresh_on_change_values = refresh_on_change_values.value.split( ',' );
- var last_selected_value = $( this )[0].attributes.getNamedItem( 'last_selected_value' );
- for( i= 0; i < refresh_on_change_values.length; i++ ) {
- if ( $( this )[0].value == refresh_on_change_values[i] || ( last_selected_value && last_selected_value.value == refresh_on_change_values[i] ) ){
- refresh = true;
- break;
- }
- }
- }
- else {
- refresh = true;
- }
- if ( refresh ){
- $( "#show_request" ).submit();
- }
- });
-});
-</script>
-
-
-<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">
- // Looks for changes in sample states using an async request. Keeps
- // calling itself (via setTimeout) until all samples are in a terminal
- // state.
- var updater = function ( sample_states ) {
- // Check if there are any items left to track
- var empty = true;
- for ( i in sample_states ) {
- empty = false;
- break;
- }
- if ( ! empty ) {
- setTimeout( function() { updater_callback( sample_states ) }, 1000 );
- }
- };
- var updater_callback = function ( sample_states ) {
- // Build request data
- var ids = []
- var states = []
- $.each( sample_states, function ( id, state ) {
- ids.push( id );
- states.push( state );
- });
- // Make ajax call
- $.ajax( {
- type: "POST",
- url: "${h.url_for( controller='requests_admin', action='sample_state_updates' )}",
- dataType: "json",
- data: { ids: ids.join( "," ), states: states.join( "," ) },
- success : function ( data ) {
- $.each( data, function( id, val ) {
- // Replace HTML
- var cell1 = $("#sampleState-" + id);
- cell1.html( val.html_state );
- var cell2 = $("#sampleDatasets-" + id);
- cell2.html( val.html_datasets );
- sample_states[ parseInt(id) ] = val.state;
- });
- updater( sample_states );
- },
- error: function() {
- // Just retry, like the old method, should try to be smarter
- updater( sample_states );
- }
- });
- };
-</script>
-
-<style type="text/css">
-.msg_head {
- padding: 0px 0px;
- cursor: pointer;
-}
-</style>
-
-<script type="text/javascript">
- function stopRKey(evt) {
- var evt = (evt) ? evt : ((event) ? event : null);
- var node = (evt.target) ? evt.target : ((evt.srcElement) ? evt.srcElement : null);
- if ((evt.keyCode == 13) && (node.type=="text")) {return false;}
- }
- document.onkeypress = stopRKey
-</script>
-
-%if request.submitted():
- <% samples_not_ready = request.sequence_run_ready() %>
- %if samples_not_ready:
- ${render_msg( "Select a target library and folder for all the samples before starting the sequence run", "warning" )}
- %endif
-%endif
-
-%if request.rejected():
- ${render_msg( "Reason for rejection: "+request.last_comment(), "warning" )}
-%endif
-
-<div class="grid-header">
- <h2>Sequencing Request "${request.name}"</h2>
-</div>
-
-<ul class="manage-table-actions">
-
- %if request.unsubmitted() and request.samples:
- <li>
- <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='requests_admin', action='list', operation='Submit', id=trans.security.encode_id(request.id) )}">
- <span>Submit request</span></a>
- </li>
- %endif
- %if request.submitted():
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='Reject', id=trans.security.encode_id(request.id))}">
- <span>Reject request</span></a>
- </li>
- %endif
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='events', id=trans.security.encode_id(request.id) )}">
- <span>History</span></a>
- </li>
-
-
-</ul>
-
-<%def name="show_basic_info_form( sample_index, sample, info )">
- <td>
- <input type="text" name=sample_${sample_index}_name value="${info['name']}" size="10"/>
- <div class="toolParamHelp" style="clear: both;">
- <i>${' (required)' }</i>
- </div>
- </td>
- %if sample:
- %if sample.request.unsubmitted():
- <td></td>
- %else:
- <td><input type="text" name=sample_${sample_index}_barcode value="${info['barcode']}" size="10"/></td>
- %endif
- %else:
- <td></td>
- %endif
- %if sample:
- %if sample.request.unsubmitted():
- <td>Unsubmitted</td>
- %else:
- <td><a href="${h.url_for( controller='requests_admin', action='show_events', sample_id=sample.id)}">${sample.current_state().name}</a></td>
- %endif
- %else:
- <td></td>
- %endif
- <td>${info['lib_widget'].get_html()}</td>
- <td>${info['folder_widget'].get_html()}</td>
- %if request.submitted() or request.complete():
- %if sample:
- <td><a href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">${len(sample.dataset_files)}</a></td>
- %else:
- <td><a href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">Add</a></td>
- %endif
- %endif
-</%def>
-
-## This function displays the "Basic Information" grid
-<%def name="render_basic_info_grid()">
- <h4>Sample Information</h4>
- <table class="grid">
- <thead>
- <tr>
- <th>Name</th>
- <th>Barcode</th>
- <th>State</th>
- <th>Data Library</th>
- <th>Folder</th>
- %if request.submitted() or request.complete():
- <th>Dataset(s) Transferred</th>
- %endif
- <th></th>
- </tr>
- <thead>
- <tbody>
- <%
- trans.sa_session.refresh( request )
- %>
- %for sample_index, info in enumerate(current_samples):
- <%
- if sample_index in range(len(request.samples)):
- sample = request.samples[sample_index]
- else:
- sample = None
- %>
- %if edit_mode == 'True':
- <tr>
- ${show_basic_info_form( sample_index, sample, info )}
- </tr>
- %else:
- <tr>
- %if sample_index in range(len(request.samples)):
- <td>${info['name']}</td>
- <td>${info['barcode']}</td>
- %if sample.request.unsubmitted():
- <td>Unsubmitted</td>
- %else:
- <td id="sampleState-${sample.id}">${render_sample_state( sample )}</td>
- %endif
-
-## <td>
-## %if sample:
-## %if sample.request.unsubmitted():
-## Unsubmitted
-## %else:
-## <a href="${h.url_for( controller='requests_admin', action='show_events', sample_id=sample.id)}">${sample.current_state().name}</a>
-## %endif
-## %endif
-## </td>
- %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>
- %else:
- <td></td>
- %endif
- %if info['folder']:
- <td>${info['folder'].name}</td>
- %else:
- <td></td>
- %endif
- %if request.submitted() or request.complete():
- <td id="sampleDatasets-${sample.id}">
- ${render_sample_datasets( sample )}
-## <a href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">${len(sample.dataset_files)}</a>
- </td>
- %endif
-
-
- %else:
- ${show_basic_info_form( sample_index, sample, info )}
- %endif
- %if request.unsubmitted() or request.rejected():
- <td>
- %if sample:
- %if sample.request.unsubmitted():
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='delete_sample', request_id=request.id, sample_id=sample_index )}">
- <img src="${h.url_for('/static/images/delete_icon.png')}" />
- <span></span></a>
- %endif
- %endif
- </td>
- %endif
- </tr>
- %endif
- %endfor
- </tbody>
- </table>
-</%def>
-
-<%def name="render_sample_form( index, sample_name, sample_values, fields_dict )">
- <td>
- ${sample_name}
- </td>
- %for field_index, field in fields_dict.items():
- <td>
- %if field['type'] == 'TextField':
- <input type="text" name="sample_${index}_field_${field_index}" value="${sample_values[field_index]}" size="7"/>
- %elif field['type'] == 'SelectField':
- <select name="sample_${index}_field_${field_index}" last_selected_value="2">
- %for option_index, option in enumerate(field['selectlist']):
- %if option == sample_values[field_index]:
- <option value="${option}" selected>${option}</option>
- %else:
- <option value="${option}">${option}</option>
- %endif
- %endfor
- </select>
- %elif field['type'] == 'WorkflowField':
- <select name="sample_${index}_field_${field_index}">
- %if str(sample_values[field_index]) == 'none':
- <option value="none" selected>Select one</option>
- %else:
- <option value="none">Select one</option>
- %endif
- %for option_index, option in enumerate(request.user.stored_workflows):
- %if not option.deleted:
- %if str(option.id) == str(sample_values[field_index]):
- <option value="${option.id}" selected>${option.name}</option>
- %else:
- <option value="${option.id}">${option.name}</option>
- %endif
- %endif
- %endfor
- </select>
- %elif field['type'] == 'CheckboxField':
- <input type="checkbox" name="sample_${index}_field_${field_index}" value="Yes"/>
- %endif
- <div class="toolParamHelp" style="clear: both;">
- <i>${'('+field['required']+')' }</i>
- </div>
- </td>
- %endfor
-</%def>
-
-<%def name="render_sample( index, sample_name, sample_values, fields_dict )">
- <td>
- ${sample_name}
- </td>
- %for field_index, field in fields_dict.items():
- <td>
- %if sample_values[field_index]:
- %if field['type'] == 'WorkflowField':
- %if str(sample_values[field_index]) != 'none':
- <% workflow = trans.sa_session.query( trans.app.model.StoredWorkflow ).get( int(sample_values[field_index]) ) %>
- <a href="${h.url_for( controller='workflow', action='run', id=trans.security.encode_id(workflow.id) )}">${workflow.name}</a>
- %endif
- %else:
- ${sample_values[field_index]}
- %endif
- %else:
- <i>None</i>
- %endif
- </td>
- %endfor
-</%def>
-
-<div class="toolForm">
- <div class="form-row">
- <div class="msg_list">
- <h4 class="msg_head"><u>Request Information</u></h4>
- <div class="msg_body">
- %for index, rd in enumerate(request_details):
- <div class="form-row">
- <label>${rd['label']}</label>
- %if not rd['value']:
- <i>None</i>
- %else:
- %if rd['label'] == 'State':
- <a href="${h.url_for( controller='requests_admin', action='list', operation='events', id=trans.security.encode_id(request.id) )}">${rd['value']}</a>
- %else:
- ${rd['value']}
- %endif
- %endif
- </div>
- <div style="clear: both"></div>
- %endfor
- <div class="form-row">
- <ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='Edit', id=trans.security.encode_id(request.id))}">
- <span>Edit request details</span></a>
- </li>
- </ul>
- </div>
- </div>
- </div>
- </div>
-</div>
-
-<br/>
-
-
-<%def name="render_grid( grid_index, grid_name, fields_dict )">
- <br/>
- <div class="msg_list">
- %if grid_name:
- <h4 class="msg_head"><u>${grid_name}</u></h4>
- %else:
- <h4>Grid ${grid_index}</h4>
- %endif
- %if edit_mode == 'False' or len(current_samples) <= len(request.samples):
- <div class="msg_body">
- %else:
- <div class="msg_body2">
- %endif
- <table class="grid">
- <thead>
- <tr>
- <th>Name</th>
- %for index, field in fields_dict.items():
- <th>
- ${field['label']}
- <div class="toolParamHelp" style="clear: both;">
- <i>${field['helptext']}</i>
- </div>
- </th>
- %endfor
- <th></th>
- </tr>
- <thead>
- <tbody>
- <%
- trans.sa_session.refresh( request )
- %>
- %for sample_index, sample in enumerate(current_samples):
- %if edit_mode == 'True':
- <tr>
- ${render_sample_form( sample_index, sample['name'], sample['field_values'], fields_dict)}
- </tr>
- %else:
- <tr>
- %if sample_index in range(len(request.samples)):
- ${render_sample( sample_index, sample['name'], sample['field_values'], fields_dict )}
- %else:
- ${render_sample_form( sample_index, sample['name'], sample['field_values'], fields_dict)}
- %endif
- </tr>
- %endif
- %endfor
- </tbody>
- </table>
- </div>
- </div>
-</%def>
-
-<div class="toolForm">
- ##<div class="toolFormTitle">Samples (${len(request.samples)})</div>
- <form id="show_request" name="show_request" action="${h.url_for( controller='requests_admin', action='show_request', edit_mode=edit_mode )}" enctype="multipart/form-data" method="post" >
- <div class="form-row">
- %if current_samples:
- ## first render the basic info grid
- ${render_basic_info_grid()}
- ## then render the other grid(s)
- <% trans.sa_session.refresh( request.type.sample_form ) %>
- %for grid_index, grid_name in enumerate(request.type.sample_form.layout):
- ${render_grid( grid_index, grid_name, request.type.sample_form.fields_of_grid( grid_index ) )}
- <br/>
- %endfor
- %else:
- <label>There are no samples.</label>
- %endif
- </div>
- %if request.samples and request.submitted():
- <script type="text/javascript">
- // Updater
- updater({${ ",".join( [ '"%s" : "%s"' % ( s.id, s.current_state().name ) for s in request.samples ] ) }});
- </script>
- %endif
-
- %if edit_mode == 'False':
- <table class="grid">
- <tbody>
- <tr>
- <div class="form-row">
-
- %if request.unsubmitted():
- <td>
- %if current_samples:
- <label>Copy </label>
- <input type="integer" name="num_sample_to_copy" value="1" size="3"/>
- <label>sample(s) from sample</label>
- ${sample_copy.get_html()}
- %endif
- <input type="submit" name="add_sample_button" value="Add New"/>
- </td>
- %endif
- <td>
- %if len(current_samples) and len(current_samples) <= len(request.samples):
- <input type="submit" name="edit_samples_button" value="Edit samples"/>
- %endif
- </td>
- </div>
- </tr>
- </tbody>
- </table>
- %endif
- %if request.samples or current_samples:
- <div class="form-row">
- <div style="float: left; width: 250px; margin-right: 10px;">
- <input type="hidden" name="refresh" value="true" size="40"/>
- </div>
- <div style="clear: both"></div>
- </div>
- %if edit_mode == 'True':
- <div class="form-row">
- <input type="submit" name="save_samples_button" value="Save"/>
- <input type="submit" name="cancel_changes_button" value="Cancel"/>
- </div>
- %elif request.unsubmitted():
- <div class="form-row">
- <input type="submit" name="save_samples_button" value="Save"/>
- </div>
- %endif
-
- %endif
- <input type="hidden" name="request_id" value="${request.id}" />
- </form>
-</div>
-
-<br/>
-
-%if request.unsubmitted():
-<div class="toolForm">
- <form id="show_request" name="show_request" action="${h.url_for( controller='requests_admin', action='show_request', edit_mode=edit_mode )}" enctype="multipart/form-data" method="post" >
- <div class="form-row">
- <div class="msg_list">
- <h4 class="msg_head"><u>Import samples from csv file</u></h4>
- <div class="msg_body">
- <input type="file" name="file_data" />
- <input type="submit" name="import_samples_button" value="Import samples"/>
- <br/>
- <div class="toolParamHelp" style="clear: both;">
- The csv file must be in the following format:<br/>
- SampleName,DataLibrary,DataLibraryFolder,FieldValue1,FieldValue2...
- </div>
- </div>
- </div>
- </div>
- <input type="hidden" name="request_id" value="${request.id}" />
- </form>
-</div>
-%endif
-
--- a/templates/admin/samples/events.mako
+++ /dev/null
@@ -1,63 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-
-<%def name="title()">Events for Sample ${sample.name}</%def>
-
-<h2>Events for Sample "${sample.name}"</h2>
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='show_request', id=trans.security.encode_id(sample.request.id) )}">
- <span>Browse this request</span></a>
- </li>
-</ul>
-<h3>User: ${sample.request.user.email}</h3>
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-<div class="toolForm">
- <table class="grid">
- <thead>
- <tr>
- <th>State</th>
- <th>Description</th>
- <th>Last Update</th>
- <th>Comments</th>
- </tr>
- </thead>
- <tbody>
- %for state, desc, updated, comments in events_list:
- <tr class="libraryRow libraryOrFolderRow" id="libraryRow">
- <td><b><a>${state}</a></b></td>
- <td><a>${desc}</a></td>
- <td><a>${updated}</a></td>
- <td><a>${comments}</a></td>
- </tr>
- %endfor
- </tbody>
- </table>
-</div>
-<div class="toolForm">
- <div class="toolFormTitle">Change current state</div>
- <div class="toolFormBody">
- <form name="event" action="${h.url_for( controller='requests_admin', action='save_state', new=True, sample_id=sample.id)}" method="post" >
- %for w in widgets:
- <div class="form-row">
- <label>
- ${w[0]}
- </label>
- ${w[1].get_html()}
- %if w[0] == 'Comments':
- <div class="toolParamHelp" style="clear: both;">
- Optional
- </div>
- %endif
- </div>
- %endfor
- <div class="form-row">
- <input type="submit" name="add_event_button" value="Save"/>
- </div>
- </form>
- </div>
-</div>
--- a/scripts/galaxy_messaging/server/data_transfer.py
+++ b/scripts/galaxy_messaging/server/data_transfer.py
@@ -194,7 +194,7 @@ class DataTransfer(object):
Update the data transfer status for this dataset in the database
'''
try:
- log.debug('Setting status "%s" for dataset "%s"' % ( status, str(dataset_index) ) )
+ 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':
for dataset in self.dataset_files:
--- a/scripts/galaxy_messaging/server/galaxydb_interface.py
+++ b/scripts/galaxy_messaging/server/galaxydb_interface.py
@@ -28,7 +28,7 @@ class GalaxyDbInterface(object):
def __init__(self, dbstr):
self.dbstr = dbstr
self.db_engine = create_engine(self.dbstr)
-# self.db_engine.echo = True
+ 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 )
--- a/templates/requests/sample_state.mako
+++ /dev/null
@@ -1,5 +0,0 @@
-<%def name="render_sample_state( sample )">
- <a href="${h.url_for( controller='requests_admin', action='show_events', sample_id=sample.id)}">${sample.current_state().name}</a>
-</%def>
-
-${render_sample_state( sample )}
--- a/templates/admin/requests/dataset.mako
+++ b/templates/admin/requests/dataset.mako
@@ -11,7 +11,7 @@
<ul class="manage-table-actions"><li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">
+ <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></li></ul>
--- a/templates/admin/samples/bar_codes.mako
+++ /dev/null
@@ -1,46 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-
-<h2>Bar codes for Samples of Request "${request.name}"</h2>
-<h3>User: ${user.email}</h3>
-
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='show_request', id=trans.security.encode_id(request.id) )}">
- <span>Browse this request</span></a>
- </li>
-</ul>
-
-<div class="toolForm">
- <form name="bar_codes" action="${h.url_for( controller='requests_admin', action='save_bar_codes', request_id=request.id)}" method="post" >
- <table class="grid">
- <thead>
- <tr>
- <th>Name</th>
- <th>Description</th>
- <th>Bar code</th>
- </tr>
- </thead>
- <tbody>
- %for index, sample in enumerate(samples_list):
- <tr class="libraryRow libraryOrFolderRow" id="libraryRow">
- <td><b><a>${sample.name}</a></b></td>
- <td><a>${sample.desc}</a></td>
- <td>
- ${widgets[index].get_html()}
- </td>
- </tr>
- %endfor
- </tbody>
- </table>
- <div class="form-row">
- <input type="submit" name="save_bar_codes" value="Save"/>
- </div>
- </form>
-</div>
--- a/templates/requests/edit_request.mako
+++ /dev/null
@@ -1,84 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-<script type="text/javascript">
-$( function() {
- $( "select[refresh_on_change='true']").change( function() {
- var refresh = false;
- var refresh_on_change_values = $( this )[0].attributes.getNamedItem( 'refresh_on_change_values' )
- if ( refresh_on_change_values ) {
- refresh_on_change_values = refresh_on_change_values.value.split( ',' );
- var last_selected_value = $( this )[0].attributes.getNamedItem( 'last_selected_value' );
- for( i= 0; i < refresh_on_change_values.length; i++ ) {
- if ( $( this )[0].value == refresh_on_change_values[i] || ( last_selected_value && last_selected_value.value == refresh_on_change_values[i] ) ){
- refresh = true;
- break;
- }
- }
- }
- else {
- refresh = true;
- }
- if ( refresh ){
- $( "#edit_request" ).submit();
- }
- });
-});
-</script>
-
-<br/>
-<br/>
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( controller='requests', action='list')}">
- <span>Browse requests</span></a>
- </li>
-</ul>
-
-<div class="toolForm">
- <div class="toolFormTitle">Edit request "${request.name}"</div>
- %if len(select_request_type.options) == 1:
- There are no request types created for a new request.
- %else:
- <div class="toolFormBody">
- <form name="edit_request" id="edit_request" action="${h.url_for( controller='requests', action='edit', request_id=request.id)}" method="post" >
- <div class="form-row">
- <label>
- Select Request Type:
- </label>
- ${select_request_type.get_html()}
- </div>
-
- %if select_request_type.get_selected() != ('Select one', 'none'):
- %for i, field in enumerate(widgets):
- <div class="form-row">
- <label>${field['label']}</label>
- ${field['widget'].get_html()}
- %if field['label'] == 'Data library' and new_library:
- ${new_library.get_html()}
- %endif
- <div class="toolParamHelp" style="clear: both;">
- ${field['helptext']}
- </div>
- <div style="clear: both"></div>
- </div>
- %endfor
- <div class="form-row">
- <div style="float: left; width: 250px; margin-right: 10px;">
- <input type="hidden" name="refresh" value="true" size="40"/>
- </div>
- <div style="clear: both"></div>
- </div>
- <div class="form-row">
- <input type="submit" name="save_changes_request_button" value="Save changes"/>
- ##<input type="submit" name="edit_samples_button" value="Edit samples"/>
- </div>
- %endif
- </form>
- </div>
-</div>
-%endif
--- a/templates/requests/new_request.mako
+++ /dev/null
@@ -1,94 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-<script type="text/javascript">
-$( function() {
- $( "select[refresh_on_change='true']").change( function() {
- var refresh = false;
- var refresh_on_change_values = $( this )[0].attributes.getNamedItem( 'refresh_on_change_values' )
- if ( refresh_on_change_values ) {
- refresh_on_change_values = refresh_on_change_values.value.split( ',' );
- var last_selected_value = $( this )[0].attributes.getNamedItem( 'last_selected_value' );
- for( i= 0; i < refresh_on_change_values.length; i++ ) {
- if ( $( this )[0].value == refresh_on_change_values[i] || ( last_selected_value && last_selected_value.value == refresh_on_change_values[i] ) ){
- refresh = true;
- break;
- }
- }
- }
- else {
- refresh = true;
- }
- if ( refresh ){
- $( "#new_request" ).submit();
- }
- });
-});
-</script>
-
-<%def name="javascripts()">
- ${parent.javascripts()}
- ${h.js("jquery.autocomplete", "autocomplete_tagging" )}
-</%def>
-
-<%def name="stylesheets()">
- ${parent.stylesheets()}
- ${h.css( "autocomplete_tagging" )}
-</%def>
-
-<br/>
-<br/>
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( controller='requests', action='list')}">
- <span>Browse requests</span></a>
- </li>
-</ul>
-
-<div class="toolForm">
- <div class="toolFormTitle">Add a new request</div>
- %if len(select_request_type.options) == 1:
- There are no request types created for a new request.
- %else:
- <div class="toolFormBody">
- <form name="new_request" id="new_request" action="${h.url_for( controller='requests', action='new', create=True )}" method="post" >
- <div class="form-row">
- <label>
- Select Request Type:
- </label>
- ${select_request_type.get_html()}
- </div>
-
- %if select_request_type.get_selected() != ('Select one', 'none'):
- %for i, field in enumerate(widgets):
- <div class="form-row">
- <label>${field['label']}</label>
- ${field['widget'].get_html()}
- ##%if field['label'] == 'Data library' and new_library:
- ## ${new_library.get_html()}
- ##%endif
- <div class="toolParamHelp" style="clear: both;">
- ${field['helptext']}
- </div>
- <div style="clear: both"></div>
- </div>
- %endfor
- <div class="form-row">
- <div style="float: left; width: 250px; margin-right: 10px;">
- <input type="hidden" name="refresh" value="true" size="40"/>
- </div>
- <div style="clear: both"></div>
- </div>
- <div class="form-row">
- <input type="submit" name="create_request_button" value="Save"/>
- <input type="submit" name="create_request_samples_button" value="Add samples"/>
- </div>
- %endif
- </form>
- </div>
-</div>
-%endif
--- /dev/null
+++ b/templates/requests/common/index.mako
@@ -0,0 +1,16 @@
+<%inherit file="/webapps/galaxy/base_panels.mako"/>
+
+<%def name="init()">
+<%
+ self.has_left_panel=False
+ self.has_right_panel=False
+ self.active_view="requests"
+ self.message_box_visible=False
+%>
+</%def>
+
+<%def name="center_panel()">
+
+ <iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${h.url_for( controller="requests", action="list" )}"></iframe>
+
+</%def>
--- a/templates/sample/index.mako
+++ /dev/null
@@ -1,16 +0,0 @@
-<%inherit file="/webapps/galaxy/base_panels.mako"/>
-
-<%def name="init()">
-<%
- self.has_left_panel=False
- self.has_right_panel=False
- self.active_view="requests"
-
-%>
-</%def>
-
-<%def name="center_panel()">
-
- <iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${h.url_for( controller="sample", action="list", request_id=request_id )}"></iframe>
-
-</%def>
--- a/templates/requests/show_request.mako
+++ /dev/null
@@ -1,430 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-<script type="text/javascript">
-$( function() {
- $( "select[refresh_on_change='true']").change( function() {
- var refresh = false;
- var refresh_on_change_values = $( this )[0].attributes.getNamedItem( 'refresh_on_change_values' )
- if ( refresh_on_change_values ) {
- refresh_on_change_values = refresh_on_change_values.value.split( ',' );
- var last_selected_value = $( this )[0].attributes.getNamedItem( 'last_selected_value' );
- for( i= 0; i < refresh_on_change_values.length; i++ ) {
- if ( $( this )[0].value == refresh_on_change_values[i] || ( last_selected_value && last_selected_value.value == refresh_on_change_values[i] ) ){
- refresh = true;
- break;
- }
- }
- }
- else {
- refresh = true;
- }
- if ( refresh ){
- $( "#show_request" ).submit();
- }
- });
-});
-</script>
-
-
-<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>
-<style type="text/css">
-.msg_head {
- padding: 0px 0px;
- cursor: pointer;
-}
-
-}
-</style>
-
-%if request.rejected():
- ${render_msg( "Reason for rejection: "+request.last_comment(), "warning" )}
-%endif
-
-<div class="grid-header">
- <h2>Sequencing Request "${request.name}"</h2>
-</div>
-
-<ul class="manage-table-actions">
- %if request.unsubmitted() and request.samples:
- <li>
- <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='requests', action='list', operation='Submit', id=trans.security.encode_id(request.id) )}">
- <span>Submit request</span></a>
- </li>
- %endif
- <li>
- <a class="action-button" href="${h.url_for( controller='requests', action='list', operation='events', id=trans.security.encode_id(request.id) )}">
- <span>History</span></a>
- </li>
- <li>
- <a class="action-button" href="${h.url_for( controller='requests', action='list')}">
- <span>Browse requests</span></a>
- </li>
-</ul>
-
-<%def name="show_basic_info_form( sample_index, sample, info )">
- <td>
- <input type="text" name=sample_${sample_index}_name value="${info['name']}" size="10"/>
- <div class="toolParamHelp" style="clear: both;">
- <i>${' (required)' }</i>
- </div>
- </td>
- %if sample:
- %if sample.request.unsubmitted():
- <td></td>
- %else:
- <td><input type="text" name=sample_${sample_index}_barcode value="${info['barcode']}" size="10"/></td>
- %endif
- %else:
- <td></td>
- %endif
- %if sample:
- %if not sample.current_state():
- <td>Unsubmitted</td>
- %else:
- <td><a href="${h.url_for( controller='requests_admin', action='show_events', sample_id=sample.id)}">${sample.current_state().name}</a></td>
- %endif
- %else:
- <td></td>
- %endif
- <td>${info['lib_widget'].get_html()}</td>
- <td>${info['folder_widget'].get_html()}</td>
- %if request.submitted() or request.complete():
- %if sample:
- <td><a href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">${len(sample.dataset_files)}</a></td>
- %else:
- <td><a href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">Add</a></td>
- %endif
- %endif
-</%def>
-
-## This function displays the "Basic Information" grid
-<%def name="render_basic_info_grid()">
- <h4>Sample Information</h4>
- <table class="grid">
- <thead>
- <tr>
- <th>Name</th>
- <th>Barcode</th>
- <th>State</th>
- <th>Data Library</th>
- <th>Folder</th>
- %if request.submitted() or request.complete():
- <th>Dataset(s) Transferred</th>
- %endif
- <th></th>
- </tr>
- <thead>
- <tbody>
- <%
- trans.sa_session.refresh( request )
- %>
- %for sample_index, info in enumerate(current_samples):
- <%
- if sample_index in range(len(request.samples)):
- sample = request.samples[sample_index]
- else:
- sample = None
- %>
- %if edit_mode == 'True':
- <tr>
- ${show_basic_info_form( sample_index, sample, info )}
- </tr>
- %else:
- <tr>
- %if sample_index in range(len(request.samples)):
- <td>${info['name']}</td>
- <td>${info['barcode']}</td>
- <td>
- %if sample.current_state():
- <a href="${h.url_for( controller='requests', action='show_events', sample_id=sample.id)}">${sample.current_state().name}</a>
- %else:
- Unsubmitted
- %endif
- </td>
- %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>
- %else:
- <td></td>
- %endif
- %if info['folder']:
- <td>${info['folder'].name}</td>
- %else:
- <td></td>
- %endif
- %if request.submitted() or request.complete():
- <td>
- <a href="${h.url_for( controller='requests', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">${len(sample.dataset_files)}</a>
- </td>
- %endif
-
-
- %else:
- ${show_basic_info_form( sample_index, sample, info )}
- %endif
- %if request.unsubmitted() or request.rejected():
- <td>
- %if sample:
- %if sample.request.unsubmitted():
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='delete_sample', request_id=request.id, sample_id=sample_index )}">
- <img src="${h.url_for('/static/images/delete_icon.png')}" />
- <span></span></a>
- %endif
- %endif
- </td>
- %endif
- </tr>
- %endif
- %endfor
- </tbody>
- </table>
-</%def>
-
-<%def name="render_sample_form( index, sample_name, sample_values, fields_dict )">
- <td>
- ${sample_name}
- </td>
- %for field_index, field in fields_dict.items():
- <td>
- %if field['type'] == 'TextField':
- <input type="text" name="sample_${index}_field_${field_index}" value="${sample_values[field_index]}" size="7"/>
- %elif field['type'] == 'SelectField':
- <select name="sample_${index}_field_${field_index}" last_selected_value="2">
- %for option_index, option in enumerate(field['selectlist']):
- %if option == sample_values[field_index]:
- <option value="${option}" selected>${option}</option>
- %else:
- <option value="${option}">${option}</option>
- %endif
- %endfor
- </select>
- %elif field['type'] == 'WorkflowField':
- <select name="sample_${index}_field_${field_index}">
- %if str(sample_values[field_index]) == 'none':
- <option value="none" selected>Select one</option>
- %else:
- <option value="none">Select one</option>
- %endif
- %for option_index, option in enumerate(request.user.stored_workflows):
- %if not option.deleted:
- %if str(option.id) == str(sample_values[field_index]):
- <option value="${option.id}" selected>${option.name}</option>
- %else:
- <option value="${option.id}">${option.name}</option>
- %endif
- %endif
- %endfor
- </select>
- %elif field['type'] == 'CheckboxField':
- <input type="checkbox" name="sample_${index}_field_${field_index}" value="Yes"/>
- %endif
- <div class="toolParamHelp" style="clear: both;">
- <i>${'('+field['required']+')' }</i>
- </div>
- </td>
- %endfor
-</%def>
-
-<%def name="render_sample( index, sample_name, sample_values, fields_dict )">
- <td>
- ${sample_name}
- </td>
- %for field_index, field in fields_dict.items():
- <td>
- %if sample_values[field_index]:
- %if field['type'] == 'WorkflowField':
- %if str(sample_values[field_index]) != 'none':
- <% workflow = trans.sa_session.query( trans.app.model.StoredWorkflow ).get( int(sample_values[field_index]) ) %>
- <a href="${h.url_for( controller='workflow', action='run', id=trans.security.encode_id(workflow.id) )}">${workflow.name}</a>
- %endif
- %else:
- ${sample_values[field_index]}
- %endif
- %else:
- <i>None</i>
- %endif
-
- </td>
- %endfor
-</%def>
-
-<%def name="render_grid( grid_index, grid_name, fields_dict )">
- <br/>
- <div class="msg_list">
- %if grid_name:
- <h4 class="msg_head"><u>${grid_name}</u></h4>
- %else:
- <h4>Grid ${grid_index}</h4>
- %endif
- %if edit_mode == 'False' or len(current_samples) <= len(request.samples):
- <div class="msg_body">
- %else:
- <div class="msg_body2">
- %endif
- <table class="grid">
- <thead>
- <tr>
- <th>Name</th>
- %for index, field in fields_dict.items():
- <th>
- ${field['label']}
- <div class="toolParamHelp" style="clear: both;">
- <i>${field['helptext']}</i>
- </div>
- </th>
- %endfor
- <th></th>
- </tr>
- <thead>
- <tbody>
- <%
- trans.sa_session.refresh( request )
- %>
- %for sample_index, sample in enumerate(current_samples):
- %if edit_mode == 'True':
- <tr>
- ${render_sample_form( sample_index, sample['name'], sample['field_values'], fields_dict)}
- </tr>
- %else:
- <tr>
- %if sample_index in range(len(request.samples)):
- ${render_sample( sample_index, sample['name'], sample['field_values'], fields_dict )}
- %else:
- ${render_sample_form( sample_index, sample['name'], sample['field_values'], fields_dict)}
- %endif
- </tr>
- %endif
- %endfor
- </tbody>
- </table>
- </div>
- </div>
-</%def>
-
-<div class="toolForm">
- <form id="request_details" name="request_details" >
- <div class="form-row">
- <div class="msg_list">
- <h4 class="msg_head"><u>Request Information</u></h4>
- <div class="msg_body">
- %for index, rd in enumerate(request_details):
- <div class="form-row">
- <label>${rd['label']}</label>
- %if not rd['value']:
- <i>None</i>
- %else:
- %if rd['label'] == 'State':
- <a href="${h.url_for( controller='requests_admin', action='list', operation='events', id=trans.security.encode_id(request.id) )}">${rd['value']}</a>
- %else:
- ${rd['value']}
- %endif
- %endif
- </div>
- <div style="clear: both"></div>
- %endfor
- <div class="form-row">
- <ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='Edit', id=trans.security.encode_id(request.id))}">
- <span>Edit request details</span></a>
- </li>
- </ul>
- </div>
- </div>
- </div>
- </div>
- </form>
- <form id="show_request" name="show_request" action="${h.url_for( controller='requests', action='show_request', edit_mode=edit_mode )}" enctype="multipart/form-data" method="post" >
- <div class="form-row">
- %if current_samples:
- ## first render the basic info grid
- ${render_basic_info_grid()}
- ## then render the other grid(s)
- <% trans.sa_session.refresh( request.type.sample_form ) %>
- %for grid_index, grid_name in enumerate(request.type.sample_form.layout):
- ${render_grid( grid_index, grid_name, request.type.sample_form.fields_of_grid( grid_index ) )}
- <br/>
- %endfor
- %else:
- <label>There are no samples.</label>
- %endif
- </div>
- %if request.unsubmitted() and edit_mode == 'False':
- <table class="grid">
- <tbody>
- <tr>
- <div class="form-row">
- <td>
- %if current_samples:
- <label>Copy </label>
- <input type="integer" name="num_sample_to_copy" value="1" size="3"/>
- <label>sample(s) from sample</label>
- ${sample_copy.get_html()}
- %endif
- <input type="submit" name="add_sample_button" value="Add New"/>
- </td>
- <td>
- %if len(current_samples) and len(current_samples) <= len(request.samples):
- <input type="submit" name="edit_samples_button" value="Edit samples"/>
- %endif
- </td>
- </div>
- </tr>
- </tbody>
- </table>
- %endif
- %if request.unsubmitted() and (request.samples or current_samples):
- <div class="form-row">
- <div style="float: left; width: 250px; margin-right: 10px;">
- <input type="hidden" name="refresh" value="true" size="40"/>
- </div>
- <div style="clear: both"></div>
- </div>
- <div class="form-row">
- <input type="submit" name="save_samples_button" value="Save"/>
- %if edit_mode == 'True':
- <input type="submit" name="cancel_changes_button" value="Cancel"/>
- %endif
- </div>
- %endif
- <input type="hidden" name="request_id" value="${request.id}" />
- </form>
-</div>
-
-
-<br/>
-%if request.unsubmitted():
-<div class="toolForm">
- <form id="show_request" name="show_request" action="${h.url_for( controller='requests', action='show_request', edit_mode=edit_mode )}" enctype="multipart/form-data" method="post" >
- <div class="form-row">
- <div class="msg_list">
- <h4 class="msg_head"><u>Import samples from csv file</u></h4>
- <div class="msg_body">
- <input type="file" name="file_data" />
- <input type="submit" name="import_samples_button" value="Import samples"/>
- <br/>
- <div class="toolParamHelp" style="clear: both;">
- The csv file must be in the following format:<br/>
- SampleName,DataLibrary,DataLibraryFolder,FieldValue1,FieldValue2...
- </div>
- </div>
- </div>
- </div>
- <input type="hidden" name="request_id" value="${request.id}" />
- </form>
-</div>
-%endif
--- /dev/null
+++ b/templates/requests/common/new_request.mako
@@ -0,0 +1,91 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+%if message:
+ ${render_msg( message, status )}
+%endif
+
+<script type="text/javascript">
+$( function() {
+ $( "select[refresh_on_change='true']").change( function() {
+ var refresh = false;
+ var refresh_on_change_values = $( this )[0].attributes.getNamedItem( 'refresh_on_change_values' )
+ if ( refresh_on_change_values ) {
+ refresh_on_change_values = refresh_on_change_values.value.split( ',' );
+ var last_selected_value = $( this )[0].attributes.getNamedItem( 'last_selected_value' );
+ for( i= 0; i < refresh_on_change_values.length; i++ ) {
+ if ( $( this )[0].value == refresh_on_change_values[i] || ( last_selected_value && last_selected_value.value == refresh_on_change_values[i] ) ){
+ refresh = true;
+ break;
+ }
+ }
+ }
+ else {
+ refresh = true;
+ }
+ if ( refresh ){
+ $( "#new_request" ).submit();
+ }
+ });
+});
+</script>
+
+<%def name="javascripts()">
+ ${parent.javascripts()}
+ ${h.js("jquery.autocomplete", "autocomplete_tagging" )}
+</%def>
+
+<%def name="stylesheets()">
+ ${parent.stylesheets()}
+ ${h.css( "autocomplete_tagging" )}
+</%def>
+
+<br/>
+<br/>
+<ul class="manage-table-actions">
+ <li>
+ <a class="action-button" href="${h.url_for( controller=cntrller, cntrller=cntrller, action='list')}">
+ <span>Browse requests</span></a>
+ </li>
+</ul>
+
+<div class="toolForm">
+ <div class="toolFormTitle">Add a new request</div>
+ %if len(select_request_type.options) == 1:
+ There are no request types created for a new request.
+ %else:
+ <div class="toolFormBody">
+ <form name="new_request" id="new_request" action="${h.url_for( controller='requests_common', action='new', cntrller=cntrller)}" method="post" >
+ <div class="form-row">
+ <label>
+ Select request type
+ </label>
+ ${select_request_type.get_html()}
+ </div>
+
+ %if select_request_type.get_selected() != ('Select one', 'none'):
+ %for i, field in enumerate(widgets):
+ <div class="form-row">
+ <label>${field['label']}</label>
+ ${field['widget'].get_html()}
+ <div class="toolParamHelp" style="clear: both;">
+ ${field['helptext']}
+ </div>
+ <div style="clear: both"></div>
+ </div>
+ %endfor
+ <div class="form-row">
+ <input type="submit" name="create_request_button" value="Save"/>
+ <input type="submit" name="create_request_samples_button" value="Add samples"/>
+ </div>
+ %endif
+ <div class="form-row">
+ <div style="float: left; width: 250px; margin-right: 10px;">
+ <input type="hidden" name="refresh" value="true" size="40"/>
+ </div>
+ <div style="clear: both"></div>
+ </div>
+ </form>
+ </div>
+</div>
+%endif
--- a/templates/admin/requests/get_data.mako
+++ /dev/null
@@ -1,227 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-<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(sample_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: sample_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(sample_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: sample_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>
-
-
-<h2>Data transfer from Sequencer</h2>
-<h3>Sample "${sample.name}" of Request "${sample.request.name}"</h3>
-<br/>
-<br/>
-
-<ul class="manage-table-actions">
- %if sample.request.submitted() and sample.inprogress_dataset_files():
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">
- <span>Refresh this page</span></a>
- </li>
- %endif
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_request_types', operation='view', id=trans.security.encode_id(sample.request.type.id) )}">
- <span>Sequencer information</span></a>
- </li>
- <li>
- <a class="action-button" href="${h.url_for( controller='library_common', action='browse_library', cntrller='library_admin', id=trans.security.encode_id( sample.library.id ) )}">
- <span>${sample.library.name} Data Library</span></a>
- </li>
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='show_request', id=trans.security.encode_id(sample.request.id) )}">
- <span>Browse this request</span></a>
- </li>
-</ul>
-
-<div class="toolForm">
- %if len(dataset_files):
-## <form name="get_data" action="${h.url_for( controller='requests_admin', action='get_data', sample_id=sample.id)}" method="post" >
- <div class="form-row">
- <h4>Sample Dataset(s)</h4>
- %if sample.untransferred_dataset_files():
- <div class="form-row">
- <ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='get_data', start_transfer_button=True, sample_id=sample.id )}">
- <span>Start transfer</span></a>
- </li>
- </ul>
- </div>
- %endif
- <div class="form-row">
- <table class="grid">
- <thead>
- <tr>
- <th>Dataset File</th>
- <th>Transfer Status</th>
- <th></th>
- </tr>
- <thead>
- <tbody>
- %for dataset_index, dataset_file in enumerate(dataset_files):
- ${sample_dataset_files( dataset_index, dataset_file['name'], dataset_file['status'] )}
- %endfor
- </tbody>
- </table>
- </div>
- </div>
-
-## </form>
-##</div>
-
-
-<br/>
-<br/>
-%endif
-
-##<div class="toolForm">
- <form name="get_data" id="get_data" action="${h.url_for( controller='requests_admin', action='get_data', sample_id=sample.id)}" method="post" >
- <div class="form-row">
- ##<div class="toolFormTitle">Select files for transfer</div>
- <h4>Select files for transfer</h4>
- <div style="width: 60%;">
- <div class="form-row">
- <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: 98%; width: 98%; height: 150px; font-size: 100%;" ondblclick="open_folder1(${sample.id}, '${folder_path}')" onChange="display_file_details(${sample.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_files_button" value="Select"/>
- </div>
- </div>
- </div>
- </form>
-</div>
-
-<%def name="sample_dataset_files( dataset_index, dataset_name, status )">
- <tr>
- <td>
- <label class="msg_head"><a href="${h.url_for( controller='requests_admin', action='dataset_details', sample_id=trans.security.encode_id(sample.id), dataset_index=dataset_index )}">${dataset_name}</a></label>
-## <div class="msg_head"><u>${dataset_file.split('/')[-1]}</u></div>
-## <div class="msg_body">
-## ${dataset_file}
-## </div>
- </td>
- <td>
- %if status not in [sample.transfer_status.NOT_STARTED, sample.transfer_status.COMPLETE]:
- <i>${status}</i>
- %else:
- ${status}
- %endif
- </td>
- ##<td></td>
- %if status == sample.transfer_status.NOT_STARTED:
- <td>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='get_data', sample_id=sample.id, remove_dataset_button=True, dataset_index=dataset_index )}">
- <img src="${h.url_for('/static/images/delete_icon.png')}" />
- <span></span></a>
- </td>
- %else:
- <td></td>
- %endif
- </tr>
-</%def>
--- a/templates/admin/requests/add_states.mako
+++ /dev/null
@@ -1,26 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-<div class="toolForm">
- <div class="toolFormTitle">Create ${num_states} states for the '${request_type_name}' request type</div>
- <form name="new_form_fields" action="${h.url_for( controller='requests_admin', action='request_type', name=request_type_name, description=desc, num_states=num_states, request_form_id=request_form_id, sample_form_id=sample_form_id)}" method="post" >
- <div class="toolFormBody">
- %for element_count in range( num_states ):
- <div class="form-row">
- <label>${1+element_count}) State name:</label>
- <input type="text" name="state_name_${element_count}" value="" size="40"/>
- <label>State help text (optional):</label>
- <input type="text" name="state_desc_${element_count}" value="" size="40"/>
- </div>
- <div style="clear: both"></div>
- %endfor
- </div>
- <div class="form-row">
- <input type="submit" name="save_request_type" value="Save"/>
- </div>
- </form>
-</div>
--- /dev/null
+++ b/templates/requests/common/edit_request.mako
@@ -0,0 +1,84 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+%if message:
+ ${render_msg( message, status )}
+%endif
+
+<script type="text/javascript">
+$( function() {
+ $( "select[refresh_on_change='true']").change( function() {
+ var refresh = false;
+ var refresh_on_change_values = $( this )[0].attributes.getNamedItem( 'refresh_on_change_values' )
+ if ( refresh_on_change_values ) {
+ refresh_on_change_values = refresh_on_change_values.value.split( ',' );
+ var last_selected_value = $( this )[0].attributes.getNamedItem( 'last_selected_value' );
+ for( i= 0; i < refresh_on_change_values.length; i++ ) {
+ if ( $( this )[0].value == refresh_on_change_values[i] || ( last_selected_value && last_selected_value.value == refresh_on_change_values[i] ) ){
+ refresh = true;
+ break;
+ }
+ }
+ }
+ else {
+ refresh = true;
+ }
+ if ( refresh ){
+ $( "#edit_request" ).submit();
+ }
+ });
+});
+</script>
+
+<br/>
+<br/>
+<ul class="manage-table-actions">
+ <li>
+ <a class="action-button" href="${h.url_for( controller=cntrller, cntrller=cntrller, action='list')}">
+ <span>Browse requests</span></a>
+ </li>
+</ul>
+
+<div class="toolForm">
+ <div class="toolFormTitle">Edit request "${request.name}"</div>
+ %if len(select_request_type.options) == 1:
+ There are no request types created for a new request.
+ %else:
+ <div class="toolFormBody">
+ <form name="edit_request" id="edit_request" action="${h.url_for( controller='requests_common', cntrller=cntrller, action='edit', id=trans.security.encode_id(request.id))}" method="post" >
+ <div class="form-row">
+ <label>
+ Select Request Type:
+ </label>
+ ${select_request_type.get_html()}
+ </div>
+
+ %if select_request_type.get_selected() != ('Select one', 'none'):
+ %for i, field in enumerate(widgets):
+ <div class="form-row">
+ <label>${field['label']}</label>
+ ${field['widget'].get_html()}
+ %if field['label'] == 'Data library' and new_library:
+ ${new_library.get_html()}
+ %endif
+ <div class="toolParamHelp" style="clear: both;">
+ ${field['helptext']}
+ </div>
+ <div style="clear: both"></div>
+ </div>
+ %endfor
+ <div class="form-row">
+ <div style="float: left; width: 250px; margin-right: 10px;">
+ <input type="hidden" name="refresh" value="true" size="40"/>
+ </div>
+ <div style="clear: both"></div>
+ </div>
+ <div class="form-row">
+ <input type="submit" name="save_changes_request_button" value="Save changes"/>
+ ##<input type="submit" name="edit_samples_button" value="Edit samples"/>
+ </div>
+ %endif
+ </form>
+ </div>
+</div>
+%endif
--- a/lib/galaxy/web/controllers/requests.py
+++ b/lib/galaxy/web/controllers/requests.py
@@ -84,7 +84,7 @@ class RequestsGrid( grids.Grid ):
NameColumn( "Name",
key="name",
model_class=model.Request,
- link=( lambda item: iff( item.deleted, None, dict( operation="show_request", id=item.id ) ) ),
+ link=( lambda item: iff( item.deleted, None, dict( operation="show", id=item.id ) ) ),
attach_popup=True,
filterable="advanced" ),
DescriptionColumn( "Description",
@@ -92,7 +92,7 @@ class RequestsGrid( grids.Grid ):
model_class=model.Request,
filterable="advanced" ),
SamplesColumn( "Sample(s)",
- link=( lambda item: iff( item.deleted, None, dict( operation="show_request", id=item.id ) ) ), ),
+ link=( lambda item: iff( item.deleted, None, dict( operation="show", id=item.id ) ) ), ),
TypeColumn( "Type" ),
grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
grids.DeletedColumn( "Deleted",
@@ -120,7 +120,8 @@ class RequestsGrid( grids.Grid ):
]
global_actions = [
- grids.GridAction( "Create new request", dict( controller='requests',
+ grids.GridAction( "Create new request", dict( controller='requests_common',
+ cntrller='requests',
action='new',
select_request_type='True' ) )
]
@@ -149,18 +150,36 @@ class Requests( BaseController ):
action='list',
status='error',
message="Invalid request ID") )
- if operation == "show_request":
- return self.__show_request( trans, **kwd )
+ if operation == "show":
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller='requests',
+ action='show',
+ **kwd ) )
elif operation == "submit":
- return self.__submit_request( trans, **kwd )
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller='requests',
+ action='submit',
+ **kwd ) )
elif operation == "delete":
- return self.__delete_request( trans, **kwd )
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller='requests',
+ action='delete',
+ **kwd ) )
elif operation == "undelete":
- return self.__undelete_request( trans, **kwd )
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller='requests',
+ action='undelete',
+ **kwd ) )
elif operation == "edit":
- return self.__edit_request( trans, **kwd )
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller='requests',
+ action='edit',
+ show=True, **kwd ) )
elif operation == "events":
- return self.__request_events( trans, **kwd )
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller='requests',
+ action='events',
+ **kwd ) )
# if there are one or more requests that has been rejected by the admin
# recently, then show a message as a reminder to the user
rlist = trans.sa_session.query( trans.app.model.Request ) \
@@ -176,915 +195,4 @@ class Requests( BaseController ):
% rejected
# Render the list view
return self.request_grid( trans, **kwd )
- def __request_events(self, trans, **kwd):
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
- except:
- message = "Invalid request ID"
- log.warn( message )
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- status='error',
- message=message,
- **kwd) )
- events_list = []
- all_events = request.events
- for event in all_events:
- events_list.append((event.state, time_ago(event.update_time), event.comment))
- return trans.fill_template( '/requests/events.mako',
- events_list=events_list, request=request)
- def request_details(self, trans, id):
- '''
- Shows the request details
- '''
- request = trans.sa_session.query( trans.app.model.Request ).get( id )
- # list of widgets to be rendered on the request form
- request_details = []
- # main details
- request_details.append(dict(label='Description',
- value=request.desc,
- helptext=''))
- request_details.append(dict(label='Type',
- value=request.type.name,
- helptext=''))
-
- request_details.append(dict(label='State',
- value=request.state(),
- helptext=''))
- request_details.append(dict(label='Date created',
- value=request.create_time,
- helptext=''))
- # form fields
- for index, field in enumerate(request.type.request_form.fields):
- if field['required']:
- req = 'Required'
- else:
- req = 'Optional'
- if field['type'] == 'AddressField':
- if request.values.content[index]:
- request_details.append(dict(label=field['label'],
- value=trans.sa_session.query( trans.app.model.UserAddress ).get( int( request.values.content[index] ) ).get_html(),
- helptext=field['helptext']+' ('+req+')'))
- else:
- request_details.append(dict(label=field['label'],
- value=None,
- helptext=field['helptext']+' ('+req+')'))
-
- else:
- request_details.append(dict(label=field['label'],
- value=request.values.content[index],
- helptext=field['helptext']+' ('+req+')'))
- if request.notify:
- notify = 'Yes'
- else:
- notify = 'No'
- request_details.append(dict(label='Send email notification once the sequencing request is complete',
- value=notify,
- helptext=''))
- return request_details
- def __show_request(self, trans, **kwd):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- add_sample = params.get('add_sample', False)
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
- except:
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- status='error',
- message="Invalid request ID") )
- # get all data libraries accessible to this user
- libraries = request.user.accessible_libraries( trans, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] )
- current_samples = []
- for i, s in enumerate(request.samples):
- lib_widget, folder_widget = self.__library_widgets(trans, request.user, i, libraries, s, **kwd)
- current_samples.append(dict(name=s.name,
- 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))
- if add_sample:
- lib_widget, folder_widget = self.__library_widgets(trans, request.user,
- len(current_samples)+1,
- libraries, None, **kwd)
- current_samples.append(dict(name='Sample_%i' % (len(current_samples)+1),
- 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))
- return trans.fill_template( '/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples=current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details='hide', edit_mode=util.restore_text( params.get( 'edit_mode', 'False' ) ),
- message=message, status=status )
- def __library_widgets(self, trans, user, sample_index, libraries, sample=None, lib_id=None, folder_id=None, **kwd):
- '''
- This method creates the data library & folder selectbox for creating &
- editing samples. First we get a list of all the libraries accessible to
- the current user and display it in a selectbox. If the user has selected an
- existing library then display all the accessible sub folders of the selected
- data library.
- '''
- params = util.Params( kwd )
- # data library selectbox
- if not lib_id:
- lib_id = params.get( "sample_%i_library_id" % sample_index, 'none' )
- selected_lib = None
- if sample and lib_id == 'none':
- if sample.library:
- lib_id = str(sample.library.id)
- selected_lib = sample.library
- # create data library selectbox with refresh on change enabled
- lib_id_list = ['new'] + [str(lib.id) for lib in libraries.keys()]
- lib_widget = SelectField( "sample_%i_library_id" % sample_index,
- refresh_on_change=True,
- refresh_on_change_values=lib_id_list )
- # fill up the options in the Library selectbox
- # first option 'none' is the value for "Select one" option
- if lib_id == 'none':
- lib_widget.add_option('Select one', 'none', selected=True)
- else:
- lib_widget.add_option('Select one', 'none')
- # all the libraries available to the selected user
- for lib, hidden_folder_ids in libraries.items():
- if str(lib.id) == str(lib_id):
- lib_widget.add_option(lib.name, lib.id, selected=True)
- selected_lib, selected_hidden_folder_ids = lib, hidden_folder_ids.split(',')
- else:
- lib_widget.add_option(lib.name, lib.id)
- lib_widget.refresh_on_change_values.append(lib.id)
- # create the folder selectbox
- folder_widget = SelectField( "sample_%i_folder_id" % sample_index )
- # when editing a request, either the user has already selected a subfolder or not
- if sample:
- if sample.folder:
- current_fid = sample.folder.id
- else:
- # when a folder not yet associated with the request then the
- # the current folder is set to the root_folder of the
- # parent data library if present.
- if sample.library:
- current_fid = sample.library.root_folder.id
- else:
- current_fid = params.get( "sample_%i_folder_id" % sample_index, 'none' )
- else:
- if folder_id:
- current_fid = folder_id
- else:
- current_fid = 'none'
- # first option
- if lib_id == 'none':
- folder_widget.add_option('Select one', 'none', selected=True)
- else:
- folder_widget.add_option('Select one', 'none')
- if selected_lib:
- # get all show-able folders for the selected library
- showable_folders = trans.app.security_agent.get_showable_folders( user, user.all_roles(),
- selected_lib,
- [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ],
- selected_hidden_folder_ids )
- for f in showable_folders:
- if str(f.id) == str(current_fid):
- folder_widget.add_option(f.name, f.id, selected=True)
- else:
- folder_widget.add_option(f.name, f.id)
- return lib_widget, folder_widget
- def __update_samples(self, trans, request, **kwd):
- '''
- This method retrieves all the user entered sample information and
- returns an list of all the samples and their field values
- '''
- params = util.Params( kwd )
- details = params.get( 'details', 'hide' )
- edit_mode = params.get( 'edit_mode', 'False' )
- # get all data libraries accessible to this user
- libraries = request.user.accessible_libraries( trans, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] )
-
- current_samples = []
- for i, s in enumerate(request.samples):
- lib_widget, folder_widget = self.__library_widgets(trans, request.user, i, libraries, s, **kwd)
- current_samples.append(dict(name=s.name,
- barcode=s.bar_code,
- library=s.library,
- folder=s.folder,
- field_values=s.values.content,
- lib_widget=lib_widget,
- folder_widget=folder_widget))
- if edit_mode == 'False':
- sample_index = len(request.samples)
- else:
- sample_index = 0
- while True:
- lib_id = None
- folder_id = None
- if params.get( 'sample_%i_name' % sample_index, '' ):
- # data library
- try:
- library = trans.sa_session.query( trans.app.model.Library ).get( int( params.get( 'sample_%i_library_id' % sample_index, None ) ) )
- lib_id = library.id
- except:
- library = None
- # folder
- try:
- folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( int( params.get( 'sample_%i_folder_id' % sample_index, None ) ) )
- folder_id = folder.id
- except:
- if library:
- folder = library.root_folder
- else:
- folder = None
- sample_info = dict( name=util.restore_text( params.get( 'sample_%i_name' % sample_index, '' ) ),
- barcode=util.restore_text( params.get( 'sample_%i_barcode' % sample_index, '' ) ),
- library=library,
- folder=folder)
- sample_info['field_values'] = []
- for field_index in range(len(request.type.sample_form.fields)):
- sample_info['field_values'].append(util.restore_text( params.get( 'sample_%i_field_%i' % (sample_index, field_index), '' ) ))
- if edit_mode == 'False':
- sample_info['lib_widget'], sample_info['folder_widget'] = self.__library_widgets(trans,
- request.user,
- sample_index,
- libraries,
- None, lib_id, folder_id, **kwd)
- current_samples.append(sample_info)
- else:
- sample_info['lib_widget'], sample_info['folder_widget'] = self.__library_widgets(trans,
- request.user,
- sample_index,
- libraries,
- request.samples[sample_index],
- **kwd)
- current_samples[sample_index] = sample_info
- sample_index = sample_index + 1
- else:
- break
- return current_samples, details, edit_mode, libraries
- def __copy_sample(self, current_samples):
- copy_list = SelectField('copy_sample')
- copy_list.add_option('None', -1, selected=True)
- for i, s in enumerate(current_samples):
- copy_list.add_option(s['name'], i)
- return copy_list
- def __import_samples(self, trans, request, current_samples, details, libraries, **kwd):
- '''
- This method reads the samples csv file and imports all the samples
- The format of the csv file is:
- SampleName,DataLibrary,DataLibraryFolder,Field1,Field2....
- '''
- try:
- params = util.Params( kwd )
- edit_mode = params.get( 'edit_mode', 'False' )
- file_obj = params.get('file_data', '')
- reader = csv.reader(file_obj.file)
- for row in reader:
- lib_id = None
- folder_id = None
- lib = trans.sa_session.query( trans.app.model.Library ) \
- .filter( and_( trans.app.model.Library.table.c.name==row[1], \
- trans.app.model.Library.table.c.deleted==False ) )\
- .first()
- if lib:
- folder = trans.sa_session.query( trans.app.model.LibraryFolder ) \
- .filter( and_( trans.app.model.LibraryFolder.table.c.name==row[2], \
- trans.app.model.LibraryFolder.table.c.deleted==False ) )\
- .first()
- if folder:
- lib_id = lib.id
- folder_id = folder.id
- lib_widget, folder_widget = self.__library_widgets(trans, request.user, len(current_samples),
- libraries, None, lib_id, folder_id, **kwd)
- current_samples.append(dict(name=row[0],
- barcode='',
- library=None,
- folder=None,
- lib_widget=lib_widget,
- folder_widget=folder_widget,
- field_values=row[3:]))
- return trans.fill_template( '/admin/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples=current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details=details,
- edit_mode=edit_mode)
- except:
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- operation='show_request',
- id=trans.security.encode_id(request.id),
- status='error',
- message='Error in importing samples file' ))
-
- @web.expose
- @web.require_login( "create/submit sequencing requests" )
- def show_request(self, trans, **kwd):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', None ) ) )
- except:
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- status='error',
- message="Invalid request ID",
- **kwd) )
- # get the user entered sample details
- current_samples, details, edit_mode, libraries = self.__update_samples( trans, request, **kwd )
- if params.get('import_samples_button', False) == 'Import samples':
- return self.__import_samples(trans, request, current_samples, details, libraries, **kwd)
- elif params.get('add_sample_button', False) == 'Add New':
- # add an empty or filled sample
- # if the user has selected a sample no. to copy then copy the contents
- # of the src sample to the new sample else an empty sample
- src_sample_index = int(params.get( 'copy_sample', -1 ))
- # get the number of new copies of the src sample
- num_sample_to_copy = int(params.get( 'num_sample_to_copy', 1 ))
- if src_sample_index == -1:
- for ns in range(num_sample_to_copy):
- # empty sample
- lib_widget, folder_widget = self.__library_widgets(trans, request.user,
- len(current_samples),
- libraries, None, **kwd)
- current_samples.append(dict(name='Sample_%i' % (len(current_samples)+1),
- barcode='',
- library=None,
- folder=None,
- field_values=['' for field in request.type.sample_form.fields],
- lib_widget=lib_widget,
- folder_widget=folder_widget))
- else:
- src_library_id = current_samples[src_sample_index]['lib_widget'].get_selected()[1]
- src_folder_id = current_samples[src_sample_index]['folder_widget'].get_selected()[1]
- for ns in range(num_sample_to_copy):
- lib_widget, folder_widget = self.__library_widgets(trans, request.user,
- len(current_samples),
- libraries, sample=None,
- lib_id=src_library_id,
- folder_id=src_folder_id,
- **kwd)
- current_samples.append(dict(name=current_samples[src_sample_index]['name']+'_%i' % (len(current_samples)+1),
- barcode='',
- library_id='none',
- folder_id='none',
- field_values=[val for val in current_samples[src_sample_index]['field_values']],
- lib_widget=lib_widget,
- folder_widget=folder_widget))
- return trans.fill_template( '/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples=current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details=details,
- edit_mode=edit_mode)
- elif params.get('save_samples_button', False) == 'Save':
- # check for duplicate sample names
- message = ''
- for index in range(len(current_samples)-len(request.samples)):
- sample_index = index + len(request.samples)
- sample_name = current_samples[sample_index]['name']
- if not sample_name.strip():
- message = 'Please enter the name of sample number %i' % sample_index
- break
- count = 0
- for i in range(len(current_samples)):
- if sample_name == current_samples[i]['name']:
- count = count + 1
- if count > 1:
- message = "This request has <b>%i</b> samples with the name <b>%s</b>.\nSamples belonging to a request must have unique names." % (count, sample_name)
- break
- if message:
- return trans.fill_template( '/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples = current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details=details, edit_mode=edit_mode,
- status='error', message=message)
- # save all the new/unsaved samples entered by the user
- if edit_mode == 'False':
- for index in range(len(current_samples)-len(request.samples)):
- sample_index = len(request.samples)
- form_values = trans.app.model.FormValues(request.type.sample_form,
- current_samples[sample_index]['field_values'])
- trans.sa_session.add( form_values )
- trans.sa_session.flush()
- s = trans.app.model.Sample(current_samples[sample_index]['name'], '',
- request, form_values,
- current_samples[sample_index]['barcode'],
- current_samples[sample_index]['library'],
- current_samples[sample_index]['folder'],
- dataset_files=[])
- trans.sa_session.add( s )
- trans.sa_session.flush()
- else:
- status = 'done'
- message = 'Changes made to the sample(s) are saved. '
- for sample_index in range(len(current_samples)):
- sample = request.samples[sample_index]
- sample.name = current_samples[sample_index]['name']
- sample.library = current_samples[sample_index]['library']
- sample.folder = current_samples[sample_index]['folder']
- if request.submitted():
- bc_message = self.__validate_barcode(trans, sample, current_samples[sample_index]['barcode'])
- if bc_message:
- status = 'error'
- message += bc_message
- else:
- sample.bar_code = current_samples[sample_index]['barcode']
- trans.sa_session.add( sample )
- trans.sa_session.flush()
- form_values = trans.sa_session.query( trans.app.model.FormValues ).get( sample.values.id )
- form_values.content = current_samples[sample_index]['field_values']
- trans.sa_session.add( form_values )
- trans.sa_session.flush()
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- operation='show_request',
- id=trans.security.encode_id(request.id),
- status=status,
- message=message ))
- elif params.get('edit_samples_button', False) == 'Edit samples':
- edit_mode = 'True'
- return trans.fill_template( '/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples=current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details=details, libraries=libraries,
- edit_mode=edit_mode)
- elif params.get('cancel_changes_button', False) == 'Cancel':
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- operation='show_request',
- id=trans.security.encode_id(request.id)) )
- else:
- return trans.fill_template( '/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples=current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details=details, libraries=libraries,
- edit_mode=edit_mode, status=status, message=message)
-
-
- @web.expose
- @web.require_login( "create/submit sequencing requests" )
- def delete_sample(self, trans, **kwd):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', 0 ) ) )
- current_samples, details, edit_mode = self.__update_samples( request, **kwd )
- sample_index = int(params.get('sample_id', 0))
- sample_name = current_samples[sample_index]['name']
- s = request.has_sample(sample_name)
- if s:
- trans.sa_session.delete( s.values )
- trans.sa_session.delete( s )
- trans.sa_session.flush()
- del current_samples[sample_index]
- return trans.fill_template( '/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples = current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details=details,
- edit_mode=edit_mode)
- def __select_request_type(self, trans, rtid):
- requesttype_list = trans.user.accessible_request_types(trans)
- rt_ids = ['none']
- for rt in requesttype_list:
- if not rt.deleted:
- rt_ids.append(str(rt.id))
- select_reqtype = SelectField('select_request_type',
- refresh_on_change=True,
- refresh_on_change_values=rt_ids[1:])
- if rtid == 'none':
- select_reqtype.add_option('Select one', 'none', selected=True)
- else:
- select_reqtype.add_option('Select one', 'none')
- for rt in requesttype_list:
- if not rt.deleted:
- if rtid == rt.id:
- select_reqtype.add_option(rt.name, rt.id, selected=True)
- else:
- select_reqtype.add_option(rt.name, rt.id)
- return select_reqtype
- @web.expose
- @web.require_login( "create/submit sequencing requests" )
- def new(self, trans, **kwd):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- if params.get('select_request_type', False) == 'True':
- return trans.fill_template( '/requests/new_request.mako',
- select_request_type=self.__select_request_type(trans, 'none'),
- widgets=[],
- message=message,
- status=status)
- elif params.get('create', False) == 'True':
- if params.get('create_request_button', False) == 'Save' \
- or params.get('create_request_samples_button', False) == 'Add samples':
- request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
- if not util.restore_text(params.get('name', '')):
- message = 'Please enter the <b>Name</b> of the request'
- kwd['create'] = 'True'
- kwd['status'] = 'error'
- kwd['message'] = message
- kwd['create_request_button'] = None
- kwd['create_request_samples_button'] = None
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='new',
- **kwd) )
- request = self.__save_request(trans, None, **kwd)
- message = 'The new request named <b>%s</b> has been created' % request.name
- if params.get('create_request_button', False) == 'Save':
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- message=message ,
- status='done') )
- elif params.get('create_request_samples_button', False) == 'Add samples':
- new_kwd = {}
- new_kwd['id'] = trans.security.encode_id(request.id)
- new_kwd['operation'] = 'show_request'
- new_kwd['add_sample'] = True
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- message=message ,
- status='done',
- **new_kwd) )
- else:
- return self.__show_request_form(trans, **kwd)
- elif params.get('refresh', False) == 'true':
- return self.__show_request_form(trans, **kwd)
- def __show_request_form(self, trans, **kwd):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- try:
- request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
- except:
- return trans.fill_template( '/requests/new_request.mako',
- select_request_type=self.__select_request_type(trans, 'none'),
- widgets=[],
- message=message,
- status=status)
- form_values = None
- select_request_type = self.__select_request_type(trans, request_type.id)
- # list of widgets to be rendered on the request form
- widgets = []
- widgets.append(dict(label='Name of the Experiment',
- widget=TextField('name', 40,
- util.restore_text( params.get( 'name', '' ) )),
- helptext='(Required)'))
- widgets.append(dict(label='Description',
- widget=TextField('desc', 40,
- util.restore_text( params.get( 'desc', '' ) )),
- helptext='(Optional)'))
- widgets = widgets + request_type.request_form.get_widgets( trans.user, **kwd )
- widgets.append(dict(label='Send email notification once the sequencing request is complete',
- widget=CheckboxField('email_notify', False),
- helptext=''))
- return trans.fill_template( '/requests/new_request.mako',
- select_request_type=select_request_type,
- request_type=request_type,
- widgets=widgets,
- message=message,
- status=status)
- def __validate(self, trans, request):
- '''
- Validates the request entered by the user
- '''
- empty_fields = []
- # check rest of the fields of the form
- for index, field in enumerate(request.type.request_form.fields):
- if field['required'] == 'required' and request.values.content[index] in ['', None]:
- empty_fields.append(field['label'])
- if empty_fields:
- message = 'Fill the following fields of the request <b>%s</b> before submitting<br/>' % request.name
- for ef in empty_fields:
- message = message + '<b>' +ef + '</b><br/>'
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- operation='edit',
- status = 'error',
- message=message,
- id=trans.security.encode_id(request.id) ))
- # now check the required fields of all the samples of this request
- for s in request.samples:
- for index, field in enumerate(request.type.sample_form.fields):
- if field['required'] == 'required' and s.values.content[index] in ['', None]:
- empty_fields.append((s.name, field['label']))
- if empty_fields:
- message = 'Fill the following fields of the request <b>%s</b> before submitting<br/>' % request.name
- for sname, ef in empty_fields:
- message = message + '<b>%s</b> field of sample <b>%s</b><br/>' % (ef, sname)
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- operation='show_request',
- status = 'error',
- message=message,
- id=trans.security.encode_id(request.id) ))
- def __save_request(self, trans, request=None, **kwd):
- '''
- This method saves a new request if request_id is None.
- '''
- params = util.Params( kwd )
- request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
- name = util.restore_text(params.get('name', ''))
- desc = util.restore_text(params.get('desc', ''))
- notify = CheckboxField.is_checked( params.get('email_notify', '') )
- # library
- try:
- library = trans.sa_session.query( trans.app.model.Library ).get( int( params.get( 'library_id', None ) ) )
- except:
- library = None
- try:
- folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( int( params.get( 'folder_id', None ) ) )
- except:
- if library:
- folder = library.root_folder
- else:
- folder = None
- # fields
- values = []
- for index, field in enumerate(request_type.request_form.fields):
- if field['type'] == 'AddressField':
- value = util.restore_text(params.get('field_%i' % index, ''))
- if value == 'new':
- # save this new address in the list of this user's addresses
- user_address = trans.app.model.UserAddress( user=trans.user )
- user_address.desc = util.restore_text(params.get('field_%i_short_desc' % index, ''))
- user_address.name = util.restore_text(params.get('field_%i_name' % index, ''))
- user_address.institution = util.restore_text(params.get('field_%i_institution' % index, ''))
- user_address.address = util.restore_text(params.get('field_%i_address1' % index, ''))+' '+util.restore_text(params.get('field_%i_address2' % index, ''))
- user_address.city = util.restore_text(params.get('field_%i_city' % index, ''))
- user_address.state = util.restore_text(params.get('field_%i_state' % index, ''))
- user_address.postal_code = util.restore_text(params.get('field_%i_postal_code' % index, ''))
- user_address.country = util.restore_text(params.get('field_%i_country' % index, ''))
- user_address.phone = util.restore_text(params.get('field_%i_phone' % index, ''))
- trans.sa_session.add( user_address )
- trans.sa_session.flush()
- trans.sa_session.refresh( trans.user )
- values.append(int(user_address.id))
- elif value == unicode('none'):
- values.append('')
- else:
- values.append(int(value))
- elif field['type'] == 'CheckboxField':
- values.append(CheckboxField.is_checked( params.get('field_%i' % index, '') ))
- else:
- values.append(util.restore_text(params.get('field_%i' % index, '')))
- form_values = trans.app.model.FormValues(request_type.request_form, values)
- trans.sa_session.add( form_values )
- trans.sa_session.flush()
- if not request:
- request = trans.app.model.Request(name, desc, request_type,
- trans.user, form_values, notify)
- trans.sa_session.add( request )
- trans.sa_session.flush()
- trans.sa_session.refresh( request )
- # create an event with state 'New' for this new request
- comments = "Request created."
- event = trans.app.model.RequestEvent(request, request.states.NEW, comments)
- trans.sa_session.add( event )
- trans.sa_session.flush()
- else:
- request.name = name
- request.desc = desc
- request.type = request_type
- request.user = trans.user
- request.values = form_values
- request.notify = notify
- trans.sa_session.add( request )
- trans.sa_session.flush()
- return request
- @web.expose
- @web.require_login( "create/submit sequencing requests" )
- def edit(self, trans, **kwd):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', None ) ) )
- except:
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- status='error',
- message="Invalid request ID",
- **kwd) )
- if params.get('show', False) == 'True':
- return self.__edit_request(trans, id=trans.security.encode_id(request.id), **kwd)
- elif params.get('save_changes_request_button', False) == 'Save changes' \
- or params.get('edit_samples_button', False) == 'Edit samples':
- request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
- if not util.restore_text(params.get('name', '')):
- message = 'Please enter the <b>Name</b> of the request'
- kwd['status'] = 'error'
- kwd['message'] = message
- kwd['show'] = 'True'
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='edit',
- **kwd) )
- request = self.__save_request(trans, request, **kwd)
- message = 'The changes made to the request named %s has been saved' % request.name
- if params.get('save_changes_request_button', False) == 'Save changes':
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- message=message ,
- status='done') )
- elif params.get('edit_samples_button', False) == 'Edit samples':
- new_kwd = {}
- new_kwd['request_id'] = request.id
- new_kwd['edit_samples_button'] = 'Edit samples'
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='show_request',
- message=message ,
- status='done',
- **new_kwd) )
- elif params.get('refresh', False) == 'true':
- return self.__edit_request(trans, id=trans.security.encode_id(request.id), **kwd)
-
- def __edit_request(self, trans, **kwd):
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
- except:
- message = "Invalid request ID"
- log.warn( message )
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- status='error',
- message=message) )
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- select_request_type = self.__select_request_type(trans, request.type.id)
- # list of widgets to be rendered on the request form
- widgets = []
- if util.restore_text( params.get( 'name', '' ) ):
- name = util.restore_text( params.get( 'name', '' ) )
- else:
- name = request.name
- widgets.append(dict(label='Name',
- widget=TextField('name', 40, name),
- helptext='(Required)'))
- if util.restore_text( params.get( 'desc', '' ) ):
- desc = util.restore_text( params.get( 'desc', '' ) )
- else:
- desc = request.desc
- widgets.append(dict(label='Description',
- widget=TextField('desc', 40, desc),
- helptext='(Optional)'))
- widgets = widgets + request.type.request_form.get_widgets( trans.user, request.values.content, **kwd )
- widgets.append(dict(label='Send email notification once the sequencing request is complete',
- widget=CheckboxField('email_notify', request.notify),
- helptext=''))
- return trans.fill_template( '/requests/edit_request.mako',
- select_request_type=select_request_type,
- request_type=request.type,
- request=request,
- widgets=widgets,
- message=message,
- status=status)
- return self.__show_request_form(trans)
- def __delete_request(self, trans, **kwd):
- id_list = util.listify( kwd['id'] )
- delete_failed = []
- for id in id_list:
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(id) )
- except:
- message = "Invalid request ID"
- log.warn( message )
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- status='error',
- message=message,
- **kwd) )
- # a request cannot be deleted once its submitted
- if not request.new():
- delete_failed.append(request.name)
- else:
- request.deleted = True
- trans.sa_session.add( request )
- # delete all the samples belonging to this request
- for s in request.samples:
- s.deleted = True
- trans.sa_session.add( s )
- trans.sa_session.flush()
- if not len(delete_failed):
- message = '%i request(s) has been deleted.' % len(id_list)
- status = 'done'
- else:
- message = '%i request(s) has been deleted. %i request %s could not be deleted as they have been submitted.' % (len(id_list)-len(delete_failed),
- len(delete_failed), str(delete_failed))
- status = 'warning'
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- status=status,
- message=message) )
- def __undelete_request(self, trans, **kwd):
- id_list = util.listify( kwd['id'] )
- for id in id_list:
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(id) )
- except:
- message = "Invalid request ID"
- log.warn( message )
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- status='error',
- message=message,
- **kwd) )
- request.deleted = False
- trans.sa_session.add( request )
- # undelete all the samples belonging to this request
- for s in request.samples:
- s.deleted = False
- trans.sa_session.add( s )
- trans.sa_session.flush()
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- status='done',
- message='%i request(s) has been undeleted.' % len(id_list) ) )
- def __submit_request(self, trans, **kwd):
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
- except:
- message = "Invalid request ID"
- log.warn( message )
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- status='error',
- message=message,
- **kwd) )
- # check if all the required request and its sample fields have been filled
- self.__validate(trans, request)
- # change the request state to 'Submitted'
- comments = "Sequencing request is in progress."
- event = trans.app.model.RequestEvent(request, request.states.SUBMITTED, comments)
- trans.sa_session.add( event )
- trans.sa_session.flush()
- # get the new state
- new_state = request.type.states[0]
- for s in request.samples:
- event = trans.app.model.SampleEvent(s, new_state, 'Samples submitted to the system')
- trans.sa_session.add( event )
- trans.sa_session.flush()
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- id=trans.security.encode_id(request.id),
- status='done',
- message='The request <b>%s</b> has been submitted.' % request.name
- ) )
- @web.expose
- @web.require_login( "create/submit sequencing requests" )
- def show_events(self, trans, **kwd):
- params = util.Params( kwd )
- try:
- sample_id = int(params.get('sample_id', False))
- sample = trans.sa_session.query( trans.app.model.Sample ).get( sample_id )
- except:
- message = "Invalid sample ID"
- return trans.response.send_redirect( web.url_for( controller='requests',
- action='list',
- status='error',
- message=message,
- **kwd) )
- events_list = []
- all_events = sample.events
- for event in all_events:
- events_list.append((event.state.name, event.state.desc, time_ago(event.update_time), event.comment))
- return trans.fill_template( '/sample/sample_events.mako',
- events_list=events_list,
- sample_name=sample.name,
- request=sample.request)
- #
- # Data transfer from sequencer
- #
- @web.expose
- @web.require_login( "create/submit sequencing requests" )
- def show_datatx_page( 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',
- action='list',
- status='error',
- message="Invalid sample ID",
- **kwd) )
- return trans.fill_template( '/requests/show_data.mako',
- sample=sample, dataset_files=sample.dataset_files )
+
--- a/lib/galaxy/web/controllers/forms.py
+++ b/lib/galaxy/web/controllers/forms.py
@@ -474,8 +474,7 @@ class Forms( BaseController ):
return trans.response.send_redirect( web.url_for( controller='forms',
action='new',
status='error',
- message='Error in importing <b>%s</b> file' % csv_file,
- **kwd))
+ message='Error in importing <b>%s</b> file' % csv_file.file))
self.__imported_from_file = True
return fields, list(layouts)
def __validate_form(self, **kwd):
--- a/templates/admin/requests/edit_request.mako
+++ /dev/null
@@ -1,88 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-<script type="text/javascript">
-$( function() {
- $( "select[refresh_on_change='true']").change( function() {
- var refresh = false;
- var refresh_on_change_values = $( this )[0].attributes.getNamedItem( 'refresh_on_change_values' )
- if ( refresh_on_change_values ) {
- refresh_on_change_values = refresh_on_change_values.value.split( ',' );
- var last_selected_value = $( this )[0].attributes.getNamedItem( 'last_selected_value' );
- for( i= 0; i < refresh_on_change_values.length; i++ ) {
- if ( $( this )[0].value == refresh_on_change_values[i] || ( last_selected_value && last_selected_value.value == refresh_on_change_values[i] ) ){
- refresh = true;
- break;
- }
- }
- }
- else {
- refresh = true;
- }
- if ( refresh ){
- $( "#edit_request" ).submit();
- }
- });
-});
-</script>
-
-<br/>
-<br/>
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='show_request', id=trans.security.encode_id(request.id) )}">
- <span>Browse this request</span></a>
- </li>
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='list')}">
- <span>Browse requests</span></a>
- </li>
-</ul>
-
-<div class="toolForm">
- <div class="toolFormTitle">Edit request "${request.name}" from ${request.user.email}</div>
- %if len(select_request_type.options) == 1:
- There are no request types created for a new request.
- %else:
- <div class="toolFormBody">
- <form name="edit_request" id="edit_request" action="${h.url_for( controller='requests_admin', action='edit', request_id=request.id)}" method="post" >
- <div class="form-row">
- <label>
- Select Request Type:
- </label>
- ${select_request_type.get_html()}
- </div>
-
- %if select_request_type.get_selected() != ('Select one', 'none'):
- %for i, field in enumerate(widgets):
- <div class="form-row">
- <label>${field['label']}</label>
- ${field['widget'].get_html()}
- %if field['label'] == 'Library' and new_library:
- ${new_library.get_html()}
- %endif
- <div class="toolParamHelp" style="clear: both;">
- ${field['helptext']}
- </div>
- <div style="clear: both"></div>
- </div>
- %endfor
- <div class="form-row">
- <div style="float: left; width: 250px; margin-right: 10px;">
- <input type="hidden" name="refresh" value="true" size="40"/>
- </div>
- <div style="clear: both"></div>
- </div>
- <div class="form-row">
- <input type="submit" name="save_changes_request_button" value="Save changes"/>
- ##<input type="submit" name="edit_samples_button" value="Edit samples"/>
- </div>
- %endif
- </form>
- </div>
-</div>
-%endif
--- a/templates/requests/sample_datasets.mako
+++ /dev/null
@@ -1,7 +0,0 @@
-<%def name="render_sample_datasets( sample )">
- <a href="${h.url_for(controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id))}">${sample.transferred_dataset_files()}/${len(sample.dataset_files)}</a>
-</%def>
-
-
-
-${render_sample_datasets( sample )}
--- /dev/null
+++ b/templates/requests/common/events.mako
@@ -0,0 +1,39 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+<h2>History of Sequencing Request "${request.name}"</h2>
+<ul class="manage-table-actions">
+ <li>
+ <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='show_request', id=trans.security.encode_id(request.id) )}">
+ <span>Browse this request</span></a>
+ </li>
+ <li>
+ <a class="action-button" href="${h.url_for( controller=cntrller, action='list')}">
+ <span>Browse all requests</span></a>
+ </li>
+</ul>
+
+%if message:
+ ${render_msg( message, status )}
+%endif
+
+<div class="toolForm">
+ <table class="grid">
+ <thead>
+ <tr>
+ <th>State</th>
+ <th>Last Update</th>
+ <th>Comments</th>
+ </tr>
+ </thead>
+ <tbody>
+ %for state, updated, comments in events_list:
+ <tr class="libraryRow libraryOrFolderRow" id="libraryRow">
+ <td><b><a>${state}</a></b></td>
+ <td><a>${updated}</a></td>
+ <td><a>${comments}</a></td>
+ </tr>
+ %endfor
+ </tbody>
+ </table>
+</div>
--- /dev/null
+++ b/templates/requests/common/show_request.mako
@@ -0,0 +1,547 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+<%namespace file="/requests/common/sample_state.mako" import="render_sample_state" />
+<%namespace file="/requests/common/sample_datasets.mako" import="render_sample_datasets" />
+
+
+
+<script type="text/javascript">
+$( function() {
+ $( "select[refresh_on_change='true']").change( function() {
+ var refresh = false;
+ var refresh_on_change_values = $( this )[0].attributes.getNamedItem( 'refresh_on_change_values' )
+ if ( refresh_on_change_values ) {
+ refresh_on_change_values = refresh_on_change_values.value.split( ',' );
+ var last_selected_value = $( this )[0].attributes.getNamedItem( 'last_selected_value' );
+ for( i= 0; i < refresh_on_change_values.length; i++ ) {
+ if ( $( this )[0].value == refresh_on_change_values[i] || ( last_selected_value && last_selected_value.value == refresh_on_change_values[i] ) ){
+ refresh = true;
+ break;
+ }
+ }
+ }
+ else {
+ refresh = true;
+ }
+ if ( refresh ){
+ $( "#show_request" ).submit();
+ }
+ });
+});
+</script>
+
+
+<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">
+ // Looks for changes in sample states using an async request. Keeps
+ // calling itself (via setTimeout) until all samples are in a terminal
+ // state.
+ var updater = function ( sample_states ) {
+ // Check if there are any items left to track
+ var empty = true;
+ for ( i in sample_states ) {
+ empty = false;
+ break;
+ }
+ if ( ! empty ) {
+ setTimeout( function() { updater_callback( sample_states ) }, 1000 );
+ }
+ };
+ var updater_callback = function ( sample_states ) {
+ // Build request data
+ var ids = []
+ var states = []
+ $.each( sample_states, function ( id, state ) {
+ ids.push( id );
+ states.push( state );
+ });
+ // Make ajax call
+ $.ajax( {
+ type: "POST",
+ url: "${h.url_for( controller='requests_common', action='sample_state_updates' )}",
+ dataType: "json",
+ data: { ids: ids.join( "," ), states: states.join( "," ) },
+ success : function ( data ) {
+ $.each( data, function( id, val, cntrller ) {
+ // Replace HTML
+ var cell1 = $("#sampleState-" + id);
+ cell1.html( val.html_state );
+ var cell2 = $("#sampleDatasets-" + id);
+ cell2.html( val.html_datasets );
+ sample_states[ parseInt(id) ] = val.state;
+ });
+ updater( sample_states );
+ },
+ error: function() {
+ // Just retry, like the old method, should try to be smarter
+ updater( sample_states );
+ }
+ });
+ };
+</script>
+
+<style type="text/css">
+.msg_head {
+ padding: 0px 0px;
+ cursor: pointer;
+}
+</style>
+
+<script type="text/javascript">
+ function stopRKey(evt) {
+ var evt = (evt) ? evt : ((event) ? event : null);
+ var node = (evt.target) ? evt.target : ((evt.srcElement) ? evt.srcElement : null);
+ if ((evt.keyCode == 13) && (node.type=="text")) {return false;}
+ }
+ document.onkeypress = stopRKey
+</script>
+
+%if request.submitted():
+ <% samples_not_ready = request.sequence_run_ready() %>
+ %if samples_not_ready:
+ ${render_msg( "Select a target library and folder for all the samples before starting the sequence run", "warning" )}
+ %endif
+%endif
+
+%if request.rejected():
+ ${render_msg( "Reason for rejection: "+request.last_comment(), "warning" )}
+%endif
+
+<div class="grid-header">
+ <h2>Sequencing Request "${request.name}"</h2>
+</div>
+
+<ul class="manage-table-actions">
+
+ %if request.unsubmitted() and request.samples:
+ <li>
+ <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>
+ %endif
+ %endif
+ <li>
+ <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>
+ <li>
+ <a class="action-button" href="${h.url_for( controller=cntrller, action='list')}">
+ <span>Browse requests</span></a>
+ </li>
+
+</ul>
+
+
+%if message:
+ ${render_msg( message, status )}
+%endif
+
+
+
+
+<div class="toolForm">
+ <div class="form-row">
+ <div class="msg_list">
+ <h4 class="msg_head"><u>Request Information</u></h4>
+ <div class="msg_body">
+ %for index, rd in enumerate(request_details):
+ <div class="form-row">
+ <label>${rd['label']}</label>
+ %if not rd['value']:
+ <i>None</i>
+ %else:
+ %if rd['label'] == 'State':
+ <a href="${h.url_for( controller=cntrller, action='list', operation='events', id=trans.security.encode_id(request.id) )}">${rd['value']}</a>
+ %else:
+ ${rd['value']}
+ %endif
+ %endif
+ </div>
+ <div style="clear: both"></div>
+ %endfor
+ <div class="form-row">
+ <ul class="manage-table-actions">
+ <li>
+ <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='Edit', id=trans.security.encode_id(request.id))}">
+ <span>Edit request details</span></a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+
+<br/>
+
+<div class="toolForm">
+ <form id="show_request" name="show_request" action="${h.url_for( controller='requests_common', cntrller=cntrller, action='request_page', edit_mode=edit_mode )}" method="post" >
+ <div class="form-row">
+ %if current_samples:
+ ## first render the basic info grid
+ ${render_basic_info_grid()}
+ ## then render the other grid(s)
+ <% trans.sa_session.refresh( request.type.sample_form ) %>
+ %for grid_index, grid_name in enumerate(request.type.sample_form.layout):
+ ${render_grid( grid_index, grid_name, request.type.sample_form.fields_of_grid( grid_index ) )}
+ <br/>
+ %endfor
+ %else:
+ <label>There are no samples.</label>
+ %endif
+ </div>
+ %if request.samples and request.submitted():
+ <script type="text/javascript">
+ // Updater
+ updater({${ ",".join( [ '"%s" : "%s"' % ( s.id, s.current_state().name ) for s in request.samples ] ) }});
+ </script>
+ %endif
+
+ %if edit_mode == 'False':
+ <table class="grid">
+ <tbody>
+ <tr>
+ <div class="form-row">
+
+ %if request.unsubmitted():
+ <td>
+ %if current_samples:
+ <label>Copy </label>
+ <input type="integer" name="num_sample_to_copy" value="1" size="3"/>
+ <label>sample(s) from sample</label>
+ ${sample_copy.get_html()}
+ %endif
+ <input type="submit" name="add_sample_button" value="Add New"/>
+ </td>
+ %endif
+ <td>
+ %if len(current_samples) and len(current_samples) <= len(request.samples):
+ <input type="submit" name="edit_samples_button" value="Edit samples"/>
+ %endif
+ </td>
+ </div>
+ </tr>
+ </tbody>
+ </table>
+ %endif
+ %if request.samples or current_samples:
+ <div class="form-row">
+ <div style="float: left; width: 250px; margin-right: 10px;">
+ <input type="hidden" name="refresh" value="true" size="40"/>
+ </div>
+ <div style="clear: both"></div>
+ </div>
+ %if edit_mode == 'True':
+ <div class="form-row">
+ <input type="submit" name="save_samples_button" value="Save"/>
+ <input type="submit" name="cancel_changes_button" value="Cancel"/>
+ </div>
+ %elif request.unsubmitted():
+ <div class="form-row">
+ <input type="submit" name="save_samples_button" value="Save"/>
+ </div>
+ %endif
+
+ %endif
+ <input type="hidden" name="id" value="${trans.security.encode_id(request.id)}" />
+ </form>
+</div>
+
+<br/>
+
+%if request.unsubmitted():
+<div class="toolForm">
+ <form id="import" name="import" action="${h.url_for( controller='requests_common', action='request_page', edit_mode=edit_mode, request_id=trans.security.encode_id(request.id) )}" enctype="multipart/form-data" method="post" >
+ <div class="form-row">
+ <div class="msg_list">
+ <h4 class="msg_head"><u>Import samples from csv file</u></h4>
+ <div class="msg_body">
+ <input type="file" name="file_data" />
+ <input type="submit" name="import_samples_button" value="Import samples"/>
+ <br/>
+ <div class="toolParamHelp" style="clear: both;">
+ The csv file must be in the following format:<br/>
+ SampleName,DataLibrary,DataLibraryFolder,FieldValue1,FieldValue2...
+ </div>
+ </div>
+ </div>
+ </div>
+## <input type="hidden" name="request_id" value="${request.id}" />
+ </form>
+</div>
+%endif
+
+<%def name="render_grid( grid_index, grid_name, fields_dict )">
+ <br/>
+ <div class="msg_list">
+ %if grid_name:
+ <h4 class="msg_head"><u>${grid_name}</u></h4>
+ %else:
+ <h4>Grid ${grid_index}</h4>
+ %endif
+ %if edit_mode == 'False' or len(current_samples) <= len(request.samples):
+ <div class="msg_body">
+ %else:
+ <div class="msg_body2">
+ %endif
+ <table class="grid">
+ <thead>
+ <tr>
+ <th>Name</th>
+ %for index, field in fields_dict.items():
+ <th>
+ ${field['label']}
+ <div class="toolParamHelp" style="clear: both;">
+ <i>${field['helptext']}</i>
+ </div>
+ </th>
+ %endfor
+ <th></th>
+ </tr>
+ <thead>
+ <tbody>
+ <%
+ trans.sa_session.refresh( request )
+ %>
+ %for sample_index, sample in enumerate(current_samples):
+ %if edit_mode == 'True':
+ <tr>
+ ${render_sample_form( sample_index, sample['name'], sample['field_values'], fields_dict)}
+ </tr>
+ %else:
+ <tr>
+ %if sample_index in range(len(request.samples)):
+ ${render_sample( sample_index, sample['name'], sample['field_values'], fields_dict )}
+ %else:
+ ${render_sample_form( sample_index, sample['name'], sample['field_values'], fields_dict)}
+ %endif
+ </tr>
+ %endif
+ %endfor
+ </tbody>
+ </table>
+ </div>
+ </div>
+</%def>
+
+## This function displays the "Basic Information" grid
+<%def name="render_basic_info_grid()">
+ <h4>Sample Information</h4>
+ <table class="grid">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Barcode</th>
+ <th>State</th>
+ <th>Data Library</th>
+ <th>Folder</th>
+ %if request.submitted() or request.complete():
+ <th>Dataset(s) Transferred</th>
+ %endif
+ <th></th>
+ </tr>
+ <thead>
+ <tbody>
+ <%
+ trans.sa_session.refresh( request )
+ %>
+ %for sample_index, info in enumerate(current_samples):
+ <%
+ if sample_index in range(len(request.samples)):
+ sample = request.samples[sample_index]
+ else:
+ sample = None
+ %>
+ %if edit_mode == 'True':
+ <tr>
+ ${show_basic_info_form( sample_index, sample, info )}
+ </tr>
+ %else:
+ <tr>
+ %if sample_index in range(len(request.samples)):
+ <td>${info['name']}</td>
+ <td>${info['barcode']}</td>
+ %if sample.request.unsubmitted():
+ <td>Unsubmitted</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>
+ %else:
+ <td></td>
+ %endif
+ %if info['folder']:
+ <td>${info['folder'].name}</td>
+ %else:
+ <td></td>
+ %endif
+ %if request.submitted() or request.complete():
+ <td id="sampleDatasets-${sample.id}">
+ ${render_sample_datasets( cntrller, sample )}
+ </td>
+ %endif
+
+
+ %else:
+ ${show_basic_info_form( sample_index, sample, info )}
+ %endif
+ %if request.unsubmitted() or request.rejected():
+ <td>
+ %if sample:
+ %if sample.request.unsubmitted():
+ <a class="action-button" href="${h.url_for( controller='requests_common', cntrller=cntrller, action='delete_sample', request_id=request.id, sample_id=sample_index )}">
+ <img src="${h.url_for('/static/images/delete_icon.png')}" />
+ <span></span></a>
+ %endif
+ %endif
+ </td>
+ %endif
+ </tr>
+ %endif
+ %endfor
+ </tbody>
+ </table>
+</%def>
+
+<%def name="show_basic_info_form( sample_index, sample, info )">
+ <td>
+ <input type="text" name=sample_${sample_index}_name value="${info['name']}" size="10"/>
+ <div class="toolParamHelp" style="clear: both;">
+ <i>${' (required)' }</i>
+ </div>
+ </td>
+ %if cntrller == 'requests':
+ %if sample:
+ %if sample.request.unsubmitted():
+ <td></td>
+ %else:
+ <td><input type="text" name=sample_${sample_index}_barcode value="${info['barcode']}" size="10"/></td>
+ %endif
+ %else:
+ <td></td>
+ %endif
+ %elif cntrller == 'requests_admin':
+ %if sample:
+ %if sample.request.unsubmitted():
+ <td></td>
+ %else:
+ <td><input type="text" name=sample_${sample_index}_barcode value="${info['barcode']}" size="10"/></td>
+ %endif
+ %else:
+ <td></td>
+ %endif
+ %endif
+ %if sample:
+ %if sample.request.unsubmitted():
+ <td>Unsubmitted</td>
+ %else:
+ <td><a href="${h.url_for( controller='requests_admin', action='sample_events', sample_id=sample.id)}">${sample.current_state().name}</a></td>
+ %endif
+ %else:
+ <td></td>
+ %endif
+ <td>${info['lib_widget'].get_html()}</td>
+ <td>${info['folder_widget'].get_html()}</td>
+ %if request.submitted() or request.complete():
+ %if sample:
+ <td><a href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">${len(sample.dataset_files)}</a></td>
+ %else:
+ <td><a href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">Add</a></td>
+ %endif
+ %endif
+</%def>
+
+<%def name="render_sample( index, sample_name, sample_values, fields_dict )">
+ <td>
+ ${sample_name}
+ </td>
+ %for field_index, field in fields_dict.items():
+ <td>
+ %if sample_values[field_index]:
+ %if field['type'] == 'WorkflowField':
+ %if str(sample_values[field_index]) != 'none':
+ <% workflow = trans.sa_session.query( trans.app.model.StoredWorkflow ).get( int(sample_values[field_index]) ) %>
+ <a href="${h.url_for( controller='workflow', action='run', id=trans.security.encode_id(workflow.id) )}">${workflow.name}</a>
+ %endif
+ %else:
+ ${sample_values[field_index]}
+ %endif
+ %else:
+ <i>None</i>
+ %endif
+ </td>
+ %endfor
+</%def>
+
+<%def name="render_sample_form( index, sample_name, sample_values, fields_dict )">
+ <td>
+ ${sample_name}
+ </td>
+ %for field_index, field in fields_dict.items():
+ <td>
+ %if field['type'] == 'TextField':
+ <input type="text" name="sample_${index}_field_${field_index}" value="${sample_values[field_index]}" size="7"/>
+ %elif field['type'] == 'SelectField':
+ <select name="sample_${index}_field_${field_index}" last_selected_value="2">
+ %for option_index, option in enumerate(field['selectlist']):
+ %if option == sample_values[field_index]:
+ <option value="${option}" selected>${option}</option>
+ %else:
+ <option value="${option}">${option}</option>
+ %endif
+ %endfor
+ </select>
+ %elif field['type'] == 'WorkflowField':
+ <select name="sample_${index}_field_${field_index}">
+ %if str(sample_values[field_index]) == 'none':
+ <option value="none" selected>Select one</option>
+ %else:
+ <option value="none">Select one</option>
+ %endif
+ %for option_index, option in enumerate(request.user.stored_workflows):
+ %if not option.deleted:
+ %if str(option.id) == str(sample_values[field_index]):
+ <option value="${option.id}" selected>${option.name}</option>
+ %else:
+ <option value="${option.id}">${option.name}</option>
+ %endif
+ %endif
+ %endfor
+ </select>
+ %elif field['type'] == 'CheckboxField':
+ <input type="checkbox" name="sample_${index}_field_${field_index}" value="Yes"/>
+ %endif
+ <div class="toolParamHelp" style="clear: both;">
+ <i>${'('+field['required']+')' }</i>
+ </div>
+ </td>
+ %endfor
+</%def>
+
+
+
+
+
+
+
+
+
+
--- a/templates/admin/requests/new_request.mako
+++ /dev/null
@@ -1,94 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-<script type="text/javascript">
-$( function() {
- $( "select[refresh_on_change='true']").change( function() {
- var refresh = false;
- var refresh_on_change_values = $( this )[0].attributes.getNamedItem( 'refresh_on_change_values' )
- if ( refresh_on_change_values ) {
- refresh_on_change_values = refresh_on_change_values.value.split( ',' );
- var last_selected_value = $( this )[0].attributes.getNamedItem( 'last_selected_value' );
- for( i= 0; i < refresh_on_change_values.length; i++ ) {
- if ( $( this )[0].value == refresh_on_change_values[i] || ( last_selected_value && last_selected_value.value == refresh_on_change_values[i] ) ){
- refresh = true;
- break;
- }
- }
- }
- else {
- refresh = true;
- }
- if ( refresh ){
- $( "#new_request" ).submit();
- }
- });
-});
-</script>
-
-<%def name="javascripts()">
- ${parent.javascripts()}
- ${h.js("jquery.autocomplete", "autocomplete_tagging" )}
-</%def>
-
-<%def name="stylesheets()">
- ${parent.stylesheets()}
- ${h.css( "autocomplete_tagging" )}
-</%def>
-
-<br/>
-<br/>
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='list')}">
- <span>Browse requests</span></a>
- </li>
-</ul>
-
-<div class="toolForm">
- <div class="toolFormTitle">Add a new request</div>
- %if len(select_request_type.options) == 1:
- There are no request types created for a new request.
- %else:
- <div class="toolFormBody">
- <form name="new_request" id="new_request" action="${h.url_for( controller='requests_admin', action='new', create=True )}" method="post" >
- <div class="form-row">
- <label>
- Select Request Type
- </label>
- ${select_request_type.get_html()}
- </div>
-
- %if select_request_type.get_selected() != ('Select one', 'none'):
- %for i, field in enumerate(widgets):
- <div class="form-row">
- <label>${field['label']}</label>
- ${field['widget'].get_html()}
- %if field['label'] == 'Library' and new_library:
- ${new_library.get_html()}
- %endif
- <div class="toolParamHelp" style="clear: both;">
- ${field['helptext']}
- </div>
- <div style="clear: both"></div>
- </div>
- %endfor
- <div class="form-row">
- <div style="float: left; width: 250px; margin-right: 10px;">
- <input type="hidden" name="refresh" value="true" size="40"/>
- </div>
- <div style="clear: both"></div>
- </div>
- <div class="form-row">
- <input type="submit" name="create_request_button" value="Save"/>
- <input type="submit" name="create_request_samples_button" value="Add samples"/>
- </div>
- %endif
- </form>
- </div>
-</div>
-%endif
--- a/templates/sample/sample_events.mako
+++ /dev/null
@@ -1,45 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-
-<%def name="title()">Events for Sample ${sample_name}</%def>
-
-
-<h2>Events for Sample "${sample_name}"</h2>
-
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( controller='requests', action='list', operation='show_request', id=trans.security.encode_id(request.id) )}">
- <span>Browse this request</span></a>
- </li>
- <li>
- <a class="action-button" href="${h.url_for( controller='requests', action='list')}">
- <span>Browse requests</span></a>
- </li>
-</ul>
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-<div class="toolForm">
- <table class="grid">
- <thead>
- <tr>
- <th>State</th>
- <th>Description</th>
- <th>Updated</th>
- <th>Comments</th>
- </tr>
- </thead>
- <tbody>
- %for state, desc, updated, comments in events_list:
- <tr class="libraryRow libraryOrFolderRow" id="libraryRow">
- <td><b><a>${state}</a></b></td>
- <td><a>${desc}</a></td>
- <td><a>${updated}</a></td>
- <td><a>${comments}</a></td>
- </tr>
- %endfor
- </tbody>
- </table>
-</div>
--- /dev/null
+++ b/templates/requests/common/sample_datasets.mako
@@ -0,0 +1,7 @@
+<%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>
+</%def>
+
+
+
+${render_sample_datasets( cntrller, sample )}
--- a/test/base/twilltestcase.py
+++ b/test/base/twilltestcase.py
@@ -1485,14 +1485,13 @@ class TwillTestCase( unittest.TestCase )
url ="%s&%s=%s" % ( url, key, role_ids_str )
self.home()
self.visit_url( "%s/%s" % ( self.url, url ) )
- print url
check_str = "Permissions updated for request type '%s'" % request_type_name
self.check_page_for_string( check_str )
self.home()
def create_request( self, request_type_id, name, desc, fields ):
self.home()
- self.visit_url( "%s/requests/new?create=True&select_request_type=%i" % ( self.url,
- request_type_id ) )
+ self.visit_url( "%s/requests_common/new?select_request_type=%i&refresh=true&cntrller=requests" % ( self.url,
+ request_type_id ) )
self.check_page_for_string( 'Add a new request' )
tc.fv( "1", "name", name )
tc.fv( "1", "desc", desc )
@@ -1514,21 +1513,20 @@ class TwillTestCase( unittest.TestCase )
self.check_page_for_string( new_desc )
def add_samples( self, request_id, request_name, samples ):
self.home()
- url = "%s/requests/list?sort=-create_time&operation=show_request&id=%s" % ( self.url, self.security.encode_id( request_id ) )
+ url = "%s/requests/list?operation=show&id=%s" % ( self.url, self.security.encode_id( request_id ) )
self.visit_url( url )
self.check_page_for_string( 'Sequencing Request "%s"' % request_name )
self.check_page_for_string( 'There are no samples.' )
# this redundant stmt below is add so that the second form in
# the page gets selected
- tc.fv( "3", "request_id", request_id )
+ url = ["%s/requests_common/request_page?cntrller=requests&edit_mode=False&id=%s" % ( self.url, self.security.encode_id( request_id ) )]
for sample_index, sample in enumerate(samples):
- tc.submit( "add_sample_button" )
- self.check_page_for_string( 'Sequencing Request "%s"' % request_name )
sample_name, fields = sample
- tc.fv( "3", "sample_%i_name" % sample_index, sample_name )
+ url.append("sample_%i_name=%s" % (sample_index, sample_name.replace(' ', '+')))
for field_index, field_value in enumerate(fields):
- tc.fv( "3", "sample_%i_field_%i" % ( sample_index, field_index ), field_value )
- tc.submit( "save_samples_button" )
+ url.append("sample_%i_field_%i=%s" % ( sample_index, field_index , field_value.replace(' ', '+') ))
+ url.append("save_samples_button=Save")
+ self.visit_url('&'.join(url))
for sample_name, fields in samples:
self.check_page_for_string( sample_name )
self.check_page_for_string( 'Unsubmitted' )
@@ -1549,19 +1547,24 @@ class TwillTestCase( unittest.TestCase )
tc.fv( "1", "comment", comment )
tc.submit( "reject_button" )
self.check_page_for_string( 'Request <b>%s</b> has been rejected.' % request_name )
- self.visit_url( "%s/requests/list?sort=-create_time&operation=show_request&id=%s" % ( self.url, self.security.encode_id( request_id ) ))
+ self.visit_url( "%s/requests/list?&operation=show&id=%s" % ( self.url, self.security.encode_id( request_id ) ))
self.check_page_for_string( comment )
- def add_bar_codes( self, request_id, request_name, bar_codes ):
+ def add_bar_codes( self, request_id, request_name, bar_codes, samples ):
self.home()
- self.visit_url( "%s/requests_admin/bar_codes?request_id=%i" % (self.url, request_id) )
- self.check_page_for_string( 'Bar codes for Samples of Request "%s"' % request_name )
+ url = "%s/requests/list?operation=show&id=%s" % ( self.url, self.security.encode_id( request_id ) )
+ self.visit_url( url )
+ self.check_page_for_string( 'Sequencing Request "%s"' % request_name )
+ url = ["%s/requests_common/request_page?save_samples_button=Save&cntrller=requests&edit_mode=True&id=%s" % ( self.url, self.security.encode_id( request_id ) )]
for index, bar_code in enumerate(bar_codes):
- tc.fv( "1", "sample_%i_bar_code" % index, bar_code )
- tc.submit( "save_bar_codes" )
- self.check_page_for_string( 'Bar codes have been saved for this request' )
+ url.append("sample_%i_barcode=%s" % (index, bar_code ))
+ url.append("sample_%i_name=%s" % (index, samples[index].name.replace(' ', '+') ))
+ self.visit_url('&'.join(url))
+ self.check_page_for_string( 'Changes made to the sample(s) are saved.' )
+ for index, bar_code in enumerate(bar_codes):
+ self.check_page_for_string( bar_code )
def change_sample_state( self, sample_name, sample_id, new_state_id, new_state_name, comment='' ):
self.home()
- self.visit_url( "%s/requests_admin/show_events?sample_id=%i" % (self.url, sample_id) )
+ self.visit_url( "%s/requests_common/sample_events?cntrller=requests_admin&sample_id=%i" % (self.url, sample_id) )
self.check_page_for_string( 'Events for Sample "%s"' % sample_name )
tc.fv( "1", "select_state", str(new_state_id) )
tc.fv( "1", "comment", comment )
--- a/templates/requests/show_data.mako
+++ /dev/null
@@ -1,86 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-<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>
-<style type="text/css">
-.msg_head {
- padding: 0px 0px;
- cursor: pointer;
-}
-
-}
-</style>
-
-
-<h2>Data transfer from Sequencer</h2>
-<h3>Sample "${sample.name}" of Request "${sample.request.name}"</h3>
-
-<ul class="manage-table-actions">
- %if sample.request.submitted() and sample.inprogress_dataset_files():
- <li>
- <a class="action-button" href="${h.url_for( controller='requests', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">
- <span>Refresh this page</span></a>
- </li>
- %endif
- %if sample.library:
- <li>
- <a class="action-button" href="${h.url_for( controller='library_common', action='browse_library', cntrller='library', id=trans.security.encode_id( sample.library.id ) )}">
- <span>${sample.library.name} Data Library</span></a>
- </li>
- %endif
- <li>
- <a class="action-button" href="${h.url_for( controller='requests', action='list', operation='show_request', id=trans.security.encode_id(sample.request.id) )}">
- <span>Browse this request</span></a>
- </li>
-</ul>
-
-<div class="toolForm">
- <form name="get_data" action="${h.url_for( controller='requests_admin', action='get_data', sample_id=sample.id)}" method="post" >
- <div class="form-row">
- %if len(dataset_files):
- <table class="grid">
- <thead>
- <tr>
- <th>Dataset File</th>
- <th>Transfer Status</th>
- </tr>
- <thead>
- <tbody>
- %for dataset_index, dataset_file in enumerate(dataset_files):
- ${sample_dataset_files( dataset_index, dataset_file[0], dataset_file[1] )}
- %endfor
- </tbody>
- </table>
- %else:
- There are no dataset files.
- %endif
- </div>
- </form>
-</div>
-
-<%def name="sample_dataset_files( dataset_index, dataset_file, status )">
- <tr>
- <td>${dataset_file.split('/')[-1]}</td>
- <td>
- %if status == sample.transfer_status.IN_PROGRESS:
- <i>${status}</i>
- %else:
- ${status}
- %endif
- </td>
- </tr>
-</%def>
--- /dev/null
+++ b/lib/galaxy/web/controllers/requests_common.py
@@ -0,0 +1,1099 @@
+from galaxy.web.base.controller import *
+from galaxy.web.framework.helpers import time_ago, iff, grids
+from galaxy.model.orm import *
+from galaxy.datatypes import sniff
+from galaxy import model, util
+from galaxy.util.streamball import StreamBall
+import logging, tempfile, zipfile, tarfile, os, sys, subprocess
+from galaxy.web.form_builder import *
+from datetime import datetime, timedelta
+from galaxy.web.controllers.forms import get_all_forms
+from sqlalchemy.sql.expression import func, and_
+from sqlalchemy.sql import select
+import pexpect
+import ConfigParser, threading, time
+from amqplib import client_0_8 as amqp
+import csv
+log = logging.getLogger( __name__ )
+
+
+class RequestsCommon( BaseController ):
+
+ @web.json
+ def sample_state_updates( self, trans, ids=None, states=None, cntrller=None ):
+ # Avoid caching
+ trans.response.headers['Pragma'] = 'no-cache'
+ trans.response.headers['Expires'] = '0'
+ # Create new HTML for any that have changed
+ rval = {}
+ if ids is not None and states is not None:
+ ids = map( int, ids.split( "," ) )
+ states = states.split( "," )
+ for id, state in zip( ids, states ):
+ sample = trans.sa_session.query( self.app.model.Sample ).get( id )
+ if sample.current_state().name != state:
+ rval[id] = {
+ "state": sample.current_state().name,
+ "datasets": len(sample.dataset_files),
+ "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' )
+ }
+ return rval
+
+
+ @web.expose
+ @web.require_login( "create/submit sequencing requests" )
+ def new(self, trans, **kwd):
+ params = util.Params( kwd )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ if params.get('select_request_type', False) == 'True':
+ return trans.fill_template( '/requests/common/new_request.mako',
+ cntrller=cntrller,
+ select_request_type=self.__select_request_type(trans, 'none'),
+ widgets=[],
+ message=message,
+ status=status)
+ elif params.get('create_request_button', False) == 'Save' \
+ or params.get('create_request_samples_button', False) == 'Add samples':
+ request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
+ if not util.restore_text(params.get('name', '')) \
+ or util.restore_text(params.get('select_user', '')) == unicode('none'):
+ message = 'Please enter the <b>Name</b> of the request and the <b>user</b> on behalf of whom this request will be submitted before saving this request'
+ kwd['create'] = 'True'
+ kwd['status'] = 'error'
+ kwd['message'] = message
+ kwd['create_request_button'] = None
+ kwd['create_request_samples_button'] = None
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller=cntrller,
+ action='new',
+ **kwd) )
+ request = self.__save_request(trans, None, **kwd)
+ message = 'The new request named <b>%s</b> has been created' % request.name
+ if params.get('create_request_button', False) == 'Save':
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ message=message ,
+ status='done') )
+ elif params.get('create_request_samples_button', False) == 'Add samples':
+ new_kwd = {}
+ new_kwd['id'] = trans.security.encode_id(request.id)
+ new_kwd['operation'] = 'show'
+ new_kwd['add_sample'] = True
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ message=message ,
+ status='done',
+ **new_kwd) )
+ elif params.get('refresh', False) == 'true':
+ return self.__show_request_form(trans, **kwd)
+
+ def __select_request_type(self, trans, rtid):
+ requesttype_list = trans.user.accessible_request_types(trans)
+ rt_ids = ['none']
+ for rt in requesttype_list:
+ if not rt.deleted:
+ rt_ids.append(str(rt.id))
+ select_reqtype = SelectField('select_request_type',
+ refresh_on_change=True,
+ refresh_on_change_values=rt_ids[1:])
+ if rtid == 'none':
+ select_reqtype.add_option('Select one', 'none', selected=True)
+ else:
+ select_reqtype.add_option('Select one', 'none')
+ for rt in requesttype_list:
+ if not rt.deleted:
+ if rtid == rt.id:
+ select_reqtype.add_option(rt.name, rt.id, selected=True)
+ else:
+ select_reqtype.add_option(rt.name, rt.id)
+ return select_reqtype
+
+ def __show_request_form(self, trans, **kwd):
+ params = util.Params( kwd )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ try:
+ request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
+ except:
+ return trans.fill_template( '/requests/common/new_request.mako',
+ cntrller=cntrller,
+ select_request_type=self.__select_request_type(trans, 'none'),
+ widgets=[],
+ message=message,
+ status=status)
+ form_values = None
+ select_request_type = self.__select_request_type(trans, request_type.id)
+ # user
+ if cntrller == 'requests_admin' and trans.user_is_admin():
+ user_id = params.get( 'select_user', 'none' )
+ try:
+ user = trans.sa_session.query( trans.app.model.User ).get( int( user_id ) )
+ except:
+ user = None
+ elif cntrller == 'requests':
+ user = trans.user
+ # list of widgets to be rendered on the request form
+ widgets = []
+ if cntrller == 'requests_admin' and trans.user_is_admin():
+ widgets.append(dict(label='Select user',
+ widget=self.__select_user(trans, user_id),
+ helptext='The request would be submitted on behalf of this user (Required)'))
+ widgets.append(dict(label='Name of the Experiment',
+ widget=TextField('name', 40,
+ util.restore_text( params.get( 'name', '' ) )),
+ helptext='(Required)'))
+ widgets.append(dict(label='Description',
+ widget=TextField('desc', 40,
+ util.restore_text( params.get( 'desc', '' ) )),
+ helptext='(Optional)'))
+ widgets = widgets + request_type.request_form.get_widgets( user, **kwd )
+ widgets.append(dict(label='Send email notification when the sequencing request is complete',
+ widget=CheckboxField('email_notify', False),
+ helptext='Email would be sent to the lab admin and the user for whom this request has been created.'))
+ return trans.fill_template( '/requests/common/new_request.mako',
+ cntrller=cntrller,
+ select_request_type=select_request_type,
+ request_type=request_type,
+ widgets=widgets,
+ message=message,
+ status=status)
+ def __select_user(self, trans, userid):
+ user_list = trans.sa_session.query( trans.app.model.User )\
+ .order_by( trans.app.model.User.email.asc() )
+ user_ids = ['none']
+ for user in user_list:
+ if not user.deleted:
+ user_ids.append(str(user.id))
+ select_user = SelectField('select_user',
+ refresh_on_change=True,
+ refresh_on_change_values=user_ids[1:])
+ if userid == 'none':
+ select_user.add_option('Select one', 'none', selected=True)
+ else:
+ select_user.add_option('Select one', 'none')
+ for user in user_list:
+ if not user.deleted:
+ if userid == str(user.id):
+ select_user.add_option(user.email, user.id, selected=True)
+ else:
+ select_user.add_option(user.email, user.id)
+ return select_user
+ def __save_request(self, trans, request, **kwd):
+ '''
+ This method saves a new request if request_id is None.
+ '''
+ params = util.Params( kwd )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
+ if request:
+ user = request.user
+ else:
+ if cntrller == 'requests_admin' and trans.user_is_admin():
+ user = trans.sa_session.query( trans.app.model.User ).get( int( params.get( 'select_user', '' ) ) )
+ elif cntrller == 'requests':
+ user = trans.user
+ name = util.restore_text(params.get('name', ''))
+ desc = util.restore_text(params.get('desc', ''))
+ notify = CheckboxField.is_checked( params.get('email_notify', '') )
+ # fields
+ values = []
+ for index, field in enumerate(request_type.request_form.fields):
+ if field['type'] == 'AddressField':
+ value = util.restore_text(params.get('field_%i' % index, ''))
+ if value == 'new':
+ # save this new address in the list of this user's addresses
+ user_address = trans.app.model.UserAddress( user=user )
+ user_address.desc = util.restore_text(params.get('field_%i_short_desc' % index, ''))
+ user_address.name = util.restore_text(params.get('field_%i_name' % index, ''))
+ user_address.institution = util.restore_text(params.get('field_%i_institution' % index, ''))
+ user_address.address = util.restore_text(params.get('field_%i_address1' % index, ''))+' '+util.restore_text(params.get('field_%i_address2' % index, ''))
+ user_address.city = util.restore_text(params.get('field_%i_city' % index, ''))
+ user_address.state = util.restore_text(params.get('field_%i_state' % index, ''))
+ user_address.postal_code = util.restore_text(params.get('field_%i_postal_code' % index, ''))
+ user_address.country = util.restore_text(params.get('field_%i_country' % index, ''))
+ user_address.phone = util.restore_text(params.get('field_%i_phone' % index, ''))
+ trans.sa_session.add( user_address )
+ trans.sa_session.flush()
+ trans.sa_session.refresh( user )
+ values.append(int(user_address.id))
+ elif value == unicode('none'):
+ values.append('')
+ else:
+ values.append(int(value))
+ elif field['type'] == 'CheckboxField':
+ values.append(CheckboxField.is_checked( params.get('field_%i' % index, '') ))
+ else:
+ values.append(util.restore_text(params.get('field_%i' % index, '')))
+ form_values = trans.app.model.FormValues(request_type.request_form, values)
+ trans.sa_session.add( form_values )
+ trans.sa_session.flush()
+ if not request:
+ request = trans.app.model.Request(name, desc, request_type,
+ user, form_values, notify)
+ trans.sa_session.add( request )
+ trans.sa_session.flush()
+ trans.sa_session.refresh( request )
+ # create an event with state 'New' for this new request
+ if request.user.email is not trans.user:
+ comments = "Request created by admin (%s) on behalf of %s." % (trans.user.email, request.user.email)
+ else:
+ comments = "Request created."
+ event = trans.app.model.RequestEvent(request, request.states.NEW, comments)
+ trans.sa_session.add( event )
+ trans.sa_session.flush()
+ else:
+ request.name = name
+ request.desc = desc
+ request.type = request_type
+ request.user = user
+ request.notify = notify
+ request.values = form_values
+ trans.sa_session.add( request )
+ trans.sa_session.flush()
+ return request
+
+ @web.expose
+ @web.require_login( "create/submit sequencing requests" )
+ def edit(self, trans, **kwd):
+ params = util.Params( kwd )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ try:
+ request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id( params.get( 'id', None ) ) )
+ except:
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ status='error',
+ message="Invalid request ID") )
+ if params.get('show', False) == 'True':
+ return self.__edit_request(trans, **kwd)
+ elif params.get('save_changes_request_button', False) == 'Save changes' \
+ or params.get('edit_samples_button', False) == 'Edit samples':
+ request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
+ if not util.restore_text(params.get('name', '')):
+ message = 'Please enter the <b>Name</b> of the request'
+ kwd['status'] = 'error'
+ kwd['message'] = message
+ kwd['show'] = 'True'
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller=cntrller,
+ action='edit',
+ **kwd) )
+ request = self.__save_request(trans, request, **kwd)
+ message = 'The changes made to the request named %s has been saved' % request.name
+ if params.get('save_changes_request_button', False) == 'Save changes':
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ cntrller=cntrller,
+ action='list',
+ message=message ,
+ status='done') )
+ elif params.get('edit_samples_button', False) == 'Edit samples':
+ new_kwd = {}
+ new_kwd['id'] = request.id
+ new_kwd['edit_samples_button'] = 'Edit samples'
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ cntrller=cntrller,
+ action='show',
+ message=message ,
+ status='done',
+ **new_kwd) )
+ elif params.get('refresh', False) == 'true':
+ return self.__edit_request(trans, **kwd)
+
+ def __edit_request(self, trans, **kwd):
+ try:
+ request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
+ except:
+ message = "Invalid request ID"
+ log.warn( message )
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ cntrller=cntrller,
+ action='list',
+ status='error',
+ message=message) )
+ params = util.Params( kwd )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ select_request_type = self.__select_request_type(trans, request.type.id)
+ # list of widgets to be rendered on the request form
+ widgets = []
+ if util.restore_text( params.get( 'name', '' ) ):
+ name = util.restore_text( params.get( 'name', '' ) )
+ else:
+ name = request.name
+ widgets.append(dict(label='Name',
+ widget=TextField('name', 40, name),
+ helptext='(Required)'))
+ if util.restore_text( params.get( 'desc', '' ) ):
+ desc = util.restore_text( params.get( 'desc', '' ) )
+ else:
+ desc = request.desc
+ widgets.append(dict(label='Description',
+ widget=TextField('desc', 40, desc),
+ helptext='(Optional)'))
+ widgets = widgets + request.type.request_form.get_widgets( request.user, request.values.content, **kwd )
+ widgets.append(dict(label='Send email notification once the sequencing request is complete',
+ widget=CheckboxField('email_notify', request.notify),
+ helptext=''))
+ return trans.fill_template( 'requests/common/edit_request.mako',
+ cntrller=cntrller,
+ select_request_type=select_request_type,
+ request_type=request.type,
+ request=request,
+ widgets=widgets,
+ message=message,
+ status=status)
+ def __validate(self, trans, cntrller, request):
+ '''
+ Validates the request entered by the user
+ '''
+ if not request.samples:
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ operation='show',
+ message='Please add one or more samples to this request before submitting.',
+ status='error',
+ id=trans.security.encode_id(request.id)) )
+ empty_fields = []
+ # check rest of the fields of the form
+ for index, field in enumerate(request.type.request_form.fields):
+ if field['required'] == 'required' and request.values.content[index] in ['', None]:
+ empty_fields.append(field['label'])
+ if empty_fields:
+ message = 'Fill the following fields of the request <b>%s</b> before submitting<br/>' % request.name
+ for ef in empty_fields:
+ message = message + '<b>' +ef + '</b><br/>'
+ return message
+ return None
+ @web.expose
+ @web.require_login( "create/submit sequencing requests" )
+ def submit(self, trans, **kwd):
+ params = util.Params( kwd )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ try:
+ request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
+ except:
+ message = "Invalid request ID"
+ log.warn( message )
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ status='error',
+ message=message,
+ **kwd) )
+ message = self.__validate(trans, cntrller, request)
+ if message:
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ operation='edit',
+ status = 'error',
+ message=message,
+ id=trans.security.encode_id(request.id) ) )
+ # change the request state to 'Submitted'
+ if request.user.email is not trans.user:
+ comments = "Request submitted by admin (%s) on behalf of %s." % (trans.user.email, request.user.email)
+ else:
+ comments = ""
+ event = trans.app.model.RequestEvent(request, request.states.SUBMITTED, comments)
+ trans.sa_session.add( event )
+ trans.sa_session.flush()
+ # change the state of each of the samples of thus request
+ new_state = request.type.states[0]
+ for s in request.samples:
+ event = trans.app.model.SampleEvent(s, new_state, 'Samples created.')
+ trans.sa_session.add( event )
+ trans.sa_session.add( request )
+ trans.sa_session.flush()
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ id=trans.security.encode_id(request.id),
+ status='done',
+ message='The request <b>%s</b> has been submitted.' % request.name
+ ) )
+ @web.expose
+ @web.require_login( "create/submit sequencing requests" )
+ def delete(self, trans, **kwd):
+ params = util.Params( kwd )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ id_list = util.listify( kwd['id'] )
+ for id in id_list:
+ try:
+ request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(id) )
+ except:
+ message = "Invalid request ID"
+ log.warn( message )
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ status='error',
+ message=message,
+ **kwd) )
+ request.deleted = True
+ trans.sa_session.add( request )
+ # delete all the samples belonging to this request
+ for s in request.samples:
+ s.deleted = True
+ trans.sa_session.add( s )
+ trans.sa_session.flush()
+ message = '%i request(s) has been deleted.' % len(id_list)
+ status = 'done'
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ status=status,
+ message=message) )
+ @web.expose
+ @web.require_login( "create/submit sequencing requests" )
+ def undelete(self, trans, **kwd):
+ params = util.Params( kwd )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ id_list = util.listify( kwd['id'] )
+ for id in id_list:
+ try:
+ request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(id) )
+ except:
+ message = "Invalid request ID"
+ log.warn( message )
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ status='error',
+ message=message,
+ **kwd) )
+ request.deleted = False
+ trans.sa_session.add( request )
+ # undelete all the samples belonging to this request
+ for s in request.samples:
+ s.deleted = False
+ trans.sa_session.add( s )
+ trans.sa_session.flush()
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ status='done',
+ message='%i request(s) has been undeleted.' % len(id_list) ) )
+ @web.expose
+ @web.require_login( "create/submit sequencing requests" )
+ def events(self, trans, **kwd):
+ params = util.Params( kwd )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ try:
+ request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
+ except:
+ message = "Invalid request ID"
+ log.warn( message )
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ status='error',
+ message=message) )
+ events_list = []
+ all_events = request.events
+ for event in all_events:
+ events_list.append((event.state, time_ago(event.update_time), event.comment))
+ return trans.fill_template( '/requests/common/events.mako',
+ cntrller=cntrller,
+ events_list=events_list, request=request)
+ @web.expose
+ @web.require_login( "create/submit sequencing requests" )
+ def show(self, trans, **kwd):
+ params = util.Params( kwd )
+ message = util.restore_text( params.get( 'message', '' ) )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ status = params.get( 'status', 'done' )
+ add_sample = params.get('add_sample', False)
+ try:
+ request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
+ except:
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ status='error',
+ message="Invalid request ID") )
+ # get all data libraries accessible to this user
+ libraries = request.user.accessible_libraries( trans, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] )
+ current_samples = []
+ for i, s in enumerate(request.samples):
+ lib_widget, folder_widget = self.__library_widgets(trans, request.user, i, libraries, s, **kwd)
+ current_samples.append(dict(name=s.name,
+ 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))
+ if add_sample:
+ lib_widget, folder_widget = self.__library_widgets(trans, request.user,
+ len(current_samples)+1,
+ libraries, None, **kwd)
+ current_samples.append(dict(name='Sample_%i' % (len(current_samples)+1),
+ 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))
+ return trans.fill_template( '/requests/common/show_request.mako',
+ cntrller=cntrller,
+ request=request,
+ request_details=self.request_details(trans, request.id),
+ current_samples=current_samples,
+ sample_copy=self.__copy_sample(current_samples),
+ details='hide', edit_mode=util.restore_text( params.get( 'edit_mode', 'False' ) ),
+ message=message, status=status )
+
+ def __update_samples(self, trans, request, **kwd):
+ '''
+ This method retrieves all the user entered sample information and
+ returns an list of all the samples and their field values
+ '''
+ params = util.Params( kwd )
+ details = params.get( 'details', 'hide' )
+ edit_mode = params.get( 'edit_mode', 'False' )
+ # get all data libraries accessible to this user
+ libraries = request.user.accessible_libraries( trans, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] )
+
+ current_samples = []
+ for i, s in enumerate(request.samples):
+ lib_widget, folder_widget = self.__library_widgets(trans, request.user, i, libraries, s, **kwd)
+ current_samples.append(dict(name=s.name,
+ barcode=s.bar_code,
+ library=s.library,
+ folder=s.folder,
+ field_values=s.values.content,
+ lib_widget=lib_widget,
+ folder_widget=folder_widget))
+ if edit_mode == 'False':
+ sample_index = len(request.samples)
+ else:
+ sample_index = 0
+ while True:
+ lib_id = None
+ folder_id = None
+ if params.get( 'sample_%i_name' % sample_index, '' ):
+ # data library
+ try:
+ library = trans.sa_session.query( trans.app.model.Library ).get( int( params.get( 'sample_%i_library_id' % sample_index, None ) ) )
+ lib_id = library.id
+ except:
+ library = None
+ # folder
+ try:
+ folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( int( params.get( 'sample_%i_folder_id' % sample_index, None ) ) )
+ folder_id = folder.id
+ except:
+ if library:
+ folder = library.root_folder
+ else:
+ folder = None
+ sample_info = dict( name=util.restore_text( params.get( 'sample_%i_name' % sample_index, '' ) ),
+ barcode=util.restore_text( params.get( 'sample_%i_barcode' % sample_index, '' ) ),
+ library=library,
+ folder=folder)
+ sample_info['field_values'] = []
+ for field_index in range(len(request.type.sample_form.fields)):
+ sample_info['field_values'].append(util.restore_text( params.get( 'sample_%i_field_%i' % (sample_index, field_index), '' ) ))
+ if edit_mode == 'False':
+ sample_info['lib_widget'], sample_info['folder_widget'] = self.__library_widgets(trans, request.user,
+ sample_index, libraries,
+ None, lib_id, folder_id, **kwd)
+ current_samples.append(sample_info)
+ else:
+ sample_info['lib_widget'], sample_info['folder_widget'] = self.__library_widgets(trans,
+ request.user,
+ sample_index,
+ libraries,
+ request.samples[sample_index],
+ **kwd)
+ current_samples[sample_index] = sample_info
+ sample_index = sample_index + 1
+ else:
+ break
+ return current_samples, details, edit_mode, libraries
+
+ def __library_widgets(self, trans, user, sample_index, libraries, sample=None, lib_id=None, folder_id=None, **kwd):
+ '''
+ This method creates the data library & folder selectbox for creating &
+ editing samples. First we get a list of all the libraries accessible to
+ the current user and display it in a selectbox. If the user has selected an
+ existing library then display all the accessible sub folders of the selected
+ data library.
+ '''
+ params = util.Params( kwd )
+ # data library selectbox
+ if not lib_id:
+ lib_id = params.get( "sample_%i_library_id" % sample_index, 'none' )
+ selected_lib = None
+ if sample and lib_id == 'none':
+ if sample.library:
+ lib_id = str(sample.library.id)
+ selected_lib = sample.library
+ # create data library selectbox with refresh on change enabled
+ lib_id_list = ['new'] + [str(lib.id) for lib in libraries.keys()]
+ lib_widget = SelectField( "sample_%i_library_id" % sample_index,
+ refresh_on_change=True,
+ refresh_on_change_values=lib_id_list )
+ # fill up the options in the Library selectbox
+ # first option 'none' is the value for "Select one" option
+ if lib_id == 'none':
+ lib_widget.add_option('Select one', 'none', selected=True)
+ else:
+ lib_widget.add_option('Select one', 'none')
+ # all the libraries available to the selected user
+ for lib, hidden_folder_ids in libraries.items():
+ if str(lib.id) == str(lib_id):
+ lib_widget.add_option(lib.name, lib.id, selected=True)
+ selected_lib, selected_hidden_folder_ids = lib, hidden_folder_ids.split(',')
+ else:
+ lib_widget.add_option(lib.name, lib.id)
+ lib_widget.refresh_on_change_values.append(lib.id)
+ # create the folder selectbox
+ folder_widget = SelectField( "sample_%i_folder_id" % sample_index )
+ # when editing a request, either the user has already selected a subfolder or not
+ if sample:
+ if sample.folder:
+ current_fid = sample.folder.id
+ else:
+ # when a folder not yet associated with the request then the
+ # the current folder is set to the root_folder of the
+ # parent data library if present.
+ if sample.library:
+ current_fid = sample.library.root_folder.id
+ else:
+ current_fid = params.get( "sample_%i_folder_id" % sample_index, 'none' )
+ else:
+ if folder_id:
+ current_fid = folder_id
+ else:
+ current_fid = 'none'
+ # first option
+ if lib_id == 'none':
+ folder_widget.add_option('Select one', 'none', selected=True)
+ else:
+ folder_widget.add_option('Select one', 'none')
+ if selected_lib:
+ # get all show-able folders for the selected library
+ showable_folders = trans.app.security_agent.get_showable_folders( user, user.all_roles(),
+ selected_lib,
+ [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ],
+ selected_hidden_folder_ids )
+ for f in showable_folders:
+ if str(f.id) == str(current_fid):
+ folder_widget.add_option(f.name, f.id, selected=True)
+ else:
+ folder_widget.add_option(f.name, f.id)
+ return lib_widget, folder_widget
+
+ def __copy_sample(self, current_samples):
+ copy_list = SelectField('copy_sample')
+ copy_list.add_option('None', -1, selected=True)
+ for i, s in enumerate(current_samples):
+ copy_list.add_option(s['name'], i)
+ return copy_list
+
+ @web.expose
+ @web.require_login( "create/submit sequencing requests" )
+ def request_page(self, trans, **kwd):
+ params = util.Params( kwd )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ message = util.restore_text( params.get( 'message', '' ) )
+ status = params.get( 'status', 'done' )
+ try:
+ request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id( kwd['id']) )
+ except:
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ status='error',
+ message="Invalid request ID") )
+ # get the user entered sample details
+ current_samples, details, edit_mode, libraries = self.__update_samples( trans, request, **kwd )
+ if params.get('import_samples_button', False) == 'Import samples':
+ return self.__import_samples(trans, cntrller, request, current_samples, details, libraries, **kwd)
+ elif params.get('add_sample_button', False) == 'Add New':
+ # add an empty or filled sample
+ # if the user has selected a sample no. to copy then copy the contents
+ # of the src sample to the new sample else an empty sample
+ src_sample_index = int(params.get( 'copy_sample', -1 ))
+ # get the number of new copies of the src sample
+ num_sample_to_copy = int(params.get( 'num_sample_to_copy', 1 ))
+ if src_sample_index == -1:
+ for ns in range(num_sample_to_copy):
+ # empty sample
+ lib_widget, folder_widget = self.__library_widgets(trans, request.user,
+ len(current_samples),
+ libraries, None, **kwd)
+ current_samples.append(dict(name='Sample_%i' % (len(current_samples)+1),
+ barcode='',
+ library=None,
+ folder=None,
+ field_values=['' for field in request.type.sample_form.fields],
+ lib_widget=lib_widget,
+ folder_widget=folder_widget))
+ else:
+ src_library_id = current_samples[src_sample_index]['lib_widget'].get_selected()[1]
+ src_folder_id = current_samples[src_sample_index]['folder_widget'].get_selected()[1]
+ for ns in range(num_sample_to_copy):
+ lib_widget, folder_widget = self.__library_widgets(trans, request.user,
+ len(current_samples),
+ libraries, sample=None,
+ lib_id=src_library_id,
+ folder_id=src_folder_id,
+ **kwd)
+ current_samples.append(dict(name=current_samples[src_sample_index]['name']+'_%i' % (len(current_samples)+1),
+ barcode='',
+ library_id='none',
+ folder_id='none',
+ field_values=[val for val in current_samples[src_sample_index]['field_values']],
+ lib_widget=lib_widget,
+ folder_widget=folder_widget))
+ return trans.fill_template( '/requests/common/show_request.mako',
+ cntrller=cntrller,
+ request=request,
+ request_details=self.request_details(trans, request.id),
+ current_samples=current_samples,
+ sample_copy=self.__copy_sample(current_samples),
+ details=details,
+ edit_mode=edit_mode)
+ elif params.get('save_samples_button', False) == 'Save':
+ # check for duplicate sample names
+ message = ''
+ for index in range(len(current_samples)-len(request.samples)):
+ sample_index = index + len(request.samples)
+ sample_name = current_samples[sample_index]['name']
+ if not sample_name.strip():
+ message = 'Please enter the name of sample number %i' % sample_index
+ break
+ count = 0
+ for i in range(len(current_samples)):
+ if sample_name == current_samples[i]['name']:
+ count = count + 1
+ if count > 1:
+ message = "This request has <b>%i</b> samples with the name <b>%s</b>.\nSamples belonging to a request must have unique names." % (count, sample_name)
+ break
+ if message:
+ return trans.fill_template( '/requests/common/show_request.mako',
+ cntrller=cntrller,
+ request=request,
+ request_details=self.request_details(trans, request.id),
+ current_samples = current_samples,
+ sample_copy=self.__copy_sample(current_samples),
+ details=details, edit_mode=edit_mode,
+ status='error', message=message)
+ # save all the new/unsaved samples entered by the user
+ if edit_mode == 'False':
+ for index in range(len(current_samples)-len(request.samples)):
+ sample_index = len(request.samples)
+ form_values = trans.app.model.FormValues(request.type.sample_form,
+ current_samples[sample_index]['field_values'])
+ trans.sa_session.add( form_values )
+ trans.sa_session.flush()
+ s = trans.app.model.Sample(current_samples[sample_index]['name'], '',
+ request, form_values,
+ current_samples[sample_index]['barcode'],
+ current_samples[sample_index]['library'],
+ current_samples[sample_index]['folder'],
+ dataset_files=[])
+ trans.sa_session.add( s )
+ trans.sa_session.flush()
+
+ else:
+ status = 'done'
+ message = 'Changes made to the sample(s) are saved. '
+ for sample_index in range(len(current_samples)):
+ sample = request.samples[sample_index]
+ sample.name = current_samples[sample_index]['name']
+ sample.library = current_samples[sample_index]['library']
+ sample.folder = current_samples[sample_index]['folder']
+ if request.submitted():
+ bc_message = self.__validate_barcode(trans, sample, current_samples[sample_index]['barcode'])
+ if bc_message:
+ status = 'error'
+ message += bc_message
+ else:
+ if not sample.bar_code:
+ # if this is a 'new' (still in its first state) sample
+ # change the state to the next
+ if sample.current_state().id == request.type.states[0].id:
+ event = trans.app.model.SampleEvent(sample,
+ request.type.states[1],
+ 'Sample added to the system')
+ trans.sa_session.add( event )
+ trans.sa_session.flush()
+ sample.bar_code = current_samples[sample_index]['barcode']
+ trans.sa_session.add( sample )
+ trans.sa_session.flush()
+ form_values = trans.sa_session.query( trans.app.model.FormValues ).get( sample.values.id )
+ form_values.content = current_samples[sample_index]['field_values']
+ trans.sa_session.add( form_values )
+ trans.sa_session.flush()
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ operation='show',
+ id=trans.security.encode_id(request.id),
+ status=status,
+ message=message ))
+ elif params.get('edit_samples_button', False) == 'Edit samples':
+ edit_mode = 'True'
+ return trans.fill_template( '/requests/common/show_request.mako',
+ cntrller=cntrller,
+ request=request,
+ request_details=self.request_details(trans, request.id),
+ current_samples=current_samples,
+ sample_copy=self.__copy_sample(current_samples),
+ details=details, libraries=libraries,
+ edit_mode=edit_mode)
+ elif params.get('cancel_changes_button', False) == 'Cancel':
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ operation='show',
+ id=trans.security.encode_id(request.id)) )
+ else:
+ return trans.fill_template( '/requests/common/show_request.mako',
+ cntrller=cntrller,
+ request=request,
+ request_details=self.request_details(trans, request.id),
+ current_samples=current_samples,
+ sample_copy=self.__copy_sample(current_samples),
+ details=details, libraries=libraries,
+ edit_mode=edit_mode, status=status, message=message)
+
+ def __import_samples(self, trans, cntrller, request, current_samples, details, libraries, **kwd):
+ '''
+ This method reads the samples csv file and imports all the samples
+ The format of the csv file is:
+ SampleName,DataLibrary,DataLibraryFolder,Field1,Field2....
+ '''
+ try:
+ params = util.Params( kwd )
+ edit_mode = params.get( 'edit_mode', 'False' )
+ file_obj = params.get('file_data', '')
+ reader = csv.reader(file_obj.file)
+ for row in reader:
+ lib_id = None
+ folder_id = None
+ lib = trans.sa_session.query( trans.app.model.Library ) \
+ .filter( and_( trans.app.model.Library.table.c.name==row[1], \
+ trans.app.model.Library.table.c.deleted==False ) )\
+ .first()
+ if lib:
+ folder = trans.sa_session.query( trans.app.model.LibraryFolder ) \
+ .filter( and_( trans.app.model.LibraryFolder.table.c.name==row[2], \
+ trans.app.model.LibraryFolder.table.c.deleted==False ) )\
+ .first()
+ if folder:
+ lib_id = lib.id
+ folder_id = folder.id
+ lib_widget, folder_widget = self.__library_widgets(trans, request.user, len(current_samples),
+ libraries, None, lib_id, folder_id, **kwd)
+ current_samples.append(dict(name=row[0],
+ barcode='',
+ library=None,
+ folder=None,
+ lib_widget=lib_widget,
+ folder_widget=folder_widget,
+ field_values=row[3:]))
+ return trans.fill_template( '/requests/common/show_request.mako',
+ cntrller=cntrller,
+ request=request,
+ request_details=self.request_details(trans, request.id),
+ current_samples=current_samples,
+ sample_copy=self.__copy_sample(current_samples),
+ details=details,
+ edit_mode=edit_mode)
+ except:
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ operation='show',
+ id=trans.security.encode_id(request.id),
+ status='error',
+ message='Error in importing samples file' ))
+
+ def __validate_barcode(self, trans, sample, barcode):
+ '''
+ This method makes sure that the given barcode about to be assigned to
+ the given sample is gobally unique. That is, barcodes must be unique
+ across requests in Galaxy LIMS
+ '''
+ message = ''
+ for index in range(len(sample.request.samples)):
+ # check for empty bar code
+ if not barcode.strip():
+ message = 'Please fill the barcode for sample <b>%s</b>.' % sample.name
+ break
+ # check all the saved bar codes
+ all_samples = trans.sa_session.query( trans.app.model.Sample )
+ for s in all_samples:
+ if barcode == s.bar_code:
+ if sample.id == s.id:
+ continue
+ else:
+ message = '''The bar code <b>%s</b> of sample <b>%s</b>
+ belongs another sample. The sample bar codes must be
+ unique throughout the system''' % \
+ (barcode, sample.name)
+ break
+ if message:
+ break
+ return message
+
+
+ @web.expose
+ @web.require_login( "create/submit sequencing requests" )
+ def delete_sample(self, trans, **kwd):
+ params = util.Params( kwd )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', 0 ) ) )
+ current_samples, details, edit_mode, libraries = self.__update_samples( trans, request, **kwd )
+ sample_index = int(params.get('sample_id', 0))
+ sample_name = current_samples[sample_index]['name']
+ s = request.has_sample(sample_name)
+ if s:
+ trans.sa_session.delete( s.values )
+ trans.sa_session.delete( s )
+ trans.sa_session.flush()
+ del current_samples[sample_index]
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ operation='show',
+ id=trans.security.encode_id(request.id),
+ status='done',
+ message='Sample <b>%s</b> has been deleted.' % sample_name ))
+ return trans.fill_template( '/requests/common/show_request.mako',
+ controller=cntrller,
+ request=request,
+ request_details=self.request_details(trans, request.id),
+ current_samples = current_samples,
+ sample_copy=self.__copy_sample(current_samples),
+ details=details,
+ edit_mode=edit_mode)
+ def request_details(self, trans, id):
+ '''
+ Shows the request details
+ '''
+ request = trans.sa_session.query( trans.app.model.Request ).get( id )
+ # list of widgets to be rendered on the request form
+ request_details = []
+ # main details
+ request_details.append(dict(label='User',
+ value=str(request.user.email),
+ helptext=''))
+ request_details.append(dict(label='Description',
+ value=request.desc,
+ helptext=''))
+ request_details.append(dict(label='Type',
+ value=request.type.name,
+ helptext=''))
+ request_details.append(dict(label='State',
+ value=request.state(),
+ helptext=''))
+ request_details.append(dict(label='Date created',
+ value=request.create_time,
+ helptext=''))
+ # form fields
+ for index, field in enumerate(request.type.request_form.fields):
+ if field['required']:
+ req = 'Required'
+ else:
+ req = 'Optional'
+ if field['type'] == 'AddressField':
+ if request.values.content[index]:
+ request_details.append(dict(label=field['label'],
+ value=trans.sa_session.query( trans.app.model.UserAddress ).get( int( request.values.content[index] ) ).get_html(),
+ helptext=field['helptext']+' ('+req+')'))
+ else:
+ request_details.append(dict(label=field['label'],
+ value=None,
+ helptext=field['helptext']+' ('+req+')'))
+ else:
+ request_details.append(dict(label=field['label'],
+ value=request.values.content[index],
+ helptext=field['helptext']+' ('+req+')'))
+ if request.notify:
+ notify = 'Yes'
+ else:
+ notify = 'No'
+ request_details.append(dict(label='Send email notification once the sequencing request is complete',
+ value=notify,
+ helptext=''))
+ return request_details
+ @web.expose
+ @web.require_login( "create/submit sequencing requests" )
+ def sample_events(self, trans, **kwd):
+ params = util.Params( kwd )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ try:
+ sample_id = int(params.get('sample_id', False))
+ sample = trans.sa_session.query( trans.app.model.Sample ).get( sample_id )
+ except:
+ message = "Invalid sample ID"
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ status='error',
+ message=message) )
+ events_list = []
+ all_events = sample.events
+ for event in all_events:
+ events_list.append((event.state.name, event.state.desc,
+ time_ago(event.update_time),
+ event.comment))
+ widgets, title = self.__change_state_widgets(trans, sample)
+ return trans.fill_template( '/requests/common/sample_events.mako',
+ cntrller=cntrller,
+ events_list=events_list,
+ sample=sample, widgets=widgets, title=title)
+ def __change_state_widgets(self, trans, sample):
+ possible_states = sample.request.type.states
+ curr_state = sample.current_state()
+ states_input = SelectField('select_state')
+ for state in possible_states:
+ if curr_state.name == state.name:
+ states_input.add_option(state.name+' (Current)', state.id, selected=True)
+ else:
+ states_input.add_option(state.name, state.id)
+ widgets = []
+ widgets.append(('Select the new state of the sample from the list of possible state(s)',
+ states_input))
+ widgets.append(('Comments', TextArea('comment')))
+ title = 'Change current state'
+ return widgets, title
+ @web.expose
+ @web.require_admin
+ def show_datatx_page( self, trans, **kwd ):
+ params = util.Params( kwd )
+ cntrller = util.restore_text( params.get( 'cntrller', 'requests' ) )
+ 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=cntrller,
+ action='list',
+ status='error',
+ message="Invalid sample ID") )
+ # check if a library and folder has been set for this sample yet.
+ if not sample.library or not sample.folder:
+ return trans.response.send_redirect( web.url_for( controller=cntrller,
+ action='list',
+ 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) ) )
+ 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])
+ else:
+ folder_path = util.restore_text( sample.request.type.datatx_info.get('data_dir', '') )
+ if folder_path and folder_path[-1] != os.sep:
+ folder_path += os.sep
+ if not sample.request.type.datatx_info['host'] or not sample.request.type.datatx_info['username'] \
+ or not sample.request.type.datatx_info['password']:
+ status = 'error'
+ 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,
+ message=message, status=status, files=[],
+ folder_path=folder_path )
--- a/templates/requests/events.mako
+++ /dev/null
@@ -1,39 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-
-<h2>History of Sequencing Request "${request.name}"</h2>
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( controller='requests', action='list', operation='show_request', id=trans.security.encode_id(request.id) )}">
- <span>Browse this request</span></a>
- </li>
- <li>
- <a class="action-button" href="${h.url_for( controller='requests', action='list')}">
- <span>Browse all requests</span></a>
- </li>
-</ul>
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-<div class="toolForm">
- <table class="grid">
- <thead>
- <tr>
- <th>State</th>
- <th>Last Update</th>
- <th>Comments</th>
- </tr>
- </thead>
- <tbody>
- %for state, updated, comments in events_list:
- <tr class="libraryRow libraryOrFolderRow" id="libraryRow">
- <td><b><a>${state}</a></b></td>
- <td><a>${updated}</a></td>
- <td><a>${comments}</a></td>
- </tr>
- %endfor
- </tbody>
- </table>
-</div>
--- /dev/null
+++ b/templates/requests/common/sample_state.mako
@@ -0,0 +1,5 @@
+<%def name="render_sample_state( cntrller, sample )">
+ <a href="${h.url_for( controller='requests_common', cntrller=cntrller, action='sample_events', sample_id=sample.id)}">${sample.current_state().name}</a>
+</%def>
+
+${render_sample_state( cntrller, sample )}
--- /dev/null
+++ b/templates/requests/common/get_data.mako
@@ -0,0 +1,239 @@
+<%inherit file="/base.mako"/>
+<%namespace file="/message.mako" import="render_msg" />
+
+
+%if message:
+ ${render_msg( message, status )}
+%endif
+
+<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(sample_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: sample_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(sample_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: sample_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>
+
+
+<h2>Data transfer from Sequencer</h2>
+<h3>Sample "${sample.name}" of Request "${sample.request.name}"</h3>
+<br/>
+<br/>
+
+<ul class="manage-table-actions">
+ %if sample.request.submitted() and sample.inprogress_dataset_files():
+ <li>
+ <a class="action-button" href="${h.url_for( controller='requests_common', cntrller=cntrller, action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">
+ <span>Refresh this page</span></a>
+ </li>
+ %endif
+ %if cntrller == 'requests_admin' and trans.user_is_admin():
+ <li>
+ <a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_request_types', operation='view', id=trans.security.encode_id(sample.request.type.id) )}">
+ <span>Sequencer information</span></a>
+ </li>
+
+ <li>
+ <a class="action-button" href="${h.url_for( controller='library_common', action='browse_library', cntrller='library_admin', id=trans.security.encode_id( sample.library.id ) )}">
+ <span>${sample.library.name} Data Library</span></a>
+ </li>
+ %else:
+ <li>
+ <a class="action-button" href="${h.url_for( controller='library_common', action='browse_library', cntrller='library', id=trans.security.encode_id( sample.library.id ) )}">
+ <span>${sample.library.name} Data Library</span></a>
+ </li>
+ %endif
+ <li>
+ <a class="action-button" href="${h.url_for( controller=cntrller, action='list', operation='show', id=trans.security.encode_id(sample.request.id) )}">
+ <span>Browse this request</span></a>
+ </li>
+</ul>
+
+<div class="toolForm">
+ %if len(dataset_files):
+ <div class="form-row">
+ <h4>Sample Dataset(s)</h4>
+ %if sample.untransferred_dataset_files() and cntrller == 'requests_admin':
+ <div class="form-row">
+ <ul class="manage-table-actions">
+ <li>
+ <a class="action-button" href="${h.url_for( controller='requests_admin', action='get_data', start_transfer_button=True, sample_id=sample.id )}">
+ <span>Start transfer</span></a>
+ </li>
+ </ul>
+ </div>
+ %endif
+ <div class="form-row">
+ <table class="grid">
+ <thead>
+ <tr>
+ <th>Dataset File</th>
+ <th>Transfer Status</th>
+ <th></th>
+ </tr>
+ <thead>
+ <tbody>
+ %for dataset_index, dataset_file in enumerate(dataset_files):
+ ${sample_dataset_files( dataset_index, dataset_file['name'], dataset_file['status'] )}
+ %endfor
+ </tbody>
+ </table>
+ </div>
+ </div>
+ %else:
+ <div class="form-row">
+ There are no dataset files associated with this sample.
+ </div>
+ %endif
+
+ <br/>
+ <br/>
+
+ %if cntrller == 'requests_admin' and trans.user_is_admin():
+ <form name="get_data" id="get_data" action="${h.url_for( controller='requests_admin', cntrller=cntrller, action='get_data', sample_id=sample.id)}" method="post" >
+ <div class="form-row">
+ ##<div class="toolFormTitle">Select files for transfer</div>
+ <h4>Select files for transfer</h4>
+ <div style="width: 60%;">
+ <div class="form-row">
+ <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: 98%; width: 98%; height: 150px; font-size: 100%;" ondblclick="open_folder1(${sample.id}, '${folder_path}')" onChange="display_file_details(${sample.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_files_button" value="Select"/>
+ </div>
+ </div>
+ </div>
+ </form>
+ %endif
+</div>
+
+
+
+
+<%def name="sample_dataset_files( dataset_index, dataset_name, status )">
+ <tr>
+
+ <td>
+ %if cntrller == 'requests_admin' and trans.user_is_admin():
+ <label class="msg_head"><a href="${h.url_for( controller='requests_admin', action='dataset_details', sample_id=trans.security.encode_id(sample.id), dataset_index=dataset_index )}">${dataset_name}</a></label>
+ %else:
+ ${dataset_name}
+ %endif
+ </td>
+ <td>
+ %if status not in [sample.transfer_status.NOT_STARTED, sample.transfer_status.COMPLETE]:
+ <i>${status}</i>
+ %else:
+ ${status}
+ %endif
+ </td>
+ ##<td></td>
+ %if status == sample.transfer_status.NOT_STARTED and cntrller == 'requests_admin' and trans.user_is_admin():
+ <td>
+ <a class="action-button" href="${h.url_for( controller='requests_admin', action='get_data', sample_id=sample.id, remove_dataset_button=True, dataset_index=dataset_index )}">
+ <img src="${h.url_for('/static/images/delete_icon.png')}" />
+ <span></span></a>
+ </td>
+ %else:
+ <td></td>
+ %endif
+ </tr>
+</%def>
--- a/templates/admin/requests/events.mako
+++ /dev/null
@@ -1,36 +0,0 @@
-<%inherit file="/base.mako"/>
-<%namespace file="/message.mako" import="render_msg" />
-
-<h2>History of Sequencing Request "${request.name}"</h2>
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='show_request', id=trans.security.encode_id(request.id) )}">
- <span>Browse this request</span></a>
- </li>
-</ul>
-<h3>User: ${request.user.email}</h3>
-
-%if message:
- ${render_msg( message, status )}
-%endif
-
-<div class="toolForm">
- <table class="grid">
- <thead>
- <tr>
- <th>State</th>
- <th>Last Update</th>
- <th>Comments</th>
- </tr>
- </thead>
- <tbody>
- %for state, updated, comments in events_list:
- <tr class="libraryRow libraryOrFolderRow" id="libraryRow">
- <td><b><a>${state}</a></b></td>
- <td><a>${updated}</a></td>
- <td><a>${comments}</a></td>
- </tr>
- %endfor
- </tbody>
- </table>
-</div>
--- a/lib/galaxy/web/controllers/requests_admin.py
+++ b/lib/galaxy/web/controllers/requests_admin.py
@@ -95,7 +95,7 @@ class RequestsGrid( grids.Grid ):
NameColumn( "Name",
key="name",
model_class=model.Request,
- link=( lambda item: iff( item.deleted, None, dict( operation="show_request", id=item.id ) ) ),
+ link=( lambda item: iff( item.deleted, None, dict( operation="show", id=item.id ) ) ),
attach_popup=True,
filterable="advanced" ),
DescriptionColumn( "Description",
@@ -103,7 +103,7 @@ class RequestsGrid( grids.Grid ):
model_class=model.Request,
filterable="advanced" ),
SamplesColumn( "Sample(s)",
- link=( lambda item: iff( item.deleted, None, dict( operation="show_request", id=item.id ) ) ), ),
+ link=( lambda item: iff( item.deleted, None, dict( operation="show", id=item.id ) ) ), ),
TypeColumn( "Type",
link=( lambda item: iff( item.deleted, None, dict( operation="view_type", id=item.type.id ) ) ), ),
grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
@@ -137,7 +137,8 @@ class RequestsGrid( grids.Grid ):
grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ),
]
global_actions = [
- grids.GridAction( "Create new request", dict( controller='requests_admin',
+ grids.GridAction( "Create new request", dict( controller='requests_common',
+ cntrller='requests_admin',
action='new',
select_request_type='True' ) )
]
@@ -218,6 +219,58 @@ class RequestsAdmin( BaseController ):
@web.require_admin
def index( self, trans ):
return trans.fill_template( "/admin/requests/index.mako" )
+
+ @web.expose
+ @web.require_admin
+ def list( self, trans, **kwd ):
+ '''
+ List all request made by the current user
+ '''
+ 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 request ID") )
+ if operation == "show":
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller='requests_admin',
+ action='show',
+ **kwd ) )
+ elif operation == "submit":
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller='requests_admin',
+ action='submit',
+ **kwd ) )
+ elif operation == "delete":
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller='requests_admin',
+ action='delete',
+ **kwd ) )
+ elif operation == "undelete":
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller='requests_admin',
+ action='undelete',
+ **kwd ) )
+ elif operation == "edit":
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller='requests_admin',
+ action='edit',
+ show=True, **kwd ) )
+ elif operation == "events":
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller='requests_admin',
+ action='events',
+ **kwd ) )
+ elif operation == "reject":
+ return self.__reject_request( trans, **kwd )
+ elif operation == "view_type":
+ return self.__view_request_type( trans, **kwd )
+ elif operation == "upload_datasets":
+ return self.__upload_datasets( trans, **kwd )
+ # Render the grid view
+ return self.request_grid( trans, **kwd )
@web.json
def get_file_details( self, trans, id=None, folder_path=None ):
@@ -246,239 +299,6 @@ class RequestsAdmin( BaseController ):
sample = trans.sa_session.query( self.app.model.Sample ).get( int(id) )
return self.__get_files(trans, sample, folder_path)
-
- @web.json
- def sample_state_updates( self, trans, ids=None, states=None ):
- # Avoid caching
- trans.response.headers['Pragma'] = 'no-cache'
- trans.response.headers['Expires'] = '0'
- # Create new HTML for any that have changed
- rval = {}
- if ids is not None and states is not None:
- ids = map( int, ids.split( "," ) )
- states = states.split( "," )
- for id, state in zip( ids, states ):
- sample = trans.sa_session.query( self.app.model.Sample ).get( id )
- if sample.current_state().name != state:
- rval[id] = {
- "state": sample.current_state().name,
- "datasets": len(sample.dataset_files),
- "html_state": unicode( trans.fill_template( "requests/sample_state.mako", sample=sample ), 'utf-8' ),
- "html_datasets": unicode( trans.fill_template( "requests/sample_datasets.mako", trans=trans, sample=sample ), 'utf-8' )
- }
- return rval
-
- @web.expose
- @web.require_admin
- def list( self, trans, **kwd ):
- '''
- List all request made by the current user
- '''
- 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 request ID") )
- if operation == "show_request":
- return self.__show_request( trans, **kwd )
- elif operation == "submit":
- return self.__submit_request( trans, **kwd )
- elif operation == "delete":
- return self.__delete_request( trans, **kwd )
- elif operation == "undelete":
- return self.__undelete_request( trans, **kwd )
- elif operation == "edit":
- return self.__edit_request( trans, **kwd )
- elif operation == "reject":
- return self.__reject_request( trans, **kwd )
- elif operation == "events":
- return self.__request_events( trans, **kwd )
- elif operation == "view_type":
- return self.__view_request_type( trans, **kwd )
- elif operation == "upload_datasets":
- return self.__upload_datasets( trans, **kwd )
- # Render the grid view
- return self.request_grid( trans, **kwd )
- @web.expose
- @web.require_admin
- def edit(self, trans, **kwd):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', None ) ) )
- except:
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- status='error',
- message="Invalid request ID",
- **kwd) )
- if params.get('show', False) == 'True':
- return self.__edit_request(trans, id=trans.security.encode_id(request.id), **kwd)
- elif params.get('save_changes_request_button', False) == 'Save changes' \
- or params.get('edit_samples_button', False) == 'Edit samples':
- request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
- if not util.restore_text(params.get('name', '')):
- message = 'Please enter the <b>Name</b> of the request'
- kwd['status'] = 'error'
- kwd['message'] = message
- kwd['show'] = 'True'
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='edit',
- **kwd) )
- request = self.__save_request(trans, request, **kwd)
- message = 'The changes made to the request named %s has been saved' % request.name
- if params.get('save_changes_request_button', False) == 'Save changes':
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- message=message ,
- status='done') )
- elif params.get('edit_samples_button', False) == 'Edit samples':
- new_kwd = {}
- new_kwd['request_id'] = request.id
- new_kwd['edit_samples_button'] = 'Edit samples'
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='show_request',
- message=message ,
- status='done',
- **new_kwd) )
- elif params.get('refresh', False) == 'true':
- return self.__edit_request(trans, id=trans.security.encode_id(request.id), **kwd)
-
- def __edit_request(self, trans, **kwd):
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
- except:
- message = "Invalid request ID"
- log.warn( message )
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- status='error',
- message=message) )
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- select_request_type = self.__select_request_type(trans, request.type.id)
- # list of widgets to be rendered on the request form
- widgets = []
- if util.restore_text( params.get( 'name', '' ) ):
- name = util.restore_text( params.get( 'name', '' ) )
- else:
- name = request.name
- widgets.append(dict(label='Name',
- widget=TextField('name', 40, name),
- helptext='(Required)'))
- if util.restore_text( params.get( 'desc', '' ) ):
- desc = util.restore_text( params.get( 'desc', '' ) )
- else:
- desc = request.desc
- widgets.append(dict(label='Description',
- widget=TextField('desc', 40, desc),
- helptext='(Optional)'))
- widgets = widgets + request.type.request_form.get_widgets( request.user, request.values.content, **kwd )
- widgets.append(dict(label='Send email notification once the sequencing request is complete',
- widget=CheckboxField('email_notify', request.notify),
- helptext=''))
- return trans.fill_template( '/admin/requests/edit_request.mako',
- select_request_type=select_request_type,
- request_type=request.type,
- request=request,
- widgets=widgets,
- message=message,
- status=status)
- return self.__show_request_form(trans)
- def __delete_request(self, trans, **kwd):
- id_list = util.listify( kwd['id'] )
- for id in id_list:
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(id) )
- except:
- message = "Invalid request ID"
- log.warn( message )
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- status='error',
- message=message,
- **kwd) )
- request.deleted = True
- trans.sa_session.add( request )
- # delete all the samples belonging to this request
- for s in request.samples:
- s.deleted = True
- trans.sa_session.add( s )
- trans.sa_session.flush()
- message = '%i request(s) has been deleted.' % len(id_list)
- status = 'done'
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- status=status,
- message=message) )
- def __undelete_request(self, trans, **kwd):
- id_list = util.listify( kwd['id'] )
- for id in id_list:
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(id) )
- except:
- message = "Invalid request ID"
- log.warn( message )
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- status='error',
- message=message,
- **kwd) )
- request.deleted = False
- trans.sa_session.add( request )
- # undelete all the samples belonging to this request
- for s in request.samples:
- s.deleted = False
- trans.sa_session.add( s )
- trans.sa_session.flush()
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- status='done',
- message='%i request(s) has been undeleted.' % len(id_list) ) )
- def __submit_request(self, trans, **kwd):
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
- except:
- message = "Invalid request ID"
- log.warn( message )
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- status='error',
- message=message,
- **kwd) )
- message = self.__validate(trans, request)
- if message:
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- operation='edit',
- status = 'error',
- message=message,
- id=trans.security.encode_id(request.id) ) )
- # change the request state to 'Submitted'
- if request.user.email is not trans.user:
- comments = "Request submitted by admin (%s) on behalf of %s." % (trans.user.email, request.user.email)
- else:
- comments = ""
- event = trans.app.model.RequestEvent(request, request.states.SUBMITTED, comments)
- trans.sa_session.add( event )
- trans.sa_session.flush()
- # change the state of each of the samples of thus request
- new_state = request.type.states[0]
- for s in request.samples:
- event = trans.app.model.SampleEvent(s, new_state, 'Samples created.')
- trans.sa_session.add( event )
- trans.sa_session.add( request )
- trans.sa_session.flush()
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- id=trans.security.encode_id(request.id),
- status='done',
- message='The request <b>%s</b> has been submitted.' % request.name
- ) )
def __reject_request(self, trans, **kwd):
try:
request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
@@ -526,759 +346,10 @@ class RequestsAdmin( BaseController ):
status='done',
message='Request <b>%s</b> has been rejected.' % request.name) )
- def __request_events(self, trans, **kwd):
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
- except:
- message = "Invalid request ID"
- log.warn( message )
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- status='error',
- message=message,
- **kwd) )
- events_list = []
- all_events = request.events
- for event in all_events:
- events_list.append((event.state, time_ago(event.update_time), event.comment))
- return trans.fill_template( '/admin/requests/events.mako',
- events_list=events_list, request=request)
-
def __upload_datasets(self, trans, **kwd):
return trans.fill_template( '/admin/requests/upload_datasets.mako' )
-#
-#---- Request Creation ----------------------------------------------------------
-#
- def __select_request_type(self, trans, rtid):
- requesttype_list = trans.user.accessible_request_types(trans)
- rt_ids = ['none']
- for rt in requesttype_list:
- if not rt.deleted:
- rt_ids.append(str(rt.id))
- select_reqtype = SelectField('select_request_type',
- refresh_on_change=True,
- refresh_on_change_values=rt_ids[1:])
- if rtid == 'none':
- select_reqtype.add_option('Select one', 'none', selected=True)
- else:
- select_reqtype.add_option('Select one', 'none')
- for rt in requesttype_list:
- if not rt.deleted:
- if rtid == rt.id:
- select_reqtype.add_option(rt.name, rt.id, selected=True)
- else:
- select_reqtype.add_option(rt.name, rt.id)
- return select_reqtype
- @web.expose
- @web.require_admin
- def new(self, trans, **kwd):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- if params.get('select_request_type', False) == 'True':
- return trans.fill_template( '/admin/requests/new_request.mako',
- select_request_type=self.__select_request_type(trans, 'none'),
- widgets=[],
- message=message,
- status=status)
- elif params.get('create', False) == 'True':
- if params.get('create_request_button', False) == 'Save' \
- or params.get('create_request_samples_button', False) == 'Add samples':
- request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
- if not util.restore_text(params.get('name', '')) \
- or util.restore_text(params.get('select_user', '')) == unicode('none'):
- message = 'Please enter the <b>Name</b> of the request and the <b>user</b> on behalf of whom this request will be submitted before saving this request'
- kwd['create'] = 'True'
- kwd['status'] = 'error'
- kwd['message'] = message
- kwd['create_request_button'] = None
- kwd['create_request_samples_button'] = None
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='new',
- **kwd) )
- request = self.__save_request(trans, None, **kwd)
- message = 'The new request named %s has been created' % request.name
- if params.get('create_request_button', False) == 'Save':
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- message=message ,
- status='done') )
- elif params.get('create_request_samples_button', False) == 'Add samples':
- new_kwd = {}
- new_kwd['id'] = trans.security.encode_id(request.id)
- new_kwd['operation'] = 'show_request'
- new_kwd['add_sample'] = True
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- message=message ,
- status='done',
- **new_kwd) )
- else:
- return self.__show_request_form(trans, **kwd)
- elif params.get('refresh', False) == 'true':
- return self.__show_request_form(trans, **kwd)
- def __show_request_form(self, trans, **kwd):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- try:
- request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
- except:
- return trans.fill_template( '/admin/requests/new_request.mako',
- select_request_type=self.__select_request_type(trans, 'none'),
- widgets=[],
- message=message,
- status=status)
- form_values = None
- select_request_type = self.__select_request_type(trans, request_type.id)
- # user
- user_id = params.get( 'select_user', 'none' )
- try:
- user = trans.sa_session.query( trans.app.model.User ).get( int( user_id ) )
- except:
- user = None
- # list of widgets to be rendered on the request form
- widgets = []
- widgets.append(dict(label='Select user',
- widget=self.__select_user(trans, user_id),
- helptext='The request would be submitted on behalf of this user (Required)'))
- widgets.append(dict(label='Name of the Experiment',
- widget=TextField('name', 40,
- util.restore_text( params.get( 'name', '' ) )),
- helptext='(Required)'))
- widgets.append(dict(label='Description',
- widget=TextField('desc', 40,
- util.restore_text( params.get( 'desc', '' ) )),
- helptext='(Optional)'))
- widgets = widgets + request_type.request_form.get_widgets( user, **kwd )
- widgets.append(dict(label='Send email notification once the sequencing request is complete',
- widget=CheckboxField('email_notify', False),
- helptext='Email would be sent to the lab admin and the user for whom this request has been created.'))
- return trans.fill_template( '/admin/requests/new_request.mako',
- select_request_type=select_request_type,
- request_type=request_type,
- widgets=widgets,
- message=message,
- status=status)
- def __select_user(self, trans, userid):
- user_list = trans.sa_session.query( trans.app.model.User )\
- .order_by( trans.app.model.User.email.asc() )
- user_ids = ['none']
- for user in user_list:
- if not user.deleted:
- user_ids.append(str(user.id))
- select_user = SelectField('select_user',
- refresh_on_change=True,
- refresh_on_change_values=user_ids[1:])
- if userid == 'none':
- select_user.add_option('Select one', 'none', selected=True)
- else:
- select_user.add_option('Select one', 'none')
- for user in user_list:
- if not user.deleted:
- if userid == str(user.id):
- select_user.add_option(user.email, user.id, selected=True)
- else:
- select_user.add_option(user.email, user.id)
- return select_user
- def __validate(self, trans, request):
- '''
- Validates the request entered by the user
- '''
- if not request.samples:
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- operation='show_request',
- message='Please add one or more samples to this request before submitting.',
- status='error',
- id=trans.security.encode_id(request.id)) )
- empty_fields = []
- # check rest of the fields of the form
- for index, field in enumerate(request.type.request_form.fields):
- if field['required'] == 'required' and request.values.content[index] in ['', None]:
- empty_fields.append(field['label'])
- if empty_fields:
- message = 'Fill the following fields of the request <b>%s</b> before submitting<br/>' % request.name
- for ef in empty_fields:
- message = message + '<b>' +ef + '</b><br/>'
- return message
- return None
- def __save_request(self, trans, request=None, **kwd):
- '''
- This method saves a new request if request_id is None.
- '''
- params = util.Params( kwd )
- request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) )
- if request:
- user = request.user
- else:
- user = trans.sa_session.query( trans.app.model.User ).get( int( params.get( 'select_user', '' ) ) )
- name = util.restore_text(params.get('name', ''))
- desc = util.restore_text(params.get('desc', ''))
- notify = CheckboxField.is_checked( params.get('email_notify', '') )
- # fields
- values = []
- for index, field in enumerate(request_type.request_form.fields):
- if field['type'] == 'AddressField':
- value = util.restore_text(params.get('field_%i' % index, ''))
- if value == 'new':
- # save this new address in the list of this user's addresses
- user_address = trans.app.model.UserAddress( user=user )
- user_address.desc = util.restore_text(params.get('field_%i_short_desc' % index, ''))
- user_address.name = util.restore_text(params.get('field_%i_name' % index, ''))
- user_address.institution = util.restore_text(params.get('field_%i_institution' % index, ''))
- user_address.address = util.restore_text(params.get('field_%i_address1' % index, ''))+' '+util.restore_text(params.get('field_%i_address2' % index, ''))
- user_address.city = util.restore_text(params.get('field_%i_city' % index, ''))
- user_address.state = util.restore_text(params.get('field_%i_state' % index, ''))
- user_address.postal_code = util.restore_text(params.get('field_%i_postal_code' % index, ''))
- user_address.country = util.restore_text(params.get('field_%i_country' % index, ''))
- user_address.phone = util.restore_text(params.get('field_%i_phone' % index, ''))
- trans.sa_session.add( user_address )
- trans.sa_session.flush()
- trans.sa_session.refresh( trans.user )
- values.append(int(user_address.id))
- elif value == unicode('none'):
- values.append('')
- else:
- values.append(int(value))
- elif field['type'] == 'CheckboxField':
- values.append(CheckboxField.is_checked( params.get('field_%i' % index, '') ))
- else:
- values.append(util.restore_text(params.get('field_%i' % index, '')))
- form_values = trans.app.model.FormValues(request_type.request_form, values)
- trans.sa_session.add( form_values )
- trans.sa_session.flush()
- if not request:
- request = trans.app.model.Request(name, desc, request_type,
- user, form_values, notify)
- trans.sa_session.add( request )
- trans.sa_session.flush()
- trans.sa_session.refresh( request )
- # create an event with state 'New' for this new request
- if request.user.email is not trans.user:
- comments = "Request created by admin (%s) on behalf of %s." % (trans.user.email, request.user.email)
- else:
- comments = "Request created."
- event = trans.app.model.RequestEvent(request, request.states.NEW, comments)
- trans.sa_session.add( event )
- trans.sa_session.flush()
- else:
- request.name = name
- request.desc = desc
- request.type = request_type
- request.user = user
- request.notify = notify
- request.values = form_values
- trans.sa_session.add( request )
- trans.sa_session.flush()
- return request
-#
-#---- Request Page ----------------------------------------------------------
-#
- def __show_request(self, trans, **kwd):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- add_sample = params.get('add_sample', False)
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) )
- except:
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- status='error',
- message="Invalid request ID") )
- # get all data libraries accessible to this user
- libraries = request.user.accessible_libraries( trans, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] )
- current_samples = []
- for i, s in enumerate(request.samples):
- lib_widget, folder_widget = self.__library_widgets(trans, request.user, i, libraries, s, **kwd)
- current_samples.append(dict(name=s.name,
- 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))
- if add_sample:
- lib_widget, folder_widget = self.__library_widgets(trans, request.user,
- len(current_samples)+1,
- libraries, None, **kwd)
- current_samples.append(dict(name='Sample_%i' % (len(current_samples)+1),
- 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))
- return trans.fill_template( '/admin/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples=current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details='hide', edit_mode=util.restore_text( params.get( 'edit_mode', 'False' ) ),
- message=message, status=status )
- def __library_widgets(self, trans, user, sample_index, libraries, sample=None, lib_id=None, folder_id=None, **kwd):
- '''
- This method creates the data library & folder selectbox for creating &
- editing samples. First we get a list of all the libraries accessible to
- the current user and display it in a selectbox. If the user has selected an
- existing library then display all the accessible sub folders of the selected
- data library.
- '''
- params = util.Params( kwd )
- # data library selectbox
- if not lib_id:
- lib_id = params.get( "sample_%i_library_id" % sample_index, 'none' )
- selected_lib = None
- if sample and lib_id == 'none':
- if sample.library:
- lib_id = str(sample.library.id)
- selected_lib = sample.library
- # create data library selectbox with refresh on change enabled
- lib_id_list = ['new'] + [str(lib.id) for lib in libraries.keys()]
- lib_widget = SelectField( "sample_%i_library_id" % sample_index,
- refresh_on_change=True,
- refresh_on_change_values=lib_id_list )
- # fill up the options in the Library selectbox
- # first option 'none' is the value for "Select one" option
- if lib_id == 'none':
- lib_widget.add_option('Select one', 'none', selected=True)
- else:
- lib_widget.add_option('Select one', 'none')
- # all the libraries available to the selected user
- for lib, hidden_folder_ids in libraries.items():
- if str(lib.id) == str(lib_id):
- lib_widget.add_option(lib.name, lib.id, selected=True)
- selected_lib, selected_hidden_folder_ids = lib, hidden_folder_ids.split(',')
- else:
- lib_widget.add_option(lib.name, lib.id)
- lib_widget.refresh_on_change_values.append(lib.id)
- # create the folder selectbox
- folder_widget = SelectField( "sample_%i_folder_id" % sample_index )
- # when editing a request, either the user has already selected a subfolder or not
- if sample:
- if sample.folder:
- current_fid = sample.folder.id
- else:
- # when a folder not yet associated with the request then the
- # the current folder is set to the root_folder of the
- # parent data library if present.
- if sample.library:
- current_fid = sample.library.root_folder.id
- else:
- current_fid = params.get( "sample_%i_folder_id" % sample_index, 'none' )
- else:
- if folder_id:
- current_fid = folder_id
- else:
- current_fid = 'none'
- # first option
- if lib_id == 'none':
- folder_widget.add_option('Select one', 'none', selected=True)
- else:
- folder_widget.add_option('Select one', 'none')
- if selected_lib:
- # get all show-able folders for the selected library
- showable_folders = trans.app.security_agent.get_showable_folders( user, user.all_roles(),
- selected_lib,
- [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ],
- selected_hidden_folder_ids )
- for f in showable_folders:
- if str(f.id) == str(current_fid):
- folder_widget.add_option(f.name, f.id, selected=True)
- else:
- folder_widget.add_option(f.name, f.id)
- return lib_widget, folder_widget
- def __update_samples(self, trans, request, **kwd):
- '''
- This method retrieves all the user entered sample information and
- returns an list of all the samples and their field values
- '''
- params = util.Params( kwd )
- details = params.get( 'details', 'hide' )
- edit_mode = params.get( 'edit_mode', 'False' )
- # get all data libraries accessible to this user
- libraries = request.user.accessible_libraries( trans, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] )
-
- current_samples = []
- for i, s in enumerate(request.samples):
- lib_widget, folder_widget = self.__library_widgets(trans, request.user, i, libraries, s, **kwd)
- current_samples.append(dict(name=s.name,
- barcode=s.bar_code,
- library=s.library,
- folder=s.folder,
- field_values=s.values.content,
- lib_widget=lib_widget,
- folder_widget=folder_widget))
- if edit_mode == 'False':
- sample_index = len(request.samples)
- else:
- sample_index = 0
- while True:
- lib_id = None
- folder_id = None
- if params.get( 'sample_%i_name' % sample_index, '' ):
- # data library
- try:
- library = trans.sa_session.query( trans.app.model.Library ).get( int( params.get( 'sample_%i_library_id' % sample_index, None ) ) )
- lib_id = library.id
- except:
- library = None
- # folder
- try:
- folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( int( params.get( 'sample_%i_folder_id' % sample_index, None ) ) )
- folder_id = folder.id
- except:
- if library:
- folder = library.root_folder
- else:
- folder = None
- sample_info = dict( name=util.restore_text( params.get( 'sample_%i_name' % sample_index, '' ) ),
- barcode=util.restore_text( params.get( 'sample_%i_barcode' % sample_index, '' ) ),
- library=library,
- folder=folder)
- sample_info['field_values'] = []
- for field_index in range(len(request.type.sample_form.fields)):
- sample_info['field_values'].append(util.restore_text( params.get( 'sample_%i_field_%i' % (sample_index, field_index), '' ) ))
- if edit_mode == 'False':
- sample_info['lib_widget'], sample_info['folder_widget'] = self.__library_widgets(trans, request.user,
- sample_index, libraries,
- None, lib_id, folder_id, **kwd)
- current_samples.append(sample_info)
- else:
- sample_info['lib_widget'], sample_info['folder_widget'] = self.__library_widgets(trans,
- request.user,
- sample_index,
- libraries,
- request.samples[sample_index],
- **kwd)
- current_samples[sample_index] = sample_info
- sample_index = sample_index + 1
- else:
- break
- return current_samples, details, edit_mode, libraries
- def __copy_sample(self, current_samples):
- copy_list = SelectField('copy_sample')
- copy_list.add_option('None', -1, selected=True)
- for i, s in enumerate(current_samples):
- copy_list.add_option(s['name'], i)
- return copy_list
- def __import_samples(self, trans, request, current_samples, details, libraries, **kwd):
- '''
- This method reads the samples csv file and imports all the samples
- The format of the csv file is:
- SampleName,DataLibrary,DataLibraryFolder,Field1,Field2....
- '''
- try:
- params = util.Params( kwd )
- edit_mode = params.get( 'edit_mode', 'False' )
- file_obj = params.get('file_data', '')
- reader = csv.reader(file_obj.file)
- for row in reader:
- lib_id = None
- folder_id = None
- lib = trans.sa_session.query( trans.app.model.Library ) \
- .filter( and_( trans.app.model.Library.table.c.name==row[1], \
- trans.app.model.Library.table.c.deleted==False ) )\
- .first()
- if lib:
- folder = trans.sa_session.query( trans.app.model.LibraryFolder ) \
- .filter( and_( trans.app.model.LibraryFolder.table.c.name==row[2], \
- trans.app.model.LibraryFolder.table.c.deleted==False ) )\
- .first()
- if folder:
- lib_id = lib.id
- folder_id = folder.id
- lib_widget, folder_widget = self.__library_widgets(trans, request.user, len(current_samples),
- libraries, None, lib_id, folder_id, **kwd)
- current_samples.append(dict(name=row[0],
- barcode='',
- library=None,
- folder=None,
- lib_widget=lib_widget,
- folder_widget=folder_widget,
- field_values=row[3:]))
- return trans.fill_template( '/admin/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples=current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details=details,
- edit_mode=edit_mode)
- except:
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- operation='show_request',
- id=trans.security.encode_id(request.id),
- status='error',
- message='Error in importing samples file' ))
- @web.expose
- @web.require_login( "create/submit sequencing requests" )
- def show_request(self, trans, **kwd):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- try:
- request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', None ) ) )
- except:
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- status='error',
- message="Invalid request ID",
- **kwd) )
- # get the user entered sample details
- current_samples, details, edit_mode, libraries = self.__update_samples( trans, request, **kwd )
- if params.get('import_samples_button', False) == 'Import samples':
- return self.__import_samples(trans, request, current_samples, details, libraries, **kwd)
- elif params.get('add_sample_button', False) == 'Add New':
- # add an empty or filled sample
- # if the user has selected a sample no. to copy then copy the contents
- # of the src sample to the new sample else an empty sample
- src_sample_index = int(params.get( 'copy_sample', -1 ))
- # get the number of new copies of the src sample
- num_sample_to_copy = int(params.get( 'num_sample_to_copy', 1 ))
- if src_sample_index == -1:
- for ns in range(num_sample_to_copy):
- # empty sample
- lib_widget, folder_widget = self.__library_widgets(trans, request.user,
- len(current_samples),
- libraries, None, **kwd)
- current_samples.append(dict(name='Sample_%i' % (len(current_samples)+1),
- barcode='',
- library=None,
- folder=None,
- field_values=['' for field in request.type.sample_form.fields],
- lib_widget=lib_widget,
- folder_widget=folder_widget))
- else:
- src_library_id = current_samples[src_sample_index]['lib_widget'].get_selected()[1]
- src_folder_id = current_samples[src_sample_index]['folder_widget'].get_selected()[1]
- for ns in range(num_sample_to_copy):
- lib_widget, folder_widget = self.__library_widgets(trans, request.user,
- len(current_samples),
- libraries, sample=None,
- lib_id=src_library_id,
- folder_id=src_folder_id,
- **kwd)
- current_samples.append(dict(name=current_samples[src_sample_index]['name']+'_%i' % (len(current_samples)+1),
- barcode='',
- library_id='none',
- folder_id='none',
- field_values=[val for val in current_samples[src_sample_index]['field_values']],
- lib_widget=lib_widget,
- folder_widget=folder_widget))
- return trans.fill_template( '/admin/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples=current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details=details,
- edit_mode=edit_mode)
- elif params.get('save_samples_button', False) == 'Save':
- # check for duplicate sample names
- message = ''
- for index in range(len(current_samples)-len(request.samples)):
- sample_index = index + len(request.samples)
- sample_name = current_samples[sample_index]['name']
- if not sample_name.strip():
- message = 'Please enter the name of sample number %i' % sample_index
- break
- count = 0
- for i in range(len(current_samples)):
- if sample_name == current_samples[i]['name']:
- count = count + 1
- if count > 1:
- message = "This request has <b>%i</b> samples with the name <b>%s</b>.\nSamples belonging to a request must have unique names." % (count, sample_name)
- break
- if message:
- return trans.fill_template( '/admin/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples = current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details=details, edit_mode=edit_mode,
- status='error', message=message)
- # save all the new/unsaved samples entered by the user
- if edit_mode == 'False':
- for index in range(len(current_samples)-len(request.samples)):
- sample_index = len(request.samples)
- form_values = trans.app.model.FormValues(request.type.sample_form,
- current_samples[sample_index]['field_values'])
- trans.sa_session.add( form_values )
- trans.sa_session.flush()
- s = trans.app.model.Sample(current_samples[sample_index]['name'], '',
- request, form_values,
- current_samples[sample_index]['barcode'],
- current_samples[sample_index]['library'],
- current_samples[sample_index]['folder'],
- dataset_files=[])
- trans.sa_session.add( s )
- trans.sa_session.flush()
+
- else:
- status = 'done'
- message = 'Changes made to the sample(s) are saved. '
- for sample_index in range(len(current_samples)):
- sample = request.samples[sample_index]
- sample.name = current_samples[sample_index]['name']
- sample.library = current_samples[sample_index]['library']
- sample.folder = current_samples[sample_index]['folder']
- if request.submitted():
- bc_message = self.__validate_barcode(trans, sample, current_samples[sample_index]['barcode'])
- if bc_message:
- status = 'error'
- message += bc_message
- else:
- if not sample.bar_code:
- # if this is a 'new' (still in its first state) sample
- # change the state to the next
- if sample.current_state().id == request.type.states[0].id:
- event = trans.app.model.SampleEvent(sample,
- request.type.states[1],
- 'Sample added to the system')
- trans.sa_session.add( event )
- trans.sa_session.flush()
- sample.bar_code = current_samples[sample_index]['barcode']
- trans.sa_session.add( sample )
- trans.sa_session.flush()
- form_values = trans.sa_session.query( trans.app.model.FormValues ).get( sample.values.id )
- form_values.content = current_samples[sample_index]['field_values']
- trans.sa_session.add( form_values )
- trans.sa_session.flush()
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- operation='show_request',
- id=trans.security.encode_id(request.id),
- status=status,
- message=message ))
- elif params.get('edit_samples_button', False) == 'Edit samples':
- edit_mode = 'True'
- return trans.fill_template( '/admin/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples=current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details=details, libraries=libraries,
- edit_mode=edit_mode)
- elif params.get('cancel_changes_button', False) == 'Cancel':
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- operation='show_request',
- id=trans.security.encode_id(request.id)) )
- else:
- return trans.fill_template( '/admin/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples=current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details=details, libraries=libraries,
- edit_mode=edit_mode, status=status, message=message)
-
-
- @web.expose
- @web.require_login( "create/submit sequencing requests" )
- def delete_sample(self, trans, **kwd):
- params = util.Params( kwd )
- message = util.restore_text( params.get( 'message', '' ) )
- status = params.get( 'status', 'done' )
- request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', 0 ) ) )
- current_samples, details, edit_mode, libraries = self.__update_samples( trans, request, **kwd )
- sample_index = int(params.get('sample_id', 0))
- sample_name = current_samples[sample_index]['name']
- s = request.has_sample(sample_name)
- if s:
- trans.sa_session.delete( s.values )
- trans.sa_session.delete( s )
- trans.sa_session.flush()
- del current_samples[sample_index]
- return trans.fill_template( '/admin/requests/show_request.mako',
- request=request,
- request_details=self.request_details(trans, request.id),
- current_samples = current_samples,
- sample_copy=self.__copy_sample(current_samples),
- details=details,
- edit_mode=edit_mode)
- @web.expose
- @web.require_admin
- def request_details(self, trans, id):
- '''
- Shows the request details
- '''
- request = trans.sa_session.query( trans.app.model.Request ).get( id )
- # list of widgets to be rendered on the request form
- request_details = []
- # main details
- request_details.append(dict(label='User',
- value=str(request.user.email),
- helptext=''))
- request_details.append(dict(label='Description',
- value=request.desc,
- helptext=''))
- request_details.append(dict(label='Type',
- value=request.type.name,
- helptext=''))
- request_details.append(dict(label='State',
- value=request.state(),
- helptext=''))
- request_details.append(dict(label='Date created',
- value=request.create_time,
- helptext=''))
- # form fields
- for index, field in enumerate(request.type.request_form.fields):
- if field['required']:
- req = 'Required'
- else:
- req = 'Optional'
- if field['type'] == 'AddressField':
- if request.values.content[index]:
- request_details.append(dict(label=field['label'],
- value=trans.sa_session.query( trans.app.model.UserAddress ).get( int( request.values.content[index] ) ).get_html(),
- helptext=field['helptext']+' ('+req+')'))
- else:
- request_details.append(dict(label=field['label'],
- value=None,
- helptext=field['helptext']+' ('+req+')'))
- else:
- request_details.append(dict(label=field['label'],
- value=request.values.content[index],
- helptext=field['helptext']+' ('+req+')'))
- if request.notify:
- notify = 'Yes'
- else:
- notify = 'No'
- request_details.append(dict(label='Send email notification once the sequencing request is complete',
- value=notify,
- helptext=''))
- return request_details
- def __validate_barcode(self, trans, sample, barcode):
- '''
- This method makes sure that the given barcode about to be assigned to
- the given sample is gobally unique. That is, barcodes must be unique
- across requests in Galaxy LIMS
- '''
- message = ''
- for index in range(len(sample.request.samples)):
- # check for empty bar code
- if not barcode.strip():
- message = 'Please fill the barcode for sample <b>%s</b>.' % sample.name
- break
- # check all the saved bar codes
- all_samples = trans.sa_session.query( trans.app.model.Sample )
- for s in all_samples:
- if barcode == s.bar_code:
- if sample.id == s.id:
- continue
- else:
- message = '''The bar code <b>%s</b> of sample <b>%s</b>
- belongs another sample. The sample bar codes must be
- unique throughout the system''' % \
- (barcode, sample.name)
- break
- if message:
- break
- return message
@web.expose
@web.require_admin
def bar_codes(self, trans, **kwd):
@@ -1410,21 +481,7 @@ class RequestsAdmin( BaseController ):
email_content = "To: %s\nFrom: no-reply(a)nowhere.edu\nSubject: %s\n\n%s" % (request.user.email, subject, body)
mail.write( email_content )
x = mail.close()
- def change_state(self, trans, sample):
- possible_states = sample.request.type.states
- curr_state = sample.current_state()
- states_input = SelectField('select_state')
- for state in possible_states:
- if curr_state.name == state.name:
- states_input.add_option(state.name+' (Current)', state.id, selected=True)
- else:
- states_input.add_option(state.name, state.id)
- widgets = []
- widgets.append(('Select the new state of the sample from the list of possible state(s)',
- states_input))
- widgets.append(('Comments', TextArea('comment')))
- title = 'Change current state'
- return widgets, title
+
@web.expose
@web.require_admin
def save_state(self, trans, **kwd):
@@ -1449,71 +506,14 @@ class RequestsAdmin( BaseController ):
trans.sa_session.add( event )
trans.sa_session.flush()
self.__set_request_state( trans, sample.request )
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='show_events',
+ return trans.response.send_redirect( web.url_for( controller='requests_common',
+ cntrller='requests_admin',
+ action='sample_events',
sample_id=sample.id))
- @web.expose
- @web.require_admin
- def show_events(self, trans, **kwd):
- params = util.Params( kwd )
- try:
- sample_id = int(params.get('sample_id', False))
- sample = trans.sa_session.query( trans.app.model.Sample ).get( sample_id )
- except:
- message = "Invalid sample ID"
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- status='error',
- message=message,
- **kwd) )
- events_list = []
- all_events = sample.events
- for event in all_events:
- events_list.append((event.state.name, event.state.desc,
- time_ago(event.update_time), event.comment))
- widgets, title = self.change_state(trans, sample)
- return trans.fill_template( '/admin/samples/events.mako',
- events_list=events_list,
- sample=sample, widgets=widgets, title=title)
-
#
# Data transfer from sequencer
#
- @web.expose
- @web.require_admin
- def show_datatx_page( 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",
- **kwd) )
- # check if a library and folder has been set for this sample yet.
- if not sample.library or not sample.folder:
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
- action='list',
- operation='show_request',
- 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) ) )
- 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])
- else:
- folder_path = util.restore_text( sample.request.type.datatx_info.get('data_dir', '') )
- if folder_path[-1] != os.sep:
- folder_path += os.sep
- return trans.fill_template( '/admin/requests/get_data.mako',
- sample=sample, dataset_files=sample.dataset_files,
- message=message, status=status, files=[],
- folder_path=folder_path )
+
def __get_files(self, trans, sample, folder_path):
'''
This method retrieves the filenames to be transfer from the remote host.
@@ -1521,7 +521,8 @@ class RequestsAdmin( BaseController ):
datatx_info = sample.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_admin',
+ 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),
status='error',
@@ -1536,7 +537,8 @@ class RequestsAdmin( BaseController ):
timeout=10)
if 'No such file or directory' in output:
message = "No such folder (%s) exists on the sequencer." % folder_path
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ 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),
message=message, status='error',
@@ -1574,7 +576,8 @@ class RequestsAdmin( BaseController ):
if params.get( 'start_transfer_button', False ) == 'True':
return self.__start_datatx(trans, sample)
if not folder_path:
- return trans.fill_template( '/admin/requests/get_data.mako',
+ return trans.fill_template( '/requests/common/get_data.mako',
+ cntrller='requests_admin',
sample=sample, files=[],
dataset_files=sample.dataset_files,
folder_path=folder_path )
@@ -1585,7 +588,8 @@ class RequestsAdmin( BaseController ):
files = self.__get_files(trans, sample, folder_path)
if folder_path[-1] != os.sep:
folder_path += os.sep
- return trans.fill_template( '/admin/requests/get_data.mako',
+ 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 )
@@ -1596,7 +600,8 @@ class RequestsAdmin( BaseController ):
files = self.__get_files(trans, sample, folder_path)
if folder_path[-1] != os.sep:
folder_path += os.sep
- return trans.fill_template( '/admin/requests/get_data.mako',
+ 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 )
@@ -1607,7 +612,8 @@ class RequestsAdmin( BaseController ):
files = self.__get_files(trans, sample, folder_path)
if folder_path[-1] != os.sep:
folder_path += os.sep
- return trans.fill_template( '/admin/requests/get_data.mako',
+ 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 )
@@ -1618,7 +624,8 @@ class RequestsAdmin( BaseController ):
del sample.dataset_files[dataset_index]
trans.sa_session.add( sample )
trans.sa_session.flush()
- return trans.fill_template( '/admin/requests/get_data.mako',
+ 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)
@@ -1651,7 +658,8 @@ class RequestsAdmin( BaseController ):
folder_path=folder_path,
open_folder=True))
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ 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))
@@ -1780,13 +788,15 @@ 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_admin',
+ 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),
status='error',
message=message))
self.__send_message(trans, datatx_info, sample)
- return trans.response.send_redirect( web.url_for( controller='requests_admin',
+ 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']))
1
0

galaxy-dist commit 9289b4d7fa4d: API: Patch from Brad Chapman to prevent deleted items from appearing in library contents listing.
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 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 1277904834 14400
# Node ID 9289b4d7fa4d1d1b630c044dfbc102cf870f62e3
# Parent 1d05df35ec27cc606ddc486c83ea090e01a441d8
API: Patch from Brad Chapman to prevent deleted items from appearing in library contents listing.
--- a/lib/galaxy/web/api/contents.py
+++ b/lib/galaxy/web/api/contents.py
@@ -26,7 +26,7 @@ class ContentsController( BaseController
for subfolder in folder.active_folders:
if not admin:
can_access, folder_ids = trans.app.security_agent.check_folder_contents( trans.user, current_user_roles, subfolder )
- if admin or can_access:
+ if (admin or can_access) and not subfolder.deleted:
subfolder.api_path = folder.api_path + '/' + subfolder.name
subfolder.api_type = 'folder'
rval.append( subfolder )
@@ -34,7 +34,7 @@ class ContentsController( BaseController
for ld in folder.datasets:
if not admin:
can_access = trans.app.security_agent.can_access_dataset( current_user_roles, ld.library_dataset_dataset_association.dataset )
- if admin or can_access:
+ if (admin or can_access) and not ld.deleted:
ld.api_path = folder.api_path + '/' + ld.name
ld.api_type = 'file'
rval.append( ld )
1
0

16 Jul '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User jeremy goecks <jeremy.goecks(a)emory.edu>
# Date 1277866995 14400
# Node ID 1d05df35ec27cc606ddc486c83ea090e01a441d8
# Parent 47411bb09df44719760aac61e4b90de4fd190aad
Include job information when exporting and importing histories to/from files. This enables jobs associated with an imported history to be rerun and hence affords a history's analyses to be reproduced exactly. Jobs also have a new flag, imported, that is set to false by default but is true when a job is created via import. Also refactored export/import code to use custom JSON encoders.
This code is in a very alpha/beta state: it may not work well, at all, or as expected. Complex datasets and/or jobs are likely not yet handled. Use with caution.
Also, due to security issues, history/import and history/export methods are not currently web-accessible. Admins must manually make these methods accessible if they want to use them.
--- a/templates/root/index.mako
+++ b/templates/root/index.mako
@@ -40,9 +40,9 @@
"Show structure": function() {
galaxy_main.location = "${h.url_for( controller='history', action='display_structured' )}";
},
- "Export to File": function() {
- galaxy_main.location = "${h.url_for( controller='history', action='export_archive' )}";
- },
+ ##"Export to File": function() {
+ ## galaxy_main.location = "${h.url_for( controller='history', action='export_archive' )}";
+ ##},
"Delete": function()
{
if ( confirm( "Really delete the current history?" ) )
@@ -50,10 +50,10 @@
galaxy_main.location = "${h.url_for( controller='history', action='delete_current' )}";
}
},
- "Other Actions": null,
- "Import from File": function() {
- galaxy_main.location = "${h.url_for( controller='history', action='import_archive' )}";
- }
+ ##"Other Actions": null,
+ ##"Import from File": function() {
+ ## galaxy_main.location = "${h.url_for( controller='history', action='import_archive' )}";
+ ##}
});
// Init tool options.
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -115,6 +115,7 @@ class Job( object ):
self.job_runner_name = None
self.job_runner_external_id = None
self.post_job_actions = None
+ self.imported = False
def add_parameter( self, name, value ):
self.parameters.append( JobParameter( name, value ) )
--- /dev/null
+++ b/lib/galaxy/model/migrate/versions/0051_imported_col_for_jobs_table.py
@@ -0,0 +1,48 @@
+"""
+Migration script to add imported column for jobs table.
+"""
+
+from sqlalchemy import *
+from sqlalchemy.orm import *
+from migrate import *
+from migrate.changeset import *
+
+import logging
+log = logging.getLogger( __name__ )
+
+metadata = MetaData( migrate_engine )
+db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) )
+
+def upgrade():
+ print __doc__
+ metadata.reflect()
+
+ # Create and initialize imported column in job table.
+ Jobs_table = Table( "job", metadata, autoload=True )
+ c = Column( "imported", Boolean, default=False, index=True )
+ try:
+ # Create
+ c.create( Jobs_table )
+ assert c is Jobs_table.c.imported
+
+ # Initialize.
+ if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite':
+ default_false = "0"
+ elif migrate_engine.name == 'postgres':
+ default_false = "false"
+ db_session.execute( "UPDATE job SET imported=%s" % default_false )
+
+ except Exception, e:
+ print "Adding imported column to job table failed: %s" % str( e )
+ log.debug( "Adding imported column to job table failed: %s" % str( e ) )
+
+def downgrade():
+ metadata.reflect()
+
+ # Drop imported column from job table.
+ Jobs_table = Table( "job", metadata, autoload=True )
+ try:
+ Jobs_table.c.imported.drop()
+ except Exception, e:
+ print "Dropping column imported from job table failed: %s" % str( e )
+ log.debug( "Dropping column imported from job table failed: %s" % str( e ) )
--- a/lib/galaxy/web/controllers/history.py
+++ b/lib/galaxy/web/controllers/history.py
@@ -6,6 +6,7 @@ from galaxy.model.mapping import desc
from galaxy.model.orm import *
from galaxy.util.json import *
from galaxy.util.sanitize_html import sanitize_html
+from galaxy.tools.parameters.basic import UnvalidatedValue
from galaxy.tools.actions import upload_common
from galaxy.tags.tag_handler import GalaxyTagHandler
from sqlalchemy.sql.expression import ClauseElement
@@ -145,7 +146,7 @@ class HistoryAllPublishedGrid( grids.Gri
def apply_query_filter( self, trans, query, **kwargs ):
# A public history is published, has a slug, and is not deleted.
return query.filter( self.model_class.published == True ).filter( self.model_class.slug != None ).filter( self.model_class.deleted == False )
-
+
class HistoryController( BaseController, Sharable, UsesAnnotations, UsesHistory ):
@web.expose
def index( self, trans ):
@@ -443,26 +444,26 @@ class HistoryController( BaseController,
trans.sa_session.flush()
return new_annotation
- @web.expose
def import_archive( self, trans, archived_history=None ):
""" Import a history. """
if archived_history is not None:
- # Import archived history.
try:
history_archive_file = tarfile.open( archived_history.file.name )
-
+
# Security check: make sure that members are relative, not absolute.
for tarinfo in history_archive_file.getmembers():
if tarinfo.name.startswith("/") or tarinfo.name.find("..") != -1:
return trans.show_error_message( 'Error importing history archive: archive file is invalid.' )
-
+
# Unpack archive in temporary directory.
temp_output_dir = tempfile.mkdtemp()
history_archive_file.extractall( path=temp_output_dir )
history_archive_file.close()
-
- # Read history attributes.
+
+ #
+ # Create history.
+ #
history_attr_in = open( '%s/%s' % ( temp_output_dir, 'history_attrs.txt'), 'rb' )
history_attr_str = ''
buffsize = 1048576
@@ -473,31 +474,33 @@ class HistoryController( BaseController,
break
except OverflowError:
pass
+ history_attr_in.close()
history_attrs = from_json_string( history_attr_str )
-
+
# Create history.
new_history = model.History( name='imported from archive: %s' % history_attrs['name'].encode( 'utf-8' ), user=trans.user )
trans.sa_session.add( new_history )
-
+
+ new_history.hid_counter = history_attrs['hid_counter']
+ new_history.genome_build = history_attrs['genome_build']
+ trans.sa_session.flush()
+
# Builds a tag string for a tag, value pair.
def get_tag_str( tag, value ):
if not value:
return tag
else:
return tag + ":" + value
-
+
# Add annotation, tags.
if trans.user:
self.add_item_annotation( trans, new_history, history_attrs[ 'annotation' ] )
for tag, value in history_attrs[ 'tags' ].items():
trans.app.tag_handler.apply_item_tags( trans, trans.user, new_history, get_tag_str( tag, value ) )
-
- # Ignore hid_counter since it artificially increases the hid for all HDAs?
- # new_history.hid_counter = history_attrs['hid_counter']
- new_history.genome_build = history_attrs['genome_build']
- trans.sa_session.flush()
-
- # Read datasets attributes.
+
+ #
+ # Create datasets.
+ #
datasets_attr_in = open( '%s/%s' % ( temp_output_dir, 'datasets_attrs.txt'), 'rb' )
datasets_attr_str = ''
buffsize = 1048576
@@ -508,64 +511,187 @@ class HistoryController( BaseController,
break
except OverflowError:
pass
+ datasets_attr_in.close()
datasets_attrs = from_json_string( datasets_attr_str )
-
- # Create datasets.
+
+ # Create datasets.
for dataset_attrs in datasets_attrs:
metadata = dataset_attrs['metadata']
-
+
# Create dataset and HDA.
- hda = trans.app.model.HistoryDatasetAssociation( name = dataset_attrs['name'].encode( 'utf-8' ),
- extension = dataset_attrs['extension'],
- hid = dataset_attrs['hid'],
- info = dataset_attrs['info'].encode( 'utf-8' ),
- blurb = dataset_attrs['blurb'],
- peek = dataset_attrs['peek'],
- designation = dataset_attrs['designation'],
- visible = dataset_attrs['visible'],
- dbkey = metadata['dbkey'],
- metadata = metadata,
- history = new_history,
- create_dataset = True,
- sa_session = trans.sa_session )
+ hda = model.HistoryDatasetAssociation( name = dataset_attrs['name'].encode( 'utf-8' ),
+ extension = dataset_attrs['extension'],
+ info = dataset_attrs['info'].encode( 'utf-8' ),
+ blurb = dataset_attrs['blurb'],
+ peek = dataset_attrs['peek'],
+ designation = dataset_attrs['designation'],
+ visible = dataset_attrs['visible'],
+ dbkey = metadata['dbkey'],
+ metadata = metadata,
+ history = new_history,
+ create_dataset = True,
+ sa_session = trans.sa_session )
hda.state = hda.states.OK
trans.sa_session.add( hda )
trans.sa_session.flush()
new_history.add_dataset( hda, genome_build = None )
+ hda.hid = dataset_attrs['hid'] # Overwrite default hid set when HDA added to history.
permissions = trans.app.security_agent.history_get_default_permissions( new_history )
trans.app.security_agent.set_all_dataset_permissions( hda.dataset, permissions )
trans.sa_session.flush()
-
+
# Copy dataset data.
temp_dataset_name = '%s/datasets/%s' % ( temp_output_dir, dataset_attrs['file_name'] )
shutil.copyfile( temp_dataset_name, hda.file_name )
-
+
# Set tags, annotations.
if trans.user:
self.add_item_annotation( trans, hda, dataset_attrs[ 'annotation' ] )
for tag, value in dataset_attrs[ 'tags' ].items():
trans.app.tag_handler.apply_item_tags( trans, trans.user, hda, get_tag_str( tag, value ) )
- trans.sa_session.flush()
+ trans.sa_session.flush()
+ #
+ # Create jobs.
+ #
+
+ # Read jobs attributes.
+ jobs_attr_in = open( '%s/%s' % ( temp_output_dir, 'jobs_attrs.txt'), 'rb' )
+ jobs_attr_str = ''
+ buffsize = 1048576
+ try:
+ while True:
+ jobs_attr_str += jobs_attr_in.read( buffsize )
+ if not jobs_attr_str or len( jobs_attr_str ) % buffsize != 0:
+ break
+ except OverflowError:
+ pass
+ jobs_attr_in.close()
+
+ # Decode jobs attributes.
+ def as_hda( obj_dct ):
+ """ Hook to 'decode' an HDA; method uses history and HID to get the HDA represented by
+ the encoded object. This only works because HDAs are created above. """
+ if obj_dct.get( '__HistoryDatasetAssociation__', False ):
+ return trans.sa_session.query( model.HistoryDatasetAssociation ) \
+ .filter_by( history=new_history, hid=obj_dct['hid'] ).first()
+ return obj_dct
+ jobs_attrs = from_json_string( jobs_attr_str, object_hook=as_hda )
+
+ # Create each job.
+ for job_attrs in jobs_attrs:
+ imported_job = model.Job()
+ imported_job.user = trans.user
+ imported_job.session = trans.get_galaxy_session().id
+ imported_job.history = new_history
+ imported_job.tool_id = job_attrs[ 'tool_id' ]
+ imported_job.tool_version = job_attrs[ 'tool_version' ]
+ imported_job.set_state( job_attrs[ 'state' ] )
+ imported_job.imported = True
+ trans.sa_session.add( imported_job )
+ trans.sa_session.flush()
+
+ class HistoryDatasetAssociationIDEncoder( simplejson.JSONEncoder ):
+ """ Custom JSONEncoder for a HistoryDatasetAssociation that encodes an HDA as its ID. """
+ def default( self, obj ):
+ """ Encode an HDA, default encoding for everything else. """
+ if isinstance( obj, model.HistoryDatasetAssociation ):
+ return obj.id
+ return simplejson.JSONEncoder.default( self, obj )
+
+ # Set parameters. May be useful to look at metadata.py for creating parameters.
+ # TODO: there may be a better way to set parameters, e.g.:
+ # for name, value in tool.params_to_strings( incoming, trans.app ).iteritems():
+ # job.add_parameter( name, value )
+ # to make this work, we'd need to flesh out the HDA objects. The code below is
+ # relatively similar.
+ for name, value in job_attrs[ 'params' ].items():
+ # Transform parameter values when necessary.
+ if isinstance( value, model.HistoryDatasetAssociation ):
+ # HDA input: use hid to find input.
+ input_hda = trans.sa_session.query( model.HistoryDatasetAssociation ) \
+ .filter_by( history=new_history, hid=value.hid ).first()
+ value = input_hda.id
+ #print "added parameter %s-->%s to job %i" % ( name, value, imported_job.id )
+ imported_job.add_parameter( name, to_json_string( value, cls=HistoryDatasetAssociationIDEncoder ) )
+
+ # TODO: Connect jobs to input datasets.
+
+ # Connect jobs to output datasets.
+ for output_hid in job_attrs[ 'output_datasets' ]:
+ #print "%s job has output dataset %i" % (imported_job.id, output_hid)
+ output_hda = trans.sa_session.query( model.HistoryDatasetAssociation ) \
+ .filter_by( history=new_history, hid=output_hid ).first()
+ if output_hda:
+ imported_job.add_output_dataset( output_hda.name, output_hda )
+ trans.sa_session.flush()
+
# Cleanup.
if os.path.exists( temp_output_dir ):
shutil.rmtree( temp_output_dir )
-
+
return trans.show_ok_message( message="History '%s' has been imported. " % history_attrs['name'] )
except Exception, e:
return trans.show_error_message( 'Error importing history archive. ' + str( e ) )
-
return trans.show_form(
web.FormBuilder( web.url_for(), "Import a History from an Archive", submit_text="Submit" )
.add_input( "file", "Archived History File", "archived_history", value=None, error=None )
)
- @web.expose
def export_archive( self, trans, id=None ):
""" Export a history. """
+ #
+ # Helper methods/classes.
+ #
+
+ def unicode_wrangler( a_string ):
+ """ Convert strings to unicode in utf-8 format. Method should be used for all user input. """
+ a_string_type = type ( a_string )
+ if a_string_type is str:
+ return unicode( a_string, 'utf-8' )
+ elif a_string_type is unicode:
+ return a_string.encode( 'utf-8' )
+
+ def get_item_tag_dict( item ):
+ """ Create dictionary of an item's tags. """
+ tags = {}
+ for tag in item.tags:
+ tag_user_tname = unicode_wrangler( tag.user_tname )
+ tag_user_value = unicode_wrangler( tag.user_value )
+ tags[ tag_user_tname ] = tag_user_value
+ return tags
+
+ class HistoryDatasetAssociationEncoder( simplejson.JSONEncoder ):
+ """ Custom JSONEncoder for a HistoryDatasetAssociation. """
+ def default( self, obj ):
+ """ Encode an HDA, default encoding for everything else. """
+ if isinstance( obj, model.HistoryDatasetAssociation ):
+ return {
+ "__HistoryDatasetAssociation__" : True,
+ "create_time" : obj.create_time.__str__(),
+ "update_time" : obj.update_time.__str__(),
+ "hid" : obj.hid,
+ "name" : unicode_wrangler( obj.name ),
+ "info" : unicode_wrangler( obj.info ),
+ "blurb" : obj.blurb,
+ "peek" : obj.peek,
+ "extension" : obj.extension,
+ "metadata" : dict( obj.metadata.items() ),
+ "parent_id" : obj.parent_id,
+ "designation" : obj.designation,
+ "deleted" : obj.deleted,
+ "visible" : obj.visible,
+ "file_name" : obj.file_name.split('/')[-1],
+ "annotation" : unicode_wrangler( obj.annotation ),
+ "tags" : get_item_tag_dict( obj ),
+ }
+ return simplejson.JSONEncoder.default( self, obj )
+
+ #
# Get history to export.
+ #
if id:
history = self.get_history( trans, id, check_ownership=False, check_accessible=True )
else:
@@ -577,35 +703,24 @@ class HistoryController( BaseController,
history_export_dir_name = "./database/export"
archive_file_name = '%s/%s.tar.gz' % ( history_export_dir_name, trans.security.encode_id( history.id ) )
+
+ #
+ # Do export.
+ #
+
# TODO: for now, always create archive when exporting; this is for debugging purposes.
if True:
# Condition for only creating an archive when history is newer than archive:
#not os.path.exists ( archive_file_name ) or datetime.utcfromtimestamp( os.path.getmtime( archive_file_name ) ) < history.update_time:
# Create archive and stream back to client.
-
- # Convert strings to unicode in utf-8 format. Method should be used for all user input.
- def unicode_wrangler( a_string ):
- a_string_type = type ( a_string )
- if a_string_type is str:
- return unicode( a_string, 'utf-8' )
- elif a_string_type is unicode:
- return a_string.encode( 'utf-8' )
-
- # Create dictionary of an item's tags.
- def get_item_tag_dict( item ):
- tags = {}
- for tag in item.tags:
- tag_user_tname = unicode_wrangler( tag.user_tname )
- tag_user_value = unicode_wrangler( tag.user_value )
- tags[ tag_user_tname ] = tag_user_value
- return tags
-
try:
# Use temporary directory for temp output files.
temp_output_dir = tempfile.mkdtemp()
-
+
+ #
# Write history attributes to file.
+ #
history_attrs = {
"create_time" : history.create_time.__str__(),
"update_time" : history.update_time.__str__(),
@@ -619,58 +734,103 @@ class HistoryController( BaseController,
history_attrs_out = open( history_attrs_file_name, 'w' )
history_attrs_out.write( to_json_string( history_attrs ) )
history_attrs_out.close()
- new_name = '%s/%s' % ( temp_output_dir, "history_attrs.txt" )
- os.rename( history_attrs_file_name, new_name )
- history_attrs_file_name = new_name
-
+
+ #
# Write datasets' attributes to file.
+ #
datasets = self.get_history_datasets( trans, history )
datasets_attrs = []
for dataset in datasets:
- attribute_dict = {
- "create_time" : dataset.create_time.__str__(),
- "update_time" : dataset.update_time.__str__(),
- "hid" : dataset.hid,
- "name" : unicode_wrangler( dataset.name ),
- "info" : unicode_wrangler( dataset.info ),
- "blurb" : dataset.blurb,
- "peek" : dataset.peek,
- "extension" : dataset.extension,
- "metadata" : dict( dataset.metadata.items() ),
- "parent_id" : dataset.parent_id,
- "designation" : dataset.designation,
- "deleted" : dataset.deleted,
- "visible" : dataset.visible,
- "file_name" : dataset.file_name.split('/')[-1],
- "annotation" : unicode_wrangler( self.get_item_annotation_str( trans, history.user, dataset ) ),
- "tags" : get_item_tag_dict( dataset )
- }
- datasets_attrs.append( attribute_dict )
+ dataset.annotation = self.get_item_annotation_str( trans, history.user, dataset )
+ datasets_attrs.append( dataset )
datasets_attrs_file_name = tempfile.NamedTemporaryFile( dir=temp_output_dir ).name
datasets_attrs_out = open( datasets_attrs_file_name, 'w' )
- datasets_attrs_out.write( to_json_string( datasets_attrs ) )
+ datasets_attrs_out.write( to_json_string( datasets_attrs, cls=HistoryDatasetAssociationEncoder ) )
datasets_attrs_out.close()
- new_name = '%s/%s' % ( temp_output_dir, "datasets_attrs.txt" )
- os.rename( datasets_attrs_file_name, new_name )
- datasets_attrs_file_name = new_name
-
- # Write files to archive: (a) history attributes file; (b) datasets attributes file; and (c) datasets files.
+
+ #
+ # Write jobs attributes file.
+ #
+
+ # Get all jobs associated with HDAs.
+ jobs_dict = {}
+ for hda in datasets:
+ # Get the associated job, if any. If this hda was copied from another,
+ # we need to find the job that created the origial hda
+ job_hda = hda
+ while job_hda.copied_from_history_dataset_association: #should this check library datasets as well?
+ job_hda = job_hda.copied_from_history_dataset_association
+ if not job_hda.creating_job_associations:
+ # No viable HDA found.
+ continue
+
+ # Get the job object.
+ job = None
+ for assoc in job_hda.creating_job_associations:
+ job = assoc.job
+ break
+ if not job:
+ # No viable job.
+ continue
+
+ jobs_dict[ job.id ] = job
+
+ # Get jobs' attributes.
+ jobs_attrs = []
+ for id, job in jobs_dict.items():
+ job_attrs = {}
+ job_attrs[ 'tool_id' ] = job.tool_id
+ job_attrs[ 'tool_version' ] = job.tool_version
+ job_attrs[ 'state' ] = job.state
+
+ # Get the job's parameters
+ try:
+ params_objects = job.get_param_values( trans.app )
+ except:
+ # Could not get job params.
+ continue
+
+ params_dict = {}
+ for name, value in params_objects.items():
+ params_dict[ name ] = value
+ job_attrs[ 'params' ] = params_dict
+
+ # Get input, output datasets.
+ input_datasets = [ assoc.dataset.hid for assoc in job.input_datasets ]
+ job_attrs[ 'input_datasets' ] = input_datasets
+ output_datasets = [ assoc.dataset.hid for assoc in job.output_datasets ]
+ job_attrs[ 'output_datasets' ] = output_datasets
+
+ jobs_attrs.append( job_attrs )
+
+ jobs_attrs_file_name = tempfile.NamedTemporaryFile( dir=temp_output_dir ).name
+ jobs_attrs_out = open( jobs_attrs_file_name, 'w' )
+ jobs_attrs_out.write( to_json_string( jobs_attrs, cls=HistoryDatasetAssociationEncoder ) )
+ jobs_attrs_out.close()
+
+ #
+ # Write archive and include: (a) history attributes file; (b) datasets attributes file;
+ # (c) jobs attributes file; and (d) datasets files.
+ #
history_archive_name = '%s/%s.tar.gz' % ( history_export_dir_name, trans.security.encode_id( history.id ) )
history_archive = tarfile.open( history_archive_name, "w:gz" )
history_archive.add( history_attrs_file_name, arcname="history_attrs.txt" )
history_archive.add( datasets_attrs_file_name, arcname="datasets_attrs.txt" )
+ history_archive.add( jobs_attrs_file_name, arcname="jobs_attrs.txt" )
for i, dataset in enumerate( datasets ) :
- history_archive.add( dataset.file_name, arcname="datasets/%s" % datasets_attrs[i]['file_name'] )
+ history_archive.add( dataset.file_name, arcname="datasets/%s" % dataset.file_name.split('/')[-1] )
history_archive.close()
-
+
# Remove temp directory.
if os.path.exists( temp_output_dir ):
shutil.rmtree( temp_output_dir )
-
+
except Exception, e:
return trans.show_error_message( 'Error creating history archive. ' + str( e ) )
-
+
+ #
# Stream archive.
+ #
if os.path.exists( history_archive_name ):
valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
hname = history.name
--- a/lib/galaxy/model/mapping.py
+++ b/lib/galaxy/model/mapping.py
@@ -334,7 +334,8 @@ Job.table = Table( "job", metadata,
Column( "session_id", Integer, ForeignKey( "galaxy_session.id" ), index=True, nullable=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=True ),
Column( "job_runner_name", String( 255 ) ),
- Column( "job_runner_external_id", String( 255 ) ) )
+ Column( "job_runner_external_id", String( 255 ) ),
+ Column( "imported", Boolean, default=False, index=True ) )
JobParameter.table = Table( "job_parameter", metadata,
Column( "id", Integer, primary_key=True ),
1
0

galaxy-dist commit 10027d1e6dc3: Switch methods using sendmail to SMTP instead.
by commits-noreply@bitbucket.org 16 Jul '10
by commits-noreply@bitbucket.org 16 Jul '10
16 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 1277754926 14400
# Node ID 10027d1e6dc31200319f9d90a4d8233170393f2b
# Parent eea2c040ccb7bd0d2a7c6e5eab8e65f98a163009
Switch methods using sendmail to SMTP instead.
--- a/lib/galaxy/web/controllers/user.py
+++ b/lib/galaxy/web/controllers/user.py
@@ -4,8 +4,9 @@ Contains the user interface in the Unive
from galaxy.web.base.controller import *
from galaxy.model.orm import *
from galaxy import util
-import logging, os, string, re
+import logging, os, string, re, smtplib, socket
from random import choice
+from email.MIMEText import MIMEText
from galaxy.web.form_builder import *
from galaxy.util.json import from_json_string, to_json_string
from galaxy.web.framework.helpers import iff
@@ -152,10 +153,20 @@ class User( BaseController ):
self.__save_user_info( trans, user, action='create', new_user=True, **kwd )
if subscribe_checked:
# subscribe user to email list
- mail = os.popen( "%s -t" % trans.app.config.sendmail_path, 'w' )
- mail.write( "To: %s\nFrom: %s\nSubject: Join Mailing List\n\nJoin Mailing list." % ( trans.app.config.mailing_join_addr,email ) )
- if mail.close():
- error = "Now logged in as " + user.email + ". However, subscribing to the mailing list has failed."
+ if trans.app.config.smtp_server is None:
+ error = "Now logged in as " + user.email + ". However, subscribing to the mailing list has failed because mail is not configured for this Galaxy instance."
+ else:
+ msg = MIMEText( 'Join Mailing list.\n' )
+ to = msg[ 'To' ] = trans.app.config.mailing_join_addr
+ frm = msg[ 'From' ] = email
+ msg[ 'Subject' ] = 'Join Mailing List'
+ try:
+ s = smtplib.SMTP()
+ s.connect( trans.app.config.smtp_server )
+ s.sendmail( frm, [ to ], msg.as_string() )
+ s.close()
+ except:
+ error = "Now logged in as " + user.email + ". However, subscribing to the mailing list has failed."
if not error and not admin_view:
# The handle_user_login() method has a call to the history_set_default_permissions() method
# (needed when logging in with a history), user needs to have default permissions set before logging in
@@ -594,6 +605,8 @@ class User( BaseController ):
webapp=webapp ) )
@web.expose
def reset_password( self, trans, email=None, webapp='galaxy', **kwd ):
+ if trans.app.config.smtp_server is None:
+ return trans.show_error_message( "Mail is not configured for this Galaxy instance. Please contact an administrator." )
message = util.restore_text( kwd.get( 'message', '' ) )
status = 'done'
if kwd.get( 'reset_password_button', False ):
@@ -608,20 +621,30 @@ class User( BaseController ):
new_pass = ""
for i in range(15):
new_pass = new_pass + choice(chars)
- mail = os.popen("%s -t" % trans.app.config.sendmail_path, 'w')
- mail.write("To: %s\nFrom: no-reply(a)nowhere.edu\nSubject: Galaxy Password Reset\n\nYour password has been reset to \"%s\" (no quotes)." % (email, new_pass) )
- if mail.close():
- message = 'Failed to reset password. If this problem persists, please submit a bug report.'
+ host = trans.request.host.split(':')[0]
+ if host == 'localhost':
+ host = socket.getfqdn()
+ msg = MIMEText( 'Your password on %s has been reset to:\n\n %s\n' % ( host, new_pass ) )
+ to = msg[ 'To' ] = email
+ frm = msg[ 'From' ] = 'galaxy-no-reply@' + host
+ msg[ 'Subject' ] = 'Galaxy Password Reset'
+ try:
+ s = smtplib.SMTP()
+ s.connect( trans.app.config.smtp_server )
+ s.sendmail( frm, [ to ], msg.as_string() )
+ s.close()
+ reset_user.set_password_cleartext( new_pass )
+ trans.sa_session.add( reset_user )
+ trans.sa_session.flush()
+ trans.log_event( "User reset password: %s" % email )
+ message = "Password has been reset and emailed to: %s. <a href='%s'>Click here</a> to return to the login form." % ( email, web.url_for( action='login' ) )
+ except Exception, e:
+ message = 'Failed to reset password: %s' % str( e )
status = 'error'
- reset_user.set_password_cleartext( new_pass )
- trans.sa_session.add( reset_user )
- trans.sa_session.flush()
- trans.log_event( "User reset password: %s" % email )
- message = "Password has been reset and emailed to: %s. <a href='%s'>Click here</a> to return to the login form." % ( email, web.url_for( action='login' ) )
return trans.response.send_redirect( web.url_for( controller='user',
action='reset_password',
message=message,
- status='done' ) )
+ status=status ) )
elif email != None:
message = "The specified user does not exist"
status = 'error'
--- a/templates/user/register.mako
+++ b/templates/user/register.mako
@@ -72,13 +72,15 @@
letters, numbers, and the '-' character.
</div></div>
- <div class="form-row">
- <label>Subscribe to mailing list:</label>
- %if subscribe_checked:
- <% subscribe_check_box.checked = True %>
- %endif
- ${subscribe_check_box.get_html()}
- </div>
+ %if trans.app.config.smtp_server:
+ <div class="form-row">
+ <label>Subscribe to mailing list:</label>
+ %if subscribe_checked:
+ <% subscribe_check_box.checked = True %>
+ %endif
+ ${subscribe_check_box.get_html()}
+ </div>
+ %endif
%if user_info_select:
<div class="form-row"><label>User type</label>
--- a/lib/galaxy/web/controllers/requests_admin.py
+++ b/lib/galaxy/web/controllers/requests_admin.py
@@ -4,9 +4,10 @@ from galaxy.model.orm import *
from galaxy.datatypes import sniff
from galaxy import model, util
from galaxy.util.streamball import StreamBall
-import logging, tempfile, zipfile, tarfile, os, sys, subprocess
+import logging, tempfile, zipfile, tarfile, os, sys, subprocess, smtplib, socket
from galaxy.web.form_builder import *
from datetime import datetime, timedelta
+from email.MIMEText import MIMEText
from galaxy.web.controllers.forms import get_all_forms
from sqlalchemy.sql.expression import func, and_
from sqlalchemy.sql import select
@@ -1403,13 +1404,21 @@ class RequestsAdmin( BaseController ):
trans.sa_session.flush()
# now that the request is complete send the email notification to the
# the user
- if request.notify:
- mail = os.popen("%s -t" % trans.app.config.sendmail_path, 'w')
- subject = "Galaxy Sample Tracking: '%s' sequencing request in complete." % request.name
- body = "The '%s' sequencing request (type: %s) is now complete. Datasets from all the samples are now available for analysis or download from the respective data libraries in Galaxy." % (request.name, request.type.name)
- email_content = "To: %s\nFrom: no-reply(a)nowhere.edu\nSubject: %s\n\n%s" % (request.user.email, subject, body)
- mail.write( email_content )
- x = mail.close()
+ if request.notify and trans.app.config.smtp_server is not None:
+ host = trans.request.host.split(':')[0]
+ if host == 'localhost':
+ host = socket.getfqdn()
+ msg = MIMEText( "The '%s' sequencing request (type: %s) is now complete. Datasets from all the samples are now available for analysis or download from the respective data libraries in Galaxy." % ( request.name, request.type.name ) )
+ to = msg[ 'To' ] = request.user.email
+ frm = msg[ 'From' ] = 'galaxy-no-reply@' + host
+ msg[ 'Subject' ] = "Galaxy Sample Tracking: '%s' sequencing request in complete." % request.name
+ try:
+ s = smtplib.SMTP()
+ s.connect( trans.app.config.smtp_server )
+ s.sendmail( frm, [ to ], msg.as_string() )
+ s.close()
+ except:
+ pass
def change_state(self, trans, sample):
possible_states = sample.request.type.states
curr_state = sample.current_state()
1
0

galaxy-dist commit 47411bb09df4: Remove the display application definitions for the bx application.
by commits-noreply@bitbucket.org 29 Jun '10
by commits-noreply@bitbucket.org 29 Jun '10
29 Jun '10
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>
404 - Not Found — bitbucket.org
</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="description" content="Mercurial hosting - we're here to serve." />
<meta name="keywords" content="mercurial,hg,hosting,bitbucket," />
<link rel="stylesheet" type="text/css" href="http://bitbucket-assets.s3.amazonaws.com/css/layout.css" />
<link rel="stylesheet" type="text/css" href="http://bitbucket-assets.s3.amazonaws.com/css/screen.css" />
<link rel="stylesheet" type="text/css" href="http://bitbucket-assets.s3.amazonaws.com/css/print.css" media="print" />
<link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="Bitbucket" />
<link rel="icon" href="http://bitbucket-assets.s3.amazonaws.com/img/logo_new.png" type="image/png"/>
<script type="text/javascript">var MEDIA_URL = "http://bitbucket-assets.s3.amazonaws.com/"</script>
<script type="text/javascript" src="http://bitbucket-assets.s3.amazonaws.com/js/lib/bundle.020510May.js"></script>
<script type="text/javascript">
$(document).ready(function() {
Dropdown.init();
$(".tooltip").tipsy({gravity:'s'});
});
</script>
<noscript>
<style type="text/css">
.dropdown-container-text .dropdown {
position: static !important;
}
</style>
</noscript>
<!--[if lt IE 7]>
<style type="text/css">
body {
behavior: url(http://bitbucket-assets.s3.amazonaws.com/css/csshover.htc);
}
#issues-issue pre {
white-space: normal !important;
}
.changeset-description {
white-space: normal !important;
}
</style>
<script type="text/javascript">
$(document).ready(function(){
$('#header-wrapper').pngFix();
$('#sourcelist').pngFix();
$('.promo-signup-screenshot').pngFix();
});
</script>
<![endif]-->
</head>
<body class="">
<div id="main-wrapper">
<div id="header-wrapper">
<div id="header">
<a href="/"><img src="http://bitbucket-assets.s3.amazonaws.com/img/logo_myriad.png" alt="Bitbucket" id="header-wrapper-logo" /></a>
<div id="header-nav">
<ul class="right">
<li><a href="/">Home</a></li>
<li><a href="/plans"><b>Plans & Signup</b></a></li>
<li><a href="/repo/all">Repositories</a></li>
<li><a href="/news">News</a></li>
<li><a href="/help">Help</a></li>
<li><a href="/account/signin/">Sign in</a></li>
</ul>
</div>
</div>
</div>
<div id="content-wrapper">
<div class="container">
<h2>404 - Not Found</h2>
<p>We're sorry, but we couldn't find the page you were looking for.</p>
<p>You can perhaps find what you're looking for by doing a search in the list of <a href="/repo/all/">public repositories</a>.</p>
<p>If you feel that this is an error on our part, please <a href="mailto:support@bitbucket.org">send us an email</a> with details about the error and what you were doing when it occurred.</p>
<p>-- The Bitbucket team</p>
</div>
<div class="cb"></div>
</div>
<div class="cb footer-placeholder"></div>
</div>
<div id="footer-wrapper">
<div id="footer">
<a href="/site/terms/">TOS</a> | <a href="/site/privacy/">Privacy Policy</a> | <a href="http://blog.bitbucket.org/">Blog</a> | <a href="http://bitbucket.org/jespern/bitbucket/issues/new/">Report Bug</a> | <a href="http://groups.google.com/group/bitbucket-users">Discuss</a> | <a href="http://avantlumiere.com/">© 2008-2010</a>
| We run <small><b>
<a href="http://www.djangoproject.com/">Django 1.2.1</a> /
<a href="http://bitbucket.org/jespern/django-piston/">Piston 0.2.3rc1</a> /
<a href="http://www.selenic.com/mercurial/">Hg 1.3.1</a> /
<a href="http://www.python.org">Python 2.5.2</a> /
r3056| fe02
</b></small>
</div>
</div>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-2456069-3'], ['_trackPageview']);
var _gaq = _gaq || [];
_gaq.push(['atl._setAccount', 'UA-6032469-33'], ['atl._trackPageview']);
(function() {
var ga = document.createElement('script');
ga.src = ('https:' == document.location.protocol ? 'https://ssl' :
'http://www') + '.google-analytics.com/ga.js';
ga.setAttribute('async', 'true');
document.documentElement.firstChild.appendChild(ga);
})();
</script>
</body>
</html>
1
0

galaxy-dist commit 067a8649dae7: Remove code and drop database tables related to the old Galaxy Cloud functionality.
by commits-noreply@bitbucket.org 29 Jun '10
by commits-noreply@bitbucket.org 29 Jun '10
29 Jun '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Enis Afgan <afgane(a)gmail.com>
# Date 1277306301 14400
# Node ID 067a8649dae79ab973ff3821b62be89e21a0117a
# Parent 752cb3e325374381bdd0db6b9163b12ba88b7151
Remove code and drop database tables related to the old Galaxy Cloud functionality.
--- a/templates/cloud/edit_image.mako
+++ /dev/null
@@ -1,92 +0,0 @@
-<% _=n_ %>
-<%inherit file="/base.mako"/>
-<%def name="title()">Edit machine image</%def>
-
-<%def name="javascripts()">
-${parent.javascripts()}
-<script type="text/javascript">
-$(function(){
- $("input:text:first").focus();
-})
-</script>
-</%def>
-
-%if header:
- ${header}
-%endif
-
-%if image:
-
-<div class="form">
- <div class="form-title">Edit image</div>
- <div class="form-body">
- <form name="edit_image" action="${h.url_for( action='edit_image', id=trans.security.encode_id(image.id), edited="true" )}" method="post" >
- <%
- cls = "form-row"
- if error.has_key('provider_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Provider type:</label>
- <div class="form-row-input">
- ${image.provider_type}
- </div>
- %if error.has_key('provider_error'):
- <div class="form-row-error-message">${error['provider_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
- <%
- cls = "form-row"
- if error.has_key('id_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Machine Image ID (AMI or EMI):</label>
- <div class="form-row-input">
- <input type="text" name="image_id" value="${image.image_id}" size="40">
- </div>
- %if error.has_key('id_error'):
- <div class="form-row-error-message">${error['id_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
- <%
- cls = "form-row"
- if error.has_key('manifest_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Manifest:</label>
- <div class="form-row-input">
- <input type="text" name="manifest" value="${image.manifest}" size="40">
- </div>
- %if error.has_key('manifest_error'):
- <div class="form-row-error-message">${error['manifest_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
- <%
- cls = "form-row"
- if error.has_key('arch_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Architecture:</label>
- <div class="form-row-input">
- <input type="text" name="architecture" value="${image.architecture}" size="40">
- </div>
- %if error.has_key('arch_error'):
- <div class="form-row-error-message">${error['arch_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <div class="form-row"><input type="submit" value="Save"></div>
- </form>
- </div>
-</div>
-
-%else:
- Specified machine image could not be found.
-%endif
--- a/templates/cloud/view_provider.mako
+++ /dev/null
@@ -1,126 +0,0 @@
-<%inherit file="/base.mako"/>
-
-<%def name="title()">Cloud provider</%def>
-
-<h2>Cloud provider details</h2>
-
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( action='list' )}">
- <img src="${h.url_for('/static/images/silk/resultset_previous.png')}" />
- <span>Return to cloud management console</span>
- </a>
- </li>
-</ul>
-
-%if provider:
- ${view_provider( provider )}
-%else:
- There is no cloud provider under that name.
-%endif
-
-
-
-
-<%def name="view_provider( provider )">
- <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
- <tr>
- <td> Cloud provider name: </td>
- <td>
- ${provider.name}
- <a id="cp-popup" class="popup-arrow" style="display: none;">▼</a>
- </td>
- <td>
- <div popupmenu="cp-popup">
- <a class="action-button" href="${h.url_for( action='edit_provider', id=trans.security.encode_id(provider.id) )}">Edit</a>
- <a class="action-button" confirm="Are you sure you want to delete cloud provider '${provider.name}'?" href="${h.url_for( action='delete_provider', id=trans.security.encode_id(provider.id) )}">Delete</a>
- </div>
- </td>
- </tr>
- <tr>
- <td> Last updated: </td>
- <td> ${str(provider.update_time)[:16]}
- <%
- context.write( ' UTC (' )
- context.write( str(h.date.distance_of_time_in_words (provider.update_time, h.date.datetime.utcnow() ) ) )
- %> ago)
- </td>
- </tr>
- <tr>
- <td> Cloud provider type: </td>
- <td> ${str(provider.type)[:16]}</td>
- </tr>
- %if provider.region_connection != None:
- <tr>
- <td> Region connection: </td>
- <td> ${provider.region_connection} </td>
- </tr>
- %endif
- %if provider.region_name != None:
- <tr>
- <td> Region name: </td>
- <td> ${provider.region_name} </td>
- </tr>
- %endif
- %if provider.region_endpoint != None:
- <tr>
- <td> Region endpoint: </td>
- <td> ${provider.region_endpoint} </td>
- </tr>
- %endif
- %if provider.is_secure != None:
- <tr>
- <td> Is secure: </td>
- <td> ${provider.is_secure} </td>
- </tr>
- %endif
- %if provider.host != None:
- <tr>
- <td> Host: </td>
- <td> ${provider.host} </td>
- </tr>
- %endif
- %if provider.port != None:
- <tr>
- <td> Port: </td>
- <td> ${provider.port} </td>
- </tr>
- %endif
- %if provider.proxy != None:
- <tr>
- <td> Proxy: </td>
- <td> ${provider.proxy} </td>
- </tr>
- %endif
- %if provider.proxy_port != None:
- <tr>
- <td> Proxy port: </td>
- <td> ${provider.proxy_port} </td>
- </tr>
- %endif
- %if provider.proxy_pass != None:
- <tr>
- <td> Proxy pass: </td>
- <td> ${provider.proxy_pass} </td>
- </tr>
- %endif
- %if provider.debug != None:
- <tr>
- <td> Debug: </td>
- <td> ${provider.debug} </td>
- </tr>
- %endif
- %if provider.https_connection_factory != None:
- <tr>
- <td> HTTPS connection factory: </td>
- <td> ${provider.https_connection_factory} </td>
- </tr>
- %endif
- %if provider.path != None:
- <tr>
- <td> Path: </td>
- <td> ${provider.path} </td>
- </tr>
- %endif
- </table>
-</%def>
--- a/templates/cloud/edit_credentials.mako
+++ /dev/null
@@ -1,91 +0,0 @@
-<% _=n_ %>
-<%inherit file="/base.mako"/>
-<%def name="title()">Add credentials</%def>
-
-<%def name="javascripts()">
-${parent.javascripts()}
-<script type="text/javascript">
-$(function(){
- $("input:text:first").focus();
-})
-</script>
-</%def>
-
-%if header:
- ${header}
-%endif
-
-%if credential:
-
-<div class="form">
- <div class="form-title">Edit credentials</div>
- <div class="form-body">
- <form name="edit_credentials" action="${h.url_for( action='edit_credentials', id=trans.security.encode_id(credential.id), edited="true" )}" method="post" >
-
- <%
- cls = "form-row"
- if error.has_key('cred_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Credentials name:</label>
- <div class="form-row-input">
- <input type="text" name="credName" value="${credential.name}" size="40">
- </div>
- %if error.has_key('cred_error'):
- <div class="form-row-error-message">${error['cred_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
-
- <%
- cls = "form-row"
- if error.has_key('provider_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Cloud provider name (type):</label>
- <div class="form-row-input">${credential.provider.name} (${credential.provider.type})</div>
- <div style="clear: both"></div>
- </div>
- <%
- cls = "form-row"
- if error.has_key('access_key_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Access key:</label>
- <div class="form-row-input">
- <input type="text" name="accessKey" value="${credential.access_key}" size="40">
- </div>
- %if error.has_key('access_key_error'):
- <div class="form-row-error-message">${error['access_key_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- if error.has_key('secret_key_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Secret key:</label>
- <div class="form-row-input">
- <input type="password" name="secretKey" value="${credential.secret_key}" size="40">
- </div>
- %if error.has_key('secret_key_error'):
- <div class="form-row-error-message">${error['secret_key_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <div class="form-row"><input type="submit" value="Edit"></div>
- </form>
- </div>
-</div>
-
-%else:
- Specified credentials could not be found.
-%endif
--- a/templates/cloud/edit_provider.mako
+++ /dev/null
@@ -1,261 +0,0 @@
-<% _=n_ %>
-<%inherit file="/base.mako"/>
-<%def name="title()">Edit provider</%def>
-
-<%def name="javascripts()">
-${parent.javascripts()}
-<script type="text/javascript">
-$(function(){
-
- $("input:text:first").focus();
-
- $("#type").change(function() {
- if ($(this).val() == 'ec2') {
- clear();
- $("#autofill").attr( 'disabled', true );
- $("#autofill").attr( 'checked', false );
- $("#name").val( "EC2" );
- $("#region_name").val( "us-east-1" );
- $("#region_endpoint").val( "us-east-1.ec2.amazonaws.com" );
- $("#is_secure").val("1");
- $("#debug").val("");
- $("#path").val("/");
- }
- else if ($(this).val() == 'eucalyptus') {
- clear();
- $("#autofill").attr( 'disabled', false );
- }
- });
-})
-
-
-function af(){
-
- if ( $("#autofill_epc").attr('checked') ) {
- $("#region_name").val("eucalyptus");
- $("#region_endpoint").val("mayhem9.cs.ucsb.edu");
- $("#is_secure").val("0");
- $("#port").val("8773");
- $("#path").val("/services/Eucalyptus");
- }
- else if ( $("#autofill_ec2").attr('checked') ) {
- $("#region_name").val( "us-east-1" );
- $("#region_endpoint").val( "us-east-1.ec2.amazonaws.com" );
- $("#is_secure").val("1");
- $("#debug").val("");
- $("#path").val("/");
- }
-}
-
-function clear() {
- //$("#name").val("");
- $("#region_name").val("");
- $("#region_endpoint").val("");
- $("#is_secure").val("");
- $("#port").val("");
- $("#proxy").val("");
- $("#proxy_port").val("");
- $("#proxy_user").val("");
- $("#proxy_pass").val("");
- $("#debug").val("");
- $("#https_connection_factory").val("");
- $("#path").val("");
-
-}
-
-</script>
-</%def>
-
-%if header:
- ${header}
-%endif
-
-%if provider:
- <div class="form">
- <div class="form-title">Edit cloud provider</div>
- <div class="form-body">
- <form name="edit_provider_form" action="${h.url_for( action='edit_provider', id=trans.security.encode_id(provider.id), edited="true" )}" method="post" >
- <%
- cls = "form-row"
- if error.has_key('type_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Provider type:</label>
- <div class="form-row-input">${provider.type}
- %if provider.type == 'eucalyptus':
- <p><input type="checkbox" id="autofill_epc" onclick="javascript:af()">
- auto fill using Eucalyptus Public Cloud values
- </p></div>
- %elif provider.type == 'ec2':
- <p><input type="checkbox" id="autofill_ec2" onclick="javascript:af()">
- auto fill for Amazon EC2 (us-east-1 region)
- </p></div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- if error.has_key('name_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Provider name:</label>
- <div class="form-row-input">
- <input type="text" id="name" name="name" value="${provider.name}" size="40">
- </div>
- %if error.has_key('name_error'):
- <div class="form-row-error-message">${error['name_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Region name:</label>
- <div id="region_selection" class="form-row-input">
- <input type="text" name="region_name" id="region_name" value="${provider.region_name}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Region endpoint:</label>
- <div class="form-row-input">
- <input type="text" name="region_endpoint" id="region_endpoint" value="${provider.region_endpoint}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- if error.has_key('is_secure_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Is secure ('O' for False or '1' for True):</label>
- <div class="form-row-input">
- %if provider.is_secure == True:
- <input type="text" name="is_secure" id="is_secure" value="1" size="40">
- %else:
- <input type="text" name="is_secure" id="is_secure" value="0" size="40">
- %endif
- </div>
- %if error.has_key('is_secure_error'):
- <div class="form-row-error-message">${error['is_secure_error']}; you entered: '${provider.is_secure}'</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Host:</label>
- <div class="form-row-input">
- <input type="text" name="host" value="${provider.host}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Port:</label>
- <div class="form-row-input">
- <input type="text" name="port" id="port" value="${provider.port}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Proxy:</label>
- <div class="form-row-input">
- <input type="text" name="proxy" value="${provider.proxy}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Proxy port:</label>
- <div class="form-row-input">
- <input type="text" name="proxy_port" value="${provider.proxy_port}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Proxy user:</label>
- <div class="form-row-input">
- <input type="text" name="proxy_user" value="${provider.proxy_user}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Proxy pass:</label>
- <div class="form-row-input">
- <input type="text" name="proxy_pass" value="${provider.proxy_pass}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Debug:</label>
- <div class="form-row-input">
- <input type="text" name="debug" value="${provider.debug}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>HTTPS connection factory:</label>
- <div class="form-row-input">
- <input type="text" name="https_connection_factory" value="${provider.https_connection_factory}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Path:</label>
- <div class="form-row-input">
- <input type="text" name="path" id="path" value="${provider.path}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <div class="form-row"><input type="submit" value="Save"></div>
-
- </form>
-
- </div>
- </div>
-%endif
--- a/templates/cloud/view_usage.mako
+++ /dev/null
@@ -1,117 +0,0 @@
-<%inherit file="/base.mako"/>
-
-<%def name="title()">Cloud home</%def>
-
-%if message:
-<%
- try:
- messagetype
- except:
- messagetype = "done"
-%>
-
-
-
-<p />
-<div class="${messagetype}message">
- ${message}
-</div>
-%endif
-
-%if prevInstances:
- <h2>Usage report for instance ${prevInstances[0].uci.name}</h2>
-%else:
- <h2>Selected instance has no record of being used.</h2>
-%endif
-
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( action='list' )}">
- <img src="${h.url_for('/static/images/silk/resultset_previous.png')}" />
- <span>Return to cloud management console</span>
- </a>
- </li>
-</ul>
-
-%if prevInstances:
- <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
- <colgroup width="2%"></colgroup>
- <colgroup width="16%"></colgroup>
- <colgroup width="16%"></colgroup>
- <colgroup width="10%"></colgroup>
- <colgroup width="5%"></colgroup>
- <tr class="header">
- <th>#</th>
- <th>Launch time</th>
- <th>Termination time</th>
- <th>Time alive</th>
- <th>Type</th>
- <th></th>
- </tr>
- <%
- total_hours = 0
- %>
- %for i, prevInstance in enumerate( prevInstances ):
- <tr>
- <td>${i+1}</td>
- <td>
- %if prevInstance.launch_time:
- ${str(prevInstance.launch_time)[:16]} UCT
- %else:
- N/A
- %endif
- </td>
- <td>
- %if prevInstance.stop_time:
- ${str(prevInstance.stop_time)[:16]} UCT
- %else:
- N/A
- %endif
- </td>
- <td>
- <%
- # This is where current time and since duration is calculated
- if prevInstance.launch_time is None or prevInstance.stop_time is None:
- context.write( 'N/A' )
- else:
- context.write( str(h.date.distance_of_time_in_words (prevInstance.launch_time, prevInstance.stop_time ) ) )
- time_delta = prevInstance.stop_time - prevInstance.launch_time
- total_hours += time_delta.seconds / 3600
- if time_delta.seconds != 0:
- total_hours += 1
-
- %>
- </td>
- <td>${prevInstance.type}</td>
- </tr>
- %endfor
- </table>
- <br/>Total number of hours instance was alive: ${total_hours} <br />
- Note that these are just best effort estimates - true usage should be obtained from respective cloud provider. <br />
- <%namespace name="view_cred" file="view_credentials.mako" />
-
- <div id="hide_cred_details">
- This instance uses credentials:
- <a onclick="document.getElementById('show_cred_details').style.display = 'block';
- document.getElementById('hide_cred_details').style.display = 'none'; return 0"
- href="javascript:void(0)">
- ${prevInstances[0].uci.credentials.name}
- </a>
- </div>
- <div id="show_cred_details" style="DISPLAY: none">
- This instance uses credentials:
- <a onclick="document.getElementById('hide_cred_details').style.display = 'block';
- document.getElementById('show_cred_details').style.display = 'none'; return 0;"
- href="javascript:void(0)">
- ${prevInstances[0].uci.credentials.name}
- </a>
- ${view_cred.view_cred( prevInstances[0].uci.credentials ) }
- </div>
-
-
-%endif
-
-
-
-
-
--- a/templates/cloud/view_credentials.mako
+++ /dev/null
@@ -1,157 +0,0 @@
-<%inherit file="/base.mako"/>
-
-<%def name="title()">Cloud credentials</%def>
-
-<h2>Credentials details</h2>
-
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( action='list' )}">
- <img src="${h.url_for('/static/images/silk/resultset_previous.png')}" />
- <span>Return to cloud management console</span>
- </a>
- </li>
-</ul>
-
-%if credDetails:
- ${view_cred( credDetails )}
-%else:
- There are no credentials under that name.
-%endif
-
-
-
-
-<%def name="view_cred( credDetails )">
- <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
- <tr>
- <td> Credentials name: </td>
- <td>
- ${credDetails.name}
- <a id="wf-popup" class="popup-arrow" style="display: none;">▼</a>
- </td>
- <td>
- <div popupmenu="wf-popup">
- <a class="action-button" href="${h.url_for( action='edit_credentials', id=trans.security.encode_id(credDetails.id) )}">Edit</a>
- <a class="action-button" confirm="Are you sure you want to delete credentials '${credDetails.name}'?" href="${h.url_for( action='delete_credentials', id=trans.security.encode_id(credDetails.id) )}">Delete</a>
- </div>
- </td>
- </tr>
- <tr>
- <td> Last updated: </td>
- <td> ${str(credDetails.update_time)[:16]}
- <%
- context.write( ' UTC (' )
- context.write( str(h.date.distance_of_time_in_words (credDetails.update_time, h.date.datetime.utcnow() ) ) )
- %> ago)
- </td>
- </tr>
- <tr>
- <td> Cloud provider type: </td>
- <td> ${str(credDetails.provider.type)}</td>
- </tr>
- <tr>
- <td> Cloud provider name: </td>
- <td> ${str(credDetails.provider.name)}</td>
- </tr>
- <tr>
- <td> Access key: </td>
- <td>
- ${credDetails.access_key}
- </td>
- </tr>
- <tr>
- <td> Secret key: </td>
- <td>
- <div id="shortComment2">
- <a onclick="document.getElementById('fullComment2').style.display = 'block';
- document.getElementById('shortComment2').style.display = 'none'; return 0"
- href="javascript:void(0)">
- + Show
- </a>
- </div>
- <div id="fullComment2" style="DISPLAY: none">
- <a onclick="document.getElementById('shortComment2').style.display = 'block';
- document.getElementById('fullComment2').style.display = 'none'; return 0;"
- href="javascript:void(0)">
- - Hide
- </a><br />
- <nobr>${credDetails.secret_key}</nobr><br/>
- </div>
- </td>
- </tr>
- <tr><td id="addl"><b>Additional cloud provider information (if available):</b></td></tr>
- %if credDetails.provider.region_connection != None:
- <tr>
- <td> Region connection: </td>
- <td> ${credDetails.provider.region_connection} </td>
- </tr>
- %endif
- %if credDetails.provider.region_name != None:
- <tr>
- <td> Region name: </td>
- <td> ${credDetails.provider.region_name} </td>
- </tr>
- %endif
- %if credDetails.provider.region_endpoint != None:
- <tr>
- <td> Region endpoint: </td>
- <td> ${credDetails.provider.region_endpoint} </td>
- </tr>
- %endif
- %if credDetails.provider.is_secure != None:
- <tr>
- <td> Is secure: </td>
- <td> ${credDetails.provider.is_secure} </td>
- </tr>
- %endif
- %if credDetails.provider.host != None:
- <tr>
- <td> Host: </td>
- <td> ${credDetails.provider.host} </td>
- </tr>
- %endif
- %if credDetails.provider.port != None:
- <tr>
- <td> Port: </td>
- <td> ${credDetails.provider.port} </td>
- </tr>
- %endif
- %if credDetails.provider.proxy != None:
- <tr>
- <td> Proxy: </td>
- <td> ${credDetails.provider.proxy} </td>
- </tr>
- %endif
- %if credDetails.provider.proxy_port != None:
- <tr>
- <td> Proxy port: </td>
- <td> ${credDetails.provider.proxy_port} </td>
- </tr>
- %endif
- %if credDetails.provider.proxy_pass != None:
- <tr>
- <td> Proxy pass: </td>
- <td> ${credDetails.provider.proxy_pass} </td>
- </tr>
- %endif
- %if credDetails.provider.debug != None:
- <tr>
- <td> Debug: </td>
- <td> ${credDetails.provider.debug} </td>
- </tr>
- %endif
- %if credDetails.provider.https_connection_factory != None:
- <tr>
- <td> HTTPS connection factory: </td>
- <td> ${credDetails.provider.https_connection_factory} </td>
- </tr>
- %endif
- %if credDetails.provider.path != None:
- <tr>
- <td> Path: </td>
- <td> ${credDetails.provider.path} </td>
- </tr>
- %endif
- </table>
-</%def>
--- a/lib/galaxy/cloud/providers/eucalyptus.py
+++ /dev/null
@@ -1,1039 +0,0 @@
-import subprocess, threading, os, errno, time, datetime
-from Queue import Queue, Empty
-from datetime import datetime
-
-from galaxy import model # Database interaction class
-from galaxy.model import mapping
-from galaxy.datatypes.data import nice_size
-from galaxy.util.bunch import Bunch
-from galaxy.cloud import UCIwrapper
-from Queue import Queue
-from sqlalchemy import or_, and_
-
-import galaxy.eggs
-galaxy.eggs.require("boto")
-from boto.ec2.connection import EC2Connection
-from boto.ec2.regioninfo import RegionInfo
-import boto.exception
-import boto
-
-import logging
-log = logging.getLogger( __name__ )
-
-uci_states = Bunch(
- NEW_UCI = "newUCI",
- NEW = "new",
- CREATING = "creating",
- DELETING_UCI = "deletingUCI",
- DELETING = "deleting",
- SUBMITTED_UCI = "submittedUCI",
- SUBMITTED = "submitted",
- SHUTTING_DOWN_UCI = "shutting-downUCI",
- SHUTTING_DOWN = "shutting-down",
- ADD_STORAGE_UCI = "add-storageUCI",
- ADD_STORAGE = "add-storage",
- AVAILABLE = "available",
- RUNNING = "running",
- PENDING = "pending",
- ERROR = "error",
- DELETED = "deleted",
- SNAPSHOT_UCI = "snapshotUCI",
- SNAPSHOT = "snapshot"
-)
-
-instance_states = Bunch(
- TERMINATED = "terminated",
- SUBMITTED = "submitted",
- RUNNING = "running",
- ADDING = "adding-storage",
- PENDING = "pending",
- SHUTTING_DOWN = "shutting-down",
- ERROR = "error"
-)
-
-store_status = Bunch(
- WAITING = "waiting",
- IN_USE = "in-use",
- ADDING = "adding",
- CREATING = "creating",
- DELETED = 'deleted',
- ERROR = "error"
-)
-
-snapshot_status = Bunch(
- SUBMITTED = 'submitted',
- PENDING = 'pending',
- COMPLETED = 'completed',
- DELETE = 'delete',
- DELETED= 'deleted',
- ERROR = "error"
-)
-
-class EucalyptusCloudProvider( object ):
- """
- Eucalyptus-based cloud provider implementation for managing instances.
- """
- STOP_SIGNAL = object()
- def __init__( self, app ):
- self.type = "eucalyptus" # cloud provider type (e.g., ec2, eucalyptus, opennebula)
- self.zone = "epc"
- self.queue = Queue()
- self.sa_session = app.model.context
-
- self.threads = []
- nworkers = 5
- log.info( "Starting eucalyptus cloud controller workers..." )
- for i in range( nworkers ):
- worker = threading.Thread( target=self.run_next )
- worker.start()
- self.threads.append( worker )
- log.debug( "%d eucalyptus cloud workers ready", nworkers )
-
- def shutdown( self ):
- """Attempts to gracefully shut down the monitor thread"""
- log.info( "sending stop signal to worker threads in eucalyptus cloud manager" )
- for i in range( len( self.threads ) ):
- self.queue.put( self.STOP_SIGNAL )
- log.info( "eucalyptus cloud manager stopped" )
-
- def put( self, uci_wrapper ):
- """
- Add uci_wrapper object to the end of the request queue to be handled by
- this cloud provider.
- """
- state = uci_wrapper.get_uci_state()
- uci_wrapper.change_state( state.split('U')[0] ) # remove 'UCI' from end of state description (i.e., mark as accepted and ready for processing)
- self.queue.put( uci_wrapper )
-
- def run_next( self ):
- """Process next request, waiting until one is available if necessary."""
- cnt = 0
- while 1:
- uci_wrapper = self.queue.get()
- uci_state = uci_wrapper.get_uci_state()
- if uci_state is self.STOP_SIGNAL:
- return
- try:
- if uci_state==uci_states.NEW:
- self.create_uci( uci_wrapper )
- elif uci_state==uci_states.DELETING:
- self.delete_uci( uci_wrapper )
- elif uci_state==uci_states.SUBMITTED:
- self.start_uci( uci_wrapper )
- #self.dummy_start_uci( uci_wrapper )
- elif uci_state==uci_states.SHUTTING_DOWN:
- self.stop_uci( uci_wrapper )
- elif uci_state==uci_states.SNAPSHOT:
- self.snapshot_uci( uci_wrapper )
- elif uci_state==uci_states.ADD_STORAGE:
- self.add_storage_to_uci( uci_wrapper )
- except:
- log.exception( "Uncaught exception executing cloud request." )
- cnt += 1
-
- def get_connection( self, uci_wrapper ):
- """
- Establishes cloud connection using user's credentials associated with given UCI
- """
- log.debug( 'Establishing %s cloud connection.' % self.type )
- provider = uci_wrapper.get_provider()
- try:
- region = RegionInfo( None, provider.region_name, provider.region_endpoint )
- except Exception, ex:
- err = "Selecting region with cloud provider failed: " + str( ex )
- log.error( err )
- uci_wrapper.set_error( err, True )
- return None
- try:
- conn = EC2Connection( aws_access_key_id=uci_wrapper.get_access_key(),
- aws_secret_access_key=uci_wrapper.get_secret_key(),
- is_secure=provider.is_secure,
- port=provider.port,
- region=region,
- path=provider.path )
- except boto.exception.EC2ResponseError, e:
- err = "Establishing connection with cloud failed: " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
- return None
-
- return conn
-
- def check_key_pair( self, uci_wrapper, conn ):
- """
- Check if a key pair associated with this UCI exists on cloud provider.
- If yes, return key pair name; otherwise, generate a key pair with the cloud
- provider and, again, return key pair name.
- Key pair name for given UCI is generated from UCI's name and suffix '_kp'
- """
- kp = None
- kp_name = uci_wrapper.get_name().replace(' ','_') + "_kp"
- log.debug( "Checking user's key pair: '%s'" % kp_name )
- try:
- kp = conn.get_key_pair( kp_name )
- uci_kp_name = uci_wrapper.get_key_pair_name()
- uci_material = uci_wrapper.get_key_pair_material()
- if kp != None:
- if kp.name != uci_kp_name or uci_material == None:
- # key pair exists on the cloud but not in local database, so re-generate it (i.e., delete and then create)
- try:
- conn.delete_key_pair( kp_name )
- kp = self.create_key_pair( conn, kp_name )
- uci_wrapper.set_key_pair( kp.name, kp.material )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while deleting key pair: " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
- else:
- try:
- kp = self.create_key_pair( conn, kp_name )
- uci_wrapper.set_key_pair( kp.name, kp.material )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while creating key pair: " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
- except Exception, ex:
- err = "Exception while creating key pair: " + str( ex )
- log.error( err )
- uci_wrapper.set_error( err, True )
- except boto.exception.EC2ResponseError, e: # No keypair under this name exists so create it
- if e.code == 'InvalidKeyPair.NotFound':
- log.info( "No keypair found, creating keypair '%s'" % kp_name )
- kp = self.create_key_pair( conn, kp_name )
- uci_wrapper.set_key_pair( kp.name, kp.material )
- else:
- err = "EC2 response error while retrieving key pair: " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
-
- if kp != None:
- return kp.name
- else:
- return None
-
- def create_key_pair( self, conn, kp_name ):
- """ Initiate creation of key pair under kp_name by current cloud provider. """
- try:
- return conn.create_key_pair( kp_name )
- except boto.exception.EC2ResponseError, e:
- return None
-
- def get_mi_id( self, uci_wrapper, i_index ):
- """
- Get appropriate machine image (mi) ID based on instance type.
- """
- i_type = uci_wrapper.get_instance_type( i_index )
- if i_type=='m1.small' or i_type=='c1.medium':
- arch = 'i386'
- else:
- arch = 'x86_64'
-
- mi = self.sa_session.query( model.CloudImage ).filter_by( deleted=False, provider_type=self.type, architecture=arch ).first()
- if mi:
- return mi.image_id
- else:
- err = "Machine image could not be retrieved"
- log.error( "%s for UCI '%s'." % (err, uci_wrapper.get_name() ) )
- uci_wrapper.set_error( err+". Contact site administrator to ensure needed machine image is registered.", True )
- return None
-
- def create_uci( self, uci_wrapper ):
- """
- Create User Configured Instance (UCI) - i.e., create storage volume on cloud provider
- and register relevant information in local Galaxy database.
- """
- conn = self.get_connection( uci_wrapper )
-
- # Because only 1 storage volume may be created at UCI config time, index of this storage volume in local Galaxy DB w.r.t
- # current UCI is 0; therefore, it can be referenced in following code
- log.info( "Creating volume in zone '%s'..." % uci_wrapper.get_uci_availability_zone() )
- if uci_wrapper.get_uci_availability_zone()=='':
- log.info( "Availability zone for UCI (i.e., storage volume) was not selected, using default zone: %s" % self.zone )
- uci_wrapper.set_store_availability_zone( self.zone )
-
-# log.debug( "Creating volume; using command: conn.create_volume( %s, '%s', snapshot=None )" % ( uci_wrapper.get_store_size( 0 ), uci_wrapper.get_uci_availability_zone() ))
-# vol = conn.create_volume( uci_wrapper.get_store_size( 0 ), uci_wrapper.get_uci_availability_zone(), snapshot=None )
-# uci_wrapper.set_store_volume_id( 0, vol.id )
- store = uci_wrapper.get_all_stores_in_status( store_status.ADDING )[0] # Because at UCI creation time only 1 storage volume can be created, reference it directly
-
- log.info( "Creating storage volume in zone '%s' of size '%s'..." % ( uci_wrapper.get_uci_availability_zone(), store.size ) )
- # Because only 1 storage volume may be created at UCI config time, index of this storage volume in local Galaxy DB w.r.t
- # current UCI is 0, so reference it in following methods
- vol = conn.create_volume( store.size, uci_wrapper.get_uci_availability_zone(), snapshot=None )
- uci_wrapper.set_store_volume_id( store.id, vol.id )
-
- # Retrieve created volume again to get updated status
- try:
- vl = conn.get_all_volumes( [vol.id] )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while retrieving (i.e., updating status) of just created storage volume '" + vol.id + "': " + str( e )
- log.error( err )
- uci_wrapper.set_store_status( vol.id, uci_states.ERROR )
- uci_wrapper.set_error( err, True )
- return
- except Exception, ex:
- err = "Error while retrieving (i.e., updating status) of just created storage volume '" + vol.id + "': " + str( ex )
- log.error( err )
- uci_wrapper.set_error( err, True )
- return
-
- if len( vl ) > 0:
- # EPC does not allow creation of storage volumes (it deletes one as soon as it is created, so manually set uci_state here)
- if vl[0].status == store_status.DELETED:
- uci_wrapper.change_state( uci_state=uci_states.AVAILABLE )
- else:
- uci_wrapper.change_state( uci_state=vl[0].status )
- uci_wrapper.set_store_status( vol.id, vl[0].status )
- else:
- err = "Volume '" + vol.id +"' not found by EC2 after being created."
- log.error( err )
- uci_wrapper.set_store_status( vol.id, uci_states.ERROR )
- uci_wrapper.set_error( err, True )
-
- def delete_uci( self, uci_wrapper ):
- """
- Delete UCI - i.e., delete all storage volumes associated with this UCI.
- NOTE that this implies deletion of any and all data associated
- with this UCI from the cloud. All data will be deleted.
- Information in local Galaxy database is marked as deleted but not actually removed
- from the database.
- """
- conn = self.get_connection( uci_wrapper )
- vl = [] # volume list
- count = 0 # counter for checking if all volumes assoc. w/ UCI were deleted
-
- # Get all volumes assoc. w/ UCI, delete them from cloud as well as in local DB
- vl = uci_wrapper.get_all_stores()
- deletedList = []
- failedList = []
- for v in vl:
- log.debug( "Deleting volume with id='%s'" % v.volume_id )
- try:
- if conn.delete_volume( v.volume_id ):
- deletedList.append( v.volume_id )
- v.deleted = True
- self.sa_session.add( v )
- self.sa_session.flush()
- count += 1
- else:
- failedList.append( v.volume_id )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while deleting storage volume '" + v.volume_id + "': " + str( e )
- log.error( err )
- uci_wrapper.set_store_error( err, store_id = v.volume_id )
- uci_wrapper.set_error( err, True )
-
- # Delete UCI if all of associated
- if count == len( vl ):
- uci_wrapper.set_deleted()
- else:
- err = "Deleting following volume(s) failed: "+ str( failedList )+". However, these volumes were successfully deleted: " \
- + str( deletedList ) +". MANUAL intervention and processing needed."
- log.error( err )
- uci_wrapper.set_error( err, True )
-
- def snapshot_uci( self, uci_wrapper ):
- """
- Initiate creation of a snapshot by cloud provider for all storage volumes
- associated with this UCI.
- """
- if uci_wrapper.get_uci_state() != uci_states.ERROR:
- conn = self.get_connection( uci_wrapper )
-
- snapshots = uci_wrapper.get_snapshots( status = snapshot_status.SUBMITTED )
- for snapshot in snapshots:
- log.debug( "Snapshot DB id: '%s', volume id: '%s'" % ( snapshot.id, snapshot.store.volume_id ) )
- try:
- snap = conn.create_snapshot( volume_id=snapshot.store.volume_id )
- snap_id = str( snap ).split(':')[1]
- uci_wrapper.set_snapshot_id( snapshot.id, snap_id )
- sh = conn.get_all_snapshots( snap_id ) # get updated status
- uci_wrapper.set_snapshot_status( status=sh[0].status, snap_id=snap_id )
- except boto.exception.EC2ResponseError, e:
- err = "Cloud provider response error while creating snapshot: " + str( e )
- log.error( err )
- uci_wrapper.set_snapshot_error( error=err, snap_index=snapshot.id, set_status=True )
- uci_wrapper.set_error( err, True )
- return
- except Exception, ex:
- err = "Error while creating snapshot: " + str( ex )
- log.error( err )
- uci_wrapper.set_snapshot_error( error=err, snap_index=snapshot.id, set_status=True )
- uci_wrapper.set_error( err, True )
- return
-
- uci_wrapper.change_state( uci_state=uci_states.AVAILABLE )
-
-# if uci_wrapper.get_uci_state() != uci_states.ERROR:
-#
-# snapshots = uci_wrapper.get_snapshots( status = 'submitted' )
-# for snapshot in snapshots:
-# uci_wrapper.set_snapshot_id( snapshot.id, None, 'euca_error' )
-#
-# log.debug( "Eucalyptus snapshot attempted by user for UCI '%s'" % uci_wrapper.get_name() )
-# uci_wrapper.set_error( "Eucalyptus does not support creation of snapshots at this moment. No snapshot or other changes were performed. \
-# Feel free to resent state of this instance and use it normally.", True )
-
-
- def add_storage_to_uci( self, uci_wrapper ):
- """ Adds more storage to specified UCI """
- uci_wrapper.set_error( "Adding storage to eucalyptus-based clouds is not yet supported.", True )
-
- def dummy_start_uci( self, uci_wrapper ):
-
- uci = uci_wrapper.get_uci()
- log.debug( "Would be starting instance '%s'" % uci.name )
-# uci_wrapper.change_state( uci_states.SUBMITTED_UCI )
-# log.debug( "Set UCI state to SUBMITTED_UCI" )
- log.debug( "Sleeping a bit... (%s)" % uci.name )
- time.sleep(10)
- log.debug( "Woke up! (%s)" % uci.name )
-
- def start_uci( self, uci_wrapper ):
- """
- Start instance(s) of given UCI on the cloud.
- """
- if uci_wrapper.get_uci_state() != uci_states.ERROR:
- conn = self.get_connection( uci_wrapper )
- self.check_key_pair( uci_wrapper, conn )
- if uci_wrapper.get_key_pair_name() == None:
- err = "Key pair not found"
- log.error( "%s for UCI '%s'." % ( err, uci_wrapper.get_name() ) )
- uci_wrapper.set_error( err + ". Try resetting the state and starting the instance again.", True )
- return
-
- i_indexes = uci_wrapper.get_instances_indexes( state=instance_states.SUBMITTED ) # Get indexes of i_indexes associated with this UCI that are in 'submitted' state
- log.debug( "Starting instances with IDs: '%s' associated with UCI '%s' " % ( i_indexes, uci_wrapper.get_name(), ) )
- if len( i_indexes ) > 0:
- for i_index in i_indexes:
- # Get machine image for current instance
- mi_id = self.get_mi_id( uci_wrapper, i_index )
- log.debug( "mi_id: %s, uci_wrapper.get_key_pair_name(): %s" % ( mi_id, uci_wrapper.get_key_pair_name() ) )
- uci_wrapper.set_mi( i_index, mi_id )
-
- if uci_wrapper.get_uci_state() != uci_states.ERROR:
- # Start an instance
- log.debug( "Starting UCI instance '%s'" % uci_wrapper.get_name() )
- log.debug( "Using following command: conn.run_instances( image_id='%s', key_name='%s', instance_type='%s' )"
- % ( mi_id, uci_wrapper.get_key_pair_name(), uci_wrapper.get_instance_type( i_index ) ) )
- reservation = None
- try:
- reservation = conn.run_instances( image_id=mi_id,
- key_name=uci_wrapper.get_key_pair_name(),
- instance_type=uci_wrapper.get_instance_type( i_index ) )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error when starting UCI '"+ uci_wrapper.get_name() +"': " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
- except Exception, ex:
- err = "Error when starting UCI '" + uci_wrapper.get_name() + "': " + str( ex )
- log.error( err )
- uci_wrapper.set_error( err, True )
- # Record newly available instance data into local Galaxy database
- if reservation:
- l_time = datetime.utcnow()
-# uci_wrapper.set_instance_launch_time( self.format_time( reservation.instances[0].launch_time ), i_index=i_index )
- uci_wrapper.set_instance_launch_time( l_time, i_index=i_index )
- if not uci_wrapper.uci_launch_time_set():
- uci_wrapper.set_uci_launch_time( l_time )
- try:
- uci_wrapper.set_reservation_id( i_index, str( reservation ).split(":")[1] )
- # TODO: if more than a single instance will be started through single reservation, change this reference from element [0]
- i_id = str( reservation.instances[0]).split(":")[1]
- uci_wrapper.set_instance_id( i_index, i_id )
- s = reservation.instances[0].state
- uci_wrapper.change_state( s, i_id, s )
- vol_id = uci_wrapper.get_store_volume_id( store_id=0 ) # TODO: Once more that one vol/UCI is allowed, update this!
- uci_wrapper.set_store_status( vol_id, store_status.WAITING )
- log.debug( "Instance of UCI '%s' started, current state: '%s'" % ( uci_wrapper.get_name(), uci_wrapper.get_uci_state() ) )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error when retrieving instance information for UCI '" + uci_wrapper.get_name() + "': " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
- else:
- log.error( "UCI '%s' is in 'error' state, starting instance was aborted." % uci_wrapper.get_name() )
- else:
- err = "No instances in state '"+ instance_states.SUBMITTED +"' found for UCI '" + uci_wrapper.get_name() + \
- "'. Nothing to start."
- log.error( err )
- uci_wrapper.set_error( err, True )
- else:
- log.error( "UCI '%s' is in 'error' state, starting instance was aborted." % uci_wrapper.get_name() )
-
- def stop_uci( self, uci_wrapper):
- """
- Stop all cloud instances associated with given UCI.
- """
- conn = self.get_connection( uci_wrapper )
-
- # Get all instances associated with given UCI
- il = uci_wrapper.get_instances_ids() # instance list
- # Process list of instances and remove any references to empty instance id's
- for i in il:
- if i is None:
- il.remove( i )
- log.debug( 'List of instances being terminated: %s' % il )
- rl = conn.get_all_instances( il ) # Reservation list associated with given instances
-
- # Initiate shutdown of all instances under given UCI
- cnt = 0
- stopped = []
- not_stopped = []
- for r in rl:
- for inst in r.instances:
- log.debug( "Sending stop signal to instance '%s' associated with reservation '%s' (UCI: %s)." % ( inst, r, uci_wrapper.get_name() ) )
- try:
- inst.stop()
- uci_wrapper.set_stop_time( datetime.utcnow(), i_id=inst.id )
- uci_wrapper.change_state( instance_id=inst.id, i_state=inst.update() )
- stopped.append( inst )
- except boto.exception.EC2ResponseError, e:
- not_stopped.append( inst )
- err = "EC2 response error when stopping instance '" + inst.instance_id + "': " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
-
- uci_wrapper.reset_uci_launch_time()
- log.debug( "Termination was initiated for all instances of UCI '%s'." % uci_wrapper.get_name() )
-
-# dbInstances = get_instances( trans, uci ) #TODO: handle list!
-#
-# # Get actual cloud instance object
-# cloudInstance = get_cloud_instance( conn, dbInstances.instance_id )
-#
-# # TODO: Detach persistent storage volume(s) from instance and update volume data in local database
-# stores = get_stores( trans, uci )
-# for i, store in enumerate( stores ):
-# log.debug( "Detaching volume '%s' to instance '%s'." % ( store.volume_id, dbInstances.instance_id ) )
-# mntDevice = store.device
-# volStat = None
-## Detaching volume does not work with Eucalyptus Public Cloud, so comment it out
-## try:
-## volStat = conn.detach_volume( store.volume_id, dbInstances.instance_id, mntDevice )
-## except:
-## log.debug ( 'Error detaching volume; still going to try and stop instance %s.' % dbInstances.instance_id )
-# store.attach_time = None
-# store.device = None
-# store.inst.instance_id = None
-# store.status = volStat
-# log.debug ( '***** volume status: %s' % volStat )
-#
-# # Stop the instance and update status in local database
-# cloudInstance.stop()
-# dbInstances.stop_time = datetime.utcnow()
-# while cloudInstance.state != 'terminated':
-# log.debug( "Stopping instance %s state; current state: %s" % ( str( cloudInstance ).split(":")[1], cloudInstance.state ) )
-# time.sleep(3)
-# cloudInstance.update()
-# dbInstances.state = cloudInstance.state
-#
-# # Reset relevant UCI fields
-# uci.state = 'available'
-# uci.launch_time = None
-#
-# # Persist
-# session = trans.sa_session
-## session.save_or_update( stores )
-# session.save_or_update( dbInstances ) # TODO: Is this going to work w/ multiple instances stored in dbInstances variable?
-# session.save_or_update( uci )
-# session.flush()
-# trans.log_event( "User stopped cloud instance '%s'" % uci.name )
-# trans.set_message( "Galaxy instance '%s' stopped." % uci.name )
-
- def update( self ):
- """
- Run status update on all instances that are in 'running', 'pending', or 'shutting-down' state.
- Run status update on all storage volumes whose status is 'in-use', 'creating', or 'None'.
- Run status update on all snapshots whose status is 'pending' or 'delete'
- Run status update on any zombie UCIs, i.e., UCI's that is in 'submitted' state for an
- extended period of time.
-
- Reason behind this method is to sync state of local DB and real-world resources
- """
- log.debug( "Running general status update for %s UCIs..." % self.type )
- # Update instances
- instances = self.sa_session.query( model.CloudInstance ) \
- .filter( or_( model.CloudInstance.table.c.state==instance_states.RUNNING,
- model.CloudInstance.table.c.state==instance_states.PENDING,
- model.CloudInstance.table.c.state==instance_states.SHUTTING_DOWN ) ) \
- .all()
- for inst in instances:
- if self.type == inst.uci.credentials.provider.type:
- log.debug( "[%s] Running general status update on instance '%s'" % ( inst.uci.credentials.provider.type, inst.instance_id ) )
- self.update_instance( inst )
-
- # Update storage volume(s)
- stores = self.sa_session.query( model.CloudStore ) \
- .filter( or_( model.CloudStore.table.c.status==store_status.IN_USE,
- model.CloudStore.table.c.status==store_status.CREATING,
- model.CloudStore.table.c.status==store_status.WAITING,
- model.CloudStore.table.c.status==None ) ) \
- .all()
- for store in stores:
- if self.type == store.uci.credentials.provider.type: # and store.volume_id != None:
- log.debug( "[%s] Running general status update on store with local database ID: '%s'" % ( store.uci.credentials.provider.type, store.id ) )
- self.update_store( store )
-
- # Update pending snapshots or delete ones marked for deletion
- snapshots = self.sa_session.query( model.CloudSnapshot ) \
- .filter( or_( model.CloudSnapshot.table.c.status == snapshot_status.PENDING, model.CloudSnapshot.table.c.status == snapshot_status.DELETE ) ) \
- .all()
- for snapshot in snapshots:
- if self.type == snapshot.uci.credentials.provider.type and snapshot.status == snapshot_status.PENDING:
- log.debug( "[%s] Running general status update on snapshot '%s'" % ( snapshot.uci.credentials.provider.type, snapshot.snapshot_id ) )
- self.update_snapshot( snapshot )
- elif self.type == snapshot.uci.credentials.provider.type and snapshot.status == snapshot_status.DELETE:
- log.debug( "[%s] Initiating deletion of snapshot '%s'" % ( snapshot.uci.credentials.provider.type, snapshot.snapshot_id ) )
- self.delete_snapshot( snapshot )
-
- # Attempt at updating any zombie UCIs (i.e., instances that have been in SUBMITTED state for longer than expected - see below for exact time)
- zombies = self.sa_session.query( model.UCI ).filter_by( state=uci_states.SUBMITTED ).all()
- for zombie in zombies:
- log.debug( "zombie UCI: %s" % zombie.name )
- z_instances = self.sa_session.query( model.CloudInstance ) \
- .filter( or_( model.CloudInstance.table.c.state != instance_states.TERMINATED,
- model.CloudInstance.table.c.state == None ) ) \
- .all()
- for z_inst in z_instances:
- if self.type == z_inst.uci.credentials.provider.type:
-# log.debug( "z_inst.id: '%s', state: '%s'" % ( z_inst.id, z_inst.state ) )
- td = datetime.utcnow() - z_inst.update_time
-# log.debug( "z_inst.id: %s, time delta is %s sec" % ( z_inst.id, td.seconds ) )
- if td.seconds > 180: # if instance has been in SUBMITTED state for more than 3 minutes
- log.debug( "[%s](td=%s) Running zombie repair update on instance with DB id '%s'" % ( z_inst.uci.credentials.provider.type, td.seconds, z_inst.id ) )
- self.process_zombie( z_inst )
-
- def update_instance( self, inst ):
- """
- Update information in local database for given instance as it is obtained from cloud provider.
- Along with updating information about given instance, information about the UCI controlling
- this instance is also updated.
- """
- # Get credentials associated wit this instance
- uci_id = inst.uci_id
- uci = self.sa_session.query( model.UCI ).get( uci_id )
- self.sa_session.refresh( uci )
- conn = self.get_connection_from_uci( uci )
-
- # Get reservations handle for given instance
- try:
- rl= conn.get_all_instances( [inst.instance_id] )
- except boto.exception.EC2ResponseError, e:
- err = "Retrieving instance(s) from cloud failed for UCI '"+ uci.name +"' during general status update: " + str( e )
- log.error( err )
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.flush()
- return None
-
- # Because references to reservations are deleted shortly after instances have been terminated, getting an empty list as a response to a query
- # typically means the instance has successfully shut down but the check was not performed in short enough amount of time. Until an alternative solution
- # is found, below code sets state of given UCI to 'error' to indicate to the user something out of ordinary happened.
- if len( rl ) == 0:
- err = "Instance ID '"+inst.instance_id+"' was not found by the cloud provider. Instance might have crashed or otherwise been terminated."+ \
- "Manual check is recommended."
- log.error( err )
- inst.error = err
- uci.error = err
- inst.state = instance_states.TERMINATED
- uci.state = uci_states.ERROR
- uci.launch_time = None
- self.sa_session.add( inst )
- self.sa_session.add( uci )
- self.sa_session.flush()
- # Update instance status in local DB with info from cloud provider
- for r in rl:
- for i, cInst in enumerate( r.instances ):
- try:
- s = cInst.update()
- log.debug( "Checking state of cloud instance '%s' associated with UCI '%s' and reservation '%s'. State='%s'" % ( cInst, uci.name, r, s ) )
- if s != inst.state:
- inst.state = s
- self.sa_session.add( inst )
- self.sa_session.flush()
- # After instance has shut down, ensure UCI is marked as 'available'
- if s == instance_states.TERMINATED and uci.state != uci_states.ERROR:
- uci.state = uci_states.AVAILABLE
- uci.launch_time = None
- self.sa_session.add( uci )
- self.sa_session.flush()
- # Making sure state of UCI is updated. Once multiple instances become associated with single UCI, this will need to be changed.
- if s != uci.state and s != instance_states.TERMINATED:
- uci.state = s
- self.sa_session.add( uci )
- self.sa_session.flush()
- if cInst.public_dns_name != inst.public_dns:
- inst.public_dns = cInst.public_dns_name
- self.sa_session.add( inst )
- self.sa_session.flush()
- if cInst.private_dns_name != inst.private_dns:
- inst.private_dns = cInst.private_dns_name
- self.sa_session.add( inst )
- self.sa_session.flush()
- except boto.exception.EC2ResponseError, e:
- err = "Updating instance status from cloud failed for UCI '"+ uci.name + "' during general status update: " + str( e )
- log.error( err )
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.flush()
- return None
-
- def update_store( self, store ):
- """
- Update information in local database for given storage volume as it is obtained from cloud provider.
- Along with updating information about given storage volume, information about the UCI controlling
- this storage volume is also updated.
- """
- # Get credentials associated wit this store
- uci_id = store.uci_id
- uci = self.sa_session.query( model.UCI ).get( uci_id )
- self.sa_session.refresh( uci )
- conn = self.get_connection_from_uci( uci )
-
- if store.volume_id != None:
- # Get reservations handle for given store
- try:
- log.debug( "Updating storage volume command: vl = conn.get_all_volumes( [%s] )" % store.volume_id )
- vl = conn.get_all_volumes( [store.volume_id] )
- except boto.exception.EC2ResponseError, e:
- err = "Retrieving volume(s) from cloud failed for UCI '"+ uci.name + "' during general status update: " + str( e )
- log.error( err )
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.flush()
- return None
-
- # Update store status in local DB with info from cloud provider
- if len(vl) > 0:
- try:
- log.debug( "Storage volume '%s' current status: '%s'" % (store.volume_id, vl[0].status ) )
- if store.status != vl[0].status:
- # In case something failed during creation of UCI but actual storage volume was created and yet
- # UCI state remained as 'new', try to remedy this by updating UCI state here
- if ( store.status == None ) and ( store.volume_id != None ):
- uci.state = vl[0].status
- self.sa_session.add( uci )
- self.sa_session.flush()
- # If UCI was marked in state 'CREATING', update its status to reflect new status
- elif ( uci.state == uci_states.CREATING ):
- # Because Eucalyptus Public Cloud (EPC) deletes volumes immediately after they are created, artificially
- # set status of given UCI to 'available' based on storage volume's availability zone (i.e., it's residing
- # in EPC as opposed to some other Eucalyptus based cloud that allows creation of storage volumes.
- if store.availability_zone == 'epc':
- uci.state = uci_states.AVAILABLE
- else:
- uci.state = vl[0].status
-
- self.sa_session.add( uci )
- self.sa_session.flush()
-
- store.status = vl[0].status
- self.sa_session.add( store )
- self.sa_session.flush()
- if store.inst != None:
- if store.inst.instance_id != vl[0].instance_id:
- store.inst.instance_id = vl[0].instance_id
- self.sa_session.add( store )
- self.sa_session.flush()
- if store.attach_time != vl[0].attach_time:
- store.attach_time = vl[0].attach_time
- self.sa_session.add( store )
- self.sa_session.flush()
- if store.device != vl[0].device:
- store.device = vl[0].device
- self.sa_session.add( store )
- self.sa_session.flush()
- except boto.exception.EC2ResponseError, e:
- err = "Updating status of volume(s) from cloud failed for UCI '"+ uci.name + "' during general status update: " + str( e )
- log.error( err )
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.flush()
- return None
- else:
- err = "No storage volumes returned by cloud provider on general update"
- log.error( "%s for UCI '%s'" % ( err, uci.name ) )
- store.status = store_status.ERROR
- store.error = err
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.add( store )
- self.sa_session.flush()
- else:
- err = "Missing storage volume ID in local database on general update. Manual check is needed to check " \
- "if storage volume was actually created by cloud provider."
- log.error( "%s (for UCI '%s')" % ( err, uci.name ) )
- store.status = store_status.ERROR
- store.error = err
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.add( store )
- self.sa_session.flush()
-
- def update_snapshot( self, snapshot ):
- """
- Update information in local database for given snapshot as it is obtained from cloud provider.
- Along with updating information about given snapshot, information about the UCI controlling
- this snapshot is also updated.
- """
- # Get credentials associated wit this store
- uci_id = snapshot.uci_id
- uci = self.sa_session.query( model.UCI ).get( uci_id )
- self.sa_session.refresh( uci )
- conn = self.get_connection_from_uci( uci )
-
- try:
- log.debug( "Updating status of snapshot '%s'" % snapshot.snapshot_id )
- snap = conn.get_all_snapshots( [snapshot.snapshot_id] )
- if len( snap ) > 0:
- log.debug( "Snapshot '%s' status: %s" % ( snapshot.snapshot_id, snap[0].status ) )
- snapshot.status = snap[0].status
- self.sa_session.add( snapshot )
- self.sa_session.flush()
- else:
- err = "No snapshots returned by EC2 on general update"
- log.error( "%s for UCI '%s'" % ( err, uci.name ) )
- snapshot.status = snapshot_status.ERROR
- snapshot.error = err
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.add( snapshot )
- self.sa_session.flush()
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while updating snapshot status: " + str( e )
- log.error( err )
- snapshot.status = snapshot_status.ERROR
- snapshot.error = err
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.add( snapshot )
- self.sa_session.flush()
- except Exception, ex:
- err = "Error while updating snapshot status: " + str( ex )
- log.error( err )
- snapshot.status = snapshot_status.ERROR
- snapshot.error = err
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.add( snapshot )
- self.sa_session.flush()
-
- def delete_snapshot( self, snapshot ):
- """
- Initiate deletion of given snapshot from cloud provider.
- """
- if snapshot.status == snapshot_status.DELETE:
- # Get credentials associated wit this store
- uci_id = snapshot.uci_id
- uci = self.sa_session.query( model.UCI ).get( uci_id )
- self.sa_session.refresh( uci )
- conn = self.get_connection_from_uci( uci )
-
- try:
- log.debug( "Deleting snapshot '%s'" % snapshot.snapshot_id )
- snap = conn.delete_snapshot( snapshot.snapshot_id )
- if snap == True:
- snapshot.deleted = True
- snapshot.status = snapshot_status.DELETED
- self.sa_session.add( snapshot )
- self.sa_session.flush()
- return snap
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while deleting snapshot: " + str( e )
- log.error( err )
- snapshot.status = snapshot_status.ERROR
- snapshot.error = err
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.add( snapshot )
- self.sa_session.flush()
- except Exception, ex:
- err = "Error while deleting snapshot: " + str( ex )
- log.error( err )
- snapshot.status = snapshot_status.ERROR
- snapshot.error = err
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.add( snapshot )
- self.sa_session.flush()
- else:
- err = "Cannot delete snapshot '"+snapshot.snapshot_id+"' because its status is '"+snapshot.status+"'. Only snapshots with '" + \
- snapshot_status.COMPLETED+"' status can be deleted."
- log.error( err )
- snapshot.error = err
- self.sa_session.add( snapshot )
- self.sa_session.flush()
-
- def process_zombie( self, inst ):
- """
- Attempt at discovering if starting a cloud instance was successful but local database was not updated
- accordingly or if something else failed and instance was never started. Currently, no automatic
- repairs are being attempted; instead, appropriate error messages are set.
- """
- uci_id = inst.uci_id
- uci = self.sa_session.query( model.UCI ).get( uci_id )
- self.sa_session.refresh( uci )
-
- # Check if any instance-specific information was written to local DB; if 'yes', set instance and UCI's error message
- # suggesting manual check.
- if inst.launch_time != None or inst.reservation_id != None or inst.instance_id != None:
- # Try to recover state - this is best-case effort, so if something does not work immediately, not
- # recovery steps are attempted. Recovery is based on hope that instance_id is available in local DB; if not,
- # report as error.
- # Fields attempting to be recovered are: reservation_id, instance status, and launch_time
- if inst.instance_id != None:
- conn = self.get_connection_from_uci( uci )
- rl = conn.get_all_instances( [inst.instance_id] ) # reservation list
- # Update local DB with relevant data from instance
- if inst.reservation_id == None:
- try:
- inst.reservation_id = str(rl[0]).split(":")[1]
- except: # something failed, so skip
- pass
-
- try:
- state = rl[0].instances[0].update()
- inst.state = state
- uci.state = state
- self.sa_session.add( inst )
- self.sa_session.add( uci )
- self.sa_session.flush()
- except: # something failed, so skip
- pass
-
- if inst.launch_time == None:
- try:
- launch_time = self.format_time( rl[0].instances[0].launch_time )
- inst.launch_time = launch_time
- self.sa_session.add( inst )
- self.sa_session.flush()
- if inst.uci.launch_time == None:
- uci.launch_time = launch_time
- self.sa_session.add( uci )
- self.sa_session.flush()
- except: # something failed, so skip
- pass
- else:
- err = "Starting a machine instance (DB id: '"+str(inst.id)+"') associated with this UCI '" + str(inst.uci.name) + \
- "' seems to have failed. Because it appears that cloud instance might have gotten started, manual check is recommended."
- inst.error = err
- inst.state = instance_states.ERROR
- inst.uci.error = err
- inst.uci.state = uci_states.ERROR
- log.error( err )
- self.sa_session.add( inst )
- self.sa_session.add( uci )
- self.sa_session.flush()
-
- else: #Instance most likely never got processed, so set error message suggesting user to try starting instance again.
- err = "Starting a machine instance (DB id: '"+str(inst.id)+"') associated with this UCI '" + str(inst.uci.name) + \
- "' seems to have failed. Because it appears that cloud instance never got started, it should be safe to reset state and try " \
- "starting the instance again."
- inst.error = err
- inst.state = instance_states.ERROR
- uci.error = err
- uci.state = uci_states.ERROR
- log.error( err )
- self.sa_session.add( inst )
- self.sa_session.add( uci )
- self.sa_session.flush()
-# uw = UCIwrapper( inst.uci )
-# log.debug( "Try automatically re-submitting UCI '%s'." % uw.get_name() )
-
- def get_connection_from_uci( self, uci ):
- """
- Establish and return connection to cloud provider. Information needed to do so is obtained
- directly from uci database object.
- """
- log.debug( 'Establishing %s cloud connection' % self.type )
- a_key = uci.credentials.access_key
- s_key = uci.credentials.secret_key
- # Get connection
- try:
- region = RegionInfo( None, uci.credentials.provider.region_name, uci.credentials.provider.region_endpoint )
-# log.debug( "[%s] Using following command to connect to cloud provider: "
-# "conn = EC2Connection( aws_access_key_id=%s, "
-# "aws_secret_access_key=%s, "
-# "port=%s, "
-# "is_secure=%s, "
-# "region=region, "
-# "path=%s )" % ( self.type, a_key, s_key, uci.credentials.provider.is_secure, uci.credentials.provider.port, uci.credentials.provider.path ) )
- conn = EC2Connection( aws_access_key_id=a_key,
- aws_secret_access_key=s_key,
- is_secure=uci.credentials.provider.is_secure,
- port=uci.credentials.provider.port,
- region=region,
- path=uci.credentials.provider.path )
- except boto.exception.EC2ResponseError, e:
- err = "Establishing connection with cloud failed: " + str( e )
- log.error( err )
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.flush()
- return None
-
- return conn
-
-# def updateUCI( self, uci ):
-# """
-# Runs a global status update on all storage volumes and all instances that are
-# associated with specified UCI
-# """
-# conn = self.get_connection( uci )
-#
-# # Update status of storage volumes
-# vl = model.CloudStore.filter( model.CloudInstance.table.c.uci_id == uci.id ).all()
-# vols = []
-# for v in vl:
-# vols.append( v.volume_id )
-# try:
-# volumes = conn.get_all_volumes( vols )
-# for i, v in enumerate( volumes ):
-# uci.store[i].inst.instance_id = v.instance_id
-# uci.store[i].status = v.status
-# uci.store[i].device = v.device
-# uci.store[i].flush()
-# except:
-# log.debug( "Error updating status of volume(s) associated with UCI '%s'. Status was not updated." % uci.name )
-# pass
-#
-# # Update status of instances
-# il = model.CloudInstance.filter_by( uci_id=uci.id ).filter( model.CloudInstance.table.c.state != 'terminated' ).all()
-# instanceList = []
-# for i in il:
-# instanceList.append( i.instance_id )
-# log.debug( 'instanceList: %s' % instanceList )
-# try:
-# reservations = conn.get_all_instances( instanceList )
-# for i, r in enumerate( reservations ):
-# uci.instance[i].state = r.instances[0].update()
-# log.debug('updating instance %s; status: %s' % ( uci.instance[i].instance_id, uci.instance[i].state ) )
-# uci.state = uci.instance[i].state
-# uci.instance[i].public_dns = r.instances[0].dns_name
-# uci.instance[i].private_dns = r.instances[0].private_dns_name
-# uci.instance[i].flush()
-# uci.flush()
-# except:
-# log.debug( "Error updating status of instances associated with UCI '%s'. Instance status was not updated." % uci.name )
-# pass
-
- # --------- Helper methods ------------
-
- def format_time( self, time ):
- dict = {'T':' ', 'Z':''}
- for i, j in dict.iteritems():
- time = time.replace(i, j)
- return time
-
--- a/templates/cloud/add_credentials.mako
+++ /dev/null
@@ -1,110 +0,0 @@
-<% _=n_ %>
-<%inherit file="/base.mako"/>
-<%def name="title()">Add credentials</%def>
-
-<%def name="javascripts()">
-${parent.javascripts()}
-<script type="text/javascript">
-$(function(){
- $("input:text:first").focus();
-})
-</script>
-</%def>
-
-%if header:
- ${header}
-%endif
-
-%if providers:
-
-<div class="form">
- <div class="form-title">Add credentials</div>
- <div class="form-body">
- <form name="add_credentials" action="${h.url_for( action='add_credentials' )}" method="post" >
-
- <%
- cls = "form-row"
- if error.has_key('cred_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Credentials name:</label>
- <div class="form-row-input">
- <input type="text" name="credName" value="${credName}" size="40">
- </div>
- %if error.has_key('cred_error'):
- <div class="form-row-error-message">${error['cred_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
-
- <%
- cls = "form-row"
- if error.has_key('provider_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Cloud provider name:</label>
- <div class="form-row-input">
- <select name="providerName" style="width:40em">
- <option value="">Select Provider...</option>
- %for provider in providers:
- <option value="${provider.name}">${provider.name}</option>
- %endfor
- </select>
- <br/>or <a href="${h.url_for( action='add_provider' )}">
- <span>register additional cloud provider</span></a>
- </div>
- %if error.has_key('provider_error'):
- <div class="form-row-error-message">${error['provider_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- if error.has_key('access_key_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Access key:</label>
- <div class="form-row-input">
- <input type="text" name="accessKey" value="${accessKey}" size="40">
- </div>
- %if error.has_key('access_key_error'):
- <div class="form-row-error-message">${error['access_key_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
-
- <%
- cls = "form-row"
- if error.has_key('secret_key_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Secret key:</label>
- <div class="form-row-input">
- <input type="password" name="secretKey" value="${secretKey}" size="40">
- </div>
- %if error.has_key('secret_key_error'):
- <div class="form-row-error-message">${error['secret_key_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <div class="form-row"><input type="submit" value="Add"></div>
- </form>
- </div>
-</div>
-
-%else:
- In order to add credentials, desired cloud provider needs to be registered first.<p/>
- Register <a href="${h.url_for( action='add_ec2' )}">
- <span>Amazon EC2 (us-east-1 region) automatically</span></a>
- or add
- <a href="${h.url_for( action='add_provider' )}">
- <span>custom cloud provider</span></a>.
-%endif
--- a/templates/cloud/list_images.mako
+++ /dev/null
@@ -1,91 +0,0 @@
-<%inherit file="/base.mako"/>
-
-<%def name="title()">Cloud home</%def>
-
-%if message:
-<%
- try:
- messagetype
- except:
- messagetype = "done"
-%>
-
-<p />
-<div class="${messagetype}message">
- ${message}
-</div>
-%endif
-
-<h2>List of registered machine images:</h2>
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( controller='cloud', action='add_new_image' )}" target="galaxy_main">
- <img src="${h.url_for('/static/images/silk/add.png')}" />
- <span>Add machine image</span>
- </a>
- </li>
-</ul>
-
-%if images:
- <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
- <colgroup width="2%"></colgroup>
- <colgroup width="10%"></colgroup>
- <colgroup width="13%"></colgroup>
- <colgroup width="55%"></colgroup>
- <colgroup width="10%"></colgroup>
- <colgroup width="5%"></colgroup>
- <colgroup width="5%"></colgroup>
- <tr class="header">
- <th>#</th>
- <th>Provider type</th>
- <th>Machime image ID</th>
- <th>Manifest</th>
- <th>Architecture</th>
- <th>Edit</th>
- <th>Delete</th>
- <th></th>
- </tr>
- %for i, image in enumerate( images ):
- <tr>
- <td>${i+1}</td>
- <td>
- %if image.provider_type:
- ${image.provider_type}
- %else:
- N/A
- %endif
- </td>
- <td>
- %if image.image_id:
- ${image.image_id}
- %else:
- N/A
- %endif
- </td>
- <td>
- %if image.manifest:
- ${image.manifest}
- %else:
- N/A
- %endif
- </td>
- <td>
- %if image.architecture:
- ${image.architecture}
- %else:
- N/A
- %endif
- </td>
- <td>
- <a href="${h.url_for( controller='cloud', action='edit_image', image_id=image.image_id, manifest=image.manifest, id=trans.security.encode_id(image.id) )}">e</a>
- </td>
- <td>
- <a confirm="Are you sure you want to delete machine image '${image.image_id}'? Note that this may result in users' UCI's not to work any more!"
- href="${h.url_for( controller='cloud', action='delete_image', id=trans.security.encode_id(image.id) )}">x</a>
- </td>
- </tr>
- %endfor
- </table>
-%else:
- <h3>There are no registered machine images.</h3><br />
-%endif
--- a/templates/cloud/configure_uci.mako
+++ /dev/null
@@ -1,116 +0,0 @@
-<% _=n_ %>
-<%inherit file="/base.mako"/>
-<%def name="title()">Configure new UCI</%def>
-
-<%def name="javascripts()">
-${parent.javascripts()}
-<script type="text/javascript">
-
-var providers_zones = ${h.to_json_string(providersToZones)};
-
-$(function(){
- $("input:text:first").focus();
-
- $("#credName").change(function() {
- var zones = providers_zones[ $(this).val() ];
- var zones_select = $("#zones");
-
- zones_select.children().remove();
-
- for (var i in zones) {
- var zone = zones[i];
- var new_option = $('<option value="' + zone + '">' + zone + '</option>');
- new_option.appendTo(zones_select);
- }
-
- });
-})
-</script>
-</%def>
-
-%if header:
- ${header}
-%endif
-
-<div class="form">
- <div class="form-title">Configure new Galaxy instance</div>
- <div class="form-body">
- <form name="Configure new UCI" action="${h.url_for( action='configure_new_uci' )}" method="post" >
-
- <%
- cls = "form-row"
- if error.has_key('inst_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Instance name:</label>
- <div class="form-row-input">
- <input type="text" name="instanceName" value="${instanceName}" size="40">
- </div>
- %if error.has_key('inst_error'):
- <div class="form-row-error-message">${error['inst_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- if error.has_key('cred_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Name of registered credentials to use:</label>
- <div class="form-row-input">
- <select id="credName" name="credName" style="width:40em">
- <option value="">Select Credential...</option>
- % for cred in credName:
- <option value="${cred.name}">${cred.name}</option>
- %endfor
- </select>
- </div>
- %if error.has_key('cred_error'):
- <div class="form-row-error-message">${error['cred_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
-
- <%
- cls = "form-row"
- if error.has_key('vol_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Permanent storage size (1-1000GB):<br/>(Note: you will be able to add more storage later)</label>
- <div class="form-row-input">
- <input type="text" name="volSize" value="${volSize}" size="40">
- </div>
- %if error.has_key('vol_error'):
- <div class="form-row-error-message">${error['vol_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- if error.has_key('zone_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Create storage in zone:</label>
- <div class="form-row-input">
- <select id="zones" name="zone" style="width:40em">
- </select>
- </div>
- %if error.has_key('zone_error'):
- <div class="form-row-error-message">${error['zone_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
-
- <div class="form-row"><input type="submit" value="Add"></div>
-
- </form>
- </div>
-</div>
--- a/templates/cloud/add_provider.mako
+++ /dev/null
@@ -1,252 +0,0 @@
-<% _=n_ %>
-<%inherit file="/base.mako"/>
-<%def name="title()">Add provider</%def>
-
-<%def name="javascripts()">
-${parent.javascripts()}
-<script type="text/javascript">
-$(function(){
-
- $("input:text:first").focus();
-
- $("#type").change(function() {
- if ($(this).val() == 'ec2') {
- clear();
- $("#autofill").attr( 'disabled', true );
- $("#autofill").attr( 'checked', false );
- $("#name").val( "EC2" );
- $("#region_name").val( "us-east-1" );
- $("#region_endpoint").val( "us-east-1.ec2.amazonaws.com" );
- $("#is_secure").val("1");
- $("#debug").val("");
- $("#path").val("/");
- }
- else if ($(this).val() == 'eucalyptus') {
- clear();
- $("#autofill").attr( 'disabled', false );
- }
- });
-})
-
-
-function af(){
-
- if ( $("#autofill").attr('checked') ) {
- $("#name").val("Eucalyptus Public Cloud");
- $("#region_name").val("eucalyptus");
- $("#region_endpoint").val("mayhem9.cs.ucsb.edu");
- $("#is_secure").val("0");
- $("#port").val("8773");
- $("#path").val("/services/Eucalyptus");
- }
-}
-
-function clear() {
- $("#name").val("");
- $("#region_name").val("");
- $("#region_endpoint").val("");
- $("#is_secure").val("");
- $("#port").val("");
- $("#proxy").val("");
- $("#proxy_port").val("");
- $("#proxy_user").val("");
- $("#proxy_pass").val("");
- $("#debug").val("");
- $("#https_connection_factory").val("");
- $("#path").val("");
-
-}
-
-</script>
-</%def>
-
-%if header:
- ${header}
-%endif
-
-<div class="form">
- <div class="form-title">Add cloud provider</div>
- <div class="form-body">
- <form name="add_provider_form" action="${h.url_for( action='add_provider' )}" method="post" >
- <%
- cls = "form-row"
- if error.has_key('type_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Provider type:</label>
- <div class="form-row-input">
- <select id="type" name="type" style="width:40em">
- <option value="">Select Provider...</option>
- <option value="eucalyptus">Eucalyptus</option>
- <option value="ec2">Amazon EC2</option>
- </select>
- <br/>
- <input type="checkbox" id="autofill" onclick="javascript:af()" disabled="true">
- auto fill using Eucalyptus Public Cloud values
- </div>
- %if error.has_key('type_error'):
- <div class="form-row-error-message">${error['type_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- if error.has_key('name_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Provider name:</label>
- <div class="form-row-input">
- <input type="text" id="name" name="name" value="${name}" size="40">
- </div>
- %if error.has_key('name_error'):
- <div class="form-row-error-message">${error['name_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Region name:</label>
- <div id="region_selection" class="form-row-input">
- <input type="text" name="region_name" id="region_name" value="${region_name}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Region endpoint:</label>
- <div class="form-row-input">
- <input type="text" name="region_endpoint" id="region_endpoint" value="${region_endpoint}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- if error.has_key('is_secure_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Is secure ('O' for False or '1' for True):</label>
- <div class="form-row-input">
- <input type="text" name="is_secure" id="is_secure" value="${is_secure}" size="40">
- </div>
- %if error.has_key('is_secure_error'):
- <div class="form-row-error-message">${error['is_secure_error']}; you entered: '${is_secure}'</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Host:</label>
- <div class="form-row-input">
- <input type="text" name="host" value="${host}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Port:</label>
- <div class="form-row-input">
- <input type="text" name="port" id="port" value="${port}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Proxy:</label>
- <div class="form-row-input">
- <input type="text" name="proxy" value="${proxy}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Proxy port:</label>
- <div class="form-row-input">
- <input type="text" name="proxy_port" value="${proxy_port}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Proxy user:</label>
- <div class="form-row-input">
- <input type="text" name="proxy_user" value="${proxy_user}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Proxy pass:</label>
- <div class="form-row-input">
- <input type="text" name="proxy_pass" value="${proxy_pass}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Debug:</label>
- <div class="form-row-input">
- <input type="text" name="debug" value="${debug}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>HTTPS connection factory:</label>
- <div class="form-row-input">
- <input type="text" name="https_connection_factory" value="${https_connection_factory}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- %>
- <div class="${cls}">
- <label>Path:</label>
- <div class="form-row-input">
- <input type="text" name="path" id="path" value="${path}" size="40">
- </div>
- <div style="clear: both"></div>
- </div>
-
- <div class="form-row"><input type="submit" value="Add"></div>
-
- </form>
-
- </div>
-</div>
--- a/templates/cloud/index.mako
+++ /dev/null
@@ -1,16 +0,0 @@
-<%inherit file="/webapps/galaxy/base_panels.mako"/>
-
-<%def name="init()">
-<%
- self.has_left_panel=False
- self.has_right_panel=False
- self.active_view="cloud"
- self.message_box_visible=False
-%>
-</%def>
-
-<%def name="center_panel()">
-
- <iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${h.url_for( controller="cloud", action="list" )}"></iframe>
-
-</%def>
--- a/templates/cloud/add_image.mako
+++ /dev/null
@@ -1,98 +0,0 @@
-<% _=n_ %>
-<%inherit file="/base.mako"/>
-<%def name="title()">Add machine image</%def>
-
-<%def name="javascripts()">
-${parent.javascripts()}
-<script type="text/javascript">
-$(function(){
- //$("input:text:first").focus();
-})
-</script>
-</%def>
-
-%if header:
- ${header}
-%endif
-
-<div class="form">
- <div class="form-title">Add machine image</div>
- <div class="form-body">
- <form name="add_image" action="${h.url_for( action='add_new_image' )}" method="post" >
- <%
- cls = "form-row"
- if error.has_key('provider_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Cloud provider type:</label>
- <div class="form-row-input">
- <select name="provider_type" style="width:40em">
- <option value="">Select Provider Type...</option>
- <option value="eucalyptus">Eucalyptus</option>
- <option value="ec2">Amazon EC2</option>
- </select>
- </div>
- %if error.has_key('provider_error'):
- <div class="form-row-error-message">${error['provider_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- if error.has_key('id_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Machine Image ID (AMI or EMI):</label>
- <div class="form-row-input">
- <input type="text" name="image_id" value="${image_id}" size="40">
- </div>
- %if error.has_key('id_error'):
- <div class="form-row-error-message">${error['id_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <%
- cls = "form-row"
- if error.has_key('manifest_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Manifest:</label>
- <div class="form-row-input">
- <input type="text" name="manifest" value="${manifest}" size="40">
- </div>
- %if error.has_key('manifest_error'):
- <div class="form-row-error-message">${error['manifest_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
-
- <%
- cls = "form-row"
- if error.has_key('arch_error'):
- cls += " form-row-error"
- %>
- <div class="${cls}">
- <label>Image architecture:</label>
- <div class="form-row-input">
- <select name="architecture" style="width:40em">
- <option value="">Select Architecture Type...</option>
- <option value="i386">i386 (32 bit)</option>
- <option value="x86_64">x86_64 (64 bit)</option>
- </select>
- </div>
- %if error.has_key('arch_error'):
- <div class="form-row-error-message">${error['arch_error']}</div>
- %endif
- <div style="clear: both"></div>
- </div>
-
- <div class="form-row"><input type="submit" value="Add"></div>
- </form>
- </div>
-</div>
--- a/lib/galaxy/cloud/providers/ec2.py
+++ /dev/null
@@ -1,1232 +0,0 @@
-import subprocess, threading, os, errno, time, datetime, stat
-from Queue import Queue, Empty
-from datetime import datetime
-
-from galaxy import model # Database interaction class
-from galaxy.model import mapping
-from galaxy.datatypes.data import nice_size
-from galaxy.util.bunch import Bunch
-from galaxy.cloud import UCIwrapper
-from Queue import Queue
-from sqlalchemy import or_, and_
-
-import galaxy.eggs
-galaxy.eggs.require("boto")
-from boto.ec2.connection import EC2Connection
-from boto.ec2.regioninfo import RegionInfo
-import boto.exception
-import boto
-
-import logging
-log = logging.getLogger( __name__ )
-
-uci_states = Bunch(
- NEW_UCI = "newUCI",
- NEW = "new",
- CREATING = "creating",
- DELETING_UCI = "deletingUCI",
- DELETING = "deleting",
- SUBMITTED_UCI = "submittedUCI",
- SUBMITTED = "submitted",
- SHUTTING_DOWN_UCI = "shutting-downUCI",
- SHUTTING_DOWN = "shutting-down",
- ADD_STORAGE_UCI = "add-storageUCI",
- ADD_STORAGE = "add-storage",
- AVAILABLE = "available",
- RUNNING = "running",
- PENDING = "pending",
- ERROR = "error",
- DELETED = "deleted",
- SNAPSHOT_UCI = "snapshotUCI",
- SNAPSHOT = "snapshot"
-)
-
-instance_states = Bunch(
- TERMINATED = "terminated",
- SUBMITTED = "submitted",
- RUNNING = "running",
- ADDING = "adding-storage",
- PENDING = "pending",
- SHUTTING_DOWN = "shutting-down",
- ERROR = "error"
-)
-
-store_status = Bunch(
- WAITING = "waiting",
- IN_USE = "in-use",
- ADDING = "adding",
- CREATING = "creating",
- DELETED = 'deleted',
- ERROR = "error"
-)
-
-snapshot_status = Bunch(
- SUBMITTED = 'submitted',
- PENDING = 'pending',
- COMPLETED = 'completed',
- DELETE = 'delete',
- DELETED= 'deleted',
- ERROR = "error"
-)
-
-class EC2CloudProvider( object ):
- """
- Amazon EC2-based cloud provider implementation for managing instances.
- """
- STOP_SIGNAL = object()
- def __init__( self, app ):
- self.type = "ec2" # cloud provider type (e.g., ec2, eucalyptus, opennebula)
- self.zone = "us-east-1a"
- self.security_group = "galaxyWeb"
- self.queue = Queue()
- self.sa_session = app.model.context
-
- self.threads = []
- nworkers = 5
- log.info( "Starting EC2 cloud controller workers..." )
- for i in range( nworkers ):
- worker = threading.Thread( target=self.run_next )
- worker.start()
- self.threads.append( worker )
- log.debug( "%d EC2 cloud workers ready", nworkers )
-
- def shutdown( self ):
- """Attempts to gracefully shut down the monitor thread"""
- log.info( "sending stop signal to worker threads in EC2 cloud manager" )
- for i in range( len( self.threads ) ):
- self.queue.put( self.STOP_SIGNAL )
- log.info( "EC2 cloud manager stopped" )
-
- def put( self, uci_wrapper ):
- """
- Add uci_wrapper object to the end of the request queue to be handled by
- this cloud provider.
- """
- state = uci_wrapper.get_uci_state()
- uci_wrapper.change_state( state.split('U')[0] ) # remove 'UCI' from end of state description (i.e., mark as accepted and ready for processing)
- self.queue.put( uci_wrapper )
-
- def run_next( self ):
- """Process next request, waiting until one is available if necessary."""
- cnt = 0
- while 1:
-
- uci_wrapper = self.queue.get()
- uci_state = uci_wrapper.get_uci_state()
- if uci_state is self.STOP_SIGNAL:
- return
- try:
- if uci_state==uci_states.NEW:
- self.create_uci( uci_wrapper )
- elif uci_state==uci_states.DELETING:
- self.delete_uci( uci_wrapper )
- elif uci_state==uci_states.SUBMITTED:
- self.start_uci( uci_wrapper )
- elif uci_state==uci_states.SHUTTING_DOWN:
- self.stop_uci( uci_wrapper )
- elif uci_state==uci_states.SNAPSHOT:
- self.snapshot_uci( uci_wrapper )
- elif uci_state==uci_states.ADD_STORAGE:
- self.add_storage_to_uci( uci_wrapper )
- #self.dummy_start_uci( uci_wrapper )
- except:
- log.exception( "Uncaught exception executing cloud request." )
- cnt += 1
-
- def get_connection( self, uci_wrapper ):
- """
- Establishes cloud connection using user's credentials associated with given UCI
- """
- log.debug( 'Establishing %s cloud connection.' % self.type )
- provider = uci_wrapper.get_provider()
- try:
- region = RegionInfo( None, provider.region_name, provider.region_endpoint )
- except Exception, ex:
- err = "Selecting region with cloud provider failed: " + str( ex )
- log.error( err )
- uci_wrapper.set_error( err, True )
- return None
- try:
- conn = EC2Connection( aws_access_key_id=uci_wrapper.get_access_key(),
- aws_secret_access_key=uci_wrapper.get_secret_key(),
- is_secure=provider.is_secure,
- region=region,
- path=provider.path )
- except boto.exception.EC2ResponseError, e:
- err = "Establishing connection with cloud failed: " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
- return None
-
- return conn
-
- def check_key_pair( self, uci_wrapper, conn ):
- """
- Check if a key pair associated with this UCI exists on cloud provider.
- If yes, return key pair name; otherwise, generate a key pair with the cloud
- provider and, again, return key pair name.
- Key pair name for given UCI is generated from UCI's name and suffix '_kp'
- """
- kp = None
- kp_name = uci_wrapper.get_name().replace(' ','_') + "_kp"
- log.debug( "Checking user's key pair: '%s'" % kp_name )
- try:
- kp = conn.get_key_pair( kp_name )
- uci_kp_name = uci_wrapper.get_key_pair_name()
- uci_material = uci_wrapper.get_key_pair_material()
- if kp != None:
- if kp.name != uci_kp_name or uci_material == None:
- # key pair exists on the cloud but not in local database, so re-generate it (i.e., delete and then create)
- try:
- conn.delete_key_pair( kp_name )
- kp = self.create_key_pair( conn, kp_name )
- uci_wrapper.set_key_pair( kp.name, kp.material )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while deleting key pair: " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
- else:
- try:
- kp = self.create_key_pair( conn, kp_name )
- uci_wrapper.set_key_pair( kp.name, kp.material )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while creating key pair: " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
- except Exception, ex:
- err = "Exception while creating key pair: " + str( ex )
- log.error( err )
- uci_wrapper.set_error( err, True )
- except boto.exception.EC2ResponseError, e: # No keypair under this name exists so create it
- if e.code == 'InvalidKeyPair.NotFound':
- log.info( "No keypair found, creating keypair '%s'" % kp_name )
- kp = self.create_key_pair( conn, kp_name )
- uci_wrapper.set_key_pair( kp.name, kp.material )
- else:
- err = "EC2 response error while retrieving key pair: " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
-
- if kp != None:
- return kp.name
- else:
- return None
-
- def create_key_pair( self, conn, kp_name ):
- """ Initiate creation of key pair under kp_name by current cloud provider. """
- try:
- return conn.create_key_pair( kp_name )
- except boto.exception.EC2ResponseError, e:
- return None
-
- def get_mi_id( self, uci_wrapper, i_index ):
- """
- Get appropriate machine image (mi) based on instance size.
- """
- i_type = uci_wrapper.get_instance_type( i_index )
- if i_type=='m1.small' or i_type=='c1.medium':
- arch = 'i386'
- else:
- arch = 'x86_64'
-
- mi = self.sa_session.query( model.CloudImage ).filter_by( deleted=False, provider_type=self.type, architecture=arch ).first()
- if mi:
- return mi.image_id
- else:
- err = "Machine image could not be retrieved"
- log.error( "%s for UCI '%s'." % (err, uci_wrapper.get_name() ) )
- uci_wrapper.set_error( err+". Contact site administrator to ensure needed machine image is registered.", True )
- return None
-
- def create_uci( self, uci_wrapper ):
- """
- Create User Configured Instance (UCI) - i.e., create storage volume on cloud provider
- and register relevant information in local Galaxy database.
- """
- conn = self.get_connection( uci_wrapper )
- if uci_wrapper.get_uci_availability_zone()=='':
- log.info( "Availability zone for UCI (i.e., storage volume) was not selected, using default zone: %s" % self.zone )
- uci_wrapper.set_store_availability_zone( self.zone )
-
- store = uci_wrapper.get_all_stores_in_status( store_status.ADDING )[0] # Because at UCI creation time only 1 storage volume can be created, reference it directly
-
- log.info( "Creating storage volume in zone '%s' of size '%s'..." % ( uci_wrapper.get_uci_availability_zone(), store.size ) )
- # Because only 1 storage volume may be created at UCI config time, index of this storage volume in local Galaxy DB w.r.t
- # current UCI is 0, so reference it in following methods
- vol = conn.create_volume( store.size, uci_wrapper.get_uci_availability_zone(), snapshot=None )
- uci_wrapper.set_store_volume_id( store.id, vol.id )
-
- # Retrieve created volume again to get updated status
- try:
- vl = conn.get_all_volumes( [vol.id] )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while retrieving (i.e., updating status) of just created storage volume '" + vol.id + "': " + str( e )
- log.error( err )
- uci_wrapper.set_store_status( vol.id, uci_states.ERROR )
- uci_wrapper.set_error( err, True )
- return
- except Exception, ex:
- err = "Error while retrieving (i.e., updating status) of just created storage volume '" + vol.id + "': " + str( ex )
- log.error( err )
- uci_wrapper.set_error( err, True )
- return
-
- if len( vl ) > 0:
- uci_wrapper.change_state( uci_state=vl[0].status )
- uci_wrapper.set_store_status( vol.id, vl[0].status )
- else:
- err = "Volume '" + vol.id +"' not found by EC2 after being created."
- log.error( err )
- uci_wrapper.set_store_status( vol.id, uci_states.ERROR )
- uci_wrapper.set_error( err, True )
-
- def delete_uci( self, uci_wrapper ):
- """
- Delete UCI - i.e., delete all storage volumes associated with this UCI.
- NOTE that this implies deletion of any and all data associated
- with this UCI from the cloud. All data will be deleted.
- Information in local Galaxy database is marked as deleted but not actually removed
- from the database.
- """
- conn = self.get_connection( uci_wrapper )
- vl = [] # volume list
- count = 0 # counter for checking if all volumes assoc. w/ UCI were deleted
-
- # Get all volumes assoc. w/ UCI, delete them from cloud as well as in local DB
- vl = uci_wrapper.get_all_stores()
- deletedList = []
- failedList = []
- for v in vl:
- log.debug( "Deleting volume with id='%s'" % v.volume_id )
- try:
- if conn.delete_volume( v.volume_id ):
- deletedList.append( v.volume_id )
- v.deleted = True
- v.status = store_status.DELETED
- self.sa_session.add( v )
- self.sa_session.flush()
- count += 1
- else:
- failedList.append( v.volume_id )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while deleting storage volume '" + v.volume_id + "': " + str( e )
- log.error( err )
- uci_wrapper.set_store_error( err, store_id = v.volume_id )
- uci_wrapper.set_error( err, True )
-
- # Delete UCI if all of associated
- if count == len( vl ):
- uci_wrapper.set_deleted()
- else:
- if uci_wrapper.get_uci_state != uci_states.ERROR:
- err = "Deleting following volume(s) failed: " + str( failedList ) + ". However, these volumes were successfully deleted: " \
- + str( deletedList ) + ". MANUAL intervention and processing needed."
- log.error( err )
- uci_wrapper.set_error( err, True )
-
- def snapshot_uci( self, uci_wrapper ):
- """
- Initiate creation of a snapshot by cloud provider for all storage volumes
- associated with this UCI.
- """
- if uci_wrapper.get_uci_state() != uci_states.ERROR:
- conn = self.get_connection( uci_wrapper )
-
- snapshots = uci_wrapper.get_snapshots( status = snapshot_status.SUBMITTED )
- for snapshot in snapshots:
- log.debug( "Snapshot DB id: '%s', volume id: '%s'" % ( snapshot.id, snapshot.store.volume_id ) )
- try:
- snap = conn.create_snapshot( volume_id=snapshot.store.volume_id )
- snap_id = str( snap ).split(':')[1]
- uci_wrapper.set_snapshot_id( snapshot.id, snap_id )
- sh = conn.get_all_snapshots( snap_id ) # get updated status
- uci_wrapper.set_snapshot_status( status=sh[0].status, snap_id=snap_id )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while creating snapshot: " + str( e )
- log.error( err )
- uci_wrapper.set_snapshot_error( error=err, snap_index=snapshot.id, set_status=True )
- uci_wrapper.set_error( err, True )
- return
- except Exception, ex:
- err = "Error while creating snapshot: " + str( ex )
- log.error( err )
- uci_wrapper.set_snapshot_error( error=err, snap_index=snapshot.id, set_status=True )
- uci_wrapper.set_error( err, True )
- return
-
- uci_wrapper.change_state( uci_state=uci_states.AVAILABLE )
-
- def add_storage_to_uci( self, uci_wrapper ):
- """
- Add an additional storage volume to specified UCI by creating the storage volume
- on cloud provider, attaching it to currently running instance and adding it to
- 'galaxyData' zpool on remote instance.
- """
- conn = self.get_connection( uci_wrapper )
-
- stores = uci_wrapper.get_all_stores_in_status( store_status.ADDING )
- for store in stores:
- vol_size = store.size
- availability_zone = uci_wrapper.get_uci_availability_zone()
- log.info( "Adding storage volume to UCI '%s' in zone '%s' of size '%s'..." % ( uci_wrapper.get_name(), availability_zone, vol_size ) )
-
- try:
- vol = conn.create_volume( vol_size, availability_zone, snapshot=None )
- uci_wrapper.set_store_volume_id( store.id, vol.id )
- uci_wrapper.set_store_availability_zone( availability_zone, vol.id )
- log.debug( "New storage volume created: '%s'" % vol.id )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while creating storage volume: " + str( e )
- log.error( err )
- uci_wrapper.set_store_error( err, store_id=vol.id )
- uci_wrapper.set_error( err, True )
- return
- except Exception, ex:
- err = "Error while creating storage volume: " + str( ex )
- log.error( err )
- uci_wrapper.set_error( err, True )
- return
-
- # Retrieve created volume again to get updated status
- try:
- vl = conn.get_all_volumes( [vol.id] )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while retrieving (i.e., updating status) of just created storage volume '" + vol.id + "': " + str( e )
- log.error( err )
- uci_wrapper.set_store_error( err, store_id=vol.id )
- uci_wrapper.set_error( err, True )
- return
- except Exception, ex:
- err = "Error while retrieving (i.e., updating status) of just created storage volume '" + vol.id + "': " + str( ex )
- log.error( err )
- uci_wrapper.set_error( err, True )
- return
-
- # Wait for a while to ensure volume was created
- if len( vl ) > 0:
- vol_status = vl[0].status # Bc. only single vol is queried, reference it as 0th list element
- for i in range( 30 ):
- if vol_status != "available":
- log.debug( "(%s) Updating volume status; current status: '%s'" % (i, vol_status ) )
- uci_wrapper.change_state( uci_state=vol_status )
- time.sleep(5)
- vol_status = vl[0].status
- if vol_status == "available":
- log.debug( "(%s) New volume status '%s', continuing with file system adjustment." % (i, vol_status ) )
- uci_wrapper.set_store_status( vl[0].id, vol_status )
- break
- if i is 29:
- err = "Error while creating volume '"+vl[0].id+"'; stuck in state '"+vol_status+"'; deleting volume."
- conn.delete_volume( vl[0].id )
- log.error( err )
- uci_wrapper.set_error( err, True )
- uci_wrapper.set_store_error( err, store_id=vol.id )
- conn.delete_volume( vl[0].id )
- uci_wrapper.set_store_deleted( vl[0].id )
- return
- else:
- err = "Volume '" + vol.id +"' not found by EC2 after being created."
- log.error( err )
- uci_wrapper.set_store_error( err, store_id=vol.id )
- uci_wrapper.set_error( err, True )
- return
-
- # Get private key for given instance
- pk = uci_wrapper.get_key_pair_material()
- if pk == None: #If pk does not exist, create it
- self.check_key_pair( uci_wrapper, conn )
- pk = uci_wrapper.get_key_pair_material()
-
- # Get working directory for this UCI and store pk into a file
- wd = uci_wrapper.get_uci_working_directory()
- if not os.path.exists( wd ):
- os.mkdir( wd )
- pk_file_path = os.path.join( wd, "pk" )
-
- if pk != None:
- # Save private key to a file
- pk_file = open( pk_file_path, "w" )
- pk_file.write( pk )
- pk_file.close()
- else:
- err = "ERROR: Private key not available for this UCI."
- log.error( err )
- uci_wrapper.set_store_error( err, store_id=vol.id )
- uci_wrapper.set_error( err, True )
- return
-
- if os.path.exists( pk_file_path ):
- # Change permissions of the file - this is required by later used ssh
- os.chmod( pk_file_path, stat.S_IRUSR | stat.S_IWUSR )
-
- # Get # of storage volumes associated with this UCI to know as which device to connect new volume to the instance
- device_num = len( uci_wrapper.get_all_stores_in_status( store_status.IN_USE ) ) + 5 # First device num is 5, so all subsequent ones should follow
-
- # Get instance that the new storage volume is to be attached to. Although a list is returned,
- # only 1 instance can be in 'adding-storage' state (because, for now, only 1 instance is assoc. with
- # each UCI) and volume can be attached to only to it
- il = uci_wrapper.get_instaces_in_state( instance_states.ADDING )
- if len( il ) > 0:
- # Attach new volume to the instance
- log.debug( "Attaching new storage volume '%s' to UCI '%s' as device '%s'" %
- ( vol.id, uci_wrapper.get_name(), device_num ) )
- try:
- vol_status = conn.attach_volume( vol.id, il[0].instance_id, device_num )
- except boto.exception.EC2ResponseError, e:
- err = "Attaching just created storage volume '" + vol.id + "'to instance '" + \
- il[0].instance_id + "' as device '" + str( device_num ) + "' failed: " + str( e )
- log.error( err )
- uci_wrapper.set_store_error( err, store_id=vol.id )
- uci_wrapper.set_error( err, True )
- return
- # For a while, keep checking attachment status of the new volume
- for i in range(30):
- log.debug( "Checking attachment status of new volume '%s': '%s'" % ( vol.id, vol_status ) )
- if vol_status == 'attached':
- uci_wrapper.set_store_status( vol.id, vol_status )
- uci_wrapper.set_store_device( vol.id, device_num )
- break
- if i == 29:
- err = "Storage volume '" + vol.id + "' failed to attach to instance '" + il[0].instance_id + \
- "'. Manual check needed."
- log.error( err )
- uci_wrapper.set_store_error( err, store_id=vol.id )
- uci_wrapper.set_error( err, False )
- return
-
- time.sleep(4)
- vol_list = conn.get_all_volumes( [vol.id] )
- for v in vol_list:
- vol_status = v.attachment_state()
-
- # Once storage volume is attached, add it to the zpool by issuing system level command
- cmd = 'ssh -o StrictHostKeyChecking=no -i '+ pk_file_path +' root@'+il[0].public_dns+' "zpool add galaxyData c7d' + str( device_num )+'"'
- log.debug( "Adding new storage volume to zpool cmd: %s" % cmd )
- stdout = os.system( cmd )
- if stdout != 0:
- err = "Adding newly created storage volume to zpool on instance '" + il[0].instance_id + \
- "' failed. Error code: " + str( stdout )
- log.error( err )
- uci_wrapper.set_store_error( err, store_id=vol.id )
- uci_wrapper.set_error( err, False )
- return
- else:
- err = "No instance(s) found in 'adding-storage' state. New disk not added to UCI's zpool."
- log.error( err )
- uci_wrapper.set_store_error( err, store_id=vol.id )
- uci_wrapper.set_error( err, True )
- return
-
- # Update UCI's total storage size
- uci_wrapper.set_uci_total_size( uci_wrapper.get_uci_total_size() + vol.size )
- # Reset UCI's and instance's state
- uci_wrapper.change_state( uci_state=uci_states.RUNNING, instance_id=il[0].instance_id, i_state=instance_states.RUNNING )
- log.debug( "Successfully added storage volume '%s' to UCI '%s'." % ( vol.id, uci_wrapper.get_name() ) )
-
- def dummy_start_uci( self, uci_wrapper ):
-
- uci = uci_wrapper.get_uci()
- log.debug( "Dummy start UCI '%s'" % uci.name )
-
-
-# uci_wrapper.change_state( uci_state.PENDING )
-# log.debug( "Sleeping a bit... (%s)" % uci.name )
-# time.sleep(20)
-# log.debug( "Woke up! (%s)" % uci.name )
-
- def start_uci( self, uci_wrapper ):
- """
- Start instance(s) of given UCI on the cloud.
- """
- if uci_wrapper.get_uci_state() != uci_states.ERROR:
- conn = self.get_connection( uci_wrapper )
- self.check_key_pair( uci_wrapper, conn )
- if uci_wrapper.get_key_pair_name() == None:
- err = "Key pair not found"
- log.error( "%s for UCI '%s'." % ( err, uci_wrapper.get_name() ) )
- uci_wrapper.set_error( err + ". Try resetting the state and starting the instance again.", True )
- return
-
- i_indexes = uci_wrapper.get_instances_indexes( state=instance_states.SUBMITTED ) # Get indexes of i_indexes associated with this UCI that are in 'submitted' state
- log.debug( "Starting instances with IDs: '%s' associated with UCI '%s' " % ( i_indexes, uci_wrapper.get_name(), ) )
- if len( i_indexes ) > 0:
- for i_index in i_indexes:
- # Get machine image for current instance
- mi_id = self.get_mi_id( uci_wrapper, i_index )
- log.debug( "mi_id: %s, uci_wrapper.get_key_pair_name(): %s" % ( mi_id, uci_wrapper.get_key_pair_name() ) )
- uci_wrapper.set_mi( i_index, mi_id )
-
- if mi_id != None:
- # Check if galaxy security group exists (and create it if it does not)
- log.debug( "Setting up '%s' security group." % self.security_group )
- try:
- conn.get_all_security_groups( [self.security_group] ) # security groups
- except boto.exception.EC2ResponseError, e:
- if e.code == 'InvalidGroup.NotFound':
- log.info( "No security group found, creating security group '%s'" % self.security_group )
- try:
- gSecurityGroup = conn.create_security_group(self.security_group, 'Security group for Galaxy.')
- gSecurityGroup.authorize( 'tcp', 80, 80, '0.0.0.0/0' ) # Open HTTP port
- gSecurityGroup.authorize( 'tcp', 22, 22, '0.0.0.0/0' ) # Open SSH port
- except boto.exception.EC2ResponseError, ee:
- err = "EC2 response error while creating security group: " + str( ee )
- log.error( err )
- uci_wrapper.set_error( err, True )
- else:
- err = "EC2 response error while retrieving security group: " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
-
-
- if uci_wrapper.get_uci_state() != uci_states.ERROR:
- # Start an instance
- log.debug( "Starting instance for UCI '%s'" % uci_wrapper.get_name() )
- #TODO: Once multiple volumes can be attached to a single instance, update 'userdata' composition
- # Compose user data; for storage volumes, separate multiple volumes with a colon (:) ensuring that
- # the last volume in the list is not followed by a colon.
- stores = uci_wrapper.get_all_stores()
- volume_ids = ""
- if len( stores ) > 0:
- for i, store in enumerate( stores ):
- volume_ids += store.volume_id
- if i < len( stores )-1:
- volume_ids += ":"
- else:
- err = "No storage volumes found that are associated with UCI '%s'" + uci_wrapper.get_name()
- log.error( err )
- uci_wrapper.set_error( err, True )
- return
- userdata = volume_ids+"|"+uci_wrapper.get_access_key()+"|"+uci_wrapper.get_secret_key()
- log.debug( "Using following command: conn.run_instances( image_id='%s', key_name='%s', security_groups=['%s'], user_data=[OMITTED], instance_type='%s', placement='%s' )"
- % ( mi_id, uci_wrapper.get_key_pair_name(), self.security_group, uci_wrapper.get_instance_type( i_index ), uci_wrapper.get_uci_availability_zone() ) )
- # Start an instance
- reservation = None
- try:
- reservation = conn.run_instances( image_id=mi_id,
- key_name=uci_wrapper.get_key_pair_name(),
- security_groups=[self.security_group],
- user_data=userdata,
- instance_type=uci_wrapper.get_instance_type( i_index ),
- placement=uci_wrapper.get_uci_availability_zone() )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error when starting UCI '"+ uci_wrapper.get_name() +"': " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
- except Exception, ex:
- err = "Error when starting UCI '" + uci_wrapper.get_name() + "': " + str( ex )
- log.error( err )
- uci_wrapper.set_error( err, True )
- # Record newly available instance data into local Galaxy database
- if reservation:
- l_time = datetime.utcnow()
- # uci_wrapper.set_instance_launch_time( self.format_time( reservation.instances[0].launch_time ), i_index=i_index )
- uci_wrapper.set_instance_launch_time( l_time, i_index=i_index )
- if not uci_wrapper.uci_launch_time_set():
- uci_wrapper.set_uci_launch_time( l_time )
- try:
- uci_wrapper.set_reservation_id( i_index, str( reservation ).split(":")[1] )
- # TODO: if more than a single instance will be started through single reservation, change this reference to element [0]
- i_id = str( reservation.instances[0]).split(":")[1]
- uci_wrapper.set_instance_id( i_index, i_id )
- s = reservation.instances[0].state
- uci_wrapper.change_state( s, i_id, s )
- uci_wrapper.set_security_group_name( self.security_group, i_id=i_id )
- vol_id = uci_wrapper.get_store_volume_id( store_id=0 ) # TODO: Once more that one vol/UCI is allowed, update this!
- # Following line is pointless bc. general update updates status of volume to 'available'
- # before it actually connects to starting instance... This has been dealt w/ in general update method
- #uci_wrapper.set_store_status( vol_id, store_status.WAITING )
- uci_wrapper.set_store_instance( vol_id, i_id )
- log.debug( "Instance of UCI '%s' started, current state: '%s'" % ( uci_wrapper.get_name(), uci_wrapper.get_uci_state() ) )
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error when retrieving instance information for UCI '" + uci_wrapper.get_name() + "': " + str( e )
- log.error( err )
- uci_wrapper.set_error( err, True )
- else:
- log.error( "UCI '%s' is in 'error' state, starting instance was aborted." % uci_wrapper.get_name() )
- else:
- err = "No instances in state '"+ instance_states.SUBMITTED +"' found for UCI '" + uci_wrapper.get_name() + \
- "'. Nothing to start."
- log.error( err )
- uci_wrapper.set_error( err, True )
- else:
- log.error( "UCI '%s' is in 'error' state, starting instance was aborted." % uci_wrapper.get_name() )
-
- def stop_uci( self, uci_wrapper):
- """
- Stop all of cloud instances associated with given UCI.
- """
- conn = self.get_connection( uci_wrapper )
-
- # Get all instances associated with given UCI
- il = uci_wrapper.get_instances_ids() # instance list
- # Process list of instances and remove any references to empty instance id's
- for i in il:
- if i is None:
- il.remove( i )
- log.debug( 'List of instances being terminated: %s' % il )
- rl = conn.get_all_instances( il ) # Reservation list associated with given instances
-
- # Initiate shutdown of all instances under given UCI
- cnt = 0
- stopped = []
- not_stopped = []
- for r in rl:
- for inst in r.instances:
- log.debug( "Sending stop signal to instance '%s' associated with reservation '%s'." % ( inst, r ) )
- try:
- inst.stop()
- uci_wrapper.set_stop_time( datetime.utcnow(), i_id=inst.id )
- uci_wrapper.change_state( instance_id=inst.id, i_state=inst.update() )
- stopped.append( inst )
- except boto.exception.EC2ResponseError, e:
- not_stopped.append( inst )
- err = "EC2 response error when stopping instance '" + inst.instance_id + "': " + str(e)
- log.error( err )
- uci_wrapper.set_error( err, True )
-
- uci_wrapper.reset_uci_launch_time()
- log.debug( "Termination was initiated for all instances of UCI '%s'." % uci_wrapper.get_name() )
-
-
-# dbInstances = get_instances( trans, uci ) #TODO: handle list!
-#
-# # Get actual cloud instance object
-# cloudInstance = get_cloud_instance( conn, dbInstances.instance_id )
-#
-# # TODO: Detach persistent storage volume(s) from instance and update volume data in local database
-# stores = get_stores( trans, uci )
-# for i, store in enumerate( stores ):
-# log.debug( "Detaching volume '%s' to instance '%s'." % ( store.volume_id, dbInstances.instance_id ) )
-# mntDevice = store.device
-# volStat = None
-## Detaching volume does not work with Eucalyptus Public Cloud, so comment it out
-## try:
-## volStat = conn.detach_volume( store.volume_id, dbInstances.instance_id, mntDevice )
-## except:
-## log.debug ( 'Error detaching volume; still going to try and stop instance %s.' % dbInstances.instance_id )
-# store.attach_time = None
-# store.device = None
-# store.i_id = None
-# store.status = volStat
-# log.debug ( '***** volume status: %s' % volStat )
-#
-#
-# # Stop the instance and update status in local database
-# cloudInstance.stop()
-# dbInstances.stop_time = datetime.utcnow()
-# while cloudInstance.state != 'terminated':
-# log.debug( "Stopping instance %s state; current state: %s" % ( str( cloudInstance ).split(":")[1], cloudInstance.state ) )
-# time.sleep(3)
-# cloudInstance.update()
-# dbInstances.state = cloudInstance.state
-#
-# # Reset relevant UCI fields
-# uci.state = 'available'
-# uci.launch_time = None
-#
-# # Persist
-# session = trans.sa_session
-## session.save_or_update( stores )
-# session.save_or_update( dbInstances ) # TODO: Is this going to work w/ multiple instances stored in dbInstances variable?
-# session.save_or_update( uci )
-# session.flush()
-# trans.log_event( "User stopped cloud instance '%s'" % uci.name )
-# trans.set_message( "Galaxy instance '%s' stopped." % uci.name )
-
- def update( self ):
- """
- Run status update on all instances that are in 'running', 'pending', or 'shutting-down' state.
- Run status update on all storage volumes whose status is 'in-use', 'creating', or 'None'.
- Run status update on all snapshots whose status is 'pending' or 'delete'
- Run status update on any zombie UCIs, i.e., UCI's that is in 'submitted' state for an
- extended period of time.
-
- Reason behind this method is to sync state of local DB and real-world resources
- """
- log.debug( "Running general status update for %s UCIs..." % self.type )
- # Update instances
- instances = self.sa_session.query( model.CloudInstance ) \
- .filter( or_( model.CloudInstance.table.c.state==instance_states.RUNNING,
- model.CloudInstance.table.c.state==instance_states.PENDING,
- model.CloudInstance.table.c.state==instance_states.SHUTTING_DOWN ) ) \
- .all()
- for inst in instances:
- if self.type == inst.uci.credentials.provider.type:
- log.debug( "[%s] Running general status update on instance '%s'"
- % ( inst.uci.credentials.provider.type, inst.instance_id ) )
- self.update_instance( inst )
- # Update storage volume(s) associated with current instance
- stores = self.sa_session.query( model.CloudStore ) \
- .filter_by( uci_id=inst.uci_id, deleted=False ) \
- .all()
- for store in stores:
- if self.type == store.uci.credentials.provider.type: # and store.volume_id != None:
- log.debug( "[%s] Running general status update on store with local database ID: '%s'"
- % ( store.uci.credentials.provider.type, store.id ) )
- self.update_store( store )
-
- # Update storage volume(s)
- stores = self.sa_session.query( model.CloudStore ) \
- .filter( or_( model.CloudStore.table.c.status==store_status.CREATING,
-# model.CloudStore.table.c.status==store_status.IN_USE,
-# model.CloudStore.table.c.status==store_status.WAITING,
- model.CloudStore.table.c.status==None ) ) \
- .all()
- for store in stores:
- if self.type == store.uci.credentials.provider.type: # and store.volume_id != None:
- log.debug( "[%s] Running general status update on store with local database ID: '%s'"
- % ( store.uci.credentials.provider.type, store.id ) )
- self.update_store( store )
-# else:
-# log.error( "[%s] There exists an entry for UCI (%s) storage volume without an ID. Storage volume might have been created with "
-# "cloud provider though. Manual check is recommended." % ( store.uci.credentials.provider.type, store.uci.name ) )
-# store.uci.error = "There exists an entry in local database for a storage volume without an ID. Storage volume might have been created " \
-# "with cloud provider though. Manual check is recommended. After understanding what happened, local database entry for given " \
-# "storage volume should be updated."
-# store.status = store_status.ERROR
-# store.uci.state = uci_states.ERROR
-# store.uci.flush()
-# store.flush()
-
- # Update pending snapshots or delete ones marked for deletion
- snapshots = self.sa_session.query( model.CloudSnapshot ) \
- .filter( or_( model.CloudSnapshot.table.c.status == snapshot_status.PENDING, model.CloudSnapshot.table.c.status == snapshot_status.DELETE ) ) \
- .all()
- for snapshot in snapshots:
- if self.type == snapshot.uci.credentials.provider.type and snapshot.status == snapshot_status.PENDING:
- log.debug( "[%s] Running general status update on snapshot '%s'" % ( snapshot.uci.credentials.provider.type, snapshot.snapshot_id ) )
- self.update_snapshot( snapshot )
- elif self.type == snapshot.uci.credentials.provider.type and snapshot.status == snapshot_status.DELETE:
- log.debug( "[%s] Initiating deletion of snapshot '%s'" % ( snapshot.uci.credentials.provider.type, snapshot.snapshot_id ) )
- self.delete_snapshot( snapshot )
-
- # Attempt at updating any zombie UCIs (i.e., instances that have been in SUBMITTED state for longer than expected - see below for exact time)
- zombies = self.sa_session.query( model.UCI ).filter_by( state=uci_states.SUBMITTED ).all()
- for zombie in zombies:
- z_instances = self.sa_session.query( model.CloudInstance ) \
- .filter_by( uci_id=zombie.id ) \
- .filter( or_( model.CloudInstance.table.c.state != instance_states.TERMINATED,
- model.CloudInstance.table.c.state == None ) ) \
- .all()
- for z_inst in z_instances:
- if self.type == z_inst.uci.credentials.provider.type:
-# log.debug( "z_inst.id: '%s', state: '%s'" % ( z_inst.id, z_inst.state ) )
- td = datetime.utcnow() - z_inst.update_time
- if td.seconds > 180: # if instance has been in SUBMITTED state for more than 3 minutes
- log.debug( "[%s] Running zombie repair update on instance with DB id '%s'" % ( z_inst.uci.credentials.provider.type, z_inst.id ) )
- self.process_zombie( z_inst )
-
- def update_instance( self, inst ):
- """
- Update information in local database for given instance as it is obtained from cloud provider.
- Along with updating information about given instance, information about the UCI controlling
- this instance is also updated.
- """
- # Get credentials associated wit this instance
- uci_id = inst.uci_id
- uci = self.sa_session.query( model.UCI ).get( uci_id )
- self.sa_session.refresh( uci )
- conn = self.get_connection_from_uci( uci )
-
- # Get reservations handle for given instance
- try:
- rl= conn.get_all_instances( [inst.instance_id] )
- except boto.exception.EC2ResponseError, e:
- err = "Retrieving instance(s) from cloud failed for UCI '"+ uci.name +"' during general status update: " + str( e )
- log.error( err )
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.flush()
- return None
-
- # Because references to reservations are deleted shortly after instances have been terminated, getting an empty list as a response to a query
- # typically means the instance has successfully shut down but the check was not performed in short enough amount of time. Until an alternative solution
- # is found, below code sets state of given UCI to 'error' to indicate to the user something out of ordinary happened.
- if len( rl ) == 0:
- err = "Instance ID '"+inst.instance_id+"' was not found by the cloud provider. Instance might have crashed or otherwise been terminated."+ \
- "Manual check is recommended."
- log.error( err )
- inst.error = err
- uci.error = err
- inst.state = instance_states.TERMINATED
- uci.state = uci_states.ERROR
- uci.launch_time = None
- self.sa_session.add( inst )
- self.sa_session.add( uci )
- self.sa_session.flush()
- # Update instance status in local DB with info from cloud provider
- for r in rl:
- for i, cInst in enumerate( r.instances ):
- try:
- s = cInst.update()
- log.debug( "Checking state of cloud instance '%s' associated with UCI '%s' " \
- "and reservation '%s'. State='%s'" % ( cInst, uci.name, r, s ) )
- if s != inst.state:
- inst.state = s
- self.sa_session.add( inst )
- self.sa_session.flush()
- # After instance has shut down, ensure UCI is marked as 'available'
- if s == instance_states.TERMINATED and uci.state != uci_states.ERROR:
- uci.state = uci_states.AVAILABLE
- uci.launch_time = None
- self.sa_session.add( uci )
- self.sa_session.flush()
- # Making sure state of UCI is updated. Once multiple instances become associated with single UCI, this will need to be changed.
- if s != uci.state and s != instance_states.TERMINATED:
- uci.state = s
- self.sa_session.add( uci )
- self.sa_session.flush()
- if cInst.public_dns_name != inst.public_dns:
- inst.public_dns = cInst.public_dns_name
- self.sa_session.add( inst )
- self.sa_session.flush()
- if cInst.private_dns_name != inst.private_dns:
- inst.private_dns = cInst.private_dns_name
- self.sa_session.add( inst )
- self.sa_session.flush()
- except boto.exception.EC2ResponseError, e:
- err = "Updating instance status from cloud failed for UCI '"+ uci.name + \
- "' during general status update: " + str( e )
- log.error( err )
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.flush()
- return None
-
- def update_store( self, store ):
- """
- Update information in local database for given storage volume as it is obtained from cloud provider.
- Along with updating information about given storage volume, information about the UCI controlling
- this storage volume is also updated.
- """
- # Get credentials associated wit this store
- uci_id = store.uci_id
- uci = self.sa_session.query( model.UCI ).get( uci_id )
- self.sa_session.refresh( uci )
- conn = self.get_connection_from_uci( uci )
-
- # Get reservations handle for given store
- try:
- log.debug( "Retrieving reference to storage volume '%s' during update..." % store.volume_id )
- vl = conn.get_all_volumes( [store.volume_id] )
- except boto.exception.EC2ResponseError, e:
- err = "Retrieving volume(s) from cloud failed for UCI '"+ uci.name + "' during general status update: " + str( e )
- log.error( err )
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.flush()
- return None
-
- # Update store status in local DB with info from cloud provider
- if len(vl) > 0:
- try:
- log.debug( "General status update for storage volume '%s'; current status: '%s'" % (store.volume_id, vl[0].status ) )
- if store.status != vl[0].status:
- # In case something failed during creation of UCI but actual storage volume was created and yet
- # UCI state remained as 'new', try to remedy this by updating UCI state here
- if ( store.status == None ) and ( store.volume_id != None ):
- uci.state = vl[0].status
- self.sa_session.add( uci )
- self.sa_session.flush()
- # If UCI was marked in state 'CREATING', update its status to reflect new status
- elif ( uci.state == uci_states.CREATING ):
- uci.state = vl[0].status
- self.sa_session.add( uci )
- self.sa_session.flush()
-
- store.status = vl[0].status
- self.sa_session.add( store )
- self.sa_session.flush()
- # Boto does not seem to be reporting these values although fields exist so comment them out...
-# log.debug( "vl[0].instance_id: '%s'" % vl[0].instance_id )
-# if store.inst != None:
-# if store.inst.instance_id != vl[0].instance_id:
-# store.inst.instance_id = vl[0].instance_id
-# self.sa_session.add( store )
-# self.sa_session.flush()
-# log.debug( "vl[0].attach_time: '%s'" % vl[0].attach_time )
-# if store.attach_time != vl[0].attach_time:
-# store.attach_time = vl[0].attach_time
-# self.sa_session.add( store )
-# self.sa_session.flush()
-## log.debug( "vl[0].device: '%s'" % vl[0].device )
-# if store.device != vl[0].device:
-# store.device = vl[0].device
-# self.sa_session.add( store )
-# self.sa_session.flush()
- except boto.exception.EC2ResponseError, e:
- err = "Updating status of volume(s) from cloud failed for UCI '"+ uci.name + \
- "' during general status update: " + str( e )
- log.error( err )
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.flush()
- return None
- else:
- err = "No storage volumes returned by cloud provider on general update for volume with id: " + store.volume_id
- log.error( "%s for UCI '%s'" % ( err, uci.name ) )
- store.status = store_status.ERROR
- store.error = err
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.add( store )
- self.sa_session.flush()
-
- def update_snapshot( self, snapshot ):
- """
- Update information in local database for given snapshot as it is obtained from cloud provider.
- Along with updating information about given snapshot, information about the UCI controlling
- this snapshot is also updated.
- """
- # Get credentials associated wit this store
- uci_id = snapshot.uci_id
- uci = self.sa_session.query( model.UCI ).get( uci_id )
- self.sa_session.refresh( uci )
- conn = self.get_connection_from_uci( uci )
-
- try:
- log.debug( "Updating status of snapshot '%s'" % snapshot.snapshot_id )
- snap = conn.get_all_snapshots( [snapshot.snapshot_id] )
- if len( snap ) > 0:
- log.debug( "Snapshot '%s' status: %s" % ( snapshot.snapshot_id, snap[0].status ) )
- snapshot.status = snap[0].status
- self.sa_session.add( snapshot )
- self.sa_session.flush()
- else:
- err = "No snapshots returned by EC2 on general update"
- log.error( "%s for UCI '%s'" % ( err, uci.name ) )
- snapshot.status = snapshot_status.ERROR
- snapshot.error = err
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.add( snapshot )
- self.sa_session.flush()
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while updating snapshot status: " + str( e )
- log.error( err )
- snapshot.status = snapshot_status.ERROR
- snapshot.error = err
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.add( snapshot )
- self.sa_session.flush()
- except Exception, ex:
- err = "Error while updating snapshot status: " + str( ex )
- log.error( err )
- snapshot.status = snapshot_status.ERROR
- snapshot.error = err
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.add( snapshot )
- self.sa_session.flush()
-
- def delete_snapshot( self, snapshot ):
- """
- Initiate deletion of given snapshot from cloud provider.
- """
- if snapshot.status == snapshot_status.DELETE:
- # Get credentials associated wit this store
- uci_id = snapshot.uci_id
- uci = self.sa_session.query( model.UCI ).get( uci_id )
- self.sa_session.refresh( uci )
- conn = self.get_connection_from_uci( uci )
-
- try:
- log.debug( "Deleting snapshot '%s'" % snapshot.snapshot_id )
- snap = conn.delete_snapshot( snapshot.snapshot_id )
- if snap == True:
- snapshot.deleted = True
- snapshot.status = snapshot_status.DELETED
- self.sa_session.add( snapshot )
- self.sa_session.flush()
- return snap
- except boto.exception.EC2ResponseError, e:
- err = "EC2 response error while deleting snapshot: " + str( e )
- log.error( err )
- snapshot.status = snapshot_status.ERROR
- snapshot.error = err
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.add( snapshot )
- self.sa_session.flush()
- except Exception, ex:
- err = "Error while deleting snapshot: " + str( ex )
- log.error( err )
- snapshot.status = snapshot_status.ERROR
- snapshot.error = err
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.add( snapshot )
- self.sa_session.flush()
- else:
- err = "Cannot delete snapshot '"+snapshot.snapshot_id+"' because its status is '"+snapshot.status+"'. Only snapshots with '" + \
- snapshot_status.COMPLETED+"' status can be deleted."
- log.error( err )
- snapshot.error = err
- self.sa_session.add( snapshot )
- self.sa_session.flush()
-
- def process_zombie( self, inst ):
- """
- Attempt at discovering if starting a cloud instance was successful but local database was not updated
- accordingly or if something else failed and instance was never started. Currently, no automatic
- repairs are being attempted; instead, appropriate error messages are set.
- """
- uci_id = inst.uci_id
- uci = self.sa_session.query( model.UCI ).get( uci_id )
- self.sa_session.refresh( uci )
-
- # Check if any instance-specific information was written to local DB; if 'yes', set instance and UCI's error message
- # suggesting manual check.
- if inst.launch_time != None or inst.reservation_id != None or inst.instance_id != None:
- # Try to recover state - this is best-case effort, so if something does not work immediately, not
- # recovery steps are attempted. Recovery is based on hope that instance_id is available in local DB; if not,
- # report as error.
- # Fields attempting to be recovered are: reservation_id, instance status, and launch_time
- if inst.instance_id != None:
- conn = self.get_connection_from_uci( uci )
- rl = conn.get_all_instances( [inst.instance_id] ) # reservation list
- # Update local DB with relevant data from instance
- if inst.reservation_id == None:
- try:
- inst.reservation_id = str(rl[0]).split(":")[1]
- except: # something failed, so skip
- pass
-
- try:
- state = rl[0].instances[0].update()
- inst.state = state
- uci.state = state
- self.sa_session.add( inst )
- self.sa_session.add( uci )
- self.sa_session.flush()
- except: # something failed, so skip
- pass
-
- if inst.launch_time == None:
- try:
- launch_time = self.format_time( rl[0].instances[0].launch_time )
- inst.launch_time = launch_time
- self.sa_session.add( inst )
- self.sa_session.flush()
- if inst.uci.launch_time == None:
- uci.launch_time = launch_time
- self.sa_session.add( uci )
- self.sa_session.flush()
- except: # something failed, so skip
- pass
- else:
- err = "Starting a machine instance (DB id: '"+str(inst.id)+"') associated with this UCI '" + str(inst.uci.name) + \
- "' seems to have failed. Because it appears that cloud instance might have gotten started, manual check is recommended."
- inst.error = err
- inst.state = instance_states.ERROR
- inst.uci.error = err
- inst.uci.state = uci_states.ERROR
- log.error( err )
- self.sa_session.add( inst )
- self.sa_session.add( uci )
- self.sa_session.flush()
-
- else: #Instance most likely never got processed, so set error message suggesting user to try starting instance again.
- err = "Starting a machine instance (DB id: '"+str(inst.id)+"') associated with this UCI '" + str(inst.uci.name) + \
- "' seems to have failed. Because it appears that cloud instance never got started, it should be safe to reset state and try " \
- "starting the instance again."
- inst.error = err
- inst.state = instance_states.ERROR
- uci.error = err
- uci.state = uci_states.ERROR
- log.error( err )
- self.sa_session.add( inst )
- self.sa_session.add( uci )
- self.sa_session.flush()
-# uw = UCIwrapper( inst.uci )
-# log.debug( "Try automatically re-submitting UCI '%s'." % uw.get_name() )
-
- def get_connection_from_uci( self, uci ):
- """
- Establish and return connection to cloud provider. Information needed to do so is obtained
- directly from uci database object.
- """
- log.debug( 'Establishing %s cloud connection' % self.type )
- a_key = uci.credentials.access_key
- s_key = uci.credentials.secret_key
- # Get connection
- try:
- region = RegionInfo( None, uci.credentials.provider.region_name, uci.credentials.provider.region_endpoint )
- conn = EC2Connection( aws_access_key_id=a_key,
- aws_secret_access_key=s_key,
- is_secure=uci.credentials.provider.is_secure,
- region=region,
- path=uci.credentials.provider.path )
- except boto.exception.EC2ResponseError, e:
- err = "Establishing connection with cloud failed: " + str( e )
- log.error( err )
- uci.error = err
- uci.state = uci_states.ERROR
- self.sa_session.add( uci )
- self.sa_session.flush()
- return None
-
- return conn
-
-# def updateUCI( self, uci ):
-# """
-# Runs a global status update on all storage volumes and all instances that are
-# associated with specified UCI
-# """
-# conn = self.get_connection( uci )
-#
-# # Update status of storage volumes
-# vl = model.CloudStore.filter( model.CloudInstance.table.c.uci_id == uci.id ).all()
-# vols = []
-# for v in vl:
-# vols.append( v.volume_id )
-# try:
-# volumes = conn.get_all_volumes( vols )
-# for i, v in enumerate( volumes ):
-# uci.store[i].i_id = v.instance_id
-# uci.store[i].status = v.status
-# uci.store[i].device = v.device
-# uci.store[i].flush()
-# except:
-# log.debug( "Error updating status of volume(s) associated with UCI '%s'. Status was not updated." % uci.name )
-# pass
-#
-# # Update status of instances
-# il = model.CloudInstance.filter_by( uci_id=uci.id ).filter( model.CloudInstance.table.c.state != 'terminated' ).all()
-# instanceList = []
-# for i in il:
-# instanceList.append( i.instance_id )
-# log.debug( 'instanceList: %s' % instanceList )
-# try:
-# reservations = conn.get_all_instances( instanceList )
-# for i, r in enumerate( reservations ):
-# uci.instance[i].state = r.instances[0].update()
-# log.debug('updating instance %s; status: %s' % ( uci.instance[i].instance_id, uci.instance[i].state ) )
-# uci.state = uci.instance[i].state
-# uci.instance[i].public_dns = r.instances[0].dns_name
-# uci.instance[i].private_dns = r.instances[0].private_dns_name
-# uci.instance[i].flush()
-# uci.flush()
-# except:
-# log.debug( "Error updating status of instances associated with UCI '%s'. Instance status was not updated." % uci.name )
-# pass
-
- # --------- Helper methods ------------
-
- def format_time( self, time ):
- dict = {'T':' ', 'Z':''}
- for i, j in dict.iteritems():
- time = time.replace(i, j)
- return time
-
--- a/templates/cloud/view_instance.mako
+++ /dev/null
@@ -1,142 +0,0 @@
-<%inherit file="/base.mako"/>
-<%def name="title()">Live instance details</%def>
-
-<%
- # Because of the one-to-many relationship between liveInstance (i.e., UCI) and actual instances, need to know
- # which one is currently active. Because only one instance of UCI can be alive at any point in time, simply
- # select the most recent one.
- # TODO: Once individual UCI's will be able to start more than one instance, this will need to be fixed
- #i_id = len(liveInstance.instance) - 1
-%>
-
-<h2>Live instance details</h2>
-
-%if liveInstance:
- <ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( action='list' )}">
- <img src="${h.url_for('/static/images/silk/resultset_previous.png')}" />
- <span>Return to cloud management console</span>
- </a>
- </li>
- </ul>
-
- <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
- <tr>
- <td> Instance name: </td>
- <td>
- ${liveInstance.uci.name}
- <a id="li-popup" class="popup-arrow" style="display: none;">▼</a>
- </td>
- <td>
- <div popupmenu="li-popup">
- <a class="action-button" href="${h.url_for( action='rename_uci', id=trans.security.encode_id(liveInstance.uci.id) )}">Rename</a>
- <a class="action-button" confirm="Are you sure you want to stop instance '${liveInstance.uci.name}'?" href="${h.url_for( action='stop', id=trans.security.encode_id(liveInstance.uci.id) )}">Stop</a>
- </div>
- </td>
- </tr>
- <tr>
- <td> Date created: </td>
- <td> ${str(liveInstance.uci.create_time)[:16]}
- <%
- context.write( ' UTC (' )
- context.write( str(h.date.distance_of_time_in_words (liveInstance.uci.create_time, h.date.datetime.utcnow() ) ) )
- %> ago)
- </td>
- </tr>
- <tr>
- <td> Alive since: </td>
- <td> ${str(liveInstance.launch_time)[:16]}
- <%
- context.write( ' UTC (' )
- context.write( str(h.date.distance_of_time_in_words (liveInstance.launch_time, h.date.datetime.utcnow() ) ) )
- %> ago)
- </td>
- </tr>
- %if liveInstance.instance_id != None:
- <tr>
- <td> Instance ID: </td>
- <td> ${liveInstance.instance_id} </td>
- </tr>
- %endif
- %if liveInstance.reservation_id != None:
- <tr>
- <td> Reservation ID: </td>
- <td> ${liveInstance.reservation_id} </td>
- </tr>
- %endif
- <tr>
- <td> AMI: </td>
- <td> ${liveInstance.image.image_id} </td>
- </tr>
- <tr>
- <td> State:</td>
- <td> ${liveInstance.state} </td>
- </tr>
- <tr>
- <td> Type:</td>
- <td> ${liveInstance.type} </td>
- </tr>
- <tr>
- <td> Storage size:</td>
- <td> ${liveInstance.uci.total_size} </td>
- </tr>
- %if liveInstance.public_dns != None and liveInstance.public_dns != '':
- <tr>
- <td> Public DNS:</td>
- <%
- lnk="http://"+str(liveInstance.public_dns)
- %>
- <td><a href="${lnk}" target="_blank">${liveInstance.public_dns}</a></td>
- </tr>
- %endif
- %if liveInstance.private_dns != None and liveInstance.private_dns != '':
- <tr>
- <td> Private DNS:</td>
- <td> ${liveInstance.private_dns}</td>
- </tr>
- %endif
- %if liveInstance.security_group != None:
- <tr>
- <td> Security group zone:</td>
- <td> ${liveInstance.security_group} </td>
- </tr>
- %endif
- %if liveInstance.availability_zone != None:
- <tr>
- <td> Availabilty zone:</td>
- <td> ${liveInstance.availability_zone} </td>
- </tr>
- %endif
- %if liveInstance.uci.key_pair_name != None:
- <tr>
- <td> Keypair file name:</td>
- <td> ${liveInstance.uci.key_pair_name} </td>
- </tr>
- %endif
- %if liveInstance.uci.key_pair_material != None:
- <tr>
- <td> Keypair material:</td>
- <td>
- <div id="short">
- <a onclick="document.getElementById('full').style.display = 'block';
- document.getElementById('short').style.display = 'none'; return 0"
- href="javascript:void(0)">
- + Show
- </a>
- </div>
- <div id="full" style="DISPLAY: none">
- <a onclick="document.getElementById('short').style.display = 'block';
- document.getElementById('full').style.display = 'none'; return 0;"
- href="javascript:void(0)">
- - Hide</a>
- ${liveInstance.uci.key_pair_material}<br/>
- </div>
- </td>
- </tr>
- %endif
-
- </table>
-%else:
- There is no live instance under that name.
-%endif
--- a/templates/webapps/galaxy/admin/index.mako
+++ b/templates/webapps/galaxy/admin/index.mako
@@ -118,14 +118,6 @@
<div class="toolTitle"><a href="${h.url_for( controller='requests_admin', action='list')}" target="galaxy_main">Manage requests</a></div></div></div>
- <div class="toolSectionTitle">
- <span>Cloud</span>
- </div>
- <div class="toolSectionBody">
- <div class="toolSectionBg">
- <div class="toolTitle"><a href="${h.url_for( controller='cloud', action='list_machine_images' )}" target="galaxy_main">Manage machine images</a></div>
- </div>
- </div></div></div></div>
--- a/lib/galaxy/web/controllers/cloud.py
+++ /dev/null
@@ -1,1276 +0,0 @@
-from galaxy.web.base.controller import *
-
-import pkg_resources
-pkg_resources.require( "simplejson" )
-import simplejson
-import urllib2
-
-from galaxy.tools.parameters import *
-from galaxy.tools import DefaultToolState
-from galaxy.tools.parameters.grouping import Repeat, Conditional
-from galaxy.datatypes.data import Data
-from galaxy.util.odict import odict
-from galaxy.util.bunch import Bunch
-from galaxy.util.topsort import topsort, topsort_levels, CycleError
-from galaxy.model.mapping import desc
-from galaxy.model.orm import *
-from datetime import datetime, timedelta
-
-pkg_resources.require( "WebHelpers" )
-from webhelpers import *
-
-# Required for Cloud tab
-import galaxy.eggs
-galaxy.eggs.require("boto")
-from boto.ec2.connection import EC2Connection
-from boto.ec2.regioninfo import RegionInfo
-from galaxy.cloud import CloudManager
-import boto.exception
-import boto
-
-import logging
-log = logging.getLogger( __name__ )
-
-uci_states = Bunch(
- NEW_UCI = "newUCI",
- NEW = "new",
- CREATING = "creating",
- DELETING_UCI = "deletingUCI",
- DELETING = "deleting",
- SUBMITTED_UCI = "submittedUCI",
- SUBMITTED = "submitted",
- SHUTTING_DOWN_UCI = "shutting-downUCI",
- SHUTTING_DOWN = "shutting-down",
- ADD_STORAGE_UCI = "add-storageUCI",
- ADD_STORAGE = "add-storage",
- AVAILABLE = "available",
- RUNNING = "running",
- PENDING = "pending",
- ERROR = "error",
- DELETED = "deleted",
- SNAPSHOT_UCI = "snapshotUCI",
- SNAPSHOT = "snapshot"
-)
-
-instance_states = Bunch(
- TERMINATED = "terminated",
- SUBMITTED = "submitted",
- RUNNING = "running",
- ADDING = "adding-storage",
- PENDING = "pending",
- SHUTTING_DOWN = "shutting-down",
- ERROR = "error"
-)
-
-store_status = Bunch(
- WAITING = "waiting",
- IN_USE = "in-use",
- ADDING = "adding",
- CREATING = "creating",
- DELETED = 'deleted',
- ERROR = "error"
-)
-
-snapshot_status = Bunch(
- SUBMITTED = 'submitted',
- PENDING = 'pending',
- COMPLETED = 'completed',
- DELETE = 'delete',
- DELETED= 'deleted',
- ERROR = "error"
-)
-
-class CloudController( BaseController ):
-
- @web.expose
- def index( self, trans ):
- return trans.fill_template( "cloud/index.mako" )
-
- @web.expose
- @web.require_login( "use Galaxy cloud" )
- def list( self, trans ):
- """
- Render cloud main page (management of cloud resources)
- """
- user = trans.get_user()
-
- cloudCredentials = trans.sa_session.query( model.CloudUserCredentials ) \
- .filter_by( user=user ) \
- .filter( model.CloudUserCredentials.table.c.deleted != True ) \
- .order_by( model.CloudUserCredentials.table.c.name ) \
- .all()
-
- cloudProviders = trans.sa_session.query( model.CloudProvider ) \
- .filter_by( user=user ) \
- .filter( model.CloudProvider.table.c.deleted != True ) \
- .order_by( model.CloudProvider.table.c.name ) \
- .all()
-
- liveInstances = trans.sa_session.query( model.UCI ) \
- .filter_by( user=user ) \
- .filter( or_( model.UCI.table.c.state==uci_states.RUNNING,
- model.UCI.table.c.state==uci_states.PENDING,
- model.UCI.table.c.state==uci_states.SUBMITTED,
- model.UCI.table.c.state==uci_states.SUBMITTED_UCI,
- model.UCI.table.c.state==uci_states.SHUTTING_DOWN,
- model.UCI.table.c.state==uci_states.SHUTTING_DOWN_UCI,
- model.UCI.table.c.state==uci_states.ADD_STORAGE,
- model.UCI.table.c.state==uci_states.ADD_STORAGE_UCI ) ) \
- .order_by( desc( model.UCI.table.c.update_time ) ) \
- .all()
-
- prevInstances = trans.sa_session.query( model.UCI ) \
- .filter_by( user=user, deleted=False ) \
- .filter( or_( model.UCI.table.c.state==uci_states.AVAILABLE,
- model.UCI.table.c.state==uci_states.NEW,
- model.UCI.table.c.state==uci_states.NEW_UCI,
- model.UCI.table.c.state==uci_states.CREATING,
- model.UCI.table.c.state==uci_states.ERROR,
- model.UCI.table.c.state==uci_states.DELETED,
- model.UCI.table.c.state==uci_states.DELETING,
- model.UCI.table.c.state==uci_states.DELETING_UCI,
- model.UCI.table.c.state==uci_states.SNAPSHOT,
- model.UCI.table.c.state==uci_states.SNAPSHOT_UCI ) ) \
- .order_by( desc( model.UCI.table.c.update_time ) ) \
- .all()
-
- # Check after update there are instances in pending state; if so, display message
- pendingInstances = trans.sa_session.query( model.UCI ) \
- .filter_by( user=user ) \
- .filter( or_( model.UCI.table.c.state==uci_states.PENDING,
- model.UCI.table.c.state==uci_states.SUBMITTED,
- model.UCI.table.c.state==uci_states.SUBMITTED_UCI ) ) \
- .all()
- if pendingInstances:
- trans.set_message( "Galaxy instance started. NOTE: Please wait about 5 minutes for the instance to "
- "start up. A button to connect to the instance will appear alongside "
- "instance description once cloud instance of Galaxy is ready." )
-
-# log.debug( "provider.is_secure: '%s'" % trans.sa_session.query( model.CloudProvider).filter_by(id=1).first().is_secure )
-# trans.sa_session.query( model.CloudProvider).filter_by(id=1).first().is_secure=False
-# trans.sa_session.flush()
-# log.debug( "provider.is_secure: '%s'" % trans.sa_session.query( model.CloudProvider).filter_by(id=1).first().is_secure )
-
-# log.debug( "image: '%s'" % model.CloudImage.is_secure )
-
- return trans.fill_template( "cloud/configure_cloud.mako",
- cloudCredentials = cloudCredentials,
- liveInstances = liveInstances,
- prevInstances = prevInstances,
- cloudProviders = cloudProviders )
-
- # ----- UCI methods -----
-
- @web.expose
- @web.require_login( "use Galaxy cloud" )
- def configure_new_uci( self, trans, instanceName='', credName='', volSize='', zone='' ):
- """
- Configure and add new cloud instance to user's instance pool
- """
- inst_error = vol_error = cred_error = None
- error = {}
- user = trans.get_user()
-
- if instanceName:
- # Check if volume size is entered as an integer
- try:
- volSize = int( volSize )
- except ValueError:
- error['vol_error'] = "Volume size must be integer value between 1 and 1000."
-
- # Create new user configured instance
- try:
- if trans.sa_session.query( model.UCI ) \
- .filter_by (user=user, deleted=False, name=instanceName ) \
- .first():
- error['inst_error'] = "An instance with that name already exist."
- elif instanceName=='' or len( instanceName ) > 255:
- error['inst_error'] = "Instance name must be between 1 and 255 characters long."
- elif credName=='':
- error['cred_error'] = "You must select credentials."
- elif volSize == '':
- error['vol_error'] = "You must specify volume size as an integer value between 1 and 1000."
- elif ( int( volSize ) < 1 ) or ( int( volSize ) > 1000 ):
- error['vol_error'] = "Volume size must be integer value between 1 and 1000."
- elif zone=='':
- error['zone_error'] = "You must select a zone where this UCI will be registered."
- else:
- # Capture user configured instance information
- uci = model.UCI()
- uci.name = instanceName
- creds = trans.sa_session.query( model.CloudUserCredentials ) \
- .filter( model.CloudUserCredentials.table.c.name==credName ).first()
- uci.credentials = creds
- uci.user= user
- uci.total_size = volSize # This is OK now because new instance is being created and only one storage volume can be created at UCI creation time
- uci.state = uci_states.NEW_UCI
-
- storage = model.CloudStore()
- storage.user = user
- storage.uci = uci
- storage.size = volSize
- # If '(any)' zone was selected, just choose the first one that's available
- if zone == "(any)":
- zones = None
- conn = get_connection( trans, creds )
- if conn != None:
- try:
- zones = conn.get_all_zones()
- if len( zones ) > 0:
- zone = str( zones[0] ).split(':')[1]
- except boto.exception.EC2ResponseError, e:
- log.error( "Retrieving zones for credentials '%s' failed: %s" % ( storedCred.name, e ) )
- providersToZones[storedCred.name] = [ "Retrieving zones failed: " + str( e ) ]
- storage.availability_zone = zone
- storage.status = store_status.ADDING
- # Persist
- session = trans.sa_session
- session.add( uci )
- session.add( storage )
- session.flush()
- # Log and display the management page
- trans.log_event( "User configured new cloud instance: '%s'" % instanceName )
- trans.set_message( "New Galaxy instance '%s' configured. Once instance status shows 'available' you will be able to start the instance." % instanceName )
- return self.list( trans )
- except AttributeError, ae:
- inst_error = "No registered cloud images. You must contact administrator to add some before proceeding."
- log.debug("AttributeError when registering new UCI '%s': %s " % ( instanceName, str( ae ) ) )
- else:
- storedCreds = trans.sa_session.query( model.CloudUserCredentials ).filter_by( user=user, deleted=False ).all()
- if len( storedCreds ) == 0:
- return trans.show_error_message( "You must register credentials before configuring a Galaxy cloud instance." )
- # Create dict mapping of cloud-providers-to-zones available by those providers
- providersToZones = {}
- for storedCred in storedCreds:
- zones = None
- conn = get_connection( trans, storedCred )
- if conn != None:
- avail_zones = []
- try:
- zones = conn.get_all_zones()
- if len( zones ) > 0:
- avail_zones.append( "(any)" )
- for z in zones:
- z = str( z ).split(':')[1]
- avail_zones.append( z )
- providersToZones[storedCred.name] = avail_zones
- except boto.exception.EC2ResponseError, e:
- log.error( "Retrieving zones for credentials '%s' failed: %s" % ( storedCred.name, e ) )
- providersToZones[storedCred.name] = [ "Retrieving zones failed: " + str( e ) ]
- else:
- providersToZones[storedCred.name] = ['Connection with cloud provider could not be established.']
-
- # Hard-coded solution
- # if storedCred.provider.storedCred.provider.region_name == 'us-east-1':
- # ec2_zones = ['us-east-1a', 'us-east-1b', 'us-east-1c', 'us-east-1d']
- # providersToZones[storedCred.name] = ec2_zones
- # elif storedCred.provider.region_name == 'eu-west-1':
- # ec2_zones = ['eu-west-1a', 'eu-west-1b']
- # providersToZones[storedCred.name] = ec2_zones
- # elif storedCred.provider.type == 'eucalyptus':
- # providersToZones[storedCred.name] = ['epc']
- # else:
- # providersToZones[storedCred.name] = ['Unknown provider zone']
-
- return trans.fill_template( "cloud/configure_uci.mako",
- instanceName = instanceName,
- credName = storedCreds,
- volSize = volSize,
- zone = zone,
- error = error,
- providersToZones = providersToZones )
-
- @web.expose
- @web.require_login( "start Galaxy cloud instance" )
- def start( self, trans, id, type='m1.small' ):
- """
- Start a new cloud resource instance
- """
- user = trans.get_user()
- uci = get_uci( trans, id )
- stores = get_stores( trans, uci )
- # Ensure instance is available and then store relevant data
- # into DB to initiate instance startup by cloud manager
- if ( len(stores) is not 0 ) and ( uci.state == uci_states.AVAILABLE ):
- instance = model.CloudInstance()
- instance.user = user
- instance.uci = uci
- instance.state = instance_states.SUBMITTED
- instance.availability_zone = stores[0].availability_zone # Bc. all EBS volumes need to be in the same avail. zone, just check 1st
- instance.type = type
- uci.state = uci_states.SUBMITTED_UCI
- # Persist
- session = trans.sa_session
- session.add( instance )
- session.add( uci )
- session.flush()
- # Log
- trans.log_event ("User initiated starting of UCI '%s'." % uci.name )
- trans.set_message( "Galaxy instance started. NOTE: Please wait about 5 minutes for the instance to "
- "start up. A button to connect to the instance will appear alongside "
- "instance description once cloud instance of Galaxy is ready." )
- return self.list( trans )
-
- if len(stores) == 0:
- error( "This instance does not have any storage volumes associated it and thus cannot be started." )
- else:
- error( "Cannot start instance that is in state '%s'." % uci.state )
- return self.list( trans )
-
- @web.expose
- @web.require_login( "stop Galaxy cloud instance" )
- def stop( self, trans, id ):
- """
- Stop a cloud UCI instance.
- """
- uci = get_uci( trans, id )
- if ( uci.state != uci_states.DELETING ) and \
- ( uci.state != uci_states.DELETING_UCI ) and \
- ( uci.state != uci_states.ERROR ) and \
- ( uci.state != uci_states.SHUTTING_DOWN_UCI ) and \
- ( uci.state != uci_states.SHUTTING_DOWN ) and \
- ( uci.state != uci_states.ADD_STORAGE_UCI ) and \
- ( uci.state != uci_states.ADD_STORAGE ) and \
- ( uci.state != uci_states.AVAILABLE ):
- uci.state = uci_states.SHUTTING_DOWN_UCI
- session = trans.sa_session
- session.add( uci )
- session.flush()
- trans.log_event( "User stopped cloud instance '%s' (id: %s)" % ( uci.name, uci.id ) )
- trans.set_message( "Stopping of Galaxy instance '%s' initiated." % uci.name )
-
- return self.list( trans )
-
- trans.show_error_message( "Cannot stop instance that is in state '%s'." % uci.state )
- return self.list( trans )
-
- @web.expose
- @web.require_login( "use Galaxy cloud" )
- def set_uci_state( self, trans, id, state='available', clear_error=True ):
- """
- Sets state of UCI to given state, optionally resets error field, and resets UCI's launch time field to 'None'.
- """
- uci = get_uci( trans, id )
- uci.state = state
- if clear_error:
- uci.error = None
- uci.launch_time = None
- trans.sa_session.flush()
- trans.set_message( "Instance '%s' state reset." % uci.name )
- return self.list( trans )
-
- @web.expose
- @web.require_login( "view instance details" )
- def view_uci_details( self, trans, id=None ):
- """
- View details about running instance
- """
- uci = get_uci( trans, id )
- instances = get_instances( trans, uci ) # TODO: Handle list (will probably need to be done in mako template)
-
- return trans.fill_template( "cloud/view_instance.mako",
- liveInstance = instances )
-
- @web.expose
- @web.require_login( "use Galaxy cloud" )
- def rename_uci( self, trans, id, new_name=None ):
- instance = get_uci( trans, id )
- if new_name is not None:
- if len(new_name) > 255:
- error( "Instance name must be less than 255 characters long." )
- user = trans.get_user()
- name_exists = trans.sa_session.query( model.UCI ) \
- .filter_by( user=user, name=new_name ) \
- .first()
- if name_exists:
- error( "Specified name ('%s') is already used by an existing instance. Please choose an alternative name." % new_name )
-
- # Update name in local DB
- instance.name = new_name
- trans.sa_session.flush()
- trans.set_message( "Instance renamed to '%s'." % new_name )
- return self.list( trans )
- else:
- return trans.show_form(
- web.FormBuilder( url_for( id=trans.security.encode_id(instance.id) ), "Rename instance", submit_text="Rename" )
- .add_text( "new_name", "Instance name", value=instance.name ) )
-
- @web.expose
- @web.require_login( "use Galaxy cloud" )
- def uci_usage_report( self, trans, id ):
- user = trans.get_user()
- id = trans.security.decode_id( id )
-
- prevInstances = trans.sa_session.query( model.CloudInstance ) \
- .filter_by( user=user, state=instance_states.TERMINATED, uci_id=id ) \
- .order_by( desc( model.CloudInstance.table.c.update_time ) ) \
- .all()
-
- return trans.fill_template( "cloud/view_usage.mako", prevInstances = prevInstances )
-
- @web.expose
- @web.require_login( "delete user configured Galaxy cloud instance" )
- def delete_uci( self, trans, id ):
- """
- Deletes User Configured Instance (UCI) from the cloud and local database. NOTE that this implies deletion of
- any and all storage associated with this UCI!
- """
- uci = get_uci( trans, id )
-
- if ( uci.state != uci_states.DELETING_UCI ) and ( uci.state != uci_states.DELETING ) and ( uci.state != uci_states.ERROR ):
- name = uci.name
- uci.state = uci_states.DELETING_UCI
- session = trans.sa_session
- session.add( uci )
- session.flush()
- trans.log_event( "User marked cloud instance '%s' for deletion." % name )
- trans.set_message( "Galaxy instance '%s' marked for deletion." % name )
- return self.list( trans )
-
- if uci.state != uci_states.ERROR:
- trans.set_message( "Cannot delete instance in state ERROR." )
- else:
- trans.set_message( "Instance '%s' is already marked for deletion." % uci.name )
- return self.list( trans )
-
- # ----- Snapshot methods -----
-
- @web.expose
- @web.require_login( "use Galaxy cloud" )
- def create_snapshot( self, trans, id ):
- user = trans.get_user()
- id = trans.security.decode_id( id )
- uci = get_uci( trans, id )
-
- stores = trans.sa_session.query( model.CloudStore ) \
- .filter_by( user=user, deleted=False, uci_id=id ) \
- .all()
-
- if ( len( stores ) > 0 ) and ( uci.state == uci_states.AVAILABLE ):
- for store in stores:
- snapshot = model.CloudSnapshot()
- snapshot.user = user
- snapshot.uci = uci
- snapshot.store = store
- snapshot.status = snapshot_status.SUBMITTED
- uci.state = uci_states.SNAPSHOT_UCI
- # Persist
- session = trans.sa_session
- session.add( snapshot )
- session.add( uci )
- session.flush()
- elif len( stores ) == 0:
- error( "No storage volumes found that are associated with this instance." )
- else:
- error( "Snapshot can be created only for an instance that is in 'available' state." )
-
- # Log and display the management page
- trans.log_event( "User initiated creation of new snapshot." )
- trans.set_message( "Creation of new snapshot initiated. " )
- return self.list( trans )
-
- @web.expose
- @web.require_login( "use Galaxy cloud" )
- def view_snapshots( self, trans, id=None ):
- """
- View details about any snapshots associated with given UCI
- """
- user = trans.get_user()
- id = trans.security.decode_id( id )
-
- snaps = trans.sa_session.query( model.CloudSnapshot ) \
- .filter_by( user=user, uci_id=id, deleted=False ) \
- .order_by( desc( model.CloudSnapshot.table.c.update_time ) ) \
- .all()
-
- return trans.fill_template( "cloud/view_snapshots.mako",
- snaps = snaps )
-
- @web.expose
- @web.require_login( "use Galaxy cloud" )
- def delete_snapshot( self, trans, uci_id=None, snap_id=None ):
- """
- Initiates deletion of a snapshot
- """
- user = trans.get_user()
- snap_id = trans.security.decode_id( snap_id )
- # Set snapshot as 'ready for deletion' to be picked up by general updater
- snap = trans.sa_session.query( model.CloudSnapshot ).get( snap_id )
-
- if snap.status == snapshot_status.COMPLETED:
- snap.status = snapshot_status.DELETE
- trans.sa_session.add( snap )
- trans.sa_session.flush()
- trans.set_message( "Snapshot '%s' is marked for deletion. Once the deletion is complete, it will no longer be visible in this list. "
- "Please note that this process may take up to a minute." % snap.snapshot_id )
- else:
- error( "Only snapshots in state 'completed' can be deleted. See the cloud provider directly "
- "if you believe the snapshot is available and can be deleted." )
-
- # Display new list of snapshots
- uci_id = trans.security.decode_id( uci_id )
- snaps = trans.sa_session.query( model.CloudSnapshot ) \
- .filter_by( user=user, uci_id=uci_id, deleted=False ) \
- .order_by( desc( model.CloudSnapshot.table.c.update_time ) ) \
- .all()
-
- return trans.fill_template( "cloud/view_snapshots.mako",
- snaps = snaps )
-
- # ----- Storage methods -----
-
- @web.expose
- @web.require_login( "add instance storage" )
- def add_storage( self, trans, id, vol_size=None ):
- error = None
- uci = get_uci( trans, id )
- stores = get_stores_in_status( trans, uci, store_status.IN_USE )
-
- # Start adding of storage making sure given UCI is running and that at least one
- # storage volume is attached to it (this is needed to by cloud controller to know
- # as which device to attach the new storage volume)
- if uci.state == uci_states.RUNNING and len( stores ) > 0:
- if vol_size is not None:
- try:
- vol_size = int( vol_size )
- except ValueError:
- error = "Volume size must be integer value between 1 and 1000."
-
- if not error:
- user = trans.get_user()
-
- storage = model.CloudStore()
- storage.user = user
- storage.uci = uci
- storage.size = vol_size
- storage.status = store_status.ADDING
-
- # Set state of instance - NOTE that this code will only work (with code in cloud controller)
- # for scenario where a UCI is associated with *1* compute instance!!!
- instances = get_instances( trans, uci )
- instances.state = instance_states.ADDING
-
- uci.state = uci_states.ADD_STORAGE_UCI
- # Persist
- session = trans.sa_session
- session.add( instances )
- session.add( storage )
- session.add( uci )
- session.flush()
- # Log and display the management page
- trans.log_event( "User added storage volume to UCI: '%s'" % uci.name )
- trans.set_message( "Adding of storage to instance '%s' initiated." % uci.name )
- return self.list( trans )
- else:
- error( "Storage can only be added to instances that are in state 'RUNNING' with existing " \
- "storage volume(s) already attached." )
-
- return trans.show_form(
- web.FormBuilder( url_for( id=trans.security.encode_id(uci.id) ), "Add storage to an instance", submit_text="Add" )
- .add_text( "vol_size", "Storage size (1-1000 GB)", value='', error=error ) )
-
- # ----- Image methods -----
- @web.expose
- @web.require_admin
- def add_new_image( self, trans, provider_type='', image_id='', manifest='', architecture='', state=None ):
- #id_error = arch_error = provider_error = manifest_error = None
- error = {}
- if provider_type or image_id or manifest or architecture:
- if provider_type=='':
- error['provider_error'] = "You must select cloud provider type for this machine image."
- elif image_id=='' or len( image_id ) > 255:
- error['id_error'] = "Image ID must be between 1 and 255 characters long."
- elif trans.sa_session.query( model.CloudImage ) \
- .filter_by( deleted=False ) \
- .filter( model.CloudImage.table.c.image_id == image_id ) \
- .first():
- error['id_error'] = "Image with ID '" + image_id + "' is already registered. \
- Please choose another ID."
- elif architecture=='':
- error['arch_error'] = "You must select architecture type for this machine image."
- else:
- # Create new image
- image = model.CloudImage()
- image.provider_type = provider_type
- image.image_id = image_id
- image.manifest = manifest
- image.architecture = architecture
- # Persist
- session = trans.sa_session
- session.add( image )
- session.flush()
- # Log and display the management page
- trans.log_event( "New cloud image added: '%s'" % image.image_id )
- trans.set_message( "Cloud image '%s' added." % image.image_id )
- if state:
- image.state = state
- images = trans.sa_session.query( model.CloudImage ).all()
- return trans.fill_template( '/cloud/list_images.mako', images=images )
-
- return trans.fill_template( "cloud/add_image.mako",
- provider_type = provider_type,
- image_id = image_id,
- manifest = manifest,
- architecture = architecture,
- error = error )
-# return trans.show_form(
-# web.FormBuilder( web.url_for(), "Add new cloud image", submit_text="Add" )
-# .add_text( "provider_type", "Provider type", value='ec2 or eucalyptus', error=provider_error )
-# .add_text( "image_id", "Machine Image ID (AMI or EMI)", value='', error=id_error )
-# .add_text( "manifest", "Manifest", value='', error=manifest_error )
-# .add_text( "architecture", "Architecture", value='i386 or x86_64', error=arch_error ) )
-
- @web.expose
- @web.require_login( "use Galaxy cloud" )
- def list_machine_images( self, trans ):
- images = trans.sa_session.query( model.CloudImage ).filter_by( deleted=False ).all()
- return trans.fill_template( '/cloud/list_images.mako', images=images )
-
- @web.expose
- @web.require_admin
- def delete_image( self, trans, id=None ):
- if not isinstance( id, int ):
- id = trans.security.decode_id( id )
-
- image = trans.sa_session.query( model.CloudImage ).get( id )
- image.deleted = True
- trans.sa_session.add( image )
- trans.sa_session.flush()
- return self.list_machine_images( trans )
-
- @web.expose
- @web.require_admin
- def edit_image( self, trans, provider_type='', image_id='', manifest='', architecture='', id='', edited=False ):
- error = {}
- if not isinstance( id, int ):
- id = trans.security.decode_id( id )
-
- if not edited:
- image = trans.sa_session.query( model.CloudImage ).get( id )
- return trans.fill_template( "cloud/edit_image.mako",
- image = image,
- error = error
- )
- else:
- image = trans.sa_session.query( model.CloudImage ).get( id )
- if image_id=='' or len( image_id ) > 255:
- error['id_error'] = "Image ID must be between 1 and 255 characters in length."
- elif trans.sa_session.query( model.CloudImage ) \
- .filter_by( deleted=False ) \
- .filter( and_( model.CloudImage.table.c.id != image.id, model.CloudImage.table.c.image_id==image_id ) ) \
- .first():
- error['id_error'] = "Image with ID '" + image_id + "' already exist. Please choose an alternative name."
- elif architecture=='' or len( architecture ) > 255:
- error['arch_error'] = "Architecture type must be between 1 and 255 characters long."
- if error:
- return trans.fill_template( "cloud/edit_image.mako",
- image = image,
- error = error
- )
- else:
- image.image_id = image_id
- image.manifest = manifest
- image.architecture = architecture
- # Persist
- session = trans.sa_session
- session.add( image )
- session.flush()
- # Log and display the management page
- trans.set_message( "Machine image '%s' edited." % image.image_id )
- return self.list_machine_images( trans )
-
- # ----- Credentials methods -----
-
- @web.expose
- @web.require_login( "add credentials" )
- def add_credentials( self, trans, credName='', accessKey='', secretKey='', providerName='' ):
- """
- Add user's cloud credentials stored under name `credName`.
- """
- user = trans.get_user()
- error = {}
-
- if credName or providerName or accessKey or secretKey:
- if credName=='' or len( credName ) > 255:
- error['cred_error'] = "Credentials name must be between 1 and 255 characters in length."
- elif trans.sa_session.query( model.CloudUserCredentials ) \
- .filter_by( user=user, deleted=False ) \
- .filter( model.CloudUserCredentials.table.c.name == credName ) \
- .first():
- error['cred_error'] = "Credentials with that name already exist."
- elif providerName=='':
- error['provider_error'] = "You must select cloud provider associated with these credentials."
- elif accessKey=='' or len( accessKey ) > 255:
- error['access_key_error'] = "Access key must be between 1 and 255 characters long."
- elif secretKey=='' or len( secretKey ) > 255:
- error['secret_key_error'] = "Secret key must be between 1 and 255 characters long."
- else:
- # Create new user stored credentials
- credentials = model.CloudUserCredentials()
- credentials.name = credName
- credentials.user = user
- credentials.access_key = accessKey
- credentials.secret_key = secretKey
- provider = get_provider( trans, providerName )
- credentials.provider = provider
- # Persist
- session = trans.sa_session
- session.add( credentials )
- session.flush()
- # Log and display the management page
- trans.log_event( "User added new credentials" )
- trans.set_message( "Credential '%s' created" % credentials.name )
- return self.list( trans )
-
- providers = trans.sa_session.query( model.CloudProvider ).filter_by( user=user ).all()
- return trans.fill_template( "cloud/add_credentials.mako",
- credName = credName,
- providerName = providerName,
- accessKey = accessKey,
- secretKey = secretKey,
- error = error,
- providers = providers
- )
-
- @web.expose
- @web.require_login( "use Galaxy cloud" )
- def edit_credentials( self, trans, id, credName=None, accessKey=None, secretKey=None, edited=False ):
- error = {}
- if not edited:
- credentials = get_stored_credentials( trans, id )
- return trans.fill_template( "cloud/edit_credentials.mako",
- credential = credentials,
- error = error
- )
- else:
- user = trans.get_user()
- credentials = get_stored_credentials( trans, id )
- if credName=='' or len( credName ) > 255:
- error['cred_error'] = "Credentials name must be between 1 and 255 characters in length."
- elif trans.sa_session.query( model.CloudUserCredentials ) \
- .filter_by( user=user ) \
- .filter( and_( model.CloudUserCredentials.table.c.id != credentials.id, model.CloudUserCredentials.table.c.name==credName ) ) \
- .first():
- error['cred_error'] = "Credentials with name '" + credName + "' already exist. Please choose an alternative name."
- elif accessKey=='' or len( accessKey ) > 255:
- error['access_key_error'] = "Access key must be between 1 and 255 characters long."
- elif secretKey=='' or len( secretKey ) > 255:
- error['secret_key_error'] = "Secret key must be between 1 and 255 characters long."
-
- if error:
- return trans.fill_template( "cloud/edit_credentials.mako",
- credential = credentials,
- error = error
- )
- else:
- # Edit user stored credentials
- credentials.name = credName
- credentials.access_key = accessKey
- credentials.secret_key = secretKey
- # Persist
- session = trans.sa_session
- session.add( credentials )
- session.flush()
- # Log and display the management page
- trans.set_message( "Credential '%s' edited." % credentials.name )
- return self.list( trans )
-
- @web.expose
- @web.require_login( "view credentials" )
- def view_credentials( self, trans, id=None ):
- """
- View details for user credentials
- """
- # Load credentials from database
- stored = get_stored_credentials( trans, id )
-
- return trans.fill_template( "cloud/view_credentials.mako",
- credDetails = stored )
-
- @web.expose
- @web.require_login( "test cloud credentials" )
- def test_cred( self, trans, id=None ):
- """
- Tests credentials provided by user with selected cloud provider
- """
-
- @web.expose
- @web.require_login( "delete credentials" )
- def delete_credentials( self, trans, id=None ):
- """
- Delete user's cloud credentials checking that no registered instances are tied to given credentials.
- """
- # Load credentials from database
- user = trans.get_user()
- stored = get_stored_credentials( trans, id )
- # Check if there are any UCIs that depend on these credentials
- UCI = None
- UCI = trans.sa_session.query( model.UCI ) \
- .filter_by( user=user, credentials_id=stored.id, deleted=False ) \
- .first()
-
- if UCI == None:
- # Delete and save
- stored.deleted = True
- trans.sa_session.add( stored )
- trans.sa_session.flush()
- # Display the management page
- trans.set_message( "Credentials '%s' deleted." % stored.name )
- return self.list( trans )
- else:
- error( "Existing instance(s) depend on credentials '%s'. You must delete those instances before being able \
- to delete these credentials." % stored.name )
- return self.list( trans )
-
- # ----- Provider methods -----
-
- @web.expose
- @web.require_login( "add provider" )
- def add_provider( self, trans, name='', type='', region_name='', region_endpoint='', is_secure='', host='', port='', proxy='', proxy_port='',
- proxy_user='', proxy_pass='', debug='', https_connection_factory='', path='' ):
- user = trans.get_user()
- error = {}
-
- if region_name or region_endpoint or name or is_secure or port or proxy or debug or path:
- try:
- is_secure = int(is_secure)
- except ValueError:
- error['is_secure_error'] = "Field 'is secure' can only take on an integer value '0' or '1'"
-
- if trans.sa_session.query( model.CloudProvider ) \
- .filter_by (user=user, name=name) \
- .filter( model.CloudProvider.table.c.deleted != True ) \
- .first():
- error['name_error'] = "A provider with that name already exist."
- elif name=='' or len( name ) > 255:
- error['name_error'] = "Provider name must be between 1 and 255 characters long."
- elif type=='':
- error['type_error'] = "Provider type must be selected."
- elif not (is_secure == 0 or is_secure == 1):
- error['is_secure_error'] = "Field 'is secure' can only take on an integer value '0' or '1'"
- else:
- provider = model.CloudProvider()
- provider.user = user
- provider.type = type
- provider.name = name
- if region_name:
- provider.region_name = region_name
- else:
- provider.region_name = None
-
- if region_endpoint:
- provider.region_endpoint = region_endpoint
- else:
- provider.region_endpoint = None
-
- if is_secure==0:
- provider.is_secure = False
- else:
- provider.is_secure = True
-
- if host:
- provider.host = host
- else:
- provider.host = None
-
- if port:
- provider.port = port
- else:
- provider.port = None
-
- if proxy:
- provider.proxy = proxy
- else:
- provider.proxy = None
-
- if proxy_port:
- provider.proxy_port = proxy_port
- else:
- provider.proxy_port = None
-
- if proxy_user:
- provider.proxy_user = proxy_user
- else:
- provider.proxy_user = None
-
- if proxy_pass:
- provider.proxy_pass = proxy_pass
- else:
- provider.proxy_pass = None
-
- if debug:
- provider.debug = debug
- else:
- provider.debug = None
-
- if https_connection_factory:
- provider.https_connection_factory = https_connection_factory
- else:
- provider.https_connection_factory = None
-
- provider.path = path
- # Persist
- session = trans.sa_session
- session.add( provider )
- session.flush()
- # Log and display the management page
- trans.log_event( "User configured new cloud provider: '%s'" % name )
- trans.set_message( "New cloud provider '%s' added." % name )
- return self.list( trans )
-
- return trans.fill_template( "cloud/add_provider.mako",
- name = name,
- type = type,
- region_name = region_name,
- region_endpoint = region_endpoint,
- is_secure = is_secure,
- host = host,
- port = port,
- proxy = proxy,
- proxy_port = proxy_port,
- proxy_user = proxy_user,
- proxy_pass = proxy_pass,
- debug = debug,
- https_connection_factory = https_connection_factory,
- path = path,
- error = error
- )
-
- @web.expose
- @web.require_login( "add Amazon EC2 provider" )
- def add_ec2( self, trans ):
- """ Default provider setup for Amazon's EC2. """
- self.add_provider( trans, name='Amazon EC2', type='ec2', region_name='us-east-1', region_endpoint='us-east-1.ec2.amazonaws.com', is_secure=1, path='/' )
- return self.add( trans )
-
- @web.expose
- @web.require_login( "use Galaxy cloud" )
- def view_provider( self, trans, id=None ):
- """
- View details about given cloud provider
- """
- # Load credentials from database
- provider = get_provider_by_id( trans, id )
-
- return trans.fill_template( "cloud/view_provider.mako",
- provider = provider )
-
- @web.expose
- @web.require_login( "use Galaxy cloud" )
- def edit_provider( self, trans, id, name='', type='', region_name='', region_endpoint='', is_secure='', host='', port='', proxy='', proxy_port='',
- proxy_user='', proxy_pass='', debug='', https_connection_factory='', path='', edited=False ):
- error = {}
- if edited == False:
- provider = get_provider_by_id( trans, id )
- return trans.fill_template( "cloud/edit_provider.mako",
- provider = provider,
- error = error
- )
- else:
- user = trans.get_user()
- provider = get_provider_by_id( trans, id )
-
- try:
- is_secure = int(is_secure)
- except ValueError:
- error['is_secure_error'] = "Field 'is secure' can only take on an integer value '0' or '1'"
-
- if name=='' or len( name ) > 255:
- error['name_error'] = "Cloud provider name must be between 1 and 255 characters in length."
- elif trans.sa_session.query( model.CloudProvider ) \
- .filter_by( user=user ) \
- .filter( and_( model.CloudProvider.table.c.id != provider.id, model.CloudProvider.table.c.name == name ) ) \
- .first():
- error['name_error'] = "Cloud provider with name '" + name + "' already exist. Please choose an alternative name."
- elif not ( is_secure == 0 or is_secure == 1):
- error['is_secure_error'] = "Field 'is secure' can only take on an integer value '0' or '1'"
-
- if error:
- return trans.fill_template( "cloud/edit_provider.mako",
- provider = provider,
- error = error
- )
- else:
- provider.name = name
- if region_name and region_name != 'None':
- provider.region_name = region_name
- else:
- provider.region_name = None
-
- if region_endpoint and region_endpoint != 'None':
- provider.region_endpoint = region_endpoint
- else:
- provider.region_endpoint = None
-
- if is_secure==0:
- provider.is_secure = False
- else:
- provider.is_secure = True
-
- if host and host != 'None':
- provider.host = host
- else:
- provider.host = None
-
- if port and port != 'None':
- provider.port = port
- else:
- provider.port = None
-
- if proxy and proxy != 'None':
- provider.proxy = proxy
- else:
- provider.proxy = None
-
- if proxy_port and proxy_port != 'None':
- provider.proxy_port = proxy_port
- else:
- provider.proxy_port = None
-
- if proxy_user and proxy_user != 'None':
- provider.proxy_user = proxy_user
- else:
- provider.proxy_user = None
-
- if proxy_pass and proxy_pass != 'None':
- provider.proxy_pass = proxy_pass
- else:
- provider.proxy_pass = None
-
- if debug and debug != 'None':
- provider.debug = debug
- else:
- provider.debug = None
-
- if https_connection_factory and https_connection_factory != 'None':
- provider.https_connection_factory = https_connection_factory
- else:
- provider.https_connection_factory = None
-
- if path and path != 'None':
- provider.path = path
- else:
- provider.path = None
- # Persist
- session = trans.sa_session
- session.add( provider )
- session.flush()
- # Log and display the management page
- trans.log_event( "User edited cloud provider: '%s'" % name )
- trans.set_message( "Cloud provider '%s' edited." % name )
- return self.list( trans )
-
- @web.expose
- @web.require_login( "delete credentials" )
- def delete_provider( self, trans, id=None ):
- """
- Delete use-registered cloud provider checking that no registered credentials are tied to given provider.
- """
- # Load provider from database
- user = trans.get_user()
- provider = get_provider_by_id( trans, id )
- creds = trans.sa_session.query( model.CloudUserCredentials ) \
- .filter_by( user=user, provider_id=provider.id ) \
- .filter( model.CloudUserCredentials.table.c.deleted != True ) \
- .all()
-
- if len( creds ) == 0:
- # Delete and save
- #sess = trans.sa_session
- provider.deleted = True
- trans.sa_session.add( provider )
- trans.sa_session.flush()
- # Display the management page
- trans.set_message( "Cloud provider '%s' deleted." % provider.name )
- return self.list( trans )
-
- error( "Existing credentails depend on cloud provider '%s'. You must delete those credentials before being able \
- to delete this cloud provider." % provider.name )
- return self.list( trans )
-
- # ----- AJAX methods -----
-
- @web.json
- def json_update( self, trans ):
- user = trans.get_user()
- UCIs = trans.sa_session.query( model.UCI ).filter_by( user=user, deleted=False ).all()
- insd = {} # instance name-state dict
- for uci in UCIs:
- dict = {}
- dict['id'] = uci.id
- dict['state'] = uci.state
- dict['total_size'] = uci.total_size
- if uci.error != None:
- dict['error'] = str( uci.error )
- else:
- dict['error'] = None
- if uci.launch_time != None:
- dict['launch_time'] = str( uci.launch_time )
- dict['time_ago'] = str( date.distance_of_time_in_words( uci.launch_time, date.datetime.utcnow() ) )
- else:
- dict['launch_time'] = None
- dict['time_ago'] = None
- insd[uci.name] = dict
- return insd
-
- @web.json
- def link_update( self, trans, uci_id=0 ):
- ild = {} # instance-link-dict
- dict = {}
- dict['uci_id'] = uci_id
- try:
- user = trans.get_user()
- # TODO: This query can assumes only one instance under given UCI can be running (i.e., started).
- inst = trans.sa_session.query( model.CloudInstance ).filter_by( user=user, uci_id=uci_id, state=uci_states.RUNNING ).first()
- urllib2.urlopen( "http://" + inst.public_dns )
- dict['public_dns'] = inst.public_dns
- dict['inst_id'] = inst.id
- ild['data'] = dict
- return ild
- except urllib2.URLError:
- dict['public_dns'] = False
- ild['data'] = dict
- return ild
-
-## ---- Utility methods -------------------------------------------------------
-
-def get_provider( trans, name ):
- user = trans.get_user()
- return trans.sa_session.query( model.CloudProvider ) \
- .filter_by (user=user, name=name) \
- .first()
-
-def get_provider_by_id( trans, id, check_ownership=True ):
- # Check if 'id' is in int (i.e., it was called from this program) or
- # it was passed from the web (in which case decode it)
- if not isinstance( id, int ):
- id = trans.security.decode_id( id )
-
- stored = trans.sa_session.query( model.CloudProvider ).get( id )
- if not stored:
- error( "Cloud provider not found" )
- # Verify ownership
- user = trans.get_user()
- if not user:
- error( "Must be logged in to use the cloud." )
- if check_ownership and not( stored.user == user ):
- error( "Cloud provider '%s' is not registered by current user." % stored.name )
- # Looks good
- return stored
-
-def get_stored_credentials( trans, id, check_ownership=True ):
- """
- Get StoredUserCredentials from the database by id, verifying ownership.
- """
- # Check if 'id' is in int (i.e., it was called from this program) or
- # it was passed from the web (in which case decode it)
- if not isinstance( id, int ):
- id = trans.security.decode_id( id )
-
- stored = trans.sa_session.query( model.CloudUserCredentials ).get( id )
- if not stored:
- error( "Credentials not found" )
- # Verify ownership
- user = trans.get_user()
- if not user:
- error( "Must be logged in to use the cloud." )
- if check_ownership and not( stored.user == user ):
- error( "Credentials are not owned by current user." )
- # Looks good
- return stored
-
-def get_uci( trans, id, check_ownership=True ):
- """
- Get a UCI object from the database by id, verifying ownership.
- """
- # Check if 'id' is in int (i.e., it was called from this program) or
- # it was passed from the web (in which case decode it)
- if not isinstance( id, int ):
- id = trans.security.decode_id( id )
-
- live = trans.sa_session.query( model.UCI ).get( id )
- if not live:
- error( "Galaxy instance not found." )
- # Verify ownership
- user = trans.get_user()
- if not user:
- error( "Must be logged in to use the cloud." )
- if check_ownership and not( live.user == user ):
- error( "Instance is not owned by current user." )
- # Looks good
- return live
-
-def get_stores( trans, uci ):
- """
- Get stores objects that are associated with given uci and are not in 'error' status
- """
- user = trans.get_user()
- stores = trans.sa_session.query( model.CloudStore ) \
- .filter_by( user=user, uci_id=uci.id, deleted=False ) \
- .filter( model.CloudStore.table.c.status != store_status.ERROR ) \
- .all()
-
- return stores
-
-def get_stores_in_status( trans, uci, status ):
- """
- Get stores objects that are associated with given uci and are not have given status
- """
- user = trans.get_user()
- stores = trans.sa_session.query( model.CloudStore ) \
- .filter_by( user=user, uci_id=uci.id, status=status ) \
- .all()
-
- return stores
-
-def get_instances( trans, uci ):
- """
- Get objects of instances that are pending or running and are connected to the given uci object
- """
- user = trans.get_user()
- instances = trans.sa_session.query( model.CloudInstance ) \
- .filter_by( user=user, uci_id=uci.id ) \
- .filter( or_(model.CloudInstance.table.c.state==instance_states.RUNNING, model.CloudInstance.table.c.state==instance_states.PENDING ) ) \
- .first()
- #.all() #TODO: return all but need to edit calling method(s) to handle list
-
- return instances
-
-def get_instances_in_state( trans, uci, state ):
- """
- Get objects of instances that are in specified state and are connected to the given uci object
- """
- user = trans.get_user()
- instances = trans.sa_session.query( model.CloudInstance ) \
- .filter_by( user=user, uci_id=uci.id, state=state ) \
- .all()
-
- return instances
-
-def get_connection( trans, creds ):
- """
- Establishes cloud connection using user's credentials
- """
- log.debug( 'Establishing cloud connection.' )
-# user = trans.get_user()
-# creds = trans.sa_session.query( model.CloudUserCredentials ) \
-# .filter_by( user=user, name=credName ) \
-# .first()
- #.filter( model.CloudUserCredentials.table.c.deleted != True ) \ MOVE TO LINE ABOVE ONCE DELETE COLUMS ARE IMPLEMENTED
-
- if creds:
- a_key = creds.access_key
- s_key = creds.secret_key
- try:
- euca_region = RegionInfo( None, creds.provider.region_name, creds.provider.region_endpoint )
- conn = EC2Connection( aws_access_key_id=a_key,
- aws_secret_access_key=s_key,
- is_secure=creds.provider.is_secure,
- port=creds.provider.port,
- region=euca_region,
- path=creds.provider.path )
- except boto.exception.EC2ResponseError, e:
- log.error( "Establishing connection with cloud failed: %s" % str(e) )
- return None
-
- return conn
--- /dev/null
+++ b/lib/galaxy/model/migrate/versions/0050_drop_cloud_tables.py
@@ -0,0 +1,147 @@
+from sqlalchemy import *
+from migrate import *
+
+import datetime
+now = datetime.datetime.utcnow
+
+# Need our custom types, but don't import anything else from model
+from galaxy.model.custom_types import *
+
+import logging
+log = logging.getLogger( __name__ )
+
+metadata = MetaData( migrate_engine )
+
+def display_migration_details():
+ print
+ print "========================================"
+ print "This script drops tables that were associated with the old Galaxy Cloud functionality."
+ print "========================================"
+
+CloudImage_table = Table( "cloud_image", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "provider_type", TEXT ),
+ Column( "image_id", TEXT, nullable=False ),
+ Column( "manifest", TEXT ),
+ Column( "state", TEXT ),
+ Column( "architecture", TEXT ),
+ Column( "deleted", Boolean, default=False ) )
+
+""" UserConfiguredInstance (UCI) table """
+UCI_table = Table( "cloud_uci", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ),
+ Column( "credentials_id", Integer, ForeignKey( "cloud_user_credentials.id" ), index=True ),
+ Column( "key_pair_name", TEXT ),
+ Column( "key_pair_material", TEXT ),
+ Column( "name", TEXT ),
+ Column( "state", TEXT ),
+ Column( "error", TEXT ),
+ Column( "total_size", Integer ),
+ Column( "launch_time", DateTime ),
+ Column( "deleted", Boolean, default=False ) )
+
+CloudInstance_table = Table( "cloud_instance", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "launch_time", DateTime ),
+ Column( "stop_time", DateTime ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ),
+ Column( "uci_id", Integer, ForeignKey( "cloud_uci.id" ), index=True ),
+ Column( "type", TEXT ),
+ Column( "reservation_id", TEXT ),
+ Column( "instance_id", TEXT ),
+ Column( "mi_id", Integer, ForeignKey( "cloud_image.id" ), index=True ),
+ Column( "state", TEXT ),
+ Column( "error", TEXT ),
+ Column( "public_dns", TEXT ),
+ Column( "private_dns", TEXT ),
+ Column( "security_group", TEXT ),
+ Column( "availability_zone", TEXT ) )
+
+CloudStore_table = Table( "cloud_store", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "attach_time", DateTime ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ),
+ Column( "uci_id", Integer, ForeignKey( "cloud_uci.id" ), index=True, nullable=False ),
+ Column( "volume_id", TEXT ),
+ Column( "size", Integer, nullable=False ),
+ Column( "availability_zone", TEXT ),
+ Column( "inst_id", Integer, ForeignKey( "cloud_instance.id" ) ),
+ Column( "status", TEXT ),
+ Column( "device", TEXT ),
+ Column( "space_consumed", Integer ),
+ Column( "error", TEXT ),
+ Column( "deleted", Boolean, default=False ) )
+
+CloudSnapshot_table = Table( "cloud_snapshot", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ),
+ Column( "uci_id", Integer, ForeignKey( "cloud_uci.id" ), index=True ),
+ Column( "store_id", Integer, ForeignKey( "cloud_store.id" ), index=True, nullable=False ),
+ Column( "snapshot_id", TEXT ),
+ Column( "status", TEXT ),
+ Column( "description", TEXT ),
+ Column( "error", TEXT ),
+ Column( "deleted", Boolean, default=False ) )
+
+CloudUserCredentials_table = Table( "cloud_user_credentials", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ),
+ Column( "provider_id", Integer, ForeignKey( "cloud_provider.id" ), index=True, nullable=False ),
+ Column( "name", TEXT ),
+ Column( "access_key", TEXT ),
+ Column( "secret_key", TEXT ),
+ Column( "deleted", Boolean, default=False ) )
+
+CloudProvider_table = Table( "cloud_provider", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "create_time", DateTime, default=now ),
+ Column( "update_time", DateTime, default=now, onupdate=now ),
+ Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ),
+ Column( "type", TEXT, nullable=False ),
+ Column( "name", TEXT ),
+ Column( "region_connection", TEXT ),
+ Column( "region_name", TEXT ),
+ Column( "region_endpoint", TEXT ),
+ Column( "is_secure", Boolean ),
+ Column( "host", TEXT ),
+ Column( "port", Integer ),
+ Column( "proxy", TEXT ),
+ Column( "proxy_port", TEXT ),
+ Column( "proxy_user", TEXT ),
+ Column( "proxy_pass", TEXT ),
+ Column( "debug", Integer ),
+ Column( "https_connection_factory", TEXT ),
+ Column( "path", TEXT ),
+ Column( "deleted", Boolean, default=False ) )
+
+def upgrade():
+ display_migration_details()
+ # Load existing tables
+ metadata.reflect()
+ try:
+ CloudProvider_table.drop()
+ CloudUserCredentials_table.drop()
+ CloudImage_table.drop()
+ UCI_table.drop()
+ CloudInstance_table.drop()
+ CloudStore_table.drop()
+ CloudSnapshot_table.drop()
+ except Exception, e:
+ log.debug( "Dropping cloud tables failed: %s" % str( e ) )
+
+
+def downgrade():
+ pass
--- a/lib/galaxy/model/mapping.py
+++ b/lib/galaxy/model/mapping.py
@@ -409,117 +409,6 @@ GalaxySessionToHistoryAssociation.table
Column( "session_id", Integer, ForeignKey( "galaxy_session.id" ), index=True ),
Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ) )
-# *************************** Start cloud tables***********************************
-CloudImage.table = Table( "cloud_image", metadata,
- Column( "id", Integer, primary_key=True ),
- Column( "create_time", DateTime, default=now ),
- Column( "update_time", DateTime, default=now, onupdate=now ),
- Column( "provider_type", TEXT ),
- Column( "image_id", TEXT, nullable=False ),
- Column( "manifest", TEXT ),
- Column( "state", TEXT ),
- Column( "architecture", TEXT ),
- Column( "deleted", Boolean, default=False ) )
-
-""" UserConfiguredInstance (UCI) table """
-UCI.table = Table( "cloud_uci", metadata,
- Column( "id", Integer, primary_key=True ),
- Column( "create_time", DateTime, default=now ),
- Column( "update_time", DateTime, default=now, onupdate=now ),
- Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ),
- Column( "credentials_id", Integer, ForeignKey( "cloud_user_credentials.id" ), index=True ),
- Column( "key_pair_name", TEXT ),
- Column( "key_pair_material", TEXT ),
- Column( "name", TEXT ),
- Column( "state", TEXT ),
- Column( "error", TEXT ),
- Column( "total_size", Integer ),
- Column( "launch_time", DateTime ),
- Column( "deleted", Boolean, default=False ) )
-
-CloudInstance.table = Table( "cloud_instance", metadata,
- Column( "id", Integer, primary_key=True ),
- Column( "create_time", DateTime, default=now ),
- Column( "update_time", DateTime, default=now, onupdate=now ),
- Column( "launch_time", DateTime ),
- Column( "stop_time", DateTime ),
- Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ),
- Column( "uci_id", Integer, ForeignKey( "cloud_uci.id" ), index=True ),
- Column( "type", TEXT ),
- Column( "reservation_id", TEXT ),
- Column( "instance_id", TEXT ),
- Column( "mi_id", Integer, ForeignKey( "cloud_image.id" ), index=True ),
- Column( "state", TEXT ),
- Column( "error", TEXT ),
- Column( "public_dns", TEXT ),
- Column( "private_dns", TEXT ),
- Column( "security_group", TEXT ),
- Column( "availability_zone", TEXT ) )
-
-CloudStore.table = Table( "cloud_store", metadata,
- Column( "id", Integer, primary_key=True ),
- Column( "create_time", DateTime, default=now ),
- Column( "update_time", DateTime, default=now, onupdate=now ),
- Column( "attach_time", DateTime ),
- Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ),
- Column( "uci_id", Integer, ForeignKey( "cloud_uci.id" ), index=True, nullable=False ),
- Column( "volume_id", TEXT ),
- Column( "size", Integer, nullable=False ),
- Column( "availability_zone", TEXT ),
- Column( "inst_id", Integer, ForeignKey( "cloud_instance.id" ) ),
- Column( "status", TEXT ),
- Column( "device", TEXT ),
- Column( "space_consumed", Integer ),
- Column( "error", TEXT ),
- Column( "deleted", Boolean, default=False ) )
-
-CloudSnapshot.table = Table( "cloud_snapshot", metadata,
- Column( "id", Integer, primary_key=True ),
- Column( "create_time", DateTime, default=now ),
- Column( "update_time", DateTime, default=now, onupdate=now ),
- Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ),
- Column( "uci_id", Integer, ForeignKey( "cloud_uci.id" ), index=True ),
- Column( "store_id", Integer, ForeignKey( "cloud_store.id" ), index=True, nullable=False ),
- Column( "snapshot_id", TEXT ),
- Column( "status", TEXT ),
- Column( "description", TEXT ),
- Column( "error", TEXT ),
- Column( "deleted", Boolean, default=False ) )
-
-CloudUserCredentials.table = Table( "cloud_user_credentials", metadata,
- Column( "id", Integer, primary_key=True ),
- Column( "create_time", DateTime, default=now ),
- Column( "update_time", DateTime, default=now, onupdate=now ),
- Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ),
- Column( "provider_id", Integer, ForeignKey( "cloud_provider.id" ), index=True, nullable=False ),
- Column( "name", TEXT ),
- Column( "access_key", TEXT ),
- Column( "secret_key", TEXT ),
- Column( "deleted", Boolean, default=False ) )
-
-CloudProvider.table = Table( "cloud_provider", metadata,
- Column( "id", Integer, primary_key=True ),
- Column( "create_time", DateTime, default=now ),
- Column( "update_time", DateTime, default=now, onupdate=now ),
- Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=False ),
- Column( "type", TEXT, nullable=False ),
- Column( "name", TEXT ),
- Column( "region_connection", TEXT ),
- Column( "region_name", TEXT ),
- Column( "region_endpoint", TEXT ),
- Column( "is_secure", Boolean ),
- Column( "host", TEXT ),
- Column( "port", Integer ),
- Column( "proxy", TEXT ),
- Column( "proxy_port", TEXT ),
- Column( "proxy_user", TEXT ),
- Column( "proxy_pass", TEXT ),
- Column( "debug", Integer ),
- Column( "https_connection_factory", TEXT ),
- Column( "path", TEXT ),
- Column( "deleted", Boolean, default=False ) )
-# *************************** End cloud tables***********************************
-
StoredWorkflow.table = Table( "stored_workflow", metadata,
Column( "id", Integer, primary_key=True ),
Column( "create_time", DateTime, default=now ),
@@ -1284,42 +1173,6 @@ assign_mapper( context, GalaxySessionToH
HistoryDatasetAssociation.mapper.add_property( "creating_job_associations", relation( JobToOutputDatasetAssociation ) )
-# vvvvvvvvvvvvvvvv Start cloud table mappings vvvvvvvvvvvvvvvv
-assign_mapper( context, CloudImage, CloudImage.table )
-
-assign_mapper( context, UCI, UCI.table,
- properties=dict( user=relation( User ),
- credentials=relation( CloudUserCredentials ),
- instance=relation( CloudInstance, backref='uci' ),
- store=relation( CloudStore, backref='uci', cascade='all, delete-orphan' ),
- snapshot=relation( CloudSnapshot, backref='uci' )
- ) )
-
-assign_mapper( context, CloudInstance, CloudInstance.table,
- properties=dict( user=relation( User ),
- image=relation( CloudImage )
- ) )
-
-assign_mapper( context, CloudStore, CloudStore.table,
- properties=dict( user=relation( User ),
- inst=relation( CloudInstance ),
- snapshot=relation( CloudSnapshot, backref="store" )
- ) )
-
-assign_mapper( context, CloudSnapshot, CloudSnapshot.table,
- properties=dict( user=relation( User )
- ) )
-
-assign_mapper( context, CloudProvider, CloudProvider.table,
- properties=dict( user=relation( User )
- ) )
-
-assign_mapper( context, CloudUserCredentials, CloudUserCredentials.table,
- properties=dict( user=relation( User),
- provider=relation( CloudProvider )
- ) )
-# ^^^^^^^^^^^^^^^ End cloud table mappings ^^^^^^^^^^^^^^^^^^
-
assign_mapper( context, Workflow, Workflow.table,
properties=dict( steps=relation( WorkflowStep, backref='workflow',
order_by=asc(WorkflowStep.table.c.order_index),
--- a/lib/galaxy/app.py
+++ b/lib/galaxy/app.py
@@ -73,8 +73,6 @@ class UniverseApplication( object ):
# FIXME: These are exposed directly for backward compatibility
self.job_queue = self.job_manager.job_queue
self.job_stop_queue = self.job_manager.job_stop_queue
- # Start the cloud manager
- self.cloud_manager = cloud.CloudManager( self )
# Track Store
## self.track_store = store.TrackStoreManager( self.config.track_store_path )
--- a/templates/cloud/view_snapshots.mako
+++ /dev/null
@@ -1,90 +0,0 @@
-<%inherit file="/base.mako"/>
-
-<%def name="title()">Snapshots</%def>
-
-%if message:
-<%
- try:
- messagetype
- except:
- messagetype = "done"
-%>
-
-
-
-<p />
-<div class="${messagetype}message">
- ${message}
-</div>
-%endif
-
-%if snaps:
- <h2>Snapshots for instance ${snaps[0].uci.name}</h2>
-%else:
- <h2>Selected instance has no recorded or associated snapshots.</h2>
-%endif
-
-<ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( action='list' )}">
- <img src="${h.url_for('/static/images/silk/resultset_previous.png')}" />
- <span>Return to cloud management console</span>
- </a>
- </li>
-</ul>
-
-%if snaps:
- <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
- <colgroup width="2%"></colgroup>
- <colgroup width="16%"></colgroup>
- <colgroup width="16%"></colgroup>
- <colgroup width="10%"></colgroup>
- <colgroup width="5%"></colgroup>
- <tr class="header">
- <th>#</th>
- <th>Create time</th>
- <th>Snapshot ID</th>
- <th>Status</th>
- <th>Delete?</th>
- <th></th>
- </tr>
- <%
- total_hours = 0
- %>
- %for i, snap in enumerate( snaps ):
- <tr>
- <td>${i+1}</td>
- <td>
- %if snap.create_time:
- ${str(snap.create_time)[:16]} UCT
- %else:
- N/A
- %endif
- </td>
- <td>
- %if snap.snapshot_id:
- ${snap.snapshot_id}
- %else:
- N/A
- %endif
- </td>
- <td>
- %if snap.status:
- ${snap.status}
- %else:
- N/A
- %endif
- </td>
- <td>
- <a confirm="Are you sure you want to delete snapshot '${snap.snapshot_id}'?"
- href="${h.url_for( controller='cloud', action='delete_snapshot', uci_id=trans.security.encode_id(snap.uci.id), snap_id=trans.security.encode_id(snap.id) )}">x</a>
- </td>
- </tr>
- %endfor
- </table>
-%endif
-
-
-
-
-
--- a/eggs.ini
+++ b/eggs.ini
@@ -54,7 +54,6 @@ wsgiref = 0.1.2
Babel = 0.9.4
wchartype = 0.1
Whoosh = 0.3.18
-boto = 1.8d
; extra version information
[tags]
--- a/templates/cloud/configure_cloud.mako
+++ /dev/null
@@ -1,360 +0,0 @@
-<%inherit file="/base.mako"/>
-
-<%def name="title()">Cloud home</%def>
-
-%if message:
-<%
- try:
- messagetype
- except:
- messagetype = "done"
-%>
-
-<div class="${messagetype}message">
- ${message}
-</div>
-%endif
-
-<%def name="javascripts()">
-${parent.javascripts()}
-${h.js( "jquery" )}
-
-<script type="text/javascript">
-function trim19(str){
- var str = str.replace(/^\s\s*/, ''),
- ws = /\s/,
- i = str.length;
- while (ws.test(str.charAt(--i)));
- return str.slice(0, i + 1);
-}
-
- function update_state() {
- $.getJSON( "${h.url_for( action='json_update' )}", {}, function ( data ) {
- for (var i in data) {
- var elem = '#' + data[i].id;
- // Because of different list managing 'live' vs. 'available' instances, reload url on various state changes
- old_state = $(elem + "-state").text();
- prev_old_state = trim19( $(elem + "-state-p").text() );
- new_state = data[i].state;
- error_msg = data[i].error;
- //console.log( "old_state[%d] = %s", i, old_state );
- //console.log( "prev_old_state[%d] = %s", i, prev_old_state );
- //console.log( "new_state[%d] = %s", i, new_state );
- //console.log( "error_msg[%d] = %s", i, error_msg );
- if ( ( old_state=='pending' && new_state=='running' ) || ( old_state=='shutting-down' && new_state=='available' ) || \
- ( old_state=='running' && new_state=='available' ) || ( old_state=='running' && new_state=='error' ) || \
- ( old_state=='pending' && new_state=='available' ) || ( old_state=='submitted' && new_state=='available' ) || \
- ( prev_old_state.match('creating') && new_state=='available' ) ) {
- var url = "${h.url_for( controller='cloud', action='list')}";
- location.replace( url );
- }
- else if ( ( ( old_state != 'error' && old_state != '' ) && new_state == 'error' ) || ( !prev_old_state.match('error') && new_state == 'error' ) ) {
- var url = "${h.url_for( controller='cloud', action='list')}";
- location.replace( url );
- }
-
- if ( new_state=='shutting-down' || new_state=='shutting-downUCI' ) {
- $(elem + "-link").text( "" );
- }
-
- // Check if Galaxy website is accessible on given instance; if so, provide link. Otherwise, wait more.
- else if ( ( $(elem+"-link").text().match('starting') || $(elem+"-link").text()=='' ) && new_state=='running' ) {
- //console.log ( 'elem.text: ' + $(elem+"-link").text() );
- $.getJSON( "${h.url_for( action='link_update' )}", { uci_id: data[i].id }, function ( data ) {
- for (var i in data) {
- var dns = data[i].public_dns;
- var uci = '#' + data[i].uci_id;
- if( !dns ) {
- $(uci+"-link").text( 'Galaxy starting...' );
- // http://stackoverflow.com/questions/275931/how-do-you-make-an-element-flash-…
- //$(uci+"-link").stop().animate({ fontSize: "14px" }, 1000).animate({ fontSize: "12px" }, 1000);
- }
- else {
- $(uci+"-link").html( '<div align="right"><a class="action-button" href="http://'+dns+'" target="_blank">' +
- '<span>Access Galaxy</span>'+
- '<img src="' + "${h.url_for( '/static/images/silk/resultset_next.png' )}" + '" /></div>' );
- //$(uci+"-link").stop().animate({ fontSize: "14px" }, 1000).animate({ fontSize: "12px" }, 1000);
- }
- }
- });
- }
-
- // Update 'size', 'state' and 'time alive' fields
- $(elem + "-size").text( data[i].total_size );
- $(elem + "-state").text( data[i].state );
- if ( new_state != 'error' ) { // Because 'error' state is handled as a JS link, don't include it in update
- $(elem + "-state-p").text( data[i].state );
- }
-
- if (data[i].launch_time) {
- $(elem + "-launch_time").text( data[i].launch_time.substring(0, 16 ) + " UTC (" + data[i].time_ago + ")" );
- }
- else {
- $(elem + "-launch_time").text( "N/A" );
- }
- }
- });
- setTimeout("update_state()", 15000);
- }
-
- $(function() {
- update_state();
- });
-
-</script>
-</%def>
-
-<h2>Galaxy in the clouds</h2>
-
-%if cloudProviders:
- ## Manage user-registered cloud providers
- <h3>Your registered cloud providers</h3>
- <ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( action='add_provider' )}">
- <img src="${h.url_for('/static/images/silk/add.png')}" />
- <span>Add provider</span>
- </a>
- </li>
- </ul>
-
- <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
- <tr class="header">
- <th>Provider name</th>
- <th>Provider type</th>
- <th></th>
- </tr>
- %for i, cloudProvder in enumerate( cloudProviders ):
- <tr>
- <td>
- ${cloudProvder.name}
- <a id="cp-${i}-popup" class="popup-arrow" style="display: none;">▼</a>
- </td>
- <td>
- ${cloudProvder.type}
- </td>
- <td>
- <div popupmenu="cp-${i}-popup">
- <a class="action-button" href="${h.url_for( action='view_provider', id=trans.security.encode_id(cloudProvder.id) )}">View</a>
- <a class="action-button" href="${h.url_for( action='edit_provider', id=trans.security.encode_id(cloudProvder.id) )}">Edit</a>
- <a class="action-button" confirm="Are you sure you want to delete cloud provider '${cloudProvder.name}'?" href="${h.url_for( action='delete_provider', id=trans.security.encode_id(cloudProvder.id) )}">Delete</a>
- </div>
- </td>
- </tr>
- %endfor
- </table>
-
-
- ## *****************************************************
- ## Manage user credentials
- <h3>Your registered cloud credentials</h3>
-
- %if cloudCredentials:
- <ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( action='add_credentials' )}">
- <img src="${h.url_for('/static/images/silk/add.png')}" />
- <span>Add credentials</span>
- </a>
- </li>
- </ul>
- <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
- <tr class="header">
- <th>Credentials name</th>
- <th>Provider name (type)</th>
- <th></th>
- </tr>
-
- %for i, cloudCredential in enumerate( cloudCredentials ):
- <tr>
- <td>
- ${cloudCredential.name}
- <a id="cr-${i}-popup" class="popup-arrow" style="display: none;">▼</a>
- </td>
- <td>
- ${cloudCredential.provider.name}
- (${cloudCredential.provider.type})
- </td>
- <td>
- <div popupmenu="cr-${i}-popup">
- <a class="action-button" href="${h.url_for( action='view_credentials', id=trans.security.encode_id(cloudCredential.id) )}">View</a>
- <a class="action-button" href="${h.url_for( action='edit_credentials', id=trans.security.encode_id(cloudCredential.id) )}">Edit</a>
- <a class="action-button" confirm="Are you sure you want to delete credentials '${cloudCredential.name}'?" href="${h.url_for( action='delete_credentials', id=trans.security.encode_id(cloudCredential.id) )}">Delete</a>
- </div>
- </td>
- </tr>
- %endfor
- </table>
-
- ## *****************************************************
- ## Manage live instances
- <p />
- <h3>Manage your cloud instances</h3>
- <ul class="manage-table-actions">
- <li>
- <a class="action-button" href="${h.url_for( action='configure_new_uci' )}">
- <img src="${h.url_for('/static/images/silk/add.png')}" />
- <span>Configure new instance</span>
- </a>
- </li>
- </ul>
-
- <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
- <colgroup width="40%"></colgroup>
- <colgroup width="15%"></colgroup>
- <colgroup width="10%"></colgroup>
- <colgroup width="25%"></colgroup>
- <colgroup width="10%"></colgroup>
- <tr class="header">
- <th>Live instance name (credentials)</th>
- <th>Storage size (GB)</th>
- <th>State</th>
- <th>Alive since</th>
- <th></th>
- <th></th>
- </tr>
- %if liveInstances:
- %for i, liveInstance in enumerate( liveInstances ):
- <tr>
- <td>
- ${liveInstance.name} (${liveInstance.credentials.name})
- <a id="li-${i}-popup" class="popup-arrow" style="display: none;">▼</a>
- </td>
- <td id="${ liveInstance.id }-size">${str(liveInstance.total_size)}</td>
- <td id="${ liveInstance.id }-state">${str(liveInstance.state)}</td>
- <td id="${ liveInstance.id }-launch_time">
- ##${str(liveInstance.launch_time)[:16]}
- <%
- #from datetime import datetime
- #from datetime import timedelta
-
- # DB stores all times in GMT, so adjust for difference (4 hours)
- #adjustedStarttime = liveInstance.update_time - timedelta(hours=4)
-
- #delta = datetime.now() - adjustedStarttime
- #context.write( str(datetime.utcnow() ) )
- #context.write( str(delta) )
-
- # This is where current time and since duration is calculated
- if liveInstance.launch_time is None:
- context.write( 'N/A' )
- else:
- context.write( str( liveInstance.launch_time )[:16] )
- context.write( ' UTC (' )
- context.write( str(h.date.distance_of_time_in_words (liveInstance.launch_time, h.date.datetime.utcnow() ) ) )
- context.write( ')' )
- %>
- </td>
- ## Handled by JavaScript function
- <td id="${ liveInstance.id }-link"></td>
- <td>
- <div popupmenu="li-${i}-popup">
- <a class="action-button" confirm="Are you sure you want to stop instance '${liveInstance.name}'?" href="${h.url_for( action='stop', id=trans.security.encode_id(liveInstance.id) )}">Stop</a>
- <a class="action-button" href="${h.url_for( action='rename_uci', id=trans.security.encode_id(liveInstance.id) )}">Rename</a>
- <a class="action-button" href="${h.url_for( action='view_uci_details', id=trans.security.encode_id(liveInstance.id) )}">View details</a>
- <a class="action-button" href="${h.url_for( action='add_storage', id=trans.security.encode_id(liveInstance.id) )}">Add storage</a>
- <a class="action-button" href="${h.url_for( action='uci_usage_report', id=trans.security.encode_id(liveInstance.id) )}">Usage report</a>
- </div>
- </td>
- </tr>
- %endfor
- %else:
- <tr>
- <td>Currently, you have no live instances.</td>
- </tr>
- %endif
- </table>
- <p />
- ## *****************************************************
- ## Manage previously configured instances
- ## <table class="mange-table noHR" border="0" cellspacing="0" cellpadding="0" width="100%">
- <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
- <colgroup width="40%"></colgroup>
- <colgroup width="15%"></colgroup>
- <colgroup width="10%"></colgroup>
- <colgroup width="25%"></colgroup>
- <colgroup width="10%"></colgroup>
- <tr class="header">
- <th>Configured instance name (credentials)</th>
- <th>Storage size (GB)</th>
- <th>State</th>
- <th></th>
- <th></th>
- </tr>
-
- %if prevInstances:
- %for i, prevInstance in enumerate( prevInstances ):
- <tr>
- <td>
- ${prevInstance.name} (${prevInstance.credentials.name})
- <a id="pi-${i}-popup" class="popup-arrow" style="display: none;">▼</a>
- </td>
- <td>${str(prevInstance.total_size)}</td>
- <td id="${ prevInstance.id }-state-p">
- <%state = str(prevInstance.state)%>
- %if state =='error':
- <div id="${prevInstance.name}-short">
- <a onclick="document.getElementById('${prevInstance.name}-full').style.display = 'block';
- document.getElementById('${prevInstance.name}-short').style.display = 'none'; return 0"
- href="javascript:void(0)">
- error
- </a>
- </div>
- <div id="${prevInstance.name}-full" style="DISPLAY: none">
- <a onclick="document.getElementById('${prevInstance.name}-short').style.display = 'block';
- document.getElementById('${prevInstance.name}-full').style.display = 'none'; return 0;"
- href="javascript:void(0)">
- error:</a><br />
- ${str(prevInstance.error)}
- <p />
- <div style="font-size:10px;">
- <a href="${h.url_for( action='set_uci_state', id=trans.security.encode_id(prevInstance.id), state='available' )}">reset state</a>
- </div>
- </div>
- %else:
- ${str(prevInstance.state)}
- %endif
- </td>
- <td>
- <div popupmenu="pi-${i}-popup">
- <a class="action-button" href="${h.url_for( action='uci_usage_report', id=trans.security.encode_id(prevInstance.id) )}">Usage report</a>
- <a class="action-button" href="${h.url_for( action='rename_uci', id=trans.security.encode_id(prevInstance.id) )}">Rename</a>
- <a class="action-button" href="${h.url_for( action='start', id=trans.security.encode_id(prevInstance.id), type='m1.small' )}"> Start m1.small</a>
- <a class="action-button" href="${h.url_for( action='start', id=trans.security.encode_id(prevInstance.id), type='c1.medium' )}"> Start c1.medium</a>
- <a class="action-button" href="${h.url_for( action='start', id=trans.security.encode_id(prevInstance.id), type='m1.large' )}"> Start m1.large</a>
- <a class="action-button" href="${h.url_for( action='start', id=trans.security.encode_id(prevInstance.id), type='m1.xlarge' )}"> Start m1.xlarge</a>
- <a class="action-button" href="${h.url_for( action='start', id=trans.security.encode_id(prevInstance.id), type='c1.xlarge' )}"> Start c1.xlarge</a>
- <a class="action-button" href="${h.url_for( action='start', id=trans.security.encode_id(prevInstance.id), type='m2.2xlarge' )}"> Start m2.2xlarge</a>
- <a class="action-button" href="${h.url_for( action='start', id=trans.security.encode_id(prevInstance.id), type='m2.4xlarge' )}"> Start m2.4xlarge</a>
- <a class="action-button" href="${h.url_for( action='create_snapshot', id=trans.security.encode_id(prevInstance.id) )}">Create snapshot</a>
- <a class="action-button" href="${h.url_for( action='view_snapshots', id=trans.security.encode_id(prevInstance.id) )}">View snapshots</a>
- <a class="action-button" confirm="Are you sure you want to delete instance '${prevInstance.name}'? This will delete all of your data assocaiated with this instance!" href="${h.url_for( action='delete_uci', id=trans.security.encode_id(prevInstance.id) )}">Delete</a>
- </div>
- </td>
- </tr>
- %endfor
- %else:
- <tr>
- <td>You have no previously configured instances (or they are all currently alive).</td>
- </tr>
- %endif
- </table>
-
- %else:
- You have no credentials associated with your Galaxy account:
- <a class="action-button" href="${h.url_for( action='add_credentials' )}">
- <img src="${h.url_for('/static/images/silk/add.png')}" />
- <span>add credentials</span>
- </a>
- or
- <a href="http://aws.amazon.com/" target="_blank">
- open AWS account with Amazon</a>.
- %endif
-
-%else:
- You have no cloud providers registered with your Galaxy account:
- <a class="action-button" href="${h.url_for( action='add_provider' )}">
- <img src="${h.url_for('/static/images/silk/add.png')}" />
- <span>add provider now</span>
- </a>
-%endif
--- a/universe_wsgi.ini.sample
+++ b/universe_wsgi.ini.sample
@@ -201,11 +201,6 @@ use_interactive = True
# Should default dataset access permissions be private for new users; default is False (datasets are public)
new_user_dataset_access_role_default_private = False
-# ---- Cloud Management --------------------------------------------------------
-
-# Uncomment following line to enable cloud management mode (only leaves the Cloud, Help and User tabs on)
-# cloud_controller_instance = True
-
# ---- Job Execution --------------------------------------------------------
# If running multiple Galaxy processes, one can be designated as the job
--- a/lib/galaxy/cloud/__init__.py
+++ /dev/null
@@ -1,734 +0,0 @@
-import logging, threading, sys, os, time, subprocess, string, tempfile, re, traceback, shutil
-
-from galaxy import util, model, config
-from galaxy.model import mapping
-from galaxy.model.orm import lazyload
-from galaxy.datatypes.tabular import *
-from galaxy.datatypes.interval import *
-from galaxy.datatypes import metadata
-from galaxy.util.bunch import Bunch
-from sqlalchemy import or_
-
-import pkg_resources
-pkg_resources.require( "PasteDeploy" )
-
-from paste.deploy.converters import asbool
-
-from Queue import Queue, Empty
-
-log = logging.getLogger( __name__ )
-
-uci_states = Bunch(
- NEW_UCI = "newUCI",
- NEW = "new",
- CREATING = "creating",
- DELETING_UCI = "deletingUCI",
- DELETING = "deleting",
- DELETED = "deleted",
- SUBMITTED_UCI = "submittedUCI",
- SUBMITTED = "submitted",
- SHUTTING_DOWN_UCI = "shutting-downUCI",
- SHUTTING_DOWN = "shutting-down",
- ADD_STORAGE_UCI = "add-storageUCI",
- ADD_STORAGE = "add-storage",
- AVAILABLE = "available",
- RUNNING = "running",
- PENDING = "pending",
- ERROR = "error",
- SNAPSHOT_UCI = "snapshotUCI",
- SNAPSHOT = "snapshot"
-)
-instance_states = Bunch(
- TERMINATED = "terminated",
- SUBMITTED = "submitted",
- RUNNING = "running",
- ADDING = "adding-storage",
- PENDING = "pending",
- SHUTTING_DOWN = "shutting-down",
- ERROR = "error"
-)
-
-store_status = Bunch(
- WAITING = "waiting",
- IN_USE = "in-use",
- ADDING = "adding",
- CREATING = "creating",
- DELETED = 'deleted',
- ERROR = "error"
-)
-
-snapshot_status = Bunch(
- SUBMITTED = 'submitted',
- PENDING = 'pending',
- COMPLETED = 'completed',
- DELETE = 'delete',
- DELETED= 'deleted',
- ERROR = "error"
-)
-
-class CloudManager( object ):
- """
- Highest level interface to cloud management.
- """
- def __init__( self, app ):
- self.app = app
- self.sa_session = app.model.context
- if self.app.config.enable_cloud_execution == True:
- # The dispatcher manager for underlying cloud instances - implements and contacts individual cloud providers
- self.provider = CloudProvider( app )
- # Monitor for updating status of cloud instances
- self.cloud_monitor = CloudMonitor( self.app, self.provider )
- else:
- self.job_queue = self.job_stop_queue = NoopCloudMonitor()
-
- def shutdown( self ):
- self.cloud_monitor.shutdown()
-
-class Sleeper( object ):
- """
- Provides a 'sleep' method that sleeps for a number of seconds *unless*
- the notify method is called (from a different thread).
- """
- def __init__( self ):
- self.condition = threading.Condition()
- def sleep( self, seconds ):
- self.condition.acquire()
- self.condition.wait( seconds )
- self.condition.release()
- def wake( self ):
- self.condition.acquire()
- self.condition.notify()
- self.condition.release()
-
-class CloudMonitor( object ):
- """
- Cloud manager, waits for user to instantiate a cloud instance and then invokes a
- CloudProvider.
- """
- STOP_SIGNAL = object()
- def __init__( self, app, provider ):
- """Start the cloud manager"""
- self.app = app
- # Keep track of the pid that started the cloud manager, only it
- # has valid threads
- self.parent_pid = os.getpid()
- self.sa_session = app.model.context
-
- # Contains requests that are waiting (only use from monitor thread)
- self.waiting = []
-
- # Helper for interruptable sleep
- self.sleeper = Sleeper()
- self.running = True
- self.provider = provider
- self.monitor_thread = threading.Thread( target=self.__monitor )
- self.monitor_thread.start()
- log.info( "Cloud manager started" )
-
- def __monitor( self ):
- """
- Daemon that continuously monitors cloud instance requests as well as state
- of running instances.
- """
- # HACK: Delay until after forking, we need a way to do post fork notification!!!
- time.sleep( 10 )
-
- cnt = 0 # Run global update only periodically so keep counter variable
- while self.running:
- try:
- self.__monitor_step()
- if cnt%30 == 0: # Run global update every 30 iterations (1 minute)
- self.provider.update()
- cnt = 0
- except:
- log.exception( "Exception in cloud manager monitor_step" )
- # Sleep
- cnt += 1
- self.sleeper.sleep( 2 )
-
- def __monitor_step( self ):
- """
- Called repeatedly by `monitor` to process cloud instance requests.
- TODO: Update following description to match the code
- Gets any new cloud instance requests from the database, then iterates
- over all new and waiting jobs to check the state of the jobs each
- depends on. If the job has dependencies that have not finished, it
- it goes to the waiting queue. If the job has dependencies with errors,
- it is marked as having errors and removed from the queue. Otherwise,
- the job is dispatched.
- """
- model = self.app.model
- new_requests = []
-
- for r in self.sa_session.query( model.UCI ) \
- .filter( or_( model.UCI.table.c.state==uci_states.NEW_UCI,
- model.UCI.table.c.state==uci_states.SUBMITTED_UCI,
- model.UCI.table.c.state==uci_states.SHUTTING_DOWN_UCI,
- model.UCI.table.c.state==uci_states.DELETING_UCI,
- model.UCI.table.c.state==uci_states.SNAPSHOT_UCI,
- model.UCI.table.c.state==uci_states.ADD_STORAGE_UCI ) ) \
- .all():
- uci_wrapper = UCIwrapper( r, self.app )
- new_requests.append( uci_wrapper )
-
- for uci_wrapper in new_requests:
- self.sa_session.expunge_all()
- self.put( uci_wrapper )
-
- def put( self, uci_wrapper ):
- """Add a request to the queue."""
- self.provider.put( uci_wrapper )
- self.sleeper.wake()
-
- def shutdown( self ):
- """Attempts to gracefully shut down the worker thread"""
- if self.parent_pid != os.getpid():
- # We're not the real queue, do nothing
- return
- else:
- log.info( "Sending stop signal to worker thread" )
- self.running = False
- self.sleeper.wake()
- log.info( "cloud manager stopped" )
- self.dispatcher.shutdown()
-
-class UCIwrapper( object ):
- """
- Wraps 'model.UCI' with convenience methods for state management
- """
- def __init__( self, uci, app ):
- self.uci_id = uci.id
- self.app = app
- self.sa_session = self.app.model.context
- base_directory = os.path.join( self.app.config.job_working_directory, "cloud" )
- self.working_directory = os.path.join( base_directory, str( self.uci_id ) )
-# log.debug( "Cloud controller working directory for UCI DB ID '%s': '%s'" % ( self.uci_id, self.working_directory ) )
- if not os.path.exists( base_directory ):
- os.mkdir( base_directory )
-
-
- # --------- Setter methods -----------------
-
- def change_state( self, uci_state=None, instance_id=None, i_state=None ):
- """
- Sets state for UCI and/or UCI's instance with instance_id as provided by cloud provider and stored in local
- Galaxy database.
- Need to provide either: (1) state for the UCI, or (2) instance_id and it's state, or (3) all arguments.
- """
-# log.debug( "Changing state - new uci_state: %s, instance_id: %s, i_state: %s" % ( uci_state, instance_id, i_state ) )
- if uci_state is not None:
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- uci.state = uci_state
- self.sa_session.flush()
- if ( instance_id is not None ) and ( i_state is not None ):
- instance = self.sa_session.query( model.CloudInstance ).filter_by( uci_id=self.uci_id, instance_id=instance_id).first()
- instance.state = i_state
- self.sa_session.add( instance )
- self.sa_session.flush()
-
- def set_mi( self, i_index, mi_id ):
- """
- Sets Machine Image (MI), e.g., 'ami-66fa190f', for UCI's instance with given index as it
- is stored in local Galaxy database.
- """
- mi = self.sa_session.query( model.CloudImage ).filter( model.CloudImage.table.c.image_id==mi_id ).first()
- instance = self.sa_session.query( model.CloudInstance ).get( i_index )
- instance.image = mi
- self.sa_session.add( instance )
- self.sa_session.flush()
-
- def set_key_pair( self, key_name, key_material=None ):
- """
- Sets key pair value for current UCI.
- """
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- uci.key_pair_name = key_name
- if key_material is not None:
- uci.key_pair_material = key_material
- self.sa_session.flush()
-
- def set_instance_launch_time( self, launch_time, i_index=None, i_id=None ):
- """
- Stores launch time in local database for instance with specified index - i_index (as it is stored in local
- Galaxy database) or with specified instance ID - i_id (as obtained from the cloud provider AND stored
- in local Galaxy Database). Either 'i_index' or 'i_id' needs to be provided.
- """
- if i_index != None:
- instance = self.sa_session.query( model.CloudInstance ).get( i_index )
- elif i_id != None:
- instance = self.sa_session.query( model.CloudInstance ).filter_by( uci_id=self.uci_id, instance_id=i_id).first()
- else:
- return None
-
- instance.launch_time = launch_time
- self.sa_session.add( instance )
- self.sa_session.flush()
-
- def set_uci_launch_time( self, launch_time ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- uci.launch_time = launch_time
- self.sa_session.add( uci )
- self.sa_session.flush()
-
- def set_stop_time( self, stop_time, i_index=None, i_id=None ):
- """
- Stores stop time in local database for instance with specified index - i_index (as it is stored in local
- Galaxy database) or with specified instance ID - i_id (as obtained from the cloud provider AND stored
- in local Galaxy Database). Either 'i_index' or 'i_id' needs to be provided.
- """
- if i_index != None:
- instance = self.sa_session.query( model.CloudInstance ).get( i_index )
- elif i_id != None:
- instance = self.sa_session.query( model.CloudInstance ).filter_by( uci_id=self.uci_id, instance_id=i_id).first()
- else:
- return None
-
- instance.stop_time = stop_time
- self.sa_session.add( instance )
- self.sa_session.flush()
-
- def set_security_group_name( self, security_group_name, i_index=None, i_id=None ):
- """
- Stores security group name in local database for instance with specified index - i_index (as it is stored in local
- Galaxy database) or with specified instance ID - i_id (as obtained from the cloud provider AND stored
- in local Galaxy Database). Either 'i_index' or 'i_id' needs to be provided.
- """
- if i_index != None:
- instance = self.sa_session.query( model.CloudInstance ).get( i_index )
- elif i_id != None:
- instance = self.sa_session.query( model.CloudInstance ).filter_by( uci_id=self.uci_id, instance_id=i_id).first()
- else:
- return None
-
- instance.security_group = security_group_name
- self.sa_session.add( instance )
- self.sa_session.flush()
-
- def set_reservation_id( self, i_index, reservation_id ):
- instance = self.sa_session.query( model.CloudInstance ).get( i_index )
- instance.reservation_id = reservation_id
- self.sa_session.add( instance )
- self.sa_session.flush()
-
- def set_instance_id( self, i_index, instance_id ):
- """
- i_index refers to UCI's instance ID as stored in local database
- instance_id refers to real-world, cloud resource ID (e.g., 'i-78hd823a')
- """
- instance = self.sa_session.query( model.CloudInstance ).get( i_index )
- instance.instance_id = instance_id
- self.sa_session.add( instance )
- self.sa_session.flush()
-
-# def set_public_dns( self, instance_id, public_dns ):
-# uci = self.sa_session.query( model.UCI ).get( self.uci_id )
-# self.sa_session.refresh( uci )
-# uci.instance[instance_id].public_dns = public_dns
-# uci.instance[instance_id].flush()
-#
-# def set_private_dns( self, instance_id, private_dns ):
-# uci = self.sa_session.query( model.UCI ).get( self.uci_id )
-# self.sa_session.refresh( uci )
-# uci.instance[instance_id].private_dns = private_dns
-# uci.instance[instance_id].flush()
-
- def reset_uci_launch_time( self ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- uci.launch_time = None
- self.sa_session.add( uci )
- self.sa_session.flush()
-
- def set_error( self, error, set_state=False ):
- """
- Sets error field of given UCI in local Galaxy database as well as any instances associated with
- this UCI whose state is 'None' or 'SUBMITTED'. If set_state is set to 'true',
- method also sets state of give UCI and corresponding instances to 'error'
- """
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- uci.error = error
- if set_state:
- uci.state = uci_states.ERROR
- # Process all instances associated with this UCI
- instances = self.sa_session.query( model.CloudInstance ) \
- .filter_by( uci=uci ) \
- .filter( or_( model.CloudInstance.table.c.state==None, model.CloudInstance.table.c.state==instance_states.SUBMITTED ) ) \
- .all()
- for i in instances:
- i.error = error
- i.state = instance_states.ERROR
- self.sa_session.add( i )
- self.sa_session.flush()
-
- self.sa_session.add( uci )
- self.sa_session.flush()
-
- def set_deleted( self ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- uci.state = uci_states.DELETED # for bookkeeping reasons, mark as deleted but don't actually delete.
- uci.deleted = True
- self.sa_session.add( uci )
- self.sa_session.flush()
-
-# def set_store_device( self, store_id, device ):
-# uci = self.sa_session.query( model.UCI ).get( self.uci_id )
-# self.sa_session.refresh( uci )
-# uci.store[store_id].device = device
-# uci.store[store_id].flush()
-
- def set_uci_total_size( self, total_size ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- uci.total_size = total_size
- self.sa_session.add( uci )
- self.sa_session.flush()
-
- def set_store_error( self, error, store_index=None, store_id=None ):
- if store_index != None:
- store = self.sa_session.query( model.CloudStore ).get( store_index )
- elif store_id != None:
- store = self.sa_session.query( model.CloudStore ).filter_by( volume_id = store_id ).first()
- else:
- return None
-
- store.error = error
- store.status = store_status.ERROR
- self.sa_session.add( store )
- self.sa_session.flush()
-
- def set_store_status( self, vol_id, status ):
- vol = self.sa_session.query( model.CloudStore ).filter( model.CloudStore.table.c.volume_id == vol_id ).first()
- vol.status = status
- self.sa_session.add( vol )
- self.sa_session.flush()
-
- def set_store_availability_zone( self, availability_zone, vol_id=None ):
- """
- Sets availability zone of storage volumes for either ALL volumes associated with current
- UCI or for the volume whose volume ID (e.g., 'vol-39F80512') is provided as argument.
- """
- if vol_id is not None:
- vol = self.sa_session.query( model.CloudStore ).filter( model.CloudStore.table.c.volume_id == vol_id ).all()
- else:
- vol = self.sa_session.query( model.CloudStore ).filter( model.CloudStore.table.c.uci_id == self.uci_id ).all()
-
- for v in vol:
- v.availability_zone = availability_zone
- self.sa_session.add( v )
- self.sa_session.flush()
-
- def set_store_volume_id( self, store_index, volume_id ):
- """
- Given store index as it is stored in local database, set volume ID as it is registered
- on the cloud provider (e.g., vol-39890501)
- """
-
- if store_index != None:
- store = self.sa_session.query( model.CloudStore ).get( store_index )
- store.volume_id = volume_id
- self.sa_session.add( store )
- self.sa_session.flush()
- else:
- return None
-
-# uci = self.sa_session.query( model.UCI ).get( self.uci_id )
-# self.sa_session.refresh( uci )
-# uci.store[store_index].volume_id = volume_id
-# #uci.store[store_index].flush()
-# self.sa_session.add( uci )
-# self.sa_session.flush()
-
- def set_store_instance( self, vol_id, instance_id ):
- """
- Stores instance ID that given store volume is attached to. Store volume ID should
- be given in following format: 'vol-78943248'
- """
- vol = self.sa_session.query( model.CloudStore ).filter( model.CloudStore.table.c.volume_id == vol_id ).first()
- inst = self.sa_session.query( model.CloudInstance ).filter_by( instance_id=instance_id ).first()
- vol.inst = inst
- self.sa_session.add( vol )
- self.sa_session.flush()
-
- def set_store_device( self, vol_id, device ):
- """
- Stores instance ID that given store volume is attached to. Store volume ID should
- be given in following format: 'vol-78943248'
- """
- vol = self.sa_session.query( model.CloudStore ).filter( model.CloudStore.table.c.volume_id == vol_id ).first()
- vol.device = str( device )
- self.sa_session.add( vol )
- self.sa_session.flush()
-
- def set_store_deleted( self, vol_id, status=None ):
- """
- Set storage volume as deleted in local database. Optionally, set the volume status too.
- """
- vol = self.sa_session.query( model.CloudStore ).filter( model.CloudStore.table.c.volume_id == vol_id ).first()
- vol.deleted = True
- if status != None:
- vol.status = status
- self.sa_session.add( vol )
- self.sa_session.flush()
-
- def set_snapshot_id( self, snap_index, id ):
- snap = model.CloudSnapshot.get( snap_index )
-
- snap.snapshot_id = id
- self.sa_session.add( snap )
- self.sa_session.flush()
-
- def set_snapshot_status( self, status, snap_index=None, snap_id=None ):
- if snap_index != None:
- snap = self.sa_session.query( model.CloudSnapshot ).get( snap_index )
- elif snap_id != None:
- snap = self.sa_session.query( model.CloudSnapshot ).filter_by( snapshot_id = snap_id).first()
- else:
- return
- snap.status = status
- self.sa_session.add( snap )
- self.sa_session.flush()
-
- def set_snapshot_error( self, error, snap_index=None, snap_id=None, set_status=False ):
- if snap_index != None:
- snap = self.sa_session.query( model.CloudSnapshot ).get( snap_index )
- elif snap_id != None:
- snap = self.sa_session.query( model.CloudSnapshot ).filter_by( snapshot_id = snap_id).first()
- else:
- return
- snap.error = error
- if set_status:
- snap.status = snapshot_status.ERROR
-
- self.sa_session.add( snap )
- self.sa_session.flush()
-
- # --------- Getter methods -----------------
-
- def get_provider_type( self ):
- """ Returns type of cloud provider associated with given UCI. """
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.credentials.provider.type
-
- def get_provider( self ):
- """ Returns database object of cloud provider associated with credentials of given UCI. """
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.credentials.provider
-
- def get_instance_type( self, i_index ):
- instance = self.sa_session.query( model.CloudInstance ).get( i_index )
- self.sa_session.refresh( instance )
- return instance.type
-
- def get_uci_state( self ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.state
-
- def get_instances_indexes( self, state=None ):
- """
- Returns indexes of instances associated with given UCI as they are stored in local Galaxy database and
- whose state corresponds to passed argument. Returned values enable indexing instances from local Galaxy database.
- """
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- instances = self.sa_session.query( model.CloudInstance ) \
- .filter_by( uci=uci ) \
- .filter( model.CloudInstance.table.c.state==state ) \
- .all()
- il = []
- for i in instances:
- il.append( i.id )
-
- return il
-
- def get_instance_state( self, instance_id ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.instance[instance_id].state
-
- def get_instaces_in_state( self, state ):
- """ Get database objects of all instances associated with this UCI in given state. """
- return self.sa_session.query( model.CloudInstance ) \
- .filter_by( uci_id=self.uci_id, state = state ) \
- .all()
-
- def get_instances_ids( self ):
- """
- Returns list IDs of all instances' associated with this UCI that are not in 'terminated' or
- 'error' but the state is defined (i.e., state is not None)
- (e.g., return value: ['i-402906D2', 'i-q0290dsD2'] ).
- """
- il = self.sa_session.query( model.CloudInstance ) \
- .filter_by( uci_id=self.uci_id ) \
- .filter( or_( model.CloudInstance.table.c.state != 'terminated',
- model.CloudInstance.table.c.state != 'error',
- model.CloudInstance.table.c.state != None ) ) \
- .all()
- instanceList = []
- for i in il:
- instanceList.append( i.instance_id )
- return instanceList
-
- def get_name( self ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.name
-
- def get_key_pair_name( self ):
- """
- Returns keypair name associated with given UCI.
- """
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.key_pair_name
-
- def get_key_pair_material( self ):
- """
- Returns keypair material (i.e., private key) associated with given UCI.
- """
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.key_pair_material
-
- def get_security_group_name( self, i_index=None, i_id=None ):
- """
- Given EITHER instance index as it is stored in local Galaxy database OR instance ID as it is
- obtained from cloud provider and stored in local Galaxy database, return security group name associated
- with given instance.
- """
- if i_index != None:
- instance = self.sa_session.query( model.CloudInstance ).get( i_index )
- return instance.security_group
- elif i_id != None:
- instance = self.sa_session.query( model.CloudInstance ).filter_by( uci_id=self.uci_id, instance_id=i_id).first()
- return instance.security_group
-
- def get_access_key( self ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.credentials.access_key
-
- def get_secret_key( self ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.credentials.secret_key
-
- def get_mi_id( self, instance_id=0 ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.instance[instance_id].image.image_id
-
- def get_public_dns( self, instance_id=0 ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.instance[instance_id].public_dns
-
- def get_private_dns( self, instance_id=0 ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.instance[instance_id].private_dns
-
- def get_uci_availability_zone( self ):
- """
- Returns UCI's availability zone.
- Because all of storage volumes associated with a given UCI must be in the same
- availability zone, availability of a UCI is determined by availability zone of
- any one storage volume.
- """
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.store[0].availability_zone
-
- def get_uci_total_size( self ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.total_size
-
- def get_store_size( self, store_id=0 ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.store[store_id].size
-
- def get_store_volume_id( self, store_id=0 ):
- """
- Given store ID associated with this UCI, get volume ID as it is registered
- on the cloud provider (e.g., 'vol-39890501')
- """
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.store[store_id].volume_id
-
- def get_all_stores_in_status( self, status ):
- """
- Return database objects of all stores associated with this UCI that have their
- status set to value passed as parameter.
- """
- return self.sa_session.query( model.CloudStore ).filter_by( deleted=False, uci_id=self.uci_id, status=status ).all()
-
- def get_all_stores( self ):
- """ Returns all storage volumes' database objects associated with this UCI that have not been marked as 'deleted'. """
- return self.sa_session.query( model.CloudStore ) \
- .filter_by( deleted=False, uci_id=self.uci_id ) \
- .all()
-
- def get_snapshots( self, status=None ):
- """ Returns database objects for all snapshots associated with this UCI and in given status."""
- return self.sa_session.query( model.CloudSnapshot ).filter_by( uci_id=self.uci_id, status=status ).all()
-
- def get_uci( self ):
- """ Returns database object for given UCI. """
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci
-
- def get_uci_working_directory( self ):
- return self.working_directory
-
- def uci_launch_time_set( self ):
- uci = self.sa_session.query( model.UCI ).get( self.uci_id )
- self.sa_session.refresh( uci )
- return uci.launch_time
-
-class CloudProvider( object ):
- def __init__( self, app ):
- import providers.eucalyptus
- import providers.ec2
-
- self.app = app
- self.cloud_provider = {}
- self.cloud_provider["eucalyptus"] = providers.eucalyptus.EucalyptusCloudProvider( app )
- self.cloud_provider["ec2"] = providers.ec2.EC2CloudProvider( app )
-
- def put( self, uci_wrapper ):
- """ Put given request for UCI manipulation into provider's request queue."""
- self.cloud_provider[uci_wrapper.get_provider_type()].put( uci_wrapper )
-
- def update( self ):
- """
- Runs a global status update across all providers for all UCIs in state other than 'terminated' and 'available'.
- Reason behind this method is to sync state of local DB and real world resources.
- """
- for provider in self.cloud_provider.keys():
-# log.debug( "Running global update for provider: '%s'" % provider )
- self.cloud_provider[provider].update()
-
- def shutdown( self ):
- for runner in self.cloud_provider.itervalues():
- runner.shutdown()
-
-class NoopCloudMonitor( object ):
- """
- Implements the CloudMonitor interface but does nothing
- """
- def put( self, *args ):
- return
- def shutdown( self ):
- return
-
1
0

galaxy-dist commit 040be2b94b8c: Added logging to EmailAction. Should gracefully fail if email is not available.
by commits-noreply@bitbucket.org 29 Jun '10
by commits-noreply@bitbucket.org 29 Jun '10
29 Jun '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Dannon Baker <dannon.baker(a)emory.edu>
# Date 1277252544 14400
# Node ID 040be2b94b8ce845278d5ac19139a4bdae8fb088
# Parent beb30aadccd75bfdec6b06d4afcd704c2c16dbc3
Added logging to EmailAction. Should gracefully fail if email is not available.
--- a/lib/galaxy/jobs/actions/post.py
+++ b/lib/galaxy/jobs/actions/post.py
@@ -67,7 +67,7 @@ class EmailAction(DefaultJobAction):
def execute(cls, trans, action, job):
smtp_server = trans.app.config.smtp_server
if smtp_server is None:
- return trans.show_error_message( "Mail is not configured for this galaxy instance, workflow action aborted." )
+ log.error("Mail is not configured for this galaxy instance. Workflow action aborted.")
# Build the email message
msg = MIMEText( "Your job '%s' at Galaxy instance %s is complete as of %s." % (job.history.name, trans.request.host, job.update_time))
msg[ 'To' ] = job.user.email
@@ -78,9 +78,8 @@ class EmailAction(DefaultJobAction):
s.connect( smtp_server )
s.sendmail( frm, [ to ], msg.as_string() )
s.close()
- return trans.show_ok_message( "Your error report has been sent" )
except Exception, e:
- return trans.show_error_message( "An error occurred sending the report by email: %s" % str( e ) )
+ log.error("EmailAction PJA Failed, exception: %s" % e)
@classmethod
def get_config_form(cls, trans):
1
0

galaxy-dist commit 03eb69c6e92e: Improvements to GOPS subtract: (a) preserve metadata for interval inputs; (b) allow arbitrary mix of interval and GFF inputs; and (c) functional tests updated to test new functionality.
by commits-noreply@bitbucket.org 29 Jun '10
by commits-noreply@bitbucket.org 29 Jun '10
29 Jun '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User jeremy goecks <jeremy.goecks(a)emory.edu>
# Date 1277304197 14400
# Node ID 03eb69c6e92ea5472b7627bd9990327f7546a8a7
# Parent 45e1d5636fffd284ea6cda2f14318b364370e985
Improvements to GOPS subtract: (a) preserve metadata for interval inputs; (b) allow arbitrary mix of interval and GFF inputs; and (c) functional tests updated to test new functionality.
--- a/tools/new_operations/gops_subtract.py
+++ b/tools/new_operations/gops_subtract.py
@@ -8,7 +8,8 @@ usage: %prog bed_file_1 bed_file_2 out_f
-2, --cols2=N,N,N,N: Columns for start, end, strand in second file
-m, --mincols=N: Require this much overlap (default 1bp)
-p, --pieces: just print pieces of second set (after padding)
- -G, --gff: inputs are GFF format, meaning start and end coordinates are 1-based, closed interval
+ -G, --gff1: input 1 is GFF format, meaning start and end coordinates are 1-based, closed interval
+ -H, --gff2: input 2 is GFF format, meaning start and end coordinates are 1-based, closed interval
"""
from galaxy import eggs
import pkg_resources
@@ -35,24 +36,29 @@ def main():
chr_col_2, start_col_2, end_col_2, strand_col_2 = parse_cols_arg( options.cols2 )
if options.mincols: mincols = int( options.mincols )
pieces = bool( options.pieces )
- gff_format = bool( options.gff )
+ in1_gff_format = bool( options.gff1 )
+ in2_gff_format = bool( options.gff2 )
in_fname, in2_fname, out_fname = args
except:
doc_optparse.exception()
- # Set reader to handle either GFF or default format.
- if gff_format:
- reader_wrapper = GFFReaderWrapper
+ # Set readers to handle either GFF or default format.
+ if in1_gff_format:
+ in1_reader_wrapper = GFFReaderWrapper
else:
- reader_wrapper = NiceReaderWrapper
+ in1_reader_wrapper = NiceReaderWrapper
+ if in2_gff_format:
+ in2_reader_wrapper = GFFReaderWrapper
+ else:
+ in2_reader_wrapper = NiceReaderWrapper
- g1 = reader_wrapper( fileinput.FileInput( in_fname ),
+ g1 = in1_reader_wrapper( fileinput.FileInput( in_fname ),
chrom_col=chr_col_1,
start_col=start_col_1,
end_col=end_col_1,
strand_col=strand_col_1,
fix_strand=True )
- g2 = reader_wrapper( fileinput.FileInput( in2_fname ),
+ g2 = in2_reader_wrapper( fileinput.FileInput( in2_fname ),
chrom_col=chr_col_2,
start_col=start_col_2,
end_col=end_col_2,
@@ -64,7 +70,7 @@ def main():
try:
for line in subtract( [g1,g2], pieces=pieces, mincols=mincols ):
if type( line ) is GenomicInterval:
- if gff_format:
+ if in1_gff_format:
line = convert_to_gff_coordinates( line )
out_file.write( "%s\n" % "\t".join( line.fields ) )
else:
--- a/tools/new_operations/operation_filter.py
+++ b/tools/new_operations/operation_filter.py
@@ -28,13 +28,17 @@ def validate_input( trans, error_map, pa
# check meta data
try:
param = param_values[name]
- startCol = int( param.metadata.startCol )
- endCol = int( param.metadata.endCol )
- chromCol = int( param.metadata.chromCol )
- if param.metadata.strandCol is not None:
- strandCol = int ( param.metadata.strandCol )
- else:
- strandCol = 0
+ if isinstance( param.datatype, trans.app.datatypes_registry.get_datatype_by_extension( 'gff' ).__class__ ):
+ # TODO: currently cannot validate GFF inputs b/c they are not derived from interval.
+ pass
+ else: # Validate interval datatype.
+ startCol = int( param.metadata.startCol )
+ endCol = int( param.metadata.endCol )
+ chromCol = int( param.metadata.chromCol )
+ if param.metadata.strandCol is not None:
+ strandCol = int ( param.metadata.strandCol )
+ else:
+ strandCol = 0
except:
error_msg = "The attributes of this dataset are not properly set. " + \
"Click the pencil icon in the history item to set the chrom, start, end and strand columns."
--- /dev/null
+++ b/test-data/gops_subtract_in2.bed
@@ -0,0 +1,500 @@
+chr13 3633323 3651020 transcript 0 +
+chr13 3633323 3633421 exon 0 +
+chr13 3641310 3641498 exon 0 +
+chr13 3642720 3642871 exon 0 +
+chr13 3644161 3644296 exon 0 +
+chr13 3648564 3648756 exon 0 +
+chr13 3649425 3651020 exon 0 +
+chr13 3802138 3803564 transcript 0 -
+chr13 3802138 3803564 exon 0 -
+chr13 3881808 3892824 transcript 0 -
+chr13 3881808 3883719 exon 0 -
+chr13 3884048 3884235 exon 0 -
+chr13 3885293 3885464 exon 0 -
+chr13 3885783 3886041 exon 0 -
+chr13 3886616 3886693 exon 0 -
+chr13 3886816 3886913 exon 0 -
+chr13 3887223 3887286 exon 0 -
+chr13 3887600 3887768 exon 0 -
+chr13 3888116 3888224 exon 0 -
+chr13 3892538 3892824 exon 0 -
+chr13 4131872 4149877 transcript 0 -
+chr13 4131872 4132082 exon 0 -
+chr13 4134448 4134531 exon 0 -
+chr13 4135847 4136013 exon 0 -
+chr13 4136379 4136489 exon 0 -
+chr13 4141372 4141495 exon 0 -
+chr13 4142463 4142541 exon 0 -
+chr13 4143535 4143652 exon 0 -
+chr13 4144451 4144619 exon 0 -
+chr13 4149760 4149877 exon 0 -
+chr13 4232985 4247605 transcript 0 +
+chr13 4232985 4233100 exon 0 +
+chr13 4235334 4235502 exon 0 +
+chr13 4236281 4236398 exon 0 +
+chr13 4237639 4237717 exon 0 +
+chr13 4238213 4238336 exon 0 +
+chr13 4241797 4241907 exon 0 +
+chr13 4242172 4242338 exon 0 +
+chr13 4246048 4246131 exon 0 +
+chr13 4247323 4247605 exon 0 +
+chr13 4247980 4249023 transcript 0 +
+chr13 4247980 4249023 exon 0 +
+chr13 4591735 4608410 transcript 0 -
+chr13 4591735 4592544 exon 0 -
+chr13 4592807 4592890 exon 0 -
+chr13 4594316 4594400 exon 0 -
+chr13 4594905 4594978 exon 0 -
+chr13 4596730 4596828 exon 0 -
+chr13 4598015 4598138 exon 0 -
+chr13 4600463 4600541 exon 0 -
+chr13 4601926 4602043 exon 0 -
+chr13 4606636 4606804 exon 0 -
+chr13 4608330 4608410 exon 0 -
+chr13 5860734 5869639 transcript 0 +
+chr13 5860734 5861088 exon 0 +
+chr13 5864013 5864590 exon 0 +
+chr13 5865884 5866008 exon 0 +
+chr13 5866477 5869639 exon 0 +
+chr13 3537320 3565507 transcript 0 +
+chr13 3537320 3537589 exon 0 +
+chr13 3548108 3548216 exon 0 +
+chr13 3550234 3550334 exon 0 +
+chr13 3553657 3553792 exon 0 +
+chr13 3555559 3555758 exon 0 +
+chr13 3556171 3556303 exon 0 +
+chr13 3559237 3559337 exon 0 +
+chr13 3561111 3561283 exon 0 +
+chr13 3563785 3563930 exon 0 +
+chr13 3564104 3564159 exon 0 +
+chr13 3564241 3565507 exon 0 +
+chr13 3565280 3610354 transcript 0 -
+chr13 3565280 3565913 exon 0 -
+chr13 3566163 3566278 exon 0 -
+chr13 3566681 3566863 exon 0 -
+chr13 3567997 3568103 exon 0 -
+chr13 3568733 3568887 exon 0 -
+chr13 3569557 3569683 exon 0 -
+chr13 3572732 3576446 exon 0 -
+chr13 3580997 3581439 exon 0 -
+chr13 3583745 3584619 exon 0 -
+chr13 3587543 3587780 exon 0 -
+chr13 3589186 3589894 exon 0 -
+chr13 3593376 3593439 exon 0 -
+chr13 3593538 3593611 exon 0 -
+chr13 3594742 3594846 exon 0 -
+chr13 3596022 3596123 exon 0 -
+chr13 3598897 3598997 exon 0 -
+chr13 3599082 3599102 exon 0 -
+chr13 3599184 3599308 exon 0 -
+chr13 3599437 3599580 exon 0 -
+chr13 3610035 3610354 exon 0 -
+chr13 8202154 8759554 transcript 0 +
+chr13 8202154 8202566 exon 0 +
+chr13 8558349 8558436 exon 0 +
+chr13 8568912 8569820 exon 0 +
+chr13 8671650 8671765 exon 0 +
+chr13 8696865 8697034 exon 0 +
+chr13 8700825 8700977 exon 0 +
+chr13 8712845 8713014 exon 0 +
+chr13 8731035 8731217 exon 0 +
+chr13 8751818 8751997 exon 0 +
+chr13 8756471 8759554 exon 0 +
+chr13 8884851 8891641 transcript 0 +
+chr13 8884851 8885242 exon 0 +
+chr13 8885977 8886150 exon 0 +
+chr13 8886730 8886823 exon 0 +
+chr13 8887168 8887299 exon 0 +
+chr13 8889563 8891641 exon 0 +
+chr13 8802213 8870288 transcript 0 -
+chr13 8802213 8805192 exon 0 -
+chr13 8819108 8819223 exon 0 -
+chr13 8819656 8819791 exon 0 -
+chr13 8834542 8834684 exon 0 -
+chr13 8836028 8836269 exon 0 -
+chr13 8841975 8842052 exon 0 -
+chr13 8844032 8844077 exon 0 -
+chr13 8846860 8846932 exon 0 -
+chr13 8848779 8848915 exon 0 -
+chr13 8852915 8852980 exon 0 -
+chr13 8853245 8853341 exon 0 -
+chr13 8856077 8856174 exon 0 -
+chr13 8860333 8860511 exon 0 -
+chr13 8870115 8870288 exon 0 -
+chr13 8849724 8870288 transcript 0 -
+chr13 8849724 8852980 exon 0 -
+chr13 8853245 8853341 exon 0 -
+chr13 8856077 8856174 exon 0 -
+chr13 8860333 8860511 exon 0 -
+chr13 8870115 8870288 exon 0 -
+chr13 8971723 8995258 transcript 0 -
+chr13 8971723 8972520 exon 0 -
+chr13 8973409 8973553 exon 0 -
+chr13 8974190 8974256 exon 0 -
+chr13 8976481 8976679 exon 0 -
+chr13 8977773 8977874 exon 0 -
+chr13 8978674 8978726 exon 0 -
+chr13 8979147 8979225 exon 0 -
+chr13 8984454 8984565 exon 0 -
+chr13 8984883 8984973 exon 0 -
+chr13 8986494 8986560 exon 0 -
+chr13 8987001 8987193 exon 0 -
+chr13 8988298 8988391 exon 0 -
+chr13 8989943 8990044 exon 0 -
+chr13 8990960 8991097 exon 0 -
+chr13 8991177 8991281 exon 0 -
+chr13 8991890 8992061 exon 0 -
+chr13 8995194 8995258 exon 0 -
+chr13 6547402 6579395 transcript 0 +
+chr13 6547402 6547475 exon 0 +
+chr13 6548825 6548928 exon 0 +
+chr13 6551879 6551986 exon 0 +
+chr13 6552427 6552579 exon 0 +
+chr13 6554599 6554714 exon 0 +
+chr13 6554797 6554894 exon 0 +
+chr13 6555787 6555948 exon 0 +
+chr13 6557007 6557134 exon 0 +
+chr13 6557401 6557490 exon 0 +
+chr13 6558600 6558729 exon 0 +
+chr13 6559275 6559389 exon 0 +
+chr13 6559847 6559944 exon 0 +
+chr13 6561198 6561333 exon 0 +
+chr13 6562621 6562760 exon 0 +
+chr13 6564236 6564353 exon 0 +
+chr13 6566595 6566728 exon 0 +
+chr13 6567909 6568030 exon 0 +
+chr13 6568144 6568221 exon 0 +
+chr13 6568445 6568611 exon 0 +
+chr13 6569847 6569948 exon 0 +
+chr13 6572286 6572407 exon 0 +
+chr13 6573605 6573677 exon 0 +
+chr13 6574262 6574375 exon 0 +
+chr13 6576635 6576761 exon 0 +
+chr13 6577686 6577832 exon 0 +
+chr13 6578663 6578766 exon 0 +
+chr13 6578895 6579395 exon 0 +
+chr13 6579119 6647970 transcript 0 -
+chr13 6579119 6580838 exon 0 -
+chr13 6581648 6581751 exon 0 -
+chr13 6583846 6583946 exon 0 -
+chr13 6585725 6585837 exon 0 -
+chr13 6586297 6586359 exon 0 -
+chr13 6587734 6587899 exon 0 -
+chr13 6597104 6597257 exon 0 -
+chr13 6597984 6598072 exon 0 -
+chr13 6599841 6599912 exon 0 -
+chr13 6601958 6602105 exon 0 -
+chr13 6602324 6602394 exon 0 -
+chr13 6602644 6602709 exon 0 -
+chr13 6604201 6604327 exon 0 -
+chr13 6604881 6604974 exon 0 -
+chr13 6613949 6614045 exon 0 -
+chr13 6618420 6618529 exon 0 -
+chr13 6618765 6618810 exon 0 -
+chr13 6620131 6620297 exon 0 -
+chr13 6621152 6621342 exon 0 -
+chr13 6624381 6624459 exon 0 -
+chr13 6635170 6635244 exon 0 -
+chr13 6647816 6647970 exon 0 -
+chr13 9093150 9172336 transcript 0 +
+chr13 9093150 9093426 exon 0 +
+chr13 9121353 9121472 exon 0 +
+chr13 9123148 9123208 exon 0 +
+chr13 9136024 9136172 exon 0 +
+chr13 9136400 9136547 exon 0 +
+chr13 9142999 9143078 exon 0 +
+chr13 9144627 9144764 exon 0 +
+chr13 9146646 9146750 exon 0 +
+chr13 9149071 9149182 exon 0 +
+chr13 9150110 9150164 exon 0 +
+chr13 9150260 9150470 exon 0 +
+chr13 9157368 9157475 exon 0 +
+chr13 9157796 9158048 exon 0 +
+chr13 9163920 9163966 exon 0 +
+chr13 9165562 9165727 exon 0 +
+chr13 9167923 9168048 exon 0 +
+chr13 9168143 9168252 exon 0 +
+chr13 9169897 9172336 exon 0 +
+chr13 9275771 9668171 transcript 0 +
+chr13 9275771 9276312 exon 0 +
+chr13 9492350 9492422 exon 0 +
+chr13 9505824 9505935 exon 0 +
+chr13 9532497 9532623 exon 0 +
+chr13 9535901 9536111 exon 0 +
+chr13 9549429 9549564 exon 0 +
+chr13 9550991 9551111 exon 0 +
+chr13 9552519 9552717 exon 0 +
+chr13 9559916 9560008 exon 0 +
+chr13 9562359 9562470 exon 0 +
+chr13 9567076 9567200 exon 0 +
+chr13 9567546 9567656 exon 0 +
+chr13 9567836 9567939 exon 0 +
+chr13 9570295 9570360 exon 0 +
+chr13 9574379 9574473 exon 0 +
+chr13 9574607 9574727 exon 0 +
+chr13 9576049 9576164 exon 0 +
+chr13 9583250 9583267 exon 0 +
+chr13 9591989 9592004 exon 0 +
+chr13 9600631 9600742 exon 0 +
+chr13 9603734 9603871 exon 0 +
+chr13 9605568 9605777 exon 0 +
+chr13 9608189 9608304 exon 0 +
+chr13 9609942 9610144 exon 0 +
+chr13 9613592 9613702 exon 0 +
+chr13 9614990 9615071 exon 0 +
+chr13 9621124 9621248 exon 0 +
+chr13 9621827 9621949 exon 0 +
+chr13 9623002 9623114 exon 0 +
+chr13 9623203 9623313 exon 0 +
+chr13 9627218 9627349 exon 0 +
+chr13 9633967 9634136 exon 0 +
+chr13 9636340 9636511 exon 0 +
+chr13 9646224 9646286 exon 0 +
+chr13 9653848 9653906 exon 0 +
+chr13 9655882 9655957 exon 0 +
+chr13 9658490 9658665 exon 0 +
+chr13 9661382 9661506 exon 0 +
+chr13 9665023 9668171 exon 0 +
+chr13 9684081 9764454 transcript 0 -
+chr13 9684081 9686230 exon 0 -
+chr13 9688308 9688494 exon 0 -
+chr13 9688697 9688970 exon 0 -
+chr13 9689691 9689760 exon 0 -
+chr13 9690148 9690356 exon 0 -
+chr13 9692675 9692794 exon 0 -
+chr13 9693514 9693592 exon 0 -
+chr13 9694374 9694430 exon 0 -
+chr13 9694984 9695072 exon 0 -
+chr13 9696889 9696982 exon 0 -
+chr13 9697873 9697951 exon 0 -
+chr13 9720018 9720178 exon 0 -
+chr13 9734577 9734712 exon 0 -
+chr13 9764300 9764454 exon 0 -
+chr13 9875858 10360049 transcript 0 -
+chr13 9875858 9878263 exon 0 -
+chr13 10027698 10027745 exon 0 -
+chr13 10121472 10121535 exon 0 -
+chr13 10223890 10224003 exon 0 -
+chr13 10359509 10360049 exon 0 -
+chr13 11645369 12199212 transcript 0 -
+chr13 11645369 11646878 exon 0 -
+chr13 11647697 11647749 exon 0 -
+chr13 11648864 11648965 exon 0 -
+chr13 11650313 11650378 exon 0 -
+chr13 11652790 11652947 exon 0 -
+chr13 11659114 11659249 exon 0 -
+chr13 11660738 11660885 exon 0 -
+chr13 11664512 11664573 exon 0 -
+chr13 11670046 11670180 exon 0 -
+chr13 11676402 11676445 exon 0 -
+chr13 11677674 11677805 exon 0 -
+chr13 11679482 11679704 exon 0 -
+chr13 11680408 11680495 exon 0 -
+chr13 11683491 11683639 exon 0 -
+chr13 11685359 11685424 exon 0 -
+chr13 11686948 11688246 exon 0 -
+chr13 11689337 11689419 exon 0 -
+chr13 11690772 11690877 exon 0 -
+chr13 11694025 11694154 exon 0 -
+chr13 11695951 11696040 exon 0 -
+chr13 11697422 11697503 exon 0 -
+chr13 11710873 11710947 exon 0 -
+chr13 11711736 11711813 exon 0 -
+chr13 11713581 11713661 exon 0 -
+chr13 11715168 11715268 exon 0 -
+chr13 11727791 11727845 exon 0 -
+chr13 11730698 11730750 exon 0 -
+chr13 11732540 11732644 exon 0 -
+chr13 11736070 11736167 exon 0 -
+chr13 11738759 11738872 exon 0 -
+chr13 11739968 11740004 exon 0 -
+chr13 11741996 11742131 exon 0 -
+chr13 11746632 11746692 exon 0 -
+chr13 11747884 11748055 exon 0 -
+chr13 11749299 11749392 exon 0 -
+chr13 11751216 11751304 exon 0 -
+chr13 11752367 11752608 exon 0 -
+chr13 11754332 11754653 exon 0 -
+chr13 11758147 11758278 exon 0 -
+chr13 11759345 11759427 exon 0 -
+chr13 11761079 11761318 exon 0 -
+chr13 11762195 11762256 exon 0 -
+chr13 11768464 11768514 exon 0 -
+chr13 11773232 11773354 exon 0 -
+chr13 11776728 11776793 exon 0 -
+chr13 11779209 11779325 exon 0 -
+chr13 11780156 11780280 exon 0 -
+chr13 11782558 11782634 exon 0 -
+chr13 11784905 11784983 exon 0 -
+chr13 11792514 11792652 exon 0 -
+chr13 11793045 11793135 exon 0 -
+chr13 11795763 11795842 exon 0 -
+chr13 11797901 11798065 exon 0 -
+chr13 11798848 11798989 exon 0 -
+chr13 11800048 11800139 exon 0 -
+chr13 11800288 11800509 exon 0 -
+chr13 11802183 11802353 exon 0 -
+chr13 11804401 11804522 exon 0 -
+chr13 11806783 11806889 exon 0 -
+chr13 11809207 11809394 exon 0 -
+chr13 11810636 11810772 exon 0 -
+chr13 11814017 11814121 exon 0 -
+chr13 11814997 11815130 exon 0 -
+chr13 11816529 11816644 exon 0 -
+chr13 11819123 11819397 exon 0 -
+chr13 11822609 11822753 exon 0 -
+chr13 11823912 11824018 exon 0 -
+chr13 11827737 11827938 exon 0 -
+chr13 11829988 11830787 exon 0 -
+chr13 11834120 11834347 exon 0 -
+chr13 11837444 11837531 exon 0 -
+chr13 11837863 11838026 exon 0 -
+chr13 11839754 11839915 exon 0 -
+chr13 11841702 11841817 exon 0 -
+chr13 11842955 11843308 exon 0 -
+chr13 11844462 11844671 exon 0 -
+chr13 11851937 11852112 exon 0 -
+chr13 11853493 11853702 exon 0 -
+chr13 11862118 11862266 exon 0 -
+chr13 11864686 11864846 exon 0 -
+chr13 11871451 11871535 exon 0 -
+chr13 11877304 11877408 exon 0 -
+chr13 11882525 11882630 exon 0 -
+chr13 11884935 11885152 exon 0 -
+chr13 11886808 11887001 exon 0 -
+chr13 11891948 11892190 exon 0 -
+chr13 11893404 11893538 exon 0 -
+chr13 11894073 11894192 exon 0 -
+chr13 11903149 11903245 exon 0 -
+chr13 11916540 11916676 exon 0 -
+chr13 11919800 11919984 exon 0 -
+chr13 11921823 11921945 exon 0 -
+chr13 11926190 11926355 exon 0 -
+chr13 11943324 11943481 exon 0 -
+chr13 11945382 11945457 exon 0 -
+chr13 11960383 11960480 exon 0 -
+chr13 11961383 11961483 exon 0 -
+chr13 11971741 11971854 exon 0 -
+chr13 11975311 11975390 exon 0 -
+chr13 11977730 11977805 exon 0 -
+chr13 11983100 11983115 exon 0 -
+chr13 11995484 11995505 exon 0 -
+chr13 12010577 12010682 exon 0 -
+chr13 12038149 12038269 exon 0 -
+chr13 12198665 12199212 exon 0 -
+chr13 12279085 12350267 transcript 0 -
+chr13 12279085 12279358 exon 0 -
+chr13 12280307 12280420 exon 0 -
+chr13 12281613 12281806 exon 0 -
+chr13 12282470 12282671 exon 0 -
+chr13 12285880 12286077 exon 0 -
+chr13 12287504 12287660 exon 0 -
+chr13 12290179 12290255 exon 0 -
+chr13 12291312 12291411 exon 0 -
+chr13 12292463 12292545 exon 0 -
+chr13 12297009 12297130 exon 0 -
+chr13 12297556 12297624 exon 0 -
+chr13 12304654 12304755 exon 0 -
+chr13 12304937 12305045 exon 0 -
+chr13 12307679 12307832 exon 0 -
+chr13 12309076 12309166 exon 0 -
+chr13 12313688 12313829 exon 0 -
+chr13 12314421 12314538 exon 0 -
+chr13 12317728 12317908 exon 0 -
+chr13 12319950 12320136 exon 0 -
+chr13 12323211 12323352 exon 0 -
+chr13 12325168 12325281 exon 0 -
+chr13 12327723 12327803 exon 0 -
+chr13 12330247 12330315 exon 0 -
+chr13 12331915 12331977 exon 0 -
+chr13 12333831 12333932 exon 0 -
+chr13 12336201 12336296 exon 0 -
+chr13 12336821 12336884 exon 0 -
+chr13 12339585 12339692 exon 0 -
+chr13 12340159 12340252 exon 0 -
+chr13 12342111 12342181 exon 0 -
+chr13 12342868 12342958 exon 0 -
+chr13 12346030 12346245 exon 0 -
+chr13 12350128 12350267 exon 0 -
+chr13 12361693 12432999 transcript 0 -
+chr13 12361693 12361919 exon 0 -
+chr13 12363044 12363203 exon 0 -
+chr13 12364965 12365031 exon 0 -
+chr13 12367344 12367491 exon 0 -
+chr13 12368629 12368809 exon 0 -
+chr13 12369665 12369800 exon 0 -
+chr13 12371060 12371243 exon 0 -
+chr13 12372695 12372836 exon 0 -
+chr13 12374781 12374890 exon 0 -
+chr13 12380773 12380924 exon 0 -
+chr13 12382940 12383088 exon 0 -
+chr13 12383926 12384157 exon 0 -
+chr13 12386576 12386669 exon 0 -
+chr13 12388741 12388827 exon 0 -
+chr13 12393201 12393283 exon 0 -
+chr13 12396580 12396659 exon 0 -
+chr13 12398540 12398628 exon 0 -
+chr13 12401166 12401253 exon 0 -
+chr13 12401861 12401981 exon 0 -
+chr13 12403113 12403228 exon 0 -
+chr13 12432641 12432999 exon 0 -
+chr13 12487641 12531160 transcript 0 +
+chr13 12487641 12487737 exon 0 +
+chr13 12488006 12488180 exon 0 +
+chr13 12488688 12488905 exon 0 +
+chr13 12491169 12491311 exon 0 +
+chr13 12493316 12493418 exon 0 +
+chr13 12493716 12493857 exon 0 +
+chr13 12494980 12495192 exon 0 +
+chr13 12495411 12495545 exon 0 +
+chr13 12497154 12497257 exon 0 +
+chr13 12498289 12498400 exon 0 +
+chr13 12498791 12498909 exon 0 +
+chr13 12499783 12499891 exon 0 +
+chr13 12500920 12501016 exon 0 +
+chr13 12501475 12501564 exon 0 +
+chr13 12502662 12502874 exon 0 +
+chr13 12503486 12503608 exon 0 +
+chr13 12504311 12504503 exon 0 +
+chr13 12505491 12505675 exon 0 +
+chr13 12505778 12505915 exon 0 +
+chr13 12506639 12506832 exon 0 +
+chr13 12507683 12507853 exon 0 +
+chr13 12508217 12508376 exon 0 +
+chr13 12509726 12509969 exon 0 +
+chr13 12510364 12510496 exon 0 +
+chr13 12513308 12513412 exon 0 +
+chr13 12513544 12513688 exon 0 +
+chr13 12514307 12514426 exon 0 +
+chr13 12514507 12514629 exon 0 +
+chr13 12515900 12516029 exon 0 +
+chr13 12516886 12517117 exon 0 +
+chr13 12518420 12518547 exon 0 +
+chr13 12519028 12519189 exon 0 +
+chr13 12522033 12522080 exon 0 +
+chr13 12522407 12522531 exon 0 +
+chr13 12523177 12523318 exon 0 +
+chr13 12523946 12524239 exon 0 +
+chr13 12524895 12525046 exon 0 +
+chr13 12525878 12526037 exon 0 +
+chr13 12526183 12526318 exon 0 +
+chr13 12526453 12526558 exon 0 +
+chr13 12526640 12526809 exon 0 +
+chr13 12527313 12527468 exon 0 +
+chr13 12527936 12528095 exon 0 +
+chr13 12530100 12530209 exon 0 +
+chr13 12530855 12531160 exon 0 +
+chr13 12531685 12553757 transcript 0 -
+chr13 12531685 12533261 exon 0 -
+chr13 12539413 12539579 exon 0 -
+chr13 12540685 12540774 exon 0 -
+chr13 12543667 12543694 exon 0 -
+chr13 12544988 12545045 exon 0 -
+chr13 12545551 12545671 exon 0 -
+chr13 12547001 12547212 exon 0 -
+chr13 12548610 12548699 exon 0 -
+chr13 12551440 12551583 exon 0 -
+chr13 12553693 12553757 exon 0 -
+chr13 12569174 12612715 transcript 0 -
+chr13 12569174 12570841 exon 0 -
+chr13 12575866 12575912 exon 0 -
--- a/tools/new_operations/subtract.xml
+++ b/tools/new_operations/subtract.xml
@@ -1,46 +1,33 @@
<tool id="gops_subtract_1" name="Subtract"><description>the intervals of two queries</description><command interpreter="python">gops_subtract.py
- #if $inputs.type == "Interval":
- $inputs.interval_input1 $inputs.interval_input2 $output
- -1 ${inputs.interval_input1.metadata.chromCol},${inputs.interval_input1.metadata.startCol},${inputs.interval_input1.metadata.endCol},${inputs.interval_input1.metadata.strandCol}
- -2 ${inputs.interval_input2.metadata.chromCol},${inputs.interval_input2.metadata.startCol},${inputs.interval_input2.metadata.endCol},${inputs.interval_input2.metadata.strandCol}
- #else
- $inputs.gff_input1 $inputs.gff_input2 $output
- ## TODO: can we use metadata like above to set these columns rather than hardcode them?
- -1 1,4,5,7
- -2 1,4,5,7
- --gff
+ $input1 $input2 $output
+
+ ##if $input1.ext in ['gff','gtf','gff3']:
+ #if isinstance( $input1.datatype, $__app__.datatypes_registry.get_datatype_by_extension('gff').__class__):
+ -1 1,4,5,7 --gff1
+ #else:
+ -1 ${input1.metadata.chromCol},${input1.metadata.startCol},${input1.metadata.endCol},${input1.metadata.strandCol}
#end if
+
+ #if isinstance( $input2.datatype, $__app__.datatypes_registry.get_datatype_by_extension('gff').__class__):
+ -2 1,4,5,7 --gff2
+ #else:
+ -2 ${input2.metadata.chromCol},${input2.metadata.startCol},${input2.metadata.endCol},${input2.metadata.strandCol}
+ #end if
+
-m $min $returntype
</command><inputs>
- <conditional name="inputs">
- <param name="type" type="select" label="File Format to Use">
- <option value="Interval">Interval</option>
- <option value="GFF">GFF</option>
- </param>
- <when value="Interval">
- <param format="interval" name="interval_input2" type="data" help="Second query">
- <label>Subtract</label>
- </param>
-
- <param format="interval" name="interval_input1" type="data" help="First query">
- <label>from</label>
- </param>
- </when>
- <when value="GFF">
- <param format="gff" name="gff_input2" type="data" help="Second query">
- <label>Subtract</label>
- </param>
-
- <param format="gff" name="gff_input1" type="data" help="First query">
- <label>from</label>
- </param>
- </when>
- </conditional>
+ <param format="interval,gff" name="input2" type="data" help="Second query">
+ <label>Subtract</label>
+ </param>
-<param name="returntype" type="select" label="Return" help="of the first query (see figure below)">
+ <param format="interval,gff" name="input1" type="data" help="First query">
+ <label>from</label>
+ </param>
+
+ <param name="returntype" type="select" label="Return" help="of the first query (see figure below)"><option value="">Intervals with no overlap</option><option value="-p">Non-overlapping pieces of intervals</option></param>
@@ -48,59 +35,57 @@
<param name="min" size="4" type="integer" value="1" help="(bp)"><label>where minimal overlap is</label></param>
-
</inputs><outputs>
- <data format="input" name="output">
- #if inputs.type == "Interval":
- metadata_source="inputs.interval_input1"
- #else:
- metadata_source="inputs.gff_input1"
- #end if
- </data>
+ <data format="input" name="output" metadata_source="input1"/></outputs><code file="operation_filter.py"/><tests><test>
- <param name="type" value="Interval"/>
- <param name="interval_input1" value="1.bed" />
- <param name="interval_input2" value="2.bed" />
+ <param name="input1" value="1.bed" />
+ <param name="input2" value="2.bed" /><param name="min" value="1" /><param name="returntype" value="" /><output name="output" file="gops-subtract.dat" /></test><test>
- <param name="type" value="Interval"/>
- <param name="interval_input1" value="1.bed" />
- <param name="interval_input2" value="2_mod.bed" ftype="interval"/>
+ <param name="input1" value="1.bed" />
+ <param name="input2" value="2_mod.bed" ftype="interval"/><param name="min" value="1" /><param name="returntype" value="" /><output name="output" file="gops_subtract_diffCols.dat" /></test><test>
- <param name="type" value="Interval"/>
- <param name="interval_input1" value="gops_subtract_bigint.bed" />
- <param name="interval_input2" value="2.bed" />
+ <param name="input1" value="gops_subtract_bigint.bed" />
+ <param name="input2" value="2.bed" /><param name="min" value="1" /><param name="returntype" value="" /><output name="output" file="gops-subtract.dat" /></test><test>
- <param name="type" value="Interval"/>
- <param name="interval_input1" value="1.bed" />
- <param name="interval_input2" value="2.bed" />
+ <param name="input1" value="1.bed" />
+ <param name="input2" value="2.bed" /><param name="min" value="10" /><param name="returntype" value="Non-overlapping pieces of intervals" /><output name="output" file="gops-subtract-p.dat" /></test>
+ <!-- Subtract two GFF files. --><test>
- <param name="type" value="GFF"/>
- <param name="gff_input1" value="gops_subtract_in1.gff" />
- <param name="gff_input2" value="gops_subtract_in2.gff" />
+ <param name="input1" value="gops_subtract_in1.gff" />
+ <param name="input2" value="gops_subtract_in2.gff" /><param name="min" value="1" /><param name="returntype" value="" />
- <output name="output" file="gops_subtract_out1.gff" />
+ <output name="output" file="gops_subtract_out1.gff" /></test>
+ <!-- Subtract BED file from GFF file. -->
+ <test>
+ <param name="input1" value="gops_subtract_in1.gff" />
+ <param name="input2" value="gops_subtract_in2.bed" />
+ <param name="min" value="1" />
+ <param name="returntype" value="" />
+ <output name="output" file="gops_subtract_out1.gff" />
+ </test>
+
</tests><help>
1
0

galaxy-dist commit 45e1d5636fff: Fixed typo in jobs.actions.post
by commits-noreply@bitbucket.org 29 Jun '10
by commits-noreply@bitbucket.org 29 Jun '10
29 Jun '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User Dannon Baker <dannon.baker(a)emory.edu>
# Date 1277253455 14400
# Node ID 45e1d5636fffd284ea6cda2f14318b364370e985
# Parent a579674852103600ecd8e8b2549e76abc3917791
Fixed typo in jobs.actions.post
--- a/lib/galaxy/jobs/actions/post.py
+++ b/lib/galaxy/jobs/actions/post.py
@@ -82,7 +82,7 @@ class EmailAction(DefaultJobAction):
s.close()
except Exception, e:
log.error("EmailAction PJA Failed, exception: %s" % e)
-L
+
@classmethod
def get_config_form(cls, trans):
form = """
1
0

galaxy-dist commit 752cb3e32537: Improvements to GOPS intersect: (a) preserve metadata for interval inputs; (b) allow arbitrary mix of interval and GFF inputs; and (c) functional tests updated to test new functionality.
by commits-noreply@bitbucket.org 29 Jun '10
by commits-noreply@bitbucket.org 29 Jun '10
29 Jun '10
# HG changeset patch -- Bitbucket.org
# Project galaxy-dist
# URL http://bitbucket.org/galaxy/galaxy-dist/overview
# User jeremy goecks <jeremy.goecks(a)emory.edu>
# Date 1277306218 14400
# Node ID 752cb3e325374381bdd0db6b9163b12ba88b7151
# Parent 03eb69c6e92ea5472b7627bd9990327f7546a8a7
Improvements to GOPS intersect: (a) preserve metadata for interval inputs; (b) allow arbitrary mix of interval and GFF inputs; and (c) functional tests updated to test new functionality.
--- a/tools/new_operations/intersect.xml
+++ b/tools/new_operations/intersect.xml
@@ -1,131 +1,110 @@
<tool id="gops_intersect_1" name="Intersect"><description>the intervals of two queries</description><command interpreter="python">gops_intersect.py
- #if $inputs.type == "Interval":
- $inputs.interval_input1 $inputs.interval_input2 $output
- -1 ${inputs.interval_input1.metadata.chromCol},${inputs.interval_input1.metadata.startCol},${inputs.interval_input1.metadata.endCol},${inputs.interval_input1.metadata.strandCol}
- -2 ${inputs.interval_input2.metadata.chromCol},${inputs.interval_input2.metadata.startCol},${inputs.interval_input2.metadata.endCol},${inputs.interval_input2.metadata.strandCol}
- $inputs.interval_returntype
- #else
- $inputs.gff_input1 $inputs.gff_input2 $output
- ## TODO: can we use metadata like above to set these columns rather than hardcode them?
- -1 1,4,5,7
- -2 1,4,5,7
- --gff
- $inputs.gff_returntype
+ $input1 $input2 $output
+
+ #if isinstance( $input1.datatype, $__app__.datatypes_registry.get_datatype_by_extension('gff').__class__):
+ -1 1,4,5,7 --gff1
+ #else:
+ -1 ${input1.metadata.chromCol},${input1.metadata.startCol},${input1.metadata.endCol},${input1.metadata.strandCol}
#end if
- -m $min
+
+ #if isinstance( $input2.datatype, $__app__.datatypes_registry.get_datatype_by_extension('gff').__class__):
+ -2 1,4,5,7 --gff2
+ #else:
+ -2 ${input2.metadata.chromCol},${input2.metadata.startCol},${input2.metadata.endCol},${input2.metadata.strandCol}
+ #end if
+
+ -m $min $returntype
</command><inputs>
- <conditional name="inputs">
- <param name="type" type="select" label="File Format to Use">
- <option value="Interval">Interval</option>
- <option value="GFF">GFF</option>
- </param>
- <when value="Interval">
- <param name="interval_returntype" type="select" label="Return" help="(see figure below)">
- <option value="">Overlapping Intervals</option>
- <option value="-p">Overlapping pieces of Intervals</option>
- </param>
- <param format="interval" name="interval_input1" type="data" help="First query">
- <label>of</label>
- </param>
- <param format="interval" name="interval_input2" type="data" help="Second query">
- <label>that intersect</label>
- </param>
- </when>
- <when value="GFF">
- <param name="gff_returntype" type="select" label="Return" help="(see figure below)">
- <option value="">Overlapping Intervals</option>
- <option value="-p">Overlapping pieces of Intervals</option>
- </param>
- <param format="gff" name="gff_input1" type="data" help="First query">
- <label>of</label>
- </param>
- <param format="gff" name="gff_input2" type="data" help="Second query">
- <label>that intersect</label>
- </param>
- </when>
- </conditional>
+ <param name="returntype" type="select" label="Return" help="(see figure below)">
+ <option value="">Overlapping Intervals</option>
+ <option value="-p">Overlapping pieces of Intervals</option>
+ </param>
+ <param format="interval,gff" name="input1" type="data" help="First query">
+ <label>of</label>
+ </param>
+ <param format="interval,gff" name="input2" type="data" help="Second query">
+ <label>that intersect</label>
+ </param><param name="min" size="4" type="integer" value="1" help="(bp)"><label>for at least</label></param></inputs><outputs>
- <data format="input" name="output">
- #if inputs.type == "Interval":
- metadata_source="inputs.interval_input1"
- #else:
- metadata_source="inputs.gff_input1"
- #end if
- </data>
+ <data format="input" name="output" metadata_source="input1"/></outputs><code file="operation_filter.py"/><tests><test><param name="type" value="Interval"/>
- <param name="interval_input1" value="1.bed" />
- <param name="interval_input2" value="2.bed" />
+ <param name="input1" value="1.bed" />
+ <param name="input2" value="2.bed" /><param name="min" value="1" />
- <param name="interval_returntype" value="" />
+ <param name="returntype" value="" /><output name="output" file="gops_intersect_out.bed" /></test><test><param name="type" value="Interval"/>
- <param name="interval_input1" value="1.bed" />
- <param name="interval_input2" value="2_mod.bed" ftype="interval"/>
+ <param name="input1" value="1.bed" />
+ <param name="input2" value="2_mod.bed" ftype="interval"/><param name="min" value="1" />
- <param name="interval_returntype" value="" />
+ <param name="returntype" value="" /><output name="output" file="gops_intersect_diffCols.bed" /></test><test>
- <param name="type" value="Interval"/>
- <param name="interval_input1" value="1.bed" />
- <param name="interval_input2" value="2_mod.bed" ftype="interval"/>
+ <param name="input1" value="1.bed" />
+ <param name="input2" value="2_mod.bed" ftype="interval"/><param name="min" value="1" />
- <param name="interval_returntype" value="Overlapping pieces of Intervals" />
+ <param name="returntype" value="Overlapping pieces of Intervals" /><output name="output" file="gops_intersect_p_diffCols.bed" /></test><test>
- <param name="type" value="Interval"/>
- <param name="interval_input1" value="1.bed" />
- <param name="interval_input2" value="2.bed" />
+ <param name="input1" value="1.bed" />
+ <param name="input2" value="2.bed" /><param name="min" value="10" />
- <param name="interval_returntype" value="Overlapping pieces of Intervals" />
+ <param name="returntype" value="Overlapping pieces of Intervals" /><output name="output" file="gops_intersect_p_out.bed" /></test><test>
- <param name="type" value="Interval"/>
- <param name="interval_input1" value="gops_bigint.interval" ftype="interval" />
- <param name="interval_input2" value="gops_bigint2.interval" ftype="interval" />
+ <param name="input1" value="gops_bigint.interval" ftype="interval" />
+ <param name="input2" value="gops_bigint2.interval" ftype="interval" /><param name="min" value="1" />
- <param name="interval_returntype" value="" />
+ <param name="returntype" value="" /><output name="output" file="gops_intersect_bigint_out.interval" /></test><test>
- <param name="type" value="Interval"/>
- <param name="interval_input1" value="gops_bigint2.interval" ftype="interval" />
- <param name="interval_input2" value="gops_bigint.interval" ftype="interval" />
+ <param name="input1" value="gops_bigint2.interval" ftype="interval" />
+ <param name="input2" value="gops_bigint.interval" ftype="interval" /><param name="min" value="1" />
- <param name="interval_returntype" value="" />
+ <param name="returntype" value="" /><output name="output" file="gops_intersect_bigint_out.interval" /></test><test>
- <param name="type" value="Interval"/>
- <param name="interval_input1" value="12.bed" ftype="bed" />
- <param name="interval_input2" value="1.bed" ftype="bed" />
+ <param name="input1" value="12.bed" ftype="bed" />
+ <param name="input2" value="1.bed" ftype="bed" /><param name="min" value="1" />
- <param name="interval_returntype" value="" />
+ <param name="returntype" value="" /><output name="output" file="gops_intersect_no_strand_out.bed" /></test>
+ <!-- Intersect two GFF files. --><test>
- <param name="type" value="GFF"/>
- <param name="gff_input1" value="gops_subtract_in1.gff" />
- <param name="gff_input2" value="gops_subtract_in2.gff" />
+ <param name="input1" value="gops_subtract_in1.gff" />
+ <param name="input2" value="gops_subtract_in2.gff" /><param name="min" value="1" />
- <param name="gff_returntype" value="" />
+ <param name="returntype" value="" /><output name="output" file="gops_intersect_out2.gff" /></test>
+ <!-- Intersect GFF file and bed file. -->
+ <test>
+ <param name="input1" value="gops_subtract_in1.gff" />
+ <param name="input2" value="gops_subtract_in2.bed" />
+ <param name="min" value="1" />
+ <param name="returntype" value="" />
+ <output name="output" file="gops_intersect_out2.gff" />
+ </test>
+
</tests><help>
--- a/tools/new_operations/subtract.xml
+++ b/tools/new_operations/subtract.xml
@@ -3,7 +3,6 @@
<command interpreter="python">gops_subtract.py
$input1 $input2 $output
- ##if $input1.ext in ['gff','gtf','gff3']:
#if isinstance( $input1.datatype, $__app__.datatypes_registry.get_datatype_by_extension('gff').__class__):
-1 1,4,5,7 --gff1
#else:
--- a/tools/new_operations/gops_intersect.py
+++ b/tools/new_operations/gops_intersect.py
@@ -1,13 +1,14 @@
#!/usr/bin/env python
"""
-Find regions of first bed file that overlap regions in a second bed file
+Find regions of first interval/GFF file that overlap regions in a second interval/GFF file
usage: %prog bed_file_1 bed_file_2 out_file
-1, --cols1=N,N,N,N: Columns for start, end, strand in first file
-2, --cols2=N,N,N,N: Columns for start, end, strand in second file
-m, --mincols=N: Require this much overlap (default 1bp)
-p, --pieces: just print pieces of second set (after padding)
- -G, --gff: inputs are GFF format, meaning start and end coordinates are 1-based, closed interval
+ -G, --gff1: input 1 is GFF format, meaning start and end coordinates are 1-based, closed interval
+ -H, --gff2: input 2 is GFF format, meaning start and end coordinates are 1-based, closed interval
"""
from galaxy import eggs
import pkg_resources
@@ -34,24 +35,29 @@ def main():
chr_col_2, start_col_2, end_col_2, strand_col_2 = parse_cols_arg( options.cols2 )
if options.mincols: mincols = int( options.mincols )
pieces = bool( options.pieces )
- gff_format = bool( options.gff )
+ in1_gff_format = bool( options.gff1 )
+ in2_gff_format = bool( options.gff2 )
in_fname, in2_fname, out_fname = args
except:
doc_optparse.exception()
- # Set reader to handle either GFF or default format.
- if gff_format:
- reader_wrapper = GFFReaderWrapper
+ # Set readers to handle either GFF or default format.
+ if in1_gff_format:
+ in1_reader_wrapper = GFFReaderWrapper
else:
- reader_wrapper = NiceReaderWrapper
+ in1_reader_wrapper = NiceReaderWrapper
+ if in2_gff_format:
+ in2_reader_wrapper = GFFReaderWrapper
+ else:
+ in2_reader_wrapper = NiceReaderWrapper
- g1 = reader_wrapper( fileinput.FileInput( in_fname ),
+ g1 = in1_reader_wrapper( fileinput.FileInput( in_fname ),
chrom_col=chr_col_1,
start_col=start_col_1,
end_col=end_col_1,
strand_col=strand_col_1,
fix_strand=True )
- g2 = reader_wrapper( fileinput.FileInput( in2_fname ),
+ g2 = in2_reader_wrapper( fileinput.FileInput( in2_fname ),
chrom_col=chr_col_2,
start_col=start_col_2,
end_col=end_col_2,
@@ -63,7 +69,7 @@ def main():
try:
for line in intersect( [g1,g2], pieces=pieces, mincols=mincols ):
if type( line ) == GenomicInterval:
- if gff_format:
+ if in1_gff_format:
line = convert_to_gff_coordinates( line )
out_file.write( "%s\n" % "\t".join( line.fields ) )
else:
1
0