galaxy-commits
Threads by month
- ----- 2026 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- 15302 discussions
commit/galaxy-central: carlfeberhard: (Add mako template missing from b8ff65e)
by commits-noreply@bitbucket.org 14 Feb '14
by commits-noreply@bitbucket.org 14 Feb '14
14 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/8494c2b3a3ed/
Changeset: 8494c2b3a3ed
User: carlfeberhard
Date: 2014-02-14 19:59:44
Summary: (Add mako template missing from b8ff65e)
Affected #: 1 file
diff -r b8ff65e15b1b40a76544ef07e7ab98a9b5ce7221 -r 8494c2b3a3edff7f900a9994ccd806d063d5010f templates/webapps/galaxy/visualization/display_in_frame.mako
--- /dev/null
+++ b/templates/webapps/galaxy/visualization/display_in_frame.mako
@@ -0,0 +1,58 @@
+<%inherit file="/display_base.mako"/>
+
+<%def name="javascripts()">
+ <% config = item_data %>
+ ${parent.javascripts()}
+
+ <script type='text/javascript'>
+ $(function() {
+ // HACK: add bookmarks container and header.
+ $('#right > .unified-panel-body > div').append(
+ $('<div/>').attr('id', 'bookmarks-container')
+ .append( $('<h4/>').text('Bookmarks') )
+ );
+ });
+ </script>
+
+ <!--[if lt IE 9]>
+ <script type='text/javascript' src="${h.url_for('/static/scripts/libs/IE/excanvas.js')}"></script>
+ <![endif]-->
+</%def>
+
+<%def name="stylesheets()">
+ ${parent.stylesheets()}
+
+ ## Style changes needed for display.
+ <style type="text/css">
+ .page-body {
+ padding: 0px;
+ }
+ #bookmarks-container {
+ padding-left: 10px;
+ }
+ .bookmark {
+ margin: 0em;
+ }
+ </style>
+</%def>
+
+<%def name="render_item_header( item )">
+ ## Don't need to show header
+</%def>
+
+<%def name="render_item_links( visualization )">
+ <a
+ href="${h.url_for( controller='/visualization', action='imp', id=trans.security.encode_id( visualization.id ) )}"
+ class="icon-button import"
+ ## Needed to overwide initial width so that link is floated left appropriately.
+ style="width: 100%"
+ title="Import visualization">Import visualization</a>
+</%def>
+
+<%def name="render_item( visualization, config )">
+ <div id="${trans.security.encode_id( visualization.id )}" class="unified-panel-body" style="overflow:none;top:0px;">
+ <iframe frameborder="0" width="100%" height="100%" sandbox="allow-forms allow-same-origin allow-scripts"
+ src="/visualization/saved?id=${encoded_visualization_id}&embedded=True">
+ </iframe>
+ </div>
+</%def>
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: Visualizations registry: allow registry visualizations to be rendered properly when displayed by slugline; Scatterplot: use embedded to cue loading of display (w/o editor)
by commits-noreply@bitbucket.org 14 Feb '14
by commits-noreply@bitbucket.org 14 Feb '14
14 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/b8ff65e15b1b/
Changeset: b8ff65e15b1b
User: carlfeberhard
Date: 2014-02-14 19:58:30
Summary: Visualizations registry: allow registry visualizations to be rendered properly when displayed by slugline; Scatterplot: use embedded to cue loading of display (w/o editor)
Affected #: 4 files
diff -r 3c7e8a024e9436eb2d7e970b7721f691728c4dae -r b8ff65e15b1b40a76544ef07e7ab98a9b5ce7221 config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
--- a/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
+++ b/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
@@ -1,1 +1,1 @@
-function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){$(".chart-info-box").remove(),q.redraw(),e(),s=d(),$(o.node()).trigger("zoom.scatterplot",{scale:n.scale(),translate:n.translate()})}function g(a,c,d){return c+=8,$(['<div class="chart-info-box" style="position: absolute">',void 0!==b.idColumn?"<div>"+d[b.idColumn]+"</div>":"","<div>",j(d),"</div>","<div>",k(d),"</div>","</div>"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,30]).scale(b.scale||1).translate(b.translate||[0,0]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.x.ticks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.y.ticks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=6;q.x.label=o.append("text").attr("class","axis-label").text(b.x.label).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("class","axis-label").text(b.y.label).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",0);t.transition().duration(b.animDuration).attr("r",b.datapointSize),e(),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.classed("highlight",!0).style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")<b.height&&p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")).attr("y1",+d.attr("cy")+b.datapointSize).attr("x2",d.attr("cx")).attr("y2",b.height).classed("hoverline",!0);var e=this.getBoundingClientRect();$("body").append(g(e.top,e.right,a)),$(o.node()).trigger("mouseover-datapoint.scatterplot",[this,a,c])}),t.on("mouseout",function(){d3.select(this).classed("highlight",!1).style("fill","black").style("fill-opacity",.2),p.selectAll(".hoverline").remove(),$(".chart-info-box").remove()})}this.scatterplot=this.scatterplot||{},this.scatterplot.chartcontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function",i=this.escapeExpression;return g+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<div data-config-key="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">',(f=c.datapointSize)?f=f.call(b,{hash:{},data:e}):(f=b.datapointSize,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n</div>\n\n<div data-config-key="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">',(f=c.width)?f=f.call(b,{hash:{},data:e}):(f=b.width,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">',(f=c.height)?f=f.call(b,{hash:{},data:e}):(f=b.height,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="'+i((f=b.x,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<div data-config-key="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="'+i((f=b.y,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.datacontrol=Handlebars.template(function(a,b,c,d,e){function f(a,b){var d,e="";return e+='\n <option value="',(d=c.index)?d=d.call(a,{hash:{},data:b}):(d=a.index,d=typeof d===j?d.apply(a):d),e+=k(d)+'">',(d=c.name)?d=d.call(a,{hash:{},data:b}):(d=a.name,d=typeof d===j?d.apply(a):d),e+=k(d)+"</option>\n "}function g(){return'checked="true"'}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var h,i="",j="function",k=this.escapeExpression,l=this;return i+='<p class="help-text">\n Use the following controls to change the data used by the chart.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n\n<div class="column-select">\n <label>Data column for X: </label>\n <select name="xColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n<div class="column-select">\n <label>Data column for Y: </label>\n <select name="yColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="include-id">\n <label for="include-id-checkbox">Include a third column as data point IDs?</label>\n <input type="checkbox" name="include-id" id="include-id-checkbox" />\n <p class="help-text-small">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n</div>\n<div class="column-select" style="display: none">\n <label for="ID-select">Data column for IDs: </label>\n <select name="idColumn">\n ',h=c.each.call(b,b.allColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="first-line-header" style="display: none;">\n <p>Possible headers: ',(h=c.possibleHeaders)?h=h.call(b,{hash:{},data:e}):(h=b.possibleHeaders,h=typeof h===j?h.apply(b):h),i+=k(h)+'\n </p>\n <label for="first-line-header-checkbox">Use the above as column headers?</label>\n <input type="checkbox" name="include-id" id="first-line-header-checkbox"\n ',h=c["if"].call(b,b.usePossibleHeaders,{hash:{},inverse:l.noop,fn:l.program(3,g,e),data:e}),(h||0===h)&&(i+=h),i+='/>\n <p class="help-text-small">\n It looks like Galaxy couldn\'t get proper column headers for this data.\n Would you like to use the column headers above as column names to select columns?\n </p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.editor=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f="";return f+='<div class="scatterplot-editor tabbable tabs-left">\n \n <ul class="nav nav-tabs">\n \n <li class="active">\n <a title="Use this tab to change which data are used"\n href="#data-control" data-toggle="tab">Data Controls</a>\n </li>\n <li>\n <a title="Use this tab to change how the chart is drawn"\n href="#chart-control" data-toggle="tab" >Chart Controls</a>\n </li>\n \n <li class="disabled">\n <a title="This tab will display the chart"\n href="#chart-display" data-toggle="tab">Chart</a>\n </li>\n \n <li class="file-controls">\n<!-- <button class="copy-btn btn btn-default"\n title="Save this as a new visualization">Save to new</button>-->\n <button class="save-btn btn btn-default">Save</button>\n </li>\n </ul>\n\n \n <div class="tab-content">\n \n <div id="data-control" class="scatterplot-config-control tab-pane active">\n \n </div>\n \n \n <div id="chart-control" class="scatterplot-config-control tab-pane">\n \n </div>\n\n \n <div id="chart-display" class="scatterplot-display tab-pane"></div>\n\n </div>\n</div>\n'});var ScatterplotConfigEditor=Backbone.View.extend(LoggableMixin).extend({className:"scatterplot-control-form",initialize:function(a){if(this.model||(this.model=new Visualization({type:"scatterplot"})),this.log(this+".initialize, attributes:",a),!a||!a.dataset)throw new Error("ScatterplotConfigEditor requires a dataset");this.dataset=a.dataset,this.log("dataset:",this.dataset),this.display=new ScatterplotDisplay({dataset:a.dataset,model:this.model})},render:function(){this.$el.empty().append(ScatterplotConfigEditor.templates.mainLayout({})),this.model.id&&(this.$el.find(".copy-btn").show(),this.$el.find(".save-btn").text("Update saved")),this.$el.find("[title]").tooltip(),this._render_dataControl(),this._render_chartControls(),this._render_chartDisplay();var a=this.model.get("config");return this.model.id&&_.isFinite(a.xColumn)&&_.isFinite(a.yColumn)&&this.renderChart(),this},_render_dataControl:function(a){a=a||this.$el;var b=this,c=this.dataset,d=c.metadata_column_names||[],e=this.model.get("config"),f=[],g=_.map(c.metadata_column_types,function(a,b){var c={index:b,type:a,name:d[b]||"column "+(b+1)};return("int"===c.type||"float"===c.type)&&f.push(c),c});f.length<2&&(f=g);var h=a.find(".tab-pane#data-control");h.html(ScatterplotConfigEditor.templates.dataControl({allColumns:g,numericColumns:f}));var i={xColumn:_.isFinite(e.xColumn)?e.xColumn:f[0].index,yColumn:_.isFinite(e.yColumn)?e.yColumn:f[1].index,idColumn:g[0].index};if(_.isFinite(e.idColumn))i.idColumn=e.idColumn;else if(g.length>2){var j=_.find(g,function(a,b){return b!==i.xColumn&&b!==i.yColumn});i.idColumn=j.index}return e=this.model.set("config",i,{silent:!0}).get("config"),h.find('[name="xColumn"]').val(e.xColumn).on("change",function(){b.model.set("config",{xColumn:Number($(this).val())})}),h.find('[name="yColumn"]').val(e.yColumn).on("change",function(){b.model.set("config",{yColumn:Number($(this).val())})}),h.find('select[name="idColumn"]').val(e.idColumn).on("change",function(){b.model.set("config",{idColumn:Number($(this).val())})}),void 0!==e.idColumn&&h.find("#include-id-checkbox").prop("checked",!0).trigger("change"),h.find("[title]").tooltip(),h},_render_chartControls:function(a){function b(){var a=$(this),b=a.slider("value");c.model.set("config",_.object([[a.parent().data("config-key"),b]])),a.siblings(".slider-output").text(b)}a=a||this.$el;var c=this,d=this.model.get("config"),e=a.find("#chart-control");e.html(ScatterplotConfigEditor.templates.chartControl(d));var f={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};e.find(".numeric-slider-input").each(function(){var a=$(this),c=a.attr("data-config-key"),e=_.extend(f[c],{value:d[c],change:b,slide:b});a.find(".slider").slider(e),a.children(".slider-output").text(d[c])});var g=this.dataset.metadata_column_names||[],h=d.xLabel||g[d.xColumn]||"X",i=d.yLabel||g[d.yColumn]||"Y";return e.find('input[name="X-axis-label"]').val(h).on("change",function(){c.model.set("config",{xLabel:$(this).val()})}),e.find('input[name="Y-axis-label"]').val(i).on("change",function(){c.model.set("config",{yLabel:$(this).val()})}),e.find("[title]").tooltip(),e},_render_chartDisplay:function(a){a=a||this.$el;var b=a.find(".tab-pane#chart-display");return this.display.setElement(b),this.display.render(),b.find("[title]").tooltip(),b},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart","click .save-btn":"saveVisualization"},saveVisualization:function(){var a=this;this.model.save().fail(function(b,c,d){console.error(b,c,d),a.trigger("save:error",view),alert("Error loading data:\n"+b.responseText)}).then(function(){a.display.render()})},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.display.fetchData()},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:scatterplot.editor,dataControl:scatterplot.datacontrol,chartControl:scatterplot.chartcontrol};var ScatterplotDisplay=Backbone.View.extend({initialize:function(a){this.data=null,this.dataset=a.dataset,this.calcNumPages()},calcNumPages:function(){var a=this.model.get("config");this.lineCount=this.dataset.metadata_data_lines,this.numPages=this.lineCount?Math.ceil(this.lineCount/a.pagination.perPage):void 0,this.lineCount&&void 0!==this.numPages||console.warn("no data total found")},fetchData:function(){this.showLoadingIndicator("getting data");var a=this,b=this.model.get("config"),c=jQuery.getJSON("/api/datasets/"+this.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:b.pagination.perPage,offset:b.pagination.currPage*b.pagination.perPage});return c.done(function(b){a.data=b.data,a.trigger("data:fetched",a),a.renderData()}),c.fail(function(b,c,d){console.error(b,c,d),a.trigger("data:error",a),alert("Error loading data:\n"+b.responseText)}),c},showLoadingIndicator:function(){this.$el.find(".scatterplot-data-info").html(['<div class="loading-indicator">','<span class="fa fa-spinner fa-spin"></span>','<span class="loading-indicator-message">loading...</span>',"</div>"].join(""))},template:function(){var a=['<div class="controls clear">','<div class="left">','<div class="page-control"></div>',"</div>",'<div class="right">','<p class="scatterplot-data-info"></p>','<button class="stats-toggle-btn">Stats</button>','<button class="rerender-btn">Redraw</button>',"</div>","</div>","<svg/>",'<div class="stats-display"></div>'].join("");return a},render:function(){return this.$el.addClass("scatterplot-display").html(this.template()),this.data&&this.renderData(),this},renderData:function(){this.renderLeftControls(),this.renderRightControls(),this.renderPlot(this.data),this.getStats()},renderLeftControls:function(){var a=this,b=this.model.get("config");return this.$el.find(".controls .left .page-control").pagination({startingPage:b.pagination.currPage,perPage:b.pagination.perPage,totalDataSize:this.lineCount||null,currDataSize:this.data.length,maxWidth:540}).off().on("pagination.page-change",function(c,d){b.pagination.currPage=d,a.model.set("config",{pagination:b.pagination}),a.resetZoom(),a.fetchData()}),this},renderRightControls:function(){var a=this;this.setLineInfo(this.data),this.$el.find(".stats-toggle-btn").off().click(function(){a.toggleStats()}),this.$el.find(".rerender-btn").off().click(function(){a.resetZoom(),a.renderPlot(this.data)})},renderPlot:function(){var a=this,b=this.$el.find("svg");this.toggleStats(!1),b.off().empty().show().on("zoom.scatterplot",function(b,c){a.model.set("config",c)}),scatterplot(b.get(0),this.model.get("config"),this.data)},setLineInfo:function(a,b){if(a){var c=this.model.get("config"),d=this.lineCount||"an unknown total",e=c.pagination.currPage*c.pagination.perPage,f=e+a.length;this.$el.find(".controls p.scatterplot-data-info").text([e+1,"to",f,"of",d].join(" "))}else this.$el.find(".controls p.scatterplot-data-info").html(b||"");return this},resetZoom:function(a,b){return a=void 0!==a?a:1,b=void 0!==b?b:[0,0],this.model.set("config",{scale:a,translate:b}),this},getStats:function(){if(this.data){var a=this,b=this.model.get("config"),c=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js");c.postMessage({data:this.data,keys:[b.xColumn,b.yColumn]}),c.onerror=function(){c.terminate()},c.onmessage=function(b){a.renderStats(b.data)}}},renderStats:function(a){var b=this.model.get("config"),c=this.$el.find(".stats-display"),d=b.x.label,e=b.y.label,f=$("<table/>").addClass("table").append(["<thead><th></th><th>",d,"</th><th>",e,"</th></thead>"].join("")).append(_.map(a,function(a,b){return $(["<tr><td>",b,"</td><td>",a[0],"</td><td>",a[1],"</td></tr>"].join(""))}));c.empty().append(f)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}}),ScatterplotModel=Visualization.extend({defaults:{type:"scatterplot",config:{pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},x:{ticks:10,label:"X"},y:{ticks:10,label:"Y"},datapointSize:4,animDuration:500,scale:1,translate:[0,0]}}});
\ No newline at end of file
+function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){$(".chart-info-box").remove(),q.redraw(),e(),s=d(),$(o.node()).trigger("zoom.scatterplot",{scale:n.scale(),translate:n.translate()})}function g(a,c,d){return c+=8,$(['<div class="chart-info-box" style="position: absolute">',void 0!==b.idColumn?"<div>"+d[b.idColumn]+"</div>":"","<div>",j(d),"</div>","<div>",k(d),"</div>","</div>"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,30]).scale(b.scale||1).translate(b.translate||[0,0]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.x.ticks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.y.ticks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=6;q.x.label=o.append("text").attr("class","axis-label").text(b.x.label).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("class","axis-label").text(b.y.label).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",0);t.transition().duration(b.animDuration).attr("r",b.datapointSize),e(),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.classed("highlight",!0).style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")<b.height&&p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")).attr("y1",+d.attr("cy")+b.datapointSize).attr("x2",d.attr("cx")).attr("y2",b.height).classed("hoverline",!0);var e=this.getBoundingClientRect();$("body").append(g(e.top,e.right,a)),$(o.node()).trigger("mouseover-datapoint.scatterplot",[this,a,c])}),t.on("mouseout",function(){d3.select(this).classed("highlight",!1).style("fill","black").style("fill-opacity",.2),p.selectAll(".hoverline").remove(),$(".chart-info-box").remove()})}this.scatterplot=this.scatterplot||{},this.scatterplot.chartcontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function",i=this.escapeExpression;return g+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<div data-config-key="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">',(f=c.datapointSize)?f=f.call(b,{hash:{},data:e}):(f=b.datapointSize,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n</div>\n\n<div data-config-key="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">',(f=c.width)?f=f.call(b,{hash:{},data:e}):(f=b.width,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">',(f=c.height)?f=f.call(b,{hash:{},data:e}):(f=b.height,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="'+i((f=b.x,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<div data-config-key="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="'+i((f=b.y,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.datacontrol=Handlebars.template(function(a,b,c,d,e){function f(a,b){var d,e="";return e+='\n <option value="',(d=c.index)?d=d.call(a,{hash:{},data:b}):(d=a.index,d=typeof d===j?d.apply(a):d),e+=k(d)+'">',(d=c.name)?d=d.call(a,{hash:{},data:b}):(d=a.name,d=typeof d===j?d.apply(a):d),e+=k(d)+"</option>\n "}function g(){return'checked="true"'}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var h,i="",j="function",k=this.escapeExpression,l=this;return i+='<p class="help-text">\n Use the following controls to change the data used by the chart.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n\n<div class="column-select">\n <label>Data column for X: </label>\n <select name="xColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n<div class="column-select">\n <label>Data column for Y: </label>\n <select name="yColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="include-id">\n <label for="include-id-checkbox">Include a third column as data point IDs?</label>\n <input type="checkbox" name="include-id" id="include-id-checkbox" />\n <p class="help-text-small">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n</div>\n<div class="column-select" style="display: none">\n <label for="ID-select">Data column for IDs: </label>\n <select name="idColumn">\n ',h=c.each.call(b,b.allColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="first-line-header" style="display: none;">\n <p>Possible headers: ',(h=c.possibleHeaders)?h=h.call(b,{hash:{},data:e}):(h=b.possibleHeaders,h=typeof h===j?h.apply(b):h),i+=k(h)+'\n </p>\n <label for="first-line-header-checkbox">Use the above as column headers?</label>\n <input type="checkbox" name="include-id" id="first-line-header-checkbox"\n ',h=c["if"].call(b,b.usePossibleHeaders,{hash:{},inverse:l.noop,fn:l.program(3,g,e),data:e}),(h||0===h)&&(i+=h),i+='/>\n <p class="help-text-small">\n It looks like Galaxy couldn\'t get proper column headers for this data.\n Would you like to use the column headers above as column names to select columns?\n </p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.editor=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f="";return f+='<div class="scatterplot-editor tabbable tabs-left">\n \n <ul class="nav nav-tabs">\n \n <li class="active">\n <a title="Use this tab to change which data are used"\n href="#data-control" data-toggle="tab">Data Controls</a>\n </li>\n <li>\n <a title="Use this tab to change how the chart is drawn"\n href="#chart-control" data-toggle="tab" >Chart Controls</a>\n </li>\n \n <li class="disabled">\n <a title="This tab will display the chart"\n href="#chart-display" data-toggle="tab">Chart</a>\n </li>\n \n <li class="file-controls">\n<!-- <button class="copy-btn btn btn-default"\n title="Save this as a new visualization">Save to new</button>-->\n <button class="save-btn btn btn-default">Save</button>\n </li>\n </ul>\n\n \n <div class="tab-content">\n \n <div id="data-control" class="scatterplot-config-control tab-pane active">\n \n </div>\n \n \n <div id="chart-control" class="scatterplot-config-control tab-pane">\n \n </div>\n\n \n <div id="chart-display" class="scatterplot-display tab-pane"></div>\n\n </div>\n</div>\n'});var ScatterplotConfigEditor=Backbone.View.extend(LoggableMixin).extend({className:"scatterplot-control-form",initialize:function(a){if(this.model||(this.model=new Visualization({type:"scatterplot"})),this.log(this+".initialize, attributes:",a),!a||!a.dataset)throw new Error("ScatterplotConfigEditor requires a dataset");this.dataset=a.dataset,this.log("dataset:",this.dataset),this.display=new ScatterplotDisplay({dataset:a.dataset,model:this.model})},render:function(){this.$el.empty().append(ScatterplotConfigEditor.templates.mainLayout({})),this.model.id&&(this.$el.find(".copy-btn").show(),this.$el.find(".save-btn").text("Update saved")),this.$el.find("[title]").tooltip(),this._render_dataControl(),this._render_chartControls(),this._render_chartDisplay();var a=this.model.get("config");return this.model.id&&_.isFinite(a.xColumn)&&_.isFinite(a.yColumn)&&this.renderChart(),this},_render_dataControl:function(a){a=a||this.$el;var b=this,c=this.dataset,d=c.metadata_column_names||[],e=this.model.get("config"),f=[],g=_.map(c.metadata_column_types,function(a,b){var c={index:b,type:a,name:d[b]||"column "+(b+1)};return("int"===c.type||"float"===c.type)&&f.push(c),c});f.length<2&&(f=g);var h=a.find(".tab-pane#data-control");h.html(ScatterplotConfigEditor.templates.dataControl({allColumns:g,numericColumns:f}));var i={xColumn:_.isFinite(e.xColumn)?e.xColumn:f[0].index,yColumn:_.isFinite(e.yColumn)?e.yColumn:f[1].index,idColumn:g[0].index};if(_.isFinite(e.idColumn))i.idColumn=e.idColumn;else if(g.length>2){var j=_.find(g,function(a,b){return b!==i.xColumn&&b!==i.yColumn});i.idColumn=j.index}return e=this.model.set("config",i,{silent:!0}).get("config"),h.find('[name="xColumn"]').val(e.xColumn).on("change",function(){b.model.set("config",{xColumn:Number($(this).val())})}),h.find('[name="yColumn"]').val(e.yColumn).on("change",function(){b.model.set("config",{yColumn:Number($(this).val())})}),h.find('select[name="idColumn"]').val(e.idColumn).on("change",function(){b.model.set("config",{idColumn:Number($(this).val())})}),void 0!==e.idColumn&&h.find("#include-id-checkbox").prop("checked",!0).trigger("change"),h.find("[title]").tooltip(),h},_render_chartControls:function(a){function b(){var a=$(this),b=a.slider("value");c.model.set("config",_.object([[a.parent().data("config-key"),b]])),a.siblings(".slider-output").text(b)}a=a||this.$el;var c=this,d=this.model.get("config"),e=a.find("#chart-control");e.html(ScatterplotConfigEditor.templates.chartControl(d));var f={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};e.find(".numeric-slider-input").each(function(){var a=$(this),c=a.attr("data-config-key"),e=_.extend(f[c],{value:d[c],change:b,slide:b});a.find(".slider").slider(e),a.children(".slider-output").text(d[c])});var g=this.dataset.metadata_column_names||[],h=d.xLabel||g[d.xColumn]||"X",i=d.yLabel||g[d.yColumn]||"Y";return e.find('input[name="X-axis-label"]').val(h).on("change",function(){c.model.set("config",{xLabel:$(this).val()})}),e.find('input[name="Y-axis-label"]').val(i).on("change",function(){c.model.set("config",{yLabel:$(this).val()})}),e.find("[title]").tooltip(),e},_render_chartDisplay:function(a){a=a||this.$el;var b=a.find(".tab-pane#chart-display");return this.display.setElement(b),this.display.render(),b.find("[title]").tooltip(),b},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart","click .save-btn":"saveVisualization"},saveVisualization:function(){var a=this;this.model.save().fail(function(b,c,d){console.error(b,c,d),a.trigger("save:error",view),alert("Error loading data:\n"+b.responseText)}).then(function(){a.display.render()})},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.display.fetchData()},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:scatterplot.editor,dataControl:scatterplot.datacontrol,chartControl:scatterplot.chartcontrol};var ScatterplotDisplay=Backbone.View.extend({initialize:function(a){this.data=null,this.dataset=a.dataset,this.lineCount=this.dataset.metadata_data_lines||null},fetchData:function(){this.showLoadingIndicator();var a=this,b=this.model.get("config"),c=jQuery.getJSON("/api/datasets/"+this.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:b.pagination.perPage,offset:b.pagination.currPage*b.pagination.perPage});return c.done(function(b){a.data=b.data,a.trigger("data:fetched",a),a.renderData()}),c.fail(function(b,c,d){console.error(b,c,d),a.trigger("data:error",a),alert("Error loading data:\n"+b.responseText)}),c},showLoadingIndicator:function(){this.$el.find(".scatterplot-data-info").html(['<div class="loading-indicator">','<span class="fa fa-spinner fa-spin"></span>','<span class="loading-indicator-message">loading...</span>',"</div>"].join(""))},template:function(){var a=['<div class="controls clear">','<div class="left">','<div class="page-control"></div>',"</div>",'<div class="right">','<p class="scatterplot-data-info"></p>','<button class="stats-toggle-btn">Stats</button>','<button class="rerender-btn">Redraw</button>',"</div>","</div>","<svg/>",'<div class="stats-display"></div>'].join("");return a},render:function(){return this.$el.addClass("scatterplot-display").html(this.template()),this.data&&this.renderData(),this},renderData:function(){this.renderLeftControls(),this.renderRightControls(),this.renderPlot(this.data),this.getStats()},renderLeftControls:function(){var a=this,b=this.model.get("config");return this.$el.find(".controls .left .page-control").pagination({startingPage:b.pagination.currPage,perPage:b.pagination.perPage,totalDataSize:this.lineCount,currDataSize:this.data.length}).off().on("pagination.page-change",function(c,d){b.pagination.currPage=d,a.model.set("config",{pagination:b.pagination}),a.resetZoom(),a.fetchData()}),this},renderRightControls:function(){var a=this;this.setLineInfo(this.data),this.$el.find(".stats-toggle-btn").off().click(function(){a.toggleStats()}),this.$el.find(".rerender-btn").off().click(function(){a.resetZoom(),a.renderPlot(this.data)})},renderPlot:function(){var a=this,b=this.$el.find("svg");this.toggleStats(!1),b.off().empty().show().on("zoom.scatterplot",function(b,c){a.model.set("config",c)}),scatterplot(b.get(0),this.model.get("config"),this.data)},setLineInfo:function(a,b){if(a){var c=this.model.get("config"),d=this.lineCount||"an unknown total",e=c.pagination.currPage*c.pagination.perPage,f=e+a.length;this.$el.find(".controls p.scatterplot-data-info").text([e+1,"to",f,"of",d].join(" "))}else this.$el.find(".controls p.scatterplot-data-info").html(b||"");return this},resetZoom:function(a,b){return a=void 0!==a?a:1,b=void 0!==b?b:[0,0],this.model.set("config",{scale:a,translate:b}),this},getStats:function(){if(this.data){var a=this,b=this.model.get("config"),c=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js");c.postMessage({data:this.data,keys:[b.xColumn,b.yColumn]}),c.onerror=function(){c.terminate()},c.onmessage=function(b){a.renderStats(b.data)}}},renderStats:function(a){var b=this.model.get("config"),c=this.$el.find(".stats-display"),d=b.x.label,e=b.y.label,f=$("<table/>").addClass("table").append(["<thead><th></th><th>",d,"</th><th>",e,"</th></thead>"].join("")).append(_.map(a,function(a,b){return $(["<tr><td>",b,"</td><td>",a[0],"</td><td>",a[1],"</td></tr>"].join(""))}));c.empty().append(f)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}}),ScatterplotModel=Visualization.extend({defaults:{type:"scatterplot",config:{pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},x:{ticks:10,label:"X"},y:{ticks:10,label:"Y"},datapointSize:4,animDuration:500,scale:1,translate:[0,0]}}});
\ No newline at end of file
diff -r 3c7e8a024e9436eb2d7e970b7721f691728c4dae -r b8ff65e15b1b40a76544ef07e7ab98a9b5ce7221 config/plugins/visualizations/scatterplot/static/scatterplot.css
--- a/config/plugins/visualizations/scatterplot/static/scatterplot.css
+++ b/config/plugins/visualizations/scatterplot/static/scatterplot.css
@@ -7,18 +7,36 @@
margin-bottom: 16px;
overflow: auto;
background-color: #ebd9b2;
- padding : 16px 12px 12px 12px;
+ padding : 8px 12px 8px 12px;
}
.chart-header h2 {
margin-bottom: 4px;
}
+.chart-header h2:hover {
+ text-decoration: underline;
+ cursor: pointer;
+}
+
.chart-header p {
- color: white;
+ color: #555;
font-size: small;
}
+figcaption {
+ margin-bottom: 8px;
+ font-size: 120%;
+}
+figcaption .title {
+ display: block;
+ font-weight: bold;
+}
+figcaption .title-info {
+ display: block;
+ font-style: italic;
+}
+
/* -------------------------------------------- main layout */
.scatterplot-editor > * { margin: 0; padding: 0; }
.scatterplot-editor .tab-pane {
diff -r 3c7e8a024e9436eb2d7e970b7721f691728c4dae -r b8ff65e15b1b40a76544ef07e7ab98a9b5ce7221 config/plugins/visualizations/scatterplot/templates/scatterplot.mako
--- a/config/plugins/visualizations/scatterplot/templates/scatterplot.mako
+++ b/config/plugins/visualizations/scatterplot/templates/scatterplot.mako
@@ -18,7 +18,6 @@
## ----------------------------------------------------------------------------
<link type="text/css" rel="Stylesheet" media="screen" href="/static/style/base.css"><link type="text/css" rel="Stylesheet" media="screen" href="/static/style/jquery-ui/smoothness/jquery-ui.css">
-
<link type="text/css" rel="Stylesheet" media="screen" href="/plugins/visualizations/scatterplot/static/scatterplot.css">
## ----------------------------------------------------------------------------
@@ -40,8 +39,35 @@
## ----------------------------------------------------------------------------
<body>
-%if not embedded:
-## dataset info: only show if on own page
+%if embedded and saved_visualization:
+<figcaption>
+ <span class="title">${title}</span>
+ <span class="title-info">${info}</span>
+</figcaption>
+<figure class="scatterplot-display"></div>
+
+<script type="text/javascript">
+$(function(){
+ var model = new ScatterplotModel({
+ id : ${h.to_json_string( visualization_id )} || undefined,
+ title : "${title}",
+ config : ${h.to_json_string( config, indent=2 )}
+ });
+ hdaJson = ${h.to_json_string( trans.security.encode_dict_ids( hda.to_dict() ), indent=2 )},
+ display = new ScatterplotDisplay({
+ el : $( '.scatterplot-display' ).attr( 'id', 'scatterplot-display-' + '${visualization_id}' ),
+ model : model,
+ dataset : hdaJson,
+ embedded: "${embedded}"
+ }).render();
+ display.fetchData();
+ //window.model = model;
+ //window.display = display;
+});
+
+</script>
+
+%else:
<div class="chart-header"><h2>${title or default_title}</h2><p>${info}</p>
@@ -61,10 +87,17 @@
model : model,
dataset : hdaJson
}).render();
- window.editor = editor;
+ //window.editor = editor;
+ $( '.chart-header h2' ).click( function(){
+ var returned = prompt( 'Enter a new title:' );
+ if( returned ){
+ model.set( 'title', returned );
+ }
+ });
model.on( 'change:title', function(){
$( '.chart-header h2' ).text( model.get( 'title' ) );
+ document.title = model.get( 'title' ) + ' | ' + '${visualization_display_name}';
})
});
diff -r 3c7e8a024e9436eb2d7e970b7721f691728c4dae -r b8ff65e15b1b40a76544ef07e7ab98a9b5ce7221 lib/galaxy/webapps/galaxy/controllers/visualization.py
--- a/lib/galaxy/webapps/galaxy/controllers/visualization.py
+++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py
@@ -584,6 +584,16 @@
ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, visualization )
# Display.
+ if trans.app.visualizations_registry and visualization.type in trans.app.visualizations_registry.plugins:
+ # if a registry visualization, load a version of display.mako that will load the vis into an iframe :(
+ #TODO: simplest path from A to B but not optimal - will be difficult to do reg visualizations any other way
+ #TODO: this will load the visualization twice (once above, once when the iframe src calls 'saved')
+ encoded_visualization_id = trans.security.encode_id( visualization.id )
+ return trans.stream_template_mako( 'visualization/display_in_frame.mako',
+ item=visualization, encoded_visualization_id=encoded_visualization_id,
+ user_item_rating=user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings,
+ content_only=True )
+
visualization_config = self.get_visualization_config( trans, visualization )
return trans.stream_template_mako( "visualization/display.mako", item = visualization, item_data = visualization_config,
user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings,
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: inithello: Remove stats/wiggle_to_simple, which is an exact duplicate of filters/wiggle_to_simple.
by commits-noreply@bitbucket.org 14 Feb '14
by commits-noreply@bitbucket.org 14 Feb '14
14 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/3c7e8a024e94/
Changeset: 3c7e8a024e94
User: inithello
Date: 2014-02-14 18:34:58
Summary: Remove stats/wiggle_to_simple, which is an exact duplicate of filters/wiggle_to_simple.
Affected #: 4 files
diff -r 128cbb948cc49ef2a08953f3facd59c409344e56 -r 3c7e8a024e9436eb2d7e970b7721f691728c4dae tool_conf.xml.main
--- a/tool_conf.xml.main
+++ b/tool_conf.xml.main
@@ -95,7 +95,7 @@
<tool file="maf/maf_reverse_complement.xml" /></section><section id="scores" name="Get Genomic Scores">
- <tool file="stats/wiggle_to_simple.xml" />
+ <tool file="filters/wiggle_to_simple.xml" /><tool file="stats/aggregate_binned_scores_in_intervals.xml" /></section><section id="bxops" name="Operate on Genomic Intervals">
diff -r 128cbb948cc49ef2a08953f3facd59c409344e56 -r 3c7e8a024e9436eb2d7e970b7721f691728c4dae tool_conf.xml.sample
--- a/tool_conf.xml.sample
+++ b/tool_conf.xml.sample
@@ -104,7 +104,7 @@
<tool file="maf/maf_filter.xml" /></section><section id="scores" name="Get Genomic Scores">
- <tool file="stats/wiggle_to_simple.xml" />
+ <tool file="filters/wiggle_to_simple.xml" /><tool file="stats/aggregate_binned_scores_in_intervals.xml" /></section><section id="bxops" name="Operate on Genomic Intervals">
diff -r 128cbb948cc49ef2a08953f3facd59c409344e56 -r 3c7e8a024e9436eb2d7e970b7721f691728c4dae tools/stats/wiggle_to_simple.py
--- a/tools/stats/wiggle_to_simple.py
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/env python
-
-"""
-Read a wiggle track and print out a series of lines containing
-"chrom position score". Ignores track lines, handles bed, variableStep
-and fixedStep wiggle lines.
-"""
-import sys
-from galaxy import eggs
-import pkg_resources; pkg_resources.require( "bx-python" )
-import bx.wiggle
-from galaxy.tools.exception_handling import *
-
-def stop_err( msg ):
- sys.stderr.write( msg )
- sys.exit()
-
-def main():
- if len( sys.argv ) > 1:
- in_file = open( sys.argv[1] )
- else:
- in_file = open( sys.stdin )
-
- if len( sys.argv ) > 2:
- out_file = open( sys.argv[2], "w" )
- else:
- out_file = sys.stdout
-
- try:
- for fields in bx.wiggle.IntervalReader( UCSCOutWrapper( in_file ) ):
- out_file.write( "%s\n" % "\t".join( map( str, fields ) ) )
- except UCSCLimitException:
- # Wiggle data was truncated, at the very least need to warn the user.
- print 'Encountered message from UCSC: "Reached output limit of 100000 data values", so be aware your data was truncated.'
- except ValueError, e:
- in_file.close()
- out_file.close()
- stop_err( str( e ) )
-
- in_file.close()
- out_file.close()
-
-if __name__ == "__main__": main()
diff -r 128cbb948cc49ef2a08953f3facd59c409344e56 -r 3c7e8a024e9436eb2d7e970b7721f691728c4dae tools/stats/wiggle_to_simple.xml
--- a/tools/stats/wiggle_to_simple.xml
+++ /dev/null
@@ -1,88 +0,0 @@
-<tool id="wiggle2simple1" name="Wiggle-to-Interval">
- <description>converter</description>
- <command interpreter="python">wiggle_to_simple.py $input $out_file1 </command>
- <inputs>
- <param format="wig" name="input" type="data" label="Convert"/>
- </inputs>
- <outputs>
- <data format="interval" name="out_file1" />
- </outputs>
- <tests>
- <test>
- <param name="input" value="2.wig" />
- <output name="out_file1" file="2.interval"/>
- </test>
- <test>
- <param name="input" value="3.wig" />
- <output name="out_file1" file="3_wig.bed"/>
- </test>
- </tests>
- <help>
-**Syntax**
-
-This tool converts wiggle data into interval type.
-
-- **Wiggle format**: The .wig format is line-oriented. Wiggle data is preceded by a UCSC track definition line. Following the track definition line is the track data, which can be entered in three different formats described below.
-
- - **BED format** with no declaration line and four columns of data::
-
- chromA chromStartA chromEndA dataValueA
- chromB chromStartB chromEndB dataValueB
-
- - **variableStep** two column data; started by a declaration line and followed with chromosome positions and data values::
-
- variableStep chrom=chrN [span=windowSize]
- chromStartA dataValueA
- chromStartB dataValueB
-
- - **fixedStep** single column data; started by a declaration line and followed with data values::
-
- fixedStep chrom=chrN start=position step=stepInterval [span=windowSize]
- dataValue1
- dataValue2
-
------
-
-**Example**
-
-- input wiggle format file::
-
- #track type=wiggle_0 name="Bed Format" description="BED format"
- chr19 59302000 59302300 -1.0
- chr19 59302300 59302600 -0.75
- chr19 59302600 59302900 -0.50
- chr19 59302900 59303200 -0.25
- chr19 59303200 59303500 0.0
- #track type=wiggle_0 name="variableStep" description="variableStep format"
- variableStep chrom=chr19 span=150
- 59304701 10.0
- 59304901 12.5
- 59305401 15.0
- 59305601 17.5
- #track type=wiggle_0 name="fixedStep" description="fixed step" visibility=full
- fixedStep chrom=chr19 start=59307401 step=300 span=200
- 1000
- 900
- 800
- 700
- 600
-
-- convert the above file to interval file::
-
- chr19 59302000 59302300 + -1.0
- chr19 59302300 59302600 + -0.75
- chr19 59302600 59302900 + -0.5
- chr19 59302900 59303200 + -0.25
- chr19 59303200 59303500 + 0.0
- chr19 59304701 59304851 + 10.0
- chr19 59304901 59305051 + 12.5
- chr19 59305401 59305551 + 15.0
- chr19 59305601 59305751 + 17.5
- chr19 59307701 59307901 + 1000.0
- chr19 59308001 59308201 + 900.0
- chr19 59308301 59308501 + 800.0
- chr19 59308601 59308801 + 700.0
- chr19 59308901 59309101 + 600.0
-
-</help>
-</tool>
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/75672e1cba2b/
Changeset: 75672e1cba2b
Branch: stable
User: dannon
Date: 2014-02-11 17:59:38
Summary: Select2 dynamic loading now uses non-static URL (Should fix main's prefixed URL issue)
Affected #: 1 file
diff -r 6f9059801fafb3b7ae2cf844b07848beab0846fa -r 75672e1cba2bd569b729156f3d8f8f1f994240f0 templates/admin/impersonate.mako
--- a/templates/admin/impersonate.mako
+++ b/templates/admin/impersonate.mako
@@ -30,7 +30,7 @@
$("#email_select").select2({
placeholder: "Select a user",
ajax: {
- url: "/api/users/",
+ url: "${h.url_for(controller="/api/users", action="index")}",
dataType: 'json',
quietMillis: 250,
matcher: function(term, text) { return text.toUpperCase().indexOf(term.toUpperCase())>=0; },
https://bitbucket.org/galaxy/galaxy-central/commits/128cbb948cc4/
Changeset: 128cbb948cc4
User: jmchilton
Date: 2014-02-14 17:54:15
Summary: Merge latest stable.
Affected #: 0 files
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: Saved visualizations list: use vis-reg plugin display name for type column (if available)
by commits-noreply@bitbucket.org 14 Feb '14
by commits-noreply@bitbucket.org 14 Feb '14
14 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/b0919189ecfa/
Changeset: b0919189ecfa
User: carlfeberhard
Date: 2014-02-14 17:21:51
Summary: Saved visualizations list: use vis-reg plugin display name for type column (if available)
Affected #: 1 file
diff -r 044ead55435801561acdf72fe225209239202a5a -r b0919189ecfa6a779d776dfc5a52f541214ddcc5 lib/galaxy/webapps/galaxy/controllers/visualization.py
--- a/lib/galaxy/webapps/galaxy/controllers/visualization.py
+++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py
@@ -202,6 +202,12 @@
url_kwargs[ 'action' ] = 'saved'
return url_kwargs
+ def get_display_name( self, trans, item ):
+ if trans.app.visualizations_registry and item.type in trans.app.visualizations_registry.plugins:
+ plugin = trans.app.visualizations_registry.plugins[ item.type ]
+ return plugin.config.get( 'name', item.type )
+ return item.type
+
# Grid definition
title = "Saved Visualizations"
model_class = model.Visualization
@@ -209,8 +215,7 @@
default_filter = dict( title="All", deleted="False", tags="All", sharing="All" )
columns = [
grids.TextColumn( "Title", key="title", attach_popup=True, link=get_url_args ),
- #TODO: should use display name when available
- grids.TextColumn( "Type", key="type" ),
+ grids.TextColumn( "Type", method='get_display_name' ),
grids.TextColumn( "Dbkey", key="dbkey" ),
grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.VisualizationTagAssociation, filterable="advanced", grid_name="VisualizationListGrid" ),
grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False ),
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: Remove scatterplot from legacy visualization framework (it will no longer appear if you haven't enabled visualization_plugins_directory in your universe_wsgi.ini); clean up; pack scripts
by commits-noreply@bitbucket.org 14 Feb '14
by commits-noreply@bitbucket.org 14 Feb '14
14 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/044ead554358/
Changeset: 044ead554358
User: carlfeberhard
Date: 2014-02-14 16:47:38
Summary: Remove scatterplot from legacy visualization framework (it will no longer appear if you haven't enabled visualization_plugins_directory in your universe_wsgi.ini); clean up; pack scripts
Affected #: 21 files
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a lib/galaxy/datatypes/tabular.py
--- a/lib/galaxy/datatypes/tabular.py
+++ b/lib/galaxy/datatypes/tabular.py
@@ -332,24 +332,6 @@
def as_ucsc_display_file( self, dataset, **kwd ):
return open( dataset.file_name )
- def get_visualizations( self, dataset ):
- """
- Returns a list of visualizations for datatype.
- """
- # Can visualize tabular data as scatterplot if there are 2+ numerical
- # columns.
- num_numerical_cols = 0
- if dataset.metadata.column_types:
- for col_type in dataset.metadata.column_types:
- if col_type in [ 'int', 'float' ]:
- num_numerical_cols += 1
-
- vizs = super( Tabular, self ).get_visualizations( dataset )
- if num_numerical_cols >= 2:
- vizs.append( 'scatterplot' )
-
- return vizs
-
# ------------- Dataproviders
@dataproviders.decorators.dataprovider_factory( 'column', dataproviders.column.ColumnarDataProvider.settings )
def column_dataprovider( self, dataset, **settings ):
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a lib/galaxy/webapps/galaxy/controllers/visualization.py
--- a/lib/galaxy/webapps/galaxy/controllers/visualization.py
+++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py
@@ -964,16 +964,6 @@
return self.get_visualization( trans, id )
@web.expose
- def scatterplot( self, trans, dataset_id, **kwargs ):
- """
- Returns a page that controls and renders a scatteplot graph.
- """
- hda = self.get_dataset( trans, dataset_id, check_ownership=False, check_accessible=True )
- return trans.fill_template_mako( "visualization/scatterplot.mako",
- hda=hda,
- query_args=kwargs )
-
- @web.expose
def phyloviz( self, trans, id=None, dataset_id=None, tree_index=0, **kwargs ):
config = None
data = None
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/mvc/dataset/hda-edit.js
--- a/static/scripts/mvc/dataset/hda-edit.js
+++ b/static/scripts/mvc/dataset/hda-edit.js
@@ -197,23 +197,18 @@
// map a function to each visualization in the icon's attributes
// create a popupmenu from that map
- var hdaView = this;
/** @inner */
function create_viz_action( visualization ) {
- switch( visualization ){
- case 'trackster':
- return create_trackster_action_fn( visualization_url, params, dbkey );
- case 'scatterplot':
- return create_scatterplot_action_fn( visualization_url, params, hdaView.linkTarget );
- default:
- return function(){
- Galaxy.frame.add({
- title : "Visualization",
- type : "url",
- content : visualization_url + '/' + visualization + '?' + $.param( params )
- });
- };
+ if( visualization === 'trackster' ){
+ return create_trackster_action_fn( visualization_url, params, dbkey );
}
+ return function(){
+ Galaxy.frame.add({
+ title : "Visualization",
+ type : "url",
+ content : visualization_url + '/' + visualization + '?' + $.param( params )
+ });
+ };
}
function titleCase( string ){
@@ -412,29 +407,6 @@
//==============================================================================
//TODO: these belong somewhere else
-
-/** Create scatterplot loading/set up function for use with the visualizations popupmenu.
- * @param {String} url url (gen. 'visualizations') to which to append 'scatterplot' and params
- * @param {Object} params parameters to convert to query string for splot page
- * @returns function that loads the scatterplot
- */
-//TODO: should be imported from scatterplot.js OR abstracted to 'load this in the galaxy_main frame'
-function create_scatterplot_action_fn( url, params, target ){
- action = function() {
- Galaxy.frame.add({
- title : "Scatterplot",
- type : "url",
- content : url + '/scatterplot?' + $.param(params),
- target : target
- });
-
- //TODO: this needs to go away
- $( 'div.popmenu-wrapper' ).remove();
- return false;
- };
- return action;
-}
-
// -----------------------------------------------------------------------------
/** Create trackster loading/set up function for use with the visualizations popupmenu.
* Shows modal dialog for load old/create new.
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/packed/galaxy.tools.js
--- /dev/null
+++ b/static/scripts/packed/galaxy.tools.js
@@ -0,0 +1,1 @@
+define(["mvc/tools"],function(b){var a=function(d,c){$("input[name='"+d+"'][type='checkbox']").attr("checked",!!c)};$("div.checkUncheckAllPlaceholder").each(function(){var c=$(this).attr("checkbox_name");select_link=$("<a class='action-button'></a>").text("Select All").click(function(){a(c,true)});unselect_link=$("<a class='action-button'></a>").text("Unselect All").click(function(){a(c,false)});$(this).append(select_link).append(" ").append(unselect_link)})});
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/packed/mvc/dataset/hda-edit.js
--- a/static/scripts/packed/mvc/dataset/hda-edit.js
+++ b/static/scripts/packed/mvc/dataset/hda-edit.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-model","mvc/dataset/hda-base"],function(d,a){var f=a.HDABaseView.extend(LoggableMixin).extend({initialize:function(g){a.HDABaseView.prototype.initialize.call(this,g);this.hasUser=g.hasUser;this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton];this.tagsEditorShown=g.tagsEditorShown||false;this.annotationEditorShown=g.annotationEditorShown||false},_render_titleButtons:function(){return a.HDABaseView.prototype._render_titleButtons.call(this).concat([this._render_editButton(),this._render_deleteButton()])},_render_editButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.DISCARDED)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var i=this.model.get("purged"),g=this.model.get("deleted"),h={title:_l("Edit attributes"),href:this.urls.edit,target:this.linkTarget,classes:"dataset-edit"};if(g||i){h.disabled=true;if(i){h.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(g){h.title=_l("Undelete dataset to edit attributes")}}}else{if(this.model.get("state")===d.HistoryDatasetAssociation.STATES.UPLOAD){h.disabled=true;h.title=_l("This dataset must finish uploading before it can be edited")}}h.faIcon="fa-pencil";return faIconButton(h)},_render_deleteButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var g=this,h={title:_l("Delete"),classes:"dataset-delete",onclick:function(){g.$el.find(".icon-btn.dataset-delete").trigger("mouseout");g.model["delete"]()}};if(this.model.get("deleted")||this.model.get("purged")){h={title:_l("Dataset is already deleted"),disabled:true}}h.faIcon="fa-times";return faIconButton(h)},_render_errButton:function(){if(this.model.get("state")!==d.HistoryDatasetAssociation.STATES.ERROR){return null}return faIconButton({title:_l("View or report this error"),href:this.urls.report_error,classes:"dataset-report-error-btn",target:this.linkTarget,faIcon:"fa-bug"})},_render_rerunButton:function(){return faIconButton({title:_l("Run this job again"),href:this.urls.rerun,classes:"dataset-rerun-btn",target:this.linkTarget,faIcon:"fa-refresh"})},_render_visualizationsButton:function(){var n=this.model.get("visualizations");if((!this.hasUser)||(!this.model.hasData())||(_.isEmpty(n))){return null}if(_.isObject(n[0])){return this._render_visualizationsFrameworkButton(n)}if(!this.urls.visualization){return null}var k=this.model.get("dbkey"),g=this.urls.visualization,j={},h={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(k){h.dbkey=k}var l=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});var m=this;function i(p){switch(p){case"trackster":return b(g,h,k);case"scatterplot":return e(g,h,m.linkTarget);default:return function(){Galaxy.frame.add({title:"Visualization",type:"url",content:g+"/"+p+"?"+$.param(h)})}}}function o(p){return p.charAt(0).toUpperCase()+p.slice(1)}if(n.length===1){l.attr("data-original-title",_l("Visualize in ")+_l(o(n[0])));l.click(i(n[0]))}else{_.each(n,function(p){j[_l(o(p))]=i(p)});make_popupmenu(l,j)}return l},_render_visualizationsFrameworkButton:function(g){if(!(this.model.hasData())||!(g&&!_.isEmpty(g))){return null}var i=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});if(g.length===1){var h=g[0];i.attr("data-original-title",_l("Visualize in ")+h.html);i.attr("href",h.href)}else{var j=[];_.each(g,function(k){k.func=function(){if(Galaxy.frame.active){Galaxy.frame.add({title:"Visualization",type:"url",content:k.href});return false}return true};j.push(k);return false});PopupMenu.create(i,j)}return i},_buildNewRender:function(){var g=a.HDABaseView.prototype._buildNewRender.call(this);g.find(".dataset-deleted-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-undelete">here</a> to undelete it or <a href="javascript:void(0);" class="dataset-purge">here</a> to immediately remove it from disk'));g.find(".dataset-hidden-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-unhide">here</a> to unhide it'));return g},_render_body_failed_metadata:function(){var h=$("<a/>").attr({href:this.urls.edit,target:this.linkTarget}).text(_l("set it manually or retry auto-detection")),g=$("<span/>").text(". "+_l("You may be able to")+" ").append(h),i=a.HDABaseView.prototype._render_body_failed_metadata.call(this);i.find(".warningmessagesmall strong").append(g);return i},_render_body_error:function(){var g=a.HDABaseView.prototype._render_body_error.call(this);g.find(".dataset-actions .left").prepend(this._render_errButton());return g},_render_body_ok:function(){var g=a.HDABaseView.prototype._render_body_ok.call(this);if(this.model.isDeletedOrPurged()){return g}this.makeDbkeyEditLink(g);if(this.hasUser){g.find(".dataset-actions .left").append(this._render_visualizationsButton());this._renderTags(g);this._renderAnnotation(g)}return g},_renderTags:function(g){var h=this;this.tagsEditor=new TagsEditor({model:this.model,el:g.find(".tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.tagsEditorShown=true},onhide:function(){h.tagsEditorShown=false},$activator:faIconButton({title:_l("Edit dataset tags"),classes:"dataset-tag-btn",faIcon:"fa-tags"}).appendTo(g.find(".dataset-actions .right"))});if(this.tagsEditorShown){this.tagsEditor.toggle(true)}},_renderAnnotation:function(g){var h=this;this.annotationEditor=new AnnotationEditor({model:this.model,el:g.find(".annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){h.annotationEditorShown=true},onhide:function(){h.annotationEditorShown=false},$activator:faIconButton({title:_l("Edit dataset annotation"),classes:"dataset-annotate-btn",faIcon:"fa-comment"}).appendTo(g.find(".dataset-actions .right"))});if(this.annotationEditorShown){this.annotationEditor.toggle(true)}},makeDbkeyEditLink:function(h){if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){var g=$('<a class="value">?</a>').attr("href",this.urls.edit).attr("target",this.linkTarget);h.find(".dataset-dbkey .value").replaceWith(g)}},events:_.extend(_.clone(a.HDABaseView.prototype.events),{"click .dataset-undelete":function(g){this.model.undelete();return false},"click .dataset-unhide":function(g){this.model.unhide();return false},"click .dataset-purge":"confirmPurge"}),confirmPurge:function c(g){this.model.purge();return false},toString:function(){var g=(this.model)?(this.model+""):("(no model)");return"HDAView("+g+")"}});function e(g,i,h){action=function(){Galaxy.frame.add({title:"Scatterplot",type:"url",content:g+"/scatterplot?"+$.param(i),target:h});$("div.popmenu-wrapper").remove();return false};return action}function b(g,i,h){return function(){var j={};if(h){j["f-dbkey"]=h}$.ajax({url:g+"/list_tracks?"+$.param(j),dataType:"html",error:function(){alert(("Could not add this dataset to browser")+".")},success:function(k){var l=window.parent;l.Galaxy.modal.show({title:"View Data in a New or Saved Visualization",buttons:{Cancel:function(){l.Galaxy.modal.hide()},"View in saved visualization":function(){l.Galaxy.modal.show({title:"Add Data to Saved Visualization",body:k,buttons:{Cancel:function(){l.Galaxy.modal.hide()},"Add to visualization":function(){$(l.document).find("input[name=id]:checked").each(function(){l.Galaxy.modal.hide();var m=$(this).val();i.id=m;l.Galaxy.frame.add({title:"Trackster",type:"url",content:g+"/trackster?"+$.param(i)})})}}})},"View in new visualization":function(){l.Galaxy.modal.hide();var m=g+"/trackster?"+$.param(i);l.Galaxy.frame.add({title:"Trackster",type:"url",content:m})}}})}});return false}}return{HDAEditView:f}});
\ No newline at end of file
+define(["mvc/dataset/hda-model","mvc/dataset/hda-base"],function(d,a){var e=a.HDABaseView.extend(LoggableMixin).extend({initialize:function(f){a.HDABaseView.prototype.initialize.call(this,f);this.hasUser=f.hasUser;this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton];this.tagsEditorShown=f.tagsEditorShown||false;this.annotationEditorShown=f.annotationEditorShown||false},_render_titleButtons:function(){return a.HDABaseView.prototype._render_titleButtons.call(this).concat([this._render_editButton(),this._render_deleteButton()])},_render_editButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.DISCARDED)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var h=this.model.get("purged"),f=this.model.get("deleted"),g={title:_l("Edit attributes"),href:this.urls.edit,target:this.linkTarget,classes:"dataset-edit"};if(f||h){g.disabled=true;if(h){g.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(f){g.title=_l("Undelete dataset to edit attributes")}}}else{if(this.model.get("state")===d.HistoryDatasetAssociation.STATES.UPLOAD){g.disabled=true;g.title=_l("This dataset must finish uploading before it can be edited")}}g.faIcon="fa-pencil";return faIconButton(g)},_render_deleteButton:function(){if((this.model.get("state")===d.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===d.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var f=this,g={title:_l("Delete"),classes:"dataset-delete",onclick:function(){f.$el.find(".icon-btn.dataset-delete").trigger("mouseout");f.model["delete"]()}};if(this.model.get("deleted")||this.model.get("purged")){g={title:_l("Dataset is already deleted"),disabled:true}}g.faIcon="fa-times";return faIconButton(g)},_render_errButton:function(){if(this.model.get("state")!==d.HistoryDatasetAssociation.STATES.ERROR){return null}return faIconButton({title:_l("View or report this error"),href:this.urls.report_error,classes:"dataset-report-error-btn",target:this.linkTarget,faIcon:"fa-bug"})},_render_rerunButton:function(){return faIconButton({title:_l("Run this job again"),href:this.urls.rerun,classes:"dataset-rerun-btn",target:this.linkTarget,faIcon:"fa-refresh"})},_render_visualizationsButton:function(){var f=this.model.get("visualizations");if((!this.hasUser)||(!this.model.hasData())||(_.isEmpty(f))){return null}if(_.isObject(f[0])){return this._render_visualizationsFrameworkButton(f)}if(!this.urls.visualization){return null}var h=this.model.get("dbkey"),l=this.urls.visualization,i={},m={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(h){m.dbkey=h}var g=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});function j(n){if(n==="trackster"){return b(l,m,h)}return function(){Galaxy.frame.add({title:"Visualization",type:"url",content:l+"/"+n+"?"+$.param(m)})}}function k(n){return n.charAt(0).toUpperCase()+n.slice(1)}if(f.length===1){g.attr("data-original-title",_l("Visualize in ")+_l(k(f[0])));g.click(j(f[0]))}else{_.each(f,function(n){i[_l(k(n))]=j(n)});make_popupmenu(g,i)}return g},_render_visualizationsFrameworkButton:function(f){if(!(this.model.hasData())||!(f&&!_.isEmpty(f))){return null}var h=faIconButton({title:_l("Visualize"),classes:"dataset-visualize-btn",faIcon:"fa-bar-chart-o"});if(f.length===1){var g=f[0];h.attr("data-original-title",_l("Visualize in ")+g.html);h.attr("href",g.href)}else{var i=[];_.each(f,function(j){j.func=function(){if(Galaxy.frame.active){Galaxy.frame.add({title:"Visualization",type:"url",content:j.href});return false}return true};i.push(j);return false});PopupMenu.create(h,i)}return h},_buildNewRender:function(){var f=a.HDABaseView.prototype._buildNewRender.call(this);f.find(".dataset-deleted-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-undelete">here</a> to undelete it or <a href="javascript:void(0);" class="dataset-purge">here</a> to immediately remove it from disk'));f.find(".dataset-hidden-msg").append(_l('Click <a href="javascript:void(0);" class="dataset-unhide">here</a> to unhide it'));return f},_render_body_failed_metadata:function(){var g=$("<a/>").attr({href:this.urls.edit,target:this.linkTarget}).text(_l("set it manually or retry auto-detection")),f=$("<span/>").text(". "+_l("You may be able to")+" ").append(g),h=a.HDABaseView.prototype._render_body_failed_metadata.call(this);h.find(".warningmessagesmall strong").append(f);return h},_render_body_error:function(){var f=a.HDABaseView.prototype._render_body_error.call(this);f.find(".dataset-actions .left").prepend(this._render_errButton());return f},_render_body_ok:function(){var f=a.HDABaseView.prototype._render_body_ok.call(this);if(this.model.isDeletedOrPurged()){return f}this.makeDbkeyEditLink(f);if(this.hasUser){f.find(".dataset-actions .left").append(this._render_visualizationsButton());this._renderTags(f);this._renderAnnotation(f)}return f},_renderTags:function(f){var g=this;this.tagsEditor=new TagsEditor({model:this.model,el:f.find(".tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){g.tagsEditorShown=true},onhide:function(){g.tagsEditorShown=false},$activator:faIconButton({title:_l("Edit dataset tags"),classes:"dataset-tag-btn",faIcon:"fa-tags"}).appendTo(f.find(".dataset-actions .right"))});if(this.tagsEditorShown){this.tagsEditor.toggle(true)}},_renderAnnotation:function(f){var g=this;this.annotationEditor=new AnnotationEditor({model:this.model,el:f.find(".annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){g.annotationEditorShown=true},onhide:function(){g.annotationEditorShown=false},$activator:faIconButton({title:_l("Edit dataset annotation"),classes:"dataset-annotate-btn",faIcon:"fa-comment"}).appendTo(f.find(".dataset-actions .right"))});if(this.annotationEditorShown){this.annotationEditor.toggle(true)}},makeDbkeyEditLink:function(g){if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){var f=$('<a class="value">?</a>').attr("href",this.urls.edit).attr("target",this.linkTarget);g.find(".dataset-dbkey .value").replaceWith(f)}},events:_.extend(_.clone(a.HDABaseView.prototype.events),{"click .dataset-undelete":function(f){this.model.undelete();return false},"click .dataset-unhide":function(f){this.model.unhide();return false},"click .dataset-purge":"confirmPurge"}),confirmPurge:function c(f){this.model.purge();return false},toString:function(){var f=(this.model)?(this.model+""):("(no model)");return"HDAView("+f+")"}});function b(f,h,g){return function(){var i={};if(g){i["f-dbkey"]=g}$.ajax({url:f+"/list_tracks?"+$.param(i),dataType:"html",error:function(){alert(("Could not add this dataset to browser")+".")},success:function(j){var k=window.parent;k.Galaxy.modal.show({title:"View Data in a New or Saved Visualization",buttons:{Cancel:function(){k.Galaxy.modal.hide()},"View in saved visualization":function(){k.Galaxy.modal.show({title:"Add Data to Saved Visualization",body:j,buttons:{Cancel:function(){k.Galaxy.modal.hide()},"Add to visualization":function(){$(k.document).find("input[name=id]:checked").each(function(){k.Galaxy.modal.hide();var l=$(this).val();h.id=l;k.Galaxy.frame.add({title:"Trackster",type:"url",content:f+"/trackster?"+$.param(h)})})}}})},"View in new visualization":function(){k.Galaxy.modal.hide();var l=f+"/trackster?"+$.param(h);k.Galaxy.frame.add({title:"Trackster",type:"url",content:l})}}})}});return false}}return{HDAEditView:e}});
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/packed/mvc/ui.js
--- a/static/scripts/packed/mvc/ui.js
+++ b/static/scripts/packed/mvc/ui.js
@@ -1,1 +1,1 @@
-var IconButton=Backbone.Model.extend({defaults:{title:"",icon_class:"",on_click:null,menu_options:null,is_menu_button:true,id:null,href:null,target:null,enabled:true,visible:true,tooltip_config:{}}});var IconButtonView=Backbone.View.extend({initialize:function(){this.model.attributes.tooltip_config={placement:"bottom"};this.model.bind("change",this.render,this)},render:function(){this.$el.tooltip("hide");var a=this.template(this.model.toJSON());a.tooltip(this.model.get("tooltip_config"));this.$el.replaceWith(a);this.setElement(a);return this},events:{click:"click"},click:function(a){if(_.isFunction(this.model.get("on_click"))){this.model.get("on_click")(a);return false}return true},template:function(b){var a='title="'+b.title+'" class="icon-button';if(b.is_menu_button){a+=" menu-button"}a+=" "+b.icon_class;if(!b.enabled){a+="_disabled"}a+='"';if(b.id){a+=' id="'+b.id+'"'}a+=' href="'+b.href+'"';if(b.target){a+=' target="'+b.target+'"'}if(!b.visible){a+=' style="display: none;"'}if(b.enabled){a="<a "+a+"/>"}else{a="<span "+a+"/>"}return $(a)}});var IconButtonCollection=Backbone.Collection.extend({model:IconButton});var IconButtonMenuView=Backbone.View.extend({tagName:"div",initialize:function(){this.render()},render:function(){var a=this;this.collection.each(function(d){var b=$("<a/>").attr("href","javascript:void(0)").attr("title",d.attributes.title).addClass("icon-button menu-button").addClass(d.attributes.icon_class).appendTo(a.$el).click(d.attributes.on_click);if(d.attributes.tooltip_config){b.tooltip(d.attributes.tooltip_config)}var c=d.get("options");if(c){make_popupmenu(b,c)}});return this}});var create_icon_buttons_menu=function(b,a){if(!a){a={}}var c=new IconButtonCollection(_.map(b,function(d){return new IconButton(_.extend(d,a))}));return new IconButtonMenuView({collection:c})};var Grid=Backbone.Collection.extend({});var GridView=Backbone.View.extend({});var PopupMenu=Backbone.View.extend({initialize:function(b,a){this.$button=b;if(!this.$button.size()){this.$button=$("<div/>")}this.options=a||[];var c=this;this.$button.click(function(d){$(".popmenu-wrapper").remove();c._renderAndShow(d);return false})},_renderAndShow:function(a){this.render();this.$el.appendTo("body").css(this._getShownPosition(a)).show();this._setUpCloseBehavior()},render:function(){this.$el.addClass("popmenu-wrapper").hide().css({position:"absolute"}).html(this.template(this.$button.attr("id"),this.options));if(this.options.length){var a=this;this.$el.find("li").each(function(c,b){var d=a.options[c];if(d.func){$(this).children("a.popupmenu-option").click(function(e){d.func.call(a,e,d)})}})}return this},template:function(b,a){return['<ul id="',b,'-menu" class="dropdown-menu">',this._templateOptions(a),"</ul>"].join("")},_templateOptions:function(a){if(!a.length){return"<li>(no options)</li>"}return _.map(a,function(d){if(d.divider){return'<li class="divider"></li>'}else{if(d.header){return['<li class="head"><a href="javascript:void(0);">',d.html,"</a></li>"].join("")}}var c=d.href||"javascript:void(0);",e=(d.target)?(' target="'+d.target+'"'):(""),b=(d.checked)?('<span class="fa fa-check"></span>'):("");return['<li><a class="popupmenu-option" href="',c,'"',e,">",b,d.html,"</a></li>"].join("")}).join("")},_getShownPosition:function(b){var c=this.$el.width();var a=b.pageX-c/2;a=Math.min(a,$(document).scrollLeft()+$(window).width()-c-5);a=Math.max(a,$(document).scrollLeft()+5);return{top:b.pageY,left:a}},_setUpCloseBehavior:function(){var c=this;function a(e){$(document).off("click.close_popup");if(window.parent!==window){try{$(window.parent.document).off("click.close_popup")}catch(d){}}else{try{$("iframe#galaxy_main").contents().off("click.close_popup")}catch(d){}}c.remove()}$("html").one("click.close_popup",a);if(window.parent!==window){try{$(window.parent.document).find("html").one("click.close_popup",a)}catch(b){}}else{try{$("iframe#galaxy_main").contents().one("click.close_popup",a)}catch(b){}}},addItem:function(b,a){a=(a>=0)?a:this.options.length;this.options.splice(a,0,b);return this},removeItem:function(a){if(a>=0){this.options.splice(a,1)}return this},findIndexByHtml:function(b){for(var a=0;a<this.options.length;a++){if(_.has(this.options[a],"html")&&(this.options[a].html===b)){return a}}return null},findItemByHtml:function(a){return this.options[(this.findIndexByHtml(a))]},toString:function(){return"PopupMenu"}});PopupMenu.create=function _create(b,a){return new PopupMenu(b,a)};PopupMenu.make_popupmenu=function(b,c){var a=[];_.each(c,function(f,d){var e={html:d};if(f===null){e.header=true}else{if(jQuery.type(f)==="function"){e.func=f}}a.push(e)});return new PopupMenu($(b),a)};PopupMenu.convertLinksToOptions=function(c,a){c=$(c);a=a||"a";var b=[];c.find(a).each(function(g,e){var f={},d=$(g);f.html=d.text();if(d.attr("href")){var j=d.attr("href"),k=d.attr("target"),h=d.attr("confirm");f.func=function(){if((h)&&(!confirm(h))){return}switch(k){case"_parent":window.parent.location=j;break;case"_top":window.top.location=j;break;default:window.location=j}}}b.push(f)});return b};PopupMenu.fromExistingDom=function(d,c,a){d=$(d);c=$(c);var b=PopupMenu.convertLinksToOptions(c,a);c.remove();return new PopupMenu(d,b)};PopupMenu.make_popup_menus=function(c,b,d){c=c||document;b=b||"div[popupmenu]";d=d||function(e,f){return"#"+e.attr("popupmenu")};var a=[];$(c).find(b).each(function(){var e=$(this),f=$(c).find(d(e,c));a.push(PopupMenu.fromDom(f,e));f.addClass("popup")});return a};var faIconButton=function(a){a=a||{};a.tooltipConfig=a.tooltipConfig||{placement:"bottom"};a.classes=["icon-btn"].concat(a.classes||[]);if(a.disabled){a.classes.push("disabled")}var b=['<a class="',a.classes.join(" "),'"',((a.title)?(' title="'+a.title+'"'):("")),((!a.disabled&&a.target)?(' target="'+a.target+'"'):("")),' href="',((!a.disabled&&a.href)?(a.href):("javascript:void(0);")),'">','<span class="fa ',a.faIcon,'"></span>',"</a>"].join("");var c=$(b).tooltip(a.tooltipConfig);if(_.isFunction(a.onclick)){c.click(a.onclick)}return c};(function(){function a(j,p){var d=27,m=13,c=$(j),e=true,g={initialVal:"",name:"search",placeholder:"search",classes:"",onclear:function(){},onfirstsearch:null,onsearch:function(q){},minSearchLen:0,escWillClear:true,oninit:function(){}};function i(q){var r=$(this).parent().children("input");r.val("");r.trigger("clear:searchInput");p.onclear()}function o(r,q){$(this).trigger("search:searchInput",q);if(typeof p.onfirstsearch==="function"&&e){e=false;p.onfirstsearch(q)}else{p.onsearch(q)}}function f(){return['<input type="text" name="',p.name,'" placeholder="',p.placeholder,'" ','class="search-query ',p.classes,'" ',"/>"].join("")}function l(){return $(f()).focus(function(q){$(this).select()}).keyup(function(r){if(r.which===d&&p.escWillClear){i.call(this,r)}else{var q=$(this).val();if((r.which===m)||(p.minSearchLen&&q.length>=p.minSearchLen)){o.call(this,r,q)}else{if(!q.length){i.call(this,r)}}}}).val(p.initialVal)}function k(){return $(['<span class="search-clear fa fa-times-circle" ','title="',_l("clear search (esc)"),'"></span>'].join("")).tooltip({placement:"bottom"}).click(function(q){i.call(this,q)})}function n(){return $(['<span class="search-loading fa fa-spinner fa-spin" ','title="',_l("loading..."),'"></span>'].join("")).hide().tooltip({placement:"bottom"})}function h(){c.find(".search-loading").toggle();c.find(".search-clear").toggle()}if(jQuery.type(p)==="string"){if(p==="toggle-loading"){h()}return c}if(jQuery.type(p)==="object"){p=jQuery.extend(true,{},g,p)}return c.addClass("search-input").prepend([l(),k(),n()])}jQuery.fn.extend({searchInput:function b(c){return this.each(function(){return a(this,c)})}})}());(function(){function b(m,l){this.currModeIndex=0;return this.init(m,l)}b.prototype.DATA_KEY="mode-button";b.prototype.defaults={modes:[{mode:"default"}]};b.prototype.init=function f(m,l){l=l||{};this.$element=$(m);this.options=jQuery.extend(true,{},this.defaults,l);var o=this;this.$element.click(function n(p){o.callModeFn();o._incModeIndex();$(this).html(o.options.modes[o.currModeIndex].html)});this.currModeIndex=0;if(this.options.initialMode){this.currModeIndex=this._getModeIndex(this.options.initialMode)}return this};b.prototype._getModeIndex=function j(l){for(var m=0;m<this.options.modes.length;m+=1){if(this.options.modes[m].mode===l){return m}}throw new Error("mode not found: "+l)};b.prototype.getCurrMode=function a(){return this.options.modes[this.currModeIndex]};b.prototype.getMode=function g(l){if(!l){return this.getCurrMode()}return this.options.modes[(this._getModeIndex(l))]};b.prototype.hasMode=function k(l){return !!this.getMode(l)};b.prototype.currentMode=function e(){return this.options.modes[this.currModeIndex]};b.prototype.setMode=function c(m){var l=this.getMode(m);this.$element.html(l.html||null);return this};b.prototype._incModeIndex=function d(){this.currModeIndex+=1;if(this.currModeIndex>=this.options.modes.length){this.currModeIndex=0}return this};b.prototype.callModeFn=function h(l){var m=this.getMode(l).onclick;if(m&&jQuery.type(m==="function")){return m.call(this)}return undefined};jQuery.fn.extend({modeButton:function i(m){var l=jQuery.makeArray(arguments).slice(1);return this.map(function(){var p=$(this),o=p.data("mode-button");if(jQuery.type(m)==="object"){o=new b(p,m);p.data("mode-button",o)}else{if(o&&jQuery.type(m)==="string"){var n=o[m];if(jQuery.type(n)==="function"){return n.apply(o,l)}}else{if(o){return o}}}return this})}})}());function LoadingIndicator(a,c){var b=this;c=jQuery.extend({cover:false},c||{});function d(){var e=['<div class="loading-indicator">','<div class="loading-indicator-text">','<span class="fa fa-spinner fa-spin fa-lg"></span>','<span class="loading-indicator-message">loading...</span>',"</div>","</div>"].join("\n");var g=$(e).hide().css(c.css||{position:"fixed"}),f=g.children(".loading-indicator-text");if(c.cover){g.css({"z-index":2,top:a.css("top"),bottom:a.css("bottom"),left:a.css("left"),right:a.css("right"),opacity:0.5,"background-color":"white","text-align":"center"});f=g.children(".loading-indicator-text").css({"margin-top":"20px"})}else{f=g.children(".loading-indicator-text").css({margin:"12px 0px 0px 10px",opacity:"0.85",color:"grey"});f.children(".loading-indicator-message").css({margin:"0px 8px 0px 0px","font-style":"italic"})}return g}b.show=function(f,e,g){f=f||"loading...";e=e||"fast";b.$indicator=d().insertBefore(a);b.message(f);b.$indicator.fadeIn(e,g);return b};b.message=function(e){b.$indicator.find("i").text(e)};b.hide=function(e,f){e=e||"fast";if(b.$indicator&&b.$indicator.size()){b.$indicator.fadeOut(e,function(){b.$indicator.remove();if(f){f()}})}else{if(f){f()}}return b};return b}function dropDownSelect(b,c){c=c||((!_.isEmpty(b))?(b[0]):(""));var a=$(['<div class="dropdown-select btn-group">','<button type="button" class="btn btn-default">','<span class="dropdown-select-selected">'+c+"</span>","</button>","</div>"].join("\n"));if(b&&b.length>1){a.find("button").addClass("dropdown-toggle").attr("data-toggle","dropdown").append(' <span class="caret"></span>');a.append(['<ul class="dropdown-menu" role="menu">',_.map(b,function(e){return['<li><a href="javascript:void(0)">',e,"</a></li>"].join("")}).join("\n"),"</ul>"].join("\n"))}function d(g){var h=$(this),f=h.parents(".dropdown-select"),e=h.text();f.find(".dropdown-select-selected").text(e);f.trigger("change.dropdown-select",e)}a.find("a").click(d);return a}(function(){function e(k,j){return this.init(k,j)}e.prototype.DATA_KEY="filter-control";e.prototype.init=function g(k,j){j=j||{filters:[]};this.$element=$(k).addClass("filter-control btn-group");this.options=jQuery.extend(true,{},this.defaults,j);this.currFilter=this.options.filters[0];return this.render()};e.prototype.render=function d(){this.$element.empty().append([this._renderKeySelect(),this._renderOpSelect(),this._renderValueInput()]);return this};e.prototype._renderKeySelect=function a(){var j=this;var k=this.options.filters.map(function(l){return l.key});this.$keySelect=dropDownSelect(k,this.currFilter.key).addClass("filter-control-key").on("change.dropdown-select",function(m,l){j.currFilter=_.findWhere(j.options.filters,{key:l});j.render()._triggerChange()});return this.$keySelect};e.prototype._renderOpSelect=function i(){var j=this,k=this.currFilter.ops;this.$opSelect=dropDownSelect(k,k[0]).addClass("filter-control-op").on("change.dropdown-select",function(m,l){j._triggerChange()});return this.$opSelect};e.prototype._renderValueInput=function c(){var j=this;if(this.currFilter.values){this.$valueSelect=dropDownSelect(this.currFilter.values,this.currFilter.values[0]).on("change.dropdown-select",function(l,k){j._triggerChange()})}else{this.$valueSelect=$("<input/>").addClass("form-control").on("change",function(k,l){j._triggerChange()})}this.$valueSelect.addClass("filter-control-value");return this.$valueSelect};e.prototype.val=function b(){var k=this.$element.find(".filter-control-key .dropdown-select-selected").text(),m=this.$element.find(".filter-control-op .dropdown-select-selected").text(),j=this.$element.find(".filter-control-value"),l=(j.hasClass("dropdown-select"))?(j.find(".dropdown-select-selected").text()):(j.val());return{key:k,op:m,value:l}};e.prototype._triggerChange=function h(){this.$element.trigger("change.filter-control",this.val())};jQuery.fn.extend({filterControl:function f(k){var j=jQuery.makeArray(arguments).slice(1);return this.map(function(){var n=$(this),m=n.data(e.prototype.DATA_KEY);if(jQuery.type(k)==="object"){m=new e(n,k);n.data(e.prototype.DATA_KEY,m)}if(m&&jQuery.type(k)==="string"){var l=m[k];if(jQuery.type(l)==="function"){return l.apply(m,j)}}return this})}})}());
\ No newline at end of file
+var IconButton=Backbone.Model.extend({defaults:{title:"",icon_class:"",on_click:null,menu_options:null,is_menu_button:true,id:null,href:null,target:null,enabled:true,visible:true,tooltip_config:{}}});var IconButtonView=Backbone.View.extend({initialize:function(){this.model.attributes.tooltip_config={placement:"bottom"};this.model.bind("change",this.render,this)},render:function(){this.$el.tooltip("hide");var a=this.template(this.model.toJSON());a.tooltip(this.model.get("tooltip_config"));this.$el.replaceWith(a);this.setElement(a);return this},events:{click:"click"},click:function(a){if(_.isFunction(this.model.get("on_click"))){this.model.get("on_click")(a);return false}return true},template:function(b){var a='title="'+b.title+'" class="icon-button';if(b.is_menu_button){a+=" menu-button"}a+=" "+b.icon_class;if(!b.enabled){a+="_disabled"}a+='"';if(b.id){a+=' id="'+b.id+'"'}a+=' href="'+b.href+'"';if(b.target){a+=' target="'+b.target+'"'}if(!b.visible){a+=' style="display: none;"'}if(b.enabled){a="<a "+a+"/>"}else{a="<span "+a+"/>"}return $(a)}});var IconButtonCollection=Backbone.Collection.extend({model:IconButton});var IconButtonMenuView=Backbone.View.extend({tagName:"div",initialize:function(){this.render()},render:function(){var a=this;this.collection.each(function(d){var b=$("<a/>").attr("href","javascript:void(0)").attr("title",d.attributes.title).addClass("icon-button menu-button").addClass(d.attributes.icon_class).appendTo(a.$el).click(d.attributes.on_click);if(d.attributes.tooltip_config){b.tooltip(d.attributes.tooltip_config)}var c=d.get("options");if(c){make_popupmenu(b,c)}});return this}});var create_icon_buttons_menu=function(b,a){if(!a){a={}}var c=new IconButtonCollection(_.map(b,function(d){return new IconButton(_.extend(d,a))}));return new IconButtonMenuView({collection:c})};var Grid=Backbone.Collection.extend({});var GridView=Backbone.View.extend({});var PopupMenu=Backbone.View.extend({initialize:function(b,a){this.$button=b;if(!this.$button.size()){this.$button=$("<div/>")}this.options=a||[];var c=this;this.$button.click(function(d){$(".popmenu-wrapper").remove();c._renderAndShow(d);return false})},_renderAndShow:function(a){this.render();this.$el.appendTo("body").css(this._getShownPosition(a)).show();this._setUpCloseBehavior()},render:function(){this.$el.addClass("popmenu-wrapper").hide().css({position:"absolute"}).html(this.template(this.$button.attr("id"),this.options));if(this.options.length){var a=this;this.$el.find("li").each(function(c,b){var d=a.options[c];if(d.func){$(this).children("a.popupmenu-option").click(function(e){d.func.call(a,e,d)})}})}return this},template:function(b,a){return['<ul id="',b,'-menu" class="dropdown-menu">',this._templateOptions(a),"</ul>"].join("")},_templateOptions:function(a){if(!a.length){return"<li>(no options)</li>"}return _.map(a,function(d){if(d.divider){return'<li class="divider"></li>'}else{if(d.header){return['<li class="head"><a href="javascript:void(0);">',d.html,"</a></li>"].join("")}}var c=d.href||"javascript:void(0);",e=(d.target)?(' target="'+d.target+'"'):(""),b=(d.checked)?('<span class="fa fa-check"></span>'):("");return['<li><a class="popupmenu-option" href="',c,'"',e,">",b,d.html,"</a></li>"].join("")}).join("")},_getShownPosition:function(b){var c=this.$el.width();var a=b.pageX-c/2;a=Math.min(a,$(document).scrollLeft()+$(window).width()-c-5);a=Math.max(a,$(document).scrollLeft()+5);return{top:b.pageY,left:a}},_setUpCloseBehavior:function(){var c=this;function a(e){$(document).off("click.close_popup");if(window.parent!==window){try{$(window.parent.document).off("click.close_popup")}catch(d){}}else{try{$("iframe#galaxy_main").contents().off("click.close_popup")}catch(d){}}c.remove()}$("html").one("click.close_popup",a);if(window.parent!==window){try{$(window.parent.document).find("html").one("click.close_popup",a)}catch(b){}}else{try{$("iframe#galaxy_main").contents().one("click.close_popup",a)}catch(b){}}},addItem:function(b,a){a=(a>=0)?a:this.options.length;this.options.splice(a,0,b);return this},removeItem:function(a){if(a>=0){this.options.splice(a,1)}return this},findIndexByHtml:function(b){for(var a=0;a<this.options.length;a++){if(_.has(this.options[a],"html")&&(this.options[a].html===b)){return a}}return null},findItemByHtml:function(a){return this.options[(this.findIndexByHtml(a))]},toString:function(){return"PopupMenu"}});PopupMenu.create=function _create(b,a){return new PopupMenu(b,a)};PopupMenu.make_popupmenu=function(b,c){var a=[];_.each(c,function(f,d){var e={html:d};if(f===null){e.header=true}else{if(jQuery.type(f)==="function"){e.func=f}}a.push(e)});return new PopupMenu($(b),a)};PopupMenu.convertLinksToOptions=function(c,a){c=$(c);a=a||"a";var b=[];c.find(a).each(function(g,e){var f={},d=$(g);f.html=d.text();if(d.attr("href")){var j=d.attr("href"),k=d.attr("target"),h=d.attr("confirm");f.func=function(){if((h)&&(!confirm(h))){return}switch(k){case"_parent":window.parent.location=j;break;case"_top":window.top.location=j;break;default:window.location=j}}}b.push(f)});return b};PopupMenu.fromExistingDom=function(d,c,a){d=$(d);c=$(c);var b=PopupMenu.convertLinksToOptions(c,a);c.remove();return new PopupMenu(d,b)};PopupMenu.make_popup_menus=function(c,b,d){c=c||document;b=b||"div[popupmenu]";d=d||function(e,f){return"#"+e.attr("popupmenu")};var a=[];$(c).find(b).each(function(){var e=$(this),f=$(c).find(d(e,c));a.push(PopupMenu.fromDom(f,e));f.addClass("popup")});return a};var faIconButton=function(a){a=a||{};a.tooltipConfig=a.tooltipConfig||{placement:"bottom"};a.classes=["icon-btn"].concat(a.classes||[]);if(a.disabled){a.classes.push("disabled")}var b=['<a class="',a.classes.join(" "),'"',((a.title)?(' title="'+a.title+'"'):("")),((!a.disabled&&a.target)?(' target="'+a.target+'"'):("")),' href="',((!a.disabled&&a.href)?(a.href):("javascript:void(0);")),'">','<span class="fa ',a.faIcon,'"></span>',"</a>"].join("");var c=$(b).tooltip(a.tooltipConfig);if(_.isFunction(a.onclick)){c.click(a.onclick)}return c};function LoadingIndicator(a,c){var b=this;c=jQuery.extend({cover:false},c||{});function d(){var e=['<div class="loading-indicator">','<div class="loading-indicator-text">','<span class="fa fa-spinner fa-spin fa-lg"></span>','<span class="loading-indicator-message">loading...</span>',"</div>","</div>"].join("\n");var g=$(e).hide().css(c.css||{position:"fixed"}),f=g.children(".loading-indicator-text");if(c.cover){g.css({"z-index":2,top:a.css("top"),bottom:a.css("bottom"),left:a.css("left"),right:a.css("right"),opacity:0.5,"background-color":"white","text-align":"center"});f=g.children(".loading-indicator-text").css({"margin-top":"20px"})}else{f=g.children(".loading-indicator-text").css({margin:"12px 0px 0px 10px",opacity:"0.85",color:"grey"});f.children(".loading-indicator-message").css({margin:"0px 8px 0px 0px","font-style":"italic"})}return g}b.show=function(f,e,g){f=f||"loading...";e=e||"fast";b.$indicator=d().insertBefore(a);b.message(f);b.$indicator.fadeIn(e,g);return b};b.message=function(e){b.$indicator.find("i").text(e)};b.hide=function(e,f){e=e||"fast";if(b.$indicator&&b.$indicator.size()){b.$indicator.fadeOut(e,function(){b.$indicator.remove();if(f){f()}})}else{if(f){f()}}return b};return b}(function(){function a(j,p){var d=27,m=13,c=$(j),e=true,g={initialVal:"",name:"search",placeholder:"search",classes:"",onclear:function(){},onfirstsearch:null,onsearch:function(q){},minSearchLen:0,escWillClear:true,oninit:function(){}};function i(q){var r=$(this).parent().children("input");r.val("");r.trigger("clear:searchInput");p.onclear()}function o(r,q){$(this).trigger("search:searchInput",q);if(typeof p.onfirstsearch==="function"&&e){e=false;p.onfirstsearch(q)}else{p.onsearch(q)}}function f(){return['<input type="text" name="',p.name,'" placeholder="',p.placeholder,'" ','class="search-query ',p.classes,'" ',"/>"].join("")}function l(){return $(f()).focus(function(q){$(this).select()}).keyup(function(r){if(r.which===d&&p.escWillClear){i.call(this,r)}else{var q=$(this).val();if((r.which===m)||(p.minSearchLen&&q.length>=p.minSearchLen)){o.call(this,r,q)}else{if(!q.length){i.call(this,r)}}}}).val(p.initialVal)}function k(){return $(['<span class="search-clear fa fa-times-circle" ','title="',_l("clear search (esc)"),'"></span>'].join("")).tooltip({placement:"bottom"}).click(function(q){i.call(this,q)})}function n(){return $(['<span class="search-loading fa fa-spinner fa-spin" ','title="',_l("loading..."),'"></span>'].join("")).hide().tooltip({placement:"bottom"})}function h(){c.find(".search-loading").toggle();c.find(".search-clear").toggle()}if(jQuery.type(p)==="string"){if(p==="toggle-loading"){h()}return c}if(jQuery.type(p)==="object"){p=jQuery.extend(true,{},g,p)}return c.addClass("search-input").prepend([l(),k(),n()])}jQuery.fn.extend({searchInput:function b(c){return this.each(function(){return a(this,c)})}})}());(function(){function b(m,l){this.currModeIndex=0;return this.init(m,l)}b.prototype.DATA_KEY="mode-button";b.prototype.defaults={modes:[{mode:"default"}]};b.prototype.init=function f(m,l){l=l||{};this.$element=$(m);this.options=jQuery.extend(true,{},this.defaults,l);var o=this;this.$element.click(function n(p){o.callModeFn();o._incModeIndex();$(this).html(o.options.modes[o.currModeIndex].html)});this.currModeIndex=0;if(this.options.initialMode){this.currModeIndex=this._getModeIndex(this.options.initialMode)}return this};b.prototype._getModeIndex=function j(l){for(var m=0;m<this.options.modes.length;m+=1){if(this.options.modes[m].mode===l){return m}}throw new Error("mode not found: "+l)};b.prototype.getCurrMode=function a(){return this.options.modes[this.currModeIndex]};b.prototype.getMode=function g(l){if(!l){return this.getCurrMode()}return this.options.modes[(this._getModeIndex(l))]};b.prototype.hasMode=function k(l){return !!this.getMode(l)};b.prototype.currentMode=function e(){return this.options.modes[this.currModeIndex]};b.prototype.setMode=function c(m){var l=this.getMode(m);this.$element.html(l.html||null);return this};b.prototype._incModeIndex=function d(){this.currModeIndex+=1;if(this.currModeIndex>=this.options.modes.length){this.currModeIndex=0}return this};b.prototype.callModeFn=function h(l){var m=this.getMode(l).onclick;if(m&&jQuery.type(m==="function")){return m.call(this)}return undefined};jQuery.fn.extend({modeButton:function i(m){var l=jQuery.makeArray(arguments).slice(1);return this.map(function(){var p=$(this),o=p.data("mode-button");if(jQuery.type(m)==="object"){o=new b(p,m);p.data("mode-button",o)}else{if(o&&jQuery.type(m)==="string"){var n=o[m];if(jQuery.type(n)==="function"){return n.apply(o,l)}}else{if(o){return o}}}return this})}})}());function dropDownSelect(b,c){c=c||((!_.isEmpty(b))?(b[0]):(""));var a=$(['<div class="dropdown-select btn-group">','<button type="button" class="btn btn-default">','<span class="dropdown-select-selected">'+c+"</span>","</button>","</div>"].join("\n"));if(b&&b.length>1){a.find("button").addClass("dropdown-toggle").attr("data-toggle","dropdown").append(' <span class="caret"></span>');a.append(['<ul class="dropdown-menu" role="menu">',_.map(b,function(e){return['<li><a href="javascript:void(0)">',e,"</a></li>"].join("")}).join("\n"),"</ul>"].join("\n"))}function d(g){var h=$(this),f=h.parents(".dropdown-select"),e=h.text();f.find(".dropdown-select-selected").text(e);f.trigger("change.dropdown-select",e)}a.find("a").click(d);return a}(function(){function e(k,j){return this.init(k,j)}e.prototype.DATA_KEY="filter-control";e.prototype.init=function g(k,j){j=j||{filters:[]};this.$element=$(k).addClass("filter-control btn-group");this.options=jQuery.extend(true,{},this.defaults,j);this.currFilter=this.options.filters[0];return this.render()};e.prototype.render=function d(){this.$element.empty().append([this._renderKeySelect(),this._renderOpSelect(),this._renderValueInput()]);return this};e.prototype._renderKeySelect=function a(){var j=this;var k=this.options.filters.map(function(l){return l.key});this.$keySelect=dropDownSelect(k,this.currFilter.key).addClass("filter-control-key").on("change.dropdown-select",function(m,l){j.currFilter=_.findWhere(j.options.filters,{key:l});j.render()._triggerChange()});return this.$keySelect};e.prototype._renderOpSelect=function i(){var j=this,k=this.currFilter.ops;this.$opSelect=dropDownSelect(k,k[0]).addClass("filter-control-op").on("change.dropdown-select",function(m,l){j._triggerChange()});return this.$opSelect};e.prototype._renderValueInput=function c(){var j=this;if(this.currFilter.values){this.$valueSelect=dropDownSelect(this.currFilter.values,this.currFilter.values[0]).on("change.dropdown-select",function(l,k){j._triggerChange()})}else{this.$valueSelect=$("<input/>").addClass("form-control").on("change",function(k,l){j._triggerChange()})}this.$valueSelect.addClass("filter-control-value");return this.$valueSelect};e.prototype.val=function b(){var k=this.$element.find(".filter-control-key .dropdown-select-selected").text(),m=this.$element.find(".filter-control-op .dropdown-select-selected").text(),j=this.$element.find(".filter-control-value"),l=(j.hasClass("dropdown-select"))?(j.find(".dropdown-select-selected").text()):(j.val());return{key:k,op:m,value:l}};e.prototype._triggerChange=function h(){this.$element.trigger("change.filter-control",this.val())};jQuery.fn.extend({filterControl:function f(k){var j=jQuery.makeArray(arguments).slice(1);return this.map(function(){var n=$(this),m=n.data(e.prototype.DATA_KEY);if(jQuery.type(k)==="object"){m=new e(n,k);n.data(e.prototype.DATA_KEY,m)}if(m&&jQuery.type(k)==="string"){var l=m[k];if(jQuery.type(l)==="function"){return l.apply(m,j)}}return this})}})}());(function(){function i(o,n){this.numPages=null;this.currPage=0;return this.init(o,n)}i.prototype.DATA_KEY="pagination";i.prototype.defaults={startingPage:0,perPage:20,totalDataSize:null,currDataSize:null};i.prototype.init=function g(n,o){o=o||{};this.$element=n;this.options=jQuery.extend(true,{},this.defaults,o);this.currPage=this.options.startingPage;if(this.options.totalDataSize!==null){this.numPages=Math.ceil(this.options.totalDataSize/this.options.perPage);if(this.currPage>=this.numPages){this.currPage=this.numPages-1}}this.$element.data(i.prototype.DATA_KEY,this);this._render();return this};function m(n){return $(['<li><a href="javascript:void(0);">',n,"</a></li>"].join(""))}i.prototype._render=function e(){if(this.options.totalDataSize===0){return this}if(this.numPages===1){return this}if(this.numPages>0){this._renderPages();this._scrollToActivePage()}else{this._renderPrevNext()}return this};i.prototype._renderPrevNext=function b(){var o=this,p=m("Prev"),n=m("Next"),q=$("<ul/>").addClass("pagination pagination-prev-next");if(this.currPage===0){p.addClass("disabled")}else{p.click(function(){o.prevPage()})}if((this.numPages&&this.currPage===(this.numPages-1))||(this.options.currDataSize&&this.options.currDataSize<this.options.perPage)){n.addClass("disabled")}else{n.click(function(){o.nextPage()})}this.$element.html(q.append([p,n]));return this.$element};i.prototype._renderPages=function a(){var n=this,q=$("<div>").addClass("pagination-scroll-container"),s=$("<ul/>").addClass("pagination pagination-page-list"),r=function(t){n.goToPage($(this).data("page"))};for(var o=0;o<this.numPages;o+=1){var p=m(o+1).attr("data-page",o).click(r);if(o===this.currPage){p.addClass("active")}s.append(p)}return this.$element.html(q.html(s))};i.prototype._scrollToActivePage=function l(){var p=this.$element.find(".pagination-scroll-container");if(!p.size()){return this}var o=this.$element.find("li.active"),n=p.width()/2;p.scrollLeft(p.scrollLeft()+o.position().left-n);return this};i.prototype.goToPage=function j(n){if(n<=0){n=0}if(this.numPages&&n>=this.numPages){n=this.numPages-1}if(n===this.currPage){return this}this.currPage=n;this.$element.trigger("pagination.page-change",this.currPage);this._render();return this};i.prototype.prevPage=function c(){return this.goToPage(this.currPage-1)};i.prototype.nextPage=function h(){return this.goToPage(this.currPage+1)};i.prototype.page=function f(){return this.currPage};i.create=function k(n,o){return new i(n,o)};jQuery.fn.extend({pagination:function d(o){var n=jQuery.makeArray(arguments).slice(1);if(jQuery.type(o)==="object"){return this.map(function(){i.create($(this),o);return this})}var q=$(this[0]),r=q.data(i.prototype.DATA_KEY);if(r){if(jQuery.type(o)==="string"){var p=r[o];if(jQuery.type(p)==="function"){return p.apply(r,n)}}else{return r}}return undefined}})}());
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/packed/templates/compiled/template-visualization-chartControl.js
--- a/static/scripts/packed/templates/compiled/template-visualization-chartControl.js
+++ /dev/null
@@ -1,1 +0,0 @@
-(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-chartControl"]=b(function(f,l,e,k,j){this.compilerInfo=[4,">= 1.0.0"];e=this.merge(e,f.helpers);j=j||{};var h="",c,g="function",i=this.escapeExpression,m=this;function d(o,n){return' checked="true"'}h+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n </p>\n\n <div id="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">';if(c=e.datapointSize){c=c.call(l,{hash:{},data:j})}else{c=l.datapointSize;c=typeof c===g?c.apply(l):c}h+=i(c)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n </div>\n\n <div id="animDuration" class="form-input checkbox-input">\n <label for="animate-chart">Animate chart transitions?: </label>\n <input type="checkbox" id="animate-chart"\n class="checkbox control"';c=e["if"].call(l,l.animDuration,{hash:{},inverse:m.noop,fn:m.program(1,d,j),data:j});if(c||c===0){h+=c}h+=' />\n <p class="form-help help-text-small">\n Uncheck this to disable the animations used on the chart\n </p>\n </div>\n\n <div id="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">';if(c=e.width){c=c.call(l,{hash:{},data:j})}else{c=l.width;c=typeof c===g?c.apply(l):c}h+=i(c)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n </div>\n\n <div id="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">';if(c=e.height){c=c.call(l,{hash:{},data:j})}else{c=l.height;c=typeof c===g?c.apply(l):c}h+=i(c)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n </div>\n\n <div id="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="';if(c=e.xLabel){c=c.call(l,{hash:{},data:j})}else{c=l.xLabel;c=typeof c===g?c.apply(l):c}h+=i(c)+'" />\n <p class="form-help help-text-small"></p>\n </div>\n\n <div id="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="';if(c=e.yLabel){c=c.call(l,{hash:{},data:j})}else{c=l.yLabel;c=typeof c===g?c.apply(l):c}h+=i(c)+'" />\n <p class="form-help help-text-small"></p>\n </div>\n\n <input id="render-button" type="button" value="Draw" />';return h})})();
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/packed/templates/compiled/template-visualization-chartDisplay.js
--- a/static/scripts/packed/templates/compiled/template-visualization-chartDisplay.js
+++ /dev/null
@@ -1,1 +0,0 @@
-(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-chartDisplay"]=b(function(e,k,d,j,i){this.compilerInfo=[4,">= 1.0.0"];d=this.merge(d,e.helpers);i=i||{};var g="",c,f="function",h=this.escapeExpression;g+='<svg width="';if(c=d.width){c=c.call(k,{hash:{},data:i})}else{c=k.width;c=typeof c===f?c.apply(k):c}g+=h(c)+'" height="';if(c=d.height){c=c.call(k,{hash:{},data:i})}else{c=k.height;c=typeof c===f?c.apply(k):c}g+=h(c)+'"></svg>';return g})})();
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/packed/templates/compiled/template-visualization-dataControl.js
--- a/static/scripts/packed/templates/compiled/template-visualization-dataControl.js
+++ /dev/null
@@ -1,1 +0,0 @@
-(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-dataControl"]=b(function(g,m,f,l,k){this.compilerInfo=[4,">= 1.0.0"];f=this.merge(f,g.helpers);k=k||{};var i="",d,h="function",j=this.escapeExpression,n=this;function e(r,q){var o="",p;o+='\n <option value="';if(p=f.index){p=p.call(r,{hash:{},data:q})}else{p=r.index;p=typeof p===h?p.apply(r):p}o+=j(p)+'">';if(p=f.name){p=p.call(r,{hash:{},data:q})}else{p=r.name;p=typeof p===h?p.apply(r):p}o+=j(p)+"</option>\n ";return o}function c(p,o){return'checked="true"'}i+='<p class="help-text">\n Use the following controls to change the data used by the chart.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n </p>\n\n \n <div class="column-select">\n <label for="X-select">Data column for X: </label>\n <select name="X" id="X-select">\n ';d=f.each.call(m,m.numericColumns,{hash:{},inverse:n.noop,fn:n.program(1,e,k),data:k});if(d||d===0){i+=d}i+='\n </select>\n </div>\n <div class="column-select">\n <label for="Y-select">Data column for Y: </label>\n <select name="Y" id="Y-select">\n ';d=f.each.call(m,m.numericColumns,{hash:{},inverse:n.noop,fn:n.program(1,e,k),data:k});if(d||d===0){i+=d}i+='\n </select>\n </div>\n\n \n <div id="include-id">\n <label for="include-id-checkbox">Include a third column as data point IDs?</label>\n <input type="checkbox" name="include-id" id="include-id-checkbox" />\n <p class="help-text-small">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n </div>\n <div class="column-select" style="display: none">\n <label for="ID-select">Data column for IDs: </label>\n <select name="ID" id="ID-select">\n ';d=f.each.call(m,m.allColumns,{hash:{},inverse:n.noop,fn:n.program(1,e,k),data:k});if(d||d===0){i+=d}i+='\n </select>\n </div>\n\n \n <div id="first-line-header" style="display: none;">\n <p>Possible headers: ';if(d=f.possibleHeaders){d=d.call(m,{hash:{},data:k})}else{d=m.possibleHeaders;d=typeof d===h?d.apply(m):d}i+=j(d)+'\n </p>\n <label for="first-line-header-checkbox">Use the above as column headers?</label>\n <input type="checkbox" name="include-id" id="first-line-header-checkbox"\n ';d=f["if"].call(m,m.usePossibleHeaders,{hash:{},inverse:n.noop,fn:n.program(3,c,k),data:k});if(d||d===0){i+=d}i+='/>\n <p class="help-text-small">\n It looks like Galaxy couldn\'t get proper column headers for this data.\n Would you like to use the column headers above as column names to select columns?\n </p>\n </div>\n\n <input id="render-button" type="button" value="Draw" />\n <div class="clear"></div>';return i})})();
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/packed/templates/compiled/template-visualization-scatterplotControlForm.js
--- a/static/scripts/packed/templates/compiled/template-visualization-scatterplotControlForm.js
+++ /dev/null
@@ -1,1 +0,0 @@
-(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-scatterplotControlForm"]=b(function(e,k,d,j,i){this.compilerInfo=[4,">= 1.0.0"];d=this.merge(d,e.helpers);i=i||{};var g="",c,f="function",h=this.escapeExpression;g+='\n\n<div class="scatterplot-container chart-container tabbable tabs-left">\n \n <ul class="nav nav-tabs">\n \n <li class="active"><a href="#data-control" data-toggle="tab"\n title="Use this tab to change which data are used">Data Controls</a></li>\n <li><a href="#chart-control" data-toggle="tab"\n title="Use this tab to change how the chart is drawn">Chart Controls</a></li>\n <li><a href="#stats-display" data-toggle="tab"\n title="This tab will display overall statistics for your data">Statistics</a></li>\n <li><a href="#chart-display" data-toggle="tab"\n title="This tab will display the chart">Chart</a>\n \n <div id="loading-indicator" style="display: none;">\n <img class="loading-img" src="';if(c=d.loadingIndicatorImagePath){c=c.call(k,{hash:{},data:i})}else{c=k.loadingIndicatorImagePath;c=typeof c===f?c.apply(k):c}g+=h(c)+'" />\n <span class="loading-message">';if(c=d.message){c=c.call(k,{hash:{},data:i})}else{c=k.message;c=typeof c===f?c.apply(k):c}g+=h(c)+'</span>\n </div>\n </li>\n </ul>\n\n \n <div class="tab-content">\n \n <div id="data-control" class="tab-pane active">\n \n </div>\n \n \n <div id="chart-control" class="tab-pane">\n \n </div>\n\n \n <div id="stats-display" class="tab-pane">\n \n </div>\n\n \n <div id="chart-display" class="tab-pane">\n \n </div>\n\n </div>\n</div>';return g})})();
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/packed/templates/compiled/template-visualization-statsDisplay.js
--- a/static/scripts/packed/templates/compiled/template-visualization-statsDisplay.js
+++ /dev/null
@@ -1,1 +0,0 @@
-(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-statsDisplay"]=b(function(f,l,e,k,j){this.compilerInfo=[4,">= 1.0.0"];e=this.merge(e,f.helpers);j=j||{};var h="",c,g="function",i=this.escapeExpression,m=this;function d(q,p){var n="",o;n+="\n <tr><td>";if(o=e.name){o=o.call(q,{hash:{},data:p})}else{o=q.name;o=typeof o===g?o.apply(q):o}n+=i(o)+"</td><td>";if(o=e.xval){o=o.call(q,{hash:{},data:p})}else{o=q.xval;o=typeof o===g?o.apply(q):o}n+=i(o)+"</td><td>";if(o=e.yval){o=o.call(q,{hash:{},data:p})}else{o=q.yval;o=typeof o===g?o.apply(q):o}n+=i(o)+"</td></tr>\n </tr>\n ";return n}h+='<p class="help-text">By column:</p>\n <table id="chart-stats-table">\n <thead><th></th><th>X</th><th>Y</th></thead>\n ';c=e.each.call(l,l.stats,{hash:{},inverse:m.noop,fn:m.program(1,d,j),data:j});if(c||c===0){h+=c}h+="\n </table>";return h})})();
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/packed/viz/scatterplot.js
--- a/static/scripts/packed/viz/scatterplot.js
+++ /dev/null
@@ -1,1 +0,0 @@
-function TwoVarScatterplot(d){var b=10,f=7,e=10,c=8,a=5;this.log=function(){if(this.debugging&&console&&console.debug){var g=Array.prototype.slice.call(arguments);g.unshift(this.toString());console.debug.apply(console,g)}};this.log("new TwoVarScatterplot:",d);this.defaults={id:"TwoVarScatterplot",containerSelector:"body",maxDataPoints:30000,datapointSize:4,animDuration:500,xNumTicks:10,yNumTicks:10,xAxisLabelBumpY:40,yAxisLabelBumpX:-40,width:400,height:400,marginTop:50,marginRight:50,marginBottom:50,marginLeft:50,xMin:null,xMax:null,yMin:null,yMax:null,xLabel:"X",yLabel:"Y"};this.config=_.extend({},this.defaults,d);this.log("intial config:",this.config);this.updateConfig=function(g,h){_.extend(this.config,g);this.log(this+".updateConfig:",this.config)};this.toString=function(){return this.config.id};this.translateStr=function(g,h){return"translate("+g+","+h+")"};this.rotateStr=function(h,g,i){return"rotate("+h+","+g+","+i+")"};this.adjustChartDimensions=function(j,h,g,i){j=j||0;h=h||0;g=g||0;i=i||0;this.svg.attr("width",this.config.width+(this.config.marginRight+h)+(this.config.marginLeft+i)).attr("height",this.config.height+(this.config.marginTop+j)+(this.config.marginBottom+g)).style("display","block");this.content=this.svg.select("g.content").attr("transform",this.translateStr(this.config.marginLeft+i,this.config.marginTop+j))};this.preprocessData=function(i,h,g){return(i.length>this.config.maxDataPoints)?(i.slice(0,this.config.maxDataPoints)):(i)};this.findMinMaxes=function(g,i,h){this.xMin=this.config.xMin||(h)?(h[0].min):(d3.min(g));this.xMax=this.config.xMax||(h)?(h[0].max):(d3.max(g));this.yMin=this.config.yMin||(h)?(h[1].min):(d3.min(i));this.yMax=this.config.yMax||(h)?(h[1].max):(d3.max(i))};this.setUpScales=function(){this.xScale=d3.scale.linear().domain([this.xMin,this.xMax]).range([0,this.config.width]),this.yScale=d3.scale.linear().domain([this.yMin,this.yMax]).range([this.config.height,0])};this.setUpXAxis=function(){this.xAxisFn=d3.svg.axis().scale(this.xScale).ticks(this.config.xNumTicks).orient("bottom");this.xAxis.attr("transform",this.translateStr(0,this.config.height)).call(this.xAxisFn);var g=d3.max(_.map([this.xMin,this.xMax],function(h){return(String(h)).length}));if(g>=a){this.xAxis.selectAll("g").filter(":nth-child(odd)").style("display","none")}this.log("this.config.xLabel:",this.config.xLabel);this.xAxisLabel.attr("x",this.config.width/2).attr("y",this.config.xAxisLabelBumpY).attr("text-anchor","middle").text(this.config.xLabel);this.log("xAxisLabel:",this.xAxisLabel)};this.setUpYAxis=function(){this.yAxisFn=d3.svg.axis().scale(this.yScale).ticks(this.config.yNumTicks).orient("left");this.yAxis.call(this.yAxisFn);var g=this.yAxis.selectAll("text").filter(function(k,j){return j!==0});this.log("yTickLabels:",g);this.yLongestLabel=d3.max(g[0].map(function(k,j){return(d3.select(k).text()).length}))||0;var h=b+(this.yLongestLabel*f)+c+e;this.config.yAxisLabelBumpX=-(h-e);if(this.config.marginLeft<h){var i=(h)-this.config.marginLeft;i=(i<0)?(0):(i);this.adjustChartDimensions(0,0,0,i)}this.yAxisLabel.attr("x",this.config.yAxisLabelBumpX).attr("y",this.config.height/2).attr("text-anchor","middle").attr("transform",this.rotateStr(-90,this.config.yAxisLabelBumpX,this.config.height/2)).text(this.config.yLabel)};this.renderGrid=function(){this.vGridLines=this.content.selectAll("line.v-grid-line").data(this.xScale.ticks(this.xAxisFn.ticks()[0]));this.vGridLines.enter().append("svg:line").classed("grid-line v-grid-line",true);this.vGridLines.attr("x1",this.xScale).attr("y1",0).attr("x2",this.xScale).attr("y2",this.config.height);this.vGridLines.exit().remove();this.hGridLines=this.content.selectAll("line.h-grid-line").data(this.yScale.ticks(this.yAxisFn.ticks()[0]));this.hGridLines.enter().append("svg:line").classed("grid-line h-grid-line",true);this.hGridLines.attr("x1",0).attr("y1",this.yScale).attr("x2",this.config.width).attr("y2",this.yScale);this.hGridLines.exit().remove()};this.renderDatapoints=function(g,n,j){this.log(this+".renderDatapoints",arguments);var k=0,m=this,i=function(p,o){return m.xScale(g[o])},h=function(p,o){return m.yScale(n[o])};var l=this.content.selectAll(".glyph").data(g);k=0;l.enter().append("svg:circle").each(function(){k+=1}).classed("glyph",true).attr("cx",0).attr("cy",this.config.height).attr("r",0);this.log(k," new glyphs created");k=0;l.transition().duration(this.config.animDuration).each(function(){k+=1}).attr("cx",i).attr("cy",h).attr("r",m.config.datapointSize);this.log(k," existing glyphs transitioned");l.exit().each(function(){k+=1}).transition().duration(this.config.animDuration).attr("cy",this.config.height).attr("r",0).remove();this.log(k," glyphs removed");this._addDatapointEventhandlers(l,g,n,j)};this._addDatapointEventhandlers=function(j,g,k,h){var i=this;j.on("mouseover",function(o,l){var n=d3.select(this);n.style("fill","red").style("fill-opacity",1);i.content.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",n.attr("cx")-i.config.datapointSize).attr("y1",n.attr("cy")).attr("x2",0).attr("y2",n.attr("cy")).classed("hoverline",true);if(n.attr("cy")<i.config.height){i.content.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",n.attr("cx")).attr("y1",n.attr("cy")+i.config.datapointSize).attr("x2",n.attr("cx")).attr("y2",i.config.height).classed("hoverline",true)}var m=$(this).offset();i.datapointInfoBox=i.infoBox(m.top,m.left,i.infoHtml(g[l],k[l],(h)?(h[l]):(undefined)));$("body").append(i.datapointInfoBox)}).on("mouseout",function(){d3.select(this).style("fill","black").style("fill-opacity",0.2);i.content.selectAll(".hoverline").remove();if(i.datapointInfoBox){i.datapointInfoBox.remove()}})},this.render=function(i,j){this.log(this+".render",arguments);this.log("\t config:",this.config);var g=i[0],k=i[1],h=(i.length>2)?(i[2]):(undefined);g=this.preprocessData(g);k=this.preprocessData(k);this.log("xCol len",g.length,"yCol len",k.length);this.findMinMaxes(g,k,j);this.setUpScales();if(!this.svg){this.svg=d3.select("svg").attr("class","chart")}if(!this.content){this.content=this.svg.append("svg:g").attr("class","content").attr("id",this.config.id)}this.adjustChartDimensions();if(!this.xAxis){this.xAxis=this.content.append("g").attr("class","axis").attr("id","x-axis")}if(!this.xAxisLabel){this.xAxisLabel=this.xAxis.append("text").attr("class","axis-label").attr("id","x-axis-label")}if(!this.yAxis){this.yAxis=this.content.append("g").attr("class","axis").attr("id","y-axis")}if(!this.yAxisLabel){this.yAxisLabel=this.yAxis.append("text").attr("class","axis-label").attr("id","y-axis-label")}this.setUpXAxis();this.setUpYAxis();this.renderGrid();this.renderDatapoints(g,k,h)};this.infoHtml=function(g,j,i){var h=$("<div/>");if(i){$("<div/>").text(i).css("font-weight","bold").appendTo(h)}$("<div/>").text(g).appendTo(h);$("<div/>").text(j).appendTo(h);return h.html()};this.infoBox=function(l,k,i,h,g){h=h||0;g=g||20;var j=$("<div />").addClass("chart-info-box").css({position:"absolute",top:l+h,left:k+g});j.html(i);return j}};
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/templates/compiled/template-visualization-chartControl.js
--- a/static/scripts/templates/compiled/template-visualization-chartControl.js
+++ /dev/null
@@ -1,40 +0,0 @@
-(function() {
- var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
-templates['template-visualization-chartControl'] = template(function (Handlebars,depth0,helpers,partials,data) {
- this.compilerInfo = [4,'>= 1.0.0'];
-helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
- var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
-
-function program1(depth0,data) {
-
-
- return " checked=\"true\"";
- }
-
- buffer += "<p class=\"help-text\">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the 'handle' is in focus, your keyboard's arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the 'Draw' button to render (or re-render) the chart with the current settings.\n </p>\n\n <div id=\"datapointSize\" class=\"form-input numeric-slider-input\">\n <label for=\"datapointSize\">Size of data point: </label>\n <div class=\"slider-output\">";
- if (stack1 = helpers.datapointSize) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.datapointSize; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n Size of the graphic representation of each data point\n </p>\n </div>\n\n <div id=\"animDuration\" class=\"form-input checkbox-input\">\n <label for=\"animate-chart\">Animate chart transitions?: </label>\n <input type=\"checkbox\" id=\"animate-chart\"\n class=\"checkbox control\"";
- stack1 = helpers['if'].call(depth0, depth0.animDuration, {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
- if(stack1 || stack1 === 0) { buffer += stack1; }
- buffer += " />\n <p class=\"form-help help-text-small\">\n Uncheck this to disable the animations used on the chart\n </p>\n </div>\n\n <div id=\"width\" class=\"form-input numeric-slider-input\">\n <label for=\"width\">Chart width: </label>\n <div class=\"slider-output\">";
- if (stack1 = helpers.width) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.width; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n (not including chart margins and axes)\n </p>\n </div>\n\n <div id=\"height\" class=\"form-input numeric-slider-input\">\n <label for=\"height\">Chart height: </label>\n <div class=\"slider-output\">";
- if (stack1 = helpers.height) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.height; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "</div>\n <div class=\"slider\"></div>\n <p class=\"form-help help-text-small\">\n (not including chart margins and axes)\n </p>\n </div>\n\n <div id=\"X-axis-label\"class=\"text-input form-input\">\n <label for=\"X-axis-label\">Re-label the X axis: </label>\n <input type=\"text\" name=\"X-axis-label\" id=\"X-axis-label\" value=\"";
- if (stack1 = helpers.xLabel) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.xLabel; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "\" />\n <p class=\"form-help help-text-small\"></p>\n </div>\n\n <div id=\"Y-axis-label\" class=\"text-input form-input\">\n <label for=\"Y-axis-label\">Re-label the Y axis: </label>\n <input type=\"text\" name=\"Y-axis-label\" id=\"Y-axis-label\" value=\"";
- if (stack1 = helpers.yLabel) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.yLabel; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "\" />\n <p class=\"form-help help-text-small\"></p>\n </div>\n\n <input id=\"render-button\" type=\"button\" value=\"Draw\" />";
- return buffer;
- });
-})();
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/templates/compiled/template-visualization-chartDisplay.js
--- a/static/scripts/templates/compiled/template-visualization-chartDisplay.js
+++ /dev/null
@@ -1,20 +0,0 @@
-(function() {
- var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
-templates['template-visualization-chartDisplay'] = template(function (Handlebars,depth0,helpers,partials,data) {
- this.compilerInfo = [4,'>= 1.0.0'];
-helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
- var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression;
-
-
- buffer += "<svg width=\"";
- if (stack1 = helpers.width) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.width; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "\" height=\"";
- if (stack1 = helpers.height) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.height; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "\"></svg>";
- return buffer;
- });
-})();
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/templates/compiled/template-visualization-dataControl.js
--- a/static/scripts/templates/compiled/template-visualization-dataControl.js
+++ /dev/null
@@ -1,51 +0,0 @@
-(function() {
- var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
-templates['template-visualization-dataControl'] = template(function (Handlebars,depth0,helpers,partials,data) {
- this.compilerInfo = [4,'>= 1.0.0'];
-helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
- var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
-
-function program1(depth0,data) {
-
- var buffer = "", stack1;
- buffer += "\n <option value=\"";
- if (stack1 = helpers.index) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.index; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "\">";
- if (stack1 = helpers.name) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "</option>\n ";
- return buffer;
- }
-
-function program3(depth0,data) {
-
-
- return "checked=\"true\"";
- }
-
- buffer += "<p class=\"help-text\">\n Use the following controls to change the data used by the chart.\n Use the 'Draw' button to render (or re-render) the chart with the current settings.\n </p>\n\n "
- + "\n <div class=\"column-select\">\n <label for=\"X-select\">Data column for X: </label>\n <select name=\"X\" id=\"X-select\">\n ";
- stack1 = helpers.each.call(depth0, depth0.numericColumns, {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
- if(stack1 || stack1 === 0) { buffer += stack1; }
- buffer += "\n </select>\n </div>\n <div class=\"column-select\">\n <label for=\"Y-select\">Data column for Y: </label>\n <select name=\"Y\" id=\"Y-select\">\n ";
- stack1 = helpers.each.call(depth0, depth0.numericColumns, {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
- if(stack1 || stack1 === 0) { buffer += stack1; }
- buffer += "\n </select>\n </div>\n\n "
- + "\n <div id=\"include-id\">\n <label for=\"include-id-checkbox\">Include a third column as data point IDs?</label>\n <input type=\"checkbox\" name=\"include-id\" id=\"include-id-checkbox\" />\n <p class=\"help-text-small\">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n </div>\n <div class=\"column-select\" style=\"display: none\">\n <label for=\"ID-select\">Data column for IDs: </label>\n <select name=\"ID\" id=\"ID-select\">\n ";
- stack1 = helpers.each.call(depth0, depth0.allColumns, {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
- if(stack1 || stack1 === 0) { buffer += stack1; }
- buffer += "\n </select>\n </div>\n\n "
- + "\n <div id=\"first-line-header\" style=\"display: none;\">\n <p>Possible headers: ";
- if (stack1 = helpers.possibleHeaders) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.possibleHeaders; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "\n </p>\n <label for=\"first-line-header-checkbox\">Use the above as column headers?</label>\n <input type=\"checkbox\" name=\"include-id\" id=\"first-line-header-checkbox\"\n ";
- stack1 = helpers['if'].call(depth0, depth0.usePossibleHeaders, {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
- if(stack1 || stack1 === 0) { buffer += stack1; }
- buffer += "/>\n <p class=\"help-text-small\">\n It looks like Galaxy couldn't get proper column headers for this data.\n Would you like to use the column headers above as column names to select columns?\n </p>\n </div>\n\n <input id=\"render-button\" type=\"button\" value=\"Draw\" />\n <div class=\"clear\"></div>";
- return buffer;
- });
-})();
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/templates/compiled/template-visualization-scatterplotControlForm.js
--- a/static/scripts/templates/compiled/template-visualization-scatterplotControlForm.js
+++ /dev/null
@@ -1,33 +0,0 @@
-(function() {
- var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
-templates['template-visualization-scatterplotControlForm'] = template(function (Handlebars,depth0,helpers,partials,data) {
- this.compilerInfo = [4,'>= 1.0.0'];
-helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
- var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression;
-
-
- buffer += "\n\n<div class=\"scatterplot-container chart-container tabbable tabs-left\">\n "
- + "\n <ul class=\"nav nav-tabs\">\n "
- + "\n <li class=\"active\"><a href=\"#data-control\" data-toggle=\"tab\"\n title=\"Use this tab to change which data are used\">Data Controls</a></li>\n <li><a href=\"#chart-control\" data-toggle=\"tab\"\n title=\"Use this tab to change how the chart is drawn\">Chart Controls</a></li>\n <li><a href=\"#stats-display\" data-toggle=\"tab\"\n title=\"This tab will display overall statistics for your data\">Statistics</a></li>\n <li><a href=\"#chart-display\" data-toggle=\"tab\"\n title=\"This tab will display the chart\">Chart</a>\n "
- + "\n <div id=\"loading-indicator\" style=\"display: none;\">\n <img class=\"loading-img\" src=\"";
- if (stack1 = helpers.loadingIndicatorImagePath) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.loadingIndicatorImagePath; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "\" />\n <span class=\"loading-message\">";
- if (stack1 = helpers.message) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.message; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "</span>\n </div>\n </li>\n </ul>\n\n "
- + "\n <div class=\"tab-content\">\n "
- + "\n <div id=\"data-control\" class=\"tab-pane active\">\n "
- + "\n </div>\n \n "
- + "\n <div id=\"chart-control\" class=\"tab-pane\">\n "
- + "\n </div>\n\n "
- + "\n <div id=\"stats-display\" class=\"tab-pane\">\n "
- + "\n </div>\n\n "
- + "\n <div id=\"chart-display\" class=\"tab-pane\">\n "
- + "\n </div>\n\n </div>"
- + "\n</div>";
- return buffer;
- });
-})();
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/templates/compiled/template-visualization-statsDisplay.js
--- a/static/scripts/templates/compiled/template-visualization-statsDisplay.js
+++ /dev/null
@@ -1,33 +0,0 @@
-(function() {
- var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
-templates['template-visualization-statsDisplay'] = template(function (Handlebars,depth0,helpers,partials,data) {
- this.compilerInfo = [4,'>= 1.0.0'];
-helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
- var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
-
-function program1(depth0,data) {
-
- var buffer = "", stack1;
- buffer += "\n <tr><td>";
- if (stack1 = helpers.name) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "</td><td>";
- if (stack1 = helpers.xval) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.xval; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "</td><td>";
- if (stack1 = helpers.yval) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
- else { stack1 = depth0.yval; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
- buffer += escapeExpression(stack1)
- + "</td></tr>\n </tr>\n ";
- return buffer;
- }
-
- buffer += "<p class=\"help-text\">By column:</p>\n <table id=\"chart-stats-table\">\n <thead><th></th><th>X</th><th>Y</th></thead>\n ";
- stack1 = helpers.each.call(depth0, depth0.stats, {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
- if(stack1 || stack1 === 0) { buffer += stack1; }
- buffer += "\n </table>";
- return buffer;
- });
-})();
\ No newline at end of file
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/templates/visualization-templates.html
--- a/static/scripts/templates/visualization-templates.html
+++ /dev/null
@@ -1,182 +0,0 @@
-<script type="text/template" class="template-visualization" id="template-visualization-scatterplotControlForm">
-{{! main layout }}
-
-<div class="scatterplot-container chart-container tabbable tabs-left">
- {{! tab buttons/headers using Bootstrap }}
- <ul class="nav nav-tabs">
- {{! start with the data controls as the displayed tab }}
- <li class="active"><a href="#data-control" data-toggle="tab"
- title="Use this tab to change which data are used">Data Controls</a></li>
- <li><a href="#chart-control" data-toggle="tab"
- title="Use this tab to change how the chart is drawn">Chart Controls</a></li>
- <li><a href="#stats-display" data-toggle="tab"
- title="This tab will display overall statistics for your data">Statistics</a></li>
- <li><a href="#chart-display" data-toggle="tab"
- title="This tab will display the chart">Chart</a>
- {{! loading indicator - initially hidden }}
- <div id="loading-indicator" style="display: none;">
- <img class="loading-img" src="{{loadingIndicatorImagePath}}" />
- <span class="loading-message">{{message}}</span>
- </div>
- </li>
- </ul>
-
- {{! data form, chart config form, stats, and chart all get their own tab }}
- <div class="tab-content">
- {{! ---------------------------- tab for data settings form }}
- <div id="data-control" class="tab-pane active">
- {{! rendered separately }}
- </div>
-
- {{! ---------------------------- tab for chart graphics control form }}
- <div id="chart-control" class="tab-pane">
- {{! rendered separately }}
- </div>
-
- {{! ---------------------------- tab for data statistics }}
- <div id="stats-display" class="tab-pane">
- {{! rendered separately }}
- </div>
-
- {{! ---------------------------- tab for actual chart }}
- <div id="chart-display" class="tab-pane">
- {{! chart rendered separately }}
- </div>
-
- </div>{{! end .tab-content }}
-</div>{{! end .chart-control }}
-</script>
-
-<script type="text/template" class="template-visualization" id="template-visualization-dataControl">
-
- <p class="help-text">
- Use the following controls to change the data used by the chart.
- Use the 'Draw' button to render (or re-render) the chart with the current settings.
- </p>
-
- {{! column selector containers }}
- <div class="column-select">
- <label for="X-select">Data column for X: </label>
- <select name="X" id="X-select">
- {{#each numericColumns}}
- <option value="{{index}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
- <div class="column-select">
- <label for="Y-select">Data column for Y: </label>
- <select name="Y" id="Y-select">
- {{#each numericColumns}}
- <option value="{{index}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
-
- {{! optional id column }}
- <div id="include-id">
- <label for="include-id-checkbox">Include a third column as data point IDs?</label>
- <input type="checkbox" name="include-id" id="include-id-checkbox" />
- <p class="help-text-small">
- These will be displayed (along with the x and y values) when you hover over
- a data point.
- </p>
- </div>
- <div class="column-select" style="display: none">
- <label for="ID-select">Data column for IDs: </label>
- <select name="ID" id="ID-select">
- {{#each allColumns}}
- <option value="{{index}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
-
- {{! if we're using generic column selection names ('column 1') - allow the user to use the first line }}
- <div id="first-line-header" style="display: none;">
- <p>Possible headers: {{ possibleHeaders }}
- </p>
- <label for="first-line-header-checkbox">Use the above as column headers?</label>
- <input type="checkbox" name="include-id" id="first-line-header-checkbox"
- {{#if usePossibleHeaders }}checked="true"{{/if}}/>
- <p class="help-text-small">
- It looks like Galaxy couldn't get proper column headers for this data.
- Would you like to use the column headers above as column names to select columns?
- </p>
- </div>
-
- <input id="render-button" type="button" value="Draw" />
- <div class="clear"></div>
-</script>
-
-<script type="text/template" class="template-visualization" id="template-visualization-chartControl">
- <p class="help-text">
- Use the following controls to how the chart is displayed.
- The slide controls can be moved by the mouse or, if the 'handle' is in focus, your keyboard's arrow keys.
- Move the focus between controls by using the tab or shift+tab keys on your keyboard.
- Use the 'Draw' button to render (or re-render) the chart with the current settings.
- </p>
-
- <div id="datapointSize" class="form-input numeric-slider-input">
- <label for="datapointSize">Size of data point: </label>
- <div class="slider-output">{{datapointSize}}</div>
- <div class="slider"></div>
- <p class="form-help help-text-small">
- Size of the graphic representation of each data point
- </p>
- </div>
-
- <div id="animDuration" class="form-input checkbox-input">
- <label for="animate-chart">Animate chart transitions?: </label>
- <input type="checkbox" id="animate-chart"
- class="checkbox control"{{#if animDuration}} checked="true"{{/if}} />
- <p class="form-help help-text-small">
- Uncheck this to disable the animations used on the chart
- </p>
- </div>
-
- <div id="width" class="form-input numeric-slider-input">
- <label for="width">Chart width: </label>
- <div class="slider-output">{{width}}</div>
- <div class="slider"></div>
- <p class="form-help help-text-small">
- (not including chart margins and axes)
- </p>
- </div>
-
- <div id="height" class="form-input numeric-slider-input">
- <label for="height">Chart height: </label>
- <div class="slider-output">{{height}}</div>
- <div class="slider"></div>
- <p class="form-help help-text-small">
- (not including chart margins and axes)
- </p>
- </div>
-
- <div id="X-axis-label"class="text-input form-input">
- <label for="X-axis-label">Re-label the X axis: </label>
- <input type="text" name="X-axis-label" id="X-axis-label" value="{{xLabel}}" />
- <p class="form-help help-text-small"></p>
- </div>
-
- <div id="Y-axis-label" class="text-input form-input">
- <label for="Y-axis-label">Re-label the Y axis: </label>
- <input type="text" name="Y-axis-label" id="Y-axis-label" value="{{yLabel}}" />
- <p class="form-help help-text-small"></p>
- </div>
-
- <input id="render-button" type="button" value="Draw" />
-</script>
-
-<script type="text/template" class="template-visualization" id="template-visualization-statsDisplay">
- <p class="help-text">By column:</p>
- <table id="chart-stats-table">
- <thead><th></th><th>X</th><th>Y</th></thead>
- {{#each stats}}
- <tr><td>{{name}}</td><td>{{xval}}</td><td>{{yval}}</td></tr>
- </tr>
- {{/each}}
- </table>
-</script>
-
-<script type="text/template" class="template-visualization" id="template-visualization-chartDisplay">
- <svg width="{{width}}" height="{{height}}"></svg>
-</script>
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a static/scripts/viz/scatterplot.js
--- a/static/scripts/viz/scatterplot.js
+++ /dev/null
@@ -1,488 +0,0 @@
-/* =============================================================================
-todo:
- outside this:
- BUG: setting width, height in plot controls doesn't re-interpolate data locations!!
- BUG?: get metadata_column_names (from datatype if necessary)
- BUG: single vis in popupmenu should have tooltip with that name NOT 'Visualizations'
-
- wire label setters, anim setter
-
- TwoVarScatterplot:
- ??: maybe better to do this with a canvas...
- save as visualization
- to seperate file?
- remove underscore dependencies
- add interface to change values (seperate)?
- download svg -> base64 encode
- incorporate glyphs, glyph state renderers
-
- ScatterplotSettingsForm:
- some css bug that lowers the width of settings form when plot-controls tab is open
- causes chart to shift
- what can be abstracted/reused for other graphs?
- avoid direct manipulation of this.plot
- allow option to put plot into seperate tab of interface (for small multiples)
-
- provide callback in view to load data incrementally - for large sets
- paginate
- handle rerender
- use endpoint (here and on the server (fileptr))
- fetch (new?) data
- handle rerender
- use d3.TSV?
- render warning on long data (> maxDataPoints)
- adjust endpoint
-
- selectable list of preset column comparisons (rnaseq etc.)
- how to know what sort of Tabular the data is?
- smarter about headers
- validate columns selection (here or server)
-
- set stats column names by selected columns
- move chart into tabbed area...
-
- Scatterplot.mako:
- multiple plots on one page (small multiples)
- ?? ensure svg styles thru d3 or css?
- d3: configable (easily)
- css: standard - better maintenance
- ? override at config
-
-============================================================================= */
-/**
- * Two Variable scatterplot visualization using d3
- * Uses semi transparent circles to show density of data in x, y grid
- * usage :
- * var plot = new TwoVarScatterplot({ containerSelector : 'div#my-plot', ... })
- * plot.render( xColumnData, yColumnData );
- *
- * depends on: d3, underscore
- */
-function TwoVarScatterplot( config ){
- var TICK_LINE_AND_PADDING = 10,
- GUESS_AT_SVG_CHAR_WIDTH = 7,
- GUESS_AT_SVG_CHAR_HEIGHT = 10,
- PADDING = 8,
- X_LABEL_TOO_LONG_AT = 5;
-
- // set up logging
- //this.debugging = true;
- this.log = function(){
- if( this.debugging && console && console.debug ){
- var args = Array.prototype.slice.call( arguments );
- args.unshift( this.toString() );
- console.debug.apply( console, args );
- }
- };
- this.log( 'new TwoVarScatterplot:', config );
-
- // ........................................................ set up chart config
- // config will default to these values when not passed in
- //NOTE: called on new
- this.defaults = {
- id : 'TwoVarScatterplot',
- containerSelector : 'body',
- //TODO??: needed?
- maxDataPoints : 30000,
- datapointSize : 4,
- animDuration : 500,
- //TODO: variable effect (not always exactly # of ticks set to)
- xNumTicks : 10,
- yNumTicks : 10,
- xAxisLabelBumpY : 40,
- yAxisLabelBumpX : -40,
- width : 400,
- height : 400,
- //TODO: anyway to make this a sub-obj?
- marginTop : 50,
- marginRight : 50,
- marginBottom : 50,
- marginLeft : 50,
-
- xMin : null,
- xMax : null,
- yMin : null,
- yMax : null,
-
- xLabel : "X",
- yLabel : "Y"
- };
- this.config = _.extend( {}, this.defaults, config );
- this.log( 'intial config:', this.config );
-
- this.updateConfig = function( newConfig, rerender ){
- // setter for chart config
- //TODO: validate here
- _.extend( this.config, newConfig );
- this.log( this + '.updateConfig:', this.config );
- //TODO: implement rerender flag
- };
-
- // ........................................................ helpers
- this.toString = function(){
- return this.config.id;
- };
- // conv. methods for svg transforms
- this.translateStr = function( x, y ){
- return 'translate(' + x + ',' + y + ')';
- };
- this.rotateStr = function( d, x, y ){
- return 'rotate(' + d + ',' + x + ',' + y + ')';
- };
-
- // ........................................................ initial element creation
- this.adjustChartDimensions = function( top, right, bottom, left ){
- //this.log( this + '.adjustChartDimensions', arguments );
- top = top || 0;
- right = right || 0;
- bottom = bottom || 0;
- left = left || 0;
- this.svg
- .attr( "width", this.config.width + ( this.config.marginRight + right )
- + ( this.config.marginLeft + left ) )
- .attr( "height", this.config.height + ( this.config.marginTop + top )
- + ( this.config.marginBottom + bottom ) )
- // initial is hidden - show it
- .style( 'display', 'block' );
-
- // move content group away from margins
- //TODO: allow top, right axis
- this.content = this.svg.select( "g.content" )
- .attr( "transform", this.translateStr( this.config.marginLeft + left, this.config.marginTop + top ) );
- };
-
- // ........................................................ data and scales
- this.preprocessData = function( data, min, max ){
- //this.log( this + '.preprocessData', arguments );
- //TODO: filter by min, max if set
-
- // set a cap on the data, limit to first n points
- return ( data.length > this.config.maxDataPoints )? ( data.slice( 0, this.config.maxDataPoints ) ): ( data );
- };
-
- this.findMinMaxes = function( xCol, yCol, meta ){
- //this.log( this + '.findMinMaxes', arguments );
- // configuration takes priority, otherwise meta (from the server) if passed, last-resort: compute it here
- this.xMin = this.config.xMin || ( meta )?( meta[0].min ):( d3.min( xCol ) );
- this.xMax = this.config.xMax || ( meta )?( meta[0].max ):( d3.max( xCol ) );
- this.yMin = this.config.yMin || ( meta )?( meta[1].min ):( d3.min( yCol ) );
- this.yMax = this.config.yMax || ( meta )?( meta[1].max ):( d3.max( yCol ) );
- };
-
- this.setUpScales = function(){
- //this.log( this + '.setUpScales', arguments );
- // Interpolation for x, y based on data domains
- this.xScale = d3.scale.linear()
- .domain([ this.xMin, this.xMax ])
- .range([ 0, this.config.width ]),
- this.yScale = d3.scale.linear()
- .domain([ this.yMin, this.yMax ])
- .range([ this.config.height, 0 ]);
- };
-
- // ........................................................ axis and ticks
- this.setUpXAxis = function(){
- //this.log( this + '.setUpXAxis', arguments );
- // origin: bottom, left
- //TODO: incoporate top, right
- this.xAxisFn = d3.svg.axis()
- .scale( this.xScale )
- .ticks( this.config.xNumTicks )
- .orient( 'bottom' );
- this.xAxis// = content.select( 'g#x-axis' )
- .attr( 'transform', this.translateStr( 0, this.config.height ) )
- .call( this.xAxisFn );
- //this.log( 'xAxis:', this.xAxis );
-
- //TODO: adjust ticks when tick labels are long - move odds down and extend tick line
- // (for now) hide them
- var xLongestTickLabel = d3.max( _.map( [ this.xMin, this.xMax ],
- function( number ){ return ( String( number ) ).length; } ) );
- //this.log( 'xLongestTickLabel:', xLongestTickLabel );
- if( xLongestTickLabel >= X_LABEL_TOO_LONG_AT ){
- this.xAxis.selectAll( 'g' ).filter( ':nth-child(odd)' ).style( 'display', 'none' );
- }
-
- this.log( 'this.config.xLabel:', this.config.xLabel );
- this.xAxisLabel// = xAxis.select( 'text#x-axis-label' )
- .attr( 'x', this.config.width / 2 )
- .attr( 'y', this.config.xAxisLabelBumpY )
- .attr( 'text-anchor', 'middle' )
- .text( this.config.xLabel );
- this.log( 'xAxisLabel:', this.xAxisLabel );
- };
-
- this.setUpYAxis = function(){
- //this.log( this + '.setUpYAxis', arguments );
- this.yAxisFn = d3.svg.axis()
- .scale( this.yScale )
- .ticks( this.config.yNumTicks )
- .orient( 'left' );
- this.yAxis// = content.select( 'g#y-axis' )
- .call( this.yAxisFn );
- //this.log( 'yAxis:', this.yAxis );
-
- // a too complicated section for increasing the left margin when tick labels are long
- // get the tick labels for the y axis
- var yTickLabels = this.yAxis.selectAll( 'text' ).filter( function( e, i ){ return i !== 0; } );
- this.log( 'yTickLabels:', yTickLabels );
-
- // get the longest label length (or 0 if no labels)
- this.yLongestLabel = d3.max(
- //NOTE: d3 returns an nested array - use the plain array inside ([0])
- yTickLabels[0].map( function( e, i ){
- return ( d3.select( e ).text() ).length;
- })
- ) || 0;
- //this.log( 'yLongestLabel:', this.yLongestLabel );
- //TODO: lose the guessing if possible
- var neededY = TICK_LINE_AND_PADDING + ( this.yLongestLabel * GUESS_AT_SVG_CHAR_WIDTH )
- + PADDING + GUESS_AT_SVG_CHAR_HEIGHT;
- //this.log( 'neededY:', neededY );
-
- // increase width for yLongerStr, increase margin for y
- //TODO??: (or transform each number: 2k)
- this.config.yAxisLabelBumpX = -( neededY - GUESS_AT_SVG_CHAR_HEIGHT );
- if( this.config.marginLeft < neededY ){
- var adjusting = ( neededY ) - this.config.marginLeft;
- adjusting = ( adjusting < 0 )?( 0 ):( adjusting );
- //this.log( 'adjusting:', adjusting );
-
- // update dimensions, translations
- this.adjustChartDimensions( 0, 0, 0, adjusting );
- }
- //this.log( 'this.config.yAxisLableBumpx, this.config.marginLeft:',
- // this.config.yAxisLabelBumpX, this.config.marginLeft );
-
- this.yAxisLabel// = yAxis.select( 'text#y-axis-label' )
- .attr( 'x', this.config.yAxisLabelBumpX )
- .attr( 'y', this.config.height / 2 )
- .attr( 'text-anchor', 'middle' )
- .attr( 'transform', this.rotateStr( -90, this.config.yAxisLabelBumpX, this.config.height / 2 ) )
- .text( this.config.yLabel );
- //this.log( 'yAxisLabel:', this.yAxisLabel );
- };
-
- // ........................................................ grid lines
- this.renderGrid = function(){
- //this.log( this + '.renderGrid', arguments );
- // VERTICAL
- // select existing
- this.vGridLines = this.content.selectAll( 'line.v-grid-line' )
- .data( this.xScale.ticks( this.xAxisFn.ticks()[0] ) );
-
- // append any extra lines needed (more ticks)
- this.vGridLines.enter().append( 'svg:line' )
- .classed( 'grid-line v-grid-line', true );
-
- // update the attributes of existing and appended
- this.vGridLines
- .attr( 'x1', this.xScale )
- .attr( 'y1', 0 )
- .attr( 'x2', this.xScale )
- .attr( 'y2', this.config.height );
-
- // remove unneeded (less ticks)
- this.vGridLines.exit().remove();
- //this.log( 'vGridLines:', this.vGridLines );
-
- // HORIZONTAL
- this.hGridLines = this.content.selectAll( 'line.h-grid-line' )
- .data( this.yScale.ticks( this.yAxisFn.ticks()[0] ) );
-
- this.hGridLines.enter().append( 'svg:line' )
- .classed( 'grid-line h-grid-line', true );
-
- this.hGridLines
- .attr( 'x1', 0 )
- .attr( 'y1', this.yScale )
- .attr( 'x2', this.config.width )
- .attr( 'y2', this.yScale );
-
- this.hGridLines.exit().remove();
- //this.log( 'hGridLines:', this.hGridLines );
- };
-
- // ........................................................ data points
- this.renderDatapoints = function( xCol, yCol, ids ){
- this.log( this + '.renderDatapoints', arguments );
- var count = 0,
- plot = this,
- xPosFn = function( d, i ){
- //if( d ){ this.log( 'x.data:', newXCol[ i ], 'plotted:', plot.xScale( newXCol[ i ] ) ); }
- return plot.xScale( xCol[ i ] );
- },
- yPosFn = function( d, i ){
- //if( d ){ this.log( 'y.data:', newYCol[ i ], 'plotted:', plot.yScale( newYCol[ i ] ) ); }
- return plot.yScale( yCol[ i ] );
- };
-
- //this.datapoints = this.addDatapoints( xCol, yCol, ids, ".glyph" );
- var datapoints = this.content.selectAll( '.glyph' ).data( xCol );
-
- // enter - NEW data to be added as glyphs: give them a 'entry' position and style
- count = 0;
- datapoints.enter()
- .append( 'svg:circle' )
- .each( function(){ count += 1; } )
- .classed( "glyph", true )
- .attr( "cx", 0 )
- .attr( "cy", this.config.height )
- // start all bubbles small...
- .attr( "r", 0 );
- this.log( count, ' new glyphs created' );
-
- // for all EXISTING glyphs and those that need to be added: transition anim to final state
- count = 0;
- datapoints
- // ...animate to final position
- .transition().duration( this.config.animDuration )
- .each( function(){ count += 1; } )
- .attr( "cx", xPosFn )
- .attr( "cy", yPosFn )
- .attr( "r", plot.config.datapointSize );
- this.log( count, ' existing glyphs transitioned' );
-
- // events
- // glyphs that need to be removed: transition to from normal state to 'exit' state, remove from DOM
- datapoints.exit()
- .each( function(){ count += 1; } )
- .transition().duration( this.config.animDuration )
- .attr( "cy", this.config.height )
- .attr( "r", 0 )
- .remove();
- this.log( count, ' glyphs removed' );
-
- this._addDatapointEventhandlers( datapoints, xCol, yCol, ids );
- };
-
- this._addDatapointEventhandlers = function( datapoints, xCol, yCol, ids ){
- var plot = this;
- datapoints
- //TODO: remove magic numbers
- .on( 'mouseover', function( d, i ){
- var datapoint = d3.select( this );
- datapoint
- .style( 'fill', 'red' )
- .style( 'fill-opacity', 1 );
-
- // create horiz, vert lines to axis
- plot.content.append( 'line' )
- .attr( 'stroke', 'red' )
- .attr( 'stroke-width', 1 )
- // start not at center, but at the edge of the circle - to prevent mouseover thrashing
- .attr( 'x1', datapoint.attr( 'cx' ) - plot.config.datapointSize )
- .attr( 'y1', datapoint.attr( 'cy' ) )
- .attr( 'x2', 0 )
- .attr( 'y2', datapoint.attr( 'cy' ) )
- .classed( 'hoverline', true );
-
- // if the vertical hoverline
- if( datapoint.attr( 'cy' ) < plot.config.height ){
- plot.content.append( 'line' )
- .attr( 'stroke', 'red' )
- .attr( 'stroke-width', 1 )
- .attr( 'x1', datapoint.attr( 'cx' ) )
- .attr( 'y1', datapoint.attr( 'cy' ) + plot.config.datapointSize )
- .attr( 'x2', datapoint.attr( 'cx' ) )
- .attr( 'y2', plot.config.height )
- .classed( 'hoverline', true );
- }
-
- var datapointWindowPos = $( this ).offset();
- plot.datapointInfoBox = plot.infoBox(
- datapointWindowPos.top, datapointWindowPos.left,
- plot.infoHtml( xCol[ i ], yCol[ i ], ( ids )?( ids[ i ] ):( undefined ) )
- );
- $( 'body' ).append( plot.datapointInfoBox );
- })
- .on( 'mouseout', function(){
- d3.select( this )
- .style( 'fill', 'black' )
- .style( 'fill-opacity', 0.2 );
- plot.content.selectAll( '.hoverline' ).remove();
- if( plot.datapointInfoBox ){
- plot.datapointInfoBox.remove();
- }
- });
- },
-
- this.render = function( columnData, meta ){
- this.log( this + '.render', arguments );
- this.log( '\t config:', this.config );
-
- // prepare the data
- //pre: columns passed are numeric
- //pre: at least two columns are passed
- //assume: first column is x, second column is y, any remaining aren't used
- var xCol = columnData[0],
- yCol = columnData[1],
- ids = ( columnData.length > 2 )?( columnData[2] ):( undefined );
- //this.log( this + '.render', xCol.length, yCol.length, this.config );
-
- //pre: xCol.len == yCol.len
- xCol = this.preprocessData( xCol );
- yCol = this.preprocessData( yCol );
- this.log( 'xCol len', xCol.length, 'yCol len', yCol.length );
-
- this.findMinMaxes( xCol, yCol, meta );
- //this.log( 'xMin, xMax, yMin, yMax:', this.xMin, this.xMax, this.yMin, this.yMax );
- this.setUpScales();
-
- // find (or build if it doesn't exist) the svg dom infrastructure
- if( !this.svg ){ this.svg = d3.select( 'svg' ).attr( "class", "chart" ); }
- if( !this.content ){
- this.content = this.svg.append( "svg:g" ).attr( "class", "content" ).attr( 'id', this.config.id );
- }
- //this.log( 'svg:', this.svg );
- //this.log( 'content:', this.content );
-
- this.adjustChartDimensions();
-
- if( !this.xAxis ){ this.xAxis = this.content.append( 'g' ).attr( 'class', 'axis' ).attr( 'id', 'x-axis' ); }
- if( !this.xAxisLabel ){
- this.xAxisLabel = this.xAxis.append( 'text' ).attr( 'class', 'axis-label' ).attr( 'id', 'x-axis-label' );
- }
- //this.log( 'xAxis:', this.xAxis, 'xAxisLabel:', this.xAxisLabel );
-
- if( !this.yAxis ){ this.yAxis = this.content.append( 'g' ).attr( 'class', 'axis' ).attr( 'id', 'y-axis' ); }
- if( !this.yAxisLabel ){
- this.yAxisLabel = this.yAxis.append( 'text' ).attr( 'class', 'axis-label' ).attr( 'id', 'y-axis-label' );
- }
- //this.log( 'yAxis:', this.yAxis, 'yAxisLabel:', this.yAxisLabel );
-
- this.setUpXAxis();
- this.setUpYAxis();
-
- this.renderGrid();
- this.renderDatapoints( xCol, yCol, ids );
- };
-
- this.infoHtml = function( x, y, id ){
- var retDiv = $( '<div/>' );
- if( id ){
- $( '<div/>' ).text( id ).css( 'font-weight', 'bold' ).appendTo( retDiv );
- }
- $( '<div/>' ).text( x ).appendTo( retDiv );
- $( '<div/>' ).text( y ).appendTo( retDiv );
- return retDiv.html();
- };
-
- //TODO: html for now
- this.infoBox = function( top, left, html, adjTop, adjLeft ){
- adjTop = adjTop || 0;
- adjLeft = adjLeft || 20;
- var infoBox = $( '<div />' )
- .addClass( 'chart-info-box' )
- .css({
- 'position' : 'absolute',
- 'top' : top + adjTop,
- 'left' : left + adjLeft
- });
- infoBox.html( html );
- return infoBox;
- };
-
-}
-
-//==============================================================================
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a templates/webapps/galaxy/visualization/scatterplot.mako
--- a/templates/webapps/galaxy/visualization/scatterplot.mako
+++ /dev/null
@@ -1,256 +0,0 @@
-<%inherit file="/base.mako"/>
-
-<%def name="stylesheets()">
-${parent.stylesheets()}
-${h.css(
- "base",
- "autocomplete_tagging",
- "jquery-ui/smoothness/jquery-ui"
-)}
-
-<style type="text/css">
-/*TODO: use/move into base.less*/
-* { margin: 0px; padding: 0px; }
-
-/* -------------------------------------------- general layout */
-div.tab-pane {
- padding: 8px;
-}
-
-/* -------------------------------------------- header */
-.header {
- margin-bottom: 8px;
-}
-
-#chart-header {
- padding : 8px;
- background-color: #ebd9b2;
- margin-bottom: 16px;
- overflow: auto;
-}
-
-#chart-header .subtitle {
- margin: -4px 0px 0px 4px;
- padding : 0;
- color: white;
- font-size: small;
-}
-
-/* -------------------------------------------- main layout */
-#scatterplot {
- /*from width + margin of chart?*/
-}
-
-.scatterplot-container .tab-pane {
-}
-
-/* -------------------------------------------- all controls */
-
-#scatterplot input[type=button],
-#scatterplot select {
- width: 100%;
- max-width: 256px;
- margin-bottom: 8px;
-}
-
-#scatterplot .help-text,
-#scatterplot .help-text-small {
- color: grey;
-}
-
-#scatterplot .help-text {
- padding-bottom: 16px;
-}
-
-#scatterplot .help-text-small {
- padding: 4px;
- font-size: smaller;
-}
-
-#scatterplot > * {
-}
-
-#scatterplot input[value=Draw] {
- display: block;
- margin-top: 16px;
-}
-
-#scatterplot .numeric-slider-input {
- max-width: 70%;
-}
-
-/* -------------------------------------------- data controls */
-
-/* -------------------------------------------- chart controls */
-#chart-control .form-input {
- /*display: table-row;*/
-}
-
-#chart-control label {
- /*text-align: right;*/
- margin-bottom: 8px;
- /*display: table-cell;*/
-}
-
-#chart-control .slider {
- /*display: table-cell;*/
- height: 8px;
- display: block;
- margin: 8px 0px 0px 8px;
-}
-
-#chart-control .slider-output {
- /*display: table-cell;*/
- float: right;
-}
-
-#chart-control input[type="text"] {
- border: 1px solid lightgrey;
-}
-
-
-/* -------------------------------------------- statistics */
-#stats-display table#chart-stats-table {
- width: 100%;
-}
-
-#stats-display #chart-stats-table th {
- width: 30%;
- padding: 4px;
- font-weight: bold;
- color: grey;
-}
-
-#stats-display #chart-stats-table td {
- border: solid lightgrey;
- border-width: 1px 0px 0px 1px;
- padding: 4px;
-}
-
-#stats-display #chart-stats-table td:nth-child(1) {
- border-width: 1px 0px 0px 0px;
- padding-right: 1em;
- text-align: right;
- font-weight: bold;
- color: grey;
-}
-
-/* -------------------------------------------- load indicators */
-#loading-indicator {
- margin: 12px 0px 0px 8px;
-}
-
-#scatterplot #loading-indicator .loading-message {
- font-style: italic;
- font-size: smaller;
- color: grey;
-}
-
-/* -------------------------------------------- chart area */
-#chart-holder {
- overflow: auto;
- margin-left: 8px;
-}
-
-svg .grid-line {
- fill: none;
- stroke: lightgrey;
- stroke-opacity: 0.5;
- shape-rendering: crispEdges;
- stroke-dasharray: 3, 3;
-}
-
-svg .axis path, svg .axis line {
- fill: none;
- stroke: black;
- shape-rendering: crispEdges;
-}
-
-svg .axis text {
- font-family: monospace;
- font-size: 12px;
-}
-
-svg #x-axis-label, svg #y-axis-label {
- font-family: sans-serif;
- font-size: 10px;
-}
-
-svg .glyph {
- stroke: none;
- fill: black;
- fill-opacity: 0.2;
-}
-
-/* -------------------------------------------- info box */
-.chart-info-box {
- border-radius: 4px;
- padding: 4px;
- background-color: white;
- border: 1px solid black;
-}
-
-</style>
-
-</%def>
-
-<%def name="javascripts()">
-${parent.javascripts()}
-${h.js(
-
- "libs/underscore",
- "libs/jquery/jquery-ui",
- "libs/d3",
-
- "mvc/base-mvc",
- "utils/LazyDataLoader",
- "viz/scatterplot"
-)}
-
-${h.templates(
- "../../templates/compiled/template-visualization-scatterplotControlForm",
- "../../templates/compiled/template-visualization-dataControl",
- "../../templates/compiled/template-visualization-chartControl",
- "../../templates/compiled/template-visualization-chartDisplay",
- "../../templates/compiled/template-visualization-statsDisplay"
-)}
-
-${h.js(
- "mvc/visualization/scatterplotControlForm",
-)}
-
-<script type="text/javascript">
-$(function(){
-
- var hda = ${h.to_json_string( trans.security.encode_dict_ids( hda.to_dict() ) )},
- querySettings = ${h.to_json_string( query_args )},
- chartConfig = _.extend( querySettings, {
- containerSelector : '#chart',
- //TODO: move to ScatterplotControlForm.initialize
- marginTop : ( querySettings.marginTop > 20 )?( querySettings.marginTop ):( 20 ),
-
- xColumn : querySettings.xColumn,
- yColumn : querySettings.yColumn,
- idColumn : querySettings.idColumn
- });
- //console.debug( querySettings );
-
- var settingsForm = new ScatterplotControlForm({
- dataset : hda,
- apiDatasetsURL : "${h.url_for( controller='/api/datasets', action='index' )}",
- el : $( '#scatterplot' ),
- chartConfig : chartConfig
- }).render();
-
-});
-</script>
-</%def>
-
-<%def name="body()">
- <!--dataset info-->
- <div id="chart-header" class="header">
- <h2 class="title">Scatterplot of '${hda.name}'</h2>
- <p class="subtitle">${hda.info}</p>
- </div>
- <div id="scatterplot" class="scatterplot-control-form"></div>
-</%def>
diff -r b71c9af7c17c48e8ed1c45918a459f5b2c0167e4 -r 044ead55435801561acdf72fe225209239202a5a templates/webapps/galaxy/visualization/v_fwork_test.mako
--- a/templates/webapps/galaxy/visualization/v_fwork_test.mako
+++ /dev/null
@@ -1,132 +0,0 @@
-<%inherit file="/base.mako"/>
-<%def name="title()">
-${visualization_name}
-</%def>
-
-
-<%def name="stylesheets()">
-${parent.stylesheets()}
-${h.css(
- "base",
-)}
-
-<style type="text/css">
-/*TODO: use/move into base.less*/
-* { margin: 0px; padding: 0px; }
-</style>
-
-</%def>
-
-<%def name="process_hda( hda )">
-<%
- hda_dict = hda.to_dict()
- hda_dict[ 'id' ] = trans.security.encode_id( hda_dict[ 'id' ] )
- hda_dict[ 'history_id' ] = trans.security.encode_id( hda_dict[ 'history_id' ] )
- del hda_dict[ 'peek' ]
- return hda_dict
-%>
-</%def>
-
-<%def name="javascripts()">
-${parent.javascripts()}
-<script type="text/javascript">
-$(function(){
-
-//var data = {
-// title : 'shared with user visualization',
-// type : 'test',
-// slug : 'shared',
-// annotation : 'a visualization shared with a specific user',
-// config : {
-// x : 10,
-// y : 10
-// }
-// };
-
-//var creationPromise = jQuery.ajax( '/api/visualizations', {
-// type : 'POST',
-// contentType : 'application/json',
-// data : JSON.stringify( data )
-// });
-//creationPromise.success(function(){
-// console.debug( 'success' );
-//});
-//creationPromise.error(function(){
-// console.debug( 'error' );
-//});
-
-
-});
-</script>
-</%def>
-
-<%def name="print_var( name, var )">
-%if var is not None:
- <% t = str( type( var ) )[1:-1] %>
- <p>${name}: ${t}, ${var}</p>
-%else:
- <p>No ${name}</p>
-%endif
-</%def>
-
-<%def name="body()">
- <%
- import pprint
- print self
- print self.context
- pprint.pprint( self.context.kwargs, indent=4 )
- %>
- <%
- vars_to_print = [
- ( 'default', default ),
- ( 'string', string ),
- ( 'boolean', boolean ),
- ( 'integer', integer ),
- ( 'float', float ),
- ( 'json', json ),
- ]
- %>
- %for name, var in vars_to_print:
- ${print_var( name, var )}
- %endfor
-
- %if visualization:
- <h1>${visualization.title}</h1>
- <p>id: ${trans.security.encode_id( visualization.id )}</p>
- <p>dbkey: ${visualization.dbkey}</p>
- <p>config:
- <pre>${h.to_json_string( visualization.latest_revision.config, sort_keys=True, indent=( 4 * ' ' ) )}</pre>
- </p>
- %endif
-
- %if dataset:
- <h1>${dataset.name}</h1>
- <p>id: ${trans.security.encode_id( dataset.id )}</p>
- <p>history id: ${trans.security.encode_id( dataset.history.id )}</p>
- <pre>
- ${h.to_json_string( process_hda( dataset ), sort_keys=True, indent=( 4 * ' ' ) )}
- </pre>
- %endif
-
- %if dataset_instance:
- <h1>${dataset_instance.name}</h1>
- <p>id: ${trans.security.encode_id( dataset_instance.id )}</p>
- %if hda_ldda == 'hda':
- <p>history id: ${trans.security.encode_id( dataset_instance.history.id )}</p>
- <pre>
- ${h.to_json_string( process_hda( dataset_instance ), sort_keys=True, indent=( 4 * ' ' ) )}
- </pre>
- %else:
- <p>(LibraryDatasetDatasetAssociation)</p>
- %endif
- %endif
-
- %if query_args:
- <ul>
- %for key, val in query_args.items():
- <li>${key} : ${val}</li>
- %endfor
- </ul>
- %endif
-
-</%def>
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/6f9059801faf/
Changeset: 6f9059801faf
Branch: stable
User: greg
Date: 2014-02-14 16:40:31
Summary: Change log.warning to log.debug for messages regarding conflicting datatypes since recent changes in the Galaxy framework now result in job errors when using log.warning.
Affected #: 1 file
diff -r e088f2dc8b070d13e9fb7319463786d7ff4f5024 -r 6f9059801fafb3b7ae2cf844b07848beab0846fa lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -223,7 +223,7 @@
if extension in self.datatypes_by_extension:
# Because of the way that the value of can_process_datatype was set above, we know that the value of
# override is True.
- self.log.warning( "Overriding conflicting datatype with extension '%s', using datatype from %s." % \
+ self.log.debug( "Overriding conflicting datatype with extension '%s', using datatype from %s." % \
( str( extension ), str( config ) ) )
if make_subclass:
datatype_class = type( datatype_class_name, ( datatype_class, ), {} )
@@ -277,7 +277,7 @@
if not override:
# Do not load the datatype since it conflicts with an existing datatype which we are not supposed
# to override.
- self.log.warning( "Ignoring conflicting datatype with extension '%s' from %s." % ( extension, config ) )
+ self.log.debug( "Ignoring conflicting datatype with extension '%s' from %s." % ( extension, config ) )
# Load datatype sniffers from the config - we'll do this even if one or more datatypes were not properly processed in the config
# since sniffers are not tightly coupled with datatypes.
self.load_datatype_sniffers( root,
https://bitbucket.org/galaxy/galaxy-central/commits/b71c9af7c17c/
Changeset: b71c9af7c17c
User: inithello
Date: 2014-02-14 16:43:10
Summary: Merge grafted bugfix from stable.
Affected #: 0 files
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: greg: Change log.warning to log.debug for messages regarding conflicting datatypes since recent changes in the Galaxy framework now result in job errors when using log.warning.
by commits-noreply@bitbucket.org 14 Feb '14
by commits-noreply@bitbucket.org 14 Feb '14
14 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0b0018fa5d20/
Changeset: 0b0018fa5d20
User: greg
Date: 2014-02-14 16:40:31
Summary: Change log.warning to log.debug for messages regarding conflicting datatypes since recent changes in the Galaxy framework now result in job errors when using log.warning.
Affected #: 1 file
diff -r 955e973c5b33eba4e0e68979a7c20c0bd0558e37 -r 0b0018fa5d20228bb7a8c0dc6566b8239873610d lib/galaxy/datatypes/registry.py
--- a/lib/galaxy/datatypes/registry.py
+++ b/lib/galaxy/datatypes/registry.py
@@ -223,7 +223,7 @@
if extension in self.datatypes_by_extension:
# Because of the way that the value of can_process_datatype was set above, we know that the value of
# override is True.
- self.log.warning( "Overriding conflicting datatype with extension '%s', using datatype from %s." % \
+ self.log.debug( "Overriding conflicting datatype with extension '%s', using datatype from %s." % \
( str( extension ), str( config ) ) )
if make_subclass:
datatype_class = type( datatype_class_name, ( datatype_class, ), {} )
@@ -277,7 +277,7 @@
if not override:
# Do not load the datatype since it conflicts with an existing datatype which we are not supposed
# to override.
- self.log.warning( "Ignoring conflicting datatype with extension '%s' from %s." % ( extension, config ) )
+ self.log.debug( "Ignoring conflicting datatype with extension '%s' from %s." % ( extension, config ) )
# Load datatype sniffers from the config - we'll do this even if one or more datatypes were not properly processed in the config
# since sniffers are not tightly coupled with datatypes.
self.load_datatype_sniffers( root,
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: UI: add pagination jquery plugin able to handle known/unknown data totals; Scatterplot: use that plugin for pagination, add class for highlighted data points
by commits-noreply@bitbucket.org 14 Feb '14
by commits-noreply@bitbucket.org 14 Feb '14
14 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/955e973c5b33/
Changeset: 955e973c5b33
User: carlfeberhard
Date: 2014-02-14 16:17:56
Summary: UI: add pagination jquery plugin able to handle known/unknown data totals; Scatterplot: use that plugin for pagination, add class for highlighted data points
Affected #: 9 files
diff -r f199022e48088ab4fd71701e07899b729496b005 -r 955e973c5b33eba4e0e68979a7c20c0bd0558e37 config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
@@ -5,7 +5,7 @@
Better separation of AJAX in scatterplot.js (maybe pass in function?)
Labels should auto fill in chart control when dataset has column_names
Allow column selection/config using the peek output as a base for UI
- Allow setting perPage in config
+ Allow setting perPage in chart controls
Allow option to auto set width/height based on screen real estate avail.
Handle large number of pages better (Known genes hg19)
Use d3.nest to allow grouping, pagination/filtration by group (e.g. chromCol)
@@ -243,7 +243,7 @@
alert( 'Error loading data:\n' + xhr.responseText );
})
.then( function(){
- editor.render();
+ editor.display.render();
});
},
diff -r f199022e48088ab4fd71701e07899b729496b005 -r 955e973c5b33eba4e0e68979a7c20c0bd0558e37 config/plugins/visualizations/scatterplot/src/scatterplot-display.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot-display.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot-display.js
@@ -11,25 +11,15 @@
initialize : function( attributes ){
this.data = null,
this.dataset = attributes.dataset;
- this.calcNumPages();
- },
-
- calcNumPages : function(){
- var config = this.model.get( 'config' );
- this.lineCount = this.dataset.metadata_data_lines,
- this.numPages = ( this.lineCount )?( Math.ceil( this.lineCount / config.pagination.perPage ) ):( undefined );
- if( !this.lineCount || this.numPages === undefined ){
- console.warn( 'no data total found' );
- }
+ this.lineCount = this.dataset.metadata_data_lines || null;
},
fetchData : function(){
-//TODO: doesn't work bc it's rendered in render()...
- this.showLoadingIndicator( 'getting data' );
+ this.showLoadingIndicator();
//console.debug( 'currPage', this.config.pagination.currPage );
var view = this,
config = this.model.get( 'config' ),
-//TODO: very tied to datasets - should be generalized eventually
+ //TODO: very tied to datasets - should be generalized eventually
xhr = jQuery.getJSON( '/api/datasets/' + this.dataset.id, {
data_type : 'raw_data',
provider : 'dataset-column',
@@ -37,6 +27,7 @@
offset : ( config.pagination.currPage * config.pagination.perPage )
});
xhr.done( function( data ){
+ // no need to hide loading indicator, line info will write over that
view.data = data.data;
view.trigger( 'data:fetched', view );
view.renderData();
@@ -63,6 +54,7 @@
var html = [
'<div class="controls clear">',
'<div class="left">',
+ '<div class="page-control"></div>',
'</div>',
'<div class="right">',
'<p class="scatterplot-data-info"></p>',
@@ -92,11 +84,24 @@
},
renderLeftControls : function(){
- if( this.lineCount ){
- this.$el.find( '.controls .left' ).empty().append( this.renderPagination() );
- } else {
- this.$el.find( '.controls .left' ).empty().append( this.renderPrevNext() );
- }
+ var display = this,
+ config = this.model.get( 'config' );
+
+ this.$el.find( '.controls .left .page-control' ).pagination({
+ startingPage : config.pagination.currPage,
+ perPage : config.pagination.perPage,
+ totalDataSize: this.lineCount,
+ currDataSize : this.data.length
+
+ //TODO: move to named function and remove only named
+ }).off().on( 'pagination.page-change', function( event, page ){
+ //console.debug( 'pagination:page-change', page );
+ config.pagination.currPage = page;
+ display.model.set( 'config', { pagination: config.pagination });
+ //console.debug( pagination, display.model.get( 'config' ).pagination );
+ display.resetZoom();
+ display.fetchData();
+ });
return this;
},
@@ -155,83 +160,6 @@
return this;
},
- // ------------------------------------------------------------------------ data pagination
-//TODO: to pagination control
- goToPage : function( page ){
- var pagination = this.model.get( 'config' ).pagination;
- //console.debug( 'goToPage', page, pagination, this.numPages );
- if( page <= 0 ){ page = 0; }
- if( this.numPages && page >= this.numPages ){ page = this.numPages - 1; }
- if( page === pagination.currPage ){ return this; }
-
- //console.debug( '\t going to page ' + page )
- pagination.currPage = page;
- this.model.set( 'config', { pagination: pagination });
- this.resetZoom();
- this.fetchData();
- return this;
- },
-
- nextPage : function(){
- var currPage = this.model.get( 'config' ).pagination.currPage;
- return this.goToPage( currPage + 1 );
- },
-
- prevPage : function(){
- var currPage = this.model.get( 'config' ).pagination.currPage;
- return this.goToPage( currPage - 1 );
- },
-
- /** render previous and next pagination buttons */
- renderPrevNext : function(){
- var config = this.model.get( 'config' );
- // if there's no data or there's less than one page of data - return null
- if( !this.data ){ return null; }
- if( config.pagination.currPage === 0 && this.data.length < config.pagination.perPage ){ return null; }
-
- var view = this,
- $prev = $( '<li><a href="javascript:void(0);">Prev</a></li>' )
- .click( function(){ view.prevPage(); }),
- $next = $( '<li><a href="javascript:void(0);">Next</a></li>' )
- .click( function(){ view.nextPage(); });
-
- // disable if it either end
- if( config.pagination.currPage === 0 ){
- $prev.addClass( 'disabled' );
- }
- if( this.numPages && config.pagination.currPage === ( this.numPages - 1 ) ){
- $next.addClass( 'disabled' );
- }
- return $( '<ul/>' ).addClass( 'pagination data-prev-next' ).append([ $prev, $next ]);
- },
-
- /** render page links for each possible page (if we can) */
- renderPagination : function(){
- var config = this.model.get( 'config' );
- // if there's no data, no page count, or there's less than one page of data - return null
- if( !this.data ){ return null; }
- if( !this.numPages ){ return null; }
- if( config.pagination.currPage === 0 && this.data.length < config.pagination.perPage ){ return null; }
-
- var view = this,
- $pagesList = $( '<ul/>' ).addClass( 'pagination data-pages' );
- pageNumClick = function( ev ){
- view.goToPage( $( this ).data( 'page' ) );
- };
-
- for( var i=0; i<this.numPages; i+=1 ){
- // add html5 data tag 'page' for later click event handler use
- var $pageLi = $([ '<li><a href="javascript:void(0);">', i + 1, '</a></li>' ].join( '' ))
- .attr( 'data-page', i ).click( pageNumClick );
- // highlight the current page
- if( i === config.pagination.currPage ){
- $pageLi.addClass( 'active' );
- }
- $pagesList.append( $pageLi );
- }
- return $pagesList;
- },
-
// ------------------------------------------------------------------------ statistics display
/** create a webworker to calc stats for data given */
getStats : function(){
diff -r f199022e48088ab4fd71701e07899b729496b005 -r 955e973c5b33eba4e0e68979a7c20c0bd0558e37 config/plugins/visualizations/scatterplot/src/scatterplot.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot.js
@@ -237,6 +237,7 @@
datapoints.on( 'mouseover', function( d, i ){
var datapoint = d3.select( this );
datapoint
+ .classed( 'highlight', true )
.style( 'fill', 'red' )
.style( 'fill-opacity', 1 );
@@ -273,6 +274,7 @@
datapoints.on( 'mouseout', function(){
// return the point to normal, remove hoverlines and info box
d3.select( this )
+ .classed( 'highlight', false )
.style( 'fill', 'black' )
.style( 'fill-opacity', 0.2 );
content.selectAll( '.hoverline' ).remove();
diff -r f199022e48088ab4fd71701e07899b729496b005 -r 955e973c5b33eba4e0e68979a7c20c0bd0558e37 config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
--- a/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
+++ b/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
@@ -1,1 +1,1 @@
-function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){$(".chart-info-box").remove(),q.redraw(),e(),s=d(),$(o.node()).trigger("zoom.scatterplot",{scale:n.scale(),translate:n.translate()})}function g(a,c,d){return c+=8,$(['<div class="chart-info-box" style="position: absolute">',void 0!==b.idColumn?"<div>"+d[b.idColumn]+"</div>":"","<div>",j(d),"</div>","<div>",k(d),"</div>","</div>"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,30]).scale(b.scale||1).translate(b.translate||[0,0]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.x.ticks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.y.ticks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=6;q.x.label=o.append("text").attr("class","axis-label").text(b.x.label).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("class","axis-label").text(b.y.label).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",0);t.transition().duration(b.animDuration).attr("r",b.datapointSize),e(),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")<b.height&&p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")).attr("y1",+d.attr("cy")+b.datapointSize).attr("x2",d.attr("cx")).attr("y2",b.height).classed("hoverline",!0);var e=this.getBoundingClientRect();$("body").append(g(e.top,e.right,a)),$(o.node()).trigger("mouseover-datapoint.scatterplot",[this,a,c])}),t.on("mouseout",function(){d3.select(this).style("fill","black").style("fill-opacity",.2),p.selectAll(".hoverline").remove(),$(".chart-info-box").remove()})}this.scatterplot=this.scatterplot||{},this.scatterplot.chartcontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function",i=this.escapeExpression;return g+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<div data-config-key="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">',(f=c.datapointSize)?f=f.call(b,{hash:{},data:e}):(f=b.datapointSize,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n</div>\n\n<div data-config-key="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">',(f=c.width)?f=f.call(b,{hash:{},data:e}):(f=b.width,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">',(f=c.height)?f=f.call(b,{hash:{},data:e}):(f=b.height,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="'+i((f=b.x,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<div data-config-key="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="'+i((f=b.y,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.datacontrol=Handlebars.template(function(a,b,c,d,e){function f(a,b){var d,e="";return e+='\n <option value="',(d=c.index)?d=d.call(a,{hash:{},data:b}):(d=a.index,d=typeof d===j?d.apply(a):d),e+=k(d)+'">',(d=c.name)?d=d.call(a,{hash:{},data:b}):(d=a.name,d=typeof d===j?d.apply(a):d),e+=k(d)+"</option>\n "}function g(){return'checked="true"'}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var h,i="",j="function",k=this.escapeExpression,l=this;return i+='<p class="help-text">\n Use the following controls to change the data used by the chart.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n\n<div class="column-select">\n <label>Data column for X: </label>\n <select name="xColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n<div class="column-select">\n <label>Data column for Y: </label>\n <select name="yColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="include-id">\n <label for="include-id-checkbox">Include a third column as data point IDs?</label>\n <input type="checkbox" name="include-id" id="include-id-checkbox" />\n <p class="help-text-small">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n</div>\n<div class="column-select" style="display: none">\n <label for="ID-select">Data column for IDs: </label>\n <select name="idColumn">\n ',h=c.each.call(b,b.allColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="first-line-header" style="display: none;">\n <p>Possible headers: ',(h=c.possibleHeaders)?h=h.call(b,{hash:{},data:e}):(h=b.possibleHeaders,h=typeof h===j?h.apply(b):h),i+=k(h)+'\n </p>\n <label for="first-line-header-checkbox">Use the above as column headers?</label>\n <input type="checkbox" name="include-id" id="first-line-header-checkbox"\n ',h=c["if"].call(b,b.usePossibleHeaders,{hash:{},inverse:l.noop,fn:l.program(3,g,e),data:e}),(h||0===h)&&(i+=h),i+='/>\n <p class="help-text-small">\n It looks like Galaxy couldn\'t get proper column headers for this data.\n Would you like to use the column headers above as column names to select columns?\n </p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.editor=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f="";return f+='<div class="scatterplot-editor tabbable tabs-left">\n \n <ul class="nav nav-tabs">\n \n <li class="active">\n <a title="Use this tab to change which data are used"\n href="#data-control" data-toggle="tab">Data Controls</a>\n </li>\n <li>\n <a title="Use this tab to change how the chart is drawn"\n href="#chart-control" data-toggle="tab" >Chart Controls</a>\n </li>\n \n <li class="disabled">\n <a title="This tab will display the chart"\n href="#chart-display" data-toggle="tab">Chart</a>\n </li>\n \n <li class="file-controls">\n<!-- <button class="copy-btn btn btn-default"\n title="Save this as a new visualization">Save to new</button>-->\n <button class="save-btn btn btn-default">Save</button>\n </li>\n </ul>\n\n \n <div class="tab-content">\n \n <div id="data-control" class="scatterplot-config-control tab-pane active">\n \n </div>\n \n \n <div id="chart-control" class="scatterplot-config-control tab-pane">\n \n </div>\n\n \n <div id="chart-display" class="scatterplot-display tab-pane"></div>\n\n </div>\n</div>\n'});var ScatterplotConfigEditor=Backbone.View.extend(LoggableMixin).extend({className:"scatterplot-control-form",initialize:function(a){if(this.model||(this.model=new Visualization({type:"scatterplot"})),this.log(this+".initialize, attributes:",a),!a||!a.dataset)throw new Error("ScatterplotConfigEditor requires a dataset");this.dataset=a.dataset,this.log("dataset:",this.dataset),this.display=new ScatterplotDisplay({dataset:a.dataset,model:this.model})},render:function(){this.$el.empty().append(ScatterplotConfigEditor.templates.mainLayout({})),this.model.id&&(this.$el.find(".copy-btn").show(),this.$el.find(".save-btn").text("Update saved")),this.$el.find("[title]").tooltip(),this._render_dataControl(),this._render_chartControls(),this._render_chartDisplay();var a=this.model.get("config");return this.model.id&&_.isFinite(a.xColumn)&&_.isFinite(a.yColumn)&&this.renderChart(),this},_render_dataControl:function(a){a=a||this.$el;var b=this,c=this.dataset,d=c.metadata_column_names||[],e=this.model.get("config"),f=[],g=_.map(c.metadata_column_types,function(a,b){var c={index:b,type:a,name:d[b]||"column "+(b+1)};return("int"===c.type||"float"===c.type)&&f.push(c),c});f.length<2&&(f=g);var h=a.find(".tab-pane#data-control");h.html(ScatterplotConfigEditor.templates.dataControl({allColumns:g,numericColumns:f}));var i={xColumn:_.isFinite(e.xColumn)?e.xColumn:f[0].index,yColumn:_.isFinite(e.yColumn)?e.yColumn:f[1].index,idColumn:g[0].index};if(_.isFinite(e.idColumn))i.idColumn=e.idColumn;else if(g.length>2){var j=_.find(g,function(a,b){return b!==i.xColumn&&b!==i.yColumn});i.idColumn=j.index}return e=this.model.set("config",i,{silent:!0}).get("config"),h.find('[name="xColumn"]').val(e.xColumn).on("change",function(){b.model.set("config",{xColumn:Number($(this).val())})}),h.find('[name="yColumn"]').val(e.yColumn).on("change",function(){b.model.set("config",{yColumn:Number($(this).val())})}),h.find('select[name="idColumn"]').val(e.idColumn).on("change",function(){b.model.set("config",{idColumn:Number($(this).val())})}),void 0!==e.idColumn&&h.find("#include-id-checkbox").prop("checked",!0).trigger("change"),h.find("[title]").tooltip(),h},_render_chartControls:function(a){function b(){var a=$(this),b=a.slider("value");c.model.set("config",_.object([[a.parent().data("config-key"),b]])),a.siblings(".slider-output").text(b)}a=a||this.$el;var c=this,d=this.model.get("config"),e=a.find("#chart-control");e.html(ScatterplotConfigEditor.templates.chartControl(d));var f={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};e.find(".numeric-slider-input").each(function(){var a=$(this),c=a.attr("data-config-key"),e=_.extend(f[c],{value:d[c],change:b,slide:b});a.find(".slider").slider(e),a.children(".slider-output").text(d[c])});var g=this.dataset.metadata_column_names||[],h=d.xLabel||g[d.xColumn]||"X",i=d.yLabel||g[d.yColumn]||"Y";return e.find('input[name="X-axis-label"]').val(h).on("change",function(){c.model.set("config",{xLabel:$(this).val()})}),e.find('input[name="Y-axis-label"]').val(i).on("change",function(){c.model.set("config",{yLabel:$(this).val()})}),e.find("[title]").tooltip(),e},_render_chartDisplay:function(a){a=a||this.$el;var b=a.find(".tab-pane#chart-display");return this.display.setElement(b),this.display.render(),b.find("[title]").tooltip(),b},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart","click .save-btn":"saveVisualization"},saveVisualization:function(){var a=this;this.model.save().fail(function(b,c,d){console.error(b,c,d),a.trigger("save:error",view),alert("Error loading data:\n"+b.responseText)}).then(function(){a.render()})},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.display.fetchData()},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:scatterplot.editor,dataControl:scatterplot.datacontrol,chartControl:scatterplot.chartcontrol};var ScatterplotDisplay=Backbone.View.extend({initialize:function(a){this.data=null,this.dataset=a.dataset,this.calcNumPages()},calcNumPages:function(){var a=this.model.get("config");this.lineCount=this.dataset.metadata_data_lines,this.numPages=this.lineCount?Math.ceil(this.lineCount/a.pagination.perPage):void 0,this.lineCount&&void 0!==this.numPages||console.warn("no data total found")},fetchData:function(){this.showLoadingIndicator("getting data");var a=this,b=this.model.get("config"),c=jQuery.getJSON("/api/datasets/"+this.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:b.pagination.perPage,offset:b.pagination.currPage*b.pagination.perPage});return c.done(function(b){a.data=b.data,a.trigger("data:fetched",a),a.renderData()}),c.fail(function(b,c,d){console.error(b,c,d),a.trigger("data:error",a),alert("Error loading data:\n"+b.responseText)}),c},showLoadingIndicator:function(){this.$el.find(".scatterplot-data-info").html(['<div class="loading-indicator">','<span class="fa fa-spinner fa-spin"></span>','<span class="loading-indicator-message">loading...</span>',"</div>"].join(""))},template:function(){var a=['<div class="controls clear">','<div class="left">',"</div>",'<div class="right">','<p class="scatterplot-data-info"></p>','<button class="stats-toggle-btn">Stats</button>','<button class="rerender-btn">Redraw</button>',"</div>","</div>","<svg/>",'<div class="stats-display"></div>'].join("");return a},render:function(){return this.$el.addClass("scatterplot-display").html(this.template()),this.data&&this.renderData(),this},renderData:function(){this.renderLeftControls(),this.renderRightControls(),this.renderPlot(this.data),this.getStats()},renderLeftControls:function(){return this.lineCount?this.$el.find(".controls .left").empty().append(this.renderPagination()):this.$el.find(".controls .left").empty().append(this.renderPrevNext()),this},renderRightControls:function(){var a=this;this.setLineInfo(this.data),this.$el.find(".stats-toggle-btn").off().click(function(){a.toggleStats()}),this.$el.find(".rerender-btn").off().click(function(){a.resetZoom(),a.renderPlot(this.data)})},renderPlot:function(){var a=this,b=this.$el.find("svg");this.toggleStats(!1),b.off().empty().show().on("zoom.scatterplot",function(b,c){a.model.set("config",c)}),scatterplot(b.get(0),this.model.get("config"),this.data)},setLineInfo:function(a,b){if(a){var c=this.model.get("config"),d=this.lineCount||"an unknown total",e=c.pagination.currPage*c.pagination.perPage,f=e+a.length;this.$el.find(".controls p.scatterplot-data-info").text([e+1,"to",f,"of",d].join(" "))}else this.$el.find(".controls p.scatterplot-data-info").html(b||"");return this},resetZoom:function(a,b){return a=void 0!==a?a:1,b=void 0!==b?b:[0,0],this.model.set("config",{scale:a,translate:b}),this},goToPage:function(a){var b=this.model.get("config").pagination;return 0>=a&&(a=0),this.numPages&&a>=this.numPages&&(a=this.numPages-1),a===b.currPage?this:(b.currPage=a,this.model.set("config",{pagination:b}),this.resetZoom(),this.fetchData(),this)},nextPage:function(){var a=this.model.get("config").pagination.currPage;return this.goToPage(a+1)},prevPage:function(){var a=this.model.get("config").pagination.currPage;return this.goToPage(a-1)},renderPrevNext:function(){var a=this.model.get("config");if(!this.data)return null;if(0===a.pagination.currPage&&this.data.length<a.pagination.perPage)return null;var b=this,c=$('<li><a href="javascript:void(0);">Prev</a></li>').click(function(){b.prevPage()}),d=$('<li><a href="javascript:void(0);">Next</a></li>').click(function(){b.nextPage()});return 0===a.pagination.currPage&&c.addClass("disabled"),this.numPages&&a.pagination.currPage===this.numPages-1&&d.addClass("disabled"),$("<ul/>").addClass("pagination data-prev-next").append([c,d])},renderPagination:function(){var a=this.model.get("config");if(!this.data)return null;if(!this.numPages)return null;if(0===a.pagination.currPage&&this.data.length<a.pagination.perPage)return null;var b=this,c=$("<ul/>").addClass("pagination data-pages");pageNumClick=function(){b.goToPage($(this).data("page"))};for(var d=0;d<this.numPages;d+=1){var e=$(['<li><a href="javascript:void(0);">',d+1,"</a></li>"].join("")).attr("data-page",d).click(pageNumClick);d===a.pagination.currPage&&e.addClass("active"),c.append(e)}return c},getStats:function(){if(this.data){var a=this,b=this.model.get("config"),c=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js");c.postMessage({data:this.data,keys:[b.xColumn,b.yColumn]}),c.onerror=function(){c.terminate()},c.onmessage=function(b){a.renderStats(b.data)}}},renderStats:function(a){var b=this.model.get("config"),c=this.$el.find(".stats-display"),d=b.x.label,e=b.y.label,f=$("<table/>").addClass("table").append(["<thead><th></th><th>",d,"</th><th>",e,"</th></thead>"].join("")).append(_.map(a,function(a,b){return $(["<tr><td>",b,"</td><td>",a[0],"</td><td>",a[1],"</td></tr>"].join(""))}));c.empty().append(f)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}}),ScatterplotModel=Visualization.extend({defaults:{type:"scatterplot",config:{pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},x:{ticks:10,label:"X"},y:{ticks:10,label:"Y"},datapointSize:4,animDuration:500,scale:1,translate:[0,0]}}});
\ No newline at end of file
+function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){$(".chart-info-box").remove(),q.redraw(),e(),s=d(),$(o.node()).trigger("zoom.scatterplot",{scale:n.scale(),translate:n.translate()})}function g(a,c,d){return c+=8,$(['<div class="chart-info-box" style="position: absolute">',void 0!==b.idColumn?"<div>"+d[b.idColumn]+"</div>":"","<div>",j(d),"</div>","<div>",k(d),"</div>","</div>"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,30]).scale(b.scale||1).translate(b.translate||[0,0]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.x.ticks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.y.ticks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=6;q.x.label=o.append("text").attr("class","axis-label").text(b.x.label).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("class","axis-label").text(b.y.label).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",0);t.transition().duration(b.animDuration).attr("r",b.datapointSize),e(),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.classed("highlight",!0).style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")<b.height&&p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")).attr("y1",+d.attr("cy")+b.datapointSize).attr("x2",d.attr("cx")).attr("y2",b.height).classed("hoverline",!0);var e=this.getBoundingClientRect();$("body").append(g(e.top,e.right,a)),$(o.node()).trigger("mouseover-datapoint.scatterplot",[this,a,c])}),t.on("mouseout",function(){d3.select(this).classed("highlight",!1).style("fill","black").style("fill-opacity",.2),p.selectAll(".hoverline").remove(),$(".chart-info-box").remove()})}this.scatterplot=this.scatterplot||{},this.scatterplot.chartcontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function",i=this.escapeExpression;return g+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<div data-config-key="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">',(f=c.datapointSize)?f=f.call(b,{hash:{},data:e}):(f=b.datapointSize,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n</div>\n\n<div data-config-key="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">',(f=c.width)?f=f.call(b,{hash:{},data:e}):(f=b.width,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">',(f=c.height)?f=f.call(b,{hash:{},data:e}):(f=b.height,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="'+i((f=b.x,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<div data-config-key="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="'+i((f=b.y,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.datacontrol=Handlebars.template(function(a,b,c,d,e){function f(a,b){var d,e="";return e+='\n <option value="',(d=c.index)?d=d.call(a,{hash:{},data:b}):(d=a.index,d=typeof d===j?d.apply(a):d),e+=k(d)+'">',(d=c.name)?d=d.call(a,{hash:{},data:b}):(d=a.name,d=typeof d===j?d.apply(a):d),e+=k(d)+"</option>\n "}function g(){return'checked="true"'}this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var h,i="",j="function",k=this.escapeExpression,l=this;return i+='<p class="help-text">\n Use the following controls to change the data used by the chart.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n\n<div class="column-select">\n <label>Data column for X: </label>\n <select name="xColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n<div class="column-select">\n <label>Data column for Y: </label>\n <select name="yColumn">\n ',h=c.each.call(b,b.numericColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="include-id">\n <label for="include-id-checkbox">Include a third column as data point IDs?</label>\n <input type="checkbox" name="include-id" id="include-id-checkbox" />\n <p class="help-text-small">\n These will be displayed (along with the x and y values) when you hover over\n a data point.\n </p>\n</div>\n<div class="column-select" style="display: none">\n <label for="ID-select">Data column for IDs: </label>\n <select name="idColumn">\n ',h=c.each.call(b,b.allColumns,{hash:{},inverse:l.noop,fn:l.program(1,f,e),data:e}),(h||0===h)&&(i+=h),i+='\n </select>\n</div>\n\n\n<div id="first-line-header" style="display: none;">\n <p>Possible headers: ',(h=c.possibleHeaders)?h=h.call(b,{hash:{},data:e}):(h=b.possibleHeaders,h=typeof h===j?h.apply(b):h),i+=k(h)+'\n </p>\n <label for="first-line-header-checkbox">Use the above as column headers?</label>\n <input type="checkbox" name="include-id" id="first-line-header-checkbox"\n ',h=c["if"].call(b,b.usePossibleHeaders,{hash:{},inverse:l.noop,fn:l.program(3,g,e),data:e}),(h||0===h)&&(i+=h),i+='/>\n <p class="help-text-small">\n It looks like Galaxy couldn\'t get proper column headers for this data.\n Would you like to use the column headers above as column names to select columns?\n </p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.editor=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f="";return f+='<div class="scatterplot-editor tabbable tabs-left">\n \n <ul class="nav nav-tabs">\n \n <li class="active">\n <a title="Use this tab to change which data are used"\n href="#data-control" data-toggle="tab">Data Controls</a>\n </li>\n <li>\n <a title="Use this tab to change how the chart is drawn"\n href="#chart-control" data-toggle="tab" >Chart Controls</a>\n </li>\n \n <li class="disabled">\n <a title="This tab will display the chart"\n href="#chart-display" data-toggle="tab">Chart</a>\n </li>\n \n <li class="file-controls">\n<!-- <button class="copy-btn btn btn-default"\n title="Save this as a new visualization">Save to new</button>-->\n <button class="save-btn btn btn-default">Save</button>\n </li>\n </ul>\n\n \n <div class="tab-content">\n \n <div id="data-control" class="scatterplot-config-control tab-pane active">\n \n </div>\n \n \n <div id="chart-control" class="scatterplot-config-control tab-pane">\n \n </div>\n\n \n <div id="chart-display" class="scatterplot-display tab-pane"></div>\n\n </div>\n</div>\n'});var ScatterplotConfigEditor=Backbone.View.extend(LoggableMixin).extend({className:"scatterplot-control-form",initialize:function(a){if(this.model||(this.model=new Visualization({type:"scatterplot"})),this.log(this+".initialize, attributes:",a),!a||!a.dataset)throw new Error("ScatterplotConfigEditor requires a dataset");this.dataset=a.dataset,this.log("dataset:",this.dataset),this.display=new ScatterplotDisplay({dataset:a.dataset,model:this.model})},render:function(){this.$el.empty().append(ScatterplotConfigEditor.templates.mainLayout({})),this.model.id&&(this.$el.find(".copy-btn").show(),this.$el.find(".save-btn").text("Update saved")),this.$el.find("[title]").tooltip(),this._render_dataControl(),this._render_chartControls(),this._render_chartDisplay();var a=this.model.get("config");return this.model.id&&_.isFinite(a.xColumn)&&_.isFinite(a.yColumn)&&this.renderChart(),this},_render_dataControl:function(a){a=a||this.$el;var b=this,c=this.dataset,d=c.metadata_column_names||[],e=this.model.get("config"),f=[],g=_.map(c.metadata_column_types,function(a,b){var c={index:b,type:a,name:d[b]||"column "+(b+1)};return("int"===c.type||"float"===c.type)&&f.push(c),c});f.length<2&&(f=g);var h=a.find(".tab-pane#data-control");h.html(ScatterplotConfigEditor.templates.dataControl({allColumns:g,numericColumns:f}));var i={xColumn:_.isFinite(e.xColumn)?e.xColumn:f[0].index,yColumn:_.isFinite(e.yColumn)?e.yColumn:f[1].index,idColumn:g[0].index};if(_.isFinite(e.idColumn))i.idColumn=e.idColumn;else if(g.length>2){var j=_.find(g,function(a,b){return b!==i.xColumn&&b!==i.yColumn});i.idColumn=j.index}return e=this.model.set("config",i,{silent:!0}).get("config"),h.find('[name="xColumn"]').val(e.xColumn).on("change",function(){b.model.set("config",{xColumn:Number($(this).val())})}),h.find('[name="yColumn"]').val(e.yColumn).on("change",function(){b.model.set("config",{yColumn:Number($(this).val())})}),h.find('select[name="idColumn"]').val(e.idColumn).on("change",function(){b.model.set("config",{idColumn:Number($(this).val())})}),void 0!==e.idColumn&&h.find("#include-id-checkbox").prop("checked",!0).trigger("change"),h.find("[title]").tooltip(),h},_render_chartControls:function(a){function b(){var a=$(this),b=a.slider("value");c.model.set("config",_.object([[a.parent().data("config-key"),b]])),a.siblings(".slider-output").text(b)}a=a||this.$el;var c=this,d=this.model.get("config"),e=a.find("#chart-control");e.html(ScatterplotConfigEditor.templates.chartControl(d));var f={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};e.find(".numeric-slider-input").each(function(){var a=$(this),c=a.attr("data-config-key"),e=_.extend(f[c],{value:d[c],change:b,slide:b});a.find(".slider").slider(e),a.children(".slider-output").text(d[c])});var g=this.dataset.metadata_column_names||[],h=d.xLabel||g[d.xColumn]||"X",i=d.yLabel||g[d.yColumn]||"Y";return e.find('input[name="X-axis-label"]').val(h).on("change",function(){c.model.set("config",{xLabel:$(this).val()})}),e.find('input[name="Y-axis-label"]').val(i).on("change",function(){c.model.set("config",{yLabel:$(this).val()})}),e.find("[title]").tooltip(),e},_render_chartDisplay:function(a){a=a||this.$el;var b=a.find(".tab-pane#chart-display");return this.display.setElement(b),this.display.render(),b.find("[title]").tooltip(),b},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart","click .save-btn":"saveVisualization"},saveVisualization:function(){var a=this;this.model.save().fail(function(b,c,d){console.error(b,c,d),a.trigger("save:error",view),alert("Error loading data:\n"+b.responseText)}).then(function(){a.display.render()})},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.display.fetchData()},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:scatterplot.editor,dataControl:scatterplot.datacontrol,chartControl:scatterplot.chartcontrol};var ScatterplotDisplay=Backbone.View.extend({initialize:function(a){this.data=null,this.dataset=a.dataset,this.calcNumPages()},calcNumPages:function(){var a=this.model.get("config");this.lineCount=this.dataset.metadata_data_lines,this.numPages=this.lineCount?Math.ceil(this.lineCount/a.pagination.perPage):void 0,this.lineCount&&void 0!==this.numPages||console.warn("no data total found")},fetchData:function(){this.showLoadingIndicator("getting data");var a=this,b=this.model.get("config"),c=jQuery.getJSON("/api/datasets/"+this.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:b.pagination.perPage,offset:b.pagination.currPage*b.pagination.perPage});return c.done(function(b){a.data=b.data,a.trigger("data:fetched",a),a.renderData()}),c.fail(function(b,c,d){console.error(b,c,d),a.trigger("data:error",a),alert("Error loading data:\n"+b.responseText)}),c},showLoadingIndicator:function(){this.$el.find(".scatterplot-data-info").html(['<div class="loading-indicator">','<span class="fa fa-spinner fa-spin"></span>','<span class="loading-indicator-message">loading...</span>',"</div>"].join(""))},template:function(){var a=['<div class="controls clear">','<div class="left">','<div class="page-control"></div>',"</div>",'<div class="right">','<p class="scatterplot-data-info"></p>','<button class="stats-toggle-btn">Stats</button>','<button class="rerender-btn">Redraw</button>',"</div>","</div>","<svg/>",'<div class="stats-display"></div>'].join("");return a},render:function(){return this.$el.addClass("scatterplot-display").html(this.template()),this.data&&this.renderData(),this},renderData:function(){this.renderLeftControls(),this.renderRightControls(),this.renderPlot(this.data),this.getStats()},renderLeftControls:function(){var a=this,b=this.model.get("config");return this.$el.find(".controls .left .page-control").pagination({startingPage:b.pagination.currPage,perPage:b.pagination.perPage,totalDataSize:this.lineCount||null,currDataSize:this.data.length,maxWidth:540}).off().on("pagination.page-change",function(c,d){b.pagination.currPage=d,a.model.set("config",{pagination:b.pagination}),a.resetZoom(),a.fetchData()}),this},renderRightControls:function(){var a=this;this.setLineInfo(this.data),this.$el.find(".stats-toggle-btn").off().click(function(){a.toggleStats()}),this.$el.find(".rerender-btn").off().click(function(){a.resetZoom(),a.renderPlot(this.data)})},renderPlot:function(){var a=this,b=this.$el.find("svg");this.toggleStats(!1),b.off().empty().show().on("zoom.scatterplot",function(b,c){a.model.set("config",c)}),scatterplot(b.get(0),this.model.get("config"),this.data)},setLineInfo:function(a,b){if(a){var c=this.model.get("config"),d=this.lineCount||"an unknown total",e=c.pagination.currPage*c.pagination.perPage,f=e+a.length;this.$el.find(".controls p.scatterplot-data-info").text([e+1,"to",f,"of",d].join(" "))}else this.$el.find(".controls p.scatterplot-data-info").html(b||"");return this},resetZoom:function(a,b){return a=void 0!==a?a:1,b=void 0!==b?b:[0,0],this.model.set("config",{scale:a,translate:b}),this},getStats:function(){if(this.data){var a=this,b=this.model.get("config"),c=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js");c.postMessage({data:this.data,keys:[b.xColumn,b.yColumn]}),c.onerror=function(){c.terminate()},c.onmessage=function(b){a.renderStats(b.data)}}},renderStats:function(a){var b=this.model.get("config"),c=this.$el.find(".stats-display"),d=b.x.label,e=b.y.label,f=$("<table/>").addClass("table").append(["<thead><th></th><th>",d,"</th><th>",e,"</th></thead>"].join("")).append(_.map(a,function(a,b){return $(["<tr><td>",b,"</td><td>",a[0],"</td><td>",a[1],"</td></tr>"].join(""))}));c.empty().append(f)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}}),ScatterplotModel=Visualization.extend({defaults:{type:"scatterplot",config:{pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},x:{ticks:10,label:"X"},y:{ticks:10,label:"Y"},datapointSize:4,animDuration:500,scale:1,translate:[0,0]}}});
\ No newline at end of file
diff -r f199022e48088ab4fd71701e07899b729496b005 -r 955e973c5b33eba4e0e68979a7c20c0bd0558e37 config/plugins/visualizations/scatterplot/static/scatterplot.css
--- a/config/plugins/visualizations/scatterplot/static/scatterplot.css
+++ b/config/plugins/visualizations/scatterplot/static/scatterplot.css
@@ -125,13 +125,9 @@
margin: 0px;
}
-.scatterplot-display .controls .left {
- float: left;
-}
-
-.scatterplot-display .controls .pagination {
- display: inline-block;
- margin: 0px;
+.scatterplot-display .pagination-scroll-container {
+ /* float left makes this not collapse very well */
+ max-width: 540px;
}
.scatterplot-display .controls .data-prev-next {
@@ -139,22 +135,16 @@
margin: 0px 4px 0px 0px;
}
-.scatterplot-display .controls .pagination-container {
- display: inline-block;
- overflow: auto;
- max-width: 256px;
- /*very tweaky*/
- margin: 0px 8px 0px 0px;
- height: 27px;
- padding-top: 5px;
+.scatterplot-display .controls {
}
-.scatterplot-display .controls .pagination-container a {
- display: inline;
- /* WTH, bootstrap? */
- position: static;
- float: none;
- /* up from 4px -> 5px */
- padding: 5px 10px 5px 10px;
+
+.scatterplot-display .controls .left,
+.scatterplot-display .controls .right {
+ position: relative;
+}
+
+.scatterplot-display .controls .left {
+ float: left;
}
.scatterplot-display .controls .right {
diff -r f199022e48088ab4fd71701e07899b729496b005 -r 955e973c5b33eba4e0e68979a7c20c0bd0558e37 config/plugins/visualizations/scatterplot/templates/scatterplot.mako
--- a/config/plugins/visualizations/scatterplot/templates/scatterplot.mako
+++ b/config/plugins/visualizations/scatterplot/templates/scatterplot.mako
@@ -32,6 +32,7 @@
<script type="text/javascript" src="/static/scripts/libs/d3.js"></script><script type="text/javascript" src="/static/scripts/mvc/base-mvc.js"></script>
+<script type="text/javascript" src="/static/scripts/mvc/ui.js"></script><script type="text/javascript" src="/static/scripts/mvc/visualization/visualization-model.js"></script><script type="text/javascript" src="/plugins/visualizations/scatterplot/static/scatterplot-edit.js"></script>
diff -r f199022e48088ab4fd71701e07899b729496b005 -r 955e973c5b33eba4e0e68979a7c20c0bd0558e37 static/scripts/mvc/ui.js
--- a/static/scripts/mvc/ui.js
+++ b/static/scripts/mvc/ui.js
@@ -517,6 +517,90 @@
//==============================================================================
+function LoadingIndicator( $where, options ){
+//TODO: move out of global
+//TODO: too specific to history panel
+
+ var self = this;
+ // defaults
+ options = jQuery.extend({
+ cover : false
+ }, options || {} );
+
+ function render(){
+ var html = [
+ '<div class="loading-indicator">',
+ '<div class="loading-indicator-text">',
+ '<span class="fa fa-spinner fa-spin fa-lg"></span>',
+ '<span class="loading-indicator-message">loading...</span>',
+ '</div>',
+ '</div>'
+ ].join( '\n' );
+
+ var $indicator = $( html ).hide().css( options.css || {
+ position : 'fixed'
+ }),
+ $text = $indicator.children( '.loading-indicator-text' );
+
+ if( options.cover ){
+ $indicator.css({
+ 'z-index' : 2,
+ top : $where.css( 'top' ),
+ bottom : $where.css( 'bottom' ),
+ left : $where.css( 'left' ),
+ right : $where.css( 'right' ),
+ opacity : 0.5,
+ 'background-color': 'white',
+ 'text-align': 'center'
+ });
+ $text = $indicator.children( '.loading-indicator-text' ).css({
+ 'margin-top' : '20px'
+ });
+
+ } else {
+ $text = $indicator.children( '.loading-indicator-text' ).css({
+ margin : '12px 0px 0px 10px',
+ opacity : '0.85',
+ color : 'grey'
+ });
+ $text.children( '.loading-indicator-message' ).css({
+ margin : '0px 8px 0px 0px',
+ 'font-style' : 'italic'
+ });
+ }
+ return $indicator;
+ }
+
+ self.show = function( msg, speed, callback ){
+ msg = msg || 'loading...';
+ speed = speed || 'fast';
+ // since position is fixed - we insert as sibling
+ self.$indicator = render().insertBefore( $where );
+ self.message( msg );
+ self.$indicator.fadeIn( speed, callback );
+ return self;
+ };
+
+ self.message = function( msg ){
+ self.$indicator.find( 'i' ).text( msg );
+ };
+
+ self.hide = function( speed, callback ){
+ speed = speed || 'fast';
+ if( self.$indicator && self.$indicator.size() ){
+ self.$indicator.fadeOut( speed, function(){
+ self.$indicator.remove();
+ if( callback ){ callback(); }
+ });
+ } else {
+ if( callback ){ callback(); }
+ }
+ return self;
+ };
+ return self;
+}
+
+//==============================================================================
(function(){
/** searchInput: (jQuery plugin)
* Creates a search input, a clear button, and loading indicator
@@ -614,7 +698,7 @@
clearSearchInput.call( this, event );
});
}
-
+
// .................................................................... loadingIndicator rendering
// a button for clearing the search bar, placed on the right hand side
function $loadingIndicator(){
@@ -803,91 +887,6 @@
//==============================================================================
-function LoadingIndicator( $where, options ){
-//TODO: move out of global
-//TODO: too specific to history panel
-
- var self = this;
- // defaults
- options = jQuery.extend({
- cover : false
- }, options || {} );
-
- function render(){
- var html = [
- '<div class="loading-indicator">',
- '<div class="loading-indicator-text">',
- '<span class="fa fa-spinner fa-spin fa-lg"></span>',
- '<span class="loading-indicator-message">loading...</span>',
- '</div>',
- '</div>'
- ].join( '\n' );
-
- var $indicator = $( html ).hide().css( options.css || {
- position : 'fixed'
- }),
- $text = $indicator.children( '.loading-indicator-text' );
-
- if( options.cover ){
- $indicator.css({
- 'z-index' : 2,
- top : $where.css( 'top' ),
- bottom : $where.css( 'bottom' ),
- left : $where.css( 'left' ),
- right : $where.css( 'right' ),
- opacity : 0.5,
- 'background-color': 'white',
- 'text-align': 'center'
- });
- $text = $indicator.children( '.loading-indicator-text' ).css({
- 'margin-top' : '20px'
- });
-
- } else {
- $text = $indicator.children( '.loading-indicator-text' ).css({
- margin : '12px 0px 0px 10px',
- opacity : '0.85',
- color : 'grey'
- });
- $text.children( '.loading-indicator-message' ).css({
- margin : '0px 8px 0px 0px',
- 'font-style' : 'italic'
- });
- }
- return $indicator;
- }
-
- self.show = function( msg, speed, callback ){
- msg = msg || 'loading...';
- speed = speed || 'fast';
- // since position is fixed - we insert as sibling
- self.$indicator = render().insertBefore( $where );
- self.message( msg );
- self.$indicator.fadeIn( speed, callback );
- return self;
- };
-
- self.message = function( msg ){
- self.$indicator.find( 'i' ).text( msg );
- };
-
- self.hide = function( speed, callback ){
- speed = speed || 'fast';
- if( self.$indicator && self.$indicator.size() ){
- self.$indicator.fadeOut( speed, function(){
- self.$indicator.remove();
- if( callback ){ callback(); }
- });
- } else {
- if( callback ){ callback(); }
- }
- return self;
- };
- return self;
-}
-
-
-//==============================================================================
/**
* Template function that produces a bootstrap dropdown to replace the
* vanilla HTML select input. Pass in an array of options and an initial selection:
@@ -1082,3 +1081,221 @@
}
});
}());
+
+
+//==============================================================================
+(function(){
+ /** Builds (twitter bootstrap styled) pagination controls.
+ * If the totalDataSize is not null, a horizontal list of page buttons is displayed.
+ * If totalDataSize is null, two links ('Prev' and 'Next) are displayed.
+ * When pages are changed, a 'pagination.page-change' event is fired
+ * sending the event and the (0-based) page requested.
+ */
+ function Pagination( element, options ){
+ /** the total number of pages */
+ this.numPages = null;
+ /** the current, active page */
+ this.currPage = 0;
+ return this.init( element, options );
+ }
+
+ /** data key under which this object will be stored in the element */
+ Pagination.prototype.DATA_KEY = 'pagination';
+ /** default options */
+ Pagination.prototype.defaults = {
+ /** which page to begin at */
+ startingPage : 0,
+ /** number of data per page */
+ perPage : 20,
+ /** the total number of data (null == unknown) */
+ totalDataSize : null,
+ /** size of current data on current page */
+ currDataSize : null
+ };
+
+ /** init the control, calc numPages if possible, and render
+ * @param {jQuery} the element that will contain the pagination control
+ * @param {Object} options a map containing overrides to the pagination default options
+ */
+ Pagination.prototype.init = function _init( $element, options ){
+ options = options || {};
+ this.$element = $element;
+ this.options = jQuery.extend( true, {}, this.defaults, options );
+
+ this.currPage = this.options.startingPage;
+ if( this.options.totalDataSize !== null ){
+ this.numPages = Math.ceil( this.options.totalDataSize / this.options.perPage );
+ // limit currPage by numPages
+ if( this.currPage >= this.numPages ){
+ this.currPage = this.numPages - 1;
+ }
+ }
+ //console.debug( 'Pagination.prototype.init:', this.$element, this.currPage );
+ //console.debug( JSON.stringify( this.options ) );
+
+ // bind to data of element
+ this.$element.data( Pagination.prototype.DATA_KEY, this );
+
+ this._render();
+ return this;
+ };
+
+ /** helper to create a simple li + a combo */
+ function _make$Li( contents ){
+ return $([
+ '<li><a href="javascript:void(0);">', contents, '</a></li>'
+ ].join( '' ));
+ }
+
+ /** render previous and next pagination buttons */
+ Pagination.prototype._render = function __render(){
+ // no data - no pagination
+ if( this.options.totalDataSize === 0 ){ return this; }
+ // only one page
+ if( this.numPages === 1 ){ return this; }
+
+ // when the number of pages are known, render each page as a link
+ if( this.numPages > 0 ){
+ this._renderPages();
+ this._scrollToActivePage();
+
+ // when the number of pages is not known, render previous or next
+ } else {
+ this._renderPrevNext();
+ }
+ return this;
+ };
+
+ /** render previous and next pagination buttons */
+ Pagination.prototype._renderPrevNext = function __renderPrevNext(){
+ var pagination = this,
+ $prev = _make$Li( 'Prev' ),
+ $next = _make$Li( 'Next' ),
+ $paginationContainer = $( '<ul/>' ).addClass( 'pagination pagination-prev-next' );
+
+ // disable if it either end
+ if( this.currPage === 0 ){
+ $prev.addClass( 'disabled' );
+ } else {
+ $prev.click( function(){ pagination.prevPage(); });
+ }
+ if( ( this.numPages && this.currPage === ( this.numPages - 1 ) )
+ || ( this.options.currDataSize && this.options.currDataSize < this.options.perPage ) ){
+ $next.addClass( 'disabled' );
+ } else {
+ $next.click( function(){ pagination.nextPage(); });
+ }
+
+ this.$element.html( $paginationContainer.append([ $prev, $next ]) );
+ //console.debug( this.$element, this.$element.html() );
+ return this.$element;
+ };
+
+ /** render page links for each possible page (if we can) */
+ Pagination.prototype._renderPages = function __renderPages(){
+ // it's better to scroll the control and let the user see all pages
+ // than to force her/him to change pages in order to find the one they want (as traditional << >> does)
+ var pagination = this,
+ $scrollingContainer = $( '<div>' ).addClass( 'pagination-scroll-container' ),
+ $paginationContainer = $( '<ul/>' ).addClass( 'pagination pagination-page-list' ),
+ page$LiClick = function( ev ){
+ pagination.goToPage( $( this ).data( 'page' ) );
+ };
+
+ for( var i=0; i<this.numPages; i+=1 ){
+ // add html5 data tag 'page' for later click event handler use
+ var $pageLi = _make$Li( i + 1 ).attr( 'data-page', i ).click( page$LiClick );
+ // highlight the current page
+ if( i === this.currPage ){
+ $pageLi.addClass( 'active' );
+ }
+ //console.debug( '\t', $pageLi );
+ $paginationContainer.append( $pageLi );
+ }
+ return this.$element.html( $scrollingContainer.html( $paginationContainer ) );
+ };
+
+ /** scroll scroll-container (if any) to show the active page */
+ Pagination.prototype._scrollToActivePage = function __scrollToActivePage(){
+ // scroll to show active page in center of scrollable area
+ var $container = this.$element.find( '.pagination-scroll-container' );
+ // no scroll container : don't scroll
+ if( !$container.size() ){ return this; }
+
+ var $activePage = this.$element.find( 'li.active' ),
+ midpoint = $container.width() / 2;
+ //console.debug( $container, $activePage, midpoint );
+ $container.scrollLeft( $container.scrollLeft() + $activePage.position().left - midpoint );
+ return this;
+ };
+
+ /** go to a certain page */
+ Pagination.prototype.goToPage = function goToPage( page ){
+ if( page <= 0 ){ page = 0; }
+ if( this.numPages && page >= this.numPages ){ page = this.numPages - 1; }
+ if( page === this.currPage ){ return this; }
+
+ //console.debug( '\t going to page ' + page )
+ this.currPage = page;
+ this.$element.trigger( 'pagination.page-change', this.currPage );
+ //console.info( 'pagination:page-change', this.currPage );
+ this._render();
+ return this;
+ };
+
+ /** go to the previous page */
+ Pagination.prototype.prevPage = function prevPage(){
+ return this.goToPage( this.currPage - 1 );
+ };
+
+ /** go to the next page */
+ Pagination.prototype.nextPage = function nextPage(){
+ return this.goToPage( this.currPage + 1 );
+ };
+
+ /** return the current page */
+ Pagination.prototype.page = function page(){
+ return this.currPage;
+ };
+
+ // alternate constructor invocation
+ Pagination.create = function _create( $element, options ){
+ return new Pagination( $element, options );
+ };
+
+ // as jq plugin
+ jQuery.fn.extend({
+ pagination : function $pagination( options ){
+ var nonOptionsArgs = jQuery.makeArray( arguments ).slice( 1 );
+
+ // if passed an object - use that as an options map to create pagination for each selected
+ if( jQuery.type( options ) === 'object' ){
+ return this.map( function(){
+ Pagination.create( $( this ), options );
+ return this;
+ });
+ }
+
+ // (other invocations only work on the first element in selected)
+ var $firstElement = $( this[0] ),
+ previousControl = $firstElement.data( Pagination.prototype.DATA_KEY );
+ // if a pagination control was found for this element, either...
+ if( previousControl ){
+ // invoke a function on the pagination object if passed a string (the function name)
+ if( jQuery.type( options ) === 'string' ){
+ var fn = previousControl[ options ];
+ if( jQuery.type( fn ) === 'function' ){
+ return fn.apply( previousControl, nonOptionsArgs );
+ }
+
+ // if passed nothing, return the previously set control
+ } else {
+ return previousControl;
+ }
+ }
+ // if there is no control already set, return undefined
+ return undefined;
+ }
+ });
+}());
+
diff -r f199022e48088ab4fd71701e07899b729496b005 -r 955e973c5b33eba4e0e68979a7c20c0bd0558e37 static/style/blue/base.css
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -1390,6 +1390,12 @@
.quota-meter-bar-warn{background-color:#bf822c}
.quota-meter-bar-error{background-color:#b93e3a}
.quota-meter-text{position:absolute;top:50%;left:0;width:100px;height:16px;margin-top:-6px;text-align:center;z-index:9001;color:#000;white-space:nowrap}
+.pagination{margin:0px}
+.pagination-scroll-container{overflow:auto;background-color:#F8F8F8;border-radius:3px;border:1px solid #BFBFBF}
+.pagination-scroll-container .pagination-page-list{margin:3px 0px 3px 0px}
+.pagination-scroll-container .pagination-page-list>li:first-child>a,.pagination-scroll-container .pagination-page-list>li:first-child>span{border-radius:0px;border-left:0px}
+.pagination-scroll-container .pagination-page-list>li:last-child>a,.pagination-scroll-container .pagination-page-list>li:last-child>span{border-radius:0px}
+.pagination-scroll-container .pagination-page-list>li>a{float:none;position:static;border:1px solid #BFBFBF;border-width:0px 0px 0px 1px}
div.metadataForm{border:solid #aaaaaa 1px}
div.metadataFormTitle{font-weight:bold;padding:5px;padding-left:10px;padding-right:10px;background:#cccccc;background-repeat:repeat-x;background-position:top;border-bottom:solid #aaaaaa 1px}
div.metadataFormBody{background:#FFFFFF;padding:5px 0}
diff -r f199022e48088ab4fd71701e07899b729496b005 -r 955e973c5b33eba4e0e68979a7c20c0bd0558e37 static/style/src/less/base.less
--- a/static/style/src/less/base.less
+++ b/static/style/src/less/base.less
@@ -508,6 +508,39 @@
white-space: nowrap;
}
+// ---------------------------------------------------------------------------- pagination & scrolling pagination
+.pagination {
+ margin: 0px;
+}
+
+.pagination-scroll-container {
+ overflow : auto;
+ background-color: #F8F8F8;
+ border-radius : 3px;
+ border : 1px solid #BFBFBF;
+}
+
+.pagination-scroll-container .pagination-page-list {
+ margin : 3px 0px 3px 0px;
+}
+
+.pagination-scroll-container .pagination-page-list > li:first-child > a,
+.pagination-scroll-container .pagination-page-list > li:first-child > span {
+ border-radius : 0px;
+ border-left : 0px;
+}
+
+.pagination-scroll-container .pagination-page-list > li:last-child > a,
+.pagination-scroll-container .pagination-page-list > li:last-child > span {
+ border-radius : 0px;
+}
+
+.pagination-scroll-container .pagination-page-list > li > a {
+ float : none;
+ position : static;
+ border : 1px solid #BFBFBF;
+ border-width : 0px 0px 0px 1px;
+}
// ==== Tool form styles ====
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/e088f2dc8b07/
Changeset: e088f2dc8b07
Branch: stable
User: jmchilton
Date: 2014-02-13 18:15:10
Summary: Fix tool shed install and test framework to work with API driven tests.
Affected #: 1 file
diff -r 3691fd3e12ef8160ae993cef5ebd51ddeee95da1 -r e088f2dc8b070d13e9fb7319463786d7ff4f5024 test/install_and_test_tool_shed_repositories/base/util.py
--- a/test/install_and_test_tool_shed_repositories/base/util.py
+++ b/test/install_and_test_tool_shed_repositories/base/util.py
@@ -138,7 +138,7 @@
default_galaxy_test_host = '127.0.0.1'
# The following should be an actual value (not None). If developers manually specify their
# tests to use the API it will not work unless a master API key is specified.
-default_galaxy_master_api_key = 123456
+default_galaxy_master_api_key = '123456'
testing_single_repository_dict = {}
if 'repository_name' in os.environ and 'repository_owner' in os.environ:
https://bitbucket.org/galaxy/galaxy-central/commits/6d1dd4a389fd/
Changeset: 6d1dd4a389fd
User: jmchilton
Date: 2014-02-13 18:15:23
Summary: Merge latest stable.
Affected #: 1 file
diff -r 784f02de9effb43eafa84b5253af62091e6f2f36 -r 6d1dd4a389fd4a17fd9a7dc928860a1f0e01c214 test/install_and_test_tool_shed_repositories/base/util.py
--- a/test/install_and_test_tool_shed_repositories/base/util.py
+++ b/test/install_and_test_tool_shed_repositories/base/util.py
@@ -138,7 +138,7 @@
default_galaxy_test_host = '127.0.0.1'
# The following should be an actual value (not None). If developers manually specify their
# tests to use the API it will not work unless a master API key is specified.
-default_galaxy_master_api_key = 123456
+default_galaxy_master_api_key = '123456'
testing_single_repository_dict = {}
if 'repository_name' in os.environ and 'repository_owner' in os.environ:
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/784f02de9eff/
Changeset: 784f02de9eff
User: guerler
Date: 2014-02-13 17:27:25
Summary: Merge
Affected #: 1 file
diff -r 75a8d88ba72893daaf27e57d496ca799c20df0b9 -r 784f02de9effb43eafa84b5253af62091e6f2f36 lib/galaxy/webapps/reports/controllers/jobs.py
--- a/lib/galaxy/webapps/reports/controllers/jobs.py
+++ b/lib/galaxy/webapps/reports/controllers/jobs.py
@@ -29,7 +29,7 @@
return job.tool_id
class CreateTimeColumn( grids.DateTimeColumn ):
def get_value( self, trans, grid, job ):
- return job.create_time.isoformat()
+ return job.create_time.strftime("%b %d, %Y, %H:%M:%S")
class UserColumn( grids.GridColumn ):
def get_value( self, trans, grid, job ):
if job.user:
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: guerler: Reports: Fix time format in grids
by commits-noreply@bitbucket.org 13 Feb '14
by commits-noreply@bitbucket.org 13 Feb '14
13 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/3691fd3e12ef/
Changeset: 3691fd3e12ef
Branch: stable
User: guerler
Date: 2014-02-13 17:19:54
Summary: Reports: Fix time format in grids
Affected #: 1 file
diff -r b463c73f334811d4c48298c40de9ac489261c32d -r 3691fd3e12ef8160ae993cef5ebd51ddeee95da1 lib/galaxy/webapps/reports/controllers/jobs.py
--- a/lib/galaxy/webapps/reports/controllers/jobs.py
+++ b/lib/galaxy/webapps/reports/controllers/jobs.py
@@ -29,7 +29,7 @@
return job.tool_id
class CreateTimeColumn( grids.DateTimeColumn ):
def get_value( self, trans, grid, job ):
- return job.create_time
+ return job.create_time.strftime("%b %d, %Y, %H:%M:%S")
class UserColumn( grids.GridColumn ):
def get_value( self, trans, grid, job ):
if job.user:
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: guerler: Ui: Apply naming convention
by commits-noreply@bitbucket.org 13 Feb '14
by commits-noreply@bitbucket.org 13 Feb '14
13 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/75a8d88ba728/
Changeset: 75a8d88ba728
User: guerler
Date: 2014-02-13 10:21:06
Summary: Ui: Apply naming convention
Affected #: 12 files
diff -r b6acb69067f808147e4e69a1cde74cbb8868c600 -r 75a8d88ba72893daaf27e57d496ca799c20df0b9 static/scripts/mvc/ui/popover.js
--- a/static/scripts/mvc/ui/popover.js
+++ /dev/null
@@ -1,169 +0,0 @@
-// dependencies
-define(['utils/utils'], function(Utils) {
-
-var View = Backbone.View.extend({
-
- // default options
- optionsDefault: {
- with_close : true,
- container : 'body',
- title : null,
- placement : 'top'
- },
-
- // visibility flag
- visible: false,
-
- // initialize
- initialize: function (options) {
-
- // link this
- var self = this;
-
- // update options
- this.options = _.defaults(options, this.optionsDefault);
-
- // set element
- this.setElement(this._template(this.options));
-
- // attach popover to parent
- this.options.container.parent().append(this.$el);
-
- // attach close
- if (this.options.with_close) {
- this.$el.find('#close').on('click', function() { self.hide(); });
- }
-
- // generate unique id
- this.uuid = Utils.uuid();
-
- // add event to hide if click is outside of popup
- var self = this;
- $('body').on('mousedown.' + this.uuid, function(e) { self._hide(e) });
- },
-
- // title
- title: function(val) {
- if (val !== undefined) {
- this.$el.find('.popover-title-label').html(val);
- }
- },
-
- // show
- show: function () {
- // show popover
- this.$el.show();
- this.visible = true;
-
- // calculate position
- var position = this._get_placement(this.options.placement);
-
- // set position
- this.$el.css(position);
- },
-
- // calculate position and error
- _get_placement: function(placement) {
- // get popover dimensions
- var width = this._get_width(this.$el);
- var height = this.$el.height();
-
- // get container details
- var $container = this.options.container;
- var container_width = this._get_width($container);
- var container_height = this._get_height($container);
- var container_position = $container.position();
-
- // initialize position
- var top = 0;
- var left = 0;
-
- // calculate position
- if (placement == 'top' || placement == 'bottom') {
- left = container_position.left - width + (container_width + width) / 2;
- if (placement == 'top') {
- top = container_position.top - height - 5;
- } else {
- top = container_position.top + container_height + 5;
- }
- }
-
- // return
- return {top: top, left: left};
- },
-
- // width
- _get_width: function($el) {
- return $el.width() + parseInt($el.css('padding-left')) + parseInt($el.css('padding-right'))
- },
-
- // heigth
- _get_height: function($el) {
- return $el.height() + parseInt($el.css('padding-top')) + parseInt($el.css('padding-bottom'))
- },
-
- // hide
- hide: function () {
- this.$el.hide();
- this.visible = false;
- },
-
- // append
- append: function($el) {
- this.$el.find('.popover-content').append($el);
- },
-
- // empty
- empty: function($el) {
- this.$el.find('.popover-content').empty();
- },
-
- // remove
- remove: function() {
- // remove event handler
- $('body').off('mousedown.' + this.uuid);
-
- // remove element from dom
- this.$el.remove();
- },
-
- // remove
- _hide : function(e) {
- //the 'is' for buttons that trigger popups
- //the 'has' for icons within a button that triggers a popup
- if (!$(this.options.container).is(e.target) &&
- !$(this.el).is(e.target) &&
- $(this.el).has(e.target).length === 0) {
- this.hide();
- }
- },
-
- // template
- _template: function(options) {
- var tmpl = '<div class="popover-view popover fade ' + options.placement + ' in">' +
- '<div class="arrow"></div>' +
- '<div class="popover-title">' +
- '<div class="popover-title-label">' +
- options.title +
- '</div>';
-
- // add close icon
- if (options.with_close) {
- tmpl += '<div id="close" class="popover-close fa fa-times-circle"></div>';
- }
-
- // finalize
- tmpl += '</div>' +
- '<div class="popover-content"></div>' +
- '</div>';
-
- // return
- return tmpl;
- }
-});
-
-return {
- View: View
-}
-
-});
\ No newline at end of file
diff -r b6acb69067f808147e4e69a1cde74cbb8868c600 -r 75a8d88ba72893daaf27e57d496ca799c20df0b9 static/scripts/mvc/ui/select.js
--- a/static/scripts/mvc/ui/select.js
+++ /dev/null
@@ -1,193 +0,0 @@
-// dependencies
-define(['utils/utils'], function(Utils) {
-
-// plugin
-var View = Backbone.View.extend(
-{
- // options
- optionsDefault: {
- css : '',
- placeholder : 'No data available',
- data : []
- },
-
- // initialize
- initialize : function(options) {
- // configure options
- this.options = Utils.merge(options, this.optionsDefault);
-
- // create new element
- this.setElement(this._template(this.options));
-
- // add to dom
- this.options.container.append(this.$el);
-
- // link selection dictionary
- this.select_data = this.options.data;
-
- // refresh
- this._refresh();
-
- // initial value
- if (this.options.value) {
- this._setValue(this.options.value);
- }
-
- // add change event
- var self = this;
- if (this.options.onchange) {
- this.$el.on('change', function() {
- self.options.onchange(self.value());
- });
- }
- },
-
- // value
- value : function (new_value) {
- // get current id/value
- var before = this._getValue();
-
- // check if new_value is defined
- if (new_value !== undefined) {
- this._setValue(new_value);
- }
-
- // get current id/value
- var after = this._getValue();
- if(after === undefined) {
- return null;
- } else {
- // fire onchange
- if ((after != before && this.options.onchange)) {
- this.options.onchange(after);
- }
-
- // return current value
- return after;
- }
- },
-
- // label
- text : function () {
- return this.$el.select2('data').text;
- },
-
- // disabled
- disabled: function() {
- return !this.$el.select2('enable');
- },
-
- // enable
- enable: function() {
- this.$el.select2('enable', true);
- },
-
- // disable
- disable: function() {
- this.$el.select2('enable', false);
- },
-
- // add
- add: function(options) {
- // add options
- this.select_data.push({
- id : options.id,
- text : options.text
- });
-
- // refresh
- this._refresh();
- },
-
- // remove
- del: function(id) {
- // search option
- var index = this._getIndex(id);
-
- // check if found
- if (index != -1) {
- // remove options
- this.select_data.splice(index, 1);
-
- // refresh
- this._refresh();
- }
- },
-
- // remove
- remove: function() {
- this.$el.select2('destroy');
- },
-
- // update
- update: function(options) {
- // copy options
- this.select_data = [];
- for (var key in options.data) {
- this.select_data.push(options.data[key]);
- }
-
- // refresh
- this._refresh();
- },
-
- // refresh
- _refresh: function() {
-
- // link select data
- var select_data = this.select_data;
- if (!select_data || select_data.length == 0) {
- select_data = [];
- }
-
- // selected
- var selected = this._getValue();
-
- // add select2 data
- this.$el.select2({
- data : select_data,
- containerCssClass : this.options.css,
- placeholder : this.options.placeholder
- });
-
- // select
- var index = this._getIndex(selected);
- if (index != -1) {
- this._setValue(selected);
- }
- },
-
- // get index
- _getIndex: function(value) {
- // search index
- for (var key in this.select_data) {
- if (this.select_data[key].id == value) {
- return key;
- }
- }
-
- // not found
- return -1;
- },
-
- // get value
- _getValue: function() {
- return this.$el.select2('val');
- },
-
- // set value
- _setValue: function(new_value) {
- this.$el.select2('val', new_value);
- },
-
- // element
- _template: function(options) {
- return '<input type="hidden"/>';
- }
-});
-
-return {
- View : View
-}
-
-});
diff -r b6acb69067f808147e4e69a1cde74cbb8868c600 -r 75a8d88ba72893daaf27e57d496ca799c20df0b9 static/scripts/mvc/ui/ui-popover.js
--- /dev/null
+++ b/static/scripts/mvc/ui/ui-popover.js
@@ -0,0 +1,169 @@
+// dependencies
+define(['utils/utils'], function(Utils) {
+
+var View = Backbone.View.extend({
+
+ // default options
+ optionsDefault: {
+ with_close : true,
+ container : 'body',
+ title : null,
+ placement : 'top'
+ },
+
+ // visibility flag
+ visible: false,
+
+ // initialize
+ initialize: function (options) {
+
+ // link this
+ var self = this;
+
+ // update options
+ this.options = _.defaults(options, this.optionsDefault);
+
+ // set element
+ this.setElement(this._template(this.options));
+
+ // attach popover to parent
+ this.options.container.parent().append(this.$el);
+
+ // attach close
+ if (this.options.with_close) {
+ this.$el.find('#close').on('click', function() { self.hide(); });
+ }
+
+ // generate unique id
+ this.uuid = Utils.uuid();
+
+ // add event to hide if click is outside of popup
+ var self = this;
+ $('body').on('mousedown.' + this.uuid, function(e) { self._hide(e) });
+ },
+
+ // title
+ title: function(val) {
+ if (val !== undefined) {
+ this.$el.find('.popover-title-label').html(val);
+ }
+ },
+
+ // show
+ show: function () {
+ // show popover
+ this.$el.show();
+ this.visible = true;
+
+ // calculate position
+ var position = this._get_placement(this.options.placement);
+
+ // set position
+ this.$el.css(position);
+ },
+
+ // calculate position and error
+ _get_placement: function(placement) {
+ // get popover dimensions
+ var width = this._get_width(this.$el);
+ var height = this.$el.height();
+
+ // get container details
+ var $container = this.options.container;
+ var container_width = this._get_width($container);
+ var container_height = this._get_height($container);
+ var container_position = $container.position();
+
+ // initialize position
+ var top = 0;
+ var left = 0;
+
+ // calculate position
+ if (placement == 'top' || placement == 'bottom') {
+ left = container_position.left - width + (container_width + width) / 2;
+ if (placement == 'top') {
+ top = container_position.top - height - 5;
+ } else {
+ top = container_position.top + container_height + 5;
+ }
+ }
+
+ // return
+ return {top: top, left: left};
+ },
+
+ // width
+ _get_width: function($el) {
+ return $el.width() + parseInt($el.css('padding-left')) + parseInt($el.css('padding-right'))
+ },
+
+ // heigth
+ _get_height: function($el) {
+ return $el.height() + parseInt($el.css('padding-top')) + parseInt($el.css('padding-bottom'))
+ },
+
+ // hide
+ hide: function () {
+ this.$el.hide();
+ this.visible = false;
+ },
+
+ // append
+ append: function($el) {
+ this.$el.find('.popover-content').append($el);
+ },
+
+ // empty
+ empty: function($el) {
+ this.$el.find('.popover-content').empty();
+ },
+
+ // remove
+ remove: function() {
+ // remove event handler
+ $('body').off('mousedown.' + this.uuid);
+
+ // remove element from dom
+ this.$el.remove();
+ },
+
+ // remove
+ _hide : function(e) {
+ //the 'is' for buttons that trigger popups
+ //the 'has' for icons within a button that triggers a popup
+ if (!$(this.options.container).is(e.target) &&
+ !$(this.el).is(e.target) &&
+ $(this.el).has(e.target).length === 0) {
+ this.hide();
+ }
+ },
+
+ // template
+ _template: function(options) {
+ var tmpl = '<div class="popover-view popover fade ' + options.placement + ' in">' +
+ '<div class="arrow"></div>' +
+ '<div class="popover-title">' +
+ '<div class="popover-title-label">' +
+ options.title +
+ '</div>';
+
+ // add close icon
+ if (options.with_close) {
+ tmpl += '<div id="close" class="popover-close fa fa-times-circle"></div>';
+ }
+
+ // finalize
+ tmpl += '</div>' +
+ '<div class="popover-content"></div>' +
+ '</div>';
+
+ // return
+ return tmpl;
+ }
+});
+
+return {
+ View: View
+}
+
+});
\ No newline at end of file
diff -r b6acb69067f808147e4e69a1cde74cbb8868c600 -r 75a8d88ba72893daaf27e57d496ca799c20df0b9 static/scripts/mvc/ui/ui-select.js
--- /dev/null
+++ b/static/scripts/mvc/ui/ui-select.js
@@ -0,0 +1,193 @@
+// dependencies
+define(['utils/utils'], function(Utils) {
+
+// plugin
+var View = Backbone.View.extend(
+{
+ // options
+ optionsDefault: {
+ css : '',
+ placeholder : 'No data available',
+ data : []
+ },
+
+ // initialize
+ initialize : function(options) {
+ // configure options
+ this.options = Utils.merge(options, this.optionsDefault);
+
+ // create new element
+ this.setElement(this._template(this.options));
+
+ // add to dom
+ this.options.container.append(this.$el);
+
+ // link selection dictionary
+ this.select_data = this.options.data;
+
+ // refresh
+ this._refresh();
+
+ // initial value
+ if (this.options.value) {
+ this._setValue(this.options.value);
+ }
+
+ // add change event
+ var self = this;
+ if (this.options.onchange) {
+ this.$el.on('change', function() {
+ self.options.onchange(self.value());
+ });
+ }
+ },
+
+ // value
+ value : function (new_value) {
+ // get current id/value
+ var before = this._getValue();
+
+ // check if new_value is defined
+ if (new_value !== undefined) {
+ this._setValue(new_value);
+ }
+
+ // get current id/value
+ var after = this._getValue();
+ if(after === undefined) {
+ return null;
+ } else {
+ // fire onchange
+ if ((after != before && this.options.onchange)) {
+ this.options.onchange(after);
+ }
+
+ // return current value
+ return after;
+ }
+ },
+
+ // label
+ text : function () {
+ return this.$el.select2('data').text;
+ },
+
+ // disabled
+ disabled: function() {
+ return !this.$el.select2('enable');
+ },
+
+ // enable
+ enable: function() {
+ this.$el.select2('enable', true);
+ },
+
+ // disable
+ disable: function() {
+ this.$el.select2('enable', false);
+ },
+
+ // add
+ add: function(options) {
+ // add options
+ this.select_data.push({
+ id : options.id,
+ text : options.text
+ });
+
+ // refresh
+ this._refresh();
+ },
+
+ // remove
+ del: function(id) {
+ // search option
+ var index = this._getIndex(id);
+
+ // check if found
+ if (index != -1) {
+ // remove options
+ this.select_data.splice(index, 1);
+
+ // refresh
+ this._refresh();
+ }
+ },
+
+ // remove
+ remove: function() {
+ this.$el.select2('destroy');
+ },
+
+ // update
+ update: function(options) {
+ // copy options
+ this.select_data = [];
+ for (var key in options.data) {
+ this.select_data.push(options.data[key]);
+ }
+
+ // refresh
+ this._refresh();
+ },
+
+ // refresh
+ _refresh: function() {
+
+ // link select data
+ var select_data = this.select_data;
+ if (!select_data || select_data.length == 0) {
+ select_data = [];
+ }
+
+ // selected
+ var selected = this._getValue();
+
+ // add select2 data
+ this.$el.select2({
+ data : select_data,
+ containerCssClass : this.options.css,
+ placeholder : this.options.placeholder
+ });
+
+ // select
+ var index = this._getIndex(selected);
+ if (index != -1) {
+ this._setValue(selected);
+ }
+ },
+
+ // get index
+ _getIndex: function(value) {
+ // search index
+ for (var key in this.select_data) {
+ if (this.select_data[key].id == value) {
+ return key;
+ }
+ }
+
+ // not found
+ return -1;
+ },
+
+ // get value
+ _getValue: function() {
+ return this.$el.select2('val');
+ },
+
+ // set value
+ _setValue: function(new_value) {
+ this.$el.select2('val', new_value);
+ },
+
+ // element
+ _template: function(options) {
+ return '<input type="hidden"/>';
+ }
+});
+
+return {
+ View : View
+}
+
+});
diff -r b6acb69067f808147e4e69a1cde74cbb8868c600 -r 75a8d88ba72893daaf27e57d496ca799c20df0b9 static/scripts/mvc/upload/upload-row.js
--- a/static/scripts/mvc/upload/upload-row.js
+++ b/static/scripts/mvc/upload/upload-row.js
@@ -3,8 +3,8 @@
'mvc/upload/upload-model',
'mvc/upload/upload-extensions',
'mvc/upload/upload-settings',
- 'mvc/ui/popover',
- 'mvc/ui/select'],
+ 'mvc/ui/ui-popover',
+ 'mvc/ui/ui-select'],
function( Utils,
UploadModel,
diff -r b6acb69067f808147e4e69a1cde74cbb8868c600 -r 75a8d88ba72893daaf27e57d496ca799c20df0b9 static/scripts/mvc/upload/upload-view.js
--- a/static/scripts/mvc/upload/upload-view.js
+++ b/static/scripts/mvc/upload/upload-view.js
@@ -5,7 +5,7 @@
"mvc/upload/upload-model",
"mvc/upload/upload-row",
"mvc/upload/upload-ftp",
- "mvc/ui/popover",
+ "mvc/ui/ui-popover",
"mvc/ui",
"utils/uploadbox"],
diff -r b6acb69067f808147e4e69a1cde74cbb8868c600 -r 75a8d88ba72893daaf27e57d496ca799c20df0b9 static/scripts/packed/mvc/ui/popover.js
--- a/static/scripts/packed/mvc/ui/popover.js
+++ /dev/null
@@ -1,1 +0,0 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{with_close:true,container:"body",title:null,placement:"top"},visible:false,initialize:function(d){var c=this;this.options=_.defaults(d,this.optionsDefault);this.setElement(this._template(this.options));this.options.container.parent().append(this.$el);if(this.options.with_close){this.$el.find("#close").on("click",function(){c.hide()})}this.uuid=a.uuid();var c=this;$("body").on("mousedown."+this.uuid,function(f){c._hide(f)})},title:function(c){if(c!==undefined){this.$el.find(".popover-title-label").html(c)}},show:function(){this.$el.show();this.visible=true;var c=this._get_placement(this.options.placement);this.$el.css(c)},_get_placement:function(h){var d=this._get_width(this.$el);var j=this.$el.height();var k=this.options.container;var c=this._get_width(k);var f=this._get_height(k);var g=k.position();var i=0;var e=0;if(h=="top"||h=="bottom"){e=g.left-d+(c+d)/2;if(h=="top"){i=g.top-j-5}else{i=g.top+f+5}}return{top:i,left:e}},_get_width:function(c){return c.width()+parseInt(c.css("padding-left"))+parseInt(c.css("padding-right"))},_get_height:function(c){return c.height()+parseInt(c.css("padding-top"))+parseInt(c.css("padding-bottom"))},hide:function(){this.$el.hide();this.visible=false},append:function(c){this.$el.find(".popover-content").append(c)},empty:function(c){this.$el.find(".popover-content").empty()},remove:function(){$("body").off("mousedown."+this.uuid);this.$el.remove()},_hide:function(c){if(!$(this.options.container).is(c.target)&&!$(this.el).is(c.target)&&$(this.el).has(c.target).length===0){this.hide()}},_template:function(d){var c='<div class="popover-view popover fade '+d.placement+' in"><div class="arrow"></div><div class="popover-title"><div class="popover-title-label">'+d.title+"</div>";if(d.with_close){c+='<div id="close" class="popover-close fa fa-times-circle"></div>'}c+='</div><div class="popover-content"></div></div>';return c}});return{View:b}});
\ No newline at end of file
diff -r b6acb69067f808147e4e69a1cde74cbb8868c600 -r 75a8d88ba72893daaf27e57d496ca799c20df0b9 static/scripts/packed/mvc/ui/select.js
--- a/static/scripts/packed/mvc/ui/select.js
+++ /dev/null
@@ -1,1 +0,0 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{css:"",placeholder:"No data available",data:[]},initialize:function(d){this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.options.container.append(this.$el);this.select_data=this.options.data;this._refresh();if(this.options.value){this._setValue(this.options.value)}var c=this;if(this.options.onchange){this.$el.on("change",function(){c.options.onchange(c.value())})}},value:function(c){var d=this._getValue();if(c!==undefined){this._setValue(c)}var e=this._getValue();if(e===undefined){return null}else{if((e!=d&&this.options.onchange)){this.options.onchange(e)}return e}},text:function(){return this.$el.select2("data").text},disabled:function(){return !this.$el.select2("enable")},enable:function(){this.$el.select2("enable",true)},disable:function(){this.$el.select2("enable",false)},add:function(c){this.select_data.push({id:c.id,text:c.text});this._refresh()},del:function(d){var c=this._getIndex(d);if(c!=-1){this.select_data.splice(c,1);this._refresh()}},remove:function(){this.$el.select2("destroy")},update:function(c){this.select_data=[];for(var d in c.data){this.select_data.push(c.data[d])}this._refresh()},_refresh:function(){var e=this.select_data;if(!e||e.length==0){e=[]}var d=this._getValue();this.$el.select2({data:e,containerCssClass:this.options.css,placeholder:this.options.placeholder});var c=this._getIndex(d);if(c!=-1){this._setValue(d)}},_getIndex:function(d){for(var c in this.select_data){if(this.select_data[c].id==d){return c}}return -1},_getValue:function(){return this.$el.select2("val")},_setValue:function(c){this.$el.select2("val",c)},_template:function(c){return'<input type="hidden"/>'}});return{View:b}});
\ No newline at end of file
diff -r b6acb69067f808147e4e69a1cde74cbb8868c600 -r 75a8d88ba72893daaf27e57d496ca799c20df0b9 static/scripts/packed/mvc/ui/ui-popover.js
--- /dev/null
+++ b/static/scripts/packed/mvc/ui/ui-popover.js
@@ -0,0 +1,1 @@
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{with_close:true,container:"body",title:null,placement:"top"},visible:false,initialize:function(d){var c=this;this.options=_.defaults(d,this.optionsDefault);this.setElement(this._template(this.options));this.options.container.parent().append(this.$el);if(this.options.with_close){this.$el.find("#close").on("click",function(){c.hide()})}this.uuid=a.uuid();var c=this;$("body").on("mousedown."+this.uuid,function(f){c._hide(f)})},title:function(c){if(c!==undefined){this.$el.find(".popover-title-label").html(c)}},show:function(){this.$el.show();this.visible=true;var c=this._get_placement(this.options.placement);this.$el.css(c)},_get_placement:function(h){var d=this._get_width(this.$el);var j=this.$el.height();var k=this.options.container;var c=this._get_width(k);var f=this._get_height(k);var g=k.position();var i=0;var e=0;if(h=="top"||h=="bottom"){e=g.left-d+(c+d)/2;if(h=="top"){i=g.top-j-5}else{i=g.top+f+5}}return{top:i,left:e}},_get_width:function(c){return c.width()+parseInt(c.css("padding-left"))+parseInt(c.css("padding-right"))},_get_height:function(c){return c.height()+parseInt(c.css("padding-top"))+parseInt(c.css("padding-bottom"))},hide:function(){this.$el.hide();this.visible=false},append:function(c){this.$el.find(".popover-content").append(c)},empty:function(c){this.$el.find(".popover-content").empty()},remove:function(){$("body").off("mousedown."+this.uuid);this.$el.remove()},_hide:function(c){if(!$(this.options.container).is(c.target)&&!$(this.el).is(c.target)&&$(this.el).has(c.target).length===0){this.hide()}},_template:function(d){var c='<div class="popover-view popover fade '+d.placement+' in"><div class="arrow"></div><div class="popover-title"><div class="popover-title-label">'+d.title+"</div>";if(d.with_close){c+='<div id="close" class="popover-close fa fa-times-circle"></div>'}c+='</div><div class="popover-content"></div></div>';return c}});return{View:b}});
\ No newline at end of file
diff -r b6acb69067f808147e4e69a1cde74cbb8868c600 -r 75a8d88ba72893daaf27e57d496ca799c20df0b9 static/scripts/packed/mvc/ui/ui-select.js
--- /dev/null
+++ b/static/scripts/packed/mvc/ui/ui-select.js
@@ -0,0 +1,1 @@
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{css:"",placeholder:"No data available",data:[]},initialize:function(d){this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.options.container.append(this.$el);this.select_data=this.options.data;this._refresh();if(this.options.value){this._setValue(this.options.value)}var c=this;if(this.options.onchange){this.$el.on("change",function(){c.options.onchange(c.value())})}},value:function(c){var d=this._getValue();if(c!==undefined){this._setValue(c)}var e=this._getValue();if(e===undefined){return null}else{if((e!=d&&this.options.onchange)){this.options.onchange(e)}return e}},text:function(){return this.$el.select2("data").text},disabled:function(){return !this.$el.select2("enable")},enable:function(){this.$el.select2("enable",true)},disable:function(){this.$el.select2("enable",false)},add:function(c){this.select_data.push({id:c.id,text:c.text});this._refresh()},del:function(d){var c=this._getIndex(d);if(c!=-1){this.select_data.splice(c,1);this._refresh()}},remove:function(){this.$el.select2("destroy")},update:function(c){this.select_data=[];for(var d in c.data){this.select_data.push(c.data[d])}this._refresh()},_refresh:function(){var e=this.select_data;if(!e||e.length==0){e=[]}var d=this._getValue();this.$el.select2({data:e,containerCssClass:this.options.css,placeholder:this.options.placeholder});var c=this._getIndex(d);if(c!=-1){this._setValue(d)}},_getIndex:function(d){for(var c in this.select_data){if(this.select_data[c].id==d){return c}}return -1},_getValue:function(){return this.$el.select2("val")},_setValue:function(c){this.$el.select2("val",c)},_template:function(c){return'<input type="hidden"/>'}});return{View:b}});
\ No newline at end of file
diff -r b6acb69067f808147e4e69a1cde74cbb8868c600 -r 75a8d88ba72893daaf27e57d496ca799c20df0b9 static/scripts/packed/mvc/upload/upload-row.js
--- a/static/scripts/packed/mvc/upload/upload-row.js
+++ b/static/scripts/packed/mvc/upload/upload-row.js
@@ -1,1 +1,1 @@
-define(["utils/utils","mvc/upload/upload-model","mvc/upload/upload-extensions","mvc/upload/upload-settings","mvc/ui/popover","mvc/ui/select"],function(d,b,f,a,c,e){return Backbone.View.extend({options:{padding:8},status_classes:{init:"upload-icon-button fa fa-trash-o",queued:"upload-icon fa fa-spinner fa-spin",running:"upload-icon fa fa-spinner fa-spin",success:"upload-icon-button fa fa-check",error:"upload-icon-button fa fa-exclamation-triangle"},settings:null,select_genome:null,select_extension:null,initialize:function(j,h){this.app=j;var g=this;this.model=new b.Model(h);this.setElement(this._template(h));var i=this.$el;this.settings=new c.View({title:"Upload configuration",container:i.find("#settings"),placement:"bottom"});this.select_genome=new e.View({css:"genome",onchange:function(){g.model.set("genome",g.select_genome.value())},data:g.app.list_genomes,container:i.find("#genome"),value:g.model.get("genome")});this.select_extension=new e.View({css:"extension",onchange:function(){g.model.set("extension",g.select_extension.value())},data:g.app.list_extensions,container:i.find("#extension"),value:g.model.get("extension")});i.find("#symbol").on("click",function(){g._removeRow()});i.find("#extension-info").on("click",function(k){g._showExtensionInfo()}).on("mousedown",function(k){k.preventDefault()});i.find("#settings").on("click",function(k){g._showSettings()}).on("mousedown",function(k){k.preventDefault()});i.find("#text-content").on("keyup",function(k){g.model.set("url_paste",$(k.target).val());g.model.set("file_size",$(k.target).val().length)});i.find("#space_to_tabs").on("change",function(k){g.model.set("space_to_tabs",$(k.target).prop("checked"))});this.model.on("change:percentage",function(){g._refreshPercentage()});this.model.on("change:status",function(){g._refreshStatus()});this.model.on("change:info",function(){g._refreshInfo()});this.model.on("change:genome",function(){g._refreshGenome()});this.model.on("change:file_size",function(){g._refreshFileSize()});this.model.on("remove",function(){g.remove()});this.app.collection.on("reset",function(){g.remove()})},render:function(){var n=this.model.get("file_name");var h=this.model.get("file_size");var k=this.model.get("file_mode");var j=this.$el;j.find("#title").html(n);j.find("#size").html(d.bytesToString(h));j.find("#mode").removeClass().addClass("mode");if(k=="new"){var m=j.find("#text");var l=this.options.padding;var i=j.width()-2*l;var g=j.height()-l;m.css("width",i+"px");m.css("top",g+"px");j.height(g+m.height()+2*l);m.show();j.find("#mode").addClass("fa fa-pencil")}if(k=="local"){j.find("#mode").addClass("fa fa-laptop")}if(k=="ftp"){j.find("#mode").addClass("fa fa-code-fork")}},remove:function(){this.select_genome.remove();this.select_extension.remove();Backbone.View.prototype.remove.apply(this)},_refreshGenome:function(){var g=this.model.get("genome");this.select_genome.value(g)},_refreshInfo:function(){var g=this.model.get("info");if(g){this.$el.find("#info").html("<strong>Failed: </strong>"+g).show()}else{this.$el.find("#info").hide()}},_refreshPercentage:function(){var g=parseInt(this.model.get("percentage"));this.$el.find(".progress-bar").css({width:g+"%"});if(g!=100){this.$el.find("#percentage").html(g+"%")}else{this.$el.find("#percentage").html("Adding to history...")}},_refreshStatus:function(){var h=this.$el;var g=this.model.get("status");var j=this.status_classes[g];var i=this.$el.find("#symbol");i.removeClass();i.addClass(j);if(g=="init"){this.select_genome.enable();this.select_extension.enable();h.find("#text-content").attr("disabled",false);h.find("#space_to_tabs").attr("disabled",false)}else{this.select_genome.disable();this.select_extension.disable();h.find("#text-content").attr("disabled",true);h.find("#space_to_tabs").attr("disabled",true)}if(g=="success"){h.addClass("success");h.find("#percentage").html("100%")}if(g=="error"){h.addClass("danger");h.find(".progress").remove()}},_refreshFileSize:function(){var g=this.model.get("file_size");this.$el.find("#size").html(d.bytesToString(g))},_removeRow:function(){var g=this.model.get("status");if(g=="init"||g=="success"||g=="error"){this.app.collection.remove(this.model)}},_showExtensionInfo:function(){var g=$(this.el).find("#extension-info");var i=this.model.get("extension");var h=this.select_extension.text();if(!this.extension_popup){this.extension_popup=new c.View({content:f(i),placement:"bottom",container:g})}if(!this.extension_popup.visible){this.extension_popup.title(h);this.extension_popup.empty();this.extension_popup.append(f(i));this.extension_popup.show()}else{this.extension_popup.hide()}},_showSettings:function(){if(!this.settings.visible){this.settings.empty();this.settings.append((new a(this)).$el);this.settings.show()}else{this.settings.hide()}},_template:function(g){return'<tr id="upload-item-'+g.id+'" class="upload-item"><td><div style="position: relative;"><div id="mode"></div><div id="title" class="title"></div><div id="text" class="text"><div class="text-info">You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.</div><textarea id="text-content" class="text-content form-control"></textarea></div></div></td><td><div id="size" class="size"></div></td><td><div id="extension" class="extension" style="float: left;"/>  <div id="extension-info" class="upload-icon-button fa fa-search"/></td><td><div id="genome" class="genome" /></td><td><div id="settings" class="upload-icon-button fa fa-gear"></div><td><div id="info" class="info"><div class="progress"><div class="progress-bar progress-bar-success"></div><div id="percentage" class="percentage">0%</div></div></div></td><td><div id="symbol" class="'+this.status_classes.init+'"></div></td></tr>'}})});
\ No newline at end of file
+define(["utils/utils","mvc/upload/upload-model","mvc/upload/upload-extensions","mvc/upload/upload-settings","mvc/ui/ui-popover","mvc/ui/ui-select"],function(d,b,f,a,c,e){return Backbone.View.extend({options:{padding:8},status_classes:{init:"upload-icon-button fa fa-trash-o",queued:"upload-icon fa fa-spinner fa-spin",running:"upload-icon fa fa-spinner fa-spin",success:"upload-icon-button fa fa-check",error:"upload-icon-button fa fa-exclamation-triangle"},settings:null,select_genome:null,select_extension:null,initialize:function(j,h){this.app=j;var g=this;this.model=new b.Model(h);this.setElement(this._template(h));var i=this.$el;this.settings=new c.View({title:"Upload configuration",container:i.find("#settings"),placement:"bottom"});this.select_genome=new e.View({css:"genome",onchange:function(){g.model.set("genome",g.select_genome.value())},data:g.app.list_genomes,container:i.find("#genome"),value:g.model.get("genome")});this.select_extension=new e.View({css:"extension",onchange:function(){g.model.set("extension",g.select_extension.value())},data:g.app.list_extensions,container:i.find("#extension"),value:g.model.get("extension")});i.find("#symbol").on("click",function(){g._removeRow()});i.find("#extension-info").on("click",function(k){g._showExtensionInfo()}).on("mousedown",function(k){k.preventDefault()});i.find("#settings").on("click",function(k){g._showSettings()}).on("mousedown",function(k){k.preventDefault()});i.find("#text-content").on("keyup",function(k){g.model.set("url_paste",$(k.target).val());g.model.set("file_size",$(k.target).val().length)});i.find("#space_to_tabs").on("change",function(k){g.model.set("space_to_tabs",$(k.target).prop("checked"))});this.model.on("change:percentage",function(){g._refreshPercentage()});this.model.on("change:status",function(){g._refreshStatus()});this.model.on("change:info",function(){g._refreshInfo()});this.model.on("change:genome",function(){g._refreshGenome()});this.model.on("change:file_size",function(){g._refreshFileSize()});this.model.on("remove",function(){g.remove()});this.app.collection.on("reset",function(){g.remove()})},render:function(){var n=this.model.get("file_name");var h=this.model.get("file_size");var k=this.model.get("file_mode");var j=this.$el;j.find("#title").html(n);j.find("#size").html(d.bytesToString(h));j.find("#mode").removeClass().addClass("mode");if(k=="new"){var m=j.find("#text");var l=this.options.padding;var i=j.width()-2*l;var g=j.height()-l;m.css("width",i+"px");m.css("top",g+"px");j.height(g+m.height()+2*l);m.show();j.find("#mode").addClass("fa fa-pencil")}if(k=="local"){j.find("#mode").addClass("fa fa-laptop")}if(k=="ftp"){j.find("#mode").addClass("fa fa-code-fork")}},remove:function(){this.select_genome.remove();this.select_extension.remove();Backbone.View.prototype.remove.apply(this)},_refreshGenome:function(){var g=this.model.get("genome");this.select_genome.value(g)},_refreshInfo:function(){var g=this.model.get("info");if(g){this.$el.find("#info").html("<strong>Failed: </strong>"+g).show()}else{this.$el.find("#info").hide()}},_refreshPercentage:function(){var g=parseInt(this.model.get("percentage"));this.$el.find(".progress-bar").css({width:g+"%"});if(g!=100){this.$el.find("#percentage").html(g+"%")}else{this.$el.find("#percentage").html("Adding to history...")}},_refreshStatus:function(){var h=this.$el;var g=this.model.get("status");var j=this.status_classes[g];var i=this.$el.find("#symbol");i.removeClass();i.addClass(j);if(g=="init"){this.select_genome.enable();this.select_extension.enable();h.find("#text-content").attr("disabled",false);h.find("#space_to_tabs").attr("disabled",false)}else{this.select_genome.disable();this.select_extension.disable();h.find("#text-content").attr("disabled",true);h.find("#space_to_tabs").attr("disabled",true)}if(g=="success"){h.addClass("success");h.find("#percentage").html("100%")}if(g=="error"){h.addClass("danger");h.find(".progress").remove()}},_refreshFileSize:function(){var g=this.model.get("file_size");this.$el.find("#size").html(d.bytesToString(g))},_removeRow:function(){var g=this.model.get("status");if(g=="init"||g=="success"||g=="error"){this.app.collection.remove(this.model)}},_showExtensionInfo:function(){var g=$(this.el).find("#extension-info");var i=this.model.get("extension");var h=this.select_extension.text();if(!this.extension_popup){this.extension_popup=new c.View({content:f(i),placement:"bottom",container:g})}if(!this.extension_popup.visible){this.extension_popup.title(h);this.extension_popup.empty();this.extension_popup.append(f(i));this.extension_popup.show()}else{this.extension_popup.hide()}},_showSettings:function(){if(!this.settings.visible){this.settings.empty();this.settings.append((new a(this)).$el);this.settings.show()}else{this.settings.hide()}},_template:function(g){return'<tr id="upload-item-'+g.id+'" class="upload-item"><td><div style="position: relative;"><div id="mode"></div><div id="title" class="title"></div><div id="text" class="text"><div class="text-info">You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.</div><textarea id="text-content" class="text-content form-control"></textarea></div></div></td><td><div id="size" class="size"></div></td><td><div id="extension" class="extension" style="float: left;"/>  <div id="extension-info" class="upload-icon-button fa fa-search"/></td><td><div id="genome" class="genome" /></td><td><div id="settings" class="upload-icon-button fa fa-gear"></div><td><div id="info" class="info"><div class="progress"><div class="progress-bar progress-bar-success"></div><div id="percentage" class="percentage">0%</div></div></div></td><td><div id="symbol" class="'+this.status_classes.init+'"></div></td></tr>'}})});
\ No newline at end of file
diff -r b6acb69067f808147e4e69a1cde74cbb8868c600 -r 75a8d88ba72893daaf27e57d496ca799c20df0b9 static/scripts/packed/mvc/upload/upload-view.js
--- a/static/scripts/packed/mvc/upload/upload-view.js
+++ b/static/scripts/packed/mvc/upload/upload-view.js
@@ -1,1 +1,1 @@
-define(["galaxy.modal","utils/utils","mvc/upload/upload-button","mvc/upload/upload-model","mvc/upload/upload-row","mvc/upload/upload-ftp","mvc/ui/popover","mvc/ui","utils/uploadbox"],function(a,f,e,c,b,g,d){return Backbone.View.extend({options:{nginx_upload_path:""},modal:null,ui_button:null,uploadbox:null,current_history:null,upload_size:0,list_extensions:[{id:"auto",text:"Auto-detect"}],list_genomes:[],collection:new c.Collection(),ftp:null,counter:{announce:0,success:0,error:0,running:0,reset:function(){this.announce=this.success=this.error=this.running=0}},initialize:function(i){var h=this;if(!Galaxy.currHistoryPanel||!Galaxy.currHistoryPanel.model){window.setTimeout(function(){h.initialize()},500);return}if(!Galaxy.currUser.get("id")){return}this.ui_button=new e.Model({icon:"fa-upload",tooltip:"Download from URL or upload files from disk",label:"Load Data",onclick:function(j){if(j){h._eventShow(j)}},onunload:function(){if(h.counter.running>0){return"Several uploads are still processing."}}});$("#left .unified-panel-header-inner").append((new e.View(this.ui_button)).$el);var h=this;f.jsonFromUrl(galaxy_config.root+"api/datatypes?upload_only=True",function(j){for(key in j){h.list_extensions.push({id:j[key],text:j[key]})}});f.jsonFromUrl(galaxy_config.root+"api/genomes",function(j){for(key in j){h.list_genomes.push({id:j[key][1],text:j[key][0]})}h.list_genomes.sort(function(l,k){return l.id>k.id?1:l.id<k.id?-1:0})});if(i){this.options=_.defaults(i,this.options)}this.collection.on("remove",function(j){h._eventRemove(j)});this.collection.on("change:genome",function(k){var j=k.get("genome");h.collection.each(function(l){if(l.get("status")=="init"&&l.get("genome")=="?"){l.set("genome",j)}})})},_eventShow:function(j){j.preventDefault();if(!this.modal){var h=this;this.modal=new a.GalaxyModal({title:"Download data directly from web or upload files from your disk",body:this._template("upload-box","upload-info"),buttons:{"Choose local file":function(){h.uploadbox.select()},"Choose FTP file":function(){h._eventFtp()},"Create new file":function(){h._eventCreate()},Start:function(){h._eventStart()},Pause:function(){h._eventStop()},Reset:function(){h._eventReset()},Close:function(){h.modal.hide()},},height:"400",width:"900",closing_events:true});this.setElement("#upload-box");var h=this;this.uploadbox=this.$el.uploadbox({announce:function(k,l,m){h._eventAnnounce(k,l,m)},initialize:function(k,l,m){return h._eventInitialize(k,l,m)},progress:function(k,l,m){h._eventProgress(k,l,m)},success:function(k,l,m){h._eventSuccess(k,l,m)},error:function(k,l,m){h._eventError(k,l,m)},complete:function(){h._eventComplete()}});this._updateScreen();if(this.options.ftp_upload_dir&&this.options.ftp_upload_site){var i=this.modal.getButton("Choose FTP file");this.ftp=new d.View({title:"FTP files",container:i})}else{this.modal.hideButton("Choose FTP file")}}this.modal.show()},_eventRemove:function(i){var h=i.get("status");if(h=="success"){this.counter.success--}else{if(h=="error"){this.counter.error--}else{this.counter.announce--}}this._updateScreen();this.uploadbox.remove(i.id)},_eventAnnounce:function(h,i,k){this.counter.announce++;this._updateScreen();var j=new b(this,{id:h,file_name:i.name,file_size:i.size,file_mode:i.mode,file_path:i.path});this.collection.add(j.model);$(this.el).find("tbody:first").append(j.$el);j.render()},_eventInitialize:function(m,j,s){var k=this.collection.get(m);k.set("status","running");var o=k.get("file_name");var n=k.get("file_path");var h=k.get("file_mode");var p=k.get("extension");var r=k.get("genome");var q=k.get("url_paste");var l=k.get("space_to_tabs");var i=k.get("to_posix_lines");if(!q&&!(j.size>0)){return null}this.uploadbox.configure({url:this.options.nginx_upload_path});if(h=="local"){this.uploadbox.configure({paramname:"files_0|file_data"})}else{this.uploadbox.configure({paramname:null})}tool_input={};if(h=="new"){tool_input["files_0|url_paste"]=q}if(h=="ftp"){tool_input["files_0|ftp_files"]=n}tool_input.dbkey=r;tool_input.file_type=p;tool_input["files_0|type"]="upload_dataset";tool_input.space_to_tabs=l;tool_input.to_posix_lines=i;data={};data.history_id=this.current_history;data.tool_id="upload1";data.inputs=JSON.stringify(tool_input);return data},_eventProgress:function(i,j,h){var k=this.collection.get(i);k.set("percentage",h);this.ui_button.set("percentage",this._upload_percentage(h,j.size))},_eventSuccess:function(i,j,l){var k=this.collection.get(i);k.set("percentage",100);k.set("status","success");var h=k.get("file_size");this.ui_button.set("percentage",this._upload_percentage(100,h));this.upload_completed+=h*100;this.counter.announce--;this.counter.success++;this._updateScreen();Galaxy.currHistoryPanel.refreshHdas()},_eventError:function(h,i,k){var j=this.collection.get(h);j.set("percentage",100);j.set("status","error");j.set("info",k);this.ui_button.set("percentage",this._upload_percentage(100,i.size));this.ui_button.set("status","danger");this.upload_completed+=i.size*100;this.counter.announce--;this.counter.error++;this._updateScreen()},_eventComplete:function(){this.collection.each(function(h){if(h.get("status")=="queued"){h.set("status","init")}});this.counter.running=0;this._updateScreen()},_eventFtp:function(){if(!this.ftp.visible){this.ftp.empty();this.ftp.append((new g(this)).$el);this.ftp.show()}else{this.ftp.hide()}},_eventCreate:function(){this.uploadbox.add([{name:"New File",size:0,mode:"new"}])},_eventStart:function(){if(this.counter.announce==0||this.counter.running>0){return}var h=this;this.upload_size=0;this.upload_completed=0;this.collection.each(function(i){if(i.get("status")=="init"){i.set("status","queued");h.upload_size+=i.get("file_size")}});this.ui_button.set("percentage",0);this.ui_button.set("status","success");this.current_history=Galaxy.currHistoryPanel.model.get("id");this.counter.running=this.counter.announce;this._updateScreen();this.uploadbox.start()},_eventStop:function(){if(this.counter.running==0){return}this.ui_button.set("status","info");this.uploadbox.stop();$("#upload-info").html("Queue will pause after completing the current file...")},_eventReset:function(){if(this.counter.running==0){this.collection.reset();this.counter.reset();this._updateScreen();this.uploadbox.reset();this.ui_button.set("percentage",0)}},_updateScreen:function(){if(this.counter.announce==0){if(this.uploadbox.compatible()){message="You can Drag & Drop files into this box."}else{message="Unfortunately, your browser does not support multiple file uploads or drag&drop.<br>Some supported browsers are: Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+."}}else{if(this.counter.running==0){message="You added "+this.counter.announce+" file(s) to the queue. Add more files or click 'Start' to proceed."}else{message="Please wait..."+this.counter.announce+" out of "+this.counter.running+" remaining."}}$("#upload-info").html(message);if(this.counter.running==0&&this.counter.announce+this.counter.success+this.counter.error>0){this.modal.enableButton("Reset")}else{this.modal.disableButton("Reset")}if(this.counter.running==0&&this.counter.announce>0){this.modal.enableButton("Start")}else{this.modal.disableButton("Start")}if(this.counter.running>0){this.modal.enableButton("Pause")}else{this.modal.disableButton("Pause")}if(this.counter.running==0){this.modal.enableButton("Choose local file");this.modal.enableButton("Choose FTP file");this.modal.enableButton("Create new file")}else{this.modal.disableButton("Choose local file");this.modal.disableButton("Choose FTP file");this.modal.disableButton("Create new file")}if(this.counter.announce+this.counter.success+this.counter.error>0){$(this.el).find("#upload-table").show()}else{$(this.el).find("#upload-table").hide()}},_upload_percentage:function(h,i){return(this.upload_completed+(h*i))/this.upload_size},_template:function(i,h){return'<div id="'+i+'" class="upload-box"><table id="upload-table" class="table table-striped" style="display: none;"><thead><tr><th>Name</th><th>Size</th><th>Type</th><th>Genome</th><th>Settings</th><th>Status</th><th></th></tr></thead><tbody></tbody></table></div><h6 id="'+h+'" class="upload-info"></h6>'}})});
\ No newline at end of file
+define(["galaxy.modal","utils/utils","mvc/upload/upload-button","mvc/upload/upload-model","mvc/upload/upload-row","mvc/upload/upload-ftp","mvc/ui/ui-popover","mvc/ui","utils/uploadbox"],function(a,f,e,c,b,g,d){return Backbone.View.extend({options:{nginx_upload_path:""},modal:null,ui_button:null,uploadbox:null,current_history:null,upload_size:0,list_extensions:[{id:"auto",text:"Auto-detect"}],list_genomes:[],collection:new c.Collection(),ftp:null,counter:{announce:0,success:0,error:0,running:0,reset:function(){this.announce=this.success=this.error=this.running=0}},initialize:function(i){var h=this;if(!Galaxy.currHistoryPanel||!Galaxy.currHistoryPanel.model){window.setTimeout(function(){h.initialize()},500);return}if(!Galaxy.currUser.get("id")){return}this.ui_button=new e.Model({icon:"fa-upload",tooltip:"Download from URL or upload files from disk",label:"Load Data",onclick:function(j){if(j){h._eventShow(j)}},onunload:function(){if(h.counter.running>0){return"Several uploads are still processing."}}});$("#left .unified-panel-header-inner").append((new e.View(this.ui_button)).$el);var h=this;f.jsonFromUrl(galaxy_config.root+"api/datatypes?upload_only=True",function(j){for(key in j){h.list_extensions.push({id:j[key],text:j[key]})}});f.jsonFromUrl(galaxy_config.root+"api/genomes",function(j){for(key in j){h.list_genomes.push({id:j[key][1],text:j[key][0]})}h.list_genomes.sort(function(l,k){return l.id>k.id?1:l.id<k.id?-1:0})});if(i){this.options=_.defaults(i,this.options)}this.collection.on("remove",function(j){h._eventRemove(j)});this.collection.on("change:genome",function(k){var j=k.get("genome");h.collection.each(function(l){if(l.get("status")=="init"&&l.get("genome")=="?"){l.set("genome",j)}})})},_eventShow:function(j){j.preventDefault();if(!this.modal){var h=this;this.modal=new a.GalaxyModal({title:"Download data directly from web or upload files from your disk",body:this._template("upload-box","upload-info"),buttons:{"Choose local file":function(){h.uploadbox.select()},"Choose FTP file":function(){h._eventFtp()},"Create new file":function(){h._eventCreate()},Start:function(){h._eventStart()},Pause:function(){h._eventStop()},Reset:function(){h._eventReset()},Close:function(){h.modal.hide()},},height:"400",width:"900",closing_events:true});this.setElement("#upload-box");var h=this;this.uploadbox=this.$el.uploadbox({announce:function(k,l,m){h._eventAnnounce(k,l,m)},initialize:function(k,l,m){return h._eventInitialize(k,l,m)},progress:function(k,l,m){h._eventProgress(k,l,m)},success:function(k,l,m){h._eventSuccess(k,l,m)},error:function(k,l,m){h._eventError(k,l,m)},complete:function(){h._eventComplete()}});this._updateScreen();if(this.options.ftp_upload_dir&&this.options.ftp_upload_site){var i=this.modal.getButton("Choose FTP file");this.ftp=new d.View({title:"FTP files",container:i})}else{this.modal.hideButton("Choose FTP file")}}this.modal.show()},_eventRemove:function(i){var h=i.get("status");if(h=="success"){this.counter.success--}else{if(h=="error"){this.counter.error--}else{this.counter.announce--}}this._updateScreen();this.uploadbox.remove(i.id)},_eventAnnounce:function(h,i,k){this.counter.announce++;this._updateScreen();var j=new b(this,{id:h,file_name:i.name,file_size:i.size,file_mode:i.mode,file_path:i.path});this.collection.add(j.model);$(this.el).find("tbody:first").append(j.$el);j.render()},_eventInitialize:function(m,j,s){var k=this.collection.get(m);k.set("status","running");var o=k.get("file_name");var n=k.get("file_path");var h=k.get("file_mode");var p=k.get("extension");var r=k.get("genome");var q=k.get("url_paste");var l=k.get("space_to_tabs");var i=k.get("to_posix_lines");if(!q&&!(j.size>0)){return null}this.uploadbox.configure({url:this.options.nginx_upload_path});if(h=="local"){this.uploadbox.configure({paramname:"files_0|file_data"})}else{this.uploadbox.configure({paramname:null})}tool_input={};if(h=="new"){tool_input["files_0|url_paste"]=q}if(h=="ftp"){tool_input["files_0|ftp_files"]=n}tool_input.dbkey=r;tool_input.file_type=p;tool_input["files_0|type"]="upload_dataset";tool_input.space_to_tabs=l;tool_input.to_posix_lines=i;data={};data.history_id=this.current_history;data.tool_id="upload1";data.inputs=JSON.stringify(tool_input);return data},_eventProgress:function(i,j,h){var k=this.collection.get(i);k.set("percentage",h);this.ui_button.set("percentage",this._upload_percentage(h,j.size))},_eventSuccess:function(i,j,l){var k=this.collection.get(i);k.set("percentage",100);k.set("status","success");var h=k.get("file_size");this.ui_button.set("percentage",this._upload_percentage(100,h));this.upload_completed+=h*100;this.counter.announce--;this.counter.success++;this._updateScreen();Galaxy.currHistoryPanel.refreshHdas()},_eventError:function(h,i,k){var j=this.collection.get(h);j.set("percentage",100);j.set("status","error");j.set("info",k);this.ui_button.set("percentage",this._upload_percentage(100,i.size));this.ui_button.set("status","danger");this.upload_completed+=i.size*100;this.counter.announce--;this.counter.error++;this._updateScreen()},_eventComplete:function(){this.collection.each(function(h){if(h.get("status")=="queued"){h.set("status","init")}});this.counter.running=0;this._updateScreen()},_eventFtp:function(){if(!this.ftp.visible){this.ftp.empty();this.ftp.append((new g(this)).$el);this.ftp.show()}else{this.ftp.hide()}},_eventCreate:function(){this.uploadbox.add([{name:"New File",size:0,mode:"new"}])},_eventStart:function(){if(this.counter.announce==0||this.counter.running>0){return}var h=this;this.upload_size=0;this.upload_completed=0;this.collection.each(function(i){if(i.get("status")=="init"){i.set("status","queued");h.upload_size+=i.get("file_size")}});this.ui_button.set("percentage",0);this.ui_button.set("status","success");this.current_history=Galaxy.currHistoryPanel.model.get("id");this.counter.running=this.counter.announce;this._updateScreen();this.uploadbox.start()},_eventStop:function(){if(this.counter.running==0){return}this.ui_button.set("status","info");this.uploadbox.stop();$("#upload-info").html("Queue will pause after completing the current file...")},_eventReset:function(){if(this.counter.running==0){this.collection.reset();this.counter.reset();this._updateScreen();this.uploadbox.reset();this.ui_button.set("percentage",0)}},_updateScreen:function(){if(this.counter.announce==0){if(this.uploadbox.compatible()){message="You can Drag & Drop files into this box."}else{message="Unfortunately, your browser does not support multiple file uploads or drag&drop.<br>Some supported browsers are: Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+."}}else{if(this.counter.running==0){message="You added "+this.counter.announce+" file(s) to the queue. Add more files or click 'Start' to proceed."}else{message="Please wait..."+this.counter.announce+" out of "+this.counter.running+" remaining."}}$("#upload-info").html(message);if(this.counter.running==0&&this.counter.announce+this.counter.success+this.counter.error>0){this.modal.enableButton("Reset")}else{this.modal.disableButton("Reset")}if(this.counter.running==0&&this.counter.announce>0){this.modal.enableButton("Start")}else{this.modal.disableButton("Start")}if(this.counter.running>0){this.modal.enableButton("Pause")}else{this.modal.disableButton("Pause")}if(this.counter.running==0){this.modal.enableButton("Choose local file");this.modal.enableButton("Choose FTP file");this.modal.enableButton("Create new file")}else{this.modal.disableButton("Choose local file");this.modal.disableButton("Choose FTP file");this.modal.disableButton("Create new file")}if(this.counter.announce+this.counter.success+this.counter.error>0){$(this.el).find("#upload-table").show()}else{$(this.el).find("#upload-table").hide()}},_upload_percentage:function(h,i){return(this.upload_completed+(h*i))/this.upload_size},_template:function(i,h){return'<div id="'+i+'" class="upload-box"><table id="upload-table" class="table table-striped" style="display: none;"><thead><tr><th>Name</th><th>Size</th><th>Type</th><th>Genome</th><th>Settings</th><th>Status</th><th></th></tr></thead><tbody></tbody></table></div><h6 id="'+h+'" class="upload-info"></h6>'}})});
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: guerler: Ui: Move ui classes into a separate directory
by commits-noreply@bitbucket.org 13 Feb '14
by commits-noreply@bitbucket.org 13 Feb '14
13 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/b6acb69067f8/
Changeset: b6acb69067f8
User: guerler
Date: 2014-02-13 10:12:41
Summary: Ui: Move ui classes into a separate directory
Affected #: 12 files
diff -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 -r b6acb69067f808147e4e69a1cde74cbb8868c600 static/scripts/mvc/ui.popover.js
--- a/static/scripts/mvc/ui.popover.js
+++ /dev/null
@@ -1,169 +0,0 @@
-// dependencies
-define(['utils/utils'], function(Utils) {
-
-var View = Backbone.View.extend({
-
- // default options
- optionsDefault: {
- with_close : true,
- container : 'body',
- title : null,
- placement : 'top'
- },
-
- // visibility flag
- visible: false,
-
- // initialize
- initialize: function (options) {
-
- // link this
- var self = this;
-
- // update options
- this.options = _.defaults(options, this.optionsDefault);
-
- // set element
- this.setElement(this._template(this.options));
-
- // attach popover to parent
- this.options.container.parent().append(this.$el);
-
- // attach close
- if (this.options.with_close) {
- this.$el.find('#close').on('click', function() { self.hide(); });
- }
-
- // generate unique id
- this.uuid = Utils.uuid();
-
- // add event to hide if click is outside of popup
- var self = this;
- $('body').on('mousedown.' + this.uuid, function(e) { self._hide(e) });
- },
-
- // title
- title: function(val) {
- if (val !== undefined) {
- this.$el.find('.popover-title-label').html(val);
- }
- },
-
- // show
- show: function () {
- // show popover
- this.$el.show();
- this.visible = true;
-
- // calculate position
- var position = this._get_placement(this.options.placement);
-
- // set position
- this.$el.css(position);
- },
-
- // calculate position and error
- _get_placement: function(placement) {
- // get popover dimensions
- var width = this._get_width(this.$el);
- var height = this.$el.height();
-
- // get container details
- var $container = this.options.container;
- var container_width = this._get_width($container);
- var container_height = this._get_height($container);
- var container_position = $container.position();
-
- // initialize position
- var top = 0;
- var left = 0;
-
- // calculate position
- if (placement == 'top' || placement == 'bottom') {
- left = container_position.left - width + (container_width + width) / 2;
- if (placement == 'top') {
- top = container_position.top - height - 5;
- } else {
- top = container_position.top + container_height + 5;
- }
- }
-
- // return
- return {top: top, left: left};
- },
-
- // width
- _get_width: function($el) {
- return $el.width() + parseInt($el.css('padding-left')) + parseInt($el.css('padding-right'))
- },
-
- // heigth
- _get_height: function($el) {
- return $el.height() + parseInt($el.css('padding-top')) + parseInt($el.css('padding-bottom'))
- },
-
- // hide
- hide: function () {
- this.$el.hide();
- this.visible = false;
- },
-
- // append
- append: function($el) {
- this.$el.find('.popover-content').append($el);
- },
-
- // empty
- empty: function($el) {
- this.$el.find('.popover-content').empty();
- },
-
- // remove
- remove: function() {
- // remove event handler
- $('body').off('mousedown.' + this.uuid);
-
- // remove element from dom
- this.$el.remove();
- },
-
- // remove
- _hide : function(e) {
- //the 'is' for buttons that trigger popups
- //the 'has' for icons within a button that triggers a popup
- if (!$(this.options.container).is(e.target) &&
- !$(this.el).is(e.target) &&
- $(this.el).has(e.target).length === 0) {
- this.hide();
- }
- },
-
- // template
- _template: function(options) {
- var tmpl = '<div class="popover-view popover fade ' + options.placement + ' in">' +
- '<div class="arrow"></div>' +
- '<div class="popover-title">' +
- '<div class="popover-title-label">' +
- options.title +
- '</div>';
-
- // add close icon
- if (options.with_close) {
- tmpl += '<div id="close" class="popover-close fa fa-times-circle"></div>';
- }
-
- // finalize
- tmpl += '</div>' +
- '<div class="popover-content"></div>' +
- '</div>';
-
- // return
- return tmpl;
- }
-});
-
-return {
- View: View
-}
-
-});
\ No newline at end of file
diff -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 -r b6acb69067f808147e4e69a1cde74cbb8868c600 static/scripts/mvc/ui.select.js
--- a/static/scripts/mvc/ui.select.js
+++ /dev/null
@@ -1,193 +0,0 @@
-// dependencies
-define(['utils/utils'], function(Utils) {
-
-// plugin
-var View = Backbone.View.extend(
-{
- // options
- optionsDefault: {
- css : '',
- placeholder : 'No data available',
- data : []
- },
-
- // initialize
- initialize : function(options) {
- // configure options
- this.options = Utils.merge(options, this.optionsDefault);
-
- // create new element
- this.setElement(this._template(this.options));
-
- // add to dom
- this.options.container.append(this.$el);
-
- // link selection dictionary
- this.select_data = this.options.data;
-
- // refresh
- this._refresh();
-
- // initial value
- if (this.options.value) {
- this._setValue(this.options.value);
- }
-
- // add change event
- var self = this;
- if (this.options.onchange) {
- this.$el.on('change', function() {
- self.options.onchange(self.value());
- });
- }
- },
-
- // value
- value : function (new_value) {
- // get current id/value
- var before = this._getValue();
-
- // check if new_value is defined
- if (new_value !== undefined) {
- this._setValue(new_value);
- }
-
- // get current id/value
- var after = this._getValue();
- if(after === undefined) {
- return null;
- } else {
- // fire onchange
- if ((after != before && this.options.onchange)) {
- this.options.onchange(after);
- }
-
- // return current value
- return after;
- }
- },
-
- // label
- text : function () {
- return this.$el.select2('data').text;
- },
-
- // disabled
- disabled: function() {
- return !this.$el.select2('enable');
- },
-
- // enable
- enable: function() {
- this.$el.select2('enable', true);
- },
-
- // disable
- disable: function() {
- this.$el.select2('enable', false);
- },
-
- // add
- add: function(options) {
- // add options
- this.select_data.push({
- id : options.id,
- text : options.text
- });
-
- // refresh
- this._refresh();
- },
-
- // remove
- del: function(id) {
- // search option
- var index = this._getIndex(id);
-
- // check if found
- if (index != -1) {
- // remove options
- this.select_data.splice(index, 1);
-
- // refresh
- this._refresh();
- }
- },
-
- // remove
- remove: function() {
- this.$el.select2('destroy');
- },
-
- // update
- update: function(options) {
- // copy options
- this.select_data = [];
- for (var key in options.data) {
- this.select_data.push(options.data[key]);
- }
-
- // refresh
- this._refresh();
- },
-
- // refresh
- _refresh: function() {
-
- // link select data
- var select_data = this.select_data;
- if (!select_data || select_data.length == 0) {
- select_data = [];
- }
-
- // selected
- var selected = this._getValue();
-
- // add select2 data
- this.$el.select2({
- data : select_data,
- containerCssClass : this.options.css,
- placeholder : this.options.placeholder
- });
-
- // select
- var index = this._getIndex(selected);
- if (index != -1) {
- this._setValue(selected);
- }
- },
-
- // get index
- _getIndex: function(value) {
- // search index
- for (var key in this.select_data) {
- if (this.select_data[key].id == value) {
- return key;
- }
- }
-
- // not found
- return -1;
- },
-
- // get value
- _getValue: function() {
- return this.$el.select2('val');
- },
-
- // set value
- _setValue: function(new_value) {
- this.$el.select2('val', new_value);
- },
-
- // element
- _template: function(options) {
- return '<input type="hidden"/>';
- }
-});
-
-return {
- View : View
-}
-
-});
diff -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 -r b6acb69067f808147e4e69a1cde74cbb8868c600 static/scripts/mvc/ui/popover.js
--- /dev/null
+++ b/static/scripts/mvc/ui/popover.js
@@ -0,0 +1,169 @@
+// dependencies
+define(['utils/utils'], function(Utils) {
+
+var View = Backbone.View.extend({
+
+ // default options
+ optionsDefault: {
+ with_close : true,
+ container : 'body',
+ title : null,
+ placement : 'top'
+ },
+
+ // visibility flag
+ visible: false,
+
+ // initialize
+ initialize: function (options) {
+
+ // link this
+ var self = this;
+
+ // update options
+ this.options = _.defaults(options, this.optionsDefault);
+
+ // set element
+ this.setElement(this._template(this.options));
+
+ // attach popover to parent
+ this.options.container.parent().append(this.$el);
+
+ // attach close
+ if (this.options.with_close) {
+ this.$el.find('#close').on('click', function() { self.hide(); });
+ }
+
+ // generate unique id
+ this.uuid = Utils.uuid();
+
+ // add event to hide if click is outside of popup
+ var self = this;
+ $('body').on('mousedown.' + this.uuid, function(e) { self._hide(e) });
+ },
+
+ // title
+ title: function(val) {
+ if (val !== undefined) {
+ this.$el.find('.popover-title-label').html(val);
+ }
+ },
+
+ // show
+ show: function () {
+ // show popover
+ this.$el.show();
+ this.visible = true;
+
+ // calculate position
+ var position = this._get_placement(this.options.placement);
+
+ // set position
+ this.$el.css(position);
+ },
+
+ // calculate position and error
+ _get_placement: function(placement) {
+ // get popover dimensions
+ var width = this._get_width(this.$el);
+ var height = this.$el.height();
+
+ // get container details
+ var $container = this.options.container;
+ var container_width = this._get_width($container);
+ var container_height = this._get_height($container);
+ var container_position = $container.position();
+
+ // initialize position
+ var top = 0;
+ var left = 0;
+
+ // calculate position
+ if (placement == 'top' || placement == 'bottom') {
+ left = container_position.left - width + (container_width + width) / 2;
+ if (placement == 'top') {
+ top = container_position.top - height - 5;
+ } else {
+ top = container_position.top + container_height + 5;
+ }
+ }
+
+ // return
+ return {top: top, left: left};
+ },
+
+ // width
+ _get_width: function($el) {
+ return $el.width() + parseInt($el.css('padding-left')) + parseInt($el.css('padding-right'))
+ },
+
+ // heigth
+ _get_height: function($el) {
+ return $el.height() + parseInt($el.css('padding-top')) + parseInt($el.css('padding-bottom'))
+ },
+
+ // hide
+ hide: function () {
+ this.$el.hide();
+ this.visible = false;
+ },
+
+ // append
+ append: function($el) {
+ this.$el.find('.popover-content').append($el);
+ },
+
+ // empty
+ empty: function($el) {
+ this.$el.find('.popover-content').empty();
+ },
+
+ // remove
+ remove: function() {
+ // remove event handler
+ $('body').off('mousedown.' + this.uuid);
+
+ // remove element from dom
+ this.$el.remove();
+ },
+
+ // remove
+ _hide : function(e) {
+ //the 'is' for buttons that trigger popups
+ //the 'has' for icons within a button that triggers a popup
+ if (!$(this.options.container).is(e.target) &&
+ !$(this.el).is(e.target) &&
+ $(this.el).has(e.target).length === 0) {
+ this.hide();
+ }
+ },
+
+ // template
+ _template: function(options) {
+ var tmpl = '<div class="popover-view popover fade ' + options.placement + ' in">' +
+ '<div class="arrow"></div>' +
+ '<div class="popover-title">' +
+ '<div class="popover-title-label">' +
+ options.title +
+ '</div>';
+
+ // add close icon
+ if (options.with_close) {
+ tmpl += '<div id="close" class="popover-close fa fa-times-circle"></div>';
+ }
+
+ // finalize
+ tmpl += '</div>' +
+ '<div class="popover-content"></div>' +
+ '</div>';
+
+ // return
+ return tmpl;
+ }
+});
+
+return {
+ View: View
+}
+
+});
\ No newline at end of file
diff -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 -r b6acb69067f808147e4e69a1cde74cbb8868c600 static/scripts/mvc/ui/select.js
--- /dev/null
+++ b/static/scripts/mvc/ui/select.js
@@ -0,0 +1,193 @@
+// dependencies
+define(['utils/utils'], function(Utils) {
+
+// plugin
+var View = Backbone.View.extend(
+{
+ // options
+ optionsDefault: {
+ css : '',
+ placeholder : 'No data available',
+ data : []
+ },
+
+ // initialize
+ initialize : function(options) {
+ // configure options
+ this.options = Utils.merge(options, this.optionsDefault);
+
+ // create new element
+ this.setElement(this._template(this.options));
+
+ // add to dom
+ this.options.container.append(this.$el);
+
+ // link selection dictionary
+ this.select_data = this.options.data;
+
+ // refresh
+ this._refresh();
+
+ // initial value
+ if (this.options.value) {
+ this._setValue(this.options.value);
+ }
+
+ // add change event
+ var self = this;
+ if (this.options.onchange) {
+ this.$el.on('change', function() {
+ self.options.onchange(self.value());
+ });
+ }
+ },
+
+ // value
+ value : function (new_value) {
+ // get current id/value
+ var before = this._getValue();
+
+ // check if new_value is defined
+ if (new_value !== undefined) {
+ this._setValue(new_value);
+ }
+
+ // get current id/value
+ var after = this._getValue();
+ if(after === undefined) {
+ return null;
+ } else {
+ // fire onchange
+ if ((after != before && this.options.onchange)) {
+ this.options.onchange(after);
+ }
+
+ // return current value
+ return after;
+ }
+ },
+
+ // label
+ text : function () {
+ return this.$el.select2('data').text;
+ },
+
+ // disabled
+ disabled: function() {
+ return !this.$el.select2('enable');
+ },
+
+ // enable
+ enable: function() {
+ this.$el.select2('enable', true);
+ },
+
+ // disable
+ disable: function() {
+ this.$el.select2('enable', false);
+ },
+
+ // add
+ add: function(options) {
+ // add options
+ this.select_data.push({
+ id : options.id,
+ text : options.text
+ });
+
+ // refresh
+ this._refresh();
+ },
+
+ // remove
+ del: function(id) {
+ // search option
+ var index = this._getIndex(id);
+
+ // check if found
+ if (index != -1) {
+ // remove options
+ this.select_data.splice(index, 1);
+
+ // refresh
+ this._refresh();
+ }
+ },
+
+ // remove
+ remove: function() {
+ this.$el.select2('destroy');
+ },
+
+ // update
+ update: function(options) {
+ // copy options
+ this.select_data = [];
+ for (var key in options.data) {
+ this.select_data.push(options.data[key]);
+ }
+
+ // refresh
+ this._refresh();
+ },
+
+ // refresh
+ _refresh: function() {
+
+ // link select data
+ var select_data = this.select_data;
+ if (!select_data || select_data.length == 0) {
+ select_data = [];
+ }
+
+ // selected
+ var selected = this._getValue();
+
+ // add select2 data
+ this.$el.select2({
+ data : select_data,
+ containerCssClass : this.options.css,
+ placeholder : this.options.placeholder
+ });
+
+ // select
+ var index = this._getIndex(selected);
+ if (index != -1) {
+ this._setValue(selected);
+ }
+ },
+
+ // get index
+ _getIndex: function(value) {
+ // search index
+ for (var key in this.select_data) {
+ if (this.select_data[key].id == value) {
+ return key;
+ }
+ }
+
+ // not found
+ return -1;
+ },
+
+ // get value
+ _getValue: function() {
+ return this.$el.select2('val');
+ },
+
+ // set value
+ _setValue: function(new_value) {
+ this.$el.select2('val', new_value);
+ },
+
+ // element
+ _template: function(options) {
+ return '<input type="hidden"/>';
+ }
+});
+
+return {
+ View : View
+}
+
+});
diff -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 -r b6acb69067f808147e4e69a1cde74cbb8868c600 static/scripts/mvc/upload/upload-row.js
--- a/static/scripts/mvc/upload/upload-row.js
+++ b/static/scripts/mvc/upload/upload-row.js
@@ -3,8 +3,8 @@
'mvc/upload/upload-model',
'mvc/upload/upload-extensions',
'mvc/upload/upload-settings',
- 'mvc/ui.popover',
- 'mvc/ui.select'],
+ 'mvc/ui/popover',
+ 'mvc/ui/select'],
function( Utils,
UploadModel,
diff -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 -r b6acb69067f808147e4e69a1cde74cbb8868c600 static/scripts/mvc/upload/upload-view.js
--- a/static/scripts/mvc/upload/upload-view.js
+++ b/static/scripts/mvc/upload/upload-view.js
@@ -5,7 +5,7 @@
"mvc/upload/upload-model",
"mvc/upload/upload-row",
"mvc/upload/upload-ftp",
- "mvc/ui.popover",
+ "mvc/ui/popover",
"mvc/ui",
"utils/uploadbox"],
diff -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 -r b6acb69067f808147e4e69a1cde74cbb8868c600 static/scripts/packed/mvc/ui.popover.js
--- a/static/scripts/packed/mvc/ui.popover.js
+++ /dev/null
@@ -1,1 +0,0 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{with_close:true,container:"body",title:null,placement:"top"},visible:false,initialize:function(d){var c=this;this.options=_.defaults(d,this.optionsDefault);this.setElement(this._template(this.options));this.options.container.parent().append(this.$el);if(this.options.with_close){this.$el.find("#close").on("click",function(){c.hide()})}this.uuid=a.uuid();var c=this;$("body").on("mousedown."+this.uuid,function(f){c._hide(f)})},title:function(c){if(c!==undefined){this.$el.find(".popover-title-label").html(c)}},show:function(){this.$el.show();this.visible=true;var c=this._get_placement(this.options.placement);this.$el.css(c)},_get_placement:function(h){var d=this._get_width(this.$el);var j=this.$el.height();var k=this.options.container;var c=this._get_width(k);var f=this._get_height(k);var g=k.position();var i=0;var e=0;if(h=="top"||h=="bottom"){e=g.left-d+(c+d)/2;if(h=="top"){i=g.top-j-5}else{i=g.top+f+5}}return{top:i,left:e}},_get_width:function(c){return c.width()+parseInt(c.css("padding-left"))+parseInt(c.css("padding-right"))},_get_height:function(c){return c.height()+parseInt(c.css("padding-top"))+parseInt(c.css("padding-bottom"))},hide:function(){this.$el.hide();this.visible=false},append:function(c){this.$el.find(".popover-content").append(c)},empty:function(c){this.$el.find(".popover-content").empty()},remove:function(){$("body").off("mousedown."+this.uuid);this.$el.remove()},_hide:function(c){if(!$(this.options.container).is(c.target)&&!$(this.el).is(c.target)&&$(this.el).has(c.target).length===0){this.hide()}},_template:function(d){var c='<div class="popover-view popover fade '+d.placement+' in"><div class="arrow"></div><div class="popover-title"><div class="popover-title-label">'+d.title+"</div>";if(d.with_close){c+='<div id="close" class="popover-close fa fa-times-circle"></div>'}c+='</div><div class="popover-content"></div></div>';return c}});return{View:b}});
\ No newline at end of file
diff -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 -r b6acb69067f808147e4e69a1cde74cbb8868c600 static/scripts/packed/mvc/ui.select.js
--- a/static/scripts/packed/mvc/ui.select.js
+++ /dev/null
@@ -1,1 +0,0 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{css:"",placeholder:"No data available",data:[]},initialize:function(d){this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.options.container.append(this.$el);this.select_data=this.options.data;this._refresh();if(this.options.value){this._setValue(this.options.value)}var c=this;if(this.options.onchange){this.$el.on("change",function(){c.options.onchange(c.value())})}},value:function(c){var d=this._getValue();if(c!==undefined){this._setValue(c)}var e=this._getValue();if(e===undefined){return null}else{if((e!=d&&this.options.onchange)){this.options.onchange(e)}return e}},text:function(){return this.$el.select2("data").text},disabled:function(){return !this.$el.select2("enable")},enable:function(){this.$el.select2("enable",true)},disable:function(){this.$el.select2("enable",false)},add:function(c){this.select_data.push({id:c.id,text:c.text});this._refresh()},del:function(d){var c=this._getIndex(d);if(c!=-1){this.select_data.splice(c,1);this._refresh()}},remove:function(){this.$el.select2("destroy")},update:function(c){this.select_data=[];for(var d in c.data){this.select_data.push(c.data[d])}this._refresh()},_refresh:function(){var e=this.select_data;if(!e||e.length==0){e=[]}var d=this._getValue();this.$el.select2({data:e,containerCssClass:this.options.css,placeholder:this.options.placeholder});var c=this._getIndex(d);if(c!=-1){this._setValue(d)}},_getIndex:function(d){for(var c in this.select_data){if(this.select_data[c].id==d){return c}}return -1},_getValue:function(){return this.$el.select2("val")},_setValue:function(c){this.$el.select2("val",c)},_template:function(c){return'<input type="hidden"/>'}});return{View:b}});
\ No newline at end of file
diff -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 -r b6acb69067f808147e4e69a1cde74cbb8868c600 static/scripts/packed/mvc/ui/popover.js
--- /dev/null
+++ b/static/scripts/packed/mvc/ui/popover.js
@@ -0,0 +1,1 @@
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{with_close:true,container:"body",title:null,placement:"top"},visible:false,initialize:function(d){var c=this;this.options=_.defaults(d,this.optionsDefault);this.setElement(this._template(this.options));this.options.container.parent().append(this.$el);if(this.options.with_close){this.$el.find("#close").on("click",function(){c.hide()})}this.uuid=a.uuid();var c=this;$("body").on("mousedown."+this.uuid,function(f){c._hide(f)})},title:function(c){if(c!==undefined){this.$el.find(".popover-title-label").html(c)}},show:function(){this.$el.show();this.visible=true;var c=this._get_placement(this.options.placement);this.$el.css(c)},_get_placement:function(h){var d=this._get_width(this.$el);var j=this.$el.height();var k=this.options.container;var c=this._get_width(k);var f=this._get_height(k);var g=k.position();var i=0;var e=0;if(h=="top"||h=="bottom"){e=g.left-d+(c+d)/2;if(h=="top"){i=g.top-j-5}else{i=g.top+f+5}}return{top:i,left:e}},_get_width:function(c){return c.width()+parseInt(c.css("padding-left"))+parseInt(c.css("padding-right"))},_get_height:function(c){return c.height()+parseInt(c.css("padding-top"))+parseInt(c.css("padding-bottom"))},hide:function(){this.$el.hide();this.visible=false},append:function(c){this.$el.find(".popover-content").append(c)},empty:function(c){this.$el.find(".popover-content").empty()},remove:function(){$("body").off("mousedown."+this.uuid);this.$el.remove()},_hide:function(c){if(!$(this.options.container).is(c.target)&&!$(this.el).is(c.target)&&$(this.el).has(c.target).length===0){this.hide()}},_template:function(d){var c='<div class="popover-view popover fade '+d.placement+' in"><div class="arrow"></div><div class="popover-title"><div class="popover-title-label">'+d.title+"</div>";if(d.with_close){c+='<div id="close" class="popover-close fa fa-times-circle"></div>'}c+='</div><div class="popover-content"></div></div>';return c}});return{View:b}});
\ No newline at end of file
diff -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 -r b6acb69067f808147e4e69a1cde74cbb8868c600 static/scripts/packed/mvc/ui/select.js
--- /dev/null
+++ b/static/scripts/packed/mvc/ui/select.js
@@ -0,0 +1,1 @@
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{css:"",placeholder:"No data available",data:[]},initialize:function(d){this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.options.container.append(this.$el);this.select_data=this.options.data;this._refresh();if(this.options.value){this._setValue(this.options.value)}var c=this;if(this.options.onchange){this.$el.on("change",function(){c.options.onchange(c.value())})}},value:function(c){var d=this._getValue();if(c!==undefined){this._setValue(c)}var e=this._getValue();if(e===undefined){return null}else{if((e!=d&&this.options.onchange)){this.options.onchange(e)}return e}},text:function(){return this.$el.select2("data").text},disabled:function(){return !this.$el.select2("enable")},enable:function(){this.$el.select2("enable",true)},disable:function(){this.$el.select2("enable",false)},add:function(c){this.select_data.push({id:c.id,text:c.text});this._refresh()},del:function(d){var c=this._getIndex(d);if(c!=-1){this.select_data.splice(c,1);this._refresh()}},remove:function(){this.$el.select2("destroy")},update:function(c){this.select_data=[];for(var d in c.data){this.select_data.push(c.data[d])}this._refresh()},_refresh:function(){var e=this.select_data;if(!e||e.length==0){e=[]}var d=this._getValue();this.$el.select2({data:e,containerCssClass:this.options.css,placeholder:this.options.placeholder});var c=this._getIndex(d);if(c!=-1){this._setValue(d)}},_getIndex:function(d){for(var c in this.select_data){if(this.select_data[c].id==d){return c}}return -1},_getValue:function(){return this.$el.select2("val")},_setValue:function(c){this.$el.select2("val",c)},_template:function(c){return'<input type="hidden"/>'}});return{View:b}});
\ No newline at end of file
diff -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 -r b6acb69067f808147e4e69a1cde74cbb8868c600 static/scripts/packed/mvc/upload/upload-row.js
--- a/static/scripts/packed/mvc/upload/upload-row.js
+++ b/static/scripts/packed/mvc/upload/upload-row.js
@@ -1,1 +1,1 @@
-define(["utils/utils","mvc/upload/upload-model","mvc/upload/upload-extensions","mvc/upload/upload-settings","mvc/ui.popover","mvc/ui.select"],function(d,b,f,a,c,e){return Backbone.View.extend({options:{padding:8},status_classes:{init:"upload-icon-button fa fa-trash-o",queued:"upload-icon fa fa-spinner fa-spin",running:"upload-icon fa fa-spinner fa-spin",success:"upload-icon-button fa fa-check",error:"upload-icon-button fa fa-exclamation-triangle"},settings:null,select_genome:null,select_extension:null,initialize:function(j,h){this.app=j;var g=this;this.model=new b.Model(h);this.setElement(this._template(h));var i=this.$el;this.settings=new c.View({title:"Upload configuration",container:i.find("#settings"),placement:"bottom"});this.select_genome=new e.View({css:"genome",onchange:function(){g.model.set("genome",g.select_genome.value())},data:g.app.list_genomes,container:i.find("#genome"),value:g.model.get("genome")});this.select_extension=new e.View({css:"extension",onchange:function(){g.model.set("extension",g.select_extension.value())},data:g.app.list_extensions,container:i.find("#extension"),value:g.model.get("extension")});i.find("#symbol").on("click",function(){g._removeRow()});i.find("#extension-info").on("click",function(k){g._showExtensionInfo()}).on("mousedown",function(k){k.preventDefault()});i.find("#settings").on("click",function(k){g._showSettings()}).on("mousedown",function(k){k.preventDefault()});i.find("#text-content").on("keyup",function(k){g.model.set("url_paste",$(k.target).val());g.model.set("file_size",$(k.target).val().length)});i.find("#space_to_tabs").on("change",function(k){g.model.set("space_to_tabs",$(k.target).prop("checked"))});this.model.on("change:percentage",function(){g._refreshPercentage()});this.model.on("change:status",function(){g._refreshStatus()});this.model.on("change:info",function(){g._refreshInfo()});this.model.on("change:genome",function(){g._refreshGenome()});this.model.on("change:file_size",function(){g._refreshFileSize()});this.model.on("remove",function(){g.remove()});this.app.collection.on("reset",function(){g.remove()})},render:function(){var n=this.model.get("file_name");var h=this.model.get("file_size");var k=this.model.get("file_mode");var j=this.$el;j.find("#title").html(n);j.find("#size").html(d.bytesToString(h));j.find("#mode").removeClass().addClass("mode");if(k=="new"){var m=j.find("#text");var l=this.options.padding;var i=j.width()-2*l;var g=j.height()-l;m.css("width",i+"px");m.css("top",g+"px");j.height(g+m.height()+2*l);m.show();j.find("#mode").addClass("fa fa-pencil")}if(k=="local"){j.find("#mode").addClass("fa fa-laptop")}if(k=="ftp"){j.find("#mode").addClass("fa fa-code-fork")}},remove:function(){this.select_genome.remove();this.select_extension.remove();Backbone.View.prototype.remove.apply(this)},_refreshGenome:function(){var g=this.model.get("genome");this.select_genome.value(g)},_refreshInfo:function(){var g=this.model.get("info");if(g){this.$el.find("#info").html("<strong>Failed: </strong>"+g).show()}else{this.$el.find("#info").hide()}},_refreshPercentage:function(){var g=parseInt(this.model.get("percentage"));this.$el.find(".progress-bar").css({width:g+"%"});if(g!=100){this.$el.find("#percentage").html(g+"%")}else{this.$el.find("#percentage").html("Adding to history...")}},_refreshStatus:function(){var h=this.$el;var g=this.model.get("status");var j=this.status_classes[g];var i=this.$el.find("#symbol");i.removeClass();i.addClass(j);if(g=="init"){this.select_genome.enable();this.select_extension.enable();h.find("#text-content").attr("disabled",false);h.find("#space_to_tabs").attr("disabled",false)}else{this.select_genome.disable();this.select_extension.disable();h.find("#text-content").attr("disabled",true);h.find("#space_to_tabs").attr("disabled",true)}if(g=="success"){h.addClass("success");h.find("#percentage").html("100%")}if(g=="error"){h.addClass("danger");h.find(".progress").remove()}},_refreshFileSize:function(){var g=this.model.get("file_size");this.$el.find("#size").html(d.bytesToString(g))},_removeRow:function(){var g=this.model.get("status");if(g=="init"||g=="success"||g=="error"){this.app.collection.remove(this.model)}},_showExtensionInfo:function(){var g=$(this.el).find("#extension-info");var i=this.model.get("extension");var h=this.select_extension.text();if(!this.extension_popup){this.extension_popup=new c.View({content:f(i),placement:"bottom",container:g})}if(!this.extension_popup.visible){this.extension_popup.title(h);this.extension_popup.empty();this.extension_popup.append(f(i));this.extension_popup.show()}else{this.extension_popup.hide()}},_showSettings:function(){if(!this.settings.visible){this.settings.empty();this.settings.append((new a(this)).$el);this.settings.show()}else{this.settings.hide()}},_template:function(g){return'<tr id="upload-item-'+g.id+'" class="upload-item"><td><div style="position: relative;"><div id="mode"></div><div id="title" class="title"></div><div id="text" class="text"><div class="text-info">You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.</div><textarea id="text-content" class="text-content form-control"></textarea></div></div></td><td><div id="size" class="size"></div></td><td><div id="extension" class="extension" style="float: left;"/>  <div id="extension-info" class="upload-icon-button fa fa-search"/></td><td><div id="genome" class="genome" /></td><td><div id="settings" class="upload-icon-button fa fa-gear"></div><td><div id="info" class="info"><div class="progress"><div class="progress-bar progress-bar-success"></div><div id="percentage" class="percentage">0%</div></div></div></td><td><div id="symbol" class="'+this.status_classes.init+'"></div></td></tr>'}})});
\ No newline at end of file
+define(["utils/utils","mvc/upload/upload-model","mvc/upload/upload-extensions","mvc/upload/upload-settings","mvc/ui/popover","mvc/ui/select"],function(d,b,f,a,c,e){return Backbone.View.extend({options:{padding:8},status_classes:{init:"upload-icon-button fa fa-trash-o",queued:"upload-icon fa fa-spinner fa-spin",running:"upload-icon fa fa-spinner fa-spin",success:"upload-icon-button fa fa-check",error:"upload-icon-button fa fa-exclamation-triangle"},settings:null,select_genome:null,select_extension:null,initialize:function(j,h){this.app=j;var g=this;this.model=new b.Model(h);this.setElement(this._template(h));var i=this.$el;this.settings=new c.View({title:"Upload configuration",container:i.find("#settings"),placement:"bottom"});this.select_genome=new e.View({css:"genome",onchange:function(){g.model.set("genome",g.select_genome.value())},data:g.app.list_genomes,container:i.find("#genome"),value:g.model.get("genome")});this.select_extension=new e.View({css:"extension",onchange:function(){g.model.set("extension",g.select_extension.value())},data:g.app.list_extensions,container:i.find("#extension"),value:g.model.get("extension")});i.find("#symbol").on("click",function(){g._removeRow()});i.find("#extension-info").on("click",function(k){g._showExtensionInfo()}).on("mousedown",function(k){k.preventDefault()});i.find("#settings").on("click",function(k){g._showSettings()}).on("mousedown",function(k){k.preventDefault()});i.find("#text-content").on("keyup",function(k){g.model.set("url_paste",$(k.target).val());g.model.set("file_size",$(k.target).val().length)});i.find("#space_to_tabs").on("change",function(k){g.model.set("space_to_tabs",$(k.target).prop("checked"))});this.model.on("change:percentage",function(){g._refreshPercentage()});this.model.on("change:status",function(){g._refreshStatus()});this.model.on("change:info",function(){g._refreshInfo()});this.model.on("change:genome",function(){g._refreshGenome()});this.model.on("change:file_size",function(){g._refreshFileSize()});this.model.on("remove",function(){g.remove()});this.app.collection.on("reset",function(){g.remove()})},render:function(){var n=this.model.get("file_name");var h=this.model.get("file_size");var k=this.model.get("file_mode");var j=this.$el;j.find("#title").html(n);j.find("#size").html(d.bytesToString(h));j.find("#mode").removeClass().addClass("mode");if(k=="new"){var m=j.find("#text");var l=this.options.padding;var i=j.width()-2*l;var g=j.height()-l;m.css("width",i+"px");m.css("top",g+"px");j.height(g+m.height()+2*l);m.show();j.find("#mode").addClass("fa fa-pencil")}if(k=="local"){j.find("#mode").addClass("fa fa-laptop")}if(k=="ftp"){j.find("#mode").addClass("fa fa-code-fork")}},remove:function(){this.select_genome.remove();this.select_extension.remove();Backbone.View.prototype.remove.apply(this)},_refreshGenome:function(){var g=this.model.get("genome");this.select_genome.value(g)},_refreshInfo:function(){var g=this.model.get("info");if(g){this.$el.find("#info").html("<strong>Failed: </strong>"+g).show()}else{this.$el.find("#info").hide()}},_refreshPercentage:function(){var g=parseInt(this.model.get("percentage"));this.$el.find(".progress-bar").css({width:g+"%"});if(g!=100){this.$el.find("#percentage").html(g+"%")}else{this.$el.find("#percentage").html("Adding to history...")}},_refreshStatus:function(){var h=this.$el;var g=this.model.get("status");var j=this.status_classes[g];var i=this.$el.find("#symbol");i.removeClass();i.addClass(j);if(g=="init"){this.select_genome.enable();this.select_extension.enable();h.find("#text-content").attr("disabled",false);h.find("#space_to_tabs").attr("disabled",false)}else{this.select_genome.disable();this.select_extension.disable();h.find("#text-content").attr("disabled",true);h.find("#space_to_tabs").attr("disabled",true)}if(g=="success"){h.addClass("success");h.find("#percentage").html("100%")}if(g=="error"){h.addClass("danger");h.find(".progress").remove()}},_refreshFileSize:function(){var g=this.model.get("file_size");this.$el.find("#size").html(d.bytesToString(g))},_removeRow:function(){var g=this.model.get("status");if(g=="init"||g=="success"||g=="error"){this.app.collection.remove(this.model)}},_showExtensionInfo:function(){var g=$(this.el).find("#extension-info");var i=this.model.get("extension");var h=this.select_extension.text();if(!this.extension_popup){this.extension_popup=new c.View({content:f(i),placement:"bottom",container:g})}if(!this.extension_popup.visible){this.extension_popup.title(h);this.extension_popup.empty();this.extension_popup.append(f(i));this.extension_popup.show()}else{this.extension_popup.hide()}},_showSettings:function(){if(!this.settings.visible){this.settings.empty();this.settings.append((new a(this)).$el);this.settings.show()}else{this.settings.hide()}},_template:function(g){return'<tr id="upload-item-'+g.id+'" class="upload-item"><td><div style="position: relative;"><div id="mode"></div><div id="title" class="title"></div><div id="text" class="text"><div class="text-info">You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.</div><textarea id="text-content" class="text-content form-control"></textarea></div></div></td><td><div id="size" class="size"></div></td><td><div id="extension" class="extension" style="float: left;"/>  <div id="extension-info" class="upload-icon-button fa fa-search"/></td><td><div id="genome" class="genome" /></td><td><div id="settings" class="upload-icon-button fa fa-gear"></div><td><div id="info" class="info"><div class="progress"><div class="progress-bar progress-bar-success"></div><div id="percentage" class="percentage">0%</div></div></div></td><td><div id="symbol" class="'+this.status_classes.init+'"></div></td></tr>'}})});
\ No newline at end of file
diff -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 -r b6acb69067f808147e4e69a1cde74cbb8868c600 static/scripts/packed/mvc/upload/upload-view.js
--- a/static/scripts/packed/mvc/upload/upload-view.js
+++ b/static/scripts/packed/mvc/upload/upload-view.js
@@ -1,1 +1,1 @@
-define(["galaxy.modal","utils/utils","mvc/upload/upload-button","mvc/upload/upload-model","mvc/upload/upload-row","mvc/upload/upload-ftp","mvc/ui.popover","mvc/ui","utils/uploadbox"],function(a,f,e,c,b,g,d){return Backbone.View.extend({options:{nginx_upload_path:""},modal:null,ui_button:null,uploadbox:null,current_history:null,upload_size:0,list_extensions:[{id:"auto",text:"Auto-detect"}],list_genomes:[],collection:new c.Collection(),ftp:null,counter:{announce:0,success:0,error:0,running:0,reset:function(){this.announce=this.success=this.error=this.running=0}},initialize:function(i){var h=this;if(!Galaxy.currHistoryPanel||!Galaxy.currHistoryPanel.model){window.setTimeout(function(){h.initialize()},500);return}if(!Galaxy.currUser.get("id")){return}this.ui_button=new e.Model({icon:"fa-upload",tooltip:"Download from URL or upload files from disk",label:"Load Data",onclick:function(j){if(j){h._eventShow(j)}},onunload:function(){if(h.counter.running>0){return"Several uploads are still processing."}}});$("#left .unified-panel-header-inner").append((new e.View(this.ui_button)).$el);var h=this;f.jsonFromUrl(galaxy_config.root+"api/datatypes?upload_only=True",function(j){for(key in j){h.list_extensions.push({id:j[key],text:j[key]})}});f.jsonFromUrl(galaxy_config.root+"api/genomes",function(j){for(key in j){h.list_genomes.push({id:j[key][1],text:j[key][0]})}h.list_genomes.sort(function(l,k){return l.id>k.id?1:l.id<k.id?-1:0})});if(i){this.options=_.defaults(i,this.options)}this.collection.on("remove",function(j){h._eventRemove(j)});this.collection.on("change:genome",function(k){var j=k.get("genome");h.collection.each(function(l){if(l.get("status")=="init"&&l.get("genome")=="?"){l.set("genome",j)}})})},_eventShow:function(j){j.preventDefault();if(!this.modal){var h=this;this.modal=new a.GalaxyModal({title:"Download data directly from web or upload files from your disk",body:this._template("upload-box","upload-info"),buttons:{"Choose local file":function(){h.uploadbox.select()},"Choose FTP file":function(){h._eventFtp()},"Create new file":function(){h._eventCreate()},Start:function(){h._eventStart()},Pause:function(){h._eventStop()},Reset:function(){h._eventReset()},Close:function(){h.modal.hide()},},height:"400",width:"900",closing_events:true});this.setElement("#upload-box");var h=this;this.uploadbox=this.$el.uploadbox({announce:function(k,l,m){h._eventAnnounce(k,l,m)},initialize:function(k,l,m){return h._eventInitialize(k,l,m)},progress:function(k,l,m){h._eventProgress(k,l,m)},success:function(k,l,m){h._eventSuccess(k,l,m)},error:function(k,l,m){h._eventError(k,l,m)},complete:function(){h._eventComplete()}});this._updateScreen();if(this.options.ftp_upload_dir&&this.options.ftp_upload_site){var i=this.modal.getButton("Choose FTP file");this.ftp=new d.View({title:"FTP files",container:i})}else{this.modal.hideButton("Choose FTP file")}}this.modal.show()},_eventRemove:function(i){var h=i.get("status");if(h=="success"){this.counter.success--}else{if(h=="error"){this.counter.error--}else{this.counter.announce--}}this._updateScreen();this.uploadbox.remove(i.id)},_eventAnnounce:function(h,i,k){this.counter.announce++;this._updateScreen();var j=new b(this,{id:h,file_name:i.name,file_size:i.size,file_mode:i.mode,file_path:i.path});this.collection.add(j.model);$(this.el).find("tbody:first").append(j.$el);j.render()},_eventInitialize:function(m,j,s){var k=this.collection.get(m);k.set("status","running");var o=k.get("file_name");var n=k.get("file_path");var h=k.get("file_mode");var p=k.get("extension");var r=k.get("genome");var q=k.get("url_paste");var l=k.get("space_to_tabs");var i=k.get("to_posix_lines");if(!q&&!(j.size>0)){return null}this.uploadbox.configure({url:this.options.nginx_upload_path});if(h=="local"){this.uploadbox.configure({paramname:"files_0|file_data"})}else{this.uploadbox.configure({paramname:null})}tool_input={};if(h=="new"){tool_input["files_0|url_paste"]=q}if(h=="ftp"){tool_input["files_0|ftp_files"]=n}tool_input.dbkey=r;tool_input.file_type=p;tool_input["files_0|type"]="upload_dataset";tool_input.space_to_tabs=l;tool_input.to_posix_lines=i;data={};data.history_id=this.current_history;data.tool_id="upload1";data.inputs=JSON.stringify(tool_input);return data},_eventProgress:function(i,j,h){var k=this.collection.get(i);k.set("percentage",h);this.ui_button.set("percentage",this._upload_percentage(h,j.size))},_eventSuccess:function(i,j,l){var k=this.collection.get(i);k.set("percentage",100);k.set("status","success");var h=k.get("file_size");this.ui_button.set("percentage",this._upload_percentage(100,h));this.upload_completed+=h*100;this.counter.announce--;this.counter.success++;this._updateScreen();Galaxy.currHistoryPanel.refreshHdas()},_eventError:function(h,i,k){var j=this.collection.get(h);j.set("percentage",100);j.set("status","error");j.set("info",k);this.ui_button.set("percentage",this._upload_percentage(100,i.size));this.ui_button.set("status","danger");this.upload_completed+=i.size*100;this.counter.announce--;this.counter.error++;this._updateScreen()},_eventComplete:function(){this.collection.each(function(h){if(h.get("status")=="queued"){h.set("status","init")}});this.counter.running=0;this._updateScreen()},_eventFtp:function(){if(!this.ftp.visible){this.ftp.empty();this.ftp.append((new g(this)).$el);this.ftp.show()}else{this.ftp.hide()}},_eventCreate:function(){this.uploadbox.add([{name:"New File",size:0,mode:"new"}])},_eventStart:function(){if(this.counter.announce==0||this.counter.running>0){return}var h=this;this.upload_size=0;this.upload_completed=0;this.collection.each(function(i){if(i.get("status")=="init"){i.set("status","queued");h.upload_size+=i.get("file_size")}});this.ui_button.set("percentage",0);this.ui_button.set("status","success");this.current_history=Galaxy.currHistoryPanel.model.get("id");this.counter.running=this.counter.announce;this._updateScreen();this.uploadbox.start()},_eventStop:function(){if(this.counter.running==0){return}this.ui_button.set("status","info");this.uploadbox.stop();$("#upload-info").html("Queue will pause after completing the current file...")},_eventReset:function(){if(this.counter.running==0){this.collection.reset();this.counter.reset();this._updateScreen();this.uploadbox.reset();this.ui_button.set("percentage",0)}},_updateScreen:function(){if(this.counter.announce==0){if(this.uploadbox.compatible()){message="You can Drag & Drop files into this box."}else{message="Unfortunately, your browser does not support multiple file uploads or drag&drop.<br>Some supported browsers are: Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+."}}else{if(this.counter.running==0){message="You added "+this.counter.announce+" file(s) to the queue. Add more files or click 'Start' to proceed."}else{message="Please wait..."+this.counter.announce+" out of "+this.counter.running+" remaining."}}$("#upload-info").html(message);if(this.counter.running==0&&this.counter.announce+this.counter.success+this.counter.error>0){this.modal.enableButton("Reset")}else{this.modal.disableButton("Reset")}if(this.counter.running==0&&this.counter.announce>0){this.modal.enableButton("Start")}else{this.modal.disableButton("Start")}if(this.counter.running>0){this.modal.enableButton("Pause")}else{this.modal.disableButton("Pause")}if(this.counter.running==0){this.modal.enableButton("Choose local file");this.modal.enableButton("Choose FTP file");this.modal.enableButton("Create new file")}else{this.modal.disableButton("Choose local file");this.modal.disableButton("Choose FTP file");this.modal.disableButton("Create new file")}if(this.counter.announce+this.counter.success+this.counter.error>0){$(this.el).find("#upload-table").show()}else{$(this.el).find("#upload-table").hide()}},_upload_percentage:function(h,i){return(this.upload_completed+(h*i))/this.upload_size},_template:function(i,h){return'<div id="'+i+'" class="upload-box"><table id="upload-table" class="table table-striped" style="display: none;"><thead><tr><th>Name</th><th>Size</th><th>Type</th><th>Genome</th><th>Settings</th><th>Status</th><th></th></tr></thead><tbody></tbody></table></div><h6 id="'+h+'" class="upload-info"></h6>'}})});
\ No newline at end of file
+define(["galaxy.modal","utils/utils","mvc/upload/upload-button","mvc/upload/upload-model","mvc/upload/upload-row","mvc/upload/upload-ftp","mvc/ui/popover","mvc/ui","utils/uploadbox"],function(a,f,e,c,b,g,d){return Backbone.View.extend({options:{nginx_upload_path:""},modal:null,ui_button:null,uploadbox:null,current_history:null,upload_size:0,list_extensions:[{id:"auto",text:"Auto-detect"}],list_genomes:[],collection:new c.Collection(),ftp:null,counter:{announce:0,success:0,error:0,running:0,reset:function(){this.announce=this.success=this.error=this.running=0}},initialize:function(i){var h=this;if(!Galaxy.currHistoryPanel||!Galaxy.currHistoryPanel.model){window.setTimeout(function(){h.initialize()},500);return}if(!Galaxy.currUser.get("id")){return}this.ui_button=new e.Model({icon:"fa-upload",tooltip:"Download from URL or upload files from disk",label:"Load Data",onclick:function(j){if(j){h._eventShow(j)}},onunload:function(){if(h.counter.running>0){return"Several uploads are still processing."}}});$("#left .unified-panel-header-inner").append((new e.View(this.ui_button)).$el);var h=this;f.jsonFromUrl(galaxy_config.root+"api/datatypes?upload_only=True",function(j){for(key in j){h.list_extensions.push({id:j[key],text:j[key]})}});f.jsonFromUrl(galaxy_config.root+"api/genomes",function(j){for(key in j){h.list_genomes.push({id:j[key][1],text:j[key][0]})}h.list_genomes.sort(function(l,k){return l.id>k.id?1:l.id<k.id?-1:0})});if(i){this.options=_.defaults(i,this.options)}this.collection.on("remove",function(j){h._eventRemove(j)});this.collection.on("change:genome",function(k){var j=k.get("genome");h.collection.each(function(l){if(l.get("status")=="init"&&l.get("genome")=="?"){l.set("genome",j)}})})},_eventShow:function(j){j.preventDefault();if(!this.modal){var h=this;this.modal=new a.GalaxyModal({title:"Download data directly from web or upload files from your disk",body:this._template("upload-box","upload-info"),buttons:{"Choose local file":function(){h.uploadbox.select()},"Choose FTP file":function(){h._eventFtp()},"Create new file":function(){h._eventCreate()},Start:function(){h._eventStart()},Pause:function(){h._eventStop()},Reset:function(){h._eventReset()},Close:function(){h.modal.hide()},},height:"400",width:"900",closing_events:true});this.setElement("#upload-box");var h=this;this.uploadbox=this.$el.uploadbox({announce:function(k,l,m){h._eventAnnounce(k,l,m)},initialize:function(k,l,m){return h._eventInitialize(k,l,m)},progress:function(k,l,m){h._eventProgress(k,l,m)},success:function(k,l,m){h._eventSuccess(k,l,m)},error:function(k,l,m){h._eventError(k,l,m)},complete:function(){h._eventComplete()}});this._updateScreen();if(this.options.ftp_upload_dir&&this.options.ftp_upload_site){var i=this.modal.getButton("Choose FTP file");this.ftp=new d.View({title:"FTP files",container:i})}else{this.modal.hideButton("Choose FTP file")}}this.modal.show()},_eventRemove:function(i){var h=i.get("status");if(h=="success"){this.counter.success--}else{if(h=="error"){this.counter.error--}else{this.counter.announce--}}this._updateScreen();this.uploadbox.remove(i.id)},_eventAnnounce:function(h,i,k){this.counter.announce++;this._updateScreen();var j=new b(this,{id:h,file_name:i.name,file_size:i.size,file_mode:i.mode,file_path:i.path});this.collection.add(j.model);$(this.el).find("tbody:first").append(j.$el);j.render()},_eventInitialize:function(m,j,s){var k=this.collection.get(m);k.set("status","running");var o=k.get("file_name");var n=k.get("file_path");var h=k.get("file_mode");var p=k.get("extension");var r=k.get("genome");var q=k.get("url_paste");var l=k.get("space_to_tabs");var i=k.get("to_posix_lines");if(!q&&!(j.size>0)){return null}this.uploadbox.configure({url:this.options.nginx_upload_path});if(h=="local"){this.uploadbox.configure({paramname:"files_0|file_data"})}else{this.uploadbox.configure({paramname:null})}tool_input={};if(h=="new"){tool_input["files_0|url_paste"]=q}if(h=="ftp"){tool_input["files_0|ftp_files"]=n}tool_input.dbkey=r;tool_input.file_type=p;tool_input["files_0|type"]="upload_dataset";tool_input.space_to_tabs=l;tool_input.to_posix_lines=i;data={};data.history_id=this.current_history;data.tool_id="upload1";data.inputs=JSON.stringify(tool_input);return data},_eventProgress:function(i,j,h){var k=this.collection.get(i);k.set("percentage",h);this.ui_button.set("percentage",this._upload_percentage(h,j.size))},_eventSuccess:function(i,j,l){var k=this.collection.get(i);k.set("percentage",100);k.set("status","success");var h=k.get("file_size");this.ui_button.set("percentage",this._upload_percentage(100,h));this.upload_completed+=h*100;this.counter.announce--;this.counter.success++;this._updateScreen();Galaxy.currHistoryPanel.refreshHdas()},_eventError:function(h,i,k){var j=this.collection.get(h);j.set("percentage",100);j.set("status","error");j.set("info",k);this.ui_button.set("percentage",this._upload_percentage(100,i.size));this.ui_button.set("status","danger");this.upload_completed+=i.size*100;this.counter.announce--;this.counter.error++;this._updateScreen()},_eventComplete:function(){this.collection.each(function(h){if(h.get("status")=="queued"){h.set("status","init")}});this.counter.running=0;this._updateScreen()},_eventFtp:function(){if(!this.ftp.visible){this.ftp.empty();this.ftp.append((new g(this)).$el);this.ftp.show()}else{this.ftp.hide()}},_eventCreate:function(){this.uploadbox.add([{name:"New File",size:0,mode:"new"}])},_eventStart:function(){if(this.counter.announce==0||this.counter.running>0){return}var h=this;this.upload_size=0;this.upload_completed=0;this.collection.each(function(i){if(i.get("status")=="init"){i.set("status","queued");h.upload_size+=i.get("file_size")}});this.ui_button.set("percentage",0);this.ui_button.set("status","success");this.current_history=Galaxy.currHistoryPanel.model.get("id");this.counter.running=this.counter.announce;this._updateScreen();this.uploadbox.start()},_eventStop:function(){if(this.counter.running==0){return}this.ui_button.set("status","info");this.uploadbox.stop();$("#upload-info").html("Queue will pause after completing the current file...")},_eventReset:function(){if(this.counter.running==0){this.collection.reset();this.counter.reset();this._updateScreen();this.uploadbox.reset();this.ui_button.set("percentage",0)}},_updateScreen:function(){if(this.counter.announce==0){if(this.uploadbox.compatible()){message="You can Drag & Drop files into this box."}else{message="Unfortunately, your browser does not support multiple file uploads or drag&drop.<br>Some supported browsers are: Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+."}}else{if(this.counter.running==0){message="You added "+this.counter.announce+" file(s) to the queue. Add more files or click 'Start' to proceed."}else{message="Please wait..."+this.counter.announce+" out of "+this.counter.running+" remaining."}}$("#upload-info").html(message);if(this.counter.running==0&&this.counter.announce+this.counter.success+this.counter.error>0){this.modal.enableButton("Reset")}else{this.modal.disableButton("Reset")}if(this.counter.running==0&&this.counter.announce>0){this.modal.enableButton("Start")}else{this.modal.disableButton("Start")}if(this.counter.running>0){this.modal.enableButton("Pause")}else{this.modal.disableButton("Pause")}if(this.counter.running==0){this.modal.enableButton("Choose local file");this.modal.enableButton("Choose FTP file");this.modal.enableButton("Create new file")}else{this.modal.disableButton("Choose local file");this.modal.disableButton("Choose FTP file");this.modal.disableButton("Create new file")}if(this.counter.announce+this.counter.success+this.counter.error>0){$(this.el).find("#upload-table").show()}else{$(this.el).find("#upload-table").hide()}},_upload_percentage:function(h,i){return(this.upload_completed+(h*i))/this.upload_size},_template:function(i,h){return'<div id="'+i+'" class="upload-box"><table id="upload-table" class="table table-striped" style="display: none;"><thead><tr><th>Name</th><th>Size</th><th>Type</th><th>Genome</th><th>Settings</th><th>Status</th><th></th></tr></thead><tbody></tbody></table></div><h6 id="'+h+'" class="upload-info"></h6>'}})});
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: guerler: Upload: Improve select style
by commits-noreply@bitbucket.org 13 Feb '14
by commits-noreply@bitbucket.org 13 Feb '14
13 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0ef8c71a3ed1/
Changeset: 0ef8c71a3ed1
User: guerler
Date: 2014-02-13 09:33:39
Summary: Upload: Improve select style
Affected #: 2 files
diff -r e8810dcb7d7295fc61288c338c2ea9e36b083225 -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 static/style/blue/base.css
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -1264,6 +1264,7 @@
.upload-box .table td{margin:0px;paddign:0px}
.upload-box .upload-item .select2-arrow b{background-position:0 -3px}
.upload-box .upload-item .select2-choice{max-height:20px;line-height:18px;background:transparent}
+.upload-box .upload-item .select2-container-disabled .select2-arrow{background:transparent}
.upload-box .mode{float:left;font-size:1.2em;width:1.2em;color:#555}
.upload-box .title{width:130px;word-wrap:break-word;font-size:11px;float:left}
.upload-box .text{position:absolute;display:none}.upload-box .text .text-content{font-size:11px;width:100%;height:50px;resize:none;background:inherit;color:#000}
@@ -1684,15 +1685,15 @@
div.historyItem-paused{background:#d9edf7}div.historyItem-paused .state-icon{line-height:16px;font-family:FontAwesome;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}div.historyItem-paused .state-icon:before{content:"\f04c"}
div.historyItem-new .state-icon{line-height:16px;font-family:FontAwesome;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}div.historyItem-new .state-icon:before{content:"\f071"}
div.historyItemTitleBar.spinner .state-icon{background:url(data_running.gif) 0 1px no-repeat !important}
-div.historyItemButtons{float:right}div.historyItemButtons .icon-button.display{background-image:url(sprite-history-buttons.png);background-position:0px -48px;width:16px;height:16px}
-div.historyItemButtons .icon-button.display:hover{background-image:url(sprite-history-buttons.png);background-position:0px -64px;width:16px;height:16px}
-div.historyItemButtons .icon-button.display_disabled{background-image:url(sprite-history-buttons.png);background-position:0px -80px;width:16px;height:16px}
-div.historyItemButtons .icon-button.delete{background-image:url(sprite-history-buttons.png);background-position:0px 0px;width:16px;height:16px}
-div.historyItemButtons .icon-button.delete:hover{background-image:url(sprite-history-buttons.png);background-position:0px -16px;width:16px;height:16px}
-div.historyItemButtons .icon-button.delete_disabled{background-image:url(sprite-history-buttons.png);background-position:0px -32px;width:16px;height:16px}
-div.historyItemButtons .icon-button.edit{background-image:url(sprite-history-buttons.png);background-position:0px -96px;width:16px;height:16px}
-div.historyItemButtons .icon-button.edit:hover{background-image:url(sprite-history-buttons.png);background-position:0px -112px;width:16px;height:16px}
-div.historyItemButtons .icon-button.edit_disabled{background-image:url(sprite-history-buttons.png);background-position:0px -128px;width:16px;height:16px}
+div.historyItemButtons{float:right}div.historyItemButtons .icon-button.display{background-image:url(sprite-history-buttons.png);background-position:0px -48px;width:16px;height:16px;height:16px}
+div.historyItemButtons .icon-button.display:hover{background-image:url(sprite-history-buttons.png);background-position:0px -64px;width:16px;height:16px;height:16px}
+div.historyItemButtons .icon-button.display_disabled{background-image:url(sprite-history-buttons.png);background-position:0px -80px;width:16px;height:16px;height:16px}
+div.historyItemButtons .icon-button.delete{background-image:url(sprite-history-buttons.png);background-position:0px 0px;width:16px;height:16px;height:16px}
+div.historyItemButtons .icon-button.delete:hover{background-image:url(sprite-history-buttons.png);background-position:0px -16px;width:16px;height:16px;height:16px}
+div.historyItemButtons .icon-button.delete_disabled{background-image:url(sprite-history-buttons.png);background-position:0px -32px;width:16px;height:16px;height:16px}
+div.historyItemButtons .icon-button.edit{background-image:url(sprite-history-buttons.png);background-position:0px -96px;width:16px;height:16px;height:16px}
+div.historyItemButtons .icon-button.edit:hover{background-image:url(sprite-history-buttons.png);background-position:0px -112px;width:16px;height:16px;height:16px}
+div.historyItemButtons .icon-button.edit_disabled{background-image:url(sprite-history-buttons.png);background-position:0px -128px;width:16px;height:16px;height:16px}
div.historyItemBody div{padding-top:2px}
pre.peek{background:white;color:black;width:100%;font-size:10px;overflow:auto}pre.peek th{color:white;background:#5f6990}
pre.peek table,pre.peek th,pre.peek tr,pre.peek td{font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:10px}
diff -r e8810dcb7d7295fc61288c338c2ea9e36b083225 -r 0ef8c71a3ed14f7f6f8d53a43969501b39f83496 static/style/src/less/upload.less
--- a/static/style/src/less/upload.less
+++ b/static/style/src/less/upload.less
@@ -95,6 +95,12 @@
line-height: 18px;
background: transparent;
}
+
+ .select2-container-disabled {
+ .select2-arrow {
+ background: transparent;
+ }
+ }
}
.mode {
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: guerler: Upload: Fix select removal
by commits-noreply@bitbucket.org 13 Feb '14
by commits-noreply@bitbucket.org 13 Feb '14
13 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/e8810dcb7d72/
Changeset: e8810dcb7d72
User: guerler
Date: 2014-02-13 09:14:26
Summary: Upload: Fix select removal
Affected #: 4 files
diff -r 086352011e076cc658ce75b9d8caa665e54b31c8 -r e8810dcb7d7295fc61288c338c2ea9e36b083225 static/scripts/mvc/ui.select.js
--- a/static/scripts/mvc/ui.select.js
+++ b/static/scripts/mvc/ui.select.js
@@ -100,7 +100,7 @@
},
// remove
- remove: function(id) {
+ del: function(id) {
// search option
var index = this._getIndex(id);
@@ -114,6 +114,11 @@
}
},
+ // remove
+ remove: function() {
+ this.$el.select2('destroy');
+ },
+
// update
update: function(options) {
// copy options
diff -r 086352011e076cc658ce75b9d8caa665e54b31c8 -r e8810dcb7d7295fc61288c338c2ea9e36b083225 static/scripts/mvc/upload/upload-row.js
--- a/static/scripts/mvc/upload/upload-row.js
+++ b/static/scripts/mvc/upload/upload-row.js
@@ -195,6 +195,16 @@
}
},
+ // remove
+ remove: function() {
+ // trigger remove event
+ this.select_genome.remove();
+ this.select_extension.remove();
+
+ // call the base class remove method
+ Backbone.View.prototype.remove.apply(this);
+ },
+
//
// handle model events
//
diff -r 086352011e076cc658ce75b9d8caa665e54b31c8 -r e8810dcb7d7295fc61288c338c2ea9e36b083225 static/scripts/packed/mvc/ui.select.js
--- a/static/scripts/packed/mvc/ui.select.js
+++ b/static/scripts/packed/mvc/ui.select.js
@@ -1,1 +1,1 @@
-define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{css:"",placeholder:"No data available",data:[]},initialize:function(d){this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.options.container.append(this.$el);this.select_data=this.options.data;this._refresh();if(this.options.value){this._setValue(this.options.value)}var c=this;if(this.options.onchange){this.$el.on("change",function(){c.options.onchange(c.value())})}},value:function(c){var d=this._getValue();if(c!==undefined){this._setValue(c)}var e=this._getValue();if(e===undefined){return null}else{if((e!=d&&this.options.onchange)){this.options.onchange(e)}return e}},text:function(){return this.$el.select2("data").text},disabled:function(){return !this.$el.select2("enable")},enable:function(){this.$el.select2("enable",true)},disable:function(){this.$el.select2("enable",false)},add:function(c){this.select_data.push({id:c.id,text:c.text});this._refresh()},remove:function(d){var c=this._getIndex(d);if(c!=-1){this.select_data.splice(c,1);this._refresh()}},update:function(c){this.select_data=[];for(var d in c.data){this.select_data.push(c.data[d])}this._refresh()},_refresh:function(){var e=this.select_data;if(!e||e.length==0){e=[]}var d=this._getValue();this.$el.select2({data:e,containerCssClass:this.options.css,placeholder:this.options.placeholder});var c=this._getIndex(d);if(c!=-1){this._setValue(d)}},_getIndex:function(d){for(var c in this.select_data){if(this.select_data[c].id==d){return c}}return -1},_getValue:function(){return this.$el.select2("val")},_setValue:function(c){this.$el.select2("val",c)},_template:function(c){return'<input type="hidden"/>'}});return{View:b}});
\ No newline at end of file
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{css:"",placeholder:"No data available",data:[]},initialize:function(d){this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.options.container.append(this.$el);this.select_data=this.options.data;this._refresh();if(this.options.value){this._setValue(this.options.value)}var c=this;if(this.options.onchange){this.$el.on("change",function(){c.options.onchange(c.value())})}},value:function(c){var d=this._getValue();if(c!==undefined){this._setValue(c)}var e=this._getValue();if(e===undefined){return null}else{if((e!=d&&this.options.onchange)){this.options.onchange(e)}return e}},text:function(){return this.$el.select2("data").text},disabled:function(){return !this.$el.select2("enable")},enable:function(){this.$el.select2("enable",true)},disable:function(){this.$el.select2("enable",false)},add:function(c){this.select_data.push({id:c.id,text:c.text});this._refresh()},del:function(d){var c=this._getIndex(d);if(c!=-1){this.select_data.splice(c,1);this._refresh()}},remove:function(){this.$el.select2("destroy")},update:function(c){this.select_data=[];for(var d in c.data){this.select_data.push(c.data[d])}this._refresh()},_refresh:function(){var e=this.select_data;if(!e||e.length==0){e=[]}var d=this._getValue();this.$el.select2({data:e,containerCssClass:this.options.css,placeholder:this.options.placeholder});var c=this._getIndex(d);if(c!=-1){this._setValue(d)}},_getIndex:function(d){for(var c in this.select_data){if(this.select_data[c].id==d){return c}}return -1},_getValue:function(){return this.$el.select2("val")},_setValue:function(c){this.$el.select2("val",c)},_template:function(c){return'<input type="hidden"/>'}});return{View:b}});
\ No newline at end of file
diff -r 086352011e076cc658ce75b9d8caa665e54b31c8 -r e8810dcb7d7295fc61288c338c2ea9e36b083225 static/scripts/packed/mvc/upload/upload-row.js
--- a/static/scripts/packed/mvc/upload/upload-row.js
+++ b/static/scripts/packed/mvc/upload/upload-row.js
@@ -1,1 +1,1 @@
-define(["utils/utils","mvc/upload/upload-model","mvc/upload/upload-extensions","mvc/upload/upload-settings","mvc/ui.popover","mvc/ui.select"],function(d,b,f,a,c,e){return Backbone.View.extend({options:{padding:8},status_classes:{init:"upload-icon-button fa fa-trash-o",queued:"upload-icon fa fa-spinner fa-spin",running:"upload-icon fa fa-spinner fa-spin",success:"upload-icon-button fa fa-check",error:"upload-icon-button fa fa-exclamation-triangle"},settings:null,select_genome:null,select_extension:null,initialize:function(j,h){this.app=j;var g=this;this.model=new b.Model(h);this.setElement(this._template(h));var i=this.$el;this.settings=new c.View({title:"Upload configuration",container:i.find("#settings"),placement:"bottom"});this.select_genome=new e.View({css:"genome",onchange:function(){g.model.set("genome",g.select_genome.value())},data:g.app.list_genomes,container:i.find("#genome"),value:g.model.get("genome")});this.select_extension=new e.View({css:"extension",onchange:function(){g.model.set("extension",g.select_extension.value())},data:g.app.list_extensions,container:i.find("#extension"),value:g.model.get("extension")});i.find("#symbol").on("click",function(){g._removeRow()});i.find("#extension-info").on("click",function(k){g._showExtensionInfo()}).on("mousedown",function(k){k.preventDefault()});i.find("#settings").on("click",function(k){g._showSettings()}).on("mousedown",function(k){k.preventDefault()});i.find("#text-content").on("keyup",function(k){g.model.set("url_paste",$(k.target).val());g.model.set("file_size",$(k.target).val().length)});i.find("#space_to_tabs").on("change",function(k){g.model.set("space_to_tabs",$(k.target).prop("checked"))});this.model.on("change:percentage",function(){g._refreshPercentage()});this.model.on("change:status",function(){g._refreshStatus()});this.model.on("change:info",function(){g._refreshInfo()});this.model.on("change:genome",function(){g._refreshGenome()});this.model.on("change:file_size",function(){g._refreshFileSize()});this.model.on("remove",function(){g.remove()});this.app.collection.on("reset",function(){g.remove()})},render:function(){var n=this.model.get("file_name");var h=this.model.get("file_size");var k=this.model.get("file_mode");var j=this.$el;j.find("#title").html(n);j.find("#size").html(d.bytesToString(h));j.find("#mode").removeClass().addClass("mode");if(k=="new"){var m=j.find("#text");var l=this.options.padding;var i=j.width()-2*l;var g=j.height()-l;m.css("width",i+"px");m.css("top",g+"px");j.height(g+m.height()+2*l);m.show();j.find("#mode").addClass("fa fa-pencil")}if(k=="local"){j.find("#mode").addClass("fa fa-laptop")}if(k=="ftp"){j.find("#mode").addClass("fa fa-code-fork")}},_refreshGenome:function(){var g=this.model.get("genome");this.select_genome.value(g)},_refreshInfo:function(){var g=this.model.get("info");if(g){this.$el.find("#info").html("<strong>Failed: </strong>"+g).show()}else{this.$el.find("#info").hide()}},_refreshPercentage:function(){var g=parseInt(this.model.get("percentage"));this.$el.find(".progress-bar").css({width:g+"%"});if(g!=100){this.$el.find("#percentage").html(g+"%")}else{this.$el.find("#percentage").html("Adding to history...")}},_refreshStatus:function(){var h=this.$el;var g=this.model.get("status");var j=this.status_classes[g];var i=this.$el.find("#symbol");i.removeClass();i.addClass(j);if(g=="init"){this.select_genome.enable();this.select_extension.enable();h.find("#text-content").attr("disabled",false);h.find("#space_to_tabs").attr("disabled",false)}else{this.select_genome.disable();this.select_extension.disable();h.find("#text-content").attr("disabled",true);h.find("#space_to_tabs").attr("disabled",true)}if(g=="success"){h.addClass("success");h.find("#percentage").html("100%")}if(g=="error"){h.addClass("danger");h.find(".progress").remove()}},_refreshFileSize:function(){var g=this.model.get("file_size");this.$el.find("#size").html(d.bytesToString(g))},_removeRow:function(){var g=this.model.get("status");if(g=="init"||g=="success"||g=="error"){this.app.collection.remove(this.model)}},_showExtensionInfo:function(){var g=$(this.el).find("#extension-info");var i=this.model.get("extension");var h=this.select_extension.text();if(!this.extension_popup){this.extension_popup=new c.View({content:f(i),placement:"bottom",container:g})}if(!this.extension_popup.visible){this.extension_popup.title(h);this.extension_popup.empty();this.extension_popup.append(f(i));this.extension_popup.show()}else{this.extension_popup.hide()}},_showSettings:function(){if(!this.settings.visible){this.settings.empty();this.settings.append((new a(this)).$el);this.settings.show()}else{this.settings.hide()}},_template:function(g){return'<tr id="upload-item-'+g.id+'" class="upload-item"><td><div style="position: relative;"><div id="mode"></div><div id="title" class="title"></div><div id="text" class="text"><div class="text-info">You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.</div><textarea id="text-content" class="text-content form-control"></textarea></div></div></td><td><div id="size" class="size"></div></td><td><div id="extension" class="extension" style="float: left;"/>  <div id="extension-info" class="upload-icon-button fa fa-search"/></td><td><div id="genome" class="genome" /></td><td><div id="settings" class="upload-icon-button fa fa-gear"></div><td><div id="info" class="info"><div class="progress"><div class="progress-bar progress-bar-success"></div><div id="percentage" class="percentage">0%</div></div></div></td><td><div id="symbol" class="'+this.status_classes.init+'"></div></td></tr>'}})});
\ No newline at end of file
+define(["utils/utils","mvc/upload/upload-model","mvc/upload/upload-extensions","mvc/upload/upload-settings","mvc/ui.popover","mvc/ui.select"],function(d,b,f,a,c,e){return Backbone.View.extend({options:{padding:8},status_classes:{init:"upload-icon-button fa fa-trash-o",queued:"upload-icon fa fa-spinner fa-spin",running:"upload-icon fa fa-spinner fa-spin",success:"upload-icon-button fa fa-check",error:"upload-icon-button fa fa-exclamation-triangle"},settings:null,select_genome:null,select_extension:null,initialize:function(j,h){this.app=j;var g=this;this.model=new b.Model(h);this.setElement(this._template(h));var i=this.$el;this.settings=new c.View({title:"Upload configuration",container:i.find("#settings"),placement:"bottom"});this.select_genome=new e.View({css:"genome",onchange:function(){g.model.set("genome",g.select_genome.value())},data:g.app.list_genomes,container:i.find("#genome"),value:g.model.get("genome")});this.select_extension=new e.View({css:"extension",onchange:function(){g.model.set("extension",g.select_extension.value())},data:g.app.list_extensions,container:i.find("#extension"),value:g.model.get("extension")});i.find("#symbol").on("click",function(){g._removeRow()});i.find("#extension-info").on("click",function(k){g._showExtensionInfo()}).on("mousedown",function(k){k.preventDefault()});i.find("#settings").on("click",function(k){g._showSettings()}).on("mousedown",function(k){k.preventDefault()});i.find("#text-content").on("keyup",function(k){g.model.set("url_paste",$(k.target).val());g.model.set("file_size",$(k.target).val().length)});i.find("#space_to_tabs").on("change",function(k){g.model.set("space_to_tabs",$(k.target).prop("checked"))});this.model.on("change:percentage",function(){g._refreshPercentage()});this.model.on("change:status",function(){g._refreshStatus()});this.model.on("change:info",function(){g._refreshInfo()});this.model.on("change:genome",function(){g._refreshGenome()});this.model.on("change:file_size",function(){g._refreshFileSize()});this.model.on("remove",function(){g.remove()});this.app.collection.on("reset",function(){g.remove()})},render:function(){var n=this.model.get("file_name");var h=this.model.get("file_size");var k=this.model.get("file_mode");var j=this.$el;j.find("#title").html(n);j.find("#size").html(d.bytesToString(h));j.find("#mode").removeClass().addClass("mode");if(k=="new"){var m=j.find("#text");var l=this.options.padding;var i=j.width()-2*l;var g=j.height()-l;m.css("width",i+"px");m.css("top",g+"px");j.height(g+m.height()+2*l);m.show();j.find("#mode").addClass("fa fa-pencil")}if(k=="local"){j.find("#mode").addClass("fa fa-laptop")}if(k=="ftp"){j.find("#mode").addClass("fa fa-code-fork")}},remove:function(){this.select_genome.remove();this.select_extension.remove();Backbone.View.prototype.remove.apply(this)},_refreshGenome:function(){var g=this.model.get("genome");this.select_genome.value(g)},_refreshInfo:function(){var g=this.model.get("info");if(g){this.$el.find("#info").html("<strong>Failed: </strong>"+g).show()}else{this.$el.find("#info").hide()}},_refreshPercentage:function(){var g=parseInt(this.model.get("percentage"));this.$el.find(".progress-bar").css({width:g+"%"});if(g!=100){this.$el.find("#percentage").html(g+"%")}else{this.$el.find("#percentage").html("Adding to history...")}},_refreshStatus:function(){var h=this.$el;var g=this.model.get("status");var j=this.status_classes[g];var i=this.$el.find("#symbol");i.removeClass();i.addClass(j);if(g=="init"){this.select_genome.enable();this.select_extension.enable();h.find("#text-content").attr("disabled",false);h.find("#space_to_tabs").attr("disabled",false)}else{this.select_genome.disable();this.select_extension.disable();h.find("#text-content").attr("disabled",true);h.find("#space_to_tabs").attr("disabled",true)}if(g=="success"){h.addClass("success");h.find("#percentage").html("100%")}if(g=="error"){h.addClass("danger");h.find(".progress").remove()}},_refreshFileSize:function(){var g=this.model.get("file_size");this.$el.find("#size").html(d.bytesToString(g))},_removeRow:function(){var g=this.model.get("status");if(g=="init"||g=="success"||g=="error"){this.app.collection.remove(this.model)}},_showExtensionInfo:function(){var g=$(this.el).find("#extension-info");var i=this.model.get("extension");var h=this.select_extension.text();if(!this.extension_popup){this.extension_popup=new c.View({content:f(i),placement:"bottom",container:g})}if(!this.extension_popup.visible){this.extension_popup.title(h);this.extension_popup.empty();this.extension_popup.append(f(i));this.extension_popup.show()}else{this.extension_popup.hide()}},_showSettings:function(){if(!this.settings.visible){this.settings.empty();this.settings.append((new a(this)).$el);this.settings.show()}else{this.settings.hide()}},_template:function(g){return'<tr id="upload-item-'+g.id+'" class="upload-item"><td><div style="position: relative;"><div id="mode"></div><div id="title" class="title"></div><div id="text" class="text"><div class="text-info">You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.</div><textarea id="text-content" class="text-content form-control"></textarea></div></div></td><td><div id="size" class="size"></div></td><td><div id="extension" class="extension" style="float: left;"/>  <div id="extension-info" class="upload-icon-button fa fa-search"/></td><td><div id="genome" class="genome" /></td><td><div id="settings" class="upload-icon-button fa fa-gear"></div><td><div id="info" class="info"><div class="progress"><div class="progress-bar progress-bar-success"></div><div id="percentage" class="percentage">0%</div></div></div></td><td><div id="symbol" class="'+this.status_classes.init+'"></div></td></tr>'}})});
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: guerler: Upload: Add select2 selection fields, add select2 backbone wrapper
by commits-noreply@bitbucket.org 13 Feb '14
by commits-noreply@bitbucket.org 13 Feb '14
13 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/086352011e07/
Changeset: 086352011e07
User: guerler
Date: 2014-02-13 08:37:10
Summary: Upload: Add select2 selection fields, add select2 backbone wrapper
Affected #: 8 files
diff -r 64e6873c8825164718ca14f17379b114e3539b09 -r 086352011e076cc658ce75b9d8caa665e54b31c8 static/scripts/mvc/ui.select.js
--- /dev/null
+++ b/static/scripts/mvc/ui.select.js
@@ -0,0 +1,188 @@
+// dependencies
+define(['utils/utils'], function(Utils) {
+
+// plugin
+var View = Backbone.View.extend(
+{
+ // options
+ optionsDefault: {
+ css : '',
+ placeholder : 'No data available',
+ data : []
+ },
+
+ // initialize
+ initialize : function(options) {
+ // configure options
+ this.options = Utils.merge(options, this.optionsDefault);
+
+ // create new element
+ this.setElement(this._template(this.options));
+
+ // add to dom
+ this.options.container.append(this.$el);
+
+ // link selection dictionary
+ this.select_data = this.options.data;
+
+ // refresh
+ this._refresh();
+
+ // initial value
+ if (this.options.value) {
+ this._setValue(this.options.value);
+ }
+
+ // add change event
+ var self = this;
+ if (this.options.onchange) {
+ this.$el.on('change', function() {
+ self.options.onchange(self.value());
+ });
+ }
+ },
+
+ // value
+ value : function (new_value) {
+ // get current id/value
+ var before = this._getValue();
+
+ // check if new_value is defined
+ if (new_value !== undefined) {
+ this._setValue(new_value);
+ }
+
+ // get current id/value
+ var after = this._getValue();
+ if(after === undefined) {
+ return null;
+ } else {
+ // fire onchange
+ if ((after != before && this.options.onchange)) {
+ this.options.onchange(after);
+ }
+
+ // return current value
+ return after;
+ }
+ },
+
+ // label
+ text : function () {
+ return this.$el.select2('data').text;
+ },
+
+ // disabled
+ disabled: function() {
+ return !this.$el.select2('enable');
+ },
+
+ // enable
+ enable: function() {
+ this.$el.select2('enable', true);
+ },
+
+ // disable
+ disable: function() {
+ this.$el.select2('enable', false);
+ },
+
+ // add
+ add: function(options) {
+ // add options
+ this.select_data.push({
+ id : options.id,
+ text : options.text
+ });
+
+ // refresh
+ this._refresh();
+ },
+
+ // remove
+ remove: function(id) {
+ // search option
+ var index = this._getIndex(id);
+
+ // check if found
+ if (index != -1) {
+ // remove options
+ this.select_data.splice(index, 1);
+
+ // refresh
+ this._refresh();
+ }
+ },
+
+ // update
+ update: function(options) {
+ // copy options
+ this.select_data = [];
+ for (var key in options.data) {
+ this.select_data.push(options.data[key]);
+ }
+
+ // refresh
+ this._refresh();
+ },
+
+ // refresh
+ _refresh: function() {
+
+ // link select data
+ var select_data = this.select_data;
+ if (!select_data || select_data.length == 0) {
+ select_data = [];
+ }
+
+ // selected
+ var selected = this._getValue();
+
+ // add select2 data
+ this.$el.select2({
+ data : select_data,
+ containerCssClass : this.options.css,
+ placeholder : this.options.placeholder
+ });
+
+ // select
+ var index = this._getIndex(selected);
+ if (index != -1) {
+ this._setValue(selected);
+ }
+ },
+
+ // get index
+ _getIndex: function(value) {
+ // search index
+ for (var key in this.select_data) {
+ if (this.select_data[key].id == value) {
+ return key;
+ }
+ }
+
+ // not found
+ return -1;
+ },
+
+ // get value
+ _getValue: function() {
+ return this.$el.select2('val');
+ },
+
+ // set value
+ _setValue: function(new_value) {
+ this.$el.select2('val', new_value);
+ },
+
+ // element
+ _template: function(options) {
+ return '<input type="hidden"/>';
+ }
+});
+
+return {
+ View : View
+}
+
+});
diff -r 64e6873c8825164718ca14f17379b114e3539b09 -r 086352011e076cc658ce75b9d8caa665e54b31c8 static/scripts/mvc/upload/upload-row.js
--- a/static/scripts/mvc/upload/upload-row.js
+++ b/static/scripts/mvc/upload/upload-row.js
@@ -3,13 +3,15 @@
'mvc/upload/upload-model',
'mvc/upload/upload-extensions',
'mvc/upload/upload-settings',
- 'mvc/ui.popover'],
+ 'mvc/ui.popover',
+ 'mvc/ui.select'],
function( Utils,
UploadModel,
UploadExtensions,
UploadSettings,
- Popover
+ Popover,
+ Select
) {
// item view
@@ -31,6 +33,12 @@
// handle for settings popover
settings: null,
+ // genome selector
+ select_genome : null,
+
+ // extension selector
+ select_extension : null,
+
// render
initialize: function(app, options) {
// link app
@@ -55,6 +63,28 @@
placement : 'bottom'
});
+ // select genomes
+ this.select_genome = new Select.View({
+ css: 'genome',
+ onchange : function() {
+ self.model.set('genome', self.select_genome.value());
+ },
+ data: self.app.list_genomes,
+ container: it.find('#genome'),
+ value: self.model.get('genome')
+ });
+
+ // select extension
+ this.select_extension = new Select.View({
+ css: 'extension',
+ onchange : function() {
+ self.model.set('extension', self.select_extension.value());
+ },
+ data: self.app.list_extensions,
+ container: it.find('#extension'),
+ value: self.model.get('extension')
+ });
+
//
// ui events
//
@@ -76,16 +106,6 @@
self.model.set('file_size', $(e.target).val().length);
});
- // handle genome selection
- it.find('#genome').on('change', function(e) {
- self.model.set('genome', $(e.target).val());
- });
-
- // handle extension selection
- it.find('#extension').on('change', function(e) {
- self.model.set('extension', $(e.target).val());
- });
-
// handle space to tabs button
it.find('#space_to_tabs').on('change', function(e) {
self.model.set('space_to_tabs', $(e.target).prop('checked'));
@@ -100,9 +120,6 @@
this.model.on('change:status', function() {
self._refreshStatus();
});
- this.model.on('change:extension', function() {
- self._destroyExtensionInfo();
- });
this.model.on('change:info', function() {
self._refreshInfo();
});
@@ -113,11 +130,9 @@
self._refreshFileSize();
});
this.model.on('remove', function() {
- self._destroyExtensionInfo();
self.remove();
});
this.app.collection.on('reset', function() {
- self._destroyExtensionInfo();
self.remove();
});
},
@@ -189,7 +204,7 @@
{
// update genome info on screen
var genome = this.model.get('genome');
- this.$el.find('#genome').val(genome);
+ this.select_genome.value(genome);
},
// progress
@@ -231,17 +246,23 @@
// set new status class
sy.addClass(status_class);
-
+
// enable form fields
if (status == 'init') {
+ // select fields
+ this.select_genome.enable();
+ this.select_extension.enable();
+
+ // default fields
it.find('#text-content').attr('disabled', false);
- it.find('#genome').attr('disabled', false);
- it.find('#extension').attr('disabled', false);
it.find('#space_to_tabs').attr('disabled', false);
} else {
+ // select fields
+ this.select_genome.disable();
+ this.select_extension.disable();
+
+ // default fields
it.find('#text-content').attr('disabled', true);
- it.find('#genome').attr('disabled', true);
- it.find('#extension').attr('disabled', true);
it.find('#space_to_tabs').attr('disabled', true);
}
@@ -275,7 +296,6 @@
// only remove from queue if not in processing line
if (status == 'init' || status == 'success' || status == 'error') {
- // remove from collection
this.app.collection.remove(this.model);
}
},
@@ -286,7 +306,7 @@
// initialize
var $el = $(this.el).find('#extension-info');
var extension = this.model.get('extension');
- var title = $(this.el).find('#extension').find('option:selected').text();
+ var title = this.select_extension.text();
// create popup
if (!this.extension_popup) {
@@ -323,66 +343,43 @@
}
},
- // attach file info popup
- _destroyExtensionInfo : function()
- {
- this.$el.find('#extension-info').popover('destroy');
- },
-
// template
_template: function(options)
{
- // link this
- var self = this;
-
- // construct template
- var tmpl = '<tr id="upload-item-' + options.id + '" class="upload-item">' +
- '<td>' +
- '<div style="position: relative;">' +
- '<div id="mode"></div>' +
- '<div id="title" class="title"></div>' +
- '<div id="text" class="text">' +
- '<div class="text-info">You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.</div>' +
- '<textarea id="text-content" class="text-content form-control"></textarea>' +
- '</div>' +
+ return '<tr id="upload-item-' + options.id + '" class="upload-item">' +
+ '<td>' +
+ '<div style="position: relative;">' +
+ '<div id="mode"></div>' +
+ '<div id="title" class="title"></div>' +
+ '<div id="text" class="text">' +
+ '<div class="text-info">You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.</div>' +
+ '<textarea id="text-content" class="text-content form-control"></textarea>' +
'</div>' +
- '</td>' +
- '<td>' +
- '<div id="size" class="size"></div>' +
- '</td>';
-
- // add file type selectore
- tmpl += '<td>' +
- '<select id="extension" class="extension">';
- for (key in self.app.select_extension)
- tmpl += '<option value="' + self.app.select_extension[key][1] + '">' + self.app.select_extension[key][0] + '</option>';
- tmpl += '</select>' +
- ' <i id="extension-info" class="upload-icon-button fa fa-search"/>' +
- '</td>';
-
- // add genome selector
- tmpl += '<td>' +
- '<select id="genome" class="genome">';
- for (key in self.app.select_genome)
- tmpl += '<option value="' + self.app.select_genome[key][1] + '">' + self.app.select_genome[key][0] + '</option>';
- tmpl += '</select>' +
- '</td>';
-
- // add next row
- tmpl += '<td><div id="settings" class="upload-icon-button fa fa-gear"></div>' +
- '<td>' +
- '<div id="info" class="info">' +
- '<div class="progress">' +
- '<div class="progress-bar progress-bar-success"></div>' +
- '<div id="percentage" class="percentage">0%</div>' +
- '</div>' +
+ '</div>' +
+ '</td>' +
+ '<td>' +
+ '<div id="size" class="size"></div>' +
+ '</td>' +
+ '<td>' +
+ '<div id="extension" class="extension" style="float: left;"/>  ' +
+ '<div id="extension-info" class="upload-icon-button fa fa-search"/>' +
+ '</td>' +
+ '<td>' +
+ '<div id="genome" class="genome" />' +
+ '</td>' +
+ '<td><div id="settings" class="upload-icon-button fa fa-gear"></div>' +
+ '<td>' +
+ '<div id="info" class="info">' +
+ '<div class="progress">' +
+ '<div class="progress-bar progress-bar-success"></div>' +
+ '<div id="percentage" class="percentage">0%</div>' +
'</div>' +
- '</td>' +
- '<td><div id="symbol" class="' + this.status_classes.init + '"></div></td>' +
- '</tr>';
-
- // return html string
- return tmpl;
+ '</div>' +
+ '</td>' +
+ '<td>' +
+ '<div id="symbol" class="' + this.status_classes.init + '"></div>' +
+ '</td>' +
+ '</tr>';
}
});
diff -r 64e6873c8825164718ca14f17379b114e3539b09 -r 086352011e076cc658ce75b9d8caa665e54b31c8 static/scripts/mvc/upload/upload-view.js
--- a/static/scripts/mvc/upload/upload-view.js
+++ b/static/scripts/mvc/upload/upload-view.js
@@ -42,10 +42,10 @@
upload_size: 0,
// extension types
- select_extension :[['Auto-detect', 'auto']],
+ list_extensions :[{id: 'auto', text: 'Auto-detect'}],
// genomes
- select_genome : [['Unspecified (?)', '?']],
+ list_genomes : [],
// collection
collection : new UploadModel.Collection(),
@@ -107,32 +107,30 @@
var self = this;
Utils.jsonFromUrl(galaxy_config.root + "api/datatypes?upload_only=True",
function(datatypes) {
- for (key in datatypes)
- self.select_extension.push([datatypes[key], datatypes[key]]);
+ for (key in datatypes) {
+ self.list_extensions.push({
+ id : datatypes[key],
+ text : datatypes[key]
+ });
+ }
});
// load genomes
Utils.jsonFromUrl(galaxy_config.root + "api/genomes",
function(genomes) {
- // backup default
- var def = self.select_genome[0];
-
- // fill array
- self.select_genome = [];
- for (key in genomes)
- if (genomes[key].length > 1)
- if (genomes[key][1] !== def[1])
- self.select_genome.push(genomes[key]);
-
+ for (key in genomes) {
+ self.list_genomes.push({
+ id : genomes[key][1],
+ text : genomes[key][0]
+ });
+ }
+
// sort
- self.select_genome.sort(function(a, b) {
- return a[0] > b[0] ? 1 : a[0] < b[0] ? -1 : 0;
+ self.list_genomes.sort(function(a, b) {
+ return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
});
-
- // insert default back to array
- self.select_genome.unshift(def);
});
-
+
// read in options
if (options) {
this.options = _.defaults(options, this.options);
@@ -296,9 +294,6 @@
// configure uploadbox
this.uploadbox.configure({url : this.options.nginx_upload_path});
- // configure tool
- tool_input = {};
-
// local files
if (file_mode == 'local') {
this.uploadbox.configure({paramname : 'files_0|file_data'});
@@ -306,6 +301,9 @@
this.uploadbox.configure({paramname : null});
}
+ // configure tool
+ tool_input = {};
+
// new files
if (file_mode == 'new') {
tool_input['files_0|url_paste'] = url_paste;
@@ -313,7 +311,6 @@
// files from ftp
if (file_mode == 'ftp') {
- // add to tool configuration
tool_input['files_0|ftp_files'] = file_path;
}
diff -r 64e6873c8825164718ca14f17379b114e3539b09 -r 086352011e076cc658ce75b9d8caa665e54b31c8 static/scripts/packed/mvc/ui.select.js
--- /dev/null
+++ b/static/scripts/packed/mvc/ui.select.js
@@ -0,0 +1,1 @@
+define(["utils/utils"],function(a){var b=Backbone.View.extend({optionsDefault:{css:"",placeholder:"No data available",data:[]},initialize:function(d){this.options=a.merge(d,this.optionsDefault);this.setElement(this._template(this.options));this.options.container.append(this.$el);this.select_data=this.options.data;this._refresh();if(this.options.value){this._setValue(this.options.value)}var c=this;if(this.options.onchange){this.$el.on("change",function(){c.options.onchange(c.value())})}},value:function(c){var d=this._getValue();if(c!==undefined){this._setValue(c)}var e=this._getValue();if(e===undefined){return null}else{if((e!=d&&this.options.onchange)){this.options.onchange(e)}return e}},text:function(){return this.$el.select2("data").text},disabled:function(){return !this.$el.select2("enable")},enable:function(){this.$el.select2("enable",true)},disable:function(){this.$el.select2("enable",false)},add:function(c){this.select_data.push({id:c.id,text:c.text});this._refresh()},remove:function(d){var c=this._getIndex(d);if(c!=-1){this.select_data.splice(c,1);this._refresh()}},update:function(c){this.select_data=[];for(var d in c.data){this.select_data.push(c.data[d])}this._refresh()},_refresh:function(){var e=this.select_data;if(!e||e.length==0){e=[]}var d=this._getValue();this.$el.select2({data:e,containerCssClass:this.options.css,placeholder:this.options.placeholder});var c=this._getIndex(d);if(c!=-1){this._setValue(d)}},_getIndex:function(d){for(var c in this.select_data){if(this.select_data[c].id==d){return c}}return -1},_getValue:function(){return this.$el.select2("val")},_setValue:function(c){this.$el.select2("val",c)},_template:function(c){return'<input type="hidden"/>'}});return{View:b}});
\ No newline at end of file
diff -r 64e6873c8825164718ca14f17379b114e3539b09 -r 086352011e076cc658ce75b9d8caa665e54b31c8 static/scripts/packed/mvc/upload/upload-row.js
--- a/static/scripts/packed/mvc/upload/upload-row.js
+++ b/static/scripts/packed/mvc/upload/upload-row.js
@@ -1,1 +1,1 @@
-define(["utils/utils","mvc/upload/upload-model","mvc/upload/upload-extensions","mvc/upload/upload-settings","mvc/ui.popover"],function(d,b,e,a,c){return Backbone.View.extend({options:{padding:8},status_classes:{init:"upload-icon-button fa fa-trash-o",queued:"upload-icon fa fa-spinner fa-spin",running:"upload-icon fa fa-spinner fa-spin",success:"upload-icon-button fa fa-check",error:"upload-icon-button fa fa-exclamation-triangle"},settings:null,initialize:function(i,g){this.app=i;var f=this;this.model=new b.Model(g);this.setElement(this._template(g));var h=this.$el;this.settings=new c.View({title:"Upload configuration",container:h.find("#settings"),placement:"bottom"});h.find("#symbol").on("click",function(){f._removeRow()});h.find("#extension-info").on("click",function(j){f._showExtensionInfo()}).on("mousedown",function(j){j.preventDefault()});h.find("#settings").on("click",function(j){f._showSettings()}).on("mousedown",function(j){j.preventDefault()});h.find("#text-content").on("keyup",function(j){f.model.set("url_paste",$(j.target).val());f.model.set("file_size",$(j.target).val().length)});h.find("#genome").on("change",function(j){f.model.set("genome",$(j.target).val())});h.find("#extension").on("change",function(j){f.model.set("extension",$(j.target).val())});h.find("#space_to_tabs").on("change",function(j){f.model.set("space_to_tabs",$(j.target).prop("checked"))});this.model.on("change:percentage",function(){f._refreshPercentage()});this.model.on("change:status",function(){f._refreshStatus()});this.model.on("change:extension",function(){f._destroyExtensionInfo()});this.model.on("change:info",function(){f._refreshInfo()});this.model.on("change:genome",function(){f._refreshGenome()});this.model.on("change:file_size",function(){f._refreshFileSize()});this.model.on("remove",function(){f._destroyExtensionInfo();f.remove()});this.app.collection.on("reset",function(){f._destroyExtensionInfo();f.remove()})},render:function(){var m=this.model.get("file_name");var g=this.model.get("file_size");var j=this.model.get("file_mode");var i=this.$el;i.find("#title").html(m);i.find("#size").html(d.bytesToString(g));i.find("#mode").removeClass().addClass("mode");if(j=="new"){var l=i.find("#text");var k=this.options.padding;var h=i.width()-2*k;var f=i.height()-k;l.css("width",h+"px");l.css("top",f+"px");i.height(f+l.height()+2*k);l.show();i.find("#mode").addClass("fa fa-pencil")}if(j=="local"){i.find("#mode").addClass("fa fa-laptop")}if(j=="ftp"){i.find("#mode").addClass("fa fa-code-fork")}},_refreshGenome:function(){var f=this.model.get("genome");this.$el.find("#genome").val(f)},_refreshInfo:function(){var f=this.model.get("info");if(f){this.$el.find("#info").html("<strong>Failed: </strong>"+f).show()}else{this.$el.find("#info").hide()}},_refreshPercentage:function(){var f=parseInt(this.model.get("percentage"));this.$el.find(".progress-bar").css({width:f+"%"});if(f!=100){this.$el.find("#percentage").html(f+"%")}else{this.$el.find("#percentage").html("Adding to history...")}},_refreshStatus:function(){var g=this.$el;var f=this.model.get("status");var i=this.status_classes[f];var h=this.$el.find("#symbol");h.removeClass();h.addClass(i);if(f=="init"){g.find("#text-content").attr("disabled",false);g.find("#genome").attr("disabled",false);g.find("#extension").attr("disabled",false);g.find("#space_to_tabs").attr("disabled",false)}else{g.find("#text-content").attr("disabled",true);g.find("#genome").attr("disabled",true);g.find("#extension").attr("disabled",true);g.find("#space_to_tabs").attr("disabled",true)}if(f=="success"){g.addClass("success");g.find("#percentage").html("100%")}if(f=="error"){g.addClass("danger");g.find(".progress").remove()}},_refreshFileSize:function(){var f=this.model.get("file_size");this.$el.find("#size").html(d.bytesToString(f))},_removeRow:function(){var f=this.model.get("status");if(f=="init"||f=="success"||f=="error"){this.app.collection.remove(this.model)}},_showExtensionInfo:function(){var f=$(this.el).find("#extension-info");var h=this.model.get("extension");var g=$(this.el).find("#extension").find("option:selected").text();if(!this.extension_popup){this.extension_popup=new c.View({content:e(h),placement:"bottom",container:f})}if(!this.extension_popup.visible){this.extension_popup.title(g);this.extension_popup.empty();this.extension_popup.append(e(h));this.extension_popup.show()}else{this.extension_popup.hide()}},_showSettings:function(){if(!this.settings.visible){this.settings.empty();this.settings.append((new a(this)).$el);this.settings.show()}else{this.settings.hide()}},_destroyExtensionInfo:function(){this.$el.find("#extension-info").popover("destroy")},_template:function(h){var g=this;var f='<tr id="upload-item-'+h.id+'" class="upload-item"><td><div style="position: relative;"><div id="mode"></div><div id="title" class="title"></div><div id="text" class="text"><div class="text-info">You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.</div><textarea id="text-content" class="text-content form-control"></textarea></div></div></td><td><div id="size" class="size"></div></td>';f+='<td><select id="extension" class="extension">';for(key in g.app.select_extension){f+='<option value="'+g.app.select_extension[key][1]+'">'+g.app.select_extension[key][0]+"</option>"}f+='</select> <i id="extension-info" class="upload-icon-button fa fa-search"/></td>';f+='<td><select id="genome" class="genome">';for(key in g.app.select_genome){f+='<option value="'+g.app.select_genome[key][1]+'">'+g.app.select_genome[key][0]+"</option>"}f+="</select></td>";f+='<td><div id="settings" class="upload-icon-button fa fa-gear"></div><td><div id="info" class="info"><div class="progress"><div class="progress-bar progress-bar-success"></div><div id="percentage" class="percentage">0%</div></div></div></td><td><div id="symbol" class="'+this.status_classes.init+'"></div></td></tr>';return f}})});
\ No newline at end of file
+define(["utils/utils","mvc/upload/upload-model","mvc/upload/upload-extensions","mvc/upload/upload-settings","mvc/ui.popover","mvc/ui.select"],function(d,b,f,a,c,e){return Backbone.View.extend({options:{padding:8},status_classes:{init:"upload-icon-button fa fa-trash-o",queued:"upload-icon fa fa-spinner fa-spin",running:"upload-icon fa fa-spinner fa-spin",success:"upload-icon-button fa fa-check",error:"upload-icon-button fa fa-exclamation-triangle"},settings:null,select_genome:null,select_extension:null,initialize:function(j,h){this.app=j;var g=this;this.model=new b.Model(h);this.setElement(this._template(h));var i=this.$el;this.settings=new c.View({title:"Upload configuration",container:i.find("#settings"),placement:"bottom"});this.select_genome=new e.View({css:"genome",onchange:function(){g.model.set("genome",g.select_genome.value())},data:g.app.list_genomes,container:i.find("#genome"),value:g.model.get("genome")});this.select_extension=new e.View({css:"extension",onchange:function(){g.model.set("extension",g.select_extension.value())},data:g.app.list_extensions,container:i.find("#extension"),value:g.model.get("extension")});i.find("#symbol").on("click",function(){g._removeRow()});i.find("#extension-info").on("click",function(k){g._showExtensionInfo()}).on("mousedown",function(k){k.preventDefault()});i.find("#settings").on("click",function(k){g._showSettings()}).on("mousedown",function(k){k.preventDefault()});i.find("#text-content").on("keyup",function(k){g.model.set("url_paste",$(k.target).val());g.model.set("file_size",$(k.target).val().length)});i.find("#space_to_tabs").on("change",function(k){g.model.set("space_to_tabs",$(k.target).prop("checked"))});this.model.on("change:percentage",function(){g._refreshPercentage()});this.model.on("change:status",function(){g._refreshStatus()});this.model.on("change:info",function(){g._refreshInfo()});this.model.on("change:genome",function(){g._refreshGenome()});this.model.on("change:file_size",function(){g._refreshFileSize()});this.model.on("remove",function(){g.remove()});this.app.collection.on("reset",function(){g.remove()})},render:function(){var n=this.model.get("file_name");var h=this.model.get("file_size");var k=this.model.get("file_mode");var j=this.$el;j.find("#title").html(n);j.find("#size").html(d.bytesToString(h));j.find("#mode").removeClass().addClass("mode");if(k=="new"){var m=j.find("#text");var l=this.options.padding;var i=j.width()-2*l;var g=j.height()-l;m.css("width",i+"px");m.css("top",g+"px");j.height(g+m.height()+2*l);m.show();j.find("#mode").addClass("fa fa-pencil")}if(k=="local"){j.find("#mode").addClass("fa fa-laptop")}if(k=="ftp"){j.find("#mode").addClass("fa fa-code-fork")}},_refreshGenome:function(){var g=this.model.get("genome");this.select_genome.value(g)},_refreshInfo:function(){var g=this.model.get("info");if(g){this.$el.find("#info").html("<strong>Failed: </strong>"+g).show()}else{this.$el.find("#info").hide()}},_refreshPercentage:function(){var g=parseInt(this.model.get("percentage"));this.$el.find(".progress-bar").css({width:g+"%"});if(g!=100){this.$el.find("#percentage").html(g+"%")}else{this.$el.find("#percentage").html("Adding to history...")}},_refreshStatus:function(){var h=this.$el;var g=this.model.get("status");var j=this.status_classes[g];var i=this.$el.find("#symbol");i.removeClass();i.addClass(j);if(g=="init"){this.select_genome.enable();this.select_extension.enable();h.find("#text-content").attr("disabled",false);h.find("#space_to_tabs").attr("disabled",false)}else{this.select_genome.disable();this.select_extension.disable();h.find("#text-content").attr("disabled",true);h.find("#space_to_tabs").attr("disabled",true)}if(g=="success"){h.addClass("success");h.find("#percentage").html("100%")}if(g=="error"){h.addClass("danger");h.find(".progress").remove()}},_refreshFileSize:function(){var g=this.model.get("file_size");this.$el.find("#size").html(d.bytesToString(g))},_removeRow:function(){var g=this.model.get("status");if(g=="init"||g=="success"||g=="error"){this.app.collection.remove(this.model)}},_showExtensionInfo:function(){var g=$(this.el).find("#extension-info");var i=this.model.get("extension");var h=this.select_extension.text();if(!this.extension_popup){this.extension_popup=new c.View({content:f(i),placement:"bottom",container:g})}if(!this.extension_popup.visible){this.extension_popup.title(h);this.extension_popup.empty();this.extension_popup.append(f(i));this.extension_popup.show()}else{this.extension_popup.hide()}},_showSettings:function(){if(!this.settings.visible){this.settings.empty();this.settings.append((new a(this)).$el);this.settings.show()}else{this.settings.hide()}},_template:function(g){return'<tr id="upload-item-'+g.id+'" class="upload-item"><td><div style="position: relative;"><div id="mode"></div><div id="title" class="title"></div><div id="text" class="text"><div class="text-info">You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.</div><textarea id="text-content" class="text-content form-control"></textarea></div></div></td><td><div id="size" class="size"></div></td><td><div id="extension" class="extension" style="float: left;"/>  <div id="extension-info" class="upload-icon-button fa fa-search"/></td><td><div id="genome" class="genome" /></td><td><div id="settings" class="upload-icon-button fa fa-gear"></div><td><div id="info" class="info"><div class="progress"><div class="progress-bar progress-bar-success"></div><div id="percentage" class="percentage">0%</div></div></div></td><td><div id="symbol" class="'+this.status_classes.init+'"></div></td></tr>'}})});
\ No newline at end of file
diff -r 64e6873c8825164718ca14f17379b114e3539b09 -r 086352011e076cc658ce75b9d8caa665e54b31c8 static/scripts/packed/mvc/upload/upload-view.js
--- a/static/scripts/packed/mvc/upload/upload-view.js
+++ b/static/scripts/packed/mvc/upload/upload-view.js
@@ -1,1 +1,1 @@
-define(["galaxy.modal","utils/utils","mvc/upload/upload-button","mvc/upload/upload-model","mvc/upload/upload-row","mvc/upload/upload-ftp","mvc/ui.popover","mvc/ui","utils/uploadbox"],function(a,f,e,c,b,g,d){return Backbone.View.extend({options:{nginx_upload_path:""},modal:null,ui_button:null,uploadbox:null,current_history:null,upload_size:0,select_extension:[["Auto-detect","auto"]],select_genome:[["Unspecified (?)","?"]],collection:new c.Collection(),ftp:null,counter:{announce:0,success:0,error:0,running:0,reset:function(){this.announce=this.success=this.error=this.running=0}},initialize:function(i){var h=this;if(!Galaxy.currHistoryPanel||!Galaxy.currHistoryPanel.model){window.setTimeout(function(){h.initialize()},500);return}if(!Galaxy.currUser.get("id")){return}this.ui_button=new e.Model({icon:"fa-upload",tooltip:"Download from URL or upload files from disk",label:"Load Data",onclick:function(j){if(j){h._eventShow(j)}},onunload:function(){if(h.counter.running>0){return"Several uploads are still processing."}}});$("#left .unified-panel-header-inner").append((new e.View(this.ui_button)).$el);var h=this;f.jsonFromUrl(galaxy_config.root+"api/datatypes?upload_only=True",function(j){for(key in j){h.select_extension.push([j[key],j[key]])}});f.jsonFromUrl(galaxy_config.root+"api/genomes",function(j){var k=h.select_genome[0];h.select_genome=[];for(key in j){if(j[key].length>1){if(j[key][1]!==k[1]){h.select_genome.push(j[key])}}}h.select_genome.sort(function(m,l){return m[0]>l[0]?1:m[0]<l[0]?-1:0});h.select_genome.unshift(k)});if(i){this.options=_.defaults(i,this.options)}this.collection.on("remove",function(j){h._eventRemove(j)});this.collection.on("change:genome",function(k){var j=k.get("genome");h.collection.each(function(l){if(l.get("status")=="init"&&l.get("genome")=="?"){l.set("genome",j)}})})},_eventShow:function(j){j.preventDefault();if(!this.modal){var h=this;this.modal=new a.GalaxyModal({title:"Download data directly from web or upload files from your disk",body:this._template("upload-box","upload-info"),buttons:{"Choose local file":function(){h.uploadbox.select()},"Choose FTP file":function(){h._eventFtp()},"Create new file":function(){h._eventCreate()},Start:function(){h._eventStart()},Pause:function(){h._eventStop()},Reset:function(){h._eventReset()},Close:function(){h.modal.hide()},},height:"400",width:"900",closing_events:true});this.setElement("#upload-box");var h=this;this.uploadbox=this.$el.uploadbox({announce:function(k,l,m){h._eventAnnounce(k,l,m)},initialize:function(k,l,m){return h._eventInitialize(k,l,m)},progress:function(k,l,m){h._eventProgress(k,l,m)},success:function(k,l,m){h._eventSuccess(k,l,m)},error:function(k,l,m){h._eventError(k,l,m)},complete:function(){h._eventComplete()}});this._updateScreen();if(this.options.ftp_upload_dir&&this.options.ftp_upload_site){var i=this.modal.getButton("Choose FTP file");this.ftp=new d.View({title:"FTP files",container:i})}else{this.modal.hideButton("Choose FTP file")}}this.modal.show()},_eventRemove:function(i){var h=i.get("status");if(h=="success"){this.counter.success--}else{if(h=="error"){this.counter.error--}else{this.counter.announce--}}this._updateScreen();this.uploadbox.remove(i.id)},_eventAnnounce:function(h,i,k){this.counter.announce++;this._updateScreen();var j=new b(this,{id:h,file_name:i.name,file_size:i.size,file_mode:i.mode,file_path:i.path});this.collection.add(j.model);$(this.el).find("tbody:first").append(j.$el);j.render()},_eventInitialize:function(m,j,s){var k=this.collection.get(m);k.set("status","running");var o=k.get("file_name");var n=k.get("file_path");var h=k.get("file_mode");var p=k.get("extension");var r=k.get("genome");var q=k.get("url_paste");var l=k.get("space_to_tabs");var i=k.get("to_posix_lines");if(!q&&!(j.size>0)){return null}this.uploadbox.configure({url:this.options.nginx_upload_path});tool_input={};if(h=="local"){this.uploadbox.configure({paramname:"files_0|file_data"})}else{this.uploadbox.configure({paramname:null})}if(h=="new"){tool_input["files_0|url_paste"]=q}if(h=="ftp"){tool_input["files_0|ftp_files"]=n}tool_input.dbkey=r;tool_input.file_type=p;tool_input["files_0|type"]="upload_dataset";tool_input.space_to_tabs=l;tool_input.to_posix_lines=i;data={};data.history_id=this.current_history;data.tool_id="upload1";data.inputs=JSON.stringify(tool_input);return data},_eventProgress:function(i,j,h){var k=this.collection.get(i);k.set("percentage",h);this.ui_button.set("percentage",this._upload_percentage(h,j.size))},_eventSuccess:function(i,j,l){var k=this.collection.get(i);k.set("percentage",100);k.set("status","success");var h=k.get("file_size");this.ui_button.set("percentage",this._upload_percentage(100,h));this.upload_completed+=h*100;this.counter.announce--;this.counter.success++;this._updateScreen();Galaxy.currHistoryPanel.refreshHdas()},_eventError:function(h,i,k){var j=this.collection.get(h);j.set("percentage",100);j.set("status","error");j.set("info",k);this.ui_button.set("percentage",this._upload_percentage(100,i.size));this.ui_button.set("status","danger");this.upload_completed+=i.size*100;this.counter.announce--;this.counter.error++;this._updateScreen()},_eventComplete:function(){this.collection.each(function(h){if(h.get("status")=="queued"){h.set("status","init")}});this.counter.running=0;this._updateScreen()},_eventFtp:function(){if(!this.ftp.visible){this.ftp.empty();this.ftp.append((new g(this)).$el);this.ftp.show()}else{this.ftp.hide()}},_eventCreate:function(){this.uploadbox.add([{name:"New File",size:0,mode:"new"}])},_eventStart:function(){if(this.counter.announce==0||this.counter.running>0){return}var h=this;this.upload_size=0;this.upload_completed=0;this.collection.each(function(i){if(i.get("status")=="init"){i.set("status","queued");h.upload_size+=i.get("file_size")}});this.ui_button.set("percentage",0);this.ui_button.set("status","success");this.current_history=Galaxy.currHistoryPanel.model.get("id");this.counter.running=this.counter.announce;this._updateScreen();this.uploadbox.start()},_eventStop:function(){if(this.counter.running==0){return}this.ui_button.set("status","info");this.uploadbox.stop();$("#upload-info").html("Queue will pause after completing the current file...")},_eventReset:function(){if(this.counter.running==0){this.collection.reset();this.counter.reset();this._updateScreen();this.uploadbox.reset();this.ui_button.set("percentage",0)}},_updateScreen:function(){if(this.counter.announce==0){if(this.uploadbox.compatible()){message="You can Drag & Drop files into this box."}else{message="Unfortunately, your browser does not support multiple file uploads or drag&drop.<br>Some supported browsers are: Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+."}}else{if(this.counter.running==0){message="You added "+this.counter.announce+" file(s) to the queue. Add more files or click 'Start' to proceed."}else{message="Please wait..."+this.counter.announce+" out of "+this.counter.running+" remaining."}}$("#upload-info").html(message);if(this.counter.running==0&&this.counter.announce+this.counter.success+this.counter.error>0){this.modal.enableButton("Reset")}else{this.modal.disableButton("Reset")}if(this.counter.running==0&&this.counter.announce>0){this.modal.enableButton("Start")}else{this.modal.disableButton("Start")}if(this.counter.running>0){this.modal.enableButton("Pause")}else{this.modal.disableButton("Pause")}if(this.counter.running==0){this.modal.enableButton("Choose local file");this.modal.enableButton("Choose FTP file");this.modal.enableButton("Create new file")}else{this.modal.disableButton("Choose local file");this.modal.disableButton("Choose FTP file");this.modal.disableButton("Create new file")}if(this.counter.announce+this.counter.success+this.counter.error>0){$(this.el).find("#upload-table").show()}else{$(this.el).find("#upload-table").hide()}},_upload_percentage:function(h,i){return(this.upload_completed+(h*i))/this.upload_size},_template:function(i,h){return'<div id="'+i+'" class="upload-box"><table id="upload-table" class="table table-striped" style="display: none;"><thead><tr><th>Name</th><th>Size</th><th>Type</th><th>Genome</th><th>Settings</th><th>Status</th><th></th></tr></thead><tbody></tbody></table></div><h6 id="'+h+'" class="upload-info"></h6>'}})});
\ No newline at end of file
+define(["galaxy.modal","utils/utils","mvc/upload/upload-button","mvc/upload/upload-model","mvc/upload/upload-row","mvc/upload/upload-ftp","mvc/ui.popover","mvc/ui","utils/uploadbox"],function(a,f,e,c,b,g,d){return Backbone.View.extend({options:{nginx_upload_path:""},modal:null,ui_button:null,uploadbox:null,current_history:null,upload_size:0,list_extensions:[{id:"auto",text:"Auto-detect"}],list_genomes:[],collection:new c.Collection(),ftp:null,counter:{announce:0,success:0,error:0,running:0,reset:function(){this.announce=this.success=this.error=this.running=0}},initialize:function(i){var h=this;if(!Galaxy.currHistoryPanel||!Galaxy.currHistoryPanel.model){window.setTimeout(function(){h.initialize()},500);return}if(!Galaxy.currUser.get("id")){return}this.ui_button=new e.Model({icon:"fa-upload",tooltip:"Download from URL or upload files from disk",label:"Load Data",onclick:function(j){if(j){h._eventShow(j)}},onunload:function(){if(h.counter.running>0){return"Several uploads are still processing."}}});$("#left .unified-panel-header-inner").append((new e.View(this.ui_button)).$el);var h=this;f.jsonFromUrl(galaxy_config.root+"api/datatypes?upload_only=True",function(j){for(key in j){h.list_extensions.push({id:j[key],text:j[key]})}});f.jsonFromUrl(galaxy_config.root+"api/genomes",function(j){for(key in j){h.list_genomes.push({id:j[key][1],text:j[key][0]})}h.list_genomes.sort(function(l,k){return l.id>k.id?1:l.id<k.id?-1:0})});if(i){this.options=_.defaults(i,this.options)}this.collection.on("remove",function(j){h._eventRemove(j)});this.collection.on("change:genome",function(k){var j=k.get("genome");h.collection.each(function(l){if(l.get("status")=="init"&&l.get("genome")=="?"){l.set("genome",j)}})})},_eventShow:function(j){j.preventDefault();if(!this.modal){var h=this;this.modal=new a.GalaxyModal({title:"Download data directly from web or upload files from your disk",body:this._template("upload-box","upload-info"),buttons:{"Choose local file":function(){h.uploadbox.select()},"Choose FTP file":function(){h._eventFtp()},"Create new file":function(){h._eventCreate()},Start:function(){h._eventStart()},Pause:function(){h._eventStop()},Reset:function(){h._eventReset()},Close:function(){h.modal.hide()},},height:"400",width:"900",closing_events:true});this.setElement("#upload-box");var h=this;this.uploadbox=this.$el.uploadbox({announce:function(k,l,m){h._eventAnnounce(k,l,m)},initialize:function(k,l,m){return h._eventInitialize(k,l,m)},progress:function(k,l,m){h._eventProgress(k,l,m)},success:function(k,l,m){h._eventSuccess(k,l,m)},error:function(k,l,m){h._eventError(k,l,m)},complete:function(){h._eventComplete()}});this._updateScreen();if(this.options.ftp_upload_dir&&this.options.ftp_upload_site){var i=this.modal.getButton("Choose FTP file");this.ftp=new d.View({title:"FTP files",container:i})}else{this.modal.hideButton("Choose FTP file")}}this.modal.show()},_eventRemove:function(i){var h=i.get("status");if(h=="success"){this.counter.success--}else{if(h=="error"){this.counter.error--}else{this.counter.announce--}}this._updateScreen();this.uploadbox.remove(i.id)},_eventAnnounce:function(h,i,k){this.counter.announce++;this._updateScreen();var j=new b(this,{id:h,file_name:i.name,file_size:i.size,file_mode:i.mode,file_path:i.path});this.collection.add(j.model);$(this.el).find("tbody:first").append(j.$el);j.render()},_eventInitialize:function(m,j,s){var k=this.collection.get(m);k.set("status","running");var o=k.get("file_name");var n=k.get("file_path");var h=k.get("file_mode");var p=k.get("extension");var r=k.get("genome");var q=k.get("url_paste");var l=k.get("space_to_tabs");var i=k.get("to_posix_lines");if(!q&&!(j.size>0)){return null}this.uploadbox.configure({url:this.options.nginx_upload_path});if(h=="local"){this.uploadbox.configure({paramname:"files_0|file_data"})}else{this.uploadbox.configure({paramname:null})}tool_input={};if(h=="new"){tool_input["files_0|url_paste"]=q}if(h=="ftp"){tool_input["files_0|ftp_files"]=n}tool_input.dbkey=r;tool_input.file_type=p;tool_input["files_0|type"]="upload_dataset";tool_input.space_to_tabs=l;tool_input.to_posix_lines=i;data={};data.history_id=this.current_history;data.tool_id="upload1";data.inputs=JSON.stringify(tool_input);return data},_eventProgress:function(i,j,h){var k=this.collection.get(i);k.set("percentage",h);this.ui_button.set("percentage",this._upload_percentage(h,j.size))},_eventSuccess:function(i,j,l){var k=this.collection.get(i);k.set("percentage",100);k.set("status","success");var h=k.get("file_size");this.ui_button.set("percentage",this._upload_percentage(100,h));this.upload_completed+=h*100;this.counter.announce--;this.counter.success++;this._updateScreen();Galaxy.currHistoryPanel.refreshHdas()},_eventError:function(h,i,k){var j=this.collection.get(h);j.set("percentage",100);j.set("status","error");j.set("info",k);this.ui_button.set("percentage",this._upload_percentage(100,i.size));this.ui_button.set("status","danger");this.upload_completed+=i.size*100;this.counter.announce--;this.counter.error++;this._updateScreen()},_eventComplete:function(){this.collection.each(function(h){if(h.get("status")=="queued"){h.set("status","init")}});this.counter.running=0;this._updateScreen()},_eventFtp:function(){if(!this.ftp.visible){this.ftp.empty();this.ftp.append((new g(this)).$el);this.ftp.show()}else{this.ftp.hide()}},_eventCreate:function(){this.uploadbox.add([{name:"New File",size:0,mode:"new"}])},_eventStart:function(){if(this.counter.announce==0||this.counter.running>0){return}var h=this;this.upload_size=0;this.upload_completed=0;this.collection.each(function(i){if(i.get("status")=="init"){i.set("status","queued");h.upload_size+=i.get("file_size")}});this.ui_button.set("percentage",0);this.ui_button.set("status","success");this.current_history=Galaxy.currHistoryPanel.model.get("id");this.counter.running=this.counter.announce;this._updateScreen();this.uploadbox.start()},_eventStop:function(){if(this.counter.running==0){return}this.ui_button.set("status","info");this.uploadbox.stop();$("#upload-info").html("Queue will pause after completing the current file...")},_eventReset:function(){if(this.counter.running==0){this.collection.reset();this.counter.reset();this._updateScreen();this.uploadbox.reset();this.ui_button.set("percentage",0)}},_updateScreen:function(){if(this.counter.announce==0){if(this.uploadbox.compatible()){message="You can Drag & Drop files into this box."}else{message="Unfortunately, your browser does not support multiple file uploads or drag&drop.<br>Some supported browsers are: Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+."}}else{if(this.counter.running==0){message="You added "+this.counter.announce+" file(s) to the queue. Add more files or click 'Start' to proceed."}else{message="Please wait..."+this.counter.announce+" out of "+this.counter.running+" remaining."}}$("#upload-info").html(message);if(this.counter.running==0&&this.counter.announce+this.counter.success+this.counter.error>0){this.modal.enableButton("Reset")}else{this.modal.disableButton("Reset")}if(this.counter.running==0&&this.counter.announce>0){this.modal.enableButton("Start")}else{this.modal.disableButton("Start")}if(this.counter.running>0){this.modal.enableButton("Pause")}else{this.modal.disableButton("Pause")}if(this.counter.running==0){this.modal.enableButton("Choose local file");this.modal.enableButton("Choose FTP file");this.modal.enableButton("Create new file")}else{this.modal.disableButton("Choose local file");this.modal.disableButton("Choose FTP file");this.modal.disableButton("Create new file")}if(this.counter.announce+this.counter.success+this.counter.error>0){$(this.el).find("#upload-table").show()}else{$(this.el).find("#upload-table").hide()}},_upload_percentage:function(h,i){return(this.upload_completed+(h*i))/this.upload_size},_template:function(i,h){return'<div id="'+i+'" class="upload-box"><table id="upload-table" class="table table-striped" style="display: none;"><thead><tr><th>Name</th><th>Size</th><th>Type</th><th>Genome</th><th>Settings</th><th>Status</th><th></th></tr></thead><tbody></tbody></table></div><h6 id="'+h+'" class="upload-info"></h6>'}})});
\ No newline at end of file
diff -r 64e6873c8825164718ca14f17379b114e3539b09 -r 086352011e076cc658ce75b9d8caa665e54b31c8 static/style/blue/base.css
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -1259,16 +1259,17 @@
.upload-icon{font-size:1.2em;width:1.2em}
.upload-icon-button{font-size:1.2em;width:1.2em;cursor:pointer}
.upload-info{font-weight:normal;text-align:center}
-.upload-box{width:100%;height:95%;text-align:center;overflow:scroll;font-size:12px;line-height:1.33;-moz-border-radius:5px;border-radius:5px;border:1px dashed #bfbfbf;padding:10px;overflow-x:hidden;-ms-overflow-style:none}.upload-box .popover{max-width:500px;width:auto}
-.upload-box .table{width:100%}
+.upload-box{width:100%;height:95%;text-align:center;overflow:scroll;font-size:12px;line-height:1.33;-moz-border-radius:5px;border-radius:5px;border:1px dashed #bfbfbf;padding:10px;overflow-x:hidden;-ms-overflow-style:none}.upload-box .table{width:100%}
.upload-box .table th{text-align:center;white-space:nowrap}
.upload-box .table td{margin:0px;paddign:0px}
+.upload-box .upload-item .select2-arrow b{background-position:0 -3px}
+.upload-box .upload-item .select2-choice{max-height:20px;line-height:18px;background:transparent}
.upload-box .mode{float:left;font-size:1.2em;width:1.2em;color:#555}
.upload-box .title{width:130px;word-wrap:break-word;font-size:11px;float:left}
.upload-box .text{position:absolute;display:none}.upload-box .text .text-content{font-size:11px;width:100%;height:50px;resize:none;background:inherit;color:#000}
.upload-box .text .text-info{font-size:11px}
-.upload-box .extension{width:100px;font-size:11px}
-.upload-box .genome{width:150px;font-size:11px}
+.upload-box .extension{width:100px;min-width:100px;font-size:11px}
+.upload-box .genome{width:150px;min-width:150px;font-size:11px}
.upload-box .size{width:60px;white-space:nowrap}
.upload-box .info{width:130px;font-size:11px;line-height:1.2em}.upload-box .info .progress{top:1px;position:relative;width:100%;padding:0px;margin:0px;line-height:1.55em}.upload-box .info .progress .progress-bar{border-radius:inherit;-moz-border-radius:inherit}
.upload-box .info .progress .percentage{position:absolute;text-align:center;width:100%;color:#fff}
@@ -1683,15 +1684,15 @@
div.historyItem-paused{background:#d9edf7}div.historyItem-paused .state-icon{line-height:16px;font-family:FontAwesome;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}div.historyItem-paused .state-icon:before{content:"\f04c"}
div.historyItem-new .state-icon{line-height:16px;font-family:FontAwesome;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}div.historyItem-new .state-icon:before{content:"\f071"}
div.historyItemTitleBar.spinner .state-icon{background:url(data_running.gif) 0 1px no-repeat !important}
-div.historyItemButtons{float:right}div.historyItemButtons .icon-button.display{background-image:url(sprite-history-buttons.png);background-position:0px -48px;width:16px;height:16px;height:16px}
-div.historyItemButtons .icon-button.display:hover{background-image:url(sprite-history-buttons.png);background-position:0px -64px;width:16px;height:16px;height:16px}
-div.historyItemButtons .icon-button.display_disabled{background-image:url(sprite-history-buttons.png);background-position:0px -80px;width:16px;height:16px;height:16px}
-div.historyItemButtons .icon-button.delete{background-image:url(sprite-history-buttons.png);background-position:0px 0px;width:16px;height:16px;height:16px}
-div.historyItemButtons .icon-button.delete:hover{background-image:url(sprite-history-buttons.png);background-position:0px -16px;width:16px;height:16px;height:16px}
-div.historyItemButtons .icon-button.delete_disabled{background-image:url(sprite-history-buttons.png);background-position:0px -32px;width:16px;height:16px;height:16px}
-div.historyItemButtons .icon-button.edit{background-image:url(sprite-history-buttons.png);background-position:0px -96px;width:16px;height:16px;height:16px}
-div.historyItemButtons .icon-button.edit:hover{background-image:url(sprite-history-buttons.png);background-position:0px -112px;width:16px;height:16px;height:16px}
-div.historyItemButtons .icon-button.edit_disabled{background-image:url(sprite-history-buttons.png);background-position:0px -128px;width:16px;height:16px;height:16px}
+div.historyItemButtons{float:right}div.historyItemButtons .icon-button.display{background-image:url(sprite-history-buttons.png);background-position:0px -48px;width:16px;height:16px}
+div.historyItemButtons .icon-button.display:hover{background-image:url(sprite-history-buttons.png);background-position:0px -64px;width:16px;height:16px}
+div.historyItemButtons .icon-button.display_disabled{background-image:url(sprite-history-buttons.png);background-position:0px -80px;width:16px;height:16px}
+div.historyItemButtons .icon-button.delete{background-image:url(sprite-history-buttons.png);background-position:0px 0px;width:16px;height:16px}
+div.historyItemButtons .icon-button.delete:hover{background-image:url(sprite-history-buttons.png);background-position:0px -16px;width:16px;height:16px}
+div.historyItemButtons .icon-button.delete_disabled{background-image:url(sprite-history-buttons.png);background-position:0px -32px;width:16px;height:16px}
+div.historyItemButtons .icon-button.edit{background-image:url(sprite-history-buttons.png);background-position:0px -96px;width:16px;height:16px}
+div.historyItemButtons .icon-button.edit:hover{background-image:url(sprite-history-buttons.png);background-position:0px -112px;width:16px;height:16px}
+div.historyItemButtons .icon-button.edit_disabled{background-image:url(sprite-history-buttons.png);background-position:0px -128px;width:16px;height:16px}
div.historyItemBody div{padding-top:2px}
pre.peek{background:white;color:black;width:100%;font-size:10px;overflow:auto}pre.peek th{color:white;background:#5f6990}
pre.peek table,pre.peek th,pre.peek tr,pre.peek td{font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:10px}
diff -r 64e6873c8825164718ca14f17379b114e3539b09 -r 086352011e076cc658ce75b9d8caa665e54b31c8 static/style/src/less/upload.less
--- a/static/style/src/less/upload.less
+++ b/static/style/src/less/upload.less
@@ -71,11 +71,6 @@
overflow-x : hidden;
-ms-overflow-style: none;
- .popover {
- max-width: 500px;
- width: auto;
- }
-
.table {
width : 100%;
}
@@ -89,7 +84,19 @@
margin: 0px;
paddign: 0px;
}
-
+
+ .upload-item {
+ .select2-arrow b {
+ background-position: 0 -3px;
+ }
+
+ .select2-choice {
+ max-height: 20px;
+ line-height: 18px;
+ background: transparent;
+ }
+ }
+
.mode {
float: left;
font-size: 1.2em;
@@ -119,20 +126,21 @@
.text-info {
font-size : @font-size-small;
- // color : @gray-light;
}
}
.extension {
width: 100px;
+ min-width: 100px;
font-size : @font-size-small;
}
.genome {
width: 150px;
+ min-width: 150px;
font-size : @font-size-small;
}
-
+
.size {
width: 60px;
white-space: nowrap;
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/b463c73f3348/
Changeset: b463c73f3348
Branch: stable
User: greg
Date: 2014-02-12 16:17:38
Summary: Fixes for repositories in the tool shed whose repository tip changeset has no files (because they were all deleted) but have valid tools and other utilities in previous changeset revisions.
Affected #: 2 files
diff -r e3a4d4d813fdb8a34cfd6d596e0f4bbdb2d9e211 -r b463c73f334811d4c48298c40de9ac489261c32d lib/galaxy/webapps/tool_shed/controllers/repository.py
--- a/lib/galaxy/webapps/tool_shed/controllers/repository.py
+++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py
@@ -2312,6 +2312,7 @@
revision_label = suc.get_revision_label( trans, repository, previous_changeset_revision, include_date=False )
metadata = repository_metadata.metadata
is_malicious = repository_metadata.malicious
+ changeset_revision = previous_changeset_revision
if repository_metadata:
skip_tool_test = repository_metadata.skip_tool_tests
if skip_tool_test:
@@ -2719,10 +2720,15 @@
status = "error"
repository_type_select_field = rt_util.build_repository_type_select_field( trans, repository=repository )
changeset_revision = repository.tip( trans.app )
+ metadata = metadata_util.get_repository_metadata_by_repository_id_changeset_revision( trans,
+ id,
+ changeset_revision,
+ metadata_only=True )
return trans.fill_template( '/webapps/tool_shed/repository/browse_repository.mako',
repo=repo,
repository=repository,
changeset_revision=changeset_revision,
+ metadata=metadata,
commit_message=commit_message,
repository_type_select_field=repository_type_select_field,
message=message,
diff -r e3a4d4d813fdb8a34cfd6d596e0f4bbdb2d9e211 -r b463c73f334811d4c48298c40de9ac489261c32d templates/webapps/tool_shed/common/repository_actions_menu.mako
--- a/templates/webapps/tool_shed/common/repository_actions_menu.mako
+++ b/templates/webapps/tool_shed/common/repository_actions_menu.mako
@@ -74,7 +74,7 @@
else:
can_rate = False
- if changeset_revision is not None:
+ if metadata is not None and changeset_revision is not None:
if has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user ):
can_review_repository = True
else:
@@ -177,7 +177,7 @@
<a class="action-button" target="galaxy_main" href="${h.url_for( controller='repository', action='manage_repository_admins', id=trans.security.encode_id( repository.id ) )}">Manage repository administrators</a>
%endif
%if can_download:
- %if changeset_revision is not None:
+ %if metadata is not None and changeset_revision is not None:
<a class="action-button" href="${h.url_for( controller='repository', action='export', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Export this revision</a>
%endif
<a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=repository.tip( trans.app ), file_type='gz' )}">Download as a .tar.gz file</a>
https://bitbucket.org/galaxy/galaxy-central/commits/64e6873c8825/
Changeset: 64e6873c8825
User: inithello
Date: 2014-02-12 16:19:19
Summary: Merge stable.
Affected #: 0 files
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: greg: Fixes for repositories in the tool shed whose repository tip changeset has no files (because they were all deleted) but have valid tools and other utilities in previous changeset revisions.
by commits-noreply@bitbucket.org 12 Feb '14
by commits-noreply@bitbucket.org 12 Feb '14
12 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/f50a5a4391ee/
Changeset: f50a5a4391ee
User: greg
Date: 2014-02-12 16:17:38
Summary: Fixes for repositories in the tool shed whose repository tip changeset has no files (because they were all deleted) but have valid tools and other utilities in previous changeset revisions.
Affected #: 2 files
diff -r b8d2ef92592257a76abeba73630e71b44a708fa8 -r f50a5a4391ee589b55516c6d0fc94a9805840900 lib/galaxy/webapps/tool_shed/controllers/repository.py
--- a/lib/galaxy/webapps/tool_shed/controllers/repository.py
+++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py
@@ -2312,6 +2312,7 @@
revision_label = suc.get_revision_label( trans, repository, previous_changeset_revision, include_date=False )
metadata = repository_metadata.metadata
is_malicious = repository_metadata.malicious
+ changeset_revision = previous_changeset_revision
if repository_metadata:
skip_tool_test = repository_metadata.skip_tool_tests
if skip_tool_test:
@@ -2719,10 +2720,15 @@
status = "error"
repository_type_select_field = rt_util.build_repository_type_select_field( trans, repository=repository )
changeset_revision = repository.tip( trans.app )
+ metadata = metadata_util.get_repository_metadata_by_repository_id_changeset_revision( trans,
+ id,
+ changeset_revision,
+ metadata_only=True )
return trans.fill_template( '/webapps/tool_shed/repository/browse_repository.mako',
repo=repo,
repository=repository,
changeset_revision=changeset_revision,
+ metadata=metadata,
commit_message=commit_message,
repository_type_select_field=repository_type_select_field,
message=message,
diff -r b8d2ef92592257a76abeba73630e71b44a708fa8 -r f50a5a4391ee589b55516c6d0fc94a9805840900 templates/webapps/tool_shed/common/repository_actions_menu.mako
--- a/templates/webapps/tool_shed/common/repository_actions_menu.mako
+++ b/templates/webapps/tool_shed/common/repository_actions_menu.mako
@@ -74,7 +74,7 @@
else:
can_rate = False
- if changeset_revision is not None:
+ if metadata is not None and changeset_revision is not None:
if has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user ):
can_review_repository = True
else:
@@ -177,7 +177,7 @@
<a class="action-button" target="galaxy_main" href="${h.url_for( controller='repository', action='manage_repository_admins', id=trans.security.encode_id( repository.id ) )}">Manage repository administrators</a>
%endif
%if can_download:
- %if changeset_revision is not None:
+ %if metadata is not None and changeset_revision is not None:
<a class="action-button" href="${h.url_for( controller='repository', action='export', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=changeset_revision )}">Export this revision</a>
%endif
<a class="action-button" href="${h.url_for( controller='repository', action='download', repository_id=trans.app.security.encode_id( repository.id ), changeset_revision=repository.tip( trans.app ), file_type='gz' )}">Download as a .tar.gz file</a>
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/e3a4d4d813fd/
Changeset: e3a4d4d813fd
Branch: stable
User: greg
Date: 2014-02-11 22:41:03
Summary: Fixes for the tool shed's install and test framework.
Affected #: 5 files
diff -r bb744d8d3d6a74872a7b42b511d087c16b1ee8b3 -r e3a4d4d813fdb8a34cfd6d596e0f4bbdb2d9e211 install_and_test_tool_shed_repositories.sh
--- a/install_and_test_tool_shed_repositories.sh
+++ b/install_and_test_tool_shed_repositories.sh
@@ -56,6 +56,10 @@
mkdir -p $GALAXY_INSTALL_TEST_SHED_TOOL_PATH
fi
+if [ ! -d $GALAXY_INSTALL_TEST_TOOL_DEPENDENCY_DIR ] ; then
+ mkdir -p $GALAXY_INSTALL_TEST_TOOL_DEPENDENCY_DIR
+fi
+
test_tool_dependency_definitions () {
# Test installation of repositories of type tool_dependency_definition.
if [ -f $GALAXY_INSTALL_TEST_TOOL_DEPENDENCY_DIR/stage_1_complete ] ; then
@@ -102,6 +106,7 @@
;;
# Use "-w repositories_with_tools" parameter when you want to test repositories that contain tools.
repositories_with_tools)
+ touch $GALAXY_INSTALL_TEST_TOOL_DEPENDENCY_DIR/stage_1_complete
test_repositories_with_tools
;;
# No received parameters or any received parameter not in [ tool_dependency_definitions, repositories_with_tools ]
diff -r bb744d8d3d6a74872a7b42b511d087c16b1ee8b3 -r e3a4d4d813fdb8a34cfd6d596e0f4bbdb2d9e211 test/install_and_test_tool_shed_repositories/base/twilltestcase.py
--- a/test/install_and_test_tool_shed_repositories/base/twilltestcase.py
+++ b/test/install_and_test_tool_shed_repositories/base/twilltestcase.py
@@ -156,3 +156,22 @@
( timeout_counter, repository.status ) )
break
time.sleep( 1 )
+ # Set all metadata on each installed repository.
+ for repository_id in repository_ids:
+ galaxy_repository = test_db_util.get_repository( self.security.decode_id( repository_id ) )
+ if not galaxy_repository.metadata:
+ log.debug( 'Setting metadata on repository %s' % str( galaxy_repository.name ) )
+ timeout_counter = 0
+ url = '/admin_toolshed/reset_repository_metadata?id=%s' % repository_id
+ self.visit_url( url )
+ while not galaxy_repository.metadata:
+ test_db_util.refresh( galaxy_repository )
+ timeout_counter = timeout_counter + 1
+ if timeout_counter % 10 == 0:
+ log.debug( 'Waited %d seconds for repository %s.' % ( timeout_counter, str( galaxy_repository.name ) ) )
+ # This timeout currently defaults to 10 minutes.
+ if timeout_counter > repository_installation_timeout:
+ raise AssertionError( 'Repository installation timed out after %d seconds, repository state is %s.' % \
+ ( timeout_counter, repository.status ) )
+ break
+ time.sleep( 1 )
diff -r bb744d8d3d6a74872a7b42b511d087c16b1ee8b3 -r e3a4d4d813fdb8a34cfd6d596e0f4bbdb2d9e211 test/install_and_test_tool_shed_repositories/base/util.py
--- a/test/install_and_test_tool_shed_repositories/base/util.py
+++ b/test/install_and_test_tool_shed_repositories/base/util.py
@@ -18,6 +18,7 @@
import install_and_test_tool_shed_repositories.functional.test_install_repositories as test_install_repositories
import nose
import platform
+import string
import time
import tool_shed.repository_types.util as rt_util
import tool_shed.util.shed_util_common as suc
@@ -35,7 +36,7 @@
from galaxy.util import asbool
from galaxy.util import listify
from galaxy.util import unicodify
-from galaxy.util.json import from_json_string
+from galaxy.util.json import to_json_string
import galaxy.webapps.tool_shed.model.mapping
from nose.plugins import Plugin
@@ -614,7 +615,7 @@
repository = get_repository( name, owner, changeset_revision )
if repository is None:
error_message = 'Error getting revision %s of repository %s owned by %s: %s' % ( changeset_revision, name, owner, str( e ) )
- log.exception( error_message )
+ log.exception( error_message )
return repository, error_message
def is_excluded( exclude_list_dicts, name, owner, changeset_revision, encoded_repository_metadata_id ):
@@ -981,6 +982,22 @@
print 'tool_shed: %s name: %s owner: %s changeset_revision: %s' % \
( cleaned_tool_shed_url, name, owner, changeset_revision )
+def populate_shed_conf_file( shed_conf_file, tool_path, xml_elems=None ):
+ """Populate the file defined by shed_conf_file with xml_elems or initialize it with a template string."""
+ if xml_elems is None:
+ tool_conf_template_parser = string.Template( shed_tool_conf_xml_template )
+ xml_elems = tool_conf_template_parser.safe_substitute( shed_tool_path=tool_path )
+ file( shed_conf_file, 'w' ).write( xml_elems )
+
+def populate_galaxy_shed_tools_dict_file( galaxy_shed_tools_dict_file, shed_tools_dict=None ):
+ """
+ Populate the file defined by galaxy_shed_tools_dict_file with the contents of the shed_tools_dict
+ dictionary.
+ """
+ if shed_tools_dict is None:
+ shed_tools_dict = {}
+ file( galaxy_shed_tools_dict_file, 'w' ).write( to_json_string( shed_tools_dict ) )
+
def print_install_and_test_results( install_stage_type, install_and_test_statistics_dict, error_message ):
"Print statistics for the current test run."
if error_message:
diff -r bb744d8d3d6a74872a7b42b511d087c16b1ee8b3 -r e3a4d4d813fdb8a34cfd6d596e0f4bbdb2d9e211 test/install_and_test_tool_shed_repositories/repositories_with_tools/functional_tests.py
--- a/test/install_and_test_tool_shed_repositories/repositories_with_tools/functional_tests.py
+++ b/test/install_and_test_tool_shed_repositories/repositories_with_tools/functional_tests.py
@@ -33,7 +33,6 @@
import re
import shutil
import socket
-import string
import tempfile
import time
import threading
@@ -44,7 +43,6 @@
from galaxy.app import UniverseApplication
from galaxy.util.json import from_json_string
-from galaxy.util.json import to_json_string
from galaxy.util import unicodify
from galaxy.web import buildapp
from functional_tests import generate_config_file
@@ -134,7 +132,7 @@
tool_id = parts[ -2 ]
return tool_id, tool_version
-def install_and_test_repositories( app, galaxy_shed_tools_dict, galaxy_shed_tool_conf_file ):
+def install_and_test_repositories( app, galaxy_shed_tools_dict_file, galaxy_shed_tool_conf_file, galaxy_shed_tool_path ):
# We must make sure that functional.test_toolbox is always imported after database_contexts.galaxy_content
# is set (which occurs in the main method before this method is called). If functional.test_toolbox is
# imported before database_contexts.galaxy_content is set, sa_session will be None in all methods that use it.
@@ -166,6 +164,8 @@
# The traceback and captured output of the tool that was run will be recored for test failures. After all tests have
# completed, the repository is uninstalled, so test cases don't interfere with the next repository's functional tests.
for repository_dict in repositories_to_install:
+ # Re-initialize the received galaxy_shed_tool_conf_file to be an empty shed_tool_conf.xml.
+ install_and_test_base_util.populate_shed_conf_file( galaxy_shed_tool_conf_file, galaxy_shed_tool_path, xml_elems=None )
encoded_repository_metadata_id = repository_dict.get( 'id', None )
# Add the URL for the tool shed we're installing from, so the automated installation methods go to the right place.
repository_dict[ 'tool_shed_url' ] = install_and_test_base_util.galaxy_tool_shed_url
@@ -210,6 +210,7 @@
repository, error_message = install_and_test_base_util.install_repository( app, repository_dict )
install_and_test_statistics_dict[ 'total_repositories_processed' ] += 1
if error_message:
+ remove_tests( app, repository )
# The repository installation failed.
print 'Installation failed for revision %s of repository %s owned by %s.' % ( changeset_revision, name, owner )
processed_repositories_with_installation_error = \
@@ -247,6 +248,12 @@
install_and_test_statistics_dict,
tool_test_results_dict )
if params.get( 'test_install_error', False ):
+ # We cannot run functional tests for contained tools due to dependency installation errors.
+ remove_tests( app, repository )
+ can_run_functional_tests = False
+ print 'Cannot execute tests for tools in revision %s of repository %s owned by %s ' % \
+ ( changeset_revision, name, owner )
+ print 'because one or more dependencies has installation errors.'
# The repository was successfully installed, but one or more dependencies had installation errors,
# so we'll populate the test result containers since we cannot execute any tests.
install_and_test_base_util.save_test_results_for_changeset_revision( install_and_test_base_util.galaxy_tool_shed_url,
@@ -255,12 +262,6 @@
repository_dict,
params,
can_update_tool_shed )
- # We cannot run functional tests for contained tools due to dependency installation errors.
- print 'Cannot execute tests for tools in revision %s of repository %s owned by %s ' % \
- ( changeset_revision, name, owner )
- print 'because one or more dependencies has installation errors.'
- can_run_functional_tests = False
- remove_tests( app )
# Populate the installation containers (success or error) for the repository's immediate repository
# dependencies whose containers are not yet populated.
install_and_test_base_util.populate_install_containers_for_repository_dependencies( app,
@@ -280,32 +281,30 @@
install_and_test_base_util.get_missing_tool_dependencies( repository,
all_missing_tool_dependencies=None )
print 'Missing tool dependencies:\n%s' % str( missing_tool_dependencies )
- if missing_repository_dependencies or missing_tool_dependencies:
+ if missing_repository_dependencies or missing_tool_dependencies:
+ # The repository was installed successfully, but one or more dependencies had installation errors. Since
+ # we cannot test the tools due to these errors, we'll remove tests and tools were created during the repository
+ # installation process so nose will not discover them and attempt to execute them.
+ remove_tests( app, repository )
print 'Cannot execute tests for tools in revision %s of repository %s owned by %s ' % \
( changeset_revision, name, owner )
print 'because one or more dependencies has installation errors.'
- # The repository was installed successfully, but one or more dependencies had installation errors. Since
- # we cannot test the tools due to these errors, we'll remove tests and tools were created during the repository
- # installation process so nose will not discover them and attempt to execute them.
- remove_tests( app )
else:
print 'Revision %s of repository %s owned by %s installed successfully, so running tool tests.' % \
( changeset_revision, name, owner )
# Generate the shed_tools_dict that specifies the location of test data contained within this repository.
# and configure and run functional tests for this repository. This is equivalent to
# sh run_functional_tests.sh -installed
- file( galaxy_shed_tools_dict, 'w' ).write( to_json_string( {} ) )
+ install_and_test_base_util.populate_galaxy_shed_tools_dict_file( galaxy_shed_tools_dict_file,
+ shed_tools_dict=None )
# Find the path to the test-data directory within the installed repository.
has_test_data, shed_tools_dict = \
parse_tool_panel_config( galaxy_shed_tool_conf_file,
- from_json_string( file( galaxy_shed_tools_dict, 'r' ).read() ) )
+ from_json_string( file( galaxy_shed_tools_dict_file, 'r' ).read() ) )
# If the repository has a test-data directory we write the generated shed_tools_dict to a temporary
# file so the functional test framework can find it.
- # TODO: Eliminate the need for this shed_tools_dict since it grows large over the course of each test run.
- # If it cannot be eliminated altogether, reinitialize it with each new repository install so at this point
- # it contains only entries for the current repository dependency hierarchy being tested.
- file( galaxy_shed_tools_dict, 'w' ).write( to_json_string( shed_tools_dict ) )
- print 'Saved generated shed_tools_dict to %s\nContents: %s' % ( galaxy_shed_tools_dict, shed_tools_dict )
+ install_and_test_base_util.populate_galaxy_shed_tools_dict_file( galaxy_shed_tools_dict_file,
+ shed_tools_dict=shed_tools_dict )
try:
install_and_test_statistics_dict = test_repository_tools( app,
repository,
@@ -314,6 +313,7 @@
tool_test_results_dict,
install_and_test_statistics_dict )
except Exception, e:
+ remove_tests( app, repository )
exception_message = 'Error executing tests for repository %s: %s' % ( name, str( e ) )
log.exception( exception_message )
tool_test_results_dict[ 'failed_tests' ].append( exception_message )
@@ -330,6 +330,7 @@
params,
can_update_tool_shed )
else:
+ remove_tests( app, repository )
print 'Skipped attempt to install revision %s of repository %s owned by %s because ' % \
( changeset_revision, name, owner )
print 'it was previously installed and currently has status %s' % str( repository.status )
@@ -379,12 +380,12 @@
os.path.join( galaxy_test_tmp_dir, 'test_migrated_tool_conf.xml' ) )
galaxy_tool_sheds_conf_file = os.environ.get( 'GALAXY_INSTALL_TEST_TOOL_SHEDS_CONF',
os.path.join( galaxy_test_tmp_dir, 'test_tool_sheds_conf.xml' ) )
- galaxy_shed_tools_dict = os.environ.get( 'GALAXY_INSTALL_TEST_SHED_TOOL_DICT_FILE',
- os.path.join( galaxy_test_tmp_dir, 'shed_tool_dict' ) )
- file( galaxy_shed_tools_dict, 'w' ).write( to_json_string( {} ) )
+ galaxy_shed_tools_dict_file = os.environ.get( 'GALAXY_INSTALL_TEST_SHED_TOOL_DICT_FILE',
+ os.path.join( galaxy_test_tmp_dir, 'shed_tool_dict' ) )
+ install_and_test_base_util.populate_galaxy_shed_tools_dict_file( galaxy_shed_tools_dict_file, shed_tools_dict=None )
# Set the GALAXY_TOOL_SHED_TEST_FILE environment variable to the path of the shed_tools_dict file so that
# test.base.twilltestcase.setUp will find and parse it properly.
- os.environ[ 'GALAXY_TOOL_SHED_TEST_FILE' ] = galaxy_shed_tools_dict
+ os.environ[ 'GALAXY_TOOL_SHED_TEST_FILE' ] = galaxy_shed_tools_dict_file
if 'GALAXY_INSTALL_TEST_TOOL_DATA_PATH' in os.environ:
tool_data_path = os.environ.get( 'GALAXY_INSTALL_TEST_TOOL_DATA_PATH' )
else:
@@ -430,13 +431,10 @@
if 'GALAXY_INSTALL_TEST_TOOL_SHEDS_CONF' not in os.environ:
file( galaxy_tool_sheds_conf_file, 'w' ).write( install_and_test_base_util.tool_sheds_conf_xml )
# Generate the shed_tool_conf.xml file.
- tool_conf_template_parser = string.Template( install_and_test_base_util.shed_tool_conf_xml_template )
- shed_tool_conf_xml = tool_conf_template_parser.safe_substitute( shed_tool_path=galaxy_shed_tool_path )
- file( galaxy_shed_tool_conf_file, 'w' ).write( shed_tool_conf_xml )
+ install_and_test_base_util.populate_shed_conf_file( galaxy_shed_tool_conf_file, galaxy_shed_tool_path, xml_elems=None )
os.environ[ 'GALAXY_INSTALL_TEST_SHED_TOOL_CONF' ] = galaxy_shed_tool_conf_file
# Generate the migrated_tool_conf.xml file.
- migrated_tool_conf_xml = tool_conf_template_parser.safe_substitute( shed_tool_path=galaxy_migrated_tool_path )
- file( galaxy_migrated_tool_conf_file, 'w' ).write( migrated_tool_conf_xml )
+ install_and_test_base_util.populate_shed_conf_file( galaxy_migrated_tool_conf_file, galaxy_migrated_tool_path, xml_elems=None )
# Write the embedded web application's specific configuration to a temporary file. This is necessary in order for
# the external metadata script to find the right datasets.
kwargs = dict( admin_users = 'test(a)bx.psu.edu',
@@ -548,8 +546,9 @@
print "# This run will not update the Tool Shed database."
print "####################################################################################"
install_and_test_statistics_dict, error_message = install_and_test_repositories( app,
- galaxy_shed_tools_dict,
- galaxy_shed_tool_conf_file )
+ galaxy_shed_tools_dict_file,
+ galaxy_shed_tool_conf_file,
+ galaxy_shed_tool_path )
try:
install_and_test_base_util.print_install_and_test_results( 'repositories with tools',
install_and_test_statistics_dict,
@@ -583,12 +582,17 @@
# Return a "successful" response to buildbot.
return 0
-def remove_tests( app ):
+def remove_tests( app, repository ):
"""
- Delete any configured tool functional tests from the test_toolbox.__dict__, otherwise nose will find them
- and try to re-run the tests after uninstalling the repository, which will cause false failure reports,
- since the test data has been deleted from disk by now.
+ Delete any configured tool functional tests from the test_toolbox.__dict__.
"""
+ print '\n-------------------------------------------------------------\n'
+ if repository:
+ print 'Removing tests and tool ids for revision %s of repository %s owned by %s' % \
+ ( str( repository.changeset_revision ), str( repository.name ), str( repository.owner ) )
+ else:
+ print 'Removing tests and tool ids when received repository is None...'
+ print 'app.toolbox.tools_by_id.keys():\n%s\n' % str( app.toolbox.tools_by_id.keys() )
tests_to_delete = []
tools_to_delete_by_id = []
for key in test_toolbox.__dict__:
@@ -608,15 +612,31 @@
reset_spaces_tool_id = tool_id.replace( '_', ' ' )
if reset_spaces_app_tool_id == reset_spaces_tool_id:
print 'Setting tool id %s for deletion from app.toolbox[ tools_by_id ].' % str( app_tool_id )
- tools_to_delete_by_id.append( app_tool_id )
+ tools_to_delete_by_id.append( app_tool_id )
+ if repository:
+ metadata = repository.metadata
+ print 'metadata:\n%s\n' % str( metadata )
+ if metadata:
+ tools = metadata.get( 'tools', [] )
+ print 'tools:\n%s\n' % str( tools )
+ for tool_dict in tools:
+ print 'tool_dict:\n%s\n' % str( tool_dict )
+ guid = tool_dict.get( 'guid', None )
+ print 'guid:\n%s\n' % str( guid )
+ if guid:
+ if guid in app.toolbox.tools_by_id:
+ print 'Setting tool id %s for deletion from app.toolbox[ tools_by_id ].' % str( guid )
+ tools_to_delete_by_id.append( guid )
# Delete the discovered twill-generated tests.
for key in tests_to_delete:
if key in test_toolbox.__dict__:
print 'Deleting test %s from test_toolbox.' % str( key )
del test_toolbox.__dict__[ key ]
for tool_id in tools_to_delete_by_id:
- print 'Deleting tool id %s from app.toolbox[ tools_by_id ].' % str( tool_id )
- del app.toolbox.tools_by_id[ tool_id ]
+ if tool_id in app.toolbox.tools_by_id:
+ print 'Deleting tool id %s from app.toolbox[ tools_by_id ].' % str( tool_id )
+ del app.toolbox.tools_by_id[ tool_id ]
+ print '\n-------------------------------------------------------------\n'
def test_repository_tools( app, repository, repository_dict, tool_test_results_dicts, tool_test_results_dict,
install_and_test_statistics_dict ):
@@ -700,7 +720,7 @@
can_update_tool_shed )
# Remove the just-executed tests so twill will not find and re-test them along with the tools
# contained in the next repository.
- remove_tests( app )
+ remove_tests( app, repository )
return install_and_test_statistics_dict
if __name__ == "__main__":
diff -r bb744d8d3d6a74872a7b42b511d087c16b1ee8b3 -r e3a4d4d813fdb8a34cfd6d596e0f4bbdb2d9e211 test/install_and_test_tool_shed_repositories/tool_dependency_definitions/functional_tests.py
--- a/test/install_and_test_tool_shed_repositories/tool_dependency_definitions/functional_tests.py
+++ b/test/install_and_test_tool_shed_repositories/tool_dependency_definitions/functional_tests.py
@@ -33,7 +33,6 @@
import re
import shutil
import socket
-import string
import tempfile
import time
import threading
@@ -43,8 +42,6 @@
from base.tool_shed_util import parse_tool_panel_config
from galaxy.app import UniverseApplication
-from galaxy.util.json import from_json_string
-from galaxy.util.json import to_json_string
from galaxy.util import unicodify
from galaxy.web import buildapp
from functional_tests import generate_config_file
@@ -75,7 +72,7 @@
test_framework = install_and_test_base_util.TOOL_DEPENDENCY_DEFINITIONS
-def install_and_test_repositories( app, galaxy_shed_tools_dict, galaxy_shed_tool_conf_file ):
+def install_and_test_repositories( app, galaxy_shed_tools_dict_file, galaxy_shed_tool_conf_file, galaxy_shed_tool_path ):
# Initialize a dictionary for the summary that will be printed to stdout.
install_and_test_statistics_dict = install_and_test_base_util.initialize_install_and_test_statistics_dict()
error_message = ''
@@ -234,12 +231,13 @@
os.path.join( galaxy_test_tmp_dir, 'test_migrated_tool_conf.xml' ) )
galaxy_tool_sheds_conf_file = os.environ.get( 'GALAXY_INSTALL_TEST_TOOL_SHEDS_CONF',
os.path.join( galaxy_test_tmp_dir, 'test_tool_sheds_conf.xml' ) )
- galaxy_shed_tools_dict = os.environ.get( 'GALAXY_INSTALL_TEST_SHED_TOOL_DICT_FILE',
- os.path.join( galaxy_test_tmp_dir, 'shed_tool_dict' ) )
- file( galaxy_shed_tools_dict, 'w' ).write( to_json_string( {} ) )
+ galaxy_shed_tools_dict_file = os.environ.get( 'GALAXY_INSTALL_TEST_SHED_TOOL_DICT_FILE',
+ os.path.join( galaxy_test_tmp_dir, 'shed_tool_dict' ) )
+ install_and_test_base_util.populate_galaxy_shed_tools_dict_file( galaxy_shed_tools_dict_file,
+ shed_tools_dict=None )
# Set the GALAXY_TOOL_SHED_TEST_FILE environment variable to the path of the shed_tools_dict file so that
# test.base.twilltestcase.setUp will find and parse it properly.
- os.environ[ 'GALAXY_TOOL_SHED_TEST_FILE' ] = galaxy_shed_tools_dict
+ os.environ[ 'GALAXY_TOOL_SHED_TEST_FILE' ] = galaxy_shed_tools_dict_file
if 'GALAXY_INSTALL_TEST_TOOL_DATA_PATH' in os.environ:
tool_data_path = os.environ.get( 'GALAXY_INSTALL_TEST_TOOL_DATA_PATH' )
else:
@@ -285,13 +283,10 @@
if 'GALAXY_INSTALL_TEST_TOOL_SHEDS_CONF' not in os.environ:
file( galaxy_tool_sheds_conf_file, 'w' ).write( install_and_test_base_util.tool_sheds_conf_xml )
# Generate the shed_tool_conf.xml file.
- tool_conf_template_parser = string.Template( install_and_test_base_util.shed_tool_conf_xml_template )
- shed_tool_conf_xml = tool_conf_template_parser.safe_substitute( shed_tool_path=galaxy_shed_tool_path )
- file( galaxy_shed_tool_conf_file, 'w' ).write( shed_tool_conf_xml )
+ install_and_test_base_util.populate_shed_conf_file( galaxy_shed_tool_conf_file, galaxy_shed_tool_path, xml_elems=None )
os.environ[ 'GALAXY_INSTALL_TEST_SHED_TOOL_CONF' ] = galaxy_shed_tool_conf_file
# Generate the migrated_tool_conf.xml file.
- migrated_tool_conf_xml = tool_conf_template_parser.safe_substitute( shed_tool_path=galaxy_migrated_tool_path )
- file( galaxy_migrated_tool_conf_file, 'w' ).write( migrated_tool_conf_xml )
+ install_and_test_base_util.populate_shed_conf_file( galaxy_migrated_tool_conf_file, galaxy_migrated_tool_path, xml_elems=None )
# Write the embedded web application's specific configuration to a temporary file. This is necessary in order for
# the external metadata script to find the right datasets.
kwargs = dict( admin_users = 'test(a)bx.psu.edu',
@@ -402,8 +397,9 @@
print "# This run will not update the Tool Shed database."
print "####################################################################################"
install_and_test_statistics_dict, error_message = install_and_test_repositories( app,
- galaxy_shed_tools_dict,
- galaxy_shed_tool_conf_file )
+ galaxy_shed_tools_dict_file,
+ galaxy_shed_tool_conf_file,
+ galaxy_shed_tool_path )
try:
install_and_test_base_util.print_install_and_test_results( 'tool dependency definitions',
install_and_test_statistics_dict,
https://bitbucket.org/galaxy/galaxy-central/commits/b8d2ef925922/
Changeset: b8d2ef925922
User: inithello
Date: 2014-02-11 22:42:24
Summary: Merge with stable.
Affected #: 0 files
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: greg: Fixes for the tool shed's install and test framework.
by commits-noreply@bitbucket.org 11 Feb '14
by commits-noreply@bitbucket.org 11 Feb '14
11 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/e9878f33a333/
Changeset: e9878f33a333
User: greg
Date: 2014-02-11 22:41:03
Summary: Fixes for the tool shed's install and test framework.
Affected #: 5 files
diff -r 694411e94d9aa26c9b9a2c13567b2b5e07f74580 -r e9878f33a33333f211a16d2d399f528c859a6f77 install_and_test_tool_shed_repositories.sh
--- a/install_and_test_tool_shed_repositories.sh
+++ b/install_and_test_tool_shed_repositories.sh
@@ -56,6 +56,10 @@
mkdir -p $GALAXY_INSTALL_TEST_SHED_TOOL_PATH
fi
+if [ ! -d $GALAXY_INSTALL_TEST_TOOL_DEPENDENCY_DIR ] ; then
+ mkdir -p $GALAXY_INSTALL_TEST_TOOL_DEPENDENCY_DIR
+fi
+
test_tool_dependency_definitions () {
# Test installation of repositories of type tool_dependency_definition.
if [ -f $GALAXY_INSTALL_TEST_TOOL_DEPENDENCY_DIR/stage_1_complete ] ; then
@@ -102,6 +106,7 @@
;;
# Use "-w repositories_with_tools" parameter when you want to test repositories that contain tools.
repositories_with_tools)
+ touch $GALAXY_INSTALL_TEST_TOOL_DEPENDENCY_DIR/stage_1_complete
test_repositories_with_tools
;;
# No received parameters or any received parameter not in [ tool_dependency_definitions, repositories_with_tools ]
diff -r 694411e94d9aa26c9b9a2c13567b2b5e07f74580 -r e9878f33a33333f211a16d2d399f528c859a6f77 test/install_and_test_tool_shed_repositories/base/twilltestcase.py
--- a/test/install_and_test_tool_shed_repositories/base/twilltestcase.py
+++ b/test/install_and_test_tool_shed_repositories/base/twilltestcase.py
@@ -156,3 +156,22 @@
( timeout_counter, repository.status ) )
break
time.sleep( 1 )
+ # Set all metadata on each installed repository.
+ for repository_id in repository_ids:
+ galaxy_repository = test_db_util.get_repository( self.security.decode_id( repository_id ) )
+ if not galaxy_repository.metadata:
+ log.debug( 'Setting metadata on repository %s' % str( galaxy_repository.name ) )
+ timeout_counter = 0
+ url = '/admin_toolshed/reset_repository_metadata?id=%s' % repository_id
+ self.visit_url( url )
+ while not galaxy_repository.metadata:
+ test_db_util.refresh( galaxy_repository )
+ timeout_counter = timeout_counter + 1
+ if timeout_counter % 10 == 0:
+ log.debug( 'Waited %d seconds for repository %s.' % ( timeout_counter, str( galaxy_repository.name ) ) )
+ # This timeout currently defaults to 10 minutes.
+ if timeout_counter > repository_installation_timeout:
+ raise AssertionError( 'Repository installation timed out after %d seconds, repository state is %s.' % \
+ ( timeout_counter, repository.status ) )
+ break
+ time.sleep( 1 )
diff -r 694411e94d9aa26c9b9a2c13567b2b5e07f74580 -r e9878f33a33333f211a16d2d399f528c859a6f77 test/install_and_test_tool_shed_repositories/base/util.py
--- a/test/install_and_test_tool_shed_repositories/base/util.py
+++ b/test/install_and_test_tool_shed_repositories/base/util.py
@@ -18,6 +18,7 @@
import install_and_test_tool_shed_repositories.functional.test_install_repositories as test_install_repositories
import nose
import platform
+import string
import time
import tool_shed.repository_types.util as rt_util
import tool_shed.util.shed_util_common as suc
@@ -35,7 +36,7 @@
from galaxy.util import asbool
from galaxy.util import listify
from galaxy.util import unicodify
-from galaxy.util.json import from_json_string
+from galaxy.util.json import to_json_string
import galaxy.webapps.tool_shed.model.mapping
from nose.plugins import Plugin
@@ -614,7 +615,7 @@
repository = get_repository( name, owner, changeset_revision )
if repository is None:
error_message = 'Error getting revision %s of repository %s owned by %s: %s' % ( changeset_revision, name, owner, str( e ) )
- log.exception( error_message )
+ log.exception( error_message )
return repository, error_message
def is_excluded( exclude_list_dicts, name, owner, changeset_revision, encoded_repository_metadata_id ):
@@ -981,6 +982,22 @@
print 'tool_shed: %s name: %s owner: %s changeset_revision: %s' % \
( cleaned_tool_shed_url, name, owner, changeset_revision )
+def populate_shed_conf_file( shed_conf_file, tool_path, xml_elems=None ):
+ """Populate the file defined by shed_conf_file with xml_elems or initialize it with a template string."""
+ if xml_elems is None:
+ tool_conf_template_parser = string.Template( shed_tool_conf_xml_template )
+ xml_elems = tool_conf_template_parser.safe_substitute( shed_tool_path=tool_path )
+ file( shed_conf_file, 'w' ).write( xml_elems )
+
+def populate_galaxy_shed_tools_dict_file( galaxy_shed_tools_dict_file, shed_tools_dict=None ):
+ """
+ Populate the file defined by galaxy_shed_tools_dict_file with the contents of the shed_tools_dict
+ dictionary.
+ """
+ if shed_tools_dict is None:
+ shed_tools_dict = {}
+ file( galaxy_shed_tools_dict_file, 'w' ).write( to_json_string( shed_tools_dict ) )
+
def print_install_and_test_results( install_stage_type, install_and_test_statistics_dict, error_message ):
"Print statistics for the current test run."
if error_message:
diff -r 694411e94d9aa26c9b9a2c13567b2b5e07f74580 -r e9878f33a33333f211a16d2d399f528c859a6f77 test/install_and_test_tool_shed_repositories/repositories_with_tools/functional_tests.py
--- a/test/install_and_test_tool_shed_repositories/repositories_with_tools/functional_tests.py
+++ b/test/install_and_test_tool_shed_repositories/repositories_with_tools/functional_tests.py
@@ -33,7 +33,6 @@
import re
import shutil
import socket
-import string
import tempfile
import time
import threading
@@ -44,7 +43,6 @@
from galaxy.app import UniverseApplication
from galaxy.util.json import from_json_string
-from galaxy.util.json import to_json_string
from galaxy.util import unicodify
from galaxy.web import buildapp
from functional_tests import generate_config_file
@@ -134,7 +132,7 @@
tool_id = parts[ -2 ]
return tool_id, tool_version
-def install_and_test_repositories( app, galaxy_shed_tools_dict, galaxy_shed_tool_conf_file ):
+def install_and_test_repositories( app, galaxy_shed_tools_dict_file, galaxy_shed_tool_conf_file, galaxy_shed_tool_path ):
# We must make sure that functional.test_toolbox is always imported after database_contexts.galaxy_content
# is set (which occurs in the main method before this method is called). If functional.test_toolbox is
# imported before database_contexts.galaxy_content is set, sa_session will be None in all methods that use it.
@@ -166,6 +164,8 @@
# The traceback and captured output of the tool that was run will be recored for test failures. After all tests have
# completed, the repository is uninstalled, so test cases don't interfere with the next repository's functional tests.
for repository_dict in repositories_to_install:
+ # Re-initialize the received galaxy_shed_tool_conf_file to be an empty shed_tool_conf.xml.
+ install_and_test_base_util.populate_shed_conf_file( galaxy_shed_tool_conf_file, galaxy_shed_tool_path, xml_elems=None )
encoded_repository_metadata_id = repository_dict.get( 'id', None )
# Add the URL for the tool shed we're installing from, so the automated installation methods go to the right place.
repository_dict[ 'tool_shed_url' ] = install_and_test_base_util.galaxy_tool_shed_url
@@ -210,6 +210,7 @@
repository, error_message = install_and_test_base_util.install_repository( app, repository_dict )
install_and_test_statistics_dict[ 'total_repositories_processed' ] += 1
if error_message:
+ remove_tests( app, repository )
# The repository installation failed.
print 'Installation failed for revision %s of repository %s owned by %s.' % ( changeset_revision, name, owner )
processed_repositories_with_installation_error = \
@@ -247,6 +248,12 @@
install_and_test_statistics_dict,
tool_test_results_dict )
if params.get( 'test_install_error', False ):
+ # We cannot run functional tests for contained tools due to dependency installation errors.
+ remove_tests( app, repository )
+ can_run_functional_tests = False
+ print 'Cannot execute tests for tools in revision %s of repository %s owned by %s ' % \
+ ( changeset_revision, name, owner )
+ print 'because one or more dependencies has installation errors.'
# The repository was successfully installed, but one or more dependencies had installation errors,
# so we'll populate the test result containers since we cannot execute any tests.
install_and_test_base_util.save_test_results_for_changeset_revision( install_and_test_base_util.galaxy_tool_shed_url,
@@ -255,12 +262,6 @@
repository_dict,
params,
can_update_tool_shed )
- # We cannot run functional tests for contained tools due to dependency installation errors.
- print 'Cannot execute tests for tools in revision %s of repository %s owned by %s ' % \
- ( changeset_revision, name, owner )
- print 'because one or more dependencies has installation errors.'
- can_run_functional_tests = False
- remove_tests( app )
# Populate the installation containers (success or error) for the repository's immediate repository
# dependencies whose containers are not yet populated.
install_and_test_base_util.populate_install_containers_for_repository_dependencies( app,
@@ -280,32 +281,30 @@
install_and_test_base_util.get_missing_tool_dependencies( repository,
all_missing_tool_dependencies=None )
print 'Missing tool dependencies:\n%s' % str( missing_tool_dependencies )
- if missing_repository_dependencies or missing_tool_dependencies:
+ if missing_repository_dependencies or missing_tool_dependencies:
+ # The repository was installed successfully, but one or more dependencies had installation errors. Since
+ # we cannot test the tools due to these errors, we'll remove tests and tools were created during the repository
+ # installation process so nose will not discover them and attempt to execute them.
+ remove_tests( app, repository )
print 'Cannot execute tests for tools in revision %s of repository %s owned by %s ' % \
( changeset_revision, name, owner )
print 'because one or more dependencies has installation errors.'
- # The repository was installed successfully, but one or more dependencies had installation errors. Since
- # we cannot test the tools due to these errors, we'll remove tests and tools were created during the repository
- # installation process so nose will not discover them and attempt to execute them.
- remove_tests( app )
else:
print 'Revision %s of repository %s owned by %s installed successfully, so running tool tests.' % \
( changeset_revision, name, owner )
# Generate the shed_tools_dict that specifies the location of test data contained within this repository.
# and configure and run functional tests for this repository. This is equivalent to
# sh run_functional_tests.sh -installed
- file( galaxy_shed_tools_dict, 'w' ).write( to_json_string( {} ) )
+ install_and_test_base_util.populate_galaxy_shed_tools_dict_file( galaxy_shed_tools_dict_file,
+ shed_tools_dict=None )
# Find the path to the test-data directory within the installed repository.
has_test_data, shed_tools_dict = \
parse_tool_panel_config( galaxy_shed_tool_conf_file,
- from_json_string( file( galaxy_shed_tools_dict, 'r' ).read() ) )
+ from_json_string( file( galaxy_shed_tools_dict_file, 'r' ).read() ) )
# If the repository has a test-data directory we write the generated shed_tools_dict to a temporary
# file so the functional test framework can find it.
- # TODO: Eliminate the need for this shed_tools_dict since it grows large over the course of each test run.
- # If it cannot be eliminated altogether, reinitialize it with each new repository install so at this point
- # it contains only entries for the current repository dependency hierarchy being tested.
- file( galaxy_shed_tools_dict, 'w' ).write( to_json_string( shed_tools_dict ) )
- print 'Saved generated shed_tools_dict to %s\nContents: %s' % ( galaxy_shed_tools_dict, shed_tools_dict )
+ install_and_test_base_util.populate_galaxy_shed_tools_dict_file( galaxy_shed_tools_dict_file,
+ shed_tools_dict=shed_tools_dict )
try:
install_and_test_statistics_dict = test_repository_tools( app,
repository,
@@ -314,6 +313,7 @@
tool_test_results_dict,
install_and_test_statistics_dict )
except Exception, e:
+ remove_tests( app, repository )
exception_message = 'Error executing tests for repository %s: %s' % ( name, str( e ) )
log.exception( exception_message )
tool_test_results_dict[ 'failed_tests' ].append( exception_message )
@@ -330,6 +330,7 @@
params,
can_update_tool_shed )
else:
+ remove_tests( app, repository )
print 'Skipped attempt to install revision %s of repository %s owned by %s because ' % \
( changeset_revision, name, owner )
print 'it was previously installed and currently has status %s' % str( repository.status )
@@ -379,12 +380,12 @@
os.path.join( galaxy_test_tmp_dir, 'test_migrated_tool_conf.xml' ) )
galaxy_tool_sheds_conf_file = os.environ.get( 'GALAXY_INSTALL_TEST_TOOL_SHEDS_CONF',
os.path.join( galaxy_test_tmp_dir, 'test_tool_sheds_conf.xml' ) )
- galaxy_shed_tools_dict = os.environ.get( 'GALAXY_INSTALL_TEST_SHED_TOOL_DICT_FILE',
- os.path.join( galaxy_test_tmp_dir, 'shed_tool_dict' ) )
- file( galaxy_shed_tools_dict, 'w' ).write( to_json_string( {} ) )
+ galaxy_shed_tools_dict_file = os.environ.get( 'GALAXY_INSTALL_TEST_SHED_TOOL_DICT_FILE',
+ os.path.join( galaxy_test_tmp_dir, 'shed_tool_dict' ) )
+ install_and_test_base_util.populate_galaxy_shed_tools_dict_file( galaxy_shed_tools_dict_file, shed_tools_dict=None )
# Set the GALAXY_TOOL_SHED_TEST_FILE environment variable to the path of the shed_tools_dict file so that
# test.base.twilltestcase.setUp will find and parse it properly.
- os.environ[ 'GALAXY_TOOL_SHED_TEST_FILE' ] = galaxy_shed_tools_dict
+ os.environ[ 'GALAXY_TOOL_SHED_TEST_FILE' ] = galaxy_shed_tools_dict_file
if 'GALAXY_INSTALL_TEST_TOOL_DATA_PATH' in os.environ:
tool_data_path = os.environ.get( 'GALAXY_INSTALL_TEST_TOOL_DATA_PATH' )
else:
@@ -430,13 +431,10 @@
if 'GALAXY_INSTALL_TEST_TOOL_SHEDS_CONF' not in os.environ:
file( galaxy_tool_sheds_conf_file, 'w' ).write( install_and_test_base_util.tool_sheds_conf_xml )
# Generate the shed_tool_conf.xml file.
- tool_conf_template_parser = string.Template( install_and_test_base_util.shed_tool_conf_xml_template )
- shed_tool_conf_xml = tool_conf_template_parser.safe_substitute( shed_tool_path=galaxy_shed_tool_path )
- file( galaxy_shed_tool_conf_file, 'w' ).write( shed_tool_conf_xml )
+ install_and_test_base_util.populate_shed_conf_file( galaxy_shed_tool_conf_file, galaxy_shed_tool_path, xml_elems=None )
os.environ[ 'GALAXY_INSTALL_TEST_SHED_TOOL_CONF' ] = galaxy_shed_tool_conf_file
# Generate the migrated_tool_conf.xml file.
- migrated_tool_conf_xml = tool_conf_template_parser.safe_substitute( shed_tool_path=galaxy_migrated_tool_path )
- file( galaxy_migrated_tool_conf_file, 'w' ).write( migrated_tool_conf_xml )
+ install_and_test_base_util.populate_shed_conf_file( galaxy_migrated_tool_conf_file, galaxy_migrated_tool_path, xml_elems=None )
# Write the embedded web application's specific configuration to a temporary file. This is necessary in order for
# the external metadata script to find the right datasets.
kwargs = dict( admin_users = 'test(a)bx.psu.edu',
@@ -548,8 +546,9 @@
print "# This run will not update the Tool Shed database."
print "####################################################################################"
install_and_test_statistics_dict, error_message = install_and_test_repositories( app,
- galaxy_shed_tools_dict,
- galaxy_shed_tool_conf_file )
+ galaxy_shed_tools_dict_file,
+ galaxy_shed_tool_conf_file,
+ galaxy_shed_tool_path )
try:
install_and_test_base_util.print_install_and_test_results( 'repositories with tools',
install_and_test_statistics_dict,
@@ -583,12 +582,17 @@
# Return a "successful" response to buildbot.
return 0
-def remove_tests( app ):
+def remove_tests( app, repository ):
"""
- Delete any configured tool functional tests from the test_toolbox.__dict__, otherwise nose will find them
- and try to re-run the tests after uninstalling the repository, which will cause false failure reports,
- since the test data has been deleted from disk by now.
+ Delete any configured tool functional tests from the test_toolbox.__dict__.
"""
+ print '\n-------------------------------------------------------------\n'
+ if repository:
+ print 'Removing tests and tool ids for revision %s of repository %s owned by %s' % \
+ ( str( repository.changeset_revision ), str( repository.name ), str( repository.owner ) )
+ else:
+ print 'Removing tests and tool ids when received repository is None...'
+ print 'app.toolbox.tools_by_id.keys():\n%s\n' % str( app.toolbox.tools_by_id.keys() )
tests_to_delete = []
tools_to_delete_by_id = []
for key in test_toolbox.__dict__:
@@ -608,15 +612,31 @@
reset_spaces_tool_id = tool_id.replace( '_', ' ' )
if reset_spaces_app_tool_id == reset_spaces_tool_id:
print 'Setting tool id %s for deletion from app.toolbox[ tools_by_id ].' % str( app_tool_id )
- tools_to_delete_by_id.append( app_tool_id )
+ tools_to_delete_by_id.append( app_tool_id )
+ if repository:
+ metadata = repository.metadata
+ print 'metadata:\n%s\n' % str( metadata )
+ if metadata:
+ tools = metadata.get( 'tools', [] )
+ print 'tools:\n%s\n' % str( tools )
+ for tool_dict in tools:
+ print 'tool_dict:\n%s\n' % str( tool_dict )
+ guid = tool_dict.get( 'guid', None )
+ print 'guid:\n%s\n' % str( guid )
+ if guid:
+ if guid in app.toolbox.tools_by_id:
+ print 'Setting tool id %s for deletion from app.toolbox[ tools_by_id ].' % str( guid )
+ tools_to_delete_by_id.append( guid )
# Delete the discovered twill-generated tests.
for key in tests_to_delete:
if key in test_toolbox.__dict__:
print 'Deleting test %s from test_toolbox.' % str( key )
del test_toolbox.__dict__[ key ]
for tool_id in tools_to_delete_by_id:
- print 'Deleting tool id %s from app.toolbox[ tools_by_id ].' % str( tool_id )
- del app.toolbox.tools_by_id[ tool_id ]
+ if tool_id in app.toolbox.tools_by_id:
+ print 'Deleting tool id %s from app.toolbox[ tools_by_id ].' % str( tool_id )
+ del app.toolbox.tools_by_id[ tool_id ]
+ print '\n-------------------------------------------------------------\n'
def test_repository_tools( app, repository, repository_dict, tool_test_results_dicts, tool_test_results_dict,
install_and_test_statistics_dict ):
@@ -700,7 +720,7 @@
can_update_tool_shed )
# Remove the just-executed tests so twill will not find and re-test them along with the tools
# contained in the next repository.
- remove_tests( app )
+ remove_tests( app, repository )
return install_and_test_statistics_dict
if __name__ == "__main__":
diff -r 694411e94d9aa26c9b9a2c13567b2b5e07f74580 -r e9878f33a33333f211a16d2d399f528c859a6f77 test/install_and_test_tool_shed_repositories/tool_dependency_definitions/functional_tests.py
--- a/test/install_and_test_tool_shed_repositories/tool_dependency_definitions/functional_tests.py
+++ b/test/install_and_test_tool_shed_repositories/tool_dependency_definitions/functional_tests.py
@@ -33,7 +33,6 @@
import re
import shutil
import socket
-import string
import tempfile
import time
import threading
@@ -43,8 +42,6 @@
from base.tool_shed_util import parse_tool_panel_config
from galaxy.app import UniverseApplication
-from galaxy.util.json import from_json_string
-from galaxy.util.json import to_json_string
from galaxy.util import unicodify
from galaxy.web import buildapp
from functional_tests import generate_config_file
@@ -75,7 +72,7 @@
test_framework = install_and_test_base_util.TOOL_DEPENDENCY_DEFINITIONS
-def install_and_test_repositories( app, galaxy_shed_tools_dict, galaxy_shed_tool_conf_file ):
+def install_and_test_repositories( app, galaxy_shed_tools_dict_file, galaxy_shed_tool_conf_file, galaxy_shed_tool_path ):
# Initialize a dictionary for the summary that will be printed to stdout.
install_and_test_statistics_dict = install_and_test_base_util.initialize_install_and_test_statistics_dict()
error_message = ''
@@ -234,12 +231,13 @@
os.path.join( galaxy_test_tmp_dir, 'test_migrated_tool_conf.xml' ) )
galaxy_tool_sheds_conf_file = os.environ.get( 'GALAXY_INSTALL_TEST_TOOL_SHEDS_CONF',
os.path.join( galaxy_test_tmp_dir, 'test_tool_sheds_conf.xml' ) )
- galaxy_shed_tools_dict = os.environ.get( 'GALAXY_INSTALL_TEST_SHED_TOOL_DICT_FILE',
- os.path.join( galaxy_test_tmp_dir, 'shed_tool_dict' ) )
- file( galaxy_shed_tools_dict, 'w' ).write( to_json_string( {} ) )
+ galaxy_shed_tools_dict_file = os.environ.get( 'GALAXY_INSTALL_TEST_SHED_TOOL_DICT_FILE',
+ os.path.join( galaxy_test_tmp_dir, 'shed_tool_dict' ) )
+ install_and_test_base_util.populate_galaxy_shed_tools_dict_file( galaxy_shed_tools_dict_file,
+ shed_tools_dict=None )
# Set the GALAXY_TOOL_SHED_TEST_FILE environment variable to the path of the shed_tools_dict file so that
# test.base.twilltestcase.setUp will find and parse it properly.
- os.environ[ 'GALAXY_TOOL_SHED_TEST_FILE' ] = galaxy_shed_tools_dict
+ os.environ[ 'GALAXY_TOOL_SHED_TEST_FILE' ] = galaxy_shed_tools_dict_file
if 'GALAXY_INSTALL_TEST_TOOL_DATA_PATH' in os.environ:
tool_data_path = os.environ.get( 'GALAXY_INSTALL_TEST_TOOL_DATA_PATH' )
else:
@@ -285,13 +283,10 @@
if 'GALAXY_INSTALL_TEST_TOOL_SHEDS_CONF' not in os.environ:
file( galaxy_tool_sheds_conf_file, 'w' ).write( install_and_test_base_util.tool_sheds_conf_xml )
# Generate the shed_tool_conf.xml file.
- tool_conf_template_parser = string.Template( install_and_test_base_util.shed_tool_conf_xml_template )
- shed_tool_conf_xml = tool_conf_template_parser.safe_substitute( shed_tool_path=galaxy_shed_tool_path )
- file( galaxy_shed_tool_conf_file, 'w' ).write( shed_tool_conf_xml )
+ install_and_test_base_util.populate_shed_conf_file( galaxy_shed_tool_conf_file, galaxy_shed_tool_path, xml_elems=None )
os.environ[ 'GALAXY_INSTALL_TEST_SHED_TOOL_CONF' ] = galaxy_shed_tool_conf_file
# Generate the migrated_tool_conf.xml file.
- migrated_tool_conf_xml = tool_conf_template_parser.safe_substitute( shed_tool_path=galaxy_migrated_tool_path )
- file( galaxy_migrated_tool_conf_file, 'w' ).write( migrated_tool_conf_xml )
+ install_and_test_base_util.populate_shed_conf_file( galaxy_migrated_tool_conf_file, galaxy_migrated_tool_path, xml_elems=None )
# Write the embedded web application's specific configuration to a temporary file. This is necessary in order for
# the external metadata script to find the right datasets.
kwargs = dict( admin_users = 'test(a)bx.psu.edu',
@@ -402,8 +397,9 @@
print "# This run will not update the Tool Shed database."
print "####################################################################################"
install_and_test_statistics_dict, error_message = install_and_test_repositories( app,
- galaxy_shed_tools_dict,
- galaxy_shed_tool_conf_file )
+ galaxy_shed_tools_dict_file,
+ galaxy_shed_tool_conf_file,
+ galaxy_shed_tool_path )
try:
install_and_test_base_util.print_install_and_test_results( 'tool dependency definitions',
install_and_test_statistics_dict,
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: dannon: Select2 dynamic loading now uses non-static URL (Should fix main's prefixed URL issue)
by commits-noreply@bitbucket.org 11 Feb '14
by commits-noreply@bitbucket.org 11 Feb '14
11 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/694411e94d9a/
Changeset: 694411e94d9a
User: dannon
Date: 2014-02-11 17:59:38
Summary: Select2 dynamic loading now uses non-static URL (Should fix main's prefixed URL issue)
Affected #: 1 file
diff -r 15fc8675064ea46b7e081d9643393be354f07d65 -r 694411e94d9aa26c9b9a2c13567b2b5e07f74580 templates/admin/impersonate.mako
--- a/templates/admin/impersonate.mako
+++ b/templates/admin/impersonate.mako
@@ -30,7 +30,7 @@
$("#email_select").select2({
placeholder: "Select a user",
ajax: {
- url: "/api/users/",
+ url: "${h.url_for(controller="/api/users", action="index")}",
dataType: 'json',
quietMillis: 250,
matcher: function(term, text) { return text.toUpperCase().indexOf(term.toUpperCase())>=0; },
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: inithello: Make GALAXY_TEST_NO_CLEANUP apply to uploaded and tool output datasets.
by commits-noreply@bitbucket.org 11 Feb '14
by commits-noreply@bitbucket.org 11 Feb '14
11 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/15fc8675064e/
Changeset: 15fc8675064e
User: inithello
Date: 2014-02-11 16:38:50
Summary: Make GALAXY_TEST_NO_CLEANUP apply to uploaded and tool output datasets.
Affected #: 1 file
diff -r f3dc213a5773e356c23915b985803058aeccf9a1 -r 15fc8675064ea46b7e081d9643393be354f07d65 test/base/twilltestcase.py
--- a/test/base/twilltestcase.py
+++ b/test/base/twilltestcase.py
@@ -95,7 +95,10 @@
diff = list( difflib.unified_diff( local_file, history_data, "local_file", "history_data" ) )
diff_lines = get_lines_diff( diff )
if diff_lines > allowed_diff_count:
- diff_slice = diff[0:40]
+ if len(diff) < 60:
+ diff_slice = diff[0:40]
+ else:
+ diff_slice = diff[:25] + ["********\n", "*SNIP *\n", "********\n"] + diff[-25:]
#FIXME: This pdf stuff is rather special cased and has not been updated to consider lines_diff
#due to unknown desired behavior when used in conjunction with a non-zero lines_diff
#PDF forgiveness can probably be handled better by not special casing by __extension__ here
@@ -897,7 +900,8 @@
errmsg += str( err )
raise AssertionError( errmsg )
finally:
- os.remove( temp_name )
+ if 'GALAXY_TEST_NO_CLEANUP' not in os.environ:
+ os.remove( temp_name )
def __default_dataset_fetcher( self ):
def fetcher( hda_id, filename=None ):
@@ -971,7 +975,8 @@
errmsg += str( err )
raise AssertionError( errmsg )
finally:
- os.remove( temp_name )
+ if 'GALAXY_TEST_NO_CLEANUP' not in os.environ:
+ os.remove( temp_name )
def is_zipped( self, filename ):
if not zipfile.is_zipfile( filename ):
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
10 Feb '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/f3dc213a5773/
Changeset: f3dc213a5773
User: jmchilton
Date: 2014-02-11 05:13:35
Summary: Backout of 28d43f4.
Not quite ready for primetime.
Affected #: 2 files
diff -r b95d750d1eed64c19c0e31d6683f0f8dca73aeb3 -r f3dc213a5773e356c23915b985803058aeccf9a1 lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -16,7 +16,6 @@
from sanitize import ToolParameterSanitizer
import validation
import dynamic_options
-from .dataset_util import DatasetParamContext
# For BaseURLToolParameter
from galaxy.web import url_for
from galaxy.model.item_attrs import Dictifiable
@@ -1611,36 +1610,52 @@
self.conversions.append( ( name, conv_extensions, conv_types ) )
def get_html_field( self, trans=None, value=None, other_values={} ):
+ filter_value = None
+ if self.options:
+ try:
+ filter_value = self.options.get_options( trans, other_values )[0][0]
+ except IndexError:
+ pass # no valid options
history = self._get_history( trans )
if value is not None:
if type( value ) != list:
value = [ value ]
- dataset_param_context = DatasetParamContext( trans, history, self, value, other_values )
field = form_builder.SelectField( self.name, self.multiple, None, self.refresh_on_change, refresh_on_change_values=self.refresh_on_change_values )
# CRUCIAL: the dataset_collector function needs to be local to DataToolParameter.get_html_field()
def dataset_collector( hdas, parent_hid ):
+ current_user_roles = trans.get_current_user_roles()
for i, hda in enumerate( hdas ):
hda_name = hda.name
if parent_hid is not None:
hid = "%s.%d" % ( parent_hid, i + 1 )
else:
hid = str( hda.hid )
- valid_hda = dataset_param_context.valid_hda( hda )
- if not valid_hda:
- continue
- if not valid_hda.implicit_conversion:
- selected = dataset_param_context.selected( hda )
- if hda.visible:
- hidden_text = ""
+ if not hda.dataset.state in [galaxy.model.Dataset.states.ERROR, galaxy.model.Dataset.states.DISCARDED] and \
+ ( hda.visible or ( value and hda in value and not hda.implicitly_converted_parent_datasets ) ) and \
+ trans.app.security_agent.can_access_dataset( current_user_roles, hda.dataset ):
+ # If we are sending data to an external application, then we need to make sure there are no roles
+ # associated with the dataset that restrict it's access from "public".
+ if self.tool and self.tool.tool_type == 'data_destination' and not trans.app.security_agent.dataset_is_public( hda.dataset ):
+ continue
+ if self.options and self._options_filter_attribute( hda ) != filter_value:
+ continue
+ if hda.datatype.matches_any( self.formats ):
+ selected = ( value and ( hda in value ) )
+ if hda.visible:
+ hidden_text = ""
+ else:
+ hidden_text = " (hidden)"
+ field.add_option( "%s:%s %s" % ( hid, hidden_text, hda_name ), hda.id, selected )
else:
- hidden_text = " (hidden)"
- field.add_option( "%s:%s %s" % ( hid, hidden_text, hda_name ), hda.id, selected )
- else:
- hda = valid_hda.hda # Get converted dataset
- target_ext = valid_hda.target_ext
- selected = dataset_param_context.selected( hda )
- field.add_option( "%s: (as %s) %s" % ( hid, target_ext, hda_name ), hda.id, selected )
+ target_ext, converted_dataset = hda.find_conversion_destination( self.formats )
+ if target_ext:
+ if converted_dataset:
+ hda = converted_dataset
+ if not trans.app.security_agent.can_access_dataset( current_user_roles, hda.dataset ):
+ continue
+ selected = ( value and ( hda in value ) )
+ field.add_option( "%s: (as %s) %s" % ( hid, target_ext, hda_name ), hda.id, selected )
# Also collect children via association object
dataset_collector( hda.children, hid )
dataset_collector( history.active_datasets_children_and_roles, None )
@@ -1672,18 +1687,30 @@
if trans is None or trans.workflow_building_mode or trans.webapp.name == 'tool_shed':
return DummyDataset()
history = self._get_history( trans, history )
- dataset_param_context = DatasetParamContext( trans, history, self, None, context )
if self.optional:
return None
most_recent_dataset = []
+ filter_value = None
+ if self.options:
+ try:
+ filter_value = self.options.get_options( trans, context )[0][0]
+ except IndexError:
+ pass # no valid options
def dataset_collector( datasets ):
for i, data in enumerate( datasets ):
- if data.visible and dataset_param_context.hda_accessible( data, check_security=False ):
- match = dataset_param_context.valid_hda_matches_format( data, check_security=False )
- if not match or dataset_param_context.filter( match.hda ):
+ if data.visible and not data.deleted and data.state not in [data.states.ERROR, data.states.DISCARDED]:
+ is_valid = False
+ if data.datatype.matches_any( self.formats ):
+ is_valid = True
+ else:
+ target_ext, converted_dataset = data.find_conversion_destination( self.formats )
+ if target_ext:
+ is_valid = True
+ if converted_dataset:
+ data = converted_dataset
+ if not is_valid or ( self.options and self._options_filter_attribute( data ) != filter_value ):
continue
- data = match.hda
most_recent_dataset.append(data)
# Also collect children via association object
dataset_collector( data.children )
diff -r b95d750d1eed64c19c0e31d6683f0f8dca73aeb3 -r f3dc213a5773e356c23915b985803058aeccf9a1 lib/galaxy/tools/parameters/dataset_util.py
--- a/lib/galaxy/tools/parameters/dataset_util.py
+++ /dev/null
@@ -1,96 +0,0 @@
-import galaxy.model
-
-from logging import getLogger
-log = getLogger( __name__ )
-
-ROLES_UNSET = object()
-INVALID_STATES = [ galaxy.model.Dataset.states.ERROR, galaxy.model.Dataset.states.DISCARDED ]
-
-
-class DatasetParamContext( object ):
-
- def __init__( self, trans, history, param, value, other_values ):
- self.trans = trans
- self.history = history
- self.param = param
- self.tool = param.tool
- self.value = value
- self.current_user_roles = ROLES_UNSET
- filter_value = None
- if param.options:
- try:
- filter_value = param.options.get_options( trans, other_values )[0][0]
- except IndexError:
- pass # no valid options
- self.filter_value = filter_value
-
- def hda_accessible( self, hda, check_security=True ):
- dataset = hda.dataset
- state_valid = not dataset.state in INVALID_STATES
- return state_valid and (not check_security or self.__can_access_dataset( dataset ) )
-
- def valid_hda_matches_format( self, hda, check_implicit_conversions=True, check_security=False ):
- if self.filter( hda ):
- return False
- formats = self.param.formats
- if hda.datatype.matches_any( formats ):
- return ValidParamHdaDirect( hda )
- if not check_implicit_conversions:
- return False
- target_ext, converted_dataset = hda.find_conversion_destination( formats )
- if target_ext:
- if converted_dataset:
- hda = converted_dataset
- if check_security and not self.__can_access_dataset( hda.dataset ):
- return False
- return ValidParamHdaImplicit(converted_dataset, target_ext)
- return False
-
- def valid_hda( self, hda, check_implicit_conversions=True ):
- accessible = self.hda_accessible( hda )
- if accessible and ( hda.visible or ( self.selected( hda ) and not hda.implicitly_converted_parent_datasets ) ):
- # If we are sending data to an external application, then we need to make sure there are no roles
- # associated with the dataset that restrict it's access from "public".
- require_public = self.tool and self.tool.tool_type == 'data_destination'
- if require_public and not self.trans.app.security_agent.dataset_is_public( hda.dataset ):
- return False
- if self.filter( hda ):
- return False
- return self.valid_hda_matches_format(hda)
-
- def selected( self, hda ):
- value = self.value
- return value and hda in value
-
- def filter( self, hda ):
- param = self.param
- return param.options and param._options_filter_attribute( hda ) != self.filter_value
-
- def __can_access_dataset( self, dataset ):
- if self.current_user_roles is ROLES_UNSET:
- self.current_user_roles = self.trans.get_current_user_roles()
- return self.trans.app.security_agent.can_access_dataset( self.current_user_roles, dataset )
-
-
-class ValidParamHdaDirect( object ):
-
- def __init__( self, hda ):
- self.hda = hda
-
- @property
- def implicit_conversion( self ):
- return False
-
-
-class ValidParamHdaImplicit( object ):
-
- def __init__( self, converted_dataset, target_ext ):
- self.hda = converted_dataset
- self.target_ext = target_ext
-
- @property
- def implicit_conversion( self ):
- return True
-
-
-__all__ = [ DatasetParamContext ]
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
28 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/62d6ae1c93d9/
Changeset: 62d6ae1c93d9
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Hack to fix test_executions for 0951e07.
This whole concept I had of using these models in unit tests without a database connection present probably needs to be done away with.
Affected #: 1 file
diff -r e18b36121df0bcb9a30906716e5b0c8a747bd138 -r 62d6ae1c93d9e4cdcba84dd15bef6bd9ef8d08de test/unit/tools/test_execution.py
--- a/test/unit/tools/test_execution.py
+++ b/test/unit/tools/test_execution.py
@@ -272,7 +272,7 @@
def __init__( self, app, history ):
self.app = app
self.history = history
- self.history = galaxy.model.History()
+ self.history._active_datasets_children_and_roles = filter( lambda hda: hda.active and hda.history == history, self.app.model.context.model_objects[ galaxy.model.HistoryDatasetAssociation ] )
self.workflow_building_mode = False
self.webapp = Bunch( name="galaxy" )
self.sa_session = self.app.model.context
https://bitbucket.org/galaxy/galaxy-central/commits/36f51ba22382/
Changeset: 36f51ba22382
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Reduce code duplication between JobWrapper and TaskWrapper.
Affected #: 1 file
diff -r 62d6ae1c93d9e4cdcba84dd15bef6bd9ef8d08de -r 36f51ba223821a5eaee6db123ff1efea12390f66 lib/galaxy/jobs/__init__.py
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -647,10 +647,7 @@
if not os.path.exists( self.working_directory ):
os.mkdir( self.working_directory )
- # Restore parameters from the database
- job = self.get_job()
- if job.user is None and job.galaxy_session is None:
- raise Exception( 'Job %s has no user and no session.' % job.id )
+ job = self._load_job()
incoming = dict( [ ( p.name, p.value ) for p in job.parameters ] )
incoming = self.tool.params_from_strings( incoming, self.app )
@@ -718,6 +715,14 @@
self.version_string_cmd = self.tool.version_string_cmd
return extra_filenames
+ def _load_job( self ):
+ # Load job from database and verify it has user or session.
+ # Restore parameters from the database
+ job = self.get_job()
+ if job.user is None and job.galaxy_session is None:
+ raise Exception( 'Job %s has no user and no session.' % job.id )
+ return job
+
def fail( self, message, exception=False, stdout="", stderr="", exit_code=None ):
"""
Indicate job failure by setting state and message on all output
@@ -1398,10 +1403,8 @@
config files.
"""
# Restore parameters from the database
- job = self.get_job()
+ job = self._load_job()
task = self.get_task()
- if job.user is None and job.galaxy_session is None:
- raise Exception( 'Job %s has no user and no session.' % job.id )
incoming = dict( [ ( p.name, p.value ) for p in job.parameters ] )
incoming = self.tool.params_from_strings( incoming, self.app )
# Do any validation that could not be done at job creation
https://bitbucket.org/galaxy/galaxy-central/commits/17abb2bbdf44/
Changeset: 17abb2bbdf44
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: TaskWrapper - utilize user_template_environment like JobWrapper.
Affected #: 1 file
diff -r 36f51ba223821a5eaee6db123ff1efea12390f66 -r 17abb2bbdf442dc5f5b606d5726e073368a638c2 lib/galaxy/jobs/__init__.py
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -1415,15 +1415,10 @@
inp_data.update( [ ( da.name, da.dataset ) for da in job.input_library_datasets ] )
out_data.update( [ ( da.name, da.dataset ) for da in job.output_library_datasets ] )
# DBTODO New method for generating command line for a task?
- # These can be passed on the command line if wanted as $userId $userEmail
- if job.history and job.history.user: # check for anonymous user!
- userId = '%d' % job.history.user.id
- userEmail = str(job.history.user.email)
- else:
- userId = 'Anonymous'
- userEmail = 'Anonymous'
- incoming['userId'] = userId
- incoming['userEmail'] = userEmail
+
+ # These can be passed on the command line if wanted as $__user_*__
+ incoming.update( model.User.user_template_environment( job.history and job.history.user ) )
+
# Build params, done before hook so hook can use
param_dict = self.tool.build_param_dict( incoming, inp_data, out_data, self.get_output_fnames(), self.working_directory )
fnames = {}
https://bitbucket.org/galaxy/galaxy-central/commits/aa53f2b3b52e/
Changeset: aa53f2b3b52e
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Reduce code duplication between JobWrapper and TaskWrapper.
Create shared method for building up param dict.
Affected #: 1 file
diff -r 17abb2bbdf442dc5f5b606d5726e073368a638c2 -r aa53f2b3b52ebd274b82dface195561778f2459b lib/galaxy/jobs/__init__.py
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -649,46 +649,8 @@
job = self._load_job()
- incoming = dict( [ ( p.name, p.value ) for p in job.parameters ] )
- incoming = self.tool.params_from_strings( incoming, self.app )
- # Do any validation that could not be done at job creation
- self.tool.handle_unvalidated_param_values( incoming, self.app )
- # Restore input / output data lists
- inp_data = dict( [ ( da.name, da.dataset ) for da in job.input_datasets ] )
- out_data = dict( [ ( da.name, da.dataset ) for da in job.output_datasets ] )
- inp_data.update( [ ( da.name, da.dataset ) for da in job.input_library_datasets ] )
- out_data.update( [ ( da.name, da.dataset ) for da in job.output_library_datasets ] )
+ param_dict = self._build_param_dict( job, populate_special_output_file=True )
- # Set up output dataset association for export history jobs. Because job
- # uses a Dataset rather than an HDA or LDA, it's necessary to set up a
- # fake dataset association that provides the needed attributes for
- # preparing a job.
- class FakeDatasetAssociation ( object ):
- def __init__( self, dataset=None ):
- self.dataset = dataset
- self.file_name = dataset.file_name
- self.metadata = dict()
- self.children = []
- special = self.sa_session.query( model.JobExportHistoryArchive ).filter_by( job=job ).first()
- if not special:
- special = self.sa_session.query( model.GenomeIndexToolData ).filter_by( job=job ).first()
- if special:
- out_data[ "output_file" ] = FakeDatasetAssociation( dataset=special.dataset )
-
- # These can be passed on the command line if wanted as $__user_*__
- incoming.update( model.User.user_template_environment( job.history and job.history.user ) )
-
- # Build params, done before hook so hook can use
- param_dict = self.tool.build_param_dict( incoming,
- inp_data, out_data,
- self.get_output_fnames(),
- self.working_directory )
- # Certain tools require tasks to be completed prior to job execution
- # ( this used to be performed in the "exec_before_job" hook, but hooks are deprecated ).
- self.tool.exec_before_job( self.queue.app, inp_data, out_data, param_dict )
- # Run the before queue ("exec_before_job") hook
- self.tool.call_hook( 'exec_before_job', self.queue.app, inp_data=inp_data,
- out_data=out_data, tool=self.tool, param_dict=incoming)
self.sa_session.flush()
# Build any required config files
config_filenames = self.tool.build_config_files( param_dict, self.working_directory )
@@ -723,6 +685,52 @@
raise Exception( 'Job %s has no user and no session.' % job.id )
return job
+ def _build_param_dict( self, job, populate_special_output_file=True ):
+ incoming = dict( [ ( p.name, p.value ) for p in job.parameters ] )
+ incoming = self.tool.params_from_strings( incoming, self.app )
+ # Do any validation that could not be done at job creation
+ self.tool.handle_unvalidated_param_values( incoming, self.app )
+ # Restore input / output data lists
+ inp_data = dict( [ ( da.name, da.dataset ) for da in job.input_datasets ] )
+ out_data = dict( [ ( da.name, da.dataset ) for da in job.output_datasets ] )
+ inp_data.update( [ ( da.name, da.dataset ) for da in job.input_library_datasets ] )
+ out_data.update( [ ( da.name, da.dataset ) for da in job.output_library_datasets ] )
+
+ if populate_special_output_file:
+ # Set up output dataset association for export history jobs. Because job
+ # uses a Dataset rather than an HDA or LDA, it's necessary to set up a
+ # fake dataset association that provides the needed attributes for
+ # preparing a job.
+ class FakeDatasetAssociation ( object ):
+ def __init__( self, dataset=None ):
+ self.dataset = dataset
+ self.file_name = dataset.file_name
+ self.metadata = dict()
+ self.children = []
+ special = self.sa_session.query( model.JobExportHistoryArchive ).filter_by( job=job ).first()
+ if not special:
+ special = self.sa_session.query( model.GenomeIndexToolData ).filter_by( job=job ).first()
+ if special:
+ out_data[ "output_file" ] = FakeDatasetAssociation( dataset=special.dataset )
+
+ # These can be passed on the command line if wanted as $__user_*__
+ incoming.update( model.User.user_template_environment( job.history and job.history.user ) )
+
+ # Build params, done before hook so hook can use
+ param_dict = self.tool.build_param_dict( incoming,
+ inp_data, out_data,
+ self.get_output_fnames(),
+ self.working_directory )
+
+ # Certain tools require tasks to be completed prior to job execution
+ # ( this used to be performed in the "exec_before_job" hook, but hooks are deprecated ).
+ self.tool.exec_before_job( self.queue.app, inp_data, out_data, param_dict )
+ # Run the before queue ("exec_before_job") hook
+ self.tool.call_hook( 'exec_before_job', self.queue.app, inp_data=inp_data,
+ out_data=out_data, tool=self.tool, param_dict=incoming)
+
+ return param_dict
+
def fail( self, message, exception=False, stdout="", stderr="", exit_code=None ):
"""
Indicate job failure by setting state and message on all output
@@ -1405,34 +1413,20 @@
# Restore parameters from the database
job = self._load_job()
task = self.get_task()
- incoming = dict( [ ( p.name, p.value ) for p in job.parameters ] )
- incoming = self.tool.params_from_strings( incoming, self.app )
- # Do any validation that could not be done at job creation
- self.tool.handle_unvalidated_param_values( incoming, self.app )
- # Restore input / output data lists
- inp_data = dict( [ ( da.name, da.dataset ) for da in job.input_datasets ] )
- out_data = dict( [ ( da.name, da.dataset ) for da in job.output_datasets ] )
- inp_data.update( [ ( da.name, da.dataset ) for da in job.input_library_datasets ] )
- out_data.update( [ ( da.name, da.dataset ) for da in job.output_library_datasets ] )
+
# DBTODO New method for generating command line for a task?
- # These can be passed on the command line if wanted as $__user_*__
- incoming.update( model.User.user_template_environment( job.history and job.history.user ) )
+ param_dict = self._build_param_dict( job, populate_special_output_file=False )
- # Build params, done before hook so hook can use
- param_dict = self.tool.build_param_dict( incoming, inp_data, out_data, self.get_output_fnames(), self.working_directory )
+ # Build dict of file name re-writes for split up tasks.
fnames = {}
for v in self.get_input_fnames():
fnames[v] = os.path.join(self.working_directory, os.path.basename(v))
for dp in [x.real_path for x in self.get_output_fnames()]:
fnames[dp] = os.path.join(self.working_directory, os.path.basename(dp))
- # Certain tools require tasks to be completed prior to job execution
- # ( this used to be performed in the "exec_before_job" hook, but hooks are deprecated ).
- self.tool.exec_before_job( self.queue.app, inp_data, out_data, param_dict )
- # Run the before queue ("exec_before_job") hook
- self.tool.call_hook( 'exec_before_job', self.queue.app, inp_data=inp_data,
- out_data=out_data, tool=self.tool, param_dict=incoming)
+
self.sa_session.flush()
+
# Build any required config files
config_filenames = self.tool.build_config_files( param_dict, self.working_directory )
for config_filename in config_filenames:
https://bitbucket.org/galaxy/galaxy-central/commits/66242bb7cfc3/
Changeset: 66242bb7cfc3
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Rework logic in tools related to false paths.
Basically, if a false path is set trust it, don't explicitly check app.config.outputs_to_working_directory. The hope here is that this logic can then be used in other scenarios where a false_path might be setup at the layers (e.g. job_runner) above tool.
Affected #: 1 file
diff -r aa53f2b3b52ebd274b82dface195561778f2459b -r 66242bb7cfc3365f71136eae90e25f543a121e2c lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -2664,17 +2664,15 @@
if data:
for child in data.children:
param_dict[ "_CHILD___%s___%s" % ( name, child.designation ) ] = DatasetFilenameWrapper( child )
+ output_false_paths = dict( [ ( dp.real_path, dp.false_path ) for dp in output_paths if getattr( dp, "false_path", None ) ] )
for name, hda in output_datasets.items():
# Write outputs to the working directory (for security purposes)
# if desired.
- if self.app.config.outputs_to_working_directory:
- try:
- false_path = [ dp.false_path for dp in output_paths if dp.real_path == hda.file_name ][0]
- param_dict[name] = DatasetFilenameWrapper( hda, false_path = false_path )
- open( false_path, 'w' ).close()
- except IndexError:
- log.warning( "Unable to determine alternate path for writing job outputs, outputs will be written to their real paths" )
- param_dict[name] = DatasetFilenameWrapper( hda )
+ real_path = hda.file_name
+ if real_path in output_false_paths:
+ false_path = output_false_paths[ real_path ]
+ param_dict[name] = DatasetFilenameWrapper( hda, false_path = false_path )
+ open( false_path, 'w' ).close()
else:
param_dict[name] = DatasetFilenameWrapper( hda )
# Provide access to a path to store additional files
https://bitbucket.org/galaxy/galaxy-central/commits/429fc453199b/
Changeset: 429fc453199b
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Eliminate output path rewriting in TaskWrapper.
Rework task splitting to rewrite paths before cheetah evalulation not after.
Utilizing DatasetPath.false_path mechanisms implemented for config.outputs_to_working_directory. I think this could be made less convoluted if the combination of outputs_to_working_directory and Task splitting were optimized in some way - but this changeset keeps the behavior of that the same (copying twice) just to isolate changes to how these paths are evaluated in Cheetah.
Affected #: 3 files
diff -r 66242bb7cfc3365f71136eae90e25f543a121e2c -r 429fc453199b63568c81d8afe2c662fc2595864d lib/galaxy/jobs/__init__.py
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -26,6 +26,9 @@
from galaxy.util.json import from_json_string
from galaxy.util import unicodify
from .output_checker import check_output
+from .datasets import TaskPathRewriter
+from .datasets import OutputsToWorkingDirectoryPathRewriter
+from .datasets import NullDatasetPathRewriter
log = logging.getLogger( __name__ )
@@ -577,6 +580,7 @@
log.debug('(%s) Working directory for job is: %s' % (self.job_id, self.working_directory))
except ObjectInvalid:
raise Exception('Unable to create job working directory, job failure')
+ self.dataset_path_rewriter = self._job_dataset_path_rewriter( self.working_directory )
self.output_paths = None
self.output_hdas_and_paths = None
self.tool_provided_job_metadata = None
@@ -590,6 +594,13 @@
self.__user_system_pwent = None
self.__galaxy_system_pwent = None
+ def _job_dataset_path_rewriter( self, working_directory ):
+ if self.app.config.outputs_to_working_directory:
+ dataset_path_rewriter = OutputsToWorkingDirectoryPathRewriter( working_directory )
+ else:
+ dataset_path_rewriter = NullDatasetPathRewriter( )
+ return dataset_path_rewriter
+
def can_split( self ):
# Should the job handler split this job up?
return self.app.config.use_tasked_jobs and self.tool.parallelism
@@ -1169,6 +1180,8 @@
return self.output_hdas_and_paths
def compute_outputs( self ) :
+ dataset_path_rewriter = self.dataset_path_rewriter
+
class DatasetPath( object ):
def __init__( self, dataset_id, real_path, false_path=None, mutable=True ):
self.dataset_id = dataset_id
@@ -1187,21 +1200,18 @@
if not special:
special = self.sa_session.query( model.GenomeIndexToolData ).filter_by( job=job ).first()
false_path = None
- if self.app.config.outputs_to_working_directory:
- self.output_paths = []
- self.output_hdas_and_paths = {}
- for name, hda in [ ( da.name, da.dataset ) for da in job.output_datasets + job.output_library_datasets ]:
- false_path = os.path.abspath( os.path.join( self.working_directory, "galaxy_dataset_%d.dat" % hda.dataset.id ) )
- dsp = DatasetPath( hda.dataset.id, hda.dataset.file_name, false_path, mutable=hda.dataset.external_filename is None )
- self.output_paths.append( dsp )
- self.output_hdas_and_paths[name] = hda, dsp
- if special:
- false_path = os.path.abspath( os.path.join( self.working_directory, "galaxy_dataset_%d.dat" % special.dataset.id ) )
- else:
- results = [ ( da.name, da.dataset, DatasetPath( da.dataset.dataset.id, da.dataset.file_name, mutable=da.dataset.dataset.external_filename is None ) ) for da in job.output_datasets + job.output_library_datasets ]
- self.output_paths = [t[2] for t in results]
- self.output_hdas_and_paths = dict([(t[0], t[1:]) for t in results])
+
+ results = []
+ for da in job.output_datasets + job.output_library_datasets:
+ da_false_path = dataset_path_rewriter.rewrite_dataset_path( da.dataset, 'output' )
+ mutable = da.dataset.dataset.external_filename is None
+ dataset_path = DatasetPath( da.dataset.dataset.id, da.dataset.file_name, false_path=da_false_path, mutable=mutable )
+ results.append( ( da.name, da.dataset, dataset_path ) )
+
+ self.output_paths = [t[2] for t in results]
+ self.output_hdas_and_paths = dict([(t[0], t[1:]) for t in results])
if special:
+ false_path = dataset_path_rewriter.rewrite_dataset_path( special.dataset, 'output' )
dsp = DatasetPath( special.dataset.id, special.dataset.file_name, false_path )
self.output_paths.append( dsp )
return self.output_paths
@@ -1371,7 +1381,10 @@
def __init__(self, task, queue):
super(TaskWrapper, self).__init__(task.job, queue)
self.task_id = task.id
- self.working_directory = task.working_directory
+ working_directory = task.working_directory
+ self.working_directory = working_directory
+ job_dataset_path_rewriter = self._job_dataset_path_rewriter( self.working_directory )
+ self.dataset_path_rewriter = TaskPathRewriter( working_directory, job_dataset_path_rewriter )
if task.prepare_input_files_cmd is not None:
self.prepare_input_files_cmds = [ task.prepare_input_files_cmd ]
else:
@@ -1422,8 +1435,8 @@
fnames = {}
for v in self.get_input_fnames():
fnames[v] = os.path.join(self.working_directory, os.path.basename(v))
- for dp in [x.real_path for x in self.get_output_fnames()]:
- fnames[dp] = os.path.join(self.working_directory, os.path.basename(dp))
+ #for dp in [x.real_path for x in self.get_output_fnames()]:
+ # fnames[dp] = os.path.join(self.working_directory, os.path.basename(dp))
self.sa_session.flush()
diff -r 66242bb7cfc3365f71136eae90e25f543a121e2c -r 429fc453199b63568c81d8afe2c662fc2595864d lib/galaxy/jobs/datasets.py
--- /dev/null
+++ b/lib/galaxy/jobs/datasets.py
@@ -0,0 +1,64 @@
+import os.path
+
+from abc import ABCMeta
+from abc import abstractmethod
+
+
+class DatasetPathRewriter( object ):
+ """ Used by runner to rewrite paths. """
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def rewrite_dataset_path( self, dataset, dataset_type ):
+ """
+ Dataset type is 'input' or 'output'.
+ Return None to indicate not to rewrite this path.
+ """
+
+
+class NullDatasetPathRewriter( object ):
+ """ Used by default for jobwrapper, do not rewrite anything.
+ """
+
+ def rewrite_dataset_path( self, dataset, dataset_type ):
+ """ Keep path the same.
+ """
+ return None
+
+
+class OutputsToWorkingDirectoryPathRewriter( object ):
+ """ Rewrites all paths to place them in the specified working
+ directory for normal jobs when Galaxy is configured with
+ app.config.outputs_to_working_directory. Job runner base class
+ is responsible for copying these out after job is complete.
+ """
+
+ def __init__( self, working_directory ):
+ self.working_directory = working_directory
+
+ def rewrite_dataset_path( self, dataset, dataset_type ):
+ """ Keep path the same.
+ """
+ if dataset_type == 'output':
+ false_path = os.path.abspath( os.path.join( self.working_directory, "galaxy_dataset_%d.dat" % dataset.id ) )
+ return false_path
+ else:
+ return None
+
+
+class TaskPathRewriter( object ):
+ """ Rewrites all paths to place them in the specified working
+ directory for TaskWrapper. TaskWrapper is responsible for putting
+ them there and pulling them out.
+ """
+
+ def __init__( self, working_directory, job_dataset_path_rewriter ):
+ self.working_directory = working_directory
+ self.job_dataset_path_rewriter = job_dataset_path_rewriter
+
+ def rewrite_dataset_path( self, dataset, dataset_type ):
+ """
+ """
+ dataset_file_name = dataset.file_name
+ job_file_name = self.job_dataset_path_rewriter.rewrite_dataset_path( dataset, dataset_type ) or dataset_file_name
+ return os.path.join( self.working_directory, os.path.basename( job_file_name ) )
diff -r 66242bb7cfc3365f71136eae90e25f543a121e2c -r 429fc453199b63568c81d8afe2c662fc2595864d lib/galaxy/jobs/splitters/multi.py
--- a/lib/galaxy/jobs/splitters/multi.py
+++ b/lib/galaxy/jobs/splitters/multi.py
@@ -126,11 +126,12 @@
assert task_dirs, "Should be at least one sub-task!"
# TODO: Output datasets can be very complex. This doesn't handle metadata files
outputs = job_wrapper.get_output_hdas_and_fnames()
+ output_paths = job_wrapper.get_output_fnames()
pickone_done = []
task_dirs = [os.path.join(working_directory, x) for x in os.listdir(working_directory) if x.startswith('task_')]
task_dirs.sort(key = lambda x: int(x.split('task_')[-1]))
- for output in outputs:
- output_file_name = str(outputs[output][1])
+ for index, output in enumerate( outputs ):
+ output_file_name = str( output_paths[ index ] ) # Use false_path if set, else real path.
base_output_name = os.path.basename(output_file_name)
if output in merge_outputs:
output_dataset = outputs[output][0]
https://bitbucket.org/galaxy/galaxy-central/commits/36f39f9bb3fa/
Changeset: 36f39f9bb3fa
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Reorganize tool dataset wrapper instantiation for future extension.
This changeset should change how anything functions, but will make subsequent changes less dramatic.
Affected #: 1 file
diff -r 429fc453199b63568c81d8afe2c662fc2595864d -r 36f39f9bb3fa476a18bc332997e27035e7c1dadd lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -2614,11 +2614,14 @@
tool = Bunch( conversion_name = Bunch( extensions = conv_ext ) ),
name = conversion_name )
# Wrap actual input dataset
+ dataset = input_values[ input.name ]
+ wrapper_kwds = dict(
+ datatypes_registry=self.app.datatypes_registry,
+ tool=self,
+ name=input.name
+ )
input_values[ input.name ] = \
- DatasetFilenameWrapper( input_values[ input.name ],
- datatypes_registry = self.app.datatypes_registry,
- tool = self,
- name = input.name )
+ DatasetFilenameWrapper( dataset, **wrapper_kwds )
elif isinstance( input, SelectToolParameter ):
input_values[ input.name ] = SelectToolParameterWrapper(
input, input_values[ input.name ], self.app, other_values = param_dict )
@@ -2657,10 +2660,12 @@
for name, data in input_datasets.items():
param_dict_value = param_dict.get(name, None)
if not isinstance(param_dict_value, (DatasetFilenameWrapper, DatasetListWrapper)):
- param_dict[name] = DatasetFilenameWrapper( data,
- datatypes_registry = self.app.datatypes_registry,
- tool = self,
- name = name )
+ wrapper_kwds = dict(
+ datatypes_registry=self.app.datatypes_registry,
+ tool=self,
+ name=name,
+ )
+ param_dict[name] = DatasetFilenameWrapper( data, **wrapper_kwds )
if data:
for child in data.children:
param_dict[ "_CHILD___%s___%s" % ( name, child.designation ) ] = DatasetFilenameWrapper( child )
@@ -3573,7 +3578,12 @@
def __init__( self, datasets, **kwargs ):
if not isinstance(datasets, list):
datasets = [datasets]
- list.__init__( self, [DatasetFilenameWrapper(dataset, **kwargs) for dataset in datasets] )
+
+ def to_wrapper( dataset ):
+ wrapper_kwds = kwargs.copy()
+ return DatasetFilenameWrapper( dataset, **wrapper_kwds )
+
+ list.__init__( self, map( to_wrapper, datasets ) )
def json_fix( val ):
https://bitbucket.org/galaxy/galaxy-central/commits/c816b894910f/
Changeset: c816b894910f
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Refactor DatasetPath out into newer galaxy.jobs.datasets module.
Add unit test for DatasetPath class.
Affected #: 3 files
diff -r 36f39f9bb3fa476a18bc332997e27035e7c1dadd -r c816b894910f9c82b3ec525b6394a4f6e64186f7 lib/galaxy/jobs/__init__.py
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -29,6 +29,7 @@
from .datasets import TaskPathRewriter
from .datasets import OutputsToWorkingDirectoryPathRewriter
from .datasets import NullDatasetPathRewriter
+from .datasets import DatasetPath
log = logging.getLogger( __name__ )
@@ -1182,18 +1183,6 @@
def compute_outputs( self ) :
dataset_path_rewriter = self.dataset_path_rewriter
- class DatasetPath( object ):
- def __init__( self, dataset_id, real_path, false_path=None, mutable=True ):
- self.dataset_id = dataset_id
- self.real_path = real_path
- self.false_path = false_path
- self.mutable = mutable
-
- def __str__( self ):
- if self.false_path is None:
- return self.real_path
- else:
- return self.false_path
job = self.get_job()
# Job output datasets are combination of history, library, jeha and gitd datasets.
special = self.sa_session.query( model.JobExportHistoryArchive ).filter_by( job=job ).first()
diff -r 36f39f9bb3fa476a18bc332997e27035e7c1dadd -r c816b894910f9c82b3ec525b6394a4f6e64186f7 lib/galaxy/jobs/datasets.py
--- a/lib/galaxy/jobs/datasets.py
+++ b/lib/galaxy/jobs/datasets.py
@@ -1,9 +1,26 @@
+"""
+Utility classes allowing Job interface to reason about datasets.
+"""
import os.path
from abc import ABCMeta
from abc import abstractmethod
+class DatasetPath( object ):
+ def __init__( self, dataset_id, real_path, false_path=None, mutable=True ):
+ self.dataset_id = dataset_id
+ self.real_path = real_path
+ self.false_path = false_path
+ self.mutable = mutable
+
+ def __str__( self ):
+ if self.false_path is None:
+ return self.real_path
+ else:
+ return self.false_path
+
+
class DatasetPathRewriter( object ):
""" Used by runner to rewrite paths. """
__metaclass__ = ABCMeta
diff -r 36f39f9bb3fa476a18bc332997e27035e7c1dadd -r c816b894910f9c82b3ec525b6394a4f6e64186f7 test/unit/jobs/test_datasets.py
--- /dev/null
+++ b/test/unit/jobs/test_datasets.py
@@ -0,0 +1,17 @@
+from galaxy.jobs.datasets import DatasetPath
+
+
+def test_dataset_path():
+ dataset_path_1 = DatasetPath( 1, "/galaxy/database/files/dataset_1.dat" )
+ assert dataset_path_1.dataset_id == 1
+ assert dataset_path_1.real_path == "/galaxy/database/files/dataset_1.dat"
+ assert dataset_path_1.false_path is None
+ assert dataset_path_1.mutable
+ assert str( dataset_path_1 ) == "/galaxy/database/files/dataset_1.dat"
+
+ dataset_path_2 = DatasetPath( 2, "/galaxy/database/files/dataset_2.dat", false_path="/mnt/galaxyData/files/dataset_2.dat", mutable=False )
+ assert dataset_path_2.dataset_id == 2
+ assert dataset_path_2.real_path == "/galaxy/database/files/dataset_2.dat"
+ assert dataset_path_2.false_path == "/mnt/galaxyData/files/dataset_2.dat"
+ assert not dataset_path_2.mutable
+ assert str( dataset_path_2 ) == "/mnt/galaxyData/files/dataset_2.dat"
https://bitbucket.org/galaxy/galaxy-central/commits/55cf1bd3df0e/
Changeset: 55cf1bd3df0e
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Eliminate input (hence all) path rewriting in TaskWrapper.
Extend concept of false paths to apply to inputs as well, use the rewrites implemented in TaskPathRewriter.
Affected #: 2 files
diff -r c816b894910f9c82b3ec525b6394a4f6e64186f7 -r 55cf1bd3df0ecfa058768e47dd39b9e89f619e6e lib/galaxy/jobs/__init__.py
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -731,8 +731,9 @@
# Build params, done before hook so hook can use
param_dict = self.tool.build_param_dict( incoming,
inp_data, out_data,
- self.get_output_fnames(),
- self.working_directory )
+ output_paths=self.get_output_fnames(),
+ job_working_directory=self.working_directory,
+ input_paths=self.get_input_paths( job ) )
# Certain tools require tasks to be completed prior to job execution
# ( this used to be performed in the "exec_before_job" hook, but hooks are deprecated ).
@@ -1165,6 +1166,18 @@
filenames.extend(self.get_input_dataset_fnames(da.dataset))
return filenames
+ def get_input_paths( self, job=None ):
+ if job is None:
+ job = self.get_job()
+ paths = []
+ for da in job.input_datasets + job.input_library_datasets: # da is JobToInputDatasetAssociation object
+ if da.dataset:
+ filenames = self.get_input_dataset_fnames(da.dataset)
+ for real_path in filenames:
+ false_path = self.dataset_path_rewriter.rewrite_dataset_path( da.dataset, 'input' )
+ paths.append( DatasetPath( da.id, real_path=real_path, false_path=false_path, mutable=False ) )
+ return paths
+
def get_output_fnames( self ):
if self.output_paths is None:
self.compute_outputs()
@@ -1420,30 +1433,15 @@
param_dict = self._build_param_dict( job, populate_special_output_file=False )
- # Build dict of file name re-writes for split up tasks.
- fnames = {}
- for v in self.get_input_fnames():
- fnames[v] = os.path.join(self.working_directory, os.path.basename(v))
- #for dp in [x.real_path for x in self.get_output_fnames()]:
- # fnames[dp] = os.path.join(self.working_directory, os.path.basename(dp))
-
self.sa_session.flush()
# Build any required config files
config_filenames = self.tool.build_config_files( param_dict, self.working_directory )
- for config_filename in config_filenames:
- config_contents = open(config_filename, "r").read()
- for k, v in fnames.iteritems():
- config_contents = config_contents.replace(k, v)
- open(config_filename, "w").write(config_contents)
# FIXME: Build the param file (might return None, DEPRECATED)
param_filename = self.tool.build_param_file( param_dict, self.working_directory )
# Build the job's command line
self.command_line = self.tool.build_command_line( param_dict )
- # HACK, Fix this when refactored.
- for k, v in fnames.iteritems():
- self.command_line = self.command_line.replace(k, v)
# FIXME: for now, tools get Galaxy's lib dir in their path
if self.command_line and self.command_line.startswith( 'python' ):
self.galaxy_lib_dir = os.path.abspath( "lib" ) # cwd = galaxy root
diff -r c816b894910f9c82b3ec525b6394a4f6e64186f7 -r 55cf1bd3df0ecfa058768e47dd39b9e89f619e6e lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -2551,7 +2551,7 @@
message = e.message
return message
- def build_param_dict( self, incoming, input_datasets, output_datasets, output_paths, job_working_directory ):
+ def build_param_dict( self, incoming, input_datasets, output_datasets, output_paths, job_working_directory, input_paths=[] ):
"""
Build the dictionary of parameters for substituting into the command
line. Each value is wrapped in a `InputValueWrapper`, which allows
@@ -2564,6 +2564,8 @@
# All parameters go into the param_dict
param_dict.update( incoming )
+ input_false_paths = dict( [ ( dp.real_path, dp.false_path ) for dp in input_paths if getattr( dp, "false_path", None ) ] )
+
def wrap_values( inputs, input_values ):
"""
Wraps parameters as neccesary.
@@ -2579,6 +2581,7 @@
elif isinstance( input, DataToolParameter ) and input.multiple:
input_values[ input.name ] = \
DatasetListWrapper( input_values[ input.name ],
+ false_paths=input_false_paths,
datatypes_registry = self.app.datatypes_registry,
tool = self,
name = input.name )
@@ -2620,6 +2623,9 @@
tool=self,
name=input.name
)
+ real_path = dataset.file_name
+ if real_path in input_false_paths:
+ wrapper_kwds[ "false_path" ] = input_false_paths[ real_path ]
input_values[ input.name ] = \
DatasetFilenameWrapper( dataset, **wrapper_kwds )
elif isinstance( input, SelectToolParameter ):
@@ -2627,6 +2633,7 @@
input, input_values[ input.name ], self.app, other_values = param_dict )
elif isinstance( input, LibraryDatasetToolParameter ):
+ # TODO: Handle input rewrites in here? How to test LibraryDatasetToolParameters?
input_values[ input.name ] = LibraryDatasetValueWrapper(
input, input_values[ input.name ], param_dict )
@@ -2665,6 +2672,10 @@
tool=self,
name=name,
)
+ real_path = data.file_name
+ if real_path in input_false_paths:
+ false_path = input_false_paths[ real_path ]
+ wrapper_kwds[ 'false_path' ] = false_path
param_dict[name] = DatasetFilenameWrapper( data, **wrapper_kwds )
if data:
for child in data.children:
@@ -3575,12 +3586,15 @@
class DatasetListWrapper( list ):
"""
"""
- def __init__( self, datasets, **kwargs ):
+ def __init__( self, datasets, false_paths=[], **kwargs ):
if not isinstance(datasets, list):
datasets = [datasets]
def to_wrapper( dataset ):
+ real_path = dataset.file_name
wrapper_kwds = kwargs.copy()
+ if real_path in false_paths:
+ wrapper_kwds[ "false_path" ] = false_paths[ real_path ]
return DatasetFilenameWrapper( dataset, **wrapper_kwds )
list.__init__( self, map( to_wrapper, datasets ) )
https://bitbucket.org/galaxy/galaxy-central/commits/e6f90d86a8eb/
Changeset: e6f90d86a8eb
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Move cheetah wrappers into own module...
For clarity - more work toward reduces tools/__init__.py to a more managable size. This also includes an initial suite of test cases for these wrappers - testing simple select wrapper, select wrapper with file options, select wrapper with drilldown widget, raw object wrapper, input value wrapper, and the dataset file name wrapper with and without false paths.
Affected #: 3 files
diff -r 55cf1bd3df0ecfa058768e47dd39b9e89f619e6e -r e6f90d86a8eb3f3e48d7bdb6ab801aee22027b33 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -65,6 +65,15 @@
from galaxy.model import Workflow
from tool_shed.util import shed_util_common
from .loader import load_tool, template_macro_params
+from .wrappers import (
+ ToolParameterValueWrapper,
+ RawObjectWrapper,
+ LibraryDatasetValueWrapper,
+ InputValueWrapper,
+ SelectToolParameterWrapper,
+ DatasetFilenameWrapper,
+ DatasetListWrapper,
+)
log = logging.getLogger( __name__ )
@@ -3422,183 +3431,6 @@
self.error_level = "fatal"
self.desc = ""
-class ToolParameterValueWrapper( object ):
- """
- Base class for object that Wraps a Tool Parameter and Value.
- """
- def __nonzero__( self ):
- return bool( self.value )
- def get_display_text( self, quote=True ):
- """
- Returns a string containing the value that would be displayed to the user in the tool interface.
- When quote is True (default), the string is escaped for e.g. command-line usage.
- """
- rval = self.input.value_to_display_text( self.value, self.input.tool.app ) or ''
- if quote:
- return pipes.quote( rval ) or "''" #pipes.quote in Python < 2.7 returns an empty string instead of the expected quoted empty string
- return rval
-
-class RawObjectWrapper( ToolParameterValueWrapper ):
- """
- Wraps an object so that __str__ returns module_name:class_name.
- """
- def __init__( self, obj ):
- self.obj = obj
- def __nonzero__( self ):
- return bool( self.obj ) #FIXME: would it be safe/backwards compatible to rename .obj to .value, so that we can just inherit this method?
- def __str__( self ):
- try:
- return "%s:%s" % (self.obj.__module__, self.obj.__class__.__name__)
- except:
- #Most likely None, which lacks __module__.
- return str( self.obj )
- def __getattr__( self, key ):
- return getattr( self.obj, key )
-
-class LibraryDatasetValueWrapper( ToolParameterValueWrapper ):
- """
- Wraps an input so that __str__ gives the "param_dict" representation.
- """
- def __init__( self, input, value, other_values={} ):
- self.input = input
- self.value = value
- self._other_values = other_values
- self.counter = 0
- def __str__( self ):
- return self.value
- def __iter__( self ):
- return self
- def next( self ):
- if self.counter >= len(self.value):
- raise StopIteration
- self.counter += 1
- return self.value[self.counter-1]
- def __getattr__( self, key ):
- return getattr( self.value, key )
-
-class InputValueWrapper( ToolParameterValueWrapper ):
- """
- Wraps an input so that __str__ gives the "param_dict" representation.
- """
- def __init__( self, input, value, other_values={} ):
- self.input = input
- self.value = value
- self._other_values = other_values
- def __str__( self ):
- return self.input.to_param_dict_string( self.value, self._other_values )
- def __getattr__( self, key ):
- return getattr( self.value, key )
-
-class SelectToolParameterWrapper( ToolParameterValueWrapper ):
- """
- Wraps a SelectTooParameter so that __str__ returns the selected value, but all other
- attributes are accessible.
- """
-
- class SelectToolParameterFieldWrapper:
- """
- Provide access to any field by name or index for this particular value.
- Only applicable for dynamic_options selects, which have more than simple 'options' defined (name, value, selected).
- """
- def __init__( self, input, value, other_values ):
- self._input = input
- self._value = value
- self._other_values = other_values
- self._fields = {}
- def __getattr__( self, name ):
- if name not in self._fields:
- self._fields[ name ] = self._input.options.get_field_by_name_for_value( name, self._value, None, self._other_values )
- return self._input.separator.join( map( str, self._fields[ name ] ) )
-
- def __init__( self, input, value, app, other_values={} ):
- self.input = input
- self.value = value
- self.input.value_label = input.value_to_display_text( value, app )
- self._other_values = other_values
- self.fields = self.SelectToolParameterFieldWrapper( input, value, other_values )
- def __str__( self ):
- return self.input.to_param_dict_string( self.value, other_values = self._other_values )
- def __getattr__( self, key ):
- return getattr( self.input, key )
-
-class DatasetFilenameWrapper( ToolParameterValueWrapper ):
- """
- Wraps a dataset so that __str__ returns the filename, but all other
- attributes are accessible.
- """
-
- class MetadataWrapper:
- """
- Wraps a Metadata Collection to return MetadataParameters wrapped
- according to the metadata spec. Methods implemented to match behavior
- of a Metadata Collection.
- """
- def __init__( self, metadata ):
- self.metadata = metadata
- def __getattr__( self, name ):
- rval = self.metadata.get( name, None )
- if name in self.metadata.spec:
- if rval is None:
- rval = self.metadata.spec[name].no_value
- rval = self.metadata.spec[name].param.to_string( rval )
- # Store this value, so we don't need to recalculate if needed
- # again
- setattr( self, name, rval )
- return rval
- def __nonzero__( self ):
- return self.metadata.__nonzero__()
- def __iter__( self ):
- return self.metadata.__iter__()
- def get( self, key, default=None ):
- try:
- return getattr( self, key )
- except:
- return default
- def items( self ):
- return iter( [ ( k, self.get( k ) ) for k, v in self.metadata.items() ] )
-
- def __init__( self, dataset, datatypes_registry = None, tool = None, name = None, false_path = None ):
- if not dataset:
- try:
- # TODO: allow this to work when working with grouping
- ext = tool.inputs[name].extensions[0]
- except:
- ext = 'data'
- self.dataset = NoneDataset( datatypes_registry = datatypes_registry, ext = ext )
- else:
- self.dataset = dataset
- self.metadata = self.MetadataWrapper( dataset.metadata )
- self.false_path = false_path
-
- def __str__( self ):
- if self.false_path is not None:
- return self.false_path
- else:
- return self.dataset.file_name
- def __getattr__( self, key ):
- if self.false_path is not None and key == 'file_name':
- return self.false_path
- else:
- return getattr( self.dataset, key )
- def __nonzero__( self ):
- return bool( self.dataset )
-
-class DatasetListWrapper( list ):
- """
- """
- def __init__( self, datasets, false_paths=[], **kwargs ):
- if not isinstance(datasets, list):
- datasets = [datasets]
-
- def to_wrapper( dataset ):
- real_path = dataset.file_name
- wrapper_kwds = kwargs.copy()
- if real_path in false_paths:
- wrapper_kwds[ "false_path" ] = false_paths[ real_path ]
- return DatasetFilenameWrapper( dataset, **wrapper_kwds )
-
- list.__init__( self, map( to_wrapper, datasets ) )
-
def json_fix( val ):
if isinstance( val, list ):
diff -r 55cf1bd3df0ecfa058768e47dd39b9e89f619e6e -r e6f90d86a8eb3f3e48d7bdb6ab801aee22027b33 lib/galaxy/tools/wrappers.py
--- /dev/null
+++ b/lib/galaxy/tools/wrappers.py
@@ -0,0 +1,207 @@
+import pipes
+from galaxy.util.none_like import NoneDataset
+
+
+class ToolParameterValueWrapper( object ):
+ """
+ Base class for object that Wraps a Tool Parameter and Value.
+ """
+
+ def __nonzero__( self ):
+ return bool( self.value )
+
+ def get_display_text( self, quote=True ):
+ """
+ Returns a string containing the value that would be displayed to the user in the tool interface.
+ When quote is True (default), the string is escaped for e.g. command-line usage.
+ """
+ rval = self.input.value_to_display_text( self.value, self.input.tool.app ) or ''
+ if quote:
+ return pipes.quote( rval ) or "''" # pipes.quote in Python < 2.7 returns an empty string instead of the expected quoted empty string
+ return rval
+
+
+class RawObjectWrapper( ToolParameterValueWrapper ):
+ """
+ Wraps an object so that __str__ returns module_name:class_name.
+ """
+ def __init__( self, obj ):
+ self.obj = obj
+
+ def __nonzero__( self ):
+ return bool( self.obj ) # FIXME: would it be safe/backwards compatible to rename .obj to .value, so that we can just inherit this method?
+
+ def __str__( self ):
+ try:
+ return "%s:%s" % (self.obj.__module__, self.obj.__class__.__name__)
+ except:
+ #Most likely None, which lacks __module__.
+ return str( self.obj )
+
+ def __getattr__( self, key ):
+ return getattr( self.obj, key )
+
+
+class LibraryDatasetValueWrapper( ToolParameterValueWrapper ):
+ """
+ Wraps an input so that __str__ gives the "param_dict" representation.
+ """
+ def __init__( self, input, value, other_values={} ):
+ self.input = input
+ self.value = value
+ self._other_values = other_values
+ self.counter = 0
+
+ def __str__( self ):
+ return self.value
+
+ def __iter__( self ):
+ return self
+
+ def next( self ):
+ if self.counter >= len(self.value):
+ raise StopIteration
+ self.counter += 1
+ return self.value[ self.counter - 1 ]
+
+ def __getattr__( self, key ):
+ return getattr( self.value, key )
+
+
+class InputValueWrapper( ToolParameterValueWrapper ):
+ """
+ Wraps an input so that __str__ gives the "param_dict" representation.
+ """
+ def __init__( self, input, value, other_values={} ):
+ self.input = input
+ self.value = value
+ self._other_values = other_values
+
+ def __str__( self ):
+ return self.input.to_param_dict_string( self.value, self._other_values )
+
+ def __getattr__( self, key ):
+ return getattr( self.value, key )
+
+
+class SelectToolParameterWrapper( ToolParameterValueWrapper ):
+ """
+ Wraps a SelectTooParameter so that __str__ returns the selected value, but all other
+ attributes are accessible.
+ """
+
+ class SelectToolParameterFieldWrapper:
+ """
+ Provide access to any field by name or index for this particular value.
+ Only applicable for dynamic_options selects, which have more than simple 'options' defined (name, value, selected).
+ """
+ def __init__( self, input, value, other_values ):
+ self._input = input
+ self._value = value
+ self._other_values = other_values
+ self._fields = {}
+
+ def __getattr__( self, name ):
+ if name not in self._fields:
+ self._fields[ name ] = self._input.options.get_field_by_name_for_value( name, self._value, None, self._other_values )
+ return self._input.separator.join( map( str, self._fields[ name ] ) )
+
+ def __init__( self, input, value, app, other_values={} ):
+ self.input = input
+ self.value = value
+ self.input.value_label = input.value_to_display_text( value, app )
+ self._other_values = other_values
+ self.fields = self.SelectToolParameterFieldWrapper( input, value, other_values )
+
+ def __str__( self ):
+ return self.input.to_param_dict_string( self.value, other_values=self._other_values )
+
+ def __getattr__( self, key ):
+ return getattr( self.input, key )
+
+
+class DatasetFilenameWrapper( ToolParameterValueWrapper ):
+ """
+ Wraps a dataset so that __str__ returns the filename, but all other
+ attributes are accessible.
+ """
+
+ class MetadataWrapper:
+ """
+ Wraps a Metadata Collection to return MetadataParameters wrapped
+ according to the metadata spec. Methods implemented to match behavior
+ of a Metadata Collection.
+ """
+ def __init__( self, metadata ):
+ self.metadata = metadata
+
+ def __getattr__( self, name ):
+ rval = self.metadata.get( name, None )
+ if name in self.metadata.spec:
+ if rval is None:
+ rval = self.metadata.spec[name].no_value
+ rval = self.metadata.spec[name].param.to_string( rval )
+ # Store this value, so we don't need to recalculate if needed
+ # again
+ setattr( self, name, rval )
+ return rval
+
+ def __nonzero__( self ):
+ return self.metadata.__nonzero__()
+
+ def __iter__( self ):
+ return self.metadata.__iter__()
+
+ def get( self, key, default=None ):
+ try:
+ return getattr( self, key )
+ except:
+ return default
+
+ def items( self ):
+ return iter( [ ( k, self.get( k ) ) for k, v in self.metadata.items() ] )
+
+ def __init__( self, dataset, datatypes_registry=None, tool=None, name=None, false_path=None ):
+ if not dataset:
+ try:
+ # TODO: allow this to work when working with grouping
+ ext = tool.inputs[name].extensions[0]
+ except:
+ ext = 'data'
+ self.dataset = NoneDataset( datatypes_registry=datatypes_registry, ext=ext )
+ else:
+ self.dataset = dataset
+ self.metadata = self.MetadataWrapper( dataset.metadata )
+ self.false_path = false_path
+
+ def __str__( self ):
+ if self.false_path is not None:
+ return self.false_path
+ else:
+ return self.dataset.file_name
+
+ def __getattr__( self, key ):
+ if self.false_path is not None and key == 'file_name':
+ return self.false_path
+ else:
+ return getattr( self.dataset, key )
+
+ def __nonzero__( self ):
+ return bool( self.dataset )
+
+
+class DatasetListWrapper( list ):
+ """
+ """
+ def __init__( self, datasets, false_paths=[], **kwargs ):
+ if not isinstance(datasets, list):
+ datasets = [datasets]
+
+ def to_wrapper( dataset ):
+ real_path = dataset.file_name
+ wrapper_kwds = kwargs.copy()
+ if real_path in false_paths:
+ wrapper_kwds[ "false_path" ] = false_paths[ real_path ]
+ return DatasetFilenameWrapper( dataset, **wrapper_kwds )
+
+ list.__init__( self, map( to_wrapper, datasets ) )
diff -r 55cf1bd3df0ecfa058768e47dd39b9e89f619e6e -r e6f90d86a8eb3f3e48d7bdb6ab801aee22027b33 test/unit/tools/test_wrappers.py
--- /dev/null
+++ b/test/unit/tools/test_wrappers.py
@@ -0,0 +1,155 @@
+import tempfile
+import os
+
+from galaxy.tools.parameters.basic import SelectToolParameter
+from galaxy.tools.parameters.basic import DrillDownSelectToolParameter
+from galaxy.tools.parameters.basic import IntegerToolParameter
+from galaxy.tools.wrappers import RawObjectWrapper
+from galaxy.tools.wrappers import SelectToolParameterWrapper
+from galaxy.tools.wrappers import InputValueWrapper
+from galaxy.tools.wrappers import DatasetFilenameWrapper
+from galaxy.util.bunch import Bunch
+from elementtree.ElementTree import XML
+from galaxy.datatypes.metadata import MetadataSpecCollection
+
+
+def with_mock_tool(func):
+ def call():
+ test_directory = tempfile.mkdtemp()
+ app = MockApp(test_directory)
+ tool = MockTool(app)
+ return func(tool)
+ call.__name__ = func.__name__
+ return call
+
+
+@with_mock_tool
+def test_select_wrapper_simple_options(tool):
+ xml = XML('''<param name="blah" type="select">
+ <option value="x">I am X</option>
+ <option value="y" selected="true">I am Y</option>
+ <option value="z">I am Z</option>
+ </param>''')
+ parameter = SelectToolParameter( tool, xml )
+ wrapper = SelectToolParameterWrapper( parameter, "x", tool.app )
+ assert str(wrapper) == "x"
+ assert wrapper.name == "blah"
+ assert wrapper.value_label == "I am X"
+
+
+@with_mock_tool
+def test_select_wrapper_with_drilldown(tool):
+ parameter = _drilldown_parameter(tool)
+ wrapper = SelectToolParameterWrapper( parameter, ["option3"], tool.app )
+ assert str(wrapper) == "option3", str(wrapper)
+
+
+@with_mock_tool
+def test_select_wrapper_option_file(tool):
+ parameter = _setup_blast_tool(tool)
+ wrapper = SelectToolParameterWrapper( parameter, "val2", tool.app )
+ assert str(wrapper) == "val2"
+ assert wrapper.fields.name == "name2"
+ assert wrapper.fields.path == "path2"
+
+
+@with_mock_tool
+def test_select_wrapper_multiple(tool):
+ parameter = _setup_blast_tool(tool, multiple=True)
+ wrapper = SelectToolParameterWrapper( parameter, ["val1", "val2"], tool.app )
+ assert str(wrapper) == "val1,val2"
+ assert wrapper.fields.name == "name1,name2"
+
+
+def test_raw_object_wrapper():
+ obj = Bunch(x=4)
+ wrapper = RawObjectWrapper(obj)
+ assert wrapper.x == 4
+ assert wrapper
+
+ false_wrapper = RawObjectWrapper(False)
+ assert not false_wrapper
+
+
+@with_mock_tool
+def test_input_value_wrapper(tool):
+ parameter = IntegerToolParameter( tool, XML( '<param name="blah" type="integer" size="4" value="10" min="0" />' ) )
+ wrapper = InputValueWrapper( parameter, "5" )
+ assert str( wrapper ) == "5"
+
+
+def test_dataset_wrapper():
+ dataset = MockDataset()
+ wrapper = DatasetFilenameWrapper(dataset)
+ assert str( wrapper ) == MOCK_DATASET_PATH
+ assert wrapper.file_name == MOCK_DATASET_PATH
+
+ assert wrapper.ext == MOCK_DATASET_EXT
+
+
+def test_dataset_wrapper_false_path():
+ dataset = MockDataset()
+ new_path = "/new/path/dataset_123.dat"
+ wrapper = DatasetFilenameWrapper(dataset, false_path=new_path)
+ assert str( wrapper ) == new_path
+ assert wrapper.file_name == new_path
+
+
+def _drilldown_parameter(tool):
+ xml = XML( '''<param name="some_name" type="drill_down" display="checkbox" hierarchy="recurse" multiple="true">
+ <options>
+ <option name="Heading 1" value="heading1">
+ <option name="Option 1" value="option1"/>
+ <option name="Option 2" value="option2"/>
+ <option name="Heading 1" value="heading1">
+ <option name="Option 3" value="option3"/>
+ <option name="Option 4" value="option4"/>
+ </option>
+ </option>
+ <option name="Option 5" value="option5"/>
+ </options>
+ </param>''' )
+ parameter = DrillDownSelectToolParameter( tool, xml )
+ return parameter
+
+
+def _setup_blast_tool(tool, multiple=False):
+ tool.app.write_test_tool_data("blastdb.loc", "val1\tname1\tpath1\nval2\tname2\tpath2\n")
+ xml = XML( '''<param name="database" type="select" label="Nucleotide BLAST database" multiple="%s">
+ <options from_file="blastdb.loc">
+ <column name="value" index="0"/>
+ <column name="name" index="1"/>
+ <column name="path" index="2"/>
+ </options>
+ </param>''' % multiple )
+ parameter = SelectToolParameter( tool, xml )
+ return parameter
+
+
+MOCK_DATASET_PATH = "/galaxy/database/files/001/dataset_123.dat"
+MOCK_DATASET_EXT = "bam"
+
+
+class MockDataset(object):
+
+ def __init__(self):
+ self.metadata = MetadataSpecCollection({})
+ self.file_name = MOCK_DATASET_PATH
+ self.ext = MOCK_DATASET_EXT
+
+
+class MockTool(object):
+
+ def __init__(self, app):
+ self.app = app
+ self.options = Bunch(sanitize=False)
+
+
+class MockApp(object):
+
+ def __init__(self, test_directory):
+ self.config = Bunch(tool_data_path=test_directory)
+
+ def write_test_tool_data(self, name, contents):
+ path = os.path.join(self.config.tool_data_path, name)
+ open(path, "w").write(contents)
https://bitbucket.org/galaxy/galaxy-central/commits/e4cea1f8ef6e/
Changeset: e4cea1f8ef6e
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Rework interface between jobs and tools.
Pull code out of job wrapper and tool for building and evaluating against template environments and move them into a new ToolEvaluator class (in galaxy/tools/evalution.py). Introduce an abstraction (ComputeEnvironment) for various paths that get evaluated that may be different on a remote server (inputs, outputs, working directory, tools and config directory) and evaluate the template against an instance of this class. Created a default instance of this class (SharedComputeEnvironment). The idea will be that the LWR should be able to an LwrComputeEnvironment and send this to the JobWrapper when building up job inputs - nothing in this commit is LWR specific though so other runners should be able to remotely stage jobs using other mechanisms as well.
This commit adds extensive unit tests of this tool evaluation - testing many different branches through the code, with and without path rewriting, testing job hooks, config files, testing the cheetah evaluation of simple parameters, conditionals, repeats, and non-job stuff like $__app__ and $__root_dir__. As well as a new test case class for JobWrapper and TaskWrapper - though this just tests the relevant portions of that class - namely prepare and version handling.
Affected #: 6 files
diff -r e6f90d86a8eb3f3e48d7bdb6ab801aee22027b33 -r e4cea1f8ef6e1bb02ee2746dddc9da5dc2531165 lib/galaxy/jobs/__init__.py
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -1,6 +1,8 @@
"""
Support for running a tool in Galaxy via an internal job management system
"""
+from abc import ABCMeta
+from abc import abstractmethod
import time
import copy
@@ -25,6 +27,7 @@
from galaxy.util.expressions import ExpressionContext
from galaxy.util.json import from_json_string
from galaxy.util import unicodify
+
from .output_checker import check_output
from .datasets import TaskPathRewriter
from .datasets import OutputsToWorkingDirectoryPathRewriter
@@ -649,7 +652,7 @@
def get_version_string_path( self ):
return os.path.abspath(os.path.join(self.app.config.new_file_path, "GALAXY_VERSION_STRING_%s" % self.job_id))
- def prepare( self ):
+ def prepare( self, compute_environment=None ):
"""
Prepare the job to run by creating the working directory and the
config files.
@@ -661,15 +664,19 @@
job = self._load_job()
- param_dict = self._build_param_dict( job, populate_special_output_file=True )
+ def get_special( ):
+ special = self.sa_session.query( model.JobExportHistoryArchive ).filter_by( job=job ).first()
+ if not special:
+ special = self.sa_session.query( model.GenomeIndexToolData ).filter_by( job=job ).first()
+ return special
+
+ tool_evaluator = self._get_tool_evaluator( job )
+ compute_environment = compute_environment or self.default_compute_environment( job )
+ tool_evaluator.set_compute_environment( compute_environment, get_special=get_special )
self.sa_session.flush()
- # Build any required config files
- config_filenames = self.tool.build_config_files( param_dict, self.working_directory )
- # FIXME: Build the param file (might return None, DEPRECATED)
- param_filename = self.tool.build_param_file( param_dict, self.working_directory )
- # Build the job's command line
- self.command_line = self.tool.build_command_line( param_dict )
+
+ self.command_line, self.extra_filenames = tool_evaluator.build()
# FIXME: for now, tools get Galaxy's lib dir in their path
if self.command_line and self.command_line.startswith( 'python' ):
self.galaxy_lib_dir = os.path.abspath( "lib" ) # cwd = galaxy root
@@ -681,13 +688,14 @@
self.sa_session.add( job )
self.sa_session.flush()
# Return list of all extra files
- extra_filenames = config_filenames
- if param_filename is not None:
- extra_filenames.append( param_filename )
- self.param_dict = param_dict
- self.extra_filenames = extra_filenames
+ self.param_dict = tool_evaluator.param_dict
self.version_string_cmd = self.tool.version_string_cmd
- return extra_filenames
+ return self.extra_filenames
+
+ def default_compute_environment( self, job=None ):
+ if not job:
+ job = self.get_job()
+ return SharedComputeEnvironment( self, job )
def _load_job( self ):
# Load job from database and verify it has user or session.
@@ -697,52 +705,19 @@
raise Exception( 'Job %s has no user and no session.' % job.id )
return job
- def _build_param_dict( self, job, populate_special_output_file=True ):
- incoming = dict( [ ( p.name, p.value ) for p in job.parameters ] )
- incoming = self.tool.params_from_strings( incoming, self.app )
- # Do any validation that could not be done at job creation
- self.tool.handle_unvalidated_param_values( incoming, self.app )
- # Restore input / output data lists
- inp_data = dict( [ ( da.name, da.dataset ) for da in job.input_datasets ] )
- out_data = dict( [ ( da.name, da.dataset ) for da in job.output_datasets ] )
- inp_data.update( [ ( da.name, da.dataset ) for da in job.input_library_datasets ] )
- out_data.update( [ ( da.name, da.dataset ) for da in job.output_library_datasets ] )
+ def _get_tool_evaluator( self, job ):
+ # Hacky way to avoid cirular import for now.
+ # Placing ToolEvaluator in either jobs or tools
+ # result in ciruclar dependency.
+ from galaxy.tools.evaluation import ToolEvaluator
- if populate_special_output_file:
- # Set up output dataset association for export history jobs. Because job
- # uses a Dataset rather than an HDA or LDA, it's necessary to set up a
- # fake dataset association that provides the needed attributes for
- # preparing a job.
- class FakeDatasetAssociation ( object ):
- def __init__( self, dataset=None ):
- self.dataset = dataset
- self.file_name = dataset.file_name
- self.metadata = dict()
- self.children = []
- special = self.sa_session.query( model.JobExportHistoryArchive ).filter_by( job=job ).first()
- if not special:
- special = self.sa_session.query( model.GenomeIndexToolData ).filter_by( job=job ).first()
- if special:
- out_data[ "output_file" ] = FakeDatasetAssociation( dataset=special.dataset )
-
- # These can be passed on the command line if wanted as $__user_*__
- incoming.update( model.User.user_template_environment( job.history and job.history.user ) )
-
- # Build params, done before hook so hook can use
- param_dict = self.tool.build_param_dict( incoming,
- inp_data, out_data,
- output_paths=self.get_output_fnames(),
- job_working_directory=self.working_directory,
- input_paths=self.get_input_paths( job ) )
-
- # Certain tools require tasks to be completed prior to job execution
- # ( this used to be performed in the "exec_before_job" hook, but hooks are deprecated ).
- self.tool.exec_before_job( self.queue.app, inp_data, out_data, param_dict )
- # Run the before queue ("exec_before_job") hook
- self.tool.call_hook( 'exec_before_job', self.queue.app, inp_data=inp_data,
- out_data=out_data, tool=self.tool, param_dict=incoming)
-
- return param_dict
+ tool_evaluator = ToolEvaluator(
+ app=self.app,
+ job=job,
+ tool=self.tool,
+ local_working_directory=self.working_directory,
+ )
+ return tool_evaluator
def fail( self, message, exception=False, stdout="", stderr="", exit_code=None ):
"""
@@ -1420,7 +1395,7 @@
param_dict = self.tool.params_from_strings( param_dict, self.app )
return param_dict
- def prepare( self ):
+ def prepare( self, compute_environment=None ):
"""
Prepare the job to run by creating the working directory and the
config files.
@@ -1431,17 +1406,14 @@
# DBTODO New method for generating command line for a task?
- param_dict = self._build_param_dict( job, populate_special_output_file=False )
+ tool_evaluator = self._get_tool_evaluator( job )
+ compute_environment = compute_environment or self.default_compute_environment( job )
+ tool_evaluator.set_compute_environment( compute_environment )
self.sa_session.flush()
- # Build any required config files
- config_filenames = self.tool.build_config_files( param_dict, self.working_directory )
+ self.command_line, self.extra_filenames = tool_evaluator.build()
- # FIXME: Build the param file (might return None, DEPRECATED)
- param_filename = self.tool.build_param_file( param_dict, self.working_directory )
- # Build the job's command line
- self.command_line = self.tool.build_command_line( param_dict )
# FIXME: for now, tools get Galaxy's lib dir in their path
if self.command_line and self.command_line.startswith( 'python' ):
self.galaxy_lib_dir = os.path.abspath( "lib" ) # cwd = galaxy root
@@ -1452,14 +1424,10 @@
task.command_line = self.command_line
self.sa_session.add( task )
self.sa_session.flush()
- # # Return list of all extra files
- extra_filenames = config_filenames
- if param_filename is not None:
- extra_filenames.append( param_filename )
- self.param_dict = param_dict
- self.extra_filenames = extra_filenames
+
+ self.param_dict = tool_evaluator.param_dict
self.status = 'prepared'
- return extra_filenames
+ return self.extra_filenames
def fail( self, message, exception=False ):
log.error("TaskWrapper Failure %s" % message)
@@ -1576,6 +1544,71 @@
return os.path.join( self.working_directory, os.path.basename( output_path ) )
+class ComputeEnvironment( object ):
+ """ Definition of the job as it will be run on the (potentially) remote
+ compute server.
+ """
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def output_paths( self ):
+ """ Output DatasetPaths defined by job. """
+
+ @abstractmethod
+ def input_paths( self ):
+ """ Input DatasetPaths defined by job. """
+
+ @abstractmethod
+ def working_directory( self ):
+ """ Job working directory (potentially remote) """
+
+ @abstractmethod
+ def config_directory( self ):
+ """ Directory containing config files (potentially remote) """
+
+ @abstractmethod
+ def sep( self ):
+ """ os.path.sep for the platform this job will execute in.
+ """
+
+ @abstractmethod
+ def new_file_path( self ):
+ """ Location to dump new files for this job on remote server. """
+
+
+class SimpleComputeEnvironment( object ):
+
+ def config_directory( self ):
+ return self.working_directory( )
+
+ def sep( self ):
+ return os.path.sep
+
+
+class SharedComputeEnvironment( SimpleComputeEnvironment ):
+ """ Default ComputeEnviornment for job and task wrapper to pass
+ to ToolEvaluator - valid when Galaxy and compute share all the relevant
+ file systems.
+ """
+
+ def __init__( self, job_wrapper, job ):
+ self.app = job_wrapper.app
+ self.job_wrapper = job_wrapper
+ self.job = job
+
+ def output_paths( self ):
+ return self.job_wrapper.get_output_fnames()
+
+ def input_paths( self ):
+ return self.job_wrapper.get_input_paths( self.job )
+
+ def working_directory( self ):
+ return self.job_wrapper.working_directory
+
+ def new_file_path( self ):
+ return os.path.abspath( self.app.config.new_file_path )
+
+
class NoopQueue( object ):
"""
Implements the JobQueue / JobStopQueue interface but does nothing
diff -r e6f90d86a8eb3f3e48d7bdb6ab801aee22027b33 -r e4cea1f8ef6e1bb02ee2746dddc9da5dc2531165 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -2560,254 +2560,6 @@
message = e.message
return message
- def build_param_dict( self, incoming, input_datasets, output_datasets, output_paths, job_working_directory, input_paths=[] ):
- """
- Build the dictionary of parameters for substituting into the command
- line. Each value is wrapped in a `InputValueWrapper`, which allows
- all the attributes of the value to be used in the template, *but*
- when the __str__ method is called it actually calls the
- `to_param_dict_string` method of the associated input.
- """
- param_dict = dict()
- param_dict.update(self.template_macro_params)
- # All parameters go into the param_dict
- param_dict.update( incoming )
-
- input_false_paths = dict( [ ( dp.real_path, dp.false_path ) for dp in input_paths if getattr( dp, "false_path", None ) ] )
-
- def wrap_values( inputs, input_values ):
- """
- Wraps parameters as neccesary.
- """
- for input in inputs.itervalues():
- if isinstance( input, Repeat ):
- for d in input_values[ input.name ]:
- wrap_values( input.inputs, d )
- elif isinstance( input, Conditional ):
- values = input_values[ input.name ]
- current = values["__current_case__"]
- wrap_values( input.cases[current].inputs, values )
- elif isinstance( input, DataToolParameter ) and input.multiple:
- input_values[ input.name ] = \
- DatasetListWrapper( input_values[ input.name ],
- false_paths=input_false_paths,
- datatypes_registry = self.app.datatypes_registry,
- tool = self,
- name = input.name )
- elif isinstance( input, DataToolParameter ):
- ## FIXME: We're populating param_dict with conversions when
- ## wrapping values, this should happen as a separate
- ## step before wrapping (or call this wrapping step
- ## something more generic) (but iterating this same
- ## list twice would be wasteful)
- # Add explicit conversions by name to current parent
- for conversion_name, conversion_extensions, conversion_datatypes in input.conversions:
- # If we are at building cmdline step, then converters
- # have already executed
- conv_ext, converted_dataset = input_values[ input.name ].find_conversion_destination( conversion_datatypes )
- # When dealing with optional inputs, we'll provide a
- # valid extension to be used for None converted dataset
- if not conv_ext:
- conv_ext = conversion_extensions[0]
- # input_values[ input.name ] is None when optional
- # dataset, 'conversion' of optional dataset should
- # create wrapper around NoneDataset for converter output
- if input_values[ input.name ] and not converted_dataset:
- # Input that converter is based from has a value,
- # but converted dataset does not exist
- raise Exception( 'A path for explicit datatype conversion has not been found: %s --/--> %s'
- % ( input_values[ input.name ].extension, conversion_extensions ) )
- else:
- # Trick wrapper into using target conv ext (when
- # None) without actually being a tool parameter
- input_values[ conversion_name ] = \
- DatasetFilenameWrapper( converted_dataset,
- datatypes_registry = self.app.datatypes_registry,
- tool = Bunch( conversion_name = Bunch( extensions = conv_ext ) ),
- name = conversion_name )
- # Wrap actual input dataset
- dataset = input_values[ input.name ]
- wrapper_kwds = dict(
- datatypes_registry=self.app.datatypes_registry,
- tool=self,
- name=input.name
- )
- real_path = dataset.file_name
- if real_path in input_false_paths:
- wrapper_kwds[ "false_path" ] = input_false_paths[ real_path ]
- input_values[ input.name ] = \
- DatasetFilenameWrapper( dataset, **wrapper_kwds )
- elif isinstance( input, SelectToolParameter ):
- input_values[ input.name ] = SelectToolParameterWrapper(
- input, input_values[ input.name ], self.app, other_values = param_dict )
-
- elif isinstance( input, LibraryDatasetToolParameter ):
- # TODO: Handle input rewrites in here? How to test LibraryDatasetToolParameters?
- input_values[ input.name ] = LibraryDatasetValueWrapper(
- input, input_values[ input.name ], param_dict )
-
- else:
- input_values[ input.name ] = InputValueWrapper(
- input, input_values[ input.name ], param_dict )
-
- # HACK: only wrap if check_values is not false, this deals with external
- # tools where the inputs don't even get passed through. These
- # tools (e.g. UCSC) should really be handled in a special way.
- if self.check_values:
- wrap_values( self.inputs, param_dict )
-
- ## FIXME: when self.check_values==True, input datasets are being wrapped
- ## twice (above and below, creating 2 separate
- ## DatasetFilenameWrapper objects - first is overwritten by
- ## second), is this necessary? - if we get rid of this way to
- ## access children, can we stop this redundancy, or is there
- ## another reason for this?
- ## - Only necessary when self.check_values is False (==external dataset
- ## tool?: can this be abstracted out as part of being a datasouce tool?)
- ## - But we still want (ALWAYS) to wrap input datasets (this should be
- ## checked to prevent overhead of creating a new object?)
- # Additionally, datasets go in the param dict. We wrap them such that
- # if the bare variable name is used it returns the filename (for
- # backwards compatibility). We also add any child datasets to the
- # the param dict encoded as:
- # "_CHILD___{dataset_name}___{child_designation}",
- # but this should be considered DEPRECATED, instead use:
- # $dataset.get_child( 'name' ).filename
- for name, data in input_datasets.items():
- param_dict_value = param_dict.get(name, None)
- if not isinstance(param_dict_value, (DatasetFilenameWrapper, DatasetListWrapper)):
- wrapper_kwds = dict(
- datatypes_registry=self.app.datatypes_registry,
- tool=self,
- name=name,
- )
- real_path = data.file_name
- if real_path in input_false_paths:
- false_path = input_false_paths[ real_path ]
- wrapper_kwds[ 'false_path' ] = false_path
- param_dict[name] = DatasetFilenameWrapper( data, **wrapper_kwds )
- if data:
- for child in data.children:
- param_dict[ "_CHILD___%s___%s" % ( name, child.designation ) ] = DatasetFilenameWrapper( child )
- output_false_paths = dict( [ ( dp.real_path, dp.false_path ) for dp in output_paths if getattr( dp, "false_path", None ) ] )
- for name, hda in output_datasets.items():
- # Write outputs to the working directory (for security purposes)
- # if desired.
- real_path = hda.file_name
- if real_path in output_false_paths:
- false_path = output_false_paths[ real_path ]
- param_dict[name] = DatasetFilenameWrapper( hda, false_path = false_path )
- open( false_path, 'w' ).close()
- else:
- param_dict[name] = DatasetFilenameWrapper( hda )
- # Provide access to a path to store additional files
- # TODO: path munging for cluster/dataset server relocatability
- param_dict[name].files_path = os.path.abspath(os.path.join( job_working_directory, "dataset_%s_files" % (hda.dataset.id) ))
- for child in hda.children:
- param_dict[ "_CHILD___%s___%s" % ( name, child.designation ) ] = DatasetFilenameWrapper( child )
- for out_name, output in self.outputs.iteritems():
- if out_name not in param_dict and output.filters:
- # Assume the reason we lack this output is because a filter
- # failed to pass; for tool writing convienence, provide a
- # NoneDataset
- param_dict[ out_name ] = NoneDataset( datatypes_registry = self.app.datatypes_registry, ext = output.format )
-
- # -- Add useful attributes/functions for use in creating command line.
-
- # Function for querying a data table.
- def get_data_table_entry(table_name, query_attr, query_val, return_attr):
- """
- Queries and returns an entry in a data table.
- """
-
- if table_name in self.app.tool_data_tables:
- return self.app.tool_data_tables[ table_name ].get_entry( query_attr, query_val, return_attr )
-
- param_dict['__get_data_table_entry__'] = get_data_table_entry
-
- # We add access to app here, this allows access to app.config, etc
- param_dict['__app__'] = RawObjectWrapper( self.app )
- # More convienent access to app.config.new_file_path; we don't need to
- # wrap a string, but this method of generating additional datasets
- # should be considered DEPRECATED
- # TODO: path munging for cluster/dataset server relocatability
- param_dict['__new_file_path__'] = os.path.abspath(self.app.config.new_file_path)
- # The following points to location (xxx.loc) files which are pointers
- # to locally cached data
- param_dict['__tool_data_path__'] = param_dict['GALAXY_DATA_INDEX_DIR'] = self.app.config.tool_data_path
- # For the upload tool, we need to know the root directory and the
- # datatypes conf path, so we can load the datatypes registry
- param_dict['__root_dir__'] = param_dict['GALAXY_ROOT_DIR'] = os.path.abspath( self.app.config.root )
- param_dict['__datatypes_config__'] = param_dict['GALAXY_DATATYPES_CONF_FILE'] = self.app.datatypes_registry.integrated_datatypes_configs
- param_dict['__admin_users__'] = self.app.config.admin_users
- param_dict['__user__'] = RawObjectWrapper( param_dict.get( '__user__', None ) )
- # Return the dictionary of parameters
- return param_dict
- def build_param_file( self, param_dict, directory=None ):
- """
- Build temporary file for file based parameter transfer if needed
- """
- if self.command and "$param_file" in self.command:
- fd, param_filename = tempfile.mkstemp( dir=directory )
- os.close( fd )
- f = open( param_filename, "wt" )
- for key, value in param_dict.items():
- # parameters can be strings or lists of strings, coerce to list
- if type(value) != type([]):
- value = [ value ]
- for elem in value:
- f.write( '%s=%s\n' % (key, elem) )
- f.close()
- param_dict['param_file'] = param_filename
- return param_filename
- else:
- return None
- def build_config_files( self, param_dict, directory=None ):
- """
- Build temporary file for file based parameter transfer if needed
- """
- config_filenames = []
- for name, filename, template_text in self.config_files:
- # If a particular filename was forced by the config use it
- if filename is not None:
- if directory is None:
- raise Exception( "Config files with fixed filenames require a working directory" )
- config_filename = os.path.join( directory, filename )
- else:
- fd, config_filename = tempfile.mkstemp( dir=directory )
- os.close( fd )
- f = open( config_filename, "wt" )
- f.write( fill_template( template_text, context=param_dict ) )
- f.close()
- # For running jobs as the actual user, ensure the config file is globally readable
- os.chmod( config_filename, 0644 )
- param_dict[name] = config_filename
- config_filenames.append( config_filename )
- return config_filenames
- def build_command_line( self, param_dict ):
- """
- Build command line to invoke this tool given a populated param_dict
- """
- command_line = None
- if not self.command:
- return
- try:
- # Substituting parameters into the command
- command_line = fill_template( self.command, context=param_dict )
- # Remove newlines from command line, and any leading/trailing white space
- command_line = command_line.replace( "\n", " " ).replace( "\r", " " ).strip()
- except Exception:
- # Modify exception message to be more clear
- #e.args = ( 'Error substituting into command line. Params: %r, Command: %s' % ( param_dict, self.command ), )
- raise
- if self.interpreter:
- # TODO: path munging for cluster/dataset server relocatability
- executable = command_line.split()[0]
- abs_executable = os.path.abspath(os.path.join(self.tool_dir, executable))
- command_line = command_line.replace(executable, abs_executable, 1)
- command_line = self.interpreter + " " + command_line
- return command_line
-
def build_dependency_shell_commands( self ):
"""Return a list of commands to be run to populate the current environment to include this tools requirements."""
if self.tool_shed_repository:
diff -r e6f90d86a8eb3f3e48d7bdb6ab801aee22027b33 -r e4cea1f8ef6e1bb02ee2746dddc9da5dc2531165 lib/galaxy/tools/evaluation.py
--- /dev/null
+++ b/lib/galaxy/tools/evaluation.py
@@ -0,0 +1,380 @@
+import os
+import tempfile
+
+from galaxy import model
+from galaxy.util.bunch import Bunch
+from galaxy.util.none_like import NoneDataset
+from galaxy.util.template import fill_template
+from galaxy.tools.wrappers import (
+ DatasetFilenameWrapper,
+ DatasetListWrapper,
+ LibraryDatasetValueWrapper,
+ SelectToolParameterWrapper,
+ InputValueWrapper,
+ RawObjectWrapper
+)
+from galaxy.tools.parameters.basic import (
+ DataToolParameter,
+ LibraryDatasetToolParameter,
+ SelectToolParameter,
+)
+from galaxy.tools.parameters.grouping import Conditional, Repeat
+
+
+class ToolEvaluator( object ):
+ """ An abstraction linking together a tool and a job runtime to evaluate
+ tool inputs in an isolated, testable manner.
+ """
+
+ def __init__( self, app, tool, job, local_working_directory ):
+ self.app = app
+ self.job = job
+ self.tool = tool
+ self.local_working_directory = local_working_directory
+
+ def set_compute_environment( self, compute_environment, get_special=None ):
+ """
+ Setup the compute environment and established the outline of the param_dict
+ for evaluating command and config cheetah templates.
+ """
+ self.compute_environment = compute_environment
+
+ job = self.job
+ incoming = dict( [ ( p.name, p.value ) for p in job.parameters ] )
+ incoming = self.tool.params_from_strings( incoming, self.app )
+ # Do any validation that could not be done at job creation
+ self.tool.handle_unvalidated_param_values( incoming, self.app )
+ # Restore input / output data lists
+ inp_data = dict( [ ( da.name, da.dataset ) for da in job.input_datasets ] )
+ out_data = dict( [ ( da.name, da.dataset ) for da in job.output_datasets ] )
+ inp_data.update( [ ( da.name, da.dataset ) for da in job.input_library_datasets ] )
+ out_data.update( [ ( da.name, da.dataset ) for da in job.output_library_datasets ] )
+
+ if get_special:
+
+ # Set up output dataset association for export history jobs. Because job
+ # uses a Dataset rather than an HDA or LDA, it's necessary to set up a
+ # fake dataset association that provides the needed attributes for
+ # preparing a job.
+ class FakeDatasetAssociation ( object ):
+ def __init__( self, dataset=None ):
+ self.dataset = dataset
+ self.file_name = dataset.file_name
+ self.metadata = dict()
+ self.children = []
+
+ special = get_special()
+ if special:
+ out_data[ "output_file" ] = FakeDatasetAssociation( dataset=special.dataset )
+
+ # These can be passed on the command line if wanted as $__user_*__
+ incoming.update( model.User.user_template_environment( job.history and job.history.user ) )
+
+ # Build params, done before hook so hook can use
+ param_dict = self.build_param_dict(
+ incoming,
+ inp_data,
+ out_data,
+ output_paths=compute_environment.output_paths(),
+ job_working_directory=compute_environment.working_directory(),
+ input_paths=compute_environment.input_paths()
+ )
+
+ # Certain tools require tasks to be completed prior to job execution
+ # ( this used to be performed in the "exec_before_job" hook, but hooks are deprecated ).
+ self.tool.exec_before_job( self.app, inp_data, out_data, param_dict )
+ # Run the before queue ("exec_before_job") hook
+ self.tool.call_hook( 'exec_before_job', self.app, inp_data=inp_data,
+ out_data=out_data, tool=self.tool, param_dict=incoming)
+
+ self.param_dict = param_dict
+
+ def build_param_dict( self, incoming, input_datasets, output_datasets, output_paths, job_working_directory, input_paths=[] ):
+ """
+ Build the dictionary of parameters for substituting into the command
+ line. Each value is wrapped in a `InputValueWrapper`, which allows
+ all the attributes of the value to be used in the template, *but*
+ when the __str__ method is called it actually calls the
+ `to_param_dict_string` method of the associated input.
+ """
+ param_dict = dict()
+ param_dict.update(self.tool.template_macro_params)
+ # All parameters go into the param_dict
+ param_dict.update( incoming )
+
+ input_false_paths = dict( [ ( dp.real_path, dp.false_path ) for dp in input_paths if getattr( dp, "false_path", None ) ] )
+
+ def wrap_values( inputs, input_values ):
+ """
+ Wraps parameters as neccesary.
+ """
+ for input in inputs.itervalues():
+ if isinstance( input, Repeat ):
+ for d in input_values[ input.name ]:
+ wrap_values( input.inputs, d )
+ elif isinstance( input, Conditional ):
+ values = input_values[ input.name ]
+ current = values["__current_case__"]
+ wrap_values( input.cases[current].inputs, values )
+ elif isinstance( input, DataToolParameter ) and input.multiple:
+ input_values[ input.name ] = \
+ DatasetListWrapper( input_values[ input.name ],
+ false_paths=input_false_paths,
+ datatypes_registry=self.app.datatypes_registry,
+ tool=self.tool,
+ name=input.name )
+ elif isinstance( input, DataToolParameter ):
+ ## FIXME: We're populating param_dict with conversions when
+ ## wrapping values, this should happen as a separate
+ ## step before wrapping (or call this wrapping step
+ ## something more generic) (but iterating this same
+ ## list twice would be wasteful)
+ # Add explicit conversions by name to current parent
+ for conversion_name, conversion_extensions, conversion_datatypes in input.conversions:
+ # If we are at building cmdline step, then converters
+ # have already executed
+ conv_ext, converted_dataset = input_values[ input.name ].find_conversion_destination( conversion_datatypes )
+ # When dealing with optional inputs, we'll provide a
+ # valid extension to be used for None converted dataset
+ if not conv_ext:
+ conv_ext = conversion_extensions[0]
+ # input_values[ input.name ] is None when optional
+ # dataset, 'conversion' of optional dataset should
+ # create wrapper around NoneDataset for converter output
+ if input_values[ input.name ] and not converted_dataset:
+ # Input that converter is based from has a value,
+ # but converted dataset does not exist
+ raise Exception( 'A path for explicit datatype conversion has not been found: %s --/--> %s'
+ % ( input_values[ input.name ].extension, conversion_extensions ) )
+ else:
+ # Trick wrapper into using target conv ext (when
+ # None) without actually being a tool parameter
+ input_values[ conversion_name ] = \
+ DatasetFilenameWrapper( converted_dataset,
+ datatypes_registry=self.app.datatypes_registry,
+ tool=Bunch( conversion_name=Bunch( extensions=conv_ext ) ),
+ name=conversion_name )
+ # Wrap actual input dataset
+ dataset = input_values[ input.name ]
+ wrapper_kwds = dict(
+ datatypes_registry=self.app.datatypes_registry,
+ tool=self,
+ name=input.name
+ )
+ real_path = dataset.file_name
+ if real_path in input_false_paths:
+ wrapper_kwds[ "false_path" ] = input_false_paths[ real_path ]
+ input_values[ input.name ] = \
+ DatasetFilenameWrapper( dataset, **wrapper_kwds )
+ elif isinstance( input, SelectToolParameter ):
+ input_values[ input.name ] = SelectToolParameterWrapper(
+ input, input_values[ input.name ], self.app, other_values=param_dict )
+
+ elif isinstance( input, LibraryDatasetToolParameter ):
+ # TODO: Handle input rewrites in here? How to test LibraryDatasetToolParameters?
+ input_values[ input.name ] = LibraryDatasetValueWrapper(
+ input, input_values[ input.name ], param_dict )
+
+ else:
+ input_values[ input.name ] = InputValueWrapper(
+ input, input_values[ input.name ], param_dict )
+
+ # HACK: only wrap if check_values is not false, this deals with external
+ # tools where the inputs don't even get passed through. These
+ # tools (e.g. UCSC) should really be handled in a special way.
+ if self.tool.check_values:
+ wrap_values( self.tool.inputs, param_dict )
+
+ ## FIXME: when self.check_values==True, input datasets are being wrapped
+ ## twice (above and below, creating 2 separate
+ ## DatasetFilenameWrapper objects - first is overwritten by
+ ## second), is this necessary? - if we get rid of this way to
+ ## access children, can we stop this redundancy, or is there
+ ## another reason for this?
+ ## - Only necessary when self.check_values is False (==external dataset
+ ## tool?: can this be abstracted out as part of being a datasouce tool?)
+ ## - But we still want (ALWAYS) to wrap input datasets (this should be
+ ## checked to prevent overhead of creating a new object?)
+ # Additionally, datasets go in the param dict. We wrap them such that
+ # if the bare variable name is used it returns the filename (for
+ # backwards compatibility). We also add any child datasets to the
+ # the param dict encoded as:
+ # "_CHILD___{dataset_name}___{child_designation}",
+ # but this should be considered DEPRECATED, instead use:
+ # $dataset.get_child( 'name' ).filename
+ for name, data in input_datasets.items():
+ param_dict_value = param_dict.get(name, None)
+ if not isinstance(param_dict_value, (DatasetFilenameWrapper, DatasetListWrapper)):
+ wrapper_kwds = dict(
+ datatypes_registry=self.app.datatypes_registry,
+ tool=self,
+ name=name,
+ )
+ real_path = data.file_name
+ if real_path in input_false_paths:
+ false_path = input_false_paths[ real_path ]
+ wrapper_kwds[ 'false_path' ] = false_path
+ param_dict[name] = DatasetFilenameWrapper( data, **wrapper_kwds )
+ if data:
+ for child in data.children:
+ param_dict[ "_CHILD___%s___%s" % ( name, child.designation ) ] = DatasetFilenameWrapper( child )
+ output_false_paths = dict( [ ( dp.real_path, dp.false_path ) for dp in output_paths if getattr( dp, "false_path", None ) ] )
+ for name, hda in output_datasets.items():
+ # Write outputs to the working directory (for security purposes)
+ # if desired.
+ real_path = hda.file_name
+ if real_path in output_false_paths:
+ false_path = output_false_paths[ real_path ]
+ param_dict[name] = DatasetFilenameWrapper( hda, false_path=false_path )
+ open( false_path, 'w' ).close()
+ else:
+ param_dict[name] = DatasetFilenameWrapper( hda )
+ # Provide access to a path to store additional files
+ # TODO: path munging for cluster/dataset server relocatability
+ param_dict[name].files_path = os.path.abspath(os.path.join( job_working_directory, "dataset_%s_files" % (hda.dataset.id) ))
+ for child in hda.children:
+ param_dict[ "_CHILD___%s___%s" % ( name, child.designation ) ] = DatasetFilenameWrapper( child )
+ for out_name, output in self.tool.outputs.iteritems():
+ if out_name not in param_dict and output.filters:
+ # Assume the reason we lack this output is because a filter
+ # failed to pass; for tool writing convienence, provide a
+ # NoneDataset
+ param_dict[ out_name ] = NoneDataset( datatypes_registry=self.app.datatypes_registry, ext=output.format )
+
+ # -- Add useful attributes/functions for use in creating command line.
+
+ # Function for querying a data table.
+ def get_data_table_entry(table_name, query_attr, query_val, return_attr):
+ """
+ Queries and returns an entry in a data table.
+ """
+
+ if table_name in self.app.tool_data_tables:
+ return self.app.tool_data_tables[ table_name ].get_entry( query_attr, query_val, return_attr )
+
+ param_dict['__get_data_table_entry__'] = get_data_table_entry
+
+ # We add access to app here, this allows access to app.config, etc
+ param_dict['__app__'] = RawObjectWrapper( self.app )
+ # More convienent access to app.config.new_file_path; we don't need to
+ # wrap a string, but this method of generating additional datasets
+ # should be considered DEPRECATED
+ # TODO: path munging for cluster/dataset server relocatability
+ param_dict['__new_file_path__'] = os.path.abspath(self.compute_environment.new_file_path())
+ # The following points to location (xxx.loc) files which are pointers
+ # to locally cached data
+ param_dict['__tool_data_path__'] = param_dict['GALAXY_DATA_INDEX_DIR'] = self.app.config.tool_data_path
+ # For the upload tool, we need to know the root directory and the
+ # datatypes conf path, so we can load the datatypes registry
+ param_dict['__root_dir__'] = param_dict['GALAXY_ROOT_DIR'] = os.path.abspath( self.app.config.root )
+ param_dict['__datatypes_config__'] = param_dict['GALAXY_DATATYPES_CONF_FILE'] = self.app.datatypes_registry.integrated_datatypes_configs
+ param_dict['__admin_users__'] = self.app.config.admin_users
+ param_dict['__user__'] = RawObjectWrapper( param_dict.get( '__user__', None ) )
+ # Return the dictionary of parameters
+ return param_dict
+
+ def build( self ):
+ """
+ Build runtime description of job to execute, evaluate command and
+ config templates corresponding to this tool with these inputs on this
+ compute environment.
+ """
+ self.extra_filenames = []
+ self.command_line = None
+
+ self.__build_config_files( )
+ self.__build_param_file( )
+ self.__build_command_line( )
+
+ return self.command_line, self.extra_filenames
+
+ def __build_command_line( self ):
+ """
+ Build command line to invoke this tool given a populated param_dict
+ """
+ command = self.tool.command
+ param_dict = self.param_dict
+ interpreter = self.tool.interpreter
+ command_line = None
+ if not command:
+ return
+ try:
+ # Substituting parameters into the command
+ command_line = fill_template( command, context=param_dict )
+ # Remove newlines from command line, and any leading/trailing white space
+ command_line = command_line.replace( "\n", " " ).replace( "\r", " " ).strip()
+ except Exception:
+ # Modify exception message to be more clear
+ #e.args = ( 'Error substituting into command line. Params: %r, Command: %s' % ( param_dict, self.command ), )
+ raise
+ if interpreter:
+ # TODO: path munging for cluster/dataset server relocatability
+ executable = command_line.split()[0]
+ tool_dir = os.path.abspath( self.tool.tool_dir )
+ abs_executable = os.path.join( tool_dir, executable )
+ command_line = command_line.replace(executable, abs_executable, 1)
+ command_line = interpreter + " " + command_line
+ self.command_line = command_line
+
+ def __build_config_files( self ):
+ """
+ Build temporary file for file based parameter transfer if needed
+ """
+ param_dict = self.param_dict
+ config_filenames = []
+ for name, filename, template_text in self.tool.config_files:
+ # If a particular filename was forced by the config use it
+ directory = self.local_working_directory
+ if filename is not None:
+ config_filename = os.path.join( directory, filename )
+ else:
+ fd, config_filename = tempfile.mkstemp( dir=directory )
+ os.close( fd )
+ f = open( config_filename, "wt" )
+ f.write( fill_template( template_text, context=param_dict ) )
+ f.close()
+ # For running jobs as the actual user, ensure the config file is globally readable
+ os.chmod( config_filename, 0644 )
+ self.__register_extra_file( name, config_filename )
+ config_filenames.append( config_filename )
+ return config_filenames
+
+ def __build_param_file( self ):
+ """
+ Build temporary file for file based parameter transfer if needed
+ """
+ param_dict = self.param_dict
+ directory = self.local_working_directory
+ command = self.tool.command
+ if command and "$param_file" in command:
+ fd, param_filename = tempfile.mkstemp( dir=directory )
+ os.close( fd )
+ f = open( param_filename, "wt" )
+ for key, value in param_dict.items():
+ # parameters can be strings or lists of strings, coerce to list
+ if type(value) != type([]):
+ value = [ value ]
+ for elem in value:
+ f.write( '%s=%s\n' % (key, elem) )
+ f.close()
+ self.__register_extra_file( 'param_file', param_filename )
+ return param_filename
+ else:
+ return None
+
+ def __register_extra_file( self, name, local_config_path ):
+ """
+ Takes in the local path to a config file and registers the (potentially
+ remote) ultimate path of the config file with the parameter dict.
+ """
+ self.extra_filenames.append( local_config_path )
+ config_basename = os.path.basename( local_config_path )
+ compute_config_path = self.__join_for_compute(self.compute_environment.config_directory(), config_basename)
+ self.param_dict[ name ] = compute_config_path
+
+ def __join_for_compute( self, *args ):
+ """
+ os.path.join but with compute_environment.sep for cross-platform
+ compat.
+ """
+ return self.compute_environment.sep().join( args )
diff -r e6f90d86a8eb3f3e48d7bdb6ab801aee22027b33 -r e4cea1f8ef6e1bb02ee2746dddc9da5dc2531165 test/unit/jobs/test_job_wrapper.py
--- /dev/null
+++ b/test/unit/jobs/test_job_wrapper.py
@@ -0,0 +1,225 @@
+import os
+from contextlib import contextmanager
+
+from unittest import TestCase
+from galaxy.model import Job
+from galaxy.model import Task
+from galaxy.model import User
+from galaxy.jobs import JobWrapper
+from galaxy.jobs import TaskWrapper
+from galaxy.util.bunch import Bunch
+
+from galaxy.tools import evaluation
+
+from tools_support import UsesApp
+#from tools_support import MockTool
+
+#from ..tools_and_jobs_helpers import MockApp
+
+TEST_TOOL_ID = "cufftest"
+TEST_VERSION_COMMAND = "bwa --version"
+TEST_DEPENDENCIES_COMMANDS = ". /galaxy/modules/bwa/0.5.9/env.sh"
+TEST_COMMAND = ""
+
+
+class BaseWrapperTestCase(UsesApp):
+
+ def setUp(self):
+ self.setup_app()
+ job = Job()
+ job.id = 345
+ job.tool_id = TEST_TOOL_ID
+ job.user = User()
+ self.model_objects = {Job: {345: job}}
+ self.app.model.context = MockContext(self.model_objects)
+
+ self.app.toolbox = MockToolbox(MockTool(self))
+ self.working_directory = os.path.join(self.test_directory, "working")
+ self.app.object_store = MockObjectStore(self.working_directory)
+
+ self.queue = MockJobQueue(self.app)
+ self.job = job
+
+ def tearDown(self):
+ self.tear_down_app()
+
+ @contextmanager
+ def _prepared_wrapper(self):
+ wrapper = self._wrapper()
+ with _mock_tool_evaluator(MockEvaluator):
+ wrapper.prepare()
+ yield wrapper
+
+ def test_version_path(self):
+ wrapper = self._wrapper()
+ version_path = wrapper.get_version_string_path()
+ expected_path = os.path.join(self.test_directory, "new_files", "GALAXY_VERSION_STRING_345")
+ self.assertEquals(version_path, expected_path)
+
+ def test_prepare_sets_command_line(self):
+ with self._prepared_wrapper() as wrapper:
+ assert TEST_COMMAND in wrapper.command_line
+
+ def test_prepare_sets_dependency_shell_commands(self):
+ with self._prepared_wrapper() as wrapper:
+ assert TEST_DEPENDENCIES_COMMANDS == wrapper.dependency_shell_commands
+
+
+class JobWrapperTestCase(BaseWrapperTestCase, TestCase):
+
+ def _wrapper(self):
+ return JobWrapper(self.job, self.queue)
+
+ def test_prepare_sets_version_command(self):
+ with self._prepared_wrapper() as wrapper:
+ assert TEST_VERSION_COMMAND == wrapper.version_string_cmd, wrapper.version_string_cmd
+
+
+class TaskWrapperTestCase(BaseWrapperTestCase, TestCase):
+
+ def setUp(self):
+ super(TaskWrapperTestCase, self).setUp()
+ self.task = Task(self.job, self.working_directory, "prepare_bwa_job.sh")
+ self.task.id = 4
+ self.model_objects[Task] = {4: self.task}
+
+ def _wrapper(self):
+ return TaskWrapper(self.task, self.queue)
+
+ def test_prepare_sets_no_version_command(self):
+ with self._prepared_wrapper() as wrapper:
+ assert wrapper.version_string_cmd is None
+
+
+class MockEvaluator(object):
+
+ def __init__(self, app, tool, job, local_working_directory):
+ self.app = app
+ self.tool = tool
+ self.job = job
+ self.local_working_directory = local_working_directory
+ self.param_dict = {}
+
+ def set_compute_environment(self, *args, **kwds):
+ pass
+
+ def build(self):
+ return TEST_COMMAND, []
+
+
+class MockJobQueue(object):
+
+ def __init__(self, app):
+ self.app = app
+ self.dispatcher = MockJobDispatcher(app)
+
+
+class MockJobDispatcher(object):
+
+ def __init__(self, app):
+ pass
+
+ def url_to_destination(self):
+ pass
+
+
+class MockApp(object):
+
+ def __init__(self, object_store, test_directory, model_objects):
+ self.object_store = object_store
+ self.toolbox = MockToolbox(MockTool(self))
+ self.config = Bunch(
+ outputs_to_working_directory=False,
+ new_file_path=os.path.join(test_directory, "new_files"),
+ tool_data_path=os.path.join(test_directory, "tools"),
+ root=os.path.join(test_directory, "galaxy"),
+ datatypes_registry=Bunch(
+ integrated_datatypes_configs=os.path.join(test_directory, "datatypes_conf.xml"),
+ ),
+ )
+ self.job_config = Bunch()
+ self.model = Bunch(context=MockContext(model_objects))
+
+
+class MockContext(object):
+
+ def __init__(self, model_objects):
+ self.expunged_all = False
+ self.flushed = False
+ self.model_objects = model_objects
+ self.created_objects = []
+
+ def expunge_all(self):
+ self.expunged_all = True
+
+ def query(self, clazz):
+ return MockQuery(self.model_objects.get(clazz))
+
+ def flush(self):
+ self.flushed = True
+
+ def add(self, object):
+ self.created_objects.append(object)
+
+
+class MockQuery(object):
+
+ def __init__(self, class_objects):
+ self.class_objects = class_objects
+
+ def filter_by(self, **kwds):
+ return Bunch(first=lambda: None)
+
+ def get(self, id):
+ return self.class_objects.get(id, None)
+
+
+class MockTool(object):
+
+ def __init__(self, app):
+ self.version_string_cmd = TEST_VERSION_COMMAND
+
+ def build_dependency_shell_commands(self):
+ return TEST_DEPENDENCIES_COMMANDS
+
+
+class MockToolbox(object):
+
+ def __init__(self, test_tool):
+ self.test_tool = test_tool
+
+ @property
+ def tools_by_id(self):
+ return self
+
+ def get(self, tool_id, default=None):
+ assert tool_id == TEST_TOOL_ID
+ return self.test_tool
+
+
+class MockObjectStore(object):
+
+ def __init__(self, working_directory):
+ self.working_directory = working_directory
+ os.makedirs(working_directory)
+
+ def create(self, *args, **kwds):
+ pass
+
+ def get_filename(self, *args, **kwds):
+ if kwds.get("base_dir", "") == "job_work":
+ return self.working_directory
+ return None
+
+
+## Poor man's mocking. Need to get a real mocking library as real Galaxy development
+## dependnecy.
+@contextmanager
+def _mock_tool_evaluator(mock_constructor):
+ name = evaluation.ToolEvaluator.__name__
+ real_classs = getattr(evaluation, name)
+ try:
+ setattr(evaluation, name, mock_constructor)
+ yield
+ finally:
+ setattr(evaluation, name, real_classs)
diff -r e6f90d86a8eb3f3e48d7bdb6ab801aee22027b33 -r e4cea1f8ef6e1bb02ee2746dddc9da5dc2531165 test/unit/tools/test_evaluation.py
--- /dev/null
+++ b/test/unit/tools/test_evaluation.py
@@ -0,0 +1,252 @@
+import os
+
+from unittest import TestCase
+from galaxy.model import Job
+from galaxy.model import History
+from galaxy.model import Dataset
+from galaxy.model import JobParameter
+from galaxy.model import HistoryDatasetAssociation
+from galaxy.model import JobToInputDatasetAssociation
+from galaxy.tools.evaluation import ToolEvaluator
+from galaxy.jobs import SimpleComputeEnvironment
+from galaxy.jobs.datasets import DatasetPath
+from galaxy.util.bunch import Bunch
+
+# For MockTool
+from galaxy.tools.parameters import params_from_strings
+from galaxy.tools import ToolOutput
+from galaxy.tools.parameters.grouping import Repeat
+from galaxy.tools.parameters.grouping import Conditional
+from galaxy.tools.parameters.grouping import ConditionalWhen
+from galaxy.tools.parameters.basic import IntegerToolParameter
+from galaxy.tools.parameters.basic import SelectToolParameter
+from elementtree.ElementTree import XML # Import after model, to ensure elementtree
+
+
+# Test fixtures for Galaxy infrastructure.
+from tools_support import UsesApp
+
+# To Test:
+# - param_file handling.
+
+
+class ToolEvaluatorTestCase(TestCase, UsesApp):
+
+ def setUp(self):
+ self.setup_app()
+ self.tool = MockTool(self.app)
+ self.job = Job()
+ self.job.history = History()
+ self.job.parameters = [ JobParameter( name="thresh", value="4" ) ]
+ self.evaluator = ToolEvaluator( self.app, self.tool, self.job, self.test_directory )
+
+ def tearDown(self):
+ self.tear_down_app()
+
+ def test_simple_evaluation( self ):
+ self._setup_test_bwa_job()
+ self._set_compute_environment()
+ command_line, extra_filenames = self.evaluator.build( )
+ self.assertEquals( command_line, "bwa --thresh=4 --in=/galaxy/files/dataset_1.dat --out=/galaxy/files/dataset_2.dat" )
+
+ def test_repeat_evaluation( self ):
+ repeat = Repeat()
+ repeat.name = "r"
+ repeat.inputs = { "thresh": self.tool.test_thresh_param() }
+ self.tool.set_params( { "r": repeat } )
+ self.job.parameters = [ JobParameter( name="r", value='''[{"thresh": 4, "__index__": 0},{"thresh": 5, "__index__": 1}]''' ) ]
+ self.tool._command_line = "prog1 #for $r_i in $r # $r_i.thresh#end for#"
+ self._set_compute_environment()
+ command_line, extra_filenames = self.evaluator.build( )
+ self.assertEquals( command_line, "prog1 4 5" )
+
+ def test_conditional_evaluation( self ):
+ select_xml = XML('''<param name="always_true" type="select"><option value="true">True</option></param>''')
+ parameter = SelectToolParameter( self.tool, select_xml )
+
+ conditional = Conditional()
+ conditional.name = "c"
+ conditional.test_param = parameter
+ when = ConditionalWhen()
+ when.inputs = { "thresh": self.tool.test_thresh_param() }
+ when.value = "true"
+ conditional.cases = [ when ]
+ self.tool.set_params( { "c": conditional } )
+ self.job.parameters = [ JobParameter( name="c", value='''{"thresh": 4, "always_true": "true", "__current_case__": 0}''' ) ]
+ self.tool._command_line = "prog1 --thresh=${c.thresh} --test_param=${c.always_true}"
+ self._set_compute_environment()
+ command_line, extra_filenames = self.evaluator.build( )
+ self.assertEquals( command_line, "prog1 --thresh=4 --test_param=true" )
+
+ def test_evaluation_with_path_rewrites_wrapped( self ):
+ self.tool.check_values = True
+ self.__test_evaluation_with_path_rewrites()
+
+ def test_evaluation_with_path_rewrites_unwrapped( self ):
+ self.tool.check_values = False
+ self.__test_evaluation_with_path_rewrites()
+
+ def __test_evaluation_with_path_rewrites( self ):
+ # Various things can cause dataset paths to be rewritten (Task
+ # splitting, config.outputs_to_working_directory). This tests that
+ #functionality.
+ self._setup_test_bwa_job()
+ job_path_1 = "%s/dataset_1.dat" % self.test_directory
+ job_path_2 = "%s/dataset_2.dat" % self.test_directory
+ self._set_compute_environment(
+ input_paths=[DatasetPath(1, '/galaxy/files/dataset_1.dat', false_path=job_path_1)],
+ output_paths=[DatasetPath(2, '/galaxy/files/dataset_2.dat', false_path=job_path_2)],
+ )
+ command_line, extra_filenames = self.evaluator.build( )
+ self.assertEquals( command_line, "bwa --thresh=4 --in=%s --out=%s" % (job_path_1, job_path_2) )
+
+ def test_configfiles_evaluation( self ):
+ self.tool.config_files.append( ( "conf1", None, "$thresh" ) )
+ self.tool._command_line = "prog1 $conf1"
+ self._set_compute_environment()
+ command_line, extra_filenames = self.evaluator.build( )
+ self.assertEquals( len( extra_filenames ), 1)
+ config_filename = extra_filenames[ 0 ]
+ config_basename = os.path.basename( config_filename )
+ # Verify config file written into working directory.
+ self.assertEquals( os.path.join( self.test_directory, config_basename ), config_filename )
+ # Verify config file contents are evaluated against parameters.
+ assert open( config_filename, "r").read() == "4"
+ self.assertEquals(command_line, "prog1 %s" % config_filename)
+
+ def test_template_property_app( self ):
+ self._assert_template_property_is("$__app__.config.new_file_path", self.app.config.new_file_path)
+
+ def test_template_property_new_file_path( self ):
+ self._assert_template_property_is("$__new_file_path__", self.app.config.new_file_path)
+
+ def test_template_property_root_dir( self ):
+ self._assert_template_property_is("$__root_dir__", self.app.config.root)
+
+ def test_template_property_admin_users( self ):
+ self._assert_template_property_is("$__admin_users__", "mary(a)example.com")
+
+ def _assert_template_property_is(self, expression, value):
+ self.tool._command_line = "test.exe"
+ self.tool.config_files.append( ( "conf1", None, """%s""" % expression) )
+ self._set_compute_environment()
+ _, extra_filenames = self.evaluator.build( )
+ config_filename = extra_filenames[ 0 ]
+ self.assertEquals(open( config_filename, "r").read(), value)
+
+ def _set_compute_environment(self, **kwds):
+ if "working_directory" not in kwds:
+ kwds[ "working_directory" ] = self.test_directory
+ if "new_file_path" not in kwds:
+ kwds[ "new_file_path" ] = self.app.config.new_file_path
+ self.evaluator.set_compute_environment( TestComputeEnviornment( **kwds ) )
+ self.assertIn( "exec_before_job", self.tool.hooks_called )
+
+ def _setup_test_bwa_job( self ):
+ self.job.input_datasets = [ self._job_dataset( 'input1', '/galaxy/files/dataset_1.dat' ) ]
+ self.job.output_datasets = [ self._job_dataset( 'output1', '/galaxy/files/dataset_2.dat' ) ]
+
+ def _job_dataset( self, name, path ):
+ metadata = dict( )
+ hda = HistoryDatasetAssociation( name=name, metadata=metadata )
+ hda.dataset = Dataset( id=123, external_filename=path )
+ hda.dataset.metadata = dict()
+ hda.children = []
+ jida = JobToInputDatasetAssociation( name=name, dataset=hda )
+ return jida
+
+
+class MockHistoryDatasetAssociation( HistoryDatasetAssociation ):
+
+ def __init__( self, **kwds ):
+ self._metadata = dict()
+ super( MockHistoryDatasetAssociation, self ).__init__( **kwds )
+
+
+class TestComputeEnviornment( SimpleComputeEnvironment ):
+
+ def __init__(
+ self,
+ new_file_path,
+ working_directory,
+ input_paths=[ '/galaxy/files/dataset_1.dat' ],
+ output_paths=[ '/galaxy/files/dataset_2.dat' ],
+ ):
+ self._new_file_path = new_file_path
+ self._working_directory = working_directory
+ self._input_paths = input_paths
+ self._output_paths = output_paths
+
+ def input_paths( self ):
+ return self._input_paths
+
+ def output_paths( self ):
+ return self._output_paths
+
+ def working_directory( self ):
+ return self._working_directory
+
+ def new_file_path(self):
+ return self._new_file_path
+
+
+class MockTool( object ):
+
+ def __init__( self, app ):
+ self.app = app
+ self.hooks_called = []
+ self._config_files = []
+ self._command_line = "bwa --thresh=$thresh --in=$input1 --out=$output1"
+ self._params = { "thresh": self.test_thresh_param() }
+ self.options = Bunch(sanitize=False)
+ self.check_values = True
+
+ def test_thresh_param( self ):
+ elem = XML( '<param name="thresh" type="integer" value="5" />' )
+ return IntegerToolParameter( self, elem )
+
+ def params_from_strings( self, params, app, ignore_errors=False ):
+ return params_from_strings( self.inputs, params, app, ignore_errors )
+
+ @property
+ def template_macro_params( self ):
+ return {}
+
+ @property
+ def inputs( self ):
+ return self._params
+
+ def set_params( self, params ):
+ self._params = params
+
+ @property
+ def outputs( self ):
+ #elem_output1 = XML( '<param name="output1" type="data" format="txt"/>' )
+ # DataToolParameter( self, elem_output1 ),
+ return dict(
+ output1=ToolOutput( "output1" ),
+ )
+
+ @property
+ def config_files( self ):
+ return self._config_files
+
+ @property
+ def command( self ):
+ return self._command_line
+
+ @property
+ def interpreter( self ):
+ return None
+
+ def handle_unvalidated_param_values( self, input_values, app ):
+ pass
+
+ def build_param_dict( self, incoming, *args, **kwds ):
+ return incoming
+
+ def call_hook( self, hook_name, *args, **kwargs ):
+ self.hooks_called.append( hook_name )
+
+ def exec_before_job( self, *args, **kwargs ):
+ pass
https://bitbucket.org/galaxy/galaxy-central/commits/bbe023fc7654/
Changeset: bbe023fc7654
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Introduce dataset_path_rewrites to slightly simplify tools/evaluation.py.
Affected #: 2 files
diff -r e4cea1f8ef6e1bb02ee2746dddc9da5dc2531165 -r bbe023fc76547187210c8f99e07446ffbf493b4f lib/galaxy/jobs/datasets.py
--- a/lib/galaxy/jobs/datasets.py
+++ b/lib/galaxy/jobs/datasets.py
@@ -7,6 +7,11 @@
from abc import abstractmethod
+def dataset_path_rewrites( dataset_paths ):
+ dataset_paths_with_rewrites = filter( lambda path: getattr( path, "false_path", None ), dataset_paths )
+ return dict( [ ( dp.real_path, dp.false_path ) for dp in dataset_paths_with_rewrites ] )
+
+
class DatasetPath( object ):
def __init__( self, dataset_id, real_path, false_path=None, mutable=True ):
self.dataset_id = dataset_id
diff -r e4cea1f8ef6e1bb02ee2746dddc9da5dc2531165 -r bbe023fc76547187210c8f99e07446ffbf493b4f lib/galaxy/tools/evaluation.py
--- a/lib/galaxy/tools/evaluation.py
+++ b/lib/galaxy/tools/evaluation.py
@@ -19,6 +19,7 @@
SelectToolParameter,
)
from galaxy.tools.parameters.grouping import Conditional, Repeat
+from galaxy.jobs.datasets import dataset_path_rewrites
class ToolEvaluator( object ):
@@ -102,7 +103,7 @@
# All parameters go into the param_dict
param_dict.update( incoming )
- input_false_paths = dict( [ ( dp.real_path, dp.false_path ) for dp in input_paths if getattr( dp, "false_path", None ) ] )
+ input_false_paths = dataset_path_rewrites( input_paths )
def wrap_values( inputs, input_values ):
"""
@@ -218,7 +219,7 @@
if data:
for child in data.children:
param_dict[ "_CHILD___%s___%s" % ( name, child.designation ) ] = DatasetFilenameWrapper( child )
- output_false_paths = dict( [ ( dp.real_path, dp.false_path ) for dp in output_paths if getattr( dp, "false_path", None ) ] )
+ output_false_paths = dataset_path_rewrites( output_paths )
for name, hda in output_datasets.items():
# Write outputs to the working directory (for security purposes)
# if desired.
https://bitbucket.org/galaxy/galaxy-central/commits/55276af33bf8/
Changeset: 55276af33bf8
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Add rewrite_parameters (defaults to False) option to LWR runner.
When false the current behavior of the LWR is maintained and cheetah templates are evaluated as if the command will be submitted locally and then the LWR will rewrite the command and config files and transfer files as needed.
If this option is set to True, an instance of the new class LwrComputeEnvironment will be passed along to job_wrapper.prepare allowing the LWR runner to instruct with the tool evaluation process to use the remote paths for input, output, and config files during evaluation. This option feels less hacky - I am skeptical there are tools that will work this way that would not have worked the other way and this does have down sides such as forcing all input files to be transferred regardless of whether actually used in command or config files. This will hopefully provide an entry point for modifying behavior of tool data tables however.
tool_dir is still rewritten either way - this is a small point hopefully.
Affected #: 2 files
diff -r bbe023fc76547187210c8f99e07446ffbf493b4f -r 55276af33bf85f0ec5b773a8e6bb96dbe9d8b613 lib/galaxy/jobs/datasets.py
--- a/lib/galaxy/jobs/datasets.py
+++ b/lib/galaxy/jobs/datasets.py
@@ -25,6 +25,20 @@
else:
return self.false_path
+ def with_path_for_job( self, false_path ):
+ """
+ Clone the dataset path but with a new false_path.
+ """
+ dataset_path = self
+ if false_path is not None:
+ dataset_path = DatasetPath(
+ dataset_id=self.dataset_id,
+ real_path=self.real_path,
+ false_path=false_path,
+ mutable=self.mutable,
+ )
+ return dataset_path
+
class DatasetPathRewriter( object ):
""" Used by runner to rewrite paths. """
diff -r bbe023fc76547187210c8f99e07446ffbf493b4f -r 55276af33bf85f0ec5b773a8e6bb96dbe9d8b613 lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -2,9 +2,11 @@
from galaxy import model
from galaxy.jobs.runners import AsynchronousJobState, AsynchronousJobRunner
+from galaxy.jobs import ComputeEnvironment
from galaxy.jobs import JobDestination
from galaxy.jobs.command_factory import build_command
from galaxy.util import string_as_bool_or_none
+from galaxy.util import in_directory
from galaxy.util.bunch import Bunch
import errno
@@ -17,6 +19,7 @@
from .lwr_client import ClientJobDescription
from .lwr_client import LwrOutputs
from .lwr_client import GalaxyOutputs
+from .lwr_client import PathMapper
log = logging.getLogger( __name__ )
@@ -73,15 +76,17 @@
dependency_resolution = LwrJobRunner.__dependency_resolution( client )
remote_dependency_resolution = dependency_resolution == "remote"
requirements = job_wrapper.tool.requirements if remote_dependency_resolution else []
+ rewrite_paths = not LwrJobRunner.__rewrite_parameters( client )
client_job_description = ClientJobDescription(
command_line=command_line,
output_files=self.get_output_files(job_wrapper),
- input_files=job_wrapper.get_input_fnames(),
+ input_files=self.get_input_files(job_wrapper),
working_directory=job_wrapper.working_directory,
tool=job_wrapper.tool,
config_files=job_wrapper.extra_filenames,
requirements=requirements,
version_file=job_wrapper.get_version_string_path(),
+ rewrite_paths=rewrite_paths,
)
job_id = lwr_submit_job(client, client_job_description, remote_job_config)
log.info("lwr job submitted with job_id %s" % job_id)
@@ -106,11 +111,16 @@
client = None
remote_job_config = None
try:
- job_wrapper.prepare()
- self.__prepare_input_files_locally(job_wrapper)
client = self.get_client_from_wrapper(job_wrapper)
tool = job_wrapper.tool
remote_job_config = client.setup(tool.id, tool.version)
+ rewrite_parameters = LwrJobRunner.__rewrite_parameters( client )
+ prepare_kwds = {}
+ if rewrite_parameters:
+ compute_environment = LwrComputeEnvironment( client, job_wrapper, remote_job_config )
+ prepare_kwds[ 'compute_environment' ] = compute_environment
+ job_wrapper.prepare( **prepare_kwds )
+ self.__prepare_input_files_locally(job_wrapper)
remote_metadata = LwrJobRunner.__remote_metadata( client )
remote_work_dir_copy = LwrJobRunner.__remote_work_dir_copy( client )
dependency_resolution = LwrJobRunner.__dependency_resolution( client )
@@ -147,8 +157,12 @@
job_wrapper.prepare_input_files_cmds = None # prevent them from being used in-line
def get_output_files(self, job_wrapper):
- output_fnames = job_wrapper.get_output_fnames()
- return [ str( o ) for o in output_fnames ]
+ output_paths = job_wrapper.get_output_fnames()
+ return [ str( o ) for o in output_paths ] # Force job_path from DatasetPath objects.
+
+ def get_input_files(self, job_wrapper):
+ input_paths = job_wrapper.get_input_paths()
+ return [ str( i ) for i in input_paths ] # Force job_path from DatasetPath objects.
def get_client_from_wrapper(self, job_wrapper):
job_id = job_wrapper.job_id
@@ -323,6 +337,10 @@
use_remote_datatypes = string_as_bool_or_none( lwr_client.destination_params.get( "use_remote_datatypes", False ) )
return use_remote_datatypes
+ @staticmethod
+ def __rewrite_parameters( lwr_client ):
+ return string_as_bool_or_none( lwr_client.destination_params.get( "rewrite_parameters", False ) ) or False
+
def __build_metadata_configuration(self, client, job_wrapper, remote_metadata, remote_job_config):
metadata_kwds = {}
if remote_metadata:
@@ -352,3 +370,55 @@
metadata_kwds['datatypes_config'] = os.path.join(configs_directory, os.path.basename(integrates_datatypes_config))
return metadata_kwds
+
+
+class LwrComputeEnvironment( ComputeEnvironment ):
+
+ def __init__( self, lwr_client, job_wrapper, remote_job_config ):
+ self.lwr_client = lwr_client
+ self.job_wrapper = job_wrapper
+ self.local_path_config = job_wrapper.default_compute_environment()
+ # job_wrapper.prepare is going to expunge the job backing the following
+ # computations, so precalculate these paths.
+ self._wrapper_input_paths = self.local_path_config.input_paths()
+ self._wrapper_output_paths = self.local_path_config.output_paths()
+ self.path_mapper = PathMapper(lwr_client, remote_job_config, self.local_path_config.working_directory())
+ self._config_directory = remote_job_config[ "configs_directory" ]
+ self._working_directory = remote_job_config[ "working_directory" ]
+ self._sep = remote_job_config[ "system_properties" ][ "separator" ]
+ self._tool_dir = remote_job_config[ "tools_directory" ]
+
+ def output_paths( self ):
+ local_output_paths = self._wrapper_output_paths
+
+ results = []
+ for local_output_path in local_output_paths:
+ wrapper_path = str( local_output_path )
+ remote_path = self.path_mapper.remote_output_path_rewrite( wrapper_path )
+ results.append( local_output_path.with_path_for_job( remote_path ) )
+ return results
+
+ def input_paths( self ):
+ local_input_paths = self._wrapper_input_paths
+
+ results = []
+ for local_input_path in local_input_paths:
+ wrapper_path = str( local_input_path )
+ # This will over-copy in some cases. For instance in the case of task
+ # splitting, this input will be copied even though only the work dir
+ # input will actually be used.
+ remote_path = self.path_mapper.remote_input_path_rewrite( wrapper_path )
+ results.append( local_input_path.with_path_for_job( remote_path ) )
+ return results
+
+ def working_directory( self ):
+ return self._working_directory
+
+ def config_directory( self ):
+ return self._config_directory
+
+ def new_file_path( self ):
+ return self.working_directory() # Problems with doing this?
+
+ def sep( self ):
+ return self._sep
https://bitbucket.org/galaxy/galaxy-central/commits/79557437160a/
Changeset: 79557437160a
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Move false_path logic out of tool evaluation code and into cheetah wrapper.
This should ease replacing extra_files_path in subsequent commits.
Affected #: 4 files
diff -r 55276af33bf85f0ec5b773a8e6bb96dbe9d8b613 -r 79557437160ae40b410dc1c7d627bf722387b324 lib/galaxy/jobs/datasets.py
--- a/lib/galaxy/jobs/datasets.py
+++ b/lib/galaxy/jobs/datasets.py
@@ -9,10 +9,11 @@
def dataset_path_rewrites( dataset_paths ):
dataset_paths_with_rewrites = filter( lambda path: getattr( path, "false_path", None ), dataset_paths )
- return dict( [ ( dp.real_path, dp.false_path ) for dp in dataset_paths_with_rewrites ] )
+ return dict( [ ( dp.real_path, dp ) for dp in dataset_paths_with_rewrites ] )
class DatasetPath( object ):
+
def __init__( self, dataset_id, real_path, false_path=None, mutable=True ):
self.dataset_id = dataset_id
self.real_path = real_path
diff -r 55276af33bf85f0ec5b773a8e6bb96dbe9d8b613 -r 79557437160ae40b410dc1c7d627bf722387b324 lib/galaxy/tools/evaluation.py
--- a/lib/galaxy/tools/evaluation.py
+++ b/lib/galaxy/tools/evaluation.py
@@ -103,7 +103,7 @@
# All parameters go into the param_dict
param_dict.update( incoming )
- input_false_paths = dataset_path_rewrites( input_paths )
+ input_dataset_paths = dataset_path_rewrites( input_paths )
def wrap_values( inputs, input_values ):
"""
@@ -120,7 +120,7 @@
elif isinstance( input, DataToolParameter ) and input.multiple:
input_values[ input.name ] = \
DatasetListWrapper( input_values[ input.name ],
- false_paths=input_false_paths,
+ dataset_paths=input_dataset_paths,
datatypes_registry=self.app.datatypes_registry,
tool=self.tool,
name=input.name )
@@ -163,8 +163,8 @@
name=input.name
)
real_path = dataset.file_name
- if real_path in input_false_paths:
- wrapper_kwds[ "false_path" ] = input_false_paths[ real_path ]
+ if real_path in input_dataset_paths:
+ wrapper_kwds[ "dataset_path" ] = input_dataset_paths[ real_path ]
input_values[ input.name ] = \
DatasetFilenameWrapper( dataset, **wrapper_kwds )
elif isinstance( input, SelectToolParameter ):
@@ -212,22 +212,22 @@
name=name,
)
real_path = data.file_name
- if real_path in input_false_paths:
- false_path = input_false_paths[ real_path ]
- wrapper_kwds[ 'false_path' ] = false_path
+ if real_path in input_dataset_paths:
+ dataset_path = input_dataset_paths[ real_path ]
+ wrapper_kwds[ 'dataset_path' ] = dataset_path
param_dict[name] = DatasetFilenameWrapper( data, **wrapper_kwds )
if data:
for child in data.children:
param_dict[ "_CHILD___%s___%s" % ( name, child.designation ) ] = DatasetFilenameWrapper( child )
- output_false_paths = dataset_path_rewrites( output_paths )
+ output_dataset_paths = dataset_path_rewrites( output_paths )
for name, hda in output_datasets.items():
# Write outputs to the working directory (for security purposes)
# if desired.
real_path = hda.file_name
- if real_path in output_false_paths:
- false_path = output_false_paths[ real_path ]
- param_dict[name] = DatasetFilenameWrapper( hda, false_path=false_path )
- open( false_path, 'w' ).close()
+ if real_path in output_dataset_paths:
+ dataset_path = output_dataset_paths[ real_path ]
+ param_dict[name] = DatasetFilenameWrapper( hda, dataset_path=dataset_path )
+ open( dataset_path.false_path, 'w' ).close()
else:
param_dict[name] = DatasetFilenameWrapper( hda )
# Provide access to a path to store additional files
diff -r 55276af33bf85f0ec5b773a8e6bb96dbe9d8b613 -r 79557437160ae40b410dc1c7d627bf722387b324 lib/galaxy/tools/wrappers.py
--- a/lib/galaxy/tools/wrappers.py
+++ b/lib/galaxy/tools/wrappers.py
@@ -161,7 +161,7 @@
def items( self ):
return iter( [ ( k, self.get( k ) ) for k, v in self.metadata.items() ] )
- def __init__( self, dataset, datatypes_registry=None, tool=None, name=None, false_path=None ):
+ def __init__( self, dataset, datatypes_registry=None, tool=None, name=None, dataset_path=None ):
if not dataset:
try:
# TODO: allow this to work when working with grouping
@@ -172,7 +172,7 @@
else:
self.dataset = dataset
self.metadata = self.MetadataWrapper( dataset.metadata )
- self.false_path = false_path
+ self.false_path = getattr( dataset_path, "false_path", None )
def __str__( self ):
if self.false_path is not None:
@@ -193,15 +193,15 @@
class DatasetListWrapper( list ):
"""
"""
- def __init__( self, datasets, false_paths=[], **kwargs ):
+ def __init__( self, datasets, dataset_paths=[], **kwargs ):
if not isinstance(datasets, list):
datasets = [datasets]
def to_wrapper( dataset ):
real_path = dataset.file_name
wrapper_kwds = kwargs.copy()
- if real_path in false_paths:
- wrapper_kwds[ "false_path" ] = false_paths[ real_path ]
+ if real_path in dataset_paths:
+ wrapper_kwds[ "dataset_path" ] = dataset_paths[ real_path ]
return DatasetFilenameWrapper( dataset, **wrapper_kwds )
list.__init__( self, map( to_wrapper, datasets ) )
diff -r 55276af33bf85f0ec5b773a8e6bb96dbe9d8b613 -r 79557437160ae40b410dc1c7d627bf722387b324 test/unit/tools/test_wrappers.py
--- a/test/unit/tools/test_wrappers.py
+++ b/test/unit/tools/test_wrappers.py
@@ -90,7 +90,7 @@
def test_dataset_wrapper_false_path():
dataset = MockDataset()
new_path = "/new/path/dataset_123.dat"
- wrapper = DatasetFilenameWrapper(dataset, false_path=new_path)
+ wrapper = DatasetFilenameWrapper(dataset, dataset_path=Bunch(false_path=new_path))
assert str( wrapper ) == new_path
assert wrapper.file_name == new_path
https://bitbucket.org/galaxy/galaxy-central/commits/9b5215a5db72/
Changeset: 9b5215a5db72
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Allow 'false_path' style replacing of extra files paths.
Updates DatasetFilenameWrapper and DatasetPath to allow this.
Affected #: 3 files
diff -r 79557437160ae40b410dc1c7d627bf722387b324 -r 9b5215a5db7215881d8cc7b04bb853195196a60e lib/galaxy/jobs/datasets.py
--- a/lib/galaxy/jobs/datasets.py
+++ b/lib/galaxy/jobs/datasets.py
@@ -14,10 +14,18 @@
class DatasetPath( object ):
- def __init__( self, dataset_id, real_path, false_path=None, mutable=True ):
+ def __init__(
+ self,
+ dataset_id,
+ real_path,
+ false_path=None,
+ false_extra_files_path=None,
+ mutable=True
+ ):
self.dataset_id = dataset_id
self.real_path = real_path
self.false_path = false_path
+ self.false_extra_files_path = false_extra_files_path
self.mutable = mutable
def __str__( self ):
diff -r 79557437160ae40b410dc1c7d627bf722387b324 -r 9b5215a5db7215881d8cc7b04bb853195196a60e lib/galaxy/tools/wrappers.py
--- a/lib/galaxy/tools/wrappers.py
+++ b/lib/galaxy/tools/wrappers.py
@@ -173,6 +173,7 @@
self.dataset = dataset
self.metadata = self.MetadataWrapper( dataset.metadata )
self.false_path = getattr( dataset_path, "false_path", None )
+ self.false_extra_files_path = getattr( dataset_path, "false_extra_files_path", None )
def __str__( self ):
if self.false_path is not None:
@@ -183,6 +184,8 @@
def __getattr__( self, key ):
if self.false_path is not None and key == 'file_name':
return self.false_path
+ elif self.false_extra_files_path is not None and key == 'extra_files_path':
+ return self.false_extra_files_path
else:
return getattr( self.dataset, key )
diff -r 79557437160ae40b410dc1c7d627bf722387b324 -r 9b5215a5db7215881d8cc7b04bb853195196a60e test/unit/tools/test_wrappers.py
--- a/test/unit/tools/test_wrappers.py
+++ b/test/unit/tools/test_wrappers.py
@@ -8,6 +8,7 @@
from galaxy.tools.wrappers import SelectToolParameterWrapper
from galaxy.tools.wrappers import InputValueWrapper
from galaxy.tools.wrappers import DatasetFilenameWrapper
+from galaxy.jobs.datasets import DatasetPath
from galaxy.util.bunch import Bunch
from elementtree.ElementTree import XML
from galaxy.datatypes.metadata import MetadataSpecCollection
@@ -95,6 +96,24 @@
assert wrapper.file_name == new_path
+def test_dataset_false_extra_files_path():
+ dataset = MockDataset()
+
+ wrapper = DatasetFilenameWrapper(dataset)
+ assert wrapper.extra_files_path == MOCK_DATASET_EXTRA_FILES_PATH
+
+ new_path = "/new/path/dataset_123.dat"
+ dataset_path = DatasetPath(123, MOCK_DATASET_PATH, false_path=new_path)
+ wrapper = DatasetFilenameWrapper(dataset, dataset_path=dataset_path)
+ # Setting false_path is not enough to override
+ assert wrapper.extra_files_path == MOCK_DATASET_EXTRA_FILES_PATH
+
+ new_files_path = "/new/path/dataset_123_files"
+ dataset_path = DatasetPath(123, MOCK_DATASET_PATH, false_path=new_path, false_extra_files_path=new_files_path)
+ wrapper = DatasetFilenameWrapper(dataset, dataset_path=dataset_path)
+ assert wrapper.extra_files_path == new_files_path
+
+
def _drilldown_parameter(tool):
xml = XML( '''<param name="some_name" type="drill_down" display="checkbox" hierarchy="recurse" multiple="true"><options>
@@ -127,6 +146,7 @@
MOCK_DATASET_PATH = "/galaxy/database/files/001/dataset_123.dat"
+MOCK_DATASET_EXTRA_FILES_PATH = "/galaxy/database/files/001/dataset_123.dat"
MOCK_DATASET_EXT = "bam"
@@ -135,6 +155,7 @@
def __init__(self):
self.metadata = MetadataSpecCollection({})
self.file_name = MOCK_DATASET_PATH
+ self.extra_files_path = MOCK_DATASET_EXTRA_FILES_PATH
self.ext = MOCK_DATASET_EXT
https://bitbucket.org/galaxy/galaxy-central/commits/88b86067a11d/
Changeset: 88b86067a11d
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Extend LwrComputeEnvironmet to handle rewriting extra_files_path.
Affected #: 2 files
diff -r 9b5215a5db7215881d8cc7b04bb853195196a60e -r 88b86067a11d355159446bb98d75928149d08326 lib/galaxy/jobs/datasets.py
--- a/lib/galaxy/jobs/datasets.py
+++ b/lib/galaxy/jobs/datasets.py
@@ -34,7 +34,7 @@
else:
return self.false_path
- def with_path_for_job( self, false_path ):
+ def with_path_for_job( self, false_path, false_extra_files_path=None ):
"""
Clone the dataset path but with a new false_path.
"""
@@ -44,6 +44,7 @@
dataset_id=self.dataset_id,
real_path=self.real_path,
false_path=false_path,
+ false_extra_files_path=false_extra_files_path,
mutable=self.mutable,
)
return dataset_path
diff -r 9b5215a5db7215881d8cc7b04bb853195196a60e -r 88b86067a11d355159446bb98d75928149d08326 lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -395,7 +395,7 @@
for local_output_path in local_output_paths:
wrapper_path = str( local_output_path )
remote_path = self.path_mapper.remote_output_path_rewrite( wrapper_path )
- results.append( local_output_path.with_path_for_job( remote_path ) )
+ results.append( self._dataset_path( local_output_path, remote_path ) )
return results
def input_paths( self ):
@@ -408,9 +408,15 @@
# splitting, this input will be copied even though only the work dir
# input will actually be used.
remote_path = self.path_mapper.remote_input_path_rewrite( wrapper_path )
- results.append( local_input_path.with_path_for_job( remote_path ) )
+ results.append( self._dataset_path( local_input_path, remote_path ) )
return results
+ def _dataset_path( self, local_dataset_path, remote_path ):
+ remote_extra_files_path = None
+ if remote_path:
+ remote_extra_files_path = "%s_files" % remote_path[ 0:-len( ".dat" ) ]
+ return local_dataset_path.with_path_for_job( remote_path, remote_extra_files_path )
+
def working_directory( self ):
return self._working_directory
https://bitbucket.org/galaxy/galaxy-central/commits/eb71ed51be40/
Changeset: eb71ed51be40
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Rework version string handling for ComputeEnvironment abstraction.
Add version_path method to ComputeEnviornment interface - provide default implementation delegating to existing JobWrapper method as well as LwrComputeEnviornment piggy backing on recently added version command support.
Build full command to do this in JobWrapper.prepare so that ComputeEnviornment is available and potentially remote path can be used.
Affected #: 5 files
diff -r 88b86067a11d355159446bb98d75928149d08326 -r eb71ed51be40f3bfc523d278b67640bb280cd2e8 lib/galaxy/jobs/__init__.py
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -571,7 +571,7 @@
self.extra_filenames = []
self.command_line = None
# Tool versioning variables
- self.version_string_cmd = None
+ self.write_version_cmd = None
self.version_string = ""
self.galaxy_lib_dir = None
# With job outputs in the working directory, we need the working
@@ -689,7 +689,11 @@
self.sa_session.flush()
# Return list of all extra files
self.param_dict = tool_evaluator.param_dict
- self.version_string_cmd = self.tool.version_string_cmd
+ version_string_cmd = self.tool.version_string_cmd
+ if version_string_cmd:
+ self.write_version_cmd = "%s > %s 2>&1" % ( version_string_cmd, compute_environment.version_path() )
+ else:
+ self.write_version_cmd = None
return self.extra_filenames
def default_compute_environment( self, job=None ):
@@ -889,7 +893,7 @@
else:
final_job_state = job.states.ERROR
- if self.version_string_cmd:
+ if self.write_version_cmd:
version_filename = self.get_version_string_path()
if os.path.exists(version_filename):
self.version_string = open(version_filename).read()
@@ -1575,6 +1579,10 @@
def new_file_path( self ):
""" Location to dump new files for this job on remote server. """
+ @abstractmethod
+ def version_path( self ):
+ """ Location of the version file for the underlying tool. """
+
class SimpleComputeEnvironment( object ):
@@ -1608,6 +1616,9 @@
def new_file_path( self ):
return os.path.abspath( self.app.config.new_file_path )
+ def version_path( self ):
+ return self.job_wrapper.get_version_string_path()
+
class NoopQueue( object ):
"""
diff -r 88b86067a11d355159446bb98d75928149d08326 -r eb71ed51be40f3bfc523d278b67640bb280cd2e8 lib/galaxy/jobs/command_factory.py
--- a/lib/galaxy/jobs/command_factory.py
+++ b/lib/galaxy/jobs/command_factory.py
@@ -37,9 +37,9 @@
def __handle_version_command(commands_builder, job_wrapper):
# Prepend version string
- if job_wrapper.version_string_cmd:
- version_command = "%s > %s 2>&1" % ( job_wrapper.version_string_cmd, job_wrapper.get_version_string_path() )
- commands_builder.prepend_command(version_command)
+ write_version_cmd = job_wrapper.write_version_cmd
+ if write_version_cmd:
+ commands_builder.prepend_command(write_version_cmd)
def __handle_task_splitting(commands_builder, job_wrapper):
diff -r 88b86067a11d355159446bb98d75928149d08326 -r eb71ed51be40f3bfc523d278b67640bb280cd2e8 lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -387,6 +387,11 @@
self._working_directory = remote_job_config[ "working_directory" ]
self._sep = remote_job_config[ "system_properties" ][ "separator" ]
self._tool_dir = remote_job_config[ "tools_directory" ]
+ version_path = self.local_path_config.version_path()
+ new_version_path = self.path_mapper.remote_version_path_rewrite(version_path)
+ if new_version_path:
+ version_path = new_version_path
+ self._version_path = version_path
def output_paths( self ):
local_output_paths = self._wrapper_output_paths
@@ -428,3 +433,6 @@
def sep( self ):
return self._sep
+
+ def version_path( self ):
+ return self._version_path
diff -r 88b86067a11d355159446bb98d75928149d08326 -r eb71ed51be40f3bfc523d278b67640bb280cd2e8 test/unit/jobs/test_job_wrapper.py
--- a/test/unit/jobs/test_job_wrapper.py
+++ b/test/unit/jobs/test_job_wrapper.py
@@ -72,7 +72,7 @@
def test_prepare_sets_version_command(self):
with self._prepared_wrapper() as wrapper:
- assert TEST_VERSION_COMMAND == wrapper.version_string_cmd, wrapper.version_string_cmd
+ assert TEST_VERSION_COMMAND in wrapper.write_version_cmd, wrapper.write_version_cmd
class TaskWrapperTestCase(BaseWrapperTestCase, TestCase):
@@ -88,7 +88,7 @@
def test_prepare_sets_no_version_command(self):
with self._prepared_wrapper() as wrapper:
- assert wrapper.version_string_cmd is None
+ assert wrapper.write_version_cmd is None
class MockEvaluator(object):
diff -r 88b86067a11d355159446bb98d75928149d08326 -r eb71ed51be40f3bfc523d278b67640bb280cd2e8 test/unit/test_command_factory.py
--- a/test/unit/test_command_factory.py
+++ b/test/unit/test_command_factory.py
@@ -133,7 +133,7 @@
class MockJobWrapper(object):
def __init__(self):
- self.version_string_cmd = None
+ self.write_version_cmd = None
self.command_line = MOCK_COMMAND_LINE
self.dependency_shell_commands = []
self.metadata_line = None
https://bitbucket.org/galaxy/galaxy-central/commits/df387456caef/
Changeset: df387456caef
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Touch-up formatting of datatypes/metadata.py for clarity of subsequent changeset.
Affected #: 1 file
diff -r eb71ed51be40f3bfc523d278b67640bb280cd2e8 -r df387456caefdac23c073194fa5ed589409b7e9d lib/galaxy/datatypes/metadata.py
--- a/lib/galaxy/datatypes/metadata.py
+++ b/lib/galaxy/datatypes/metadata.py
@@ -629,6 +629,9 @@
def setup_external_metadata( self, datasets, sa_session, exec_dir=None, tmp_dir=None, dataset_files_path=None,
output_fnames=None, config_root=None, config_file=None, datatypes_config=None, job_metadata=None, kwds=None ):
kwds = kwds or {}
+ if tmp_dir is None:
+ tmp_dir = MetadataTempFile.tmp_dir
+
#fill in metadata_files_dict and return the command with args required to set metadata
def __metadata_files_list_to_cmd_line( metadata_files ):
def __get_filename_override():
@@ -637,13 +640,19 @@
if dataset_path.false_path and dataset_path.real_path == metadata_files.dataset.file_name:
return dataset_path.false_path
return ""
- return "%s,%s,%s,%s,%s,%s" % ( metadata_files.filename_in, metadata_files.filename_kwds, metadata_files.filename_out, metadata_files.filename_results_code, __get_filename_override(), metadata_files.filename_override_metadata )
+ line = "%s,%s,%s,%s,%s,%s" % (
+ metadata_files.filename_in,
+ metadata_files.filename_kwds,
+ metadata_files.filename_out,
+ metadata_files.filename_results_code,
+ __get_filename_override(),
+ metadata_files.filename_override_metadata
+ )
+ return line
if not isinstance( datasets, list ):
datasets = [ datasets ]
if exec_dir is None:
exec_dir = os.path.abspath( os.getcwd() )
- if tmp_dir is None:
- tmp_dir = MetadataTempFile.tmp_dir
if dataset_files_path is None:
dataset_files_path = galaxy.model.Dataset.file_path
if config_root is None:
https://bitbucket.org/galaxy/galaxy-central/commits/ff4b5fea89bf/
Changeset: ff4b5fea89bf
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Modify metadata command generation to allow different working directory path remotely.
Utilize in LWR to fixup remote metadata generation when command rewriting is disabled.
Affected #: 3 files
diff -r df387456caefdac23c073194fa5ed589409b7e9d -r ff4b5fea89bfa44958bfe76eab0ae9532896a0d7 lib/galaxy/datatypes/metadata.py
--- a/lib/galaxy/datatypes/metadata.py
+++ b/lib/galaxy/datatypes/metadata.py
@@ -18,6 +18,7 @@
import galaxy.model
from galaxy.util import listify, stringify_dictionary_keys, string_as_bool
from galaxy.util.odict import odict
+from galaxy.util import in_directory
from galaxy.web import form_builder
from sqlalchemy.orm import object_session
@@ -627,11 +628,21 @@
return "%s_%d" % ( dataset.__class__.__name__, dataset.id )
def setup_external_metadata( self, datasets, sa_session, exec_dir=None, tmp_dir=None, dataset_files_path=None,
- output_fnames=None, config_root=None, config_file=None, datatypes_config=None, job_metadata=None, kwds=None ):
+ output_fnames=None, config_root=None, config_file=None, datatypes_config=None, job_metadata=None, compute_tmp_dir=None, kwds=None ):
kwds = kwds or {}
if tmp_dir is None:
tmp_dir = MetadataTempFile.tmp_dir
+ # path is calculated for Galaxy, may be different on compute - rewrite
+ # for the compute server.
+ def metadata_path_on_compute(path):
+ compute_path = path
+ log.info(compute_tmp_dir)
+ if compute_tmp_dir and tmp_dir and in_directory(path, tmp_dir):
+ path_relative = os.path.relpath(path, tmp_dir)
+ compute_path = os.path.join(compute_tmp_dir, path_relative)
+ return compute_path
+
#fill in metadata_files_dict and return the command with args required to set metadata
def __metadata_files_list_to_cmd_line( metadata_files ):
def __get_filename_override():
@@ -641,13 +652,14 @@
return dataset_path.false_path
return ""
line = "%s,%s,%s,%s,%s,%s" % (
- metadata_files.filename_in,
- metadata_files.filename_kwds,
- metadata_files.filename_out,
- metadata_files.filename_results_code,
+ metadata_path_on_compute(metadata_files.filename_in),
+ metadata_path_on_compute(metadata_files.filename_kwds),
+ metadata_path_on_compute(metadata_files.filename_out),
+ metadata_path_on_compute(metadata_files.filename_results_code),
__get_filename_override(),
- metadata_files.filename_override_metadata
+ metadata_path_on_compute(metadata_files.filename_override_metadata),
)
+ log.info(line)
return line
if not isinstance( datasets, list ):
datasets = [ datasets ]
diff -r df387456caefdac23c073194fa5ed589409b7e9d -r ff4b5fea89bfa44958bfe76eab0ae9532896a0d7 lib/galaxy/jobs/command_factory.py
--- a/lib/galaxy/jobs/command_factory.py
+++ b/lib/galaxy/jobs/command_factory.py
@@ -79,6 +79,7 @@
config_root = metadata_kwds.get( 'config_root', None )
config_file = metadata_kwds.get( 'config_file', None )
datatypes_config = metadata_kwds.get( 'datatypes_config', None )
+ compute_tmp_dir = metadata_kwds.get( 'compute_tmp_dir', None )
metadata_command = job_wrapper.setup_external_metadata(
exec_dir=exec_dir,
tmp_dir=tmp_dir,
@@ -88,6 +89,7 @@
config_root=config_root,
config_file=config_file,
datatypes_config=datatypes_config,
+ compute_tmp_dir=compute_tmp_dir,
kwds={ 'overwrite' : False }
) or ''
metadata_command = metadata_command.strip()
diff -r df387456caefdac23c073194fa5ed589409b7e9d -r ff4b5fea89bfa44958bfe76eab0ae9532896a0d7 lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -351,8 +351,10 @@
metadata_kwds['exec_dir'] = remote_galaxy_home
outputs_directory = remote_job_config['outputs_directory']
configs_directory = remote_job_config['configs_directory']
+ working_directory = remote_job_config['working_directory']
outputs = [Bunch(false_path=os.path.join(outputs_directory, os.path.basename(path)), real_path=path) for path in self.get_output_files(job_wrapper)]
metadata_kwds['output_fnames'] = outputs
+ metadata_kwds['compute_tmp_dir'] = working_directory
metadata_kwds['config_root'] = remote_galaxy_home
default_config_file = os.path.join(remote_galaxy_home, 'universe_wsgi.ini')
metadata_kwds['config_file'] = remote_system_properties.get('galaxy_config_file', default_config_file)
https://bitbucket.org/galaxy/galaxy-central/commits/4e09c9e6e0d3/
Changeset: 4e09c9e6e0d3
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Refactor build_param_dict into smaller methods.
Affected #: 1 file
diff -r ff4b5fea89bfa44958bfe76eab0ae9532896a0d7 -r 4e09c9e6e0d3c47e4c05a5cfe77e07f39753bde0 lib/galaxy/tools/evaluation.py
--- a/lib/galaxy/tools/evaluation.py
+++ b/lib/galaxy/tools/evaluation.py
@@ -104,6 +104,15 @@
param_dict.update( incoming )
input_dataset_paths = dataset_path_rewrites( input_paths )
+ self.__populate_wrappers(param_dict, input_dataset_paths)
+ self.__populate_input_dataset_wrappers(param_dict, input_datasets, input_dataset_paths)
+ self.__populate_output_dataset_wrappers(param_dict, output_datasets, output_paths, job_working_directory)
+ self.__populate_non_job_params(param_dict)
+
+ # Return the dictionary of parameters
+ return param_dict
+
+ def __populate_wrappers(self, param_dict, input_dataset_paths):
def wrap_values( inputs, input_values ):
"""
@@ -186,6 +195,7 @@
if self.tool.check_values:
wrap_values( self.tool.inputs, param_dict )
+ def __populate_input_dataset_wrappers(self, param_dict, input_datasets, input_dataset_paths):
## FIXME: when self.check_values==True, input datasets are being wrapped
## twice (above and below, creating 2 separate
## DatasetFilenameWrapper objects - first is overwritten by
@@ -219,6 +229,8 @@
if data:
for child in data.children:
param_dict[ "_CHILD___%s___%s" % ( name, child.designation ) ] = DatasetFilenameWrapper( child )
+
+ def __populate_output_dataset_wrappers(self, param_dict, output_datasets, output_paths, job_working_directory):
output_dataset_paths = dataset_path_rewrites( output_paths )
for name, hda in output_datasets.items():
# Write outputs to the working directory (for security purposes)
@@ -242,6 +254,7 @@
# NoneDataset
param_dict[ out_name ] = NoneDataset( datatypes_registry=self.app.datatypes_registry, ext=output.format )
+ def __populate_non_job_params(self, param_dict):
# -- Add useful attributes/functions for use in creating command line.
# Function for querying a data table.
@@ -271,8 +284,6 @@
param_dict['__datatypes_config__'] = param_dict['GALAXY_DATATYPES_CONF_FILE'] = self.app.datatypes_registry.integrated_datatypes_configs
param_dict['__admin_users__'] = self.app.config.admin_users
param_dict['__user__'] = RawObjectWrapper( param_dict.get( '__user__', None ) )
- # Return the dictionary of parameters
- return param_dict
def build( self ):
"""
https://bitbucket.org/galaxy/galaxy-central/commits/5804d9d32de8/
Changeset: 5804d9d32de8
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Refactor wrapping values in build_param_dict.
Split wrap_values into two methods - a reusable method for walking the tool param tree that takes in the second method which actually wraps the values at a given "leaf".
Affected #: 1 file
diff -r 4e09c9e6e0d3c47e4c05a5cfe77e07f39753bde0 -r 5804d9d32de843205970ae5783071eb8ef38ade5 lib/galaxy/tools/evaluation.py
--- a/lib/galaxy/tools/evaluation.py
+++ b/lib/galaxy/tools/evaluation.py
@@ -112,88 +112,95 @@
# Return the dictionary of parameters
return param_dict
- def __populate_wrappers(self, param_dict, input_dataset_paths):
+ def __walk_inputs(self, inputs, input_values, func):
- def wrap_values( inputs, input_values ):
+ def do_walk( inputs, input_values ):
"""
Wraps parameters as neccesary.
"""
for input in inputs.itervalues():
if isinstance( input, Repeat ):
for d in input_values[ input.name ]:
- wrap_values( input.inputs, d )
+ do_walk( input.inputs, d )
elif isinstance( input, Conditional ):
values = input_values[ input.name ]
current = values["__current_case__"]
- wrap_values( input.cases[current].inputs, values )
- elif isinstance( input, DataToolParameter ) and input.multiple:
- input_values[ input.name ] = \
- DatasetListWrapper( input_values[ input.name ],
- dataset_paths=input_dataset_paths,
- datatypes_registry=self.app.datatypes_registry,
- tool=self.tool,
- name=input.name )
- elif isinstance( input, DataToolParameter ):
- ## FIXME: We're populating param_dict with conversions when
- ## wrapping values, this should happen as a separate
- ## step before wrapping (or call this wrapping step
- ## something more generic) (but iterating this same
- ## list twice would be wasteful)
- # Add explicit conversions by name to current parent
- for conversion_name, conversion_extensions, conversion_datatypes in input.conversions:
- # If we are at building cmdline step, then converters
- # have already executed
- conv_ext, converted_dataset = input_values[ input.name ].find_conversion_destination( conversion_datatypes )
- # When dealing with optional inputs, we'll provide a
- # valid extension to be used for None converted dataset
- if not conv_ext:
- conv_ext = conversion_extensions[0]
- # input_values[ input.name ] is None when optional
- # dataset, 'conversion' of optional dataset should
- # create wrapper around NoneDataset for converter output
- if input_values[ input.name ] and not converted_dataset:
- # Input that converter is based from has a value,
- # but converted dataset does not exist
- raise Exception( 'A path for explicit datatype conversion has not been found: %s --/--> %s'
- % ( input_values[ input.name ].extension, conversion_extensions ) )
- else:
- # Trick wrapper into using target conv ext (when
- # None) without actually being a tool parameter
- input_values[ conversion_name ] = \
- DatasetFilenameWrapper( converted_dataset,
- datatypes_registry=self.app.datatypes_registry,
- tool=Bunch( conversion_name=Bunch( extensions=conv_ext ) ),
- name=conversion_name )
- # Wrap actual input dataset
- dataset = input_values[ input.name ]
- wrapper_kwds = dict(
- datatypes_registry=self.app.datatypes_registry,
- tool=self,
- name=input.name
- )
- real_path = dataset.file_name
- if real_path in input_dataset_paths:
- wrapper_kwds[ "dataset_path" ] = input_dataset_paths[ real_path ]
- input_values[ input.name ] = \
- DatasetFilenameWrapper( dataset, **wrapper_kwds )
- elif isinstance( input, SelectToolParameter ):
- input_values[ input.name ] = SelectToolParameterWrapper(
- input, input_values[ input.name ], self.app, other_values=param_dict )
+ do_walk( input.cases[current].inputs, values )
+ else:
+ func( input_values, input )
- elif isinstance( input, LibraryDatasetToolParameter ):
- # TODO: Handle input rewrites in here? How to test LibraryDatasetToolParameters?
- input_values[ input.name ] = LibraryDatasetValueWrapper(
- input, input_values[ input.name ], param_dict )
+ do_walk( inputs, input_values )
- else:
- input_values[ input.name ] = InputValueWrapper(
- input, input_values[ input.name ], param_dict )
+ def __populate_wrappers(self, param_dict, input_dataset_paths):
+
+ def wrap_input( input_values, input ):
+ if isinstance( input, DataToolParameter ) and input.multiple:
+ input_values[ input.name ] = \
+ DatasetListWrapper( input_values[ input.name ],
+ dataset_paths=input_dataset_paths,
+ datatypes_registry=self.app.datatypes_registry,
+ tool=self.tool,
+ name=input.name )
+ elif isinstance( input, DataToolParameter ):
+ ## FIXME: We're populating param_dict with conversions when
+ ## wrapping values, this should happen as a separate
+ ## step before wrapping (or call this wrapping step
+ ## something more generic) (but iterating this same
+ ## list twice would be wasteful)
+ # Add explicit conversions by name to current parent
+ for conversion_name, conversion_extensions, conversion_datatypes in input.conversions:
+ # If we are at building cmdline step, then converters
+ # have already executed
+ conv_ext, converted_dataset = input_values[ input.name ].find_conversion_destination( conversion_datatypes )
+ # When dealing with optional inputs, we'll provide a
+ # valid extension to be used for None converted dataset
+ if not conv_ext:
+ conv_ext = conversion_extensions[0]
+ # input_values[ input.name ] is None when optional
+ # dataset, 'conversion' of optional dataset should
+ # create wrapper around NoneDataset for converter output
+ if input_values[ input.name ] and not converted_dataset:
+ # Input that converter is based from has a value,
+ # but converted dataset does not exist
+ raise Exception( 'A path for explicit datatype conversion has not been found: %s --/--> %s'
+ % ( input_values[ input.name ].extension, conversion_extensions ) )
+ else:
+ # Trick wrapper into using target conv ext (when
+ # None) without actually being a tool parameter
+ input_values[ conversion_name ] = \
+ DatasetFilenameWrapper( converted_dataset,
+ datatypes_registry=self.app.datatypes_registry,
+ tool=Bunch( conversion_name=Bunch( extensions=conv_ext ) ),
+ name=conversion_name )
+ # Wrap actual input dataset
+ dataset = input_values[ input.name ]
+ wrapper_kwds = dict(
+ datatypes_registry=self.app.datatypes_registry,
+ tool=self,
+ name=input.name
+ )
+ real_path = dataset.file_name
+ if real_path in input_dataset_paths:
+ wrapper_kwds[ "dataset_path" ] = input_dataset_paths[ real_path ]
+ input_values[ input.name ] = \
+ DatasetFilenameWrapper( dataset, **wrapper_kwds )
+ elif isinstance( input, SelectToolParameter ):
+ input_values[ input.name ] = SelectToolParameterWrapper(
+ input, input_values[ input.name ], self.app, other_values=param_dict )
+
+ elif isinstance( input, LibraryDatasetToolParameter ):
+ # TODO: Handle input rewrites in here? How to test LibraryDatasetToolParameters?
+ input_values[ input.name ] = LibraryDatasetValueWrapper(
+ input, input_values[ input.name ], param_dict )
+ else:
+ input_values[ input.name ] = InputValueWrapper(
+ input, input_values[ input.name ], param_dict )
# HACK: only wrap if check_values is not false, this deals with external
# tools where the inputs don't even get passed through. These
# tools (e.g. UCSC) should really be handled in a special way.
if self.tool.check_values:
- wrap_values( self.tool.inputs, param_dict )
+ self.__walk_inputs( self.tool.inputs, param_dict, wrap_input )
def __populate_input_dataset_wrappers(self, param_dict, input_datasets, input_dataset_paths):
## FIXME: when self.check_values==True, input datasets are being wrapped
https://bitbucket.org/galaxy/galaxy-central/commits/a0bf1d3b27af/
Changeset: a0bf1d3b27af
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Allow ComputeEnvironment to rewrite 'arbitrary' paths.
Previous changes enabled targetted rewriting of specific kinds of paths - working directory, inputs, outputs, extra files, version path, etc.... This change allows rewriting remaining 'unstructured' paths - namely data indices.
Right now tool evaluation framework uses this capability only for SelectParameter values and fields - which is where these paths will be for data indices. Changeset lays out the recipe for doing this and the functionality could easily be extended for arbitrary parameters or other specific kinds of inputs.
The default ComputeEnvironment does not rewrite any paths obviously, but the abstract base class docstring lays out how to extend a ComputeEnvironment to do this:
def unstructured_path_rewriter( self ):
""" Return a function that takes in a value, determines if it is path
to be rewritten (will be passed non-path values as well - onus is on
this function to determine both if its input is a path and if it should
be rewritten.)
"""
The LwrComputeEnviroment has been updated to provide such a rewriter - it will rewrite such paths, and create a dict of paths that need to be transferred, etc.... The LWR server and client side infrastructure that enables this can be found in this changeset - https://bitbucket.org/jmchilton/lwr/commits/63981e79696337399edb42be5614bc7….
This changeset includes tests for changes to wrappers and the tool evaluation module to enable this.
Affected #: 7 files
diff -r 5804d9d32de843205970ae5783071eb8ef38ade5 -r a0bf1d3b27af23cc6429681b25220bd5560b1545 lib/galaxy/jobs/__init__.py
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -1583,6 +1583,14 @@
def version_path( self ):
""" Location of the version file for the underlying tool. """
+ @abstractmethod
+ def unstructured_path_rewriter( self ):
+ """ Return a function that takes in a value, determines if it is path
+ to be rewritten (will be passed non-path values as well - onus is on
+ this function to determine both if its input is a path and if it should
+ be rewritten.)
+ """
+
class SimpleComputeEnvironment( object ):
@@ -1592,6 +1600,9 @@
def sep( self ):
return os.path.sep
+ def unstructured_path_rewriter( self ):
+ return lambda v: v
+
class SharedComputeEnvironment( SimpleComputeEnvironment ):
""" Default ComputeEnviornment for job and task wrapper to pass
diff -r 5804d9d32de843205970ae5783071eb8ef38ade5 -r a0bf1d3b27af23cc6429681b25220bd5560b1545 lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -67,7 +67,7 @@
def queue_job(self, job_wrapper):
job_destination = job_wrapper.job_destination
- command_line, client, remote_job_config = self.__prepare_job( job_wrapper, job_destination )
+ command_line, client, remote_job_config, compute_environment = self.__prepare_job( job_wrapper, job_destination )
if not command_line:
return
@@ -77,6 +77,10 @@
remote_dependency_resolution = dependency_resolution == "remote"
requirements = job_wrapper.tool.requirements if remote_dependency_resolution else []
rewrite_paths = not LwrJobRunner.__rewrite_parameters( client )
+ unstructured_path_rewrites = {}
+ if compute_environment:
+ unstructured_path_rewrites = compute_environment.unstructured_path_rewrites
+
client_job_description = ClientJobDescription(
command_line=command_line,
output_files=self.get_output_files(job_wrapper),
@@ -87,6 +91,7 @@
requirements=requirements,
version_file=job_wrapper.get_version_string_path(),
rewrite_paths=rewrite_paths,
+ arbitrary_files=unstructured_path_rewrites,
)
job_id = lwr_submit_job(client, client_job_description, remote_job_config)
log.info("lwr job submitted with job_id %s" % job_id)
@@ -110,6 +115,7 @@
command_line = None
client = None
remote_job_config = None
+ compute_environment = None
try:
client = self.get_client_from_wrapper(job_wrapper)
tool = job_wrapper.tool
@@ -145,7 +151,7 @@
if not command_line:
job_wrapper.finish( '', '' )
- return command_line, client, remote_job_config
+ return command_line, client, remote_job_config, compute_environment
def __prepare_input_files_locally(self, job_wrapper):
"""Run task splitting commands locally."""
@@ -380,6 +386,7 @@
self.lwr_client = lwr_client
self.job_wrapper = job_wrapper
self.local_path_config = job_wrapper.default_compute_environment()
+ self.unstructured_path_rewrites = {}
# job_wrapper.prepare is going to expunge the job backing the following
# computations, so precalculate these paths.
self._wrapper_input_paths = self.local_path_config.input_paths()
@@ -438,3 +445,24 @@
def version_path( self ):
return self._version_path
+
+ def rewriter( self, parameter_value ):
+ unstructured_path_rewrites = self.unstructured_path_rewrites
+ if parameter_value in unstructured_path_rewrites:
+ # Path previously mapped, use previous mapping.
+ return unstructured_path_rewrites[ parameter_value ]
+ if parameter_value in unstructured_path_rewrites.itervalues():
+ # Path is a rewritten remote path (this might never occur,
+ # consider dropping check...)
+ return parameter_value
+
+ rewrite, new_unstructured_path_rewrites = self.path_mapper.check_for_arbitrary_rewrite( parameter_value )
+ if rewrite:
+ unstructured_path_rewrites.update(new_unstructured_path_rewrites)
+ return rewrite
+ else:
+ # Did need to rewrite, use original path or value.
+ return parameter_value
+
+ def unstructured_path_rewriter( self ):
+ return self.rewriter
diff -r 5804d9d32de843205970ae5783071eb8ef38ade5 -r a0bf1d3b27af23cc6429681b25220bd5560b1545 lib/galaxy/tools/evaluation.py
--- a/lib/galaxy/tools/evaluation.py
+++ b/lib/galaxy/tools/evaluation.py
@@ -39,6 +39,7 @@
for evaluating command and config cheetah templates.
"""
self.compute_environment = compute_environment
+ self.unstructured_path_rewriter = compute_environment.unstructured_path_rewriter()
job = self.job
incoming = dict( [ ( p.name, p.value ) for p in job.parameters ] )
@@ -107,6 +108,7 @@
self.__populate_wrappers(param_dict, input_dataset_paths)
self.__populate_input_dataset_wrappers(param_dict, input_datasets, input_dataset_paths)
self.__populate_output_dataset_wrappers(param_dict, output_datasets, output_paths, job_working_directory)
+ self.__populate_unstructured_path_rewrites(param_dict)
self.__populate_non_job_params(param_dict)
# Return the dictionary of parameters
@@ -186,7 +188,7 @@
DatasetFilenameWrapper( dataset, **wrapper_kwds )
elif isinstance( input, SelectToolParameter ):
input_values[ input.name ] = SelectToolParameterWrapper(
- input, input_values[ input.name ], self.app, other_values=param_dict )
+ input, input_values[ input.name ], self.app, other_values=param_dict, path_rewriter=self.unstructured_path_rewriter )
elif isinstance( input, LibraryDatasetToolParameter ):
# TODO: Handle input rewrites in here? How to test LibraryDatasetToolParameters?
@@ -292,6 +294,18 @@
param_dict['__admin_users__'] = self.app.config.admin_users
param_dict['__user__'] = RawObjectWrapper( param_dict.get( '__user__', None ) )
+ def __populate_unstructured_path_rewrites(self, param_dict):
+
+ def rewrite_unstructured_paths( input_values, input ):
+ if isinstance( input, SelectToolParameter ):
+ input_values[ input.name ] = SelectToolParameterWrapper(
+ input, input_values[ input.name ], self.app, other_values=param_dict, path_rewriter=self.unstructured_path_rewriter )
+
+ if not self.tool.check_values and self.unstructured_path_rewriter:
+ # The tools weren't "wrapped" yet, but need to be in order to get
+ #the paths rewritten.
+ self.__walk_inputs( self.tool.inputs, param_dict, rewrite_unstructured_paths )
+
def build( self ):
"""
Build runtime description of job to execute, evaluate command and
diff -r 5804d9d32de843205970ae5783071eb8ef38ade5 -r a0bf1d3b27af23cc6429681b25220bd5560b1545 lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -631,6 +631,9 @@
return None
+DEFAULT_VALUE_MAP = lambda x: x
+
+
class SelectToolParameter( ToolParameter ):
"""
Parameter that takes on one (or many) or a specific set of values.
@@ -827,7 +830,7 @@
else:
return str( value )
- def to_param_dict_string( self, value, other_values={} ):
+ def to_param_dict_string( self, value, other_values={}, value_map=DEFAULT_VALUE_MAP ):
if value is None:
return "None"
if isinstance( value, list ):
@@ -842,7 +845,9 @@
else:
value = sanitize_param( value )
if isinstance( value, list ):
- value = self.separator.join( value )
+ value = self.separator.join( map( value_map, value ) )
+ else:
+ value = value_map( value )
return value
def value_to_basic( self, value, app ):
@@ -1425,7 +1430,7 @@
rval.append( val )
return rval
- def to_param_dict_string( self, value, other_values={} ):
+ def to_param_dict_string( self, value, other_values={}, value_map=DEFAULT_VALUE_MAP ):
def get_options_list( value ):
def get_base_option( value, options ):
for option in options:
@@ -1456,7 +1461,7 @@
if len( rval ) > 1:
if not( self.repeat ):
assert self.multiple, "Multiple values provided but parameter is not expecting multiple values"
- rval = self.separator.join( rval )
+ rval = self.separator.join( map( value_map, rval ) )
if self.tool is None or self.tool.options.sanitize:
if self.sanitizer:
rval = self.sanitizer.sanitize_param( rval )
diff -r 5804d9d32de843205970ae5783071eb8ef38ade5 -r a0bf1d3b27af23cc6429681b25220bd5560b1545 lib/galaxy/tools/wrappers.py
--- a/lib/galaxy/tools/wrappers.py
+++ b/lib/galaxy/tools/wrappers.py
@@ -84,6 +84,9 @@
return getattr( self.value, key )
+DEFAULT_PATH_REWRITER = lambda x: x
+
+
class SelectToolParameterWrapper( ToolParameterValueWrapper ):
"""
Wraps a SelectTooParameter so that __str__ returns the selected value, but all other
@@ -95,26 +98,28 @@
Provide access to any field by name or index for this particular value.
Only applicable for dynamic_options selects, which have more than simple 'options' defined (name, value, selected).
"""
- def __init__( self, input, value, other_values ):
+ def __init__( self, input, value, other_values, path_rewriter ):
self._input = input
self._value = value
self._other_values = other_values
self._fields = {}
+ self._path_rewriter = path_rewriter
def __getattr__( self, name ):
if name not in self._fields:
self._fields[ name ] = self._input.options.get_field_by_name_for_value( name, self._value, None, self._other_values )
- return self._input.separator.join( map( str, self._fields[ name ] ) )
+ return self._input.separator.join( map( self._path_rewriter, map( str, self._fields[ name ] ) ) )
- def __init__( self, input, value, app, other_values={} ):
+ def __init__( self, input, value, app, other_values={}, path_rewriter=None ):
self.input = input
self.value = value
self.input.value_label = input.value_to_display_text( value, app )
self._other_values = other_values
- self.fields = self.SelectToolParameterFieldWrapper( input, value, other_values )
+ self._path_rewriter = path_rewriter or DEFAULT_PATH_REWRITER
+ self.fields = self.SelectToolParameterFieldWrapper( input, value, other_values, self._path_rewriter )
def __str__( self ):
- return self.input.to_param_dict_string( self.value, other_values=self._other_values )
+ return self.input.to_param_dict_string( self.value, other_values=self._other_values, value_map=self._path_rewriter )
def __getattr__( self, key ):
return getattr( self.input, key )
diff -r 5804d9d32de843205970ae5783071eb8ef38ade5 -r a0bf1d3b27af23cc6429681b25220bd5560b1545 test/unit/tools/test_evaluation.py
--- a/test/unit/tools/test_evaluation.py
+++ b/test/unit/tools/test_evaluation.py
@@ -114,6 +114,34 @@
assert open( config_filename, "r").read() == "4"
self.assertEquals(command_line, "prog1 %s" % config_filename)
+ def test_arbitrary_path_rewriting_wrapped( self ):
+ self.tool.check_values = True
+ self.__test_arbitrary_path_rewriting()
+
+ def test_arbitrary_path_rewriting_unwrapped( self ):
+ self.tool.check_values = False
+ self.__test_arbitrary_path_rewriting()
+
+ def __test_arbitrary_path_rewriting( self ):
+ self.job.parameters = [ JobParameter( name="index_path", value="\"/old/path/human\"" ) ]
+ xml = XML('''<param name="index_path" type="select">
+ <option value="/old/path/human">Human</option>
+ <option value="/old/path/mouse">Mouse</option>
+ </param>''')
+ parameter = SelectToolParameter( self.tool, xml )
+ self.tool.set_params( {
+ "index_path": parameter
+ } )
+ self.tool._command_line = "prog1 $index_path"
+
+ def test_path_rewriter(v):
+ if v:
+ v = v.replace("/old", "/new")
+ return v
+ self._set_compute_environment(path_rewriter=test_path_rewriter)
+ command_line, extra_filenames = self.evaluator.build( )
+ self.assertEquals(command_line, "prog1 /new/path/human")
+
def test_template_property_app( self ):
self._assert_template_property_is("$__app__.config.new_file_path", self.app.config.new_file_path)
@@ -171,11 +199,13 @@
working_directory,
input_paths=[ '/galaxy/files/dataset_1.dat' ],
output_paths=[ '/galaxy/files/dataset_2.dat' ],
+ path_rewriter=None
):
self._new_file_path = new_file_path
self._working_directory = working_directory
self._input_paths = input_paths
self._output_paths = output_paths
+ self._path_rewriter = path_rewriter
def input_paths( self ):
return self._input_paths
@@ -189,6 +219,12 @@
def new_file_path(self):
return self._new_file_path
+ def unstructured_path_rewriter(self):
+ if self._path_rewriter:
+ return self._path_rewriter
+ else:
+ return super(TestComputeEnviornment, self).unstructured_path_rewriter()
+
class MockTool( object ):
diff -r 5804d9d32de843205970ae5783071eb8ef38ade5 -r a0bf1d3b27af23cc6429681b25220bd5560b1545 test/unit/tools/test_wrappers.py
--- a/test/unit/tools/test_wrappers.py
+++ b/test/unit/tools/test_wrappers.py
@@ -62,6 +62,21 @@
assert wrapper.fields.name == "name1,name2"
+@with_mock_tool
+def test_select_wrapper_with_path_rewritting(tool):
+ parameter = _setup_blast_tool(tool, multiple=True)
+ wrapper = SelectToolParameterWrapper( parameter, ["val1", "val2"], tool.app, other_values={}, path_rewriter=lambda v: "Rewrite<%s>" % v )
+ assert str(wrapper) == "Rewrite<val1>,Rewrite<val2>"
+ assert wrapper.fields.path == "Rewrite<path1>,Rewrite<path2>"
+
+
+@with_mock_tool
+def test_select_wrapper_drilldown_path_rewritting(tool):
+ parameter = _drilldown_parameter(tool)
+ wrapper = SelectToolParameterWrapper( parameter, ["option3"], tool.app, other_values={}, path_rewriter=lambda v: "Rewrite<%s>" % v )
+ assert str(wrapper) == "Rewrite<option3>"
+
+
def test_raw_object_wrapper():
obj = Bunch(x=4)
wrapper = RawObjectWrapper(obj)
https://bitbucket.org/galaxy/galaxy-central/commits/be47cdc22244/
Changeset: be47cdc22244
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Job related PEP-8/Galaxy style adjustments.
Affected #: 2 files
diff -r a0bf1d3b27af23cc6429681b25220bd5560b1545 -r be47cdc22244f5499814ad60b3b073a2c6415e19 lib/galaxy/jobs/handler.py
--- a/lib/galaxy/jobs/handler.py
+++ b/lib/galaxy/jobs/handler.py
@@ -20,6 +20,7 @@
JOB_WAIT, JOB_ERROR, JOB_INPUT_ERROR, JOB_INPUT_DELETED, JOB_READY, JOB_DELETED, JOB_ADMIN_DELETED, JOB_USER_OVER_QUOTA = 'wait', 'error', 'input_error', 'input_deleted', 'ready', 'deleted', 'admin_deleted', 'user_over_quota'
DEFAULT_JOB_PUT_FAILURE_MESSAGE = 'Unable to run job due to a misconfiguration of the Galaxy job running system. Please contact a site administrator.'
+
class JobHandler( object ):
"""
Handle the preparation, running, tracking, and finishing of jobs
@@ -31,12 +32,15 @@
# Queues for starting and stopping jobs
self.job_queue = JobHandlerQueue( app, self.dispatcher )
self.job_stop_queue = JobHandlerStopQueue( app, self.dispatcher )
+
def start( self ):
self.job_queue.start()
+
def shutdown( self ):
self.job_queue.shutdown()
self.job_stop_queue.shutdown()
+
class JobHandlerQueue( object ):
"""
Job manager, waits for jobs to be runnable and then dispatches to
@@ -95,14 +99,14 @@
| ( model.Job.state == model.Job.states.RUNNING ) \
| ( model.Job.state == model.Job.states.QUEUED ) ) \
& ( model.Job.handler == self.app.config.server_name ) \
- & or_( ( model.Job.user_id == None ),( model.User.active == True ) ) ).all()
+ & or_( ( model.Job.user_id == None ), ( model.User.active == True ) ) ).all()
else:
jobs_at_startup = self.sa_session.query( model.Job ).enable_eagerloads( False ) \
.filter( ( ( model.Job.state == model.Job.states.NEW ) \
| ( model.Job.state == model.Job.states.RUNNING ) \
| ( model.Job.state == model.Job.states.QUEUED ) ) \
& ( model.Job.handler == self.app.config.server_name ) ).all()
-
+
for job in jobs_at_startup:
if job.tool_id not in self.app.toolbox.tools_by_id:
log.warning( "(%s) Tool '%s' removed from tool config, unable to recover job" % ( job.id, job.tool_id ) )
@@ -160,7 +164,7 @@
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. If the job
+ it is marked as having errors and removed from the queue. If the job
belongs to an inactive user it is ignored.
Otherwise, the job is dispatched.
"""
@@ -174,11 +178,11 @@
.join(model.JobToInputDatasetAssociation) \
.join(model.HistoryDatasetAssociation) \
.join(model.Dataset) \
- .filter(and_((model.Job.state == model.Job.states.NEW),
- or_((model.HistoryDatasetAssociation._state == model.HistoryDatasetAssociation.states.FAILED_METADATA),
- (model.HistoryDatasetAssociation.deleted == True ),
- (model.Dataset.state != model.Dataset.states.OK ),
- (model.Dataset.deleted == True)))).subquery()
+ .filter(and_( (model.Job.state == model.Job.states.NEW ),
+ or_( ( model.HistoryDatasetAssociation._state == model.HistoryDatasetAssociation.states.FAILED_METADATA ),
+ ( model.HistoryDatasetAssociation.deleted == True ),
+ ( model.Dataset.state != model.Dataset.states.OK ),
+ ( model.Dataset.deleted == True) ) ) ).subquery()
ldda_not_ready = self.sa_session.query(model.Job.id).enable_eagerloads(False) \
.join(model.JobToInputLibraryDatasetAssociation) \
.join(model.LibraryDatasetDatasetAssociation) \
@@ -192,7 +196,7 @@
jobs_to_check = self.sa_session.query(model.Job).enable_eagerloads(False) \
.outerjoin( model.User ) \
.filter(and_((model.Job.state == model.Job.states.NEW),
- or_((model.Job.user_id == None),(model.User.active == True)),
+ or_((model.Job.user_id == None), (model.User.active == True)),
(model.Job.handler == self.app.config.server_name),
~model.Job.table.c.id.in_(hda_not_ready),
~model.Job.table.c.id.in_(ldda_not_ready))) \
@@ -311,7 +315,7 @@
try:
self.job_wrappers[job.id].job_destination
except Exception, e:
- failure_message = getattr(e, 'failure_message', DEFAULT_JOB_PUT_FAILURE_MESSAGE )
+ failure_message = getattr( e, 'failure_message', DEFAULT_JOB_PUT_FAILURE_MESSAGE )
if failure_message == DEFAULT_JOB_PUT_FAILURE_MESSAGE:
log.exception( 'Failed to generate job destination' )
else:
@@ -328,7 +332,7 @@
if usage > quota:
return JOB_USER_OVER_QUOTA
except AssertionError, e:
- pass # No history, should not happen with an anon user
+ pass # No history, should not happen with an anon user
return state
def __clear_user_job_count( self ):
@@ -465,11 +469,13 @@
log.info( "job handler queue stopped" )
self.dispatcher.shutdown()
+
class JobHandlerStopQueue( object ):
"""
A queue for jobs which need to be terminated prematurely.
"""
STOP_SIGNAL = object()
+
def __init__( self, app, dispatcher ):
self.app = app
self.dispatcher = dispatcher
@@ -562,7 +568,9 @@
self.sleeper.wake()
log.info( "job handler stop queue stopped" )
+
class DefaultJobDispatcher( object ):
+
def __init__( self, app ):
self.app = app
self.job_runners = self.app.job_config.get_job_runner_plugins( self.app.config.server_name )
@@ -597,9 +605,9 @@
try:
if isinstance(job_wrapper, TaskWrapper):
#DBTODO Refactor
- log.debug( "(%s) Dispatching task %s to %s runner" %( job_wrapper.job_id, job_wrapper.task_id, runner_name ) )
+ log.debug( "(%s) Dispatching task %s to %s runner" % ( job_wrapper.job_id, job_wrapper.task_id, runner_name ) )
else:
- log.debug( "(%s) Dispatching to %s runner" %( job_wrapper.job_id, runner_name ) )
+ log.debug( "(%s) Dispatching to %s runner" % ( job_wrapper.job_id, runner_name ) )
self.job_runners[runner_name].put( job_wrapper )
except KeyError:
log.error( 'put(): (%s) Invalid job runner: %s' % ( job_wrapper.job_id, runner_name ) )
@@ -626,9 +634,9 @@
# If we're stopping a task, then the runner_name may be
# None, in which case it hasn't been scheduled.
if ( None != job.get_job_runner_name() ):
- runner_name = (job.get_job_runner_name().split(":",1))[0]
+ runner_name = ( job.get_job_runner_name().split( ":", 1 ) )[ 0 ]
if ( isinstance( job, model.Job ) ):
- log.debug( "stopping job %d in %s runner" %( job.get_id(), runner_name ) )
+ log.debug( "stopping job %d in %s runner" % ( job.get_id(), runner_name ) )
elif ( isinstance( job, model.Task ) ):
log.debug( "Stopping job %d, task %d in %s runner"
% ( job.get_job().get_id(), job.get_id(), runner_name ) )
@@ -640,7 +648,7 @@
def recover( self, job, job_wrapper ):
runner_name = ( job.job_runner_name.split(":", 1) )[0]
- log.debug( "recovering job %d in %s runner" %( job.get_id(), runner_name ) )
+ log.debug( "recovering job %d in %s runner" % ( job.get_id(), runner_name ) )
try:
self.job_runners[runner_name].recover( job, job_wrapper )
except KeyError:
diff -r a0bf1d3b27af23cc6429681b25220bd5560b1545 -r be47cdc22244f5499814ad60b3b073a2c6415e19 lib/galaxy/jobs/runners/__init__.py
--- a/lib/galaxy/jobs/runners/__init__.py
+++ b/lib/galaxy/jobs/runners/__init__.py
@@ -25,7 +25,7 @@
class RunnerParams( object ):
- def __init__( self, specs = None, params = None ):
+ def __init__( self, specs=None, params=None ):
self.specs = specs or dict()
self.params = params or dict()
for name, value in self.params.items():
@@ -51,12 +51,12 @@
self.app = app
self.sa_session = app.model.context
self.nworkers = nworkers
- runner_param_specs = dict( recheck_missing_job_retries = dict( map = int, valid = lambda x: x >= 0, default = 0 ) )
+ runner_param_specs = dict( recheck_missing_job_retries=dict( map=int, valid=lambda x: x >= 0, default=0 ) )
if 'runner_param_specs' in kwargs:
runner_param_specs.update( kwargs.pop( 'runner_param_specs' ) )
if kwargs:
log.debug( 'Loading %s with params: %s', self.runner_name, kwargs )
- self.runner_params = RunnerParams( specs = runner_param_specs, params = kwargs )
+ self.runner_params = RunnerParams( specs=runner_param_specs, params=kwargs )
def _init_worker_threads(self):
"""Start ``nworkers`` worker threads.
@@ -138,12 +138,12 @@
# Make sure the job hasn't been deleted
if job_state == model.Job.states.DELETED:
- log.debug( "(%s) Job deleted by user before it entered the %s queue" % ( job_id, self.runner_name ) )
+ log.debug( "(%s) Job deleted by user before it entered the %s queue" % ( job_id, self.runner_name ) )
if self.app.config.cleanup_job in ( "always", "onsuccess" ):
job_wrapper.cleanup()
return False
elif job_state != model.Job.states.QUEUED:
- log.info( "(%s) Job is in state %s, skipping execution" % ( job_id, job_state ) )
+ log.info( "(%s) Job is in state %s, skipping execution" % ( job_id, job_state ) )
# cleanup may not be safe in all states
return False
@@ -318,6 +318,7 @@
if attribute not in self.cleanup_file_attributes:
self.cleanup_file_attributes.append( attribute )
+
class AsynchronousJobRunner( BaseJobRunner ):
"""Parent class for any job runner that runs jobs asynchronously (e.g. via
a distributed resource manager). Provides general methods for having a
@@ -364,7 +365,7 @@
# Iterate over the list of watched jobs and check state
try:
self.check_watched_items()
- except Exception, e:
+ except Exception:
log.exception('Unhandled exception checking active jobs')
# Sleep a bit before the next state check
time.sleep( 1 )
https://bitbucket.org/galaxy/galaxy-central/commits/28d43f4b2eb9/
Changeset: 28d43f4b2eb9
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Introduce DatasetParamContext to simplify DatasetToolParameter...
This reduces code duplication related dataset_collectors now and abstracts out important functionality I reuse to collect dataset collections downstream.
Affected #: 2 files
diff -r be47cdc22244f5499814ad60b3b073a2c6415e19 -r 28d43f4b2eb915e7caf56d34e79556b56772b652 lib/galaxy/tools/parameters/basic.py
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -16,6 +16,7 @@
from sanitize import ToolParameterSanitizer
import validation
import dynamic_options
+from .dataset_util import DatasetParamContext
# For BaseURLToolParameter
from galaxy.web import url_for
from galaxy.model.item_attrs import Dictifiable
@@ -1610,52 +1611,36 @@
self.conversions.append( ( name, conv_extensions, conv_types ) )
def get_html_field( self, trans=None, value=None, other_values={} ):
- filter_value = None
- if self.options:
- try:
- filter_value = self.options.get_options( trans, other_values )[0][0]
- except IndexError:
- pass # no valid options
history = self._get_history( trans )
if value is not None:
if type( value ) != list:
value = [ value ]
+ dataset_param_context = DatasetParamContext( trans, history, self, value, other_values )
field = form_builder.SelectField( self.name, self.multiple, None, self.refresh_on_change, refresh_on_change_values=self.refresh_on_change_values )
# CRUCIAL: the dataset_collector function needs to be local to DataToolParameter.get_html_field()
def dataset_collector( hdas, parent_hid ):
- current_user_roles = trans.get_current_user_roles()
for i, hda in enumerate( hdas ):
hda_name = hda.name
if parent_hid is not None:
hid = "%s.%d" % ( parent_hid, i + 1 )
else:
hid = str( hda.hid )
- if not hda.dataset.state in [galaxy.model.Dataset.states.ERROR, galaxy.model.Dataset.states.DISCARDED] and \
- ( hda.visible or ( value and hda in value and not hda.implicitly_converted_parent_datasets ) ) and \
- trans.app.security_agent.can_access_dataset( current_user_roles, hda.dataset ):
- # If we are sending data to an external application, then we need to make sure there are no roles
- # associated with the dataset that restrict it's access from "public".
- if self.tool and self.tool.tool_type == 'data_destination' and not trans.app.security_agent.dataset_is_public( hda.dataset ):
- continue
- if self.options and self._options_filter_attribute( hda ) != filter_value:
- continue
- if hda.datatype.matches_any( self.formats ):
- selected = ( value and ( hda in value ) )
- if hda.visible:
- hidden_text = ""
- else:
- hidden_text = " (hidden)"
- field.add_option( "%s:%s %s" % ( hid, hidden_text, hda_name ), hda.id, selected )
+ valid_hda = dataset_param_context.valid_hda( hda )
+ if not valid_hda:
+ continue
+ if not valid_hda.implicit_conversion:
+ selected = dataset_param_context.selected( hda )
+ if hda.visible:
+ hidden_text = ""
else:
- target_ext, converted_dataset = hda.find_conversion_destination( self.formats )
- if target_ext:
- if converted_dataset:
- hda = converted_dataset
- if not trans.app.security_agent.can_access_dataset( current_user_roles, hda.dataset ):
- continue
- selected = ( value and ( hda in value ) )
- field.add_option( "%s: (as %s) %s" % ( hid, target_ext, hda_name ), hda.id, selected )
+ hidden_text = " (hidden)"
+ field.add_option( "%s:%s %s" % ( hid, hidden_text, hda_name ), hda.id, selected )
+ else:
+ hda = valid_hda.hda # Get converted dataset
+ target_ext = valid_hda.target_ext
+ selected = dataset_param_context.selected( hda )
+ field.add_option( "%s: (as %s) %s" % ( hid, target_ext, hda_name ), hda.id, selected )
# Also collect children via association object
dataset_collector( hda.children, hid )
dataset_collector( history.active_datasets_children_and_roles, None )
@@ -1687,30 +1672,18 @@
if trans is None or trans.workflow_building_mode or trans.webapp.name == 'tool_shed':
return DummyDataset()
history = self._get_history( trans, history )
+ dataset_param_context = DatasetParamContext( trans, history, self, None, context )
if self.optional:
return None
most_recent_dataset = []
- filter_value = None
- if self.options:
- try:
- filter_value = self.options.get_options( trans, context )[0][0]
- except IndexError:
- pass # no valid options
def dataset_collector( datasets ):
for i, data in enumerate( datasets ):
- if data.visible and not data.deleted and data.state not in [data.states.ERROR, data.states.DISCARDED]:
- is_valid = False
- if data.datatype.matches_any( self.formats ):
- is_valid = True
- else:
- target_ext, converted_dataset = data.find_conversion_destination( self.formats )
- if target_ext:
- is_valid = True
- if converted_dataset:
- data = converted_dataset
- if not is_valid or ( self.options and self._options_filter_attribute( data ) != filter_value ):
+ if data.visible and dataset_param_context.hda_accessible( data, check_security=False ):
+ match = dataset_param_context.valid_hda_matches_format( data, check_security=False )
+ if not match or dataset_param_context.filter( match.hda ):
continue
+ data = match.hda
most_recent_dataset.append(data)
# Also collect children via association object
dataset_collector( data.children )
diff -r be47cdc22244f5499814ad60b3b073a2c6415e19 -r 28d43f4b2eb915e7caf56d34e79556b56772b652 lib/galaxy/tools/parameters/dataset_util.py
--- /dev/null
+++ b/lib/galaxy/tools/parameters/dataset_util.py
@@ -0,0 +1,96 @@
+import galaxy.model
+
+from logging import getLogger
+log = getLogger( __name__ )
+
+ROLES_UNSET = object()
+INVALID_STATES = [ galaxy.model.Dataset.states.ERROR, galaxy.model.Dataset.states.DISCARDED ]
+
+
+class DatasetParamContext( object ):
+
+ def __init__( self, trans, history, param, value, other_values ):
+ self.trans = trans
+ self.history = history
+ self.param = param
+ self.tool = param.tool
+ self.value = value
+ self.current_user_roles = ROLES_UNSET
+ filter_value = None
+ if param.options:
+ try:
+ filter_value = param.options.get_options( trans, other_values )[0][0]
+ except IndexError:
+ pass # no valid options
+ self.filter_value = filter_value
+
+ def hda_accessible( self, hda, check_security=True ):
+ dataset = hda.dataset
+ state_valid = not dataset.state in INVALID_STATES
+ return state_valid and (not check_security or self.__can_access_dataset( dataset ) )
+
+ def valid_hda_matches_format( self, hda, check_implicit_conversions=True, check_security=False ):
+ if self.filter( hda ):
+ return False
+ formats = self.param.formats
+ if hda.datatype.matches_any( formats ):
+ return ValidParamHdaDirect( hda )
+ if not check_implicit_conversions:
+ return False
+ target_ext, converted_dataset = hda.find_conversion_destination( formats )
+ if target_ext:
+ if converted_dataset:
+ hda = converted_dataset
+ if check_security and not self.__can_access_dataset( hda.dataset ):
+ return False
+ return ValidParamHdaImplicit(converted_dataset, target_ext)
+ return False
+
+ def valid_hda( self, hda, check_implicit_conversions=True ):
+ accessible = self.hda_accessible( hda )
+ if accessible and ( hda.visible or ( self.selected( hda ) and not hda.implicitly_converted_parent_datasets ) ):
+ # If we are sending data to an external application, then we need to make sure there are no roles
+ # associated with the dataset that restrict it's access from "public".
+ require_public = self.tool and self.tool.tool_type == 'data_destination'
+ if require_public and not self.trans.app.security_agent.dataset_is_public( hda.dataset ):
+ return False
+ if self.filter( hda ):
+ return False
+ return self.valid_hda_matches_format(hda)
+
+ def selected( self, hda ):
+ value = self.value
+ return value and hda in value
+
+ def filter( self, hda ):
+ param = self.param
+ return param.options and param._options_filter_attribute( hda ) != self.filter_value
+
+ def __can_access_dataset( self, dataset ):
+ if self.current_user_roles is ROLES_UNSET:
+ self.current_user_roles = self.trans.get_current_user_roles()
+ return self.trans.app.security_agent.can_access_dataset( self.current_user_roles, dataset )
+
+
+class ValidParamHdaDirect( object ):
+
+ def __init__( self, hda ):
+ self.hda = hda
+
+ @property
+ def implicit_conversion( self ):
+ return False
+
+
+class ValidParamHdaImplicit( object ):
+
+ def __init__( self, converted_dataset, target_ext ):
+ self.hda = converted_dataset
+ self.target_ext = target_ext
+
+ @property
+ def implicit_conversion( self ):
+ return True
+
+
+__all__ = [ DatasetParamContext ]
https://bitbucket.org/galaxy/galaxy-central/commits/924ceb1c1e8d/
Changeset: 924ceb1c1e8d
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Add get_output_name to DefaultToolAction.
Slightly simplfies default execute but more importantly will allow reuse of logic when naming dataset collections.
Affected #: 1 file
diff -r 28d43f4b2eb915e7caf56d34e79556b56772b652 -r 924ceb1c1e8d0d7bdb6fa8fb00e0d4a9991f50eb lib/galaxy/tools/actions/__init__.py
--- a/lib/galaxy/tools/actions/__init__.py
+++ b/lib/galaxy/tools/actions/__init__.py
@@ -352,23 +352,10 @@
data.state = data.states.QUEUED
data.blurb = "queued"
# Set output label
- if output.label:
- if params is None:
- params = make_dict_copy( incoming )
- # wrapping the params allows the tool config to contain things like
- # <outputs>
- # <data format="input" name="output" label="Blat on ${<input_param>.name}" />
- # </outputs>
- wrap_values( tool.inputs, params, skip_missing_values=not tool.check_values )
- #tool (only needing to be set once) and on_string (set differently for each label) are overwritten for each output dataset label being determined
- params['tool'] = tool
- params['on_string'] = on_text
- data.name = fill_template( output.label, context=params )
- else:
- if params is None:
- params = make_dict_copy( incoming )
- wrap_values( tool.inputs, params, skip_missing_values=not tool.check_values )
- data.name = self._get_default_data_name( data, tool, on_text=on_text, trans=trans, incoming=incoming, history=history, params=params, job_params=job_params )
+ if params is None:
+ params = make_dict_copy( incoming )
+ wrap_values( tool.inputs, params, skip_missing_values=not tool.check_values )
+ data.name = self.get_output_name( output, data, tool, on_text, trans, incoming, history, params, job_params )
# Store output
out_data[ name ] = data
if output.actions:
@@ -489,6 +476,14 @@
trans.log_event( "Added job to the job queue, id: %s" % str(job.id), tool_id=job.tool_id )
return job, out_data
+ def get_output_name( self, output, dataset, tool, on_text, trans, incoming, history, params, job_params ):
+ if output.label:
+ params['tool'] = tool
+ params['on_string'] = on_text
+ return fill_template( output.label, context=params )
+ else:
+ return self._get_default_data_name( dataset, tool, on_text=on_text, trans=trans, incoming=incoming, history=history, params=params, job_params=job_params )
+
def _get_default_data_name( self, dataset, tool, on_text=None, trans=None, incoming=None, history=None, params=None, job_params=None, **kwd ):
name = tool.name
if on_text:
https://bitbucket.org/galaxy/galaxy-central/commits/3d075d5c7eff/
Changeset: 3d075d5c7eff
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Refactor on_text generation for reuse when naming dataset collections.
Affected #: 1 file
diff -r 924ceb1c1e8d0d7bdb6fa8fb00e0d4a9991f50eb -r 3d075d5c7effacbff1cf61c1ab0e2686230c18a1 lib/galaxy/tools/actions/__init__.py
--- a/lib/galaxy/tools/actions/__init__.py
+++ b/lib/galaxy/tools/actions/__init__.py
@@ -246,17 +246,10 @@
else:
# No valid inputs, we will use history defaults
output_permissions = trans.app.security_agent.history_get_default_permissions( history )
+
# Build name for output datasets based on tool name and input names
- if len( input_names ) == 1:
- on_text = input_names[0]
- elif len( input_names ) == 2:
- on_text = '%s and %s' % tuple(input_names[0:2])
- elif len( input_names ) == 3:
- on_text = '%s, %s, and %s' % tuple(input_names[0:3])
- elif len( input_names ) > 3:
- on_text = '%s, %s, and others' % tuple(input_names[0:2])
- else:
- on_text = ""
+ on_text = on_text_for_names( input_names )
+
# Add the dbkey to the incoming parameters
incoming[ "dbkey" ] = input_dbkey
params = None # wrapped params are used by change_format action and by output.label; only perform this wrapping once, as needed
@@ -489,3 +482,18 @@
if on_text:
name += ( " on " + on_text )
return name
+
+
+def on_text_for_names( input_names ):
+ # Build name for output datasets based on tool name and input names
+ if len( input_names ) == 1:
+ on_text = input_names[0]
+ elif len( input_names ) == 2:
+ on_text = '%s and %s' % tuple(input_names[0:2])
+ elif len( input_names ) == 3:
+ on_text = '%s, %s, and %s' % tuple(input_names[0:3])
+ elif len( input_names ) > 3:
+ on_text = '%s, %s, and others' % tuple(input_names[0:2])
+ else:
+ on_text = ""
+ return on_text
https://bitbucket.org/galaxy/galaxy-central/commits/88799789ebd4/
Changeset: 88799789ebd4
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Refactor stateful parameter wrapping logic out of DefaultToolAction...
... into new galaxy.tools.parameters.wrapped module. This slightly simplifies this execute method and reduces code duplication, but more importantly I reuse this method downstream for naming dataset collections.
Affected #: 2 files
diff -r 3d075d5c7effacbff1cf61c1ab0e2686230c18a1 -r 88799789ebd43337c3de71ed56cbdaf225b8210c lib/galaxy/tools/actions/__init__.py
--- a/lib/galaxy/tools/actions/__init__.py
+++ b/lib/galaxy/tools/actions/__init__.py
@@ -1,11 +1,9 @@
import os
-import galaxy.tools
from galaxy.exceptions import ObjectInvalid
from galaxy.model import LibraryDatasetDatasetAssociation
from galaxy.tools.parameters import DataToolParameter
-from galaxy.tools.parameters import SelectToolParameter
-from galaxy.tools.parameters.grouping import Conditional, Repeat
+from galaxy.tools.parameters.wrapped import WrappedParameters
from galaxy.util.json import from_json_string
from galaxy.util.json import to_json_string
from galaxy.util.none_like import NoneDataset
@@ -119,63 +117,6 @@
submitting the job to the job queue. If history is not specified, use
trans.history as destination for tool's output datasets.
"""
- def make_dict_copy( from_dict ):
- """
- Makes a copy of input dictionary from_dict such that all values that are dictionaries
- result in creation of a new dictionary ( a sort of deepcopy ). We may need to handle
- other complex types ( e.g., lists, etc ), but not sure...
- Yes, we need to handle lists (and now are)...
- """
- copy_from_dict = {}
- for key, value in from_dict.items():
- if type( value ).__name__ == 'dict':
- copy_from_dict[ key ] = make_dict_copy( value )
- elif isinstance( value, list ):
- copy_from_dict[ key ] = make_list_copy( value )
- else:
- copy_from_dict[ key ] = value
- return copy_from_dict
-
- def make_list_copy( from_list ):
- new_list = []
- for value in from_list:
- if isinstance( value, dict ):
- new_list.append( make_dict_copy( value ) )
- elif isinstance( value, list ):
- new_list.append( make_list_copy( value ) )
- else:
- new_list.append( value )
- return new_list
-
- def wrap_values( inputs, input_values, skip_missing_values=False ):
- # Wrap tool inputs as necessary
- for input in inputs.itervalues():
- if input.name not in input_values and skip_missing_values:
- continue
- if isinstance( input, Repeat ):
- for d in input_values[ input.name ]:
- wrap_values( input.inputs, d, skip_missing_values=skip_missing_values )
- elif isinstance( input, Conditional ):
- values = input_values[ input.name ]
- current = values[ "__current_case__" ]
- wrap_values( input.cases[current].inputs, values, skip_missing_values=skip_missing_values )
- elif isinstance( input, DataToolParameter ) and input.multiple:
- input_values[ input.name ] = \
- galaxy.tools.DatasetListWrapper( input_values[ input.name ],
- datatypes_registry=trans.app.datatypes_registry,
- tool=tool,
- name=input.name )
- elif isinstance( input, DataToolParameter ):
- input_values[ input.name ] = \
- galaxy.tools.DatasetFilenameWrapper( input_values[ input.name ],
- datatypes_registry=trans.app.datatypes_registry,
- tool=tool,
- name=input.name )
- elif isinstance( input, SelectToolParameter ):
- input_values[ input.name ] = galaxy.tools.SelectToolParameterWrapper( input, input_values[ input.name ], tool.app, other_values=incoming )
- else:
- input_values[ input.name ] = galaxy.tools.InputValueWrapper( input, input_values[ input.name ], incoming )
-
# Set history.
if not history:
history = tool.get_default_history_by_trans( trans, create=True )
@@ -232,7 +173,7 @@
elif 'len' in custom_build_dict:
# Build is defined by len file, so use it.
chrom_info = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( custom_build_dict[ 'len' ] ).file_name
-
+
if not chrom_info:
# Default to built-in build.
chrom_info = os.path.join( trans.app.config.len_file_path, "%s.len" % input_dbkey )
@@ -252,7 +193,8 @@
# Add the dbkey to the incoming parameters
incoming[ "dbkey" ] = input_dbkey
- params = None # wrapped params are used by change_format action and by output.label; only perform this wrapping once, as needed
+ # wrapped params are used by change_format action and by output.label; only perform this wrapping once, as needed
+ wrapped_params = WrappedParameters( trans, tool, incoming )
# Keep track of parent / child relationships, we'll create all the
# datasets first, then create the associations
parent_to_child_pairs = []
@@ -293,9 +235,6 @@
#process change_format tags
if output.change_format:
- if params is None:
- params = make_dict_copy( incoming )
- wrap_values( tool.inputs, params, skip_missing_values=not tool.check_values )
for change_elem in output.change_format:
for when_elem in change_elem.findall( 'when' ):
check = when_elem.get( 'input', None )
@@ -304,7 +243,7 @@
if '$' not in check:
#allow a simple name or more complex specifications
check = '${%s}' % check
- if str( fill_template( check, context=params ) ) == when_elem.get( 'value', None ):
+ if str( fill_template( check, context=wrapped_params.params ) ) == when_elem.get( 'value', None ):
ext = when_elem.get( 'format', ext )
except: # bad tag input value; possibly referencing a param within a different conditional when block or other nonexistent grouping construct
continue
@@ -345,10 +284,7 @@
data.state = data.states.QUEUED
data.blurb = "queued"
# Set output label
- if params is None:
- params = make_dict_copy( incoming )
- wrap_values( tool.inputs, params, skip_missing_values=not tool.check_values )
- data.name = self.get_output_name( output, data, tool, on_text, trans, incoming, history, params, job_params )
+ data.name = self.get_output_name( output, data, tool, on_text, trans, incoming, history, wrapped_params.params, job_params )
# Store output
out_data[ name ] = data
if output.actions:
diff -r 3d075d5c7effacbff1cf61c1ab0e2686230c18a1 -r 88799789ebd43337c3de71ed56cbdaf225b8210c lib/galaxy/tools/parameters/wrapped.py
--- /dev/null
+++ b/lib/galaxy/tools/parameters/wrapped.py
@@ -0,0 +1,94 @@
+import galaxy.tools
+
+from galaxy.tools.parameters.basic import (
+ DataToolParameter,
+ SelectToolParameter,
+)
+from galaxy.tools.parameters.grouping import (
+ Repeat,
+ Conditional,
+)
+PARAMS_UNWRAPPED = object()
+
+
+class WrappedParameters( object ):
+
+ def __init__( self, trans, tool, incoming ):
+ self.trans = trans
+ self.tool = tool
+ self.incoming = incoming
+ self._params = PARAMS_UNWRAPPED
+
+ @property
+ def params( self ):
+ if self._params is PARAMS_UNWRAPPED:
+ params = make_dict_copy( self.incoming )
+ self.wrap_values( self.tool.inputs, params, skip_missing_values=not self.tool.check_values )
+ self._params = params
+ return self._params
+
+ def wrap_values( self, inputs, input_values, skip_missing_values=False ):
+ trans = self.trans
+ tool = self.tool
+ incoming = self.incoming
+
+ # Wrap tool inputs as necessary
+ for input in inputs.itervalues():
+ if input.name not in input_values and skip_missing_values:
+ continue
+ if isinstance( input, Repeat ):
+ for d in input_values[ input.name ]:
+ self.wrap_values( input.inputs, d, skip_missing_values=skip_missing_values )
+ elif isinstance( input, Conditional ):
+ values = input_values[ input.name ]
+ current = values[ "__current_case__" ]
+ self.wrap_values( input.cases[current].inputs, values, skip_missing_values=skip_missing_values )
+ elif isinstance( input, DataToolParameter ) and input.multiple:
+ input_values[ input.name ] = \
+ galaxy.tools.DatasetListWrapper( input_values[ input.name ],
+ datatypes_registry=trans.app.datatypes_registry,
+ tool=tool,
+ name=input.name )
+ elif isinstance( input, DataToolParameter ):
+ input_values[ input.name ] = \
+ galaxy.tools.DatasetFilenameWrapper( input_values[ input.name ],
+ datatypes_registry=trans.app.datatypes_registry,
+ tool=tool,
+ name=input.name )
+ elif isinstance( input, SelectToolParameter ):
+ input_values[ input.name ] = galaxy.tools.SelectToolParameterWrapper( input, input_values[ input.name ], tool.app, other_values=incoming )
+ else:
+ input_values[ input.name ] = galaxy.tools.InputValueWrapper( input, input_values[ input.name ], incoming )
+
+
+def make_dict_copy( from_dict ):
+ """
+ Makes a copy of input dictionary from_dict such that all values that are dictionaries
+ result in creation of a new dictionary ( a sort of deepcopy ). We may need to handle
+ other complex types ( e.g., lists, etc ), but not sure...
+ Yes, we need to handle lists (and now are)...
+ """
+ copy_from_dict = {}
+ for key, value in from_dict.items():
+ if type( value ).__name__ == 'dict':
+ copy_from_dict[ key ] = make_dict_copy( value )
+ elif isinstance( value, list ):
+ copy_from_dict[ key ] = make_list_copy( value )
+ else:
+ copy_from_dict[ key ] = value
+ return copy_from_dict
+
+
+def make_list_copy( from_list ):
+ new_list = []
+ for value in from_list:
+ if isinstance( value, dict ):
+ new_list.append( make_dict_copy( value ) )
+ elif isinstance( value, list ):
+ new_list.append( make_list_copy( value ) )
+ else:
+ new_list.append( value )
+ return new_list
+
+
+__all__ = [ WrappedParameters, make_dict_copy ]
https://bitbucket.org/galaxy/galaxy-central/commits/b95d750d1eed/
Changeset: b95d750d1eed
User: jmchilton
Date: 2014-02-11 04:34:42
Summary: Outline galaxy.tools.js and load in tool_form.mako.
Migrated the logic out of tool_form.mako that I could readily test, the rest of the custom javascript in tool_form.mako should likewise be migrated I assume.
Holds a lot of logic downstream in my work related to running tools in parallel across many datasets and over dataset collections - https://github.com/jmchilton/galaxy-central/blob/collections_1/static/scrip….
Affected #: 2 files
diff -r 88799789ebd43337c3de71ed56cbdaf225b8210c -r b95d750d1eed64c19c0e31d6683f0f8dca73aeb3 static/scripts/galaxy.tools.js
--- /dev/null
+++ b/static/scripts/galaxy.tools.js
@@ -0,0 +1,20 @@
+// dependencies
+define([ "mvc/tools" ], function( Tools ) {
+
+ var checkUncheckAll = function( name, check ) {
+ $("input[name='" + name + "'][type='checkbox']").attr('checked', !!check);
+ }
+
+ // Inserts the Select All / Unselect All buttons for checkboxes
+ $("div.checkUncheckAllPlaceholder").each( function() {
+ var check_name = $(this).attr("checkbox_name");
+ select_link = $("<a class='action-button'></a>").text("Select All").click(function() {
+ checkUncheckAll(check_name, true);
+ });
+ unselect_link = $("<a class='action-button'></a>").text("Unselect All").click(function() {
+ checkUncheckAll(check_name, false);
+ });
+ $(this).append(select_link).append(" ").append(unselect_link);
+ });
+
+});
diff -r 88799789ebd43337c3de71ed56cbdaf225b8210c -r b95d750d1eed64c19c0e31d6683f0f8dca73aeb3 templates/webapps/galaxy/tool_form.mako
--- a/templates/webapps/galaxy/tool_form.mako
+++ b/templates/webapps/galaxy/tool_form.mako
@@ -15,6 +15,9 @@
${parent.javascripts()}
${h.js( "galaxy.panels", "libs/jquery/jstorage" )}
<script type="text/javascript">
+ require( [ "galaxy.tools" ] );
+ </script>
+ <script type="text/javascript">
$(function() {
$(window).bind("refresh_on_change", function() {
$(':file').each( function() {
@@ -58,22 +61,6 @@
});
});
- function checkUncheckAll( name, check ) {
- $("input[name='" + name + "'][type='checkbox']").attr('checked', !!check);
- }
-
- // Inserts the Select All / Unselect All buttons for checkboxes
- $("div.checkUncheckAllPlaceholder").each( function() {
- var check_name = $(this).attr("checkbox_name");
- select_link = $("<a class='action-button'></a>").text("Select All").click(function() {
- checkUncheckAll(check_name, true);
- });
- unselect_link = $("<a class='action-button'></a>").text("Unselect All").click(function() {
- checkUncheckAll(check_name, false);
- });
- $(this).append(select_link).append(" ").append(unselect_link);
- });
-
$(".add-librarydataset").click(function() {
var link = $(this);
$.ajax({
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0