mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merged galaxy/galaxy-central into default
This commit is contained in:
@@ -30,9 +30,9 @@ database/openid_consumer_cache
|
||||
*.pyc
|
||||
|
||||
# Galaxy Runtime Files
|
||||
paster.lock
|
||||
paster.log
|
||||
paster.pid
|
||||
*.lock
|
||||
*.log
|
||||
*.pid
|
||||
|
||||
# Tool Shed Runtime Files
|
||||
tool_shed_webapp.lock
|
||||
@@ -108,12 +108,9 @@ tool-data/shared/jars/
|
||||
# CSS build artifacts.
|
||||
sprite-*.less
|
||||
|
||||
# Local node_modules directories
|
||||
# Local node_modules and bower_components directories
|
||||
node_modules
|
||||
|
||||
# Testing
|
||||
selenium-server.jar
|
||||
selenium_results.html
|
||||
bower_components
|
||||
|
||||
# Documentation build files.
|
||||
doc/build
|
||||
|
||||
@@ -8,16 +8,18 @@ a4113cc1cb5eaa68091c9a73375f00555b66dd11 release_2013.01.13
|
||||
5e605ed6069fe4c5ca9875e95e91b2713499e8ca release_2014.02.10
|
||||
9e53251b0b7e93b9563008a2b112f2e815a04bbc release_2014.04.14
|
||||
7e257c7b10badb65772b1528cb61d58175a42e47 release_2014.06.02
|
||||
7a4d321c0e38fa263ea83d29a35a608c3181fcba latest_2014.06.02
|
||||
9661b9d5d5b330483ae3ad2236410e0efaa7c500 latest_2014.04.14
|
||||
6b0bd93038a843b1585155f0d63f0eea2459c70b latest_2013.01.13
|
||||
3e62060b14b9afc46f8e0ec02e1a4500d77db9e1 latest_2013.02.08
|
||||
425009b3ff4d8b67d2812253b221f3c4f4a8d1e3 latest_2013.04.01
|
||||
9713d86392ef985ffcdc39ff0c8ddf51a1f9ce47 latest_2013.06.03
|
||||
9ed84cd208e07e8985ec917cb025fcbbb09edcfb latest_2013.08.12
|
||||
81fbe25bd02edcd53065e8e4476dd1dfb5a72cf2 latest_2013.11.04
|
||||
2a756ca2cb1826db7796018e77d12e2dd7b67603 latest_2014.02.10
|
||||
9bce3f426863f8ba88062f67c7efc1836e82ee7c latest_2014.06.02
|
||||
8f9dcac033694e4cabcf5daae5cca1cfefbe967f latest_2014.04.14
|
||||
9c323aad4ffdd65a3deb06a4a36f6b2c5115a60f latest_2013.01.13
|
||||
b986c184be88947b5d1d90be7f36cfd2627dd938 latest_2013.02.08
|
||||
dec9431d66b837a208e2f060d90afd913c721227 latest_2013.04.01
|
||||
19e56e66b0b344c6e2afa4541f6988e4fdb9af29 latest_2013.06.03
|
||||
cee903b8b3eee9145627ee89742555dac581791e latest_2013.08.12
|
||||
7d5aa19a166cba9039e15f338a1e3fc924c43d3a latest_2013.11.04
|
||||
0c000cc2f9c05bf4c1c2bc3a10215014fd64e696 latest_2014.02.10
|
||||
ca45b78adb4152fc6e7395514d46eba6b7d0b838 release_2014.08.11
|
||||
548ab24667d6206780237bd807f7d857a484c461 latest_2014.08.11
|
||||
f3fc4602e22b39468d780b955a1a05caf867a7e9 latest_2014.08.11
|
||||
2092948937ac30ef82f71463a235c66d34987088 release_2014.10.06
|
||||
7086b87d83a9092cbece0fec6f3e3ed03266be0c latest_2014.10.06
|
||||
9bd6f8b5b8153db752f4e61ed62f2b6c01ae4a11 latest_2014.10.06
|
||||
2e8dd2949dd3eee0f56f9a3a5ebf1b2baca24aee release_2015.01.13
|
||||
4039bfd5584aac053f686197a76ac176253e6f3d latest_2015.01.13
|
||||
|
||||
+85
-2
@@ -47,15 +47,77 @@ module.exports = function(grunt) {
|
||||
options: {
|
||||
spawn: false
|
||||
}
|
||||
},
|
||||
|
||||
// call bower to install libraries and other external resources
|
||||
"bower-install-simple" : {
|
||||
options: {
|
||||
color: true
|
||||
},
|
||||
"prod": {
|
||||
options: {
|
||||
production: true
|
||||
}
|
||||
},
|
||||
"dev": {
|
||||
options: {
|
||||
production: false
|
||||
}
|
||||
}
|
||||
},
|
||||
// where to move fetched bower components into the build structure (libName: [ bower-location, libs-location ])
|
||||
libraryLocations : {
|
||||
"jquery": [ "dist/jquery.js", "jquery/jquery.js" ],
|
||||
"jquery-migrate": [ "jquery-migrate.js", "jquery/jquery.migrate.js" ],
|
||||
"traceKit": [ "tracekit.js", "tracekit.js" ],
|
||||
"ravenjs": [ "dist/raven.js", "raven.js" ],
|
||||
"underscore": [ "underscore.js", "underscore.js" ],
|
||||
"handlebars": [ "handlebars.runtime.js", "handlebars.runtime.js" ],
|
||||
"lunr.js": [ "lunr.js", "lunr.js" ]
|
||||
//"backbone": [ "backbone.js", "backbone/backbone.js" ],
|
||||
|
||||
// these need to be updated and tested
|
||||
//"require": [ "build/require.js", "require.js" ],
|
||||
//"d3": [ "d3.js", "d3.js" ],
|
||||
//"farbtastic": [ "src/farbtastic.js", "farbtastic.js" ],
|
||||
//"jQTouch": [ "src/reference/jqtouch.js", "jquery/jqtouch.js" ],
|
||||
//"bib2json": [ "Parser.js", "bibtex.js" ],
|
||||
//"jquery-form": [ "jquery.form.js", "jquery/jquery.form.js" ],
|
||||
//"jquery-autocomplete": [ "src/jquery.autocomplete.js", "jquery/jquery.autocomplete.js" ],
|
||||
//"select2": [ "select2.js", "jquery/select2.js" ],
|
||||
//"jStorage": [ "jstorage.js", "jquery/jstorage.js" ],
|
||||
//"jquery.cookie": [ "", "jquery/jquery.cookie.js" ],
|
||||
//"dynatree": [ "dist/jquery.dynatree.js", "jquery/jquery.dynatree.js" ],
|
||||
//"jquery-mousewheel": [ "jquery.mousewheel.js", "jquery/jquery.mousewheel.js" ],
|
||||
//"jquery.event.drag-drop": [
|
||||
// [ "event.drag/jquery.event.drag.js", "jquery/jquery.event.drag.js" ],
|
||||
// [ "event.drag/jquery.event.drop.js", "jquery/jquery.event.drop.js" ]
|
||||
//],
|
||||
|
||||
// these are complicated by additional css/less
|
||||
//"toastr": [ "toastr.js", "toastr.js" ],
|
||||
//"wymeditor": [ "dist/wymeditor/jquery.wymeditor.js", "jquery/jquery.wymeditor.js" ],
|
||||
//"jstree": [ "jstree.js", "jquery/jstree.js" ],
|
||||
|
||||
// these have been customized by Galaxy
|
||||
//"bootstrap": [ "dist/js/bootstrap.js", "bootstrap.js" ],
|
||||
//"jquery-ui": [
|
||||
// // multiple components now
|
||||
// [ "", "jquery/jquery-ui.js" ]
|
||||
//],
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks( 'grunt-contrib-watch' );
|
||||
grunt.loadNpmTasks( 'grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-exec');
|
||||
grunt.loadNpmTasks( 'grunt-bower-install-simple');
|
||||
grunt.loadNpmTasks( 'grunt-exec' );
|
||||
|
||||
grunt.registerTask( 'pack', [ 'exec' ] );
|
||||
grunt.registerTask( 'default', [ 'copy', 'pack' ] );
|
||||
grunt.registerTask( 'default', [ 'copy:main', 'pack' ] );
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------- copy,pack only those changed
|
||||
// adapted from grunt-contrib-watch jslint example
|
||||
@@ -85,4 +147,25 @@ module.exports = function(grunt) {
|
||||
onChange();
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------- fetch/update external libraries
|
||||
/** copy external libraries from bower components to scripts/libs */
|
||||
function copyLibs(){
|
||||
var libraryLocations = grunt.config( 'libraryLocations' );
|
||||
for( var libName in libraryLocations ){
|
||||
if( libraryLocations.hasOwnProperty( libName ) ){
|
||||
|
||||
var BOWER_DIR = 'bower_components',
|
||||
location = libraryLocations[ libName ],
|
||||
source = [ BOWER_DIR, libName, location[0] ].join( '/' ),
|
||||
destination = 'galaxy/scripts/libs/' + location[1];
|
||||
grunt.log.writeln( source + ' -> ' + destination );
|
||||
grunt.file.copy( source, destination );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grunt.registerTask( 'copy-libs', 'copy external libraries to src', copyLibs );
|
||||
grunt.registerTask( 'install-libs', 'fetch external libraries and copy to src',
|
||||
[ 'bower-install-simple:prod', 'copy-libs' ] );
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "galaxy-client-libs",
|
||||
"version": "0.0.0",
|
||||
"description": "External client-side libraries used by Galaxy",
|
||||
"keywords": [
|
||||
"galaxy",
|
||||
"galaxyproject"
|
||||
],
|
||||
"homepage": "usegalaxy.org",
|
||||
"dependencies": {
|
||||
"jquery": "~1.11.1",
|
||||
"traceKit": "*",
|
||||
"ravenjs": "~1.1.16",
|
||||
"require": "*",
|
||||
"underscore": "~1.7.0",
|
||||
"backbone": "~1.1.2",
|
||||
"bootstrap": "~3.3.2",
|
||||
"d3": "~3.5.3",
|
||||
"farbtastic": "~2.0.0-alpha.1",
|
||||
"toastr": "~2.1.0",
|
||||
"jQTouch": "git://github.com/senchalabs/jQTouch#~1.0.0",
|
||||
"bib2json": "git://github.com/mayanklahiri/bib2json",
|
||||
"jquery-form": "~3.46.0",
|
||||
"jquery-autocomplete": "git://github.com/dyve/jquery-autocomplete",
|
||||
"select2": "~3.5.2",
|
||||
"jStorage": "~0.4.12",
|
||||
"jquery.cookie": "~1.4.1",
|
||||
"dynatree": "~1.2.5",
|
||||
"jquery-mousewheel": "~3.1.12",
|
||||
"wymeditor": "~1.0.0-rc.1",
|
||||
"jstree": "~3.0.9",
|
||||
"jquery-ui": "git://github.com/jquery/jquery-ui.git#~1.11.2",
|
||||
"jquery.event.drag-drop": "~2.2.1",
|
||||
"handlebars": "~2.0.0",
|
||||
"jquery-migrate": "~1.2.1",
|
||||
"lunr.js": "git://github.com/olivernn/lunr.js.git#~0.5.7"
|
||||
},
|
||||
"resolutions": {
|
||||
"jquery": "~1.11.1"
|
||||
}
|
||||
}
|
||||
@@ -364,7 +364,7 @@ var GalaxyMastheadTab = Backbone.View.extend({
|
||||
var $popover_element = $(this.el).find('.head');
|
||||
$popover_element.popover({
|
||||
html: true,
|
||||
content: 'Please <a href="' + galaxy_config.root + '/user/login">log in</a> or <a href="' + galaxy_config.root + '/user/create">register</a> to use this feature.',
|
||||
content: 'Please <a href="' + galaxy_config.root + 'user/login?use_panels=True">log in</a> or <a href="' + galaxy_config.root + 'user/create?use_panels=True">register</a> to use this feature.',
|
||||
placement: 'bottom'
|
||||
}).on('shown.bs.popover', function() { // hooking on bootstrap event to automatically hide popovers after delay
|
||||
setTimeout(function() {
|
||||
|
||||
@@ -805,7 +805,7 @@ var Node = Backbone.Model.extend({
|
||||
nodeView.addDataOutput( output );
|
||||
} );
|
||||
nodeView.render();
|
||||
workflow.node_changed( this );
|
||||
workflow.node_changed( this, true);
|
||||
},
|
||||
update_field_data : function( data ) {
|
||||
var node = this;
|
||||
@@ -1089,17 +1089,25 @@ $.extend( Workflow.prototype, {
|
||||
if ( this.active_node != node ) {
|
||||
this.check_changes_in_active_form();
|
||||
this.clear_active_node();
|
||||
parent.show_form_for_tool( node.form_html + node.tooltip, node );
|
||||
if (parent.__NEWTOOLFORM__) {
|
||||
parent.show_form_for_tool( node.form_html, node );
|
||||
} else {
|
||||
parent.show_form_for_tool( node.form_html + node.tooltip, node );
|
||||
}
|
||||
node.make_active();
|
||||
this.active_node = node;
|
||||
}
|
||||
},
|
||||
node_changed : function ( node ) {
|
||||
node_changed : function ( node, force ) {
|
||||
this.has_changes = true;
|
||||
if ( this.active_node == node ) {
|
||||
if ( this.active_node == node && (!parent.__NEWTOOLFORM__ || force)) {
|
||||
// Reactive with new form_html
|
||||
this.check_changes_in_active_form(); //Force changes to be saved even on new connection (previously dumped)
|
||||
parent.show_form_for_tool( node.form_html + node.tooltip, node );
|
||||
if (parent.__NEWTOOLFORM__) {
|
||||
parent.show_form_for_tool( node.form_html, node );
|
||||
} else {
|
||||
parent.show_form_for_tool( node.form_html + node.tooltip, node );
|
||||
}
|
||||
}
|
||||
},
|
||||
layout : function () {
|
||||
@@ -1305,7 +1313,6 @@ function add_node( type, title_text, tool_id ) {
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
var ext_to_type = null;
|
||||
var type_to_type = null;
|
||||
|
||||
@@ -1863,6 +1870,9 @@ $.extend( CanvasManager.prototype, {
|
||||
left: x,
|
||||
top: y
|
||||
});
|
||||
self.cv.css( { "background-position-x": x,
|
||||
"background-position-y": y
|
||||
});
|
||||
self.update_viewport_overlay();
|
||||
};
|
||||
// Dragging within canvas background
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/*
|
||||
/*!
|
||||
|
||||
Copyright (C) 2011 by Yehuda Katz
|
||||
handlebars v2.0.0
|
||||
|
||||
Copyright (C) 2011-2014 by Yehuda Katz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -20,343 +22,639 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
@license
|
||||
*/
|
||||
|
||||
// lib/handlebars/browser-prefix.js
|
||||
var Handlebars = {};
|
||||
|
||||
(function(Handlebars, undefined) {
|
||||
;
|
||||
// lib/handlebars/base.js
|
||||
|
||||
Handlebars.VERSION = "1.0.0";
|
||||
Handlebars.COMPILER_REVISION = 4;
|
||||
|
||||
Handlebars.REVISION_CHANGES = {
|
||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||
2: '== 1.0.0-rc.3',
|
||||
3: '== 1.0.0-rc.4',
|
||||
4: '>= 1.0.0'
|
||||
};
|
||||
|
||||
Handlebars.helpers = {};
|
||||
Handlebars.partials = {};
|
||||
|
||||
var toString = Object.prototype.toString,
|
||||
functionType = '[object Function]',
|
||||
objectType = '[object Object]';
|
||||
|
||||
Handlebars.registerHelper = function(name, fn, inverse) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
|
||||
Handlebars.Utils.extend(this.helpers, name);
|
||||
/* exported Handlebars */
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = factory();
|
||||
} else {
|
||||
if (inverse) { fn.not = inverse; }
|
||||
this.helpers[name] = fn;
|
||||
root.Handlebars = root.Handlebars || factory();
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.registerPartial = function(name, str) {
|
||||
if (toString.call(name) === objectType) {
|
||||
Handlebars.Utils.extend(this.partials, name);
|
||||
} else {
|
||||
this.partials[name] = str;
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.registerHelper('helperMissing', function(arg) {
|
||||
if(arguments.length === 2) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw new Error("Missing helper: '" + arg + "'");
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
|
||||
var type = toString.call(context);
|
||||
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if(type === "[object Array]") {
|
||||
if(context.length > 0) {
|
||||
return Handlebars.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
return fn(context);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.K = function() {};
|
||||
|
||||
Handlebars.createFrame = Object.create || function(object) {
|
||||
Handlebars.K.prototype = object;
|
||||
var obj = new Handlebars.K();
|
||||
Handlebars.K.prototype = null;
|
||||
return obj;
|
||||
};
|
||||
|
||||
Handlebars.logger = {
|
||||
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
||||
|
||||
methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
|
||||
|
||||
// can be overridden in the host environment
|
||||
log: function(level, obj) {
|
||||
if (Handlebars.logger.level <= level) {
|
||||
var method = Handlebars.logger.methodMap[level];
|
||||
if (typeof console !== 'undefined' && console[method]) {
|
||||
console[method].call(console, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
|
||||
|
||||
Handlebars.registerHelper('each', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var i = 0, ret = "", data;
|
||||
|
||||
var type = toString.call(context);
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if (options.data) {
|
||||
data = Handlebars.createFrame(options.data);
|
||||
}(this, function () {
|
||||
// handlebars/safe-string.js
|
||||
var __module3__ = (function() {
|
||||
"use strict";
|
||||
var __exports__;
|
||||
// Build out our basic SafeString type
|
||||
function SafeString(string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
if(context && typeof context === 'object') {
|
||||
if(context instanceof Array){
|
||||
for(var j = context.length; i<j; i++) {
|
||||
if (data) { data.index = i; }
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
}
|
||||
} else {
|
||||
for(var key in context) {
|
||||
if(context.hasOwnProperty(key)) {
|
||||
if(data) { data.key = key; }
|
||||
ret = ret + fn(context[key], {data: data});
|
||||
i++;
|
||||
SafeString.prototype.toString = function() {
|
||||
return "" + this.string;
|
||||
};
|
||||
|
||||
__exports__ = SafeString;
|
||||
return __exports__;
|
||||
})();
|
||||
|
||||
// handlebars/utils.js
|
||||
var __module2__ = (function(__dependency1__) {
|
||||
"use strict";
|
||||
var __exports__ = {};
|
||||
/*jshint -W004 */
|
||||
var SafeString = __dependency1__;
|
||||
|
||||
var escape = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`"
|
||||
};
|
||||
|
||||
var badChars = /[&<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
|
||||
function escapeChar(chr) {
|
||||
return escape[chr];
|
||||
}
|
||||
|
||||
function extend(obj /* , ...source */) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
for (var key in arguments[i]) {
|
||||
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
|
||||
obj[key] = arguments[i][key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
if(i === 0){
|
||||
ret = inverse(this);
|
||||
__exports__.extend = extend;var toString = Object.prototype.toString;
|
||||
__exports__.toString = toString;
|
||||
// Sourced from lodash
|
||||
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
||||
var isFunction = function(value) {
|
||||
return typeof value === 'function';
|
||||
};
|
||||
// fallback for older versions of Chrome and Safari
|
||||
/* istanbul ignore next */
|
||||
if (isFunction(/x/)) {
|
||||
isFunction = function(value) {
|
||||
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
||||
};
|
||||
}
|
||||
var isFunction;
|
||||
__exports__.isFunction = isFunction;
|
||||
/* istanbul ignore next */
|
||||
var isArray = Array.isArray || function(value) {
|
||||
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
|
||||
};
|
||||
__exports__.isArray = isArray;
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('if', function(conditional, options) {
|
||||
var type = toString.call(conditional);
|
||||
if(type === functionType) { conditional = conditional.call(this); }
|
||||
|
||||
if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('unless', function(conditional, options) {
|
||||
return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('with', function(context, options) {
|
||||
var type = toString.call(context);
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('log', function(context, options) {
|
||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
Handlebars.log(level, context);
|
||||
});
|
||||
;
|
||||
// lib/handlebars/utils.js
|
||||
|
||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
Handlebars.Exception = function(message) {
|
||||
var tmp = Error.prototype.constructor.apply(this, arguments);
|
||||
|
||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
||||
for (var idx = 0; idx < errorProps.length; idx++) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
};
|
||||
Handlebars.Exception.prototype = new Error();
|
||||
|
||||
// Build out our basic SafeString type
|
||||
Handlebars.SafeString = function(string) {
|
||||
this.string = string;
|
||||
};
|
||||
Handlebars.SafeString.prototype.toString = function() {
|
||||
return this.string.toString();
|
||||
};
|
||||
|
||||
var escape = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`"
|
||||
};
|
||||
|
||||
var badChars = /[&<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
|
||||
var escapeChar = function(chr) {
|
||||
return escape[chr] || "&";
|
||||
};
|
||||
|
||||
Handlebars.Utils = {
|
||||
extend: function(obj, value) {
|
||||
for(var key in value) {
|
||||
if(value.hasOwnProperty(key)) {
|
||||
obj[key] = value[key];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
escapeExpression: function(string) {
|
||||
function escapeExpression(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof Handlebars.SafeString) {
|
||||
if (string instanceof SafeString) {
|
||||
return string.toString();
|
||||
} else if (string == null || string === false) {
|
||||
} else if (string == null) {
|
||||
return "";
|
||||
} else if (!string) {
|
||||
return string + '';
|
||||
}
|
||||
|
||||
// Force a string conversion as this will be done by the append regardless and
|
||||
// the regex test will do this transparently behind the scenes, causing issues if
|
||||
// an object's to string has escaped characters in it.
|
||||
string = string.toString();
|
||||
string = "" + string;
|
||||
|
||||
if(!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
},
|
||||
}
|
||||
|
||||
isEmpty: function(value) {
|
||||
__exports__.escapeExpression = escapeExpression;function isEmpty(value) {
|
||||
if (!value && value !== 0) {
|
||||
return true;
|
||||
} else if(toString.call(value) === "[object Array]" && value.length === 0) {
|
||||
} else if (isArray(value) && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
;
|
||||
// lib/handlebars/runtime.js
|
||||
|
||||
Handlebars.VM = {
|
||||
template: function(templateSpec) {
|
||||
__exports__.isEmpty = isEmpty;function appendContextPath(contextPath, id) {
|
||||
return (contextPath ? contextPath + '.' : '') + id;
|
||||
}
|
||||
|
||||
__exports__.appendContextPath = appendContextPath;
|
||||
return __exports__;
|
||||
})(__module3__);
|
||||
|
||||
// handlebars/exception.js
|
||||
var __module4__ = (function() {
|
||||
"use strict";
|
||||
var __exports__;
|
||||
|
||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
function Exception(message, node) {
|
||||
var line;
|
||||
if (node && node.firstLine) {
|
||||
line = node.firstLine;
|
||||
|
||||
message += ' - ' + line + ':' + node.firstColumn;
|
||||
}
|
||||
|
||||
var tmp = Error.prototype.constructor.call(this, message);
|
||||
|
||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
||||
for (var idx = 0; idx < errorProps.length; idx++) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
|
||||
if (line) {
|
||||
this.lineNumber = line;
|
||||
this.column = node.firstColumn;
|
||||
}
|
||||
}
|
||||
|
||||
Exception.prototype = new Error();
|
||||
|
||||
__exports__ = Exception;
|
||||
return __exports__;
|
||||
})();
|
||||
|
||||
// handlebars/base.js
|
||||
var __module1__ = (function(__dependency1__, __dependency2__) {
|
||||
"use strict";
|
||||
var __exports__ = {};
|
||||
var Utils = __dependency1__;
|
||||
var Exception = __dependency2__;
|
||||
|
||||
var VERSION = "2.0.0";
|
||||
__exports__.VERSION = VERSION;var COMPILER_REVISION = 6;
|
||||
__exports__.COMPILER_REVISION = COMPILER_REVISION;
|
||||
var REVISION_CHANGES = {
|
||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||
2: '== 1.0.0-rc.3',
|
||||
3: '== 1.0.0-rc.4',
|
||||
4: '== 1.x.x',
|
||||
5: '== 2.0.0-alpha.x',
|
||||
6: '>= 2.0.0-beta.1'
|
||||
};
|
||||
__exports__.REVISION_CHANGES = REVISION_CHANGES;
|
||||
var isArray = Utils.isArray,
|
||||
isFunction = Utils.isFunction,
|
||||
toString = Utils.toString,
|
||||
objectType = '[object Object]';
|
||||
|
||||
function HandlebarsEnvironment(helpers, partials) {
|
||||
this.helpers = helpers || {};
|
||||
this.partials = partials || {};
|
||||
|
||||
registerDefaultHelpers(this);
|
||||
}
|
||||
|
||||
__exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = {
|
||||
constructor: HandlebarsEnvironment,
|
||||
|
||||
logger: logger,
|
||||
log: log,
|
||||
|
||||
registerHelper: function(name, fn) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
|
||||
Utils.extend(this.helpers, name);
|
||||
} else {
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
},
|
||||
unregisterHelper: function(name) {
|
||||
delete this.helpers[name];
|
||||
},
|
||||
|
||||
registerPartial: function(name, partial) {
|
||||
if (toString.call(name) === objectType) {
|
||||
Utils.extend(this.partials, name);
|
||||
} else {
|
||||
this.partials[name] = partial;
|
||||
}
|
||||
},
|
||||
unregisterPartial: function(name) {
|
||||
delete this.partials[name];
|
||||
}
|
||||
};
|
||||
|
||||
function registerDefaultHelpers(instance) {
|
||||
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
|
||||
if(arguments.length === 1) {
|
||||
// A missing field in a {{foo}} constuct.
|
||||
return undefined;
|
||||
} else {
|
||||
// Someone is actually trying to call something, blow up.
|
||||
throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'");
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse,
|
||||
fn = options.fn;
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if (isArray(context)) {
|
||||
if(context.length > 0) {
|
||||
if (options.ids) {
|
||||
options.ids = [options.name];
|
||||
}
|
||||
|
||||
return instance.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
if (options.data && options.ids) {
|
||||
var data = createFrame(options.data);
|
||||
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name);
|
||||
options = {data: data};
|
||||
}
|
||||
|
||||
return fn(context, options);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('each', function(context, options) {
|
||||
if (!options) {
|
||||
throw new Exception('Must pass iterator to #each');
|
||||
}
|
||||
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var i = 0, ret = "", data;
|
||||
|
||||
var contextPath;
|
||||
if (options.data && options.ids) {
|
||||
contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
||||
}
|
||||
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
if (options.data) {
|
||||
data = createFrame(options.data);
|
||||
}
|
||||
|
||||
if(context && typeof context === 'object') {
|
||||
if (isArray(context)) {
|
||||
for(var j = context.length; i<j; i++) {
|
||||
if (data) {
|
||||
data.index = i;
|
||||
data.first = (i === 0);
|
||||
data.last = (i === (context.length-1));
|
||||
|
||||
if (contextPath) {
|
||||
data.contextPath = contextPath + i;
|
||||
}
|
||||
}
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
}
|
||||
} else {
|
||||
for(var key in context) {
|
||||
if(context.hasOwnProperty(key)) {
|
||||
if(data) {
|
||||
data.key = key;
|
||||
data.index = i;
|
||||
data.first = (i === 0);
|
||||
|
||||
if (contextPath) {
|
||||
data.contextPath = contextPath + key;
|
||||
}
|
||||
}
|
||||
ret = ret + fn(context[key], {data: data});
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(i === 0){
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
instance.registerHelper('if', function(conditional, options) {
|
||||
if (isFunction(conditional)) { conditional = conditional.call(this); }
|
||||
|
||||
// Default behavior is to render the positive path if the value is truthy and not empty.
|
||||
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
|
||||
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
|
||||
if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('unless', function(conditional, options) {
|
||||
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
|
||||
});
|
||||
|
||||
instance.registerHelper('with', function(context, options) {
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
var fn = options.fn;
|
||||
|
||||
if (!Utils.isEmpty(context)) {
|
||||
if (options.data && options.ids) {
|
||||
var data = createFrame(options.data);
|
||||
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]);
|
||||
options = {data:data};
|
||||
}
|
||||
|
||||
return fn(context, options);
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('log', function(message, options) {
|
||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
instance.log(level, message);
|
||||
});
|
||||
|
||||
instance.registerHelper('lookup', function(obj, field) {
|
||||
return obj && obj[field];
|
||||
});
|
||||
}
|
||||
|
||||
var logger = {
|
||||
methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
|
||||
|
||||
// State enum
|
||||
DEBUG: 0,
|
||||
INFO: 1,
|
||||
WARN: 2,
|
||||
ERROR: 3,
|
||||
level: 3,
|
||||
|
||||
// can be overridden in the host environment
|
||||
log: function(level, message) {
|
||||
if (logger.level <= level) {
|
||||
var method = logger.methodMap[level];
|
||||
if (typeof console !== 'undefined' && console[method]) {
|
||||
console[method].call(console, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
__exports__.logger = logger;
|
||||
var log = logger.log;
|
||||
__exports__.log = log;
|
||||
var createFrame = function(object) {
|
||||
var frame = Utils.extend({}, object);
|
||||
frame._parent = object;
|
||||
return frame;
|
||||
};
|
||||
__exports__.createFrame = createFrame;
|
||||
return __exports__;
|
||||
})(__module2__, __module4__);
|
||||
|
||||
// handlebars/runtime.js
|
||||
var __module5__ = (function(__dependency1__, __dependency2__, __dependency3__) {
|
||||
"use strict";
|
||||
var __exports__ = {};
|
||||
var Utils = __dependency1__;
|
||||
var Exception = __dependency2__;
|
||||
var COMPILER_REVISION = __dependency3__.COMPILER_REVISION;
|
||||
var REVISION_CHANGES = __dependency3__.REVISION_CHANGES;
|
||||
var createFrame = __dependency3__.createFrame;
|
||||
|
||||
function checkRevision(compilerInfo) {
|
||||
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
|
||||
currentRevision = COMPILER_REVISION;
|
||||
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
var runtimeVersions = REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = REVISION_CHANGES[compilerRevision];
|
||||
throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
|
||||
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
|
||||
"Please update your runtime to a newer version ("+compilerInfo[1]+").");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__exports__.checkRevision = checkRevision;// TODO: Remove this line and break up compilePartial
|
||||
|
||||
function template(templateSpec, env) {
|
||||
/* istanbul ignore next */
|
||||
if (!env) {
|
||||
throw new Exception("No environment passed to template");
|
||||
}
|
||||
if (!templateSpec || !templateSpec.main) {
|
||||
throw new Exception('Unknown template object: ' + typeof templateSpec);
|
||||
}
|
||||
|
||||
// Note: Using env.VM references rather than local var references throughout this section to allow
|
||||
// for external users to override these as psuedo-supported APIs.
|
||||
env.VM.checkRevision(templateSpec.compiler);
|
||||
|
||||
var invokePartialWrapper = function(partial, indent, name, context, hash, helpers, partials, data, depths) {
|
||||
if (hash) {
|
||||
context = Utils.extend({}, context, hash);
|
||||
}
|
||||
|
||||
var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data, depths);
|
||||
|
||||
if (result == null && env.compile) {
|
||||
var options = { helpers: helpers, partials: partials, data: data, depths: depths };
|
||||
partials[name] = env.compile(partial, { data: data !== undefined, compat: templateSpec.compat }, env);
|
||||
result = partials[name](context, options);
|
||||
}
|
||||
if (result != null) {
|
||||
if (indent) {
|
||||
var lines = result.split('\n');
|
||||
for (var i = 0, l = lines.length; i < l; i++) {
|
||||
if (!lines[i] && i + 1 === l) {
|
||||
break;
|
||||
}
|
||||
|
||||
lines[i] = indent + lines[i];
|
||||
}
|
||||
result = lines.join('\n');
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
}
|
||||
};
|
||||
|
||||
// Just add water
|
||||
var container = {
|
||||
escapeExpression: Handlebars.Utils.escapeExpression,
|
||||
invokePartial: Handlebars.VM.invokePartial,
|
||||
lookup: function(depths, name) {
|
||||
var len = depths.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (depths[i] && depths[i][name] != null) {
|
||||
return depths[i][name];
|
||||
}
|
||||
}
|
||||
},
|
||||
lambda: function(current, context) {
|
||||
return typeof current === 'function' ? current.call(context) : current;
|
||||
},
|
||||
|
||||
escapeExpression: Utils.escapeExpression,
|
||||
invokePartial: invokePartialWrapper,
|
||||
|
||||
fn: function(i) {
|
||||
return templateSpec[i];
|
||||
},
|
||||
|
||||
programs: [],
|
||||
program: function(i, fn, data) {
|
||||
var programWrapper = this.programs[i];
|
||||
if(data) {
|
||||
programWrapper = Handlebars.VM.program(i, fn, data);
|
||||
program: function(i, data, depths) {
|
||||
var programWrapper = this.programs[i],
|
||||
fn = this.fn(i);
|
||||
if (data || depths) {
|
||||
programWrapper = program(this, i, fn, data, depths);
|
||||
} else if (!programWrapper) {
|
||||
programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
|
||||
programWrapper = this.programs[i] = program(this, i, fn);
|
||||
}
|
||||
return programWrapper;
|
||||
},
|
||||
|
||||
data: function(data, depth) {
|
||||
while (data && depth--) {
|
||||
data = data._parent;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
merge: function(param, common) {
|
||||
var ret = param || common;
|
||||
|
||||
if (param && common) {
|
||||
ret = {};
|
||||
Handlebars.Utils.extend(ret, common);
|
||||
Handlebars.Utils.extend(ret, param);
|
||||
if (param && common && (param !== common)) {
|
||||
ret = Utils.extend({}, common, param);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
programWithDepth: Handlebars.VM.programWithDepth,
|
||||
noop: Handlebars.VM.noop,
|
||||
compilerInfo: null
|
||||
|
||||
noop: env.VM.noop,
|
||||
compilerInfo: templateSpec.compiler
|
||||
};
|
||||
|
||||
return function(context, options) {
|
||||
var ret = function(context, options) {
|
||||
options = options || {};
|
||||
var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
|
||||
var data = options.data;
|
||||
|
||||
var compilerInfo = container.compilerInfo || [],
|
||||
compilerRevision = compilerInfo[0] || 1,
|
||||
currentRevision = Handlebars.COMPILER_REVISION;
|
||||
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
|
||||
throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
|
||||
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
|
||||
"Please update your runtime to a newer version ("+compilerInfo[1]+").";
|
||||
}
|
||||
ret._setup(options);
|
||||
if (!options.partial && templateSpec.useData) {
|
||||
data = initData(context, data);
|
||||
}
|
||||
var depths;
|
||||
if (templateSpec.useDepths) {
|
||||
depths = options.depths ? [context].concat(options.depths) : [context];
|
||||
}
|
||||
|
||||
return result;
|
||||
return templateSpec.main.call(container, context, container.helpers, container.partials, data, depths);
|
||||
};
|
||||
},
|
||||
ret.isTop = true;
|
||||
|
||||
programWithDepth: function(i, fn, data /*, $depth */) {
|
||||
var args = Array.prototype.slice.call(arguments, 3);
|
||||
ret._setup = function(options) {
|
||||
if (!options.partial) {
|
||||
container.helpers = container.merge(options.helpers, env.helpers);
|
||||
|
||||
var program = function(context, options) {
|
||||
if (templateSpec.usePartial) {
|
||||
container.partials = container.merge(options.partials, env.partials);
|
||||
}
|
||||
} else {
|
||||
container.helpers = options.helpers;
|
||||
container.partials = options.partials;
|
||||
}
|
||||
};
|
||||
|
||||
ret._child = function(i, data, depths) {
|
||||
if (templateSpec.useDepths && !depths) {
|
||||
throw new Exception('must pass parent depths');
|
||||
}
|
||||
|
||||
return program(container, i, templateSpec[i], data, depths);
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
__exports__.template = template;function program(container, i, fn, data, depths) {
|
||||
var prog = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.apply(this, [context, options.data || data].concat(args));
|
||||
return fn.call(container, context, container.helpers, container.partials, options.data || data, depths && [context].concat(depths));
|
||||
};
|
||||
program.program = i;
|
||||
program.depth = args.length;
|
||||
return program;
|
||||
},
|
||||
program: function(i, fn, data) {
|
||||
var program = function(context, options) {
|
||||
options = options || {};
|
||||
prog.program = i;
|
||||
prog.depth = depths ? depths.length : 0;
|
||||
return prog;
|
||||
}
|
||||
|
||||
return fn(context, options.data || data);
|
||||
};
|
||||
program.program = i;
|
||||
program.depth = 0;
|
||||
return program;
|
||||
},
|
||||
noop: function() { return ""; },
|
||||
invokePartial: function(partial, name, context, helpers, partials, data) {
|
||||
var options = { helpers: helpers, partials: partials, data: data };
|
||||
__exports__.program = program;function invokePartial(partial, name, context, helpers, partials, data, depths) {
|
||||
var options = { partial: true, helpers: helpers, partials: partials, data: data, depths: depths };
|
||||
|
||||
if(partial === undefined) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be found");
|
||||
throw new Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
return partial(context, options);
|
||||
} else if (!Handlebars.compile) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
} else {
|
||||
partials[name] = Handlebars.compile(partial, {data: data !== undefined});
|
||||
return partials[name](context, options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.template = Handlebars.VM.template;
|
||||
;
|
||||
// lib/handlebars/browser-suffix.js
|
||||
})(Handlebars);
|
||||
;
|
||||
__exports__.invokePartial = invokePartial;function noop() { return ""; }
|
||||
|
||||
__exports__.noop = noop;function initData(context, data) {
|
||||
if (!data || !('root' in data)) {
|
||||
data = data ? createFrame(data) : {};
|
||||
data.root = context;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
return __exports__;
|
||||
})(__module2__, __module4__, __module1__);
|
||||
|
||||
// handlebars.runtime.js
|
||||
var __module0__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
|
||||
"use strict";
|
||||
var __exports__;
|
||||
/*globals Handlebars: true */
|
||||
var base = __dependency1__;
|
||||
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
// (This is done to easily share code between commonjs and browse envs)
|
||||
var SafeString = __dependency2__;
|
||||
var Exception = __dependency3__;
|
||||
var Utils = __dependency4__;
|
||||
var runtime = __dependency5__;
|
||||
|
||||
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
|
||||
var create = function() {
|
||||
var hb = new base.HandlebarsEnvironment();
|
||||
|
||||
Utils.extend(hb, base);
|
||||
hb.SafeString = SafeString;
|
||||
hb.Exception = Exception;
|
||||
hb.Utils = Utils;
|
||||
hb.escapeExpression = Utils.escapeExpression;
|
||||
|
||||
hb.VM = runtime;
|
||||
hb.template = function(spec) {
|
||||
return runtime.template(spec, hb);
|
||||
};
|
||||
|
||||
return hb;
|
||||
};
|
||||
|
||||
var Handlebars = create();
|
||||
Handlebars.create = create;
|
||||
|
||||
Handlebars['default'] = Handlebars;
|
||||
|
||||
__exports__ = Handlebars;
|
||||
return __exports__;
|
||||
})(__module1__, __module3__, __module4__, __module2__, __module5__);
|
||||
|
||||
return __module0__;
|
||||
}));
|
||||
|
||||
+113
-75
@@ -1,5 +1,5 @@
|
||||
/*!
|
||||
* jQuery JavaScript Library v1.11.1
|
||||
* jQuery JavaScript Library v1.11.2
|
||||
* http://jquery.com/
|
||||
*
|
||||
* Includes Sizzle.js
|
||||
@@ -9,7 +9,7 @@
|
||||
* Released under the MIT license
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* Date: 2014-05-01T17:42Z
|
||||
* Date: 2014-12-17T15:27Z
|
||||
*/
|
||||
|
||||
(function( global, factory ) {
|
||||
@@ -64,7 +64,7 @@ var support = {};
|
||||
|
||||
|
||||
var
|
||||
version = "1.11.1",
|
||||
version = "1.11.2",
|
||||
|
||||
// Define a local copy of jQuery
|
||||
jQuery = function( selector, context ) {
|
||||
@@ -269,7 +269,8 @@ jQuery.extend({
|
||||
// parseFloat NaNs numeric-cast false positives (null|true|false|"")
|
||||
// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
|
||||
// subtraction forces infinities to NaN
|
||||
return !jQuery.isArray( obj ) && obj - parseFloat( obj ) >= 0;
|
||||
// adding 1 corrects loss of precision from parseFloat (#15100)
|
||||
return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0;
|
||||
},
|
||||
|
||||
isEmptyObject: function( obj ) {
|
||||
@@ -584,14 +585,14 @@ function isArraylike( obj ) {
|
||||
}
|
||||
var Sizzle =
|
||||
/*!
|
||||
* Sizzle CSS Selector Engine v1.10.19
|
||||
* Sizzle CSS Selector Engine v2.2.0-pre
|
||||
* http://sizzlejs.com/
|
||||
*
|
||||
* Copyright 2013 jQuery Foundation, Inc. and other contributors
|
||||
* Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors
|
||||
* Released under the MIT license
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* Date: 2014-04-18
|
||||
* Date: 2014-12-16
|
||||
*/
|
||||
(function( window ) {
|
||||
|
||||
@@ -618,7 +619,7 @@ var i,
|
||||
contains,
|
||||
|
||||
// Instance-specific data
|
||||
expando = "sizzle" + -(new Date()),
|
||||
expando = "sizzle" + 1 * new Date(),
|
||||
preferredDoc = window.document,
|
||||
dirruns = 0,
|
||||
done = 0,
|
||||
@@ -633,7 +634,6 @@ var i,
|
||||
},
|
||||
|
||||
// General-purpose constants
|
||||
strundefined = typeof undefined,
|
||||
MAX_NEGATIVE = 1 << 31,
|
||||
|
||||
// Instance methods
|
||||
@@ -643,12 +643,13 @@ var i,
|
||||
push_native = arr.push,
|
||||
push = arr.push,
|
||||
slice = arr.slice,
|
||||
// Use a stripped-down indexOf if we can't use a native one
|
||||
indexOf = arr.indexOf || function( elem ) {
|
||||
// Use a stripped-down indexOf as it's faster than native
|
||||
// http://jsperf.com/thor-indexof-vs-for/5
|
||||
indexOf = function( list, elem ) {
|
||||
var i = 0,
|
||||
len = this.length;
|
||||
len = list.length;
|
||||
for ( ; i < len; i++ ) {
|
||||
if ( this[i] === elem ) {
|
||||
if ( list[i] === elem ) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -688,6 +689,7 @@ var i,
|
||||
")\\)|)",
|
||||
|
||||
// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
|
||||
rwhitespace = new RegExp( whitespace + "+", "g" ),
|
||||
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
|
||||
|
||||
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
|
||||
@@ -739,6 +741,14 @@ var i,
|
||||
String.fromCharCode( high + 0x10000 ) :
|
||||
// Supplemental Plane codepoint (surrogate pair)
|
||||
String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
|
||||
},
|
||||
|
||||
// Used for iframes
|
||||
// See setDocument()
|
||||
// Removing the function wrapper causes a "Permission Denied"
|
||||
// error in IE
|
||||
unloadHandler = function() {
|
||||
setDocument();
|
||||
};
|
||||
|
||||
// Optimize for push.apply( _, NodeList )
|
||||
@@ -781,19 +791,18 @@ function Sizzle( selector, context, results, seed ) {
|
||||
|
||||
context = context || document;
|
||||
results = results || [];
|
||||
nodeType = context.nodeType;
|
||||
|
||||
if ( typeof selector !== "string" || !selector ||
|
||||
nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
|
||||
|
||||
if ( !selector || typeof selector !== "string" ) {
|
||||
return results;
|
||||
}
|
||||
|
||||
if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
|
||||
return [];
|
||||
}
|
||||
if ( !seed && documentIsHTML ) {
|
||||
|
||||
if ( documentIsHTML && !seed ) {
|
||||
|
||||
// Shortcuts
|
||||
if ( (match = rquickExpr.exec( selector )) ) {
|
||||
// Try to shortcut find operations when possible (e.g., not under DocumentFragment)
|
||||
if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
|
||||
// Speed-up: Sizzle("#ID")
|
||||
if ( (m = match[1]) ) {
|
||||
if ( nodeType === 9 ) {
|
||||
@@ -825,7 +834,7 @@ function Sizzle( selector, context, results, seed ) {
|
||||
return results;
|
||||
|
||||
// Speed-up: Sizzle(".CLASS")
|
||||
} else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
|
||||
} else if ( (m = match[3]) && support.getElementsByClassName ) {
|
||||
push.apply( results, context.getElementsByClassName( m ) );
|
||||
return results;
|
||||
}
|
||||
@@ -835,7 +844,7 @@ function Sizzle( selector, context, results, seed ) {
|
||||
if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
|
||||
nid = old = expando;
|
||||
newContext = context;
|
||||
newSelector = nodeType === 9 && selector;
|
||||
newSelector = nodeType !== 1 && selector;
|
||||
|
||||
// qSA works strangely on Element-rooted queries
|
||||
// We can work around this by specifying an extra ID on the root
|
||||
@@ -1022,7 +1031,7 @@ function createPositionalPseudo( fn ) {
|
||||
* @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
|
||||
*/
|
||||
function testContext( context ) {
|
||||
return context && typeof context.getElementsByTagName !== strundefined && context;
|
||||
return context && typeof context.getElementsByTagName !== "undefined" && context;
|
||||
}
|
||||
|
||||
// Expose support vars for convenience
|
||||
@@ -1046,9 +1055,8 @@ isXML = Sizzle.isXML = function( elem ) {
|
||||
* @returns {Object} Returns the current document
|
||||
*/
|
||||
setDocument = Sizzle.setDocument = function( node ) {
|
||||
var hasCompare,
|
||||
doc = node ? node.ownerDocument || node : preferredDoc,
|
||||
parent = doc.defaultView;
|
||||
var hasCompare, parent,
|
||||
doc = node ? node.ownerDocument || node : preferredDoc;
|
||||
|
||||
// If no document and documentElement is available, return
|
||||
if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
|
||||
@@ -1058,9 +1066,7 @@ setDocument = Sizzle.setDocument = function( node ) {
|
||||
// Set our document
|
||||
document = doc;
|
||||
docElem = doc.documentElement;
|
||||
|
||||
// Support tests
|
||||
documentIsHTML = !isXML( doc );
|
||||
parent = doc.defaultView;
|
||||
|
||||
// Support: IE>8
|
||||
// If iframe document is assigned to "document" variable and if iframe has been reloaded,
|
||||
@@ -1069,21 +1075,22 @@ setDocument = Sizzle.setDocument = function( node ) {
|
||||
if ( parent && parent !== parent.top ) {
|
||||
// IE11 does not have attachEvent, so all must suffer
|
||||
if ( parent.addEventListener ) {
|
||||
parent.addEventListener( "unload", function() {
|
||||
setDocument();
|
||||
}, false );
|
||||
parent.addEventListener( "unload", unloadHandler, false );
|
||||
} else if ( parent.attachEvent ) {
|
||||
parent.attachEvent( "onunload", function() {
|
||||
setDocument();
|
||||
});
|
||||
parent.attachEvent( "onunload", unloadHandler );
|
||||
}
|
||||
}
|
||||
|
||||
/* Support tests
|
||||
---------------------------------------------------------------------- */
|
||||
documentIsHTML = !isXML( doc );
|
||||
|
||||
/* Attributes
|
||||
---------------------------------------------------------------------- */
|
||||
|
||||
// Support: IE<8
|
||||
// Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
|
||||
// Verify that getAttribute really returns attributes and not properties
|
||||
// (excepting IE8 booleans)
|
||||
support.attributes = assert(function( div ) {
|
||||
div.className = "i";
|
||||
return !div.getAttribute("className");
|
||||
@@ -1098,17 +1105,8 @@ setDocument = Sizzle.setDocument = function( node ) {
|
||||
return !div.getElementsByTagName("*").length;
|
||||
});
|
||||
|
||||
// Check if getElementsByClassName can be trusted
|
||||
support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) {
|
||||
div.innerHTML = "<div class='a'></div><div class='a i'></div>";
|
||||
|
||||
// Support: Safari<4
|
||||
// Catch class over-caching
|
||||
div.firstChild.className = "i";
|
||||
// Support: Opera<10
|
||||
// Catch gEBCN failure to find non-leading classes
|
||||
return div.getElementsByClassName("i").length === 2;
|
||||
});
|
||||
// Support: IE<9
|
||||
support.getElementsByClassName = rnative.test( doc.getElementsByClassName );
|
||||
|
||||
// Support: IE<10
|
||||
// Check if getElementById returns elements by name
|
||||
@@ -1122,7 +1120,7 @@ setDocument = Sizzle.setDocument = function( node ) {
|
||||
// ID find and filter
|
||||
if ( support.getById ) {
|
||||
Expr.find["ID"] = function( id, context ) {
|
||||
if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
|
||||
if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
|
||||
var m = context.getElementById( id );
|
||||
// Check parentNode to catch when Blackberry 4.6 returns
|
||||
// nodes that are no longer in the document #6963
|
||||
@@ -1143,7 +1141,7 @@ setDocument = Sizzle.setDocument = function( node ) {
|
||||
Expr.filter["ID"] = function( id ) {
|
||||
var attrId = id.replace( runescape, funescape );
|
||||
return function( elem ) {
|
||||
var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
|
||||
var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
|
||||
return node && node.value === attrId;
|
||||
};
|
||||
};
|
||||
@@ -1152,14 +1150,20 @@ setDocument = Sizzle.setDocument = function( node ) {
|
||||
// Tag
|
||||
Expr.find["TAG"] = support.getElementsByTagName ?
|
||||
function( tag, context ) {
|
||||
if ( typeof context.getElementsByTagName !== strundefined ) {
|
||||
if ( typeof context.getElementsByTagName !== "undefined" ) {
|
||||
return context.getElementsByTagName( tag );
|
||||
|
||||
// DocumentFragment nodes don't have gEBTN
|
||||
} else if ( support.qsa ) {
|
||||
return context.querySelectorAll( tag );
|
||||
}
|
||||
} :
|
||||
|
||||
function( tag, context ) {
|
||||
var elem,
|
||||
tmp = [],
|
||||
i = 0,
|
||||
// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
|
||||
results = context.getElementsByTagName( tag );
|
||||
|
||||
// Filter out possible comments
|
||||
@@ -1177,7 +1181,7 @@ setDocument = Sizzle.setDocument = function( node ) {
|
||||
|
||||
// Class
|
||||
Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
|
||||
if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
|
||||
if ( documentIsHTML ) {
|
||||
return context.getElementsByClassName( className );
|
||||
}
|
||||
};
|
||||
@@ -1206,13 +1210,15 @@ setDocument = Sizzle.setDocument = function( node ) {
|
||||
// setting a boolean content attribute,
|
||||
// since its presence should be enough
|
||||
// http://bugs.jquery.com/ticket/12359
|
||||
div.innerHTML = "<select msallowclip=''><option selected=''></option></select>";
|
||||
docElem.appendChild( div ).innerHTML = "<a id='" + expando + "'></a>" +
|
||||
"<select id='" + expando + "-\f]' msallowcapture=''>" +
|
||||
"<option selected=''></option></select>";
|
||||
|
||||
// Support: IE8, Opera 11-12.16
|
||||
// Nothing should be selected when empty strings follow ^= or $= or *=
|
||||
// The test attribute must be unknown in Opera but "safe" for WinRT
|
||||
// http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
|
||||
if ( div.querySelectorAll("[msallowclip^='']").length ) {
|
||||
if ( div.querySelectorAll("[msallowcapture^='']").length ) {
|
||||
rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
|
||||
}
|
||||
|
||||
@@ -1222,12 +1228,24 @@ setDocument = Sizzle.setDocument = function( node ) {
|
||||
rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
|
||||
}
|
||||
|
||||
// Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+
|
||||
if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
|
||||
rbuggyQSA.push("~=");
|
||||
}
|
||||
|
||||
// Webkit/Opera - :checked should return selected option elements
|
||||
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
|
||||
// IE8 throws error here and will not see later tests
|
||||
if ( !div.querySelectorAll(":checked").length ) {
|
||||
rbuggyQSA.push(":checked");
|
||||
}
|
||||
|
||||
// Support: Safari 8+, iOS 8+
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=136851
|
||||
// In-page `selector#id sibing-combinator selector` fails
|
||||
if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) {
|
||||
rbuggyQSA.push(".#.+[+~]");
|
||||
}
|
||||
});
|
||||
|
||||
assert(function( div ) {
|
||||
@@ -1344,7 +1362,7 @@ setDocument = Sizzle.setDocument = function( node ) {
|
||||
|
||||
// Maintain original order
|
||||
return sortInput ?
|
||||
( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
|
||||
( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
|
||||
0;
|
||||
}
|
||||
|
||||
@@ -1371,7 +1389,7 @@ setDocument = Sizzle.setDocument = function( node ) {
|
||||
aup ? -1 :
|
||||
bup ? 1 :
|
||||
sortInput ?
|
||||
( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
|
||||
( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
|
||||
0;
|
||||
|
||||
// If the nodes are siblings, we can do a quick check
|
||||
@@ -1434,7 +1452,7 @@ Sizzle.matchesSelector = function( elem, expr ) {
|
||||
elem.document && elem.document.nodeType !== 11 ) {
|
||||
return ret;
|
||||
}
|
||||
} catch(e) {}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return Sizzle( expr, document, null, [ elem ] ).length > 0;
|
||||
@@ -1653,7 +1671,7 @@ Expr = Sizzle.selectors = {
|
||||
return pattern ||
|
||||
(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
|
||||
classCache( className, function( elem ) {
|
||||
return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
|
||||
return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1675,7 +1693,7 @@ Expr = Sizzle.selectors = {
|
||||
operator === "^=" ? check && result.indexOf( check ) === 0 :
|
||||
operator === "*=" ? check && result.indexOf( check ) > -1 :
|
||||
operator === "$=" ? check && result.slice( -check.length ) === check :
|
||||
operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
|
||||
operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
|
||||
operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
|
||||
false;
|
||||
};
|
||||
@@ -1795,7 +1813,7 @@ Expr = Sizzle.selectors = {
|
||||
matched = fn( seed, argument ),
|
||||
i = matched.length;
|
||||
while ( i-- ) {
|
||||
idx = indexOf.call( seed, matched[i] );
|
||||
idx = indexOf( seed, matched[i] );
|
||||
seed[ idx ] = !( matches[ idx ] = matched[i] );
|
||||
}
|
||||
}) :
|
||||
@@ -1834,6 +1852,8 @@ Expr = Sizzle.selectors = {
|
||||
function( elem, context, xml ) {
|
||||
input[0] = elem;
|
||||
matcher( input, null, xml, results );
|
||||
// Don't keep the element (issue #299)
|
||||
input[0] = null;
|
||||
return !results.pop();
|
||||
};
|
||||
}),
|
||||
@@ -1845,6 +1865,7 @@ Expr = Sizzle.selectors = {
|
||||
}),
|
||||
|
||||
"contains": markFunction(function( text ) {
|
||||
text = text.replace( runescape, funescape );
|
||||
return function( elem ) {
|
||||
return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
|
||||
};
|
||||
@@ -2266,7 +2287,7 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS
|
||||
i = matcherOut.length;
|
||||
while ( i-- ) {
|
||||
if ( (elem = matcherOut[i]) &&
|
||||
(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
|
||||
(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
|
||||
|
||||
seed[temp] = !(results[temp] = elem);
|
||||
}
|
||||
@@ -2301,13 +2322,16 @@ function matcherFromTokens( tokens ) {
|
||||
return elem === checkContext;
|
||||
}, implicitRelative, true ),
|
||||
matchAnyContext = addCombinator( function( elem ) {
|
||||
return indexOf.call( checkContext, elem ) > -1;
|
||||
return indexOf( checkContext, elem ) > -1;
|
||||
}, implicitRelative, true ),
|
||||
matchers = [ function( elem, context, xml ) {
|
||||
return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
|
||||
var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
|
||||
(checkContext = context).nodeType ?
|
||||
matchContext( elem, context, xml ) :
|
||||
matchAnyContext( elem, context, xml ) );
|
||||
// Avoid hanging onto element (issue #299)
|
||||
checkContext = null;
|
||||
return ret;
|
||||
} ];
|
||||
|
||||
for ( ; i < len; i++ ) {
|
||||
@@ -2557,7 +2581,7 @@ select = Sizzle.select = function( selector, context, results, seed ) {
|
||||
// Sort stability
|
||||
support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
|
||||
|
||||
// Support: Chrome<14
|
||||
// Support: Chrome 14-35+
|
||||
// Always assume duplicates if they aren't passed to the comparison function
|
||||
support.detectDuplicates = !!hasDuplicate;
|
||||
|
||||
@@ -6115,7 +6139,14 @@ var getStyles, curCSS,
|
||||
|
||||
if ( window.getComputedStyle ) {
|
||||
getStyles = function( elem ) {
|
||||
return elem.ownerDocument.defaultView.getComputedStyle( elem, null );
|
||||
// Support: IE<=11+, Firefox<=30+ (#15098, #14150)
|
||||
// IE throws on elements created in popups
|
||||
// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
|
||||
if ( elem.ownerDocument.defaultView.opener ) {
|
||||
return elem.ownerDocument.defaultView.getComputedStyle( elem, null );
|
||||
}
|
||||
|
||||
return window.getComputedStyle( elem, null );
|
||||
};
|
||||
|
||||
curCSS = function( elem, name, computed ) {
|
||||
@@ -6363,6 +6394,8 @@ function addGetHookIf( conditionFn, hookFn ) {
|
||||
|
||||
reliableMarginRightVal =
|
||||
!parseFloat( ( window.getComputedStyle( contents, null ) || {} ).marginRight );
|
||||
|
||||
div.removeChild( contents );
|
||||
}
|
||||
|
||||
// Support: IE8
|
||||
@@ -9070,7 +9103,8 @@ jQuery.extend({
|
||||
}
|
||||
|
||||
// We can fire global events as of now if asked to
|
||||
fireGlobals = s.global;
|
||||
// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
|
||||
fireGlobals = jQuery.event && s.global;
|
||||
|
||||
// Watch for a new set of requests
|
||||
if ( fireGlobals && jQuery.active++ === 0 ) {
|
||||
@@ -9329,13 +9363,6 @@ jQuery.each( [ "get", "post" ], function( i, method ) {
|
||||
};
|
||||
});
|
||||
|
||||
// Attach a bunch of functions for handling common AJAX events
|
||||
jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) {
|
||||
jQuery.fn[ type ] = function( fn ) {
|
||||
return this.on( type, fn );
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
jQuery._evalUrl = function( url ) {
|
||||
return jQuery.ajax({
|
||||
@@ -9561,8 +9588,9 @@ var xhrId = 0,
|
||||
|
||||
// Support: IE<10
|
||||
// Open requests must be manually aborted on unload (#5280)
|
||||
if ( window.ActiveXObject ) {
|
||||
jQuery( window ).on( "unload", function() {
|
||||
// See https://support.microsoft.com/kb/2856746 for more info
|
||||
if ( window.attachEvent ) {
|
||||
window.attachEvent( "onunload", function() {
|
||||
for ( var key in xhrCallbacks ) {
|
||||
xhrCallbacks[ key ]( undefined, true );
|
||||
}
|
||||
@@ -9996,6 +10024,16 @@ jQuery.fn.load = function( url, params, callback ) {
|
||||
|
||||
|
||||
|
||||
// Attach a bunch of functions for handling common AJAX events
|
||||
jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) {
|
||||
jQuery.fn[ type ] = function( fn ) {
|
||||
return this.on( type, fn );
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
jQuery.expr.filters.animated = function( elem ) {
|
||||
return jQuery.grep(jQuery.timers, function( fn ) {
|
||||
return elem === fn.elem;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1523
-123
File diff suppressed because it is too large
Load Diff
@@ -3,12 +3,32 @@
|
||||
MIT license
|
||||
*/
|
||||
|
||||
;(function(window, undefined) {
|
||||
(function(window, undefined) {
|
||||
|
||||
|
||||
var TraceKit = {};
|
||||
var _oldTraceKit = window.TraceKit;
|
||||
|
||||
// global reference to slice
|
||||
var _slice = [].slice;
|
||||
var UNKNOWN_FUNCTION = '?';
|
||||
|
||||
|
||||
/**
|
||||
* _has, a better form of hasOwnProperty
|
||||
* Example: _has(MainHostObject, property) === true/false
|
||||
*
|
||||
* @param {Object} host object to check property
|
||||
* @param {string} key to check
|
||||
*/
|
||||
function _has(object, key) {
|
||||
return Object.prototype.hasOwnProperty.call(object, key);
|
||||
}
|
||||
|
||||
function _isUndefined(what) {
|
||||
return typeof what === 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* TraceKit.noConflict: Export TraceKit out to another variable
|
||||
* Example: var TK = TraceKit.noConflict()
|
||||
@@ -19,14 +39,22 @@ TraceKit.noConflict = function noConflict() {
|
||||
};
|
||||
|
||||
/**
|
||||
* TraceKit._has, a better form of hasOwnProperty
|
||||
* Example: TraceKit._has(MainHostObject, property) === true/false
|
||||
* TraceKit.wrap: Wrap any function in a TraceKit reporter
|
||||
* Example: func = TraceKit.wrap(func);
|
||||
*
|
||||
* @param {Object} host object to check property
|
||||
* @param {string} key to check
|
||||
* @param {Function} func Function to be wrapped
|
||||
* @return {Function} The wrapped func
|
||||
*/
|
||||
TraceKit._has = function _has(object, key) {
|
||||
return Object.prototype.hasOwnProperty.call(object, key);
|
||||
TraceKit.wrap = function traceKitWrapper(func) {
|
||||
function wrapped() {
|
||||
try {
|
||||
return func.apply(this, arguments);
|
||||
} catch (e) {
|
||||
TraceKit.report(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return wrapped;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -78,6 +106,7 @@ TraceKit.report = (function reportModuleWrapper() {
|
||||
* @param {Function} handler
|
||||
*/
|
||||
function subscribe(handler) {
|
||||
installGlobalHandler();
|
||||
handlers.push(handler);
|
||||
}
|
||||
|
||||
@@ -103,9 +132,9 @@ TraceKit.report = (function reportModuleWrapper() {
|
||||
return;
|
||||
}
|
||||
for (var i in handlers) {
|
||||
if (TraceKit._has(handlers, i)) {
|
||||
if (_has(handlers, i)) {
|
||||
try {
|
||||
handlers[i].apply(null, [stack].concat(Array.prototype.slice.call(arguments, 2)));
|
||||
handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));
|
||||
} catch (inner) {
|
||||
exception = inner;
|
||||
}
|
||||
@@ -117,7 +146,7 @@ TraceKit.report = (function reportModuleWrapper() {
|
||||
}
|
||||
}
|
||||
|
||||
var _oldOnerrorHandler = window.onerror;
|
||||
var _oldOnerrorHandler, _onErrorHandlerInstalled;
|
||||
|
||||
/**
|
||||
* Ensures all global unhandled exceptions are recorded.
|
||||
@@ -127,7 +156,7 @@ TraceKit.report = (function reportModuleWrapper() {
|
||||
* @param {(number|string)} lineNo The line number at which the error
|
||||
* occurred.
|
||||
*/
|
||||
window.onerror = function traceKitWindowOnError(message, url, lineNo) {
|
||||
function traceKitWindowOnError(message, url, lineNo) {
|
||||
var stack = null;
|
||||
|
||||
if (lastExceptionStack) {
|
||||
@@ -158,14 +187,24 @@ TraceKit.report = (function reportModuleWrapper() {
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
function installGlobalHandler ()
|
||||
{
|
||||
if (_onErrorHandlerInstalled === true) {
|
||||
return;
|
||||
}
|
||||
_oldOnerrorHandler = window.onerror;
|
||||
window.onerror = traceKitWindowOnError;
|
||||
_onErrorHandlerInstalled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports an unhandled Error to TraceKit.
|
||||
* @param {Error} ex
|
||||
*/
|
||||
function report(ex) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
var args = _slice.call(arguments, 1);
|
||||
if (lastExceptionStack) {
|
||||
if (lastException === ex) {
|
||||
return; // already caught by an inner catch block, ignore
|
||||
@@ -283,29 +322,16 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
var XMLHttpRequestWrapper;
|
||||
var getXHR = function() {
|
||||
try {
|
||||
return new window.XMLHttpRequest();
|
||||
} catch (e) {
|
||||
// explicitly bubble up the exception if not found
|
||||
return new window.ActiveXObject('Microsoft.XMLHTTP');
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof (XMLHttpRequest) === 'undefined') { // IE 5.x-6.x:
|
||||
XMLHttpRequestWrapper = function IEXMLHttpRequestSub() {
|
||||
try {
|
||||
return new ActiveXObject('Msxml2.XMLHTTP.6.0');
|
||||
} catch (e) {}
|
||||
try {
|
||||
return new ActiveXObject('Msxml2.XMLHTTP.3.0');
|
||||
} catch (e) {}
|
||||
try {
|
||||
return new ActiveXObject('Msxml2.XMLHTTP');
|
||||
} catch (e) {}
|
||||
try {
|
||||
return new ActiveXObject('Microsoft.XMLHTTP');
|
||||
} catch (e) {}
|
||||
throw new Error('No XHR.');
|
||||
};
|
||||
} else {
|
||||
XMLHttpRequestWrapper = XMLHttpRequest;
|
||||
}
|
||||
|
||||
var request = new XMLHttpRequestWrapper();
|
||||
var request = getXHR();
|
||||
request.open('GET', url, false);
|
||||
request.send('');
|
||||
return request.responseText;
|
||||
@@ -320,16 +346,14 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
* @return {Array.<string>} Source contents.
|
||||
*/
|
||||
function getSource(url) {
|
||||
if (!TraceKit._has(sourceCache, url)) {
|
||||
if (!_has(sourceCache, url)) {
|
||||
// URL needs to be able to fetched within the acceptable domain. Otherwise,
|
||||
// cross-domain errors will be triggered.
|
||||
var source;
|
||||
var source = '';
|
||||
if (url.indexOf(document.domain) !== -1) {
|
||||
source = loadSource(url);
|
||||
} else {
|
||||
source = [];
|
||||
}
|
||||
sourceCache[url] = source.length ? source.split('\n') : [];
|
||||
sourceCache[url] = source ? source.split('\n') : [];
|
||||
}
|
||||
|
||||
return sourceCache[url];
|
||||
@@ -352,7 +376,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
m;
|
||||
|
||||
if (!source.length) {
|
||||
return '?';
|
||||
return UNKNOWN_FUNCTION;
|
||||
}
|
||||
|
||||
// Walk backwards from the first line in the function until we find the line which
|
||||
@@ -360,7 +384,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
for (var i = 0; i < maxLines; ++i) {
|
||||
line = source[lineNo - i] + line;
|
||||
|
||||
if (line !== undefined) {
|
||||
if (!_isUndefined(line)) {
|
||||
if ((m = reGuessFunction.exec(line))) {
|
||||
return m[1];
|
||||
} else if ((m = reFunctionArgNames.exec(line))) {
|
||||
@@ -369,7 +393,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
}
|
||||
}
|
||||
|
||||
return '?';
|
||||
return UNKNOWN_FUNCTION;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,7 +423,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
line -= 1; // convert to 0-based index
|
||||
|
||||
for (var i = start; i < end; ++i) {
|
||||
if (typeof (source[i]) !== 'undefined') {
|
||||
if (!_isUndefined(source[i])) {
|
||||
context.push(source[i]);
|
||||
}
|
||||
}
|
||||
@@ -594,7 +618,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
return null;
|
||||
}
|
||||
|
||||
var chrome = /^\s*at (?:((?:\[object object\])?\S+) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
|
||||
var chrome = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
|
||||
gecko = /^\s*(\S*)(?:\((.*?)\))?@((?:file|http|https).*?):(\d+)(?::(\d+))?\s*$/i,
|
||||
lines = ex.stack.split('\n'),
|
||||
stack = [],
|
||||
@@ -606,7 +630,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
if ((parts = gecko.exec(lines[i]))) {
|
||||
element = {
|
||||
'url': parts[3],
|
||||
'func': parts[1] || '?',
|
||||
'func': parts[1] || UNKNOWN_FUNCTION,
|
||||
'args': parts[2] ? parts[2].split(',') : '',
|
||||
'line': +parts[4],
|
||||
'column': parts[5] ? +parts[5] : null
|
||||
@@ -614,7 +638,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
} else if ((parts = chrome.exec(lines[i]))) {
|
||||
element = {
|
||||
'url': parts[2],
|
||||
'func': parts[1] || '?',
|
||||
'func': parts[1] || UNKNOWN_FUNCTION,
|
||||
'line': +parts[3],
|
||||
'column': parts[4] ? +parts[4] : null
|
||||
};
|
||||
@@ -750,7 +774,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
source;
|
||||
|
||||
for (i in scripts) {
|
||||
if (TraceKit._has(scripts, i) && !scripts[i].src) {
|
||||
if (_has(scripts, i) && !scripts[i].src) {
|
||||
inlineScriptBlocks.push(scripts[i]);
|
||||
}
|
||||
}
|
||||
@@ -904,7 +928,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
|
||||
item = {
|
||||
'url': null,
|
||||
'func': '?',
|
||||
'func': UNKNOWN_FUNCTION,
|
||||
'line': null,
|
||||
'column': null
|
||||
};
|
||||
@@ -919,7 +943,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
item.url = source.url;
|
||||
item.line = source.line;
|
||||
|
||||
if (item.func === '?') {
|
||||
if (item.func === UNKNOWN_FUNCTION) {
|
||||
item.func = guessFunctionName(item.url, item.line);
|
||||
}
|
||||
|
||||
@@ -1029,8 +1053,6 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
} catch (ex) {
|
||||
return computeStackTrace(ex, depth + 1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement;
|
||||
@@ -1045,22 +1067,15 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
* Extends support for global error handling for asynchronous browser
|
||||
* functions. Adopted from Closure Library's errorhandler.js
|
||||
*/
|
||||
(function extendToAsynchronousCallbacks(w) {
|
||||
(function extendToAsynchronousCallbacks() {
|
||||
var _helper = function _helper(fnName) {
|
||||
var originalFn = w[fnName];
|
||||
w[fnName] = function traceKitAsyncExtension() {
|
||||
var originalFn = window[fnName];
|
||||
window[fnName] = function traceKitAsyncExtension() {
|
||||
// Make a copy of the arguments
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
var args = _slice.call(arguments);
|
||||
var originalCallback = args[0];
|
||||
if (typeof (originalCallback) === 'function') {
|
||||
args[0] = function traceKitArgsZero() {
|
||||
try {
|
||||
originalCallback.apply(this, arguments);
|
||||
} catch (e) {
|
||||
TraceKit.report(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
args[0] = TraceKit.wrap(originalCallback);
|
||||
}
|
||||
// IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it
|
||||
// also only supports 2 argument and doesn't care what "this" is, so we
|
||||
@@ -1075,120 +1090,7 @@ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
|
||||
|
||||
_helper('setTimeout');
|
||||
_helper('setInterval');
|
||||
}(window));
|
||||
|
||||
/**
|
||||
* Extended support for backtraces and global error handling for most
|
||||
* asynchronous jQuery functions.
|
||||
*/
|
||||
(function traceKitAsyncForjQuery($) {
|
||||
|
||||
// quit if jQuery isn't on the page
|
||||
if (!$) {
|
||||
return;
|
||||
}
|
||||
|
||||
var _oldEventAdd = $.event.add;
|
||||
$.event.add = function traceKitEventAdd(elem, types, handler, data, selector) {
|
||||
var _handler;
|
||||
|
||||
if (handler.handler) {
|
||||
_handler = handler.handler;
|
||||
handler.handler = function traceKitHandler() {
|
||||
try {
|
||||
return _handler.apply(this, arguments);
|
||||
} catch (e) {
|
||||
TraceKit.report(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
_handler = handler;
|
||||
handler = function apply_handler() {
|
||||
try {
|
||||
return _handler.apply(this, arguments);
|
||||
} catch (e) {
|
||||
TraceKit.report(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// If the handler we are attaching doesn’t have the same guid as
|
||||
// the original, it will never be removed when someone tries to
|
||||
// unbind the original function later. Technically as a result of
|
||||
// this our guids are no longer globally unique, but whatever, that
|
||||
// never hurt anybody RIGHT?!
|
||||
if (_handler.guid) {
|
||||
handler.guid = _handler.guid;
|
||||
} else {
|
||||
handler.guid = _handler.guid = $.guid++;
|
||||
}
|
||||
|
||||
return _oldEventAdd.call(this, elem, types, handler, data, selector);
|
||||
};
|
||||
|
||||
var _oldReady = $.fn.ready;
|
||||
$.fn.ready = function traceKitjQueryReadyWrapper(fn) {
|
||||
var _fn = function () {
|
||||
try {
|
||||
return fn.apply(this, arguments);
|
||||
} catch (e) {
|
||||
TraceKit.report(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
return _oldReady.call(this, _fn);
|
||||
};
|
||||
|
||||
var _oldAjax = $.ajax;
|
||||
$.ajax = function traceKitAjaxWrapper(s) {
|
||||
if ($.isFunction(s.complete)) {
|
||||
var _oldComplete = s.complete;
|
||||
s.complete = function traceKitjQueryComplete() {
|
||||
try {
|
||||
return _oldComplete.apply(this, arguments);
|
||||
} catch (e) {
|
||||
TraceKit.report(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if ($.isFunction(s.error)) {
|
||||
var _oldError = s.error;
|
||||
s.error = function traceKitjQueryError() {
|
||||
try {
|
||||
return _oldError.apply(this, arguments);
|
||||
} catch (e) {
|
||||
TraceKit.report(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if ($.isFunction(s.success)) {
|
||||
var _oldSuccess = s.success;
|
||||
s.success = function traceKitjQuerySuccess() {
|
||||
try {
|
||||
return _oldSuccess.apply(this, arguments);
|
||||
} catch (e) {
|
||||
TraceKit.report(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return _oldAjax.call(this, s);
|
||||
} catch (e) {
|
||||
TraceKit.report(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
}(window.jQuery));
|
||||
}());
|
||||
|
||||
//Default options:
|
||||
if (!TraceKit.remoteFetching) {
|
||||
|
||||
@@ -1159,7 +1159,6 @@
|
||||
return value;
|
||||
};
|
||||
|
||||
// Predicate-generating functions. Often useful outside of Underscore.
|
||||
_.constant = function(value) {
|
||||
return function() {
|
||||
return value;
|
||||
|
||||
@@ -75,13 +75,17 @@ var HistoryPanelEdit = _super.extend(
|
||||
|
||||
/** Override to handle history as drag-drop target */
|
||||
_setUpListeners : function(){
|
||||
_super.prototype._setUpListeners.call( this );
|
||||
var panel = this;
|
||||
_super.prototype._setUpListeners.call( panel );
|
||||
|
||||
this.on( 'drop', function( ev, data ){
|
||||
this.dataDropped( data );
|
||||
panel.on( 'drop', function( ev, data ){
|
||||
panel.dataDropped( data );
|
||||
// remove the drop target
|
||||
this.dropTargetOff();
|
||||
panel.dropTargetOff();
|
||||
});
|
||||
panel.on( 'view:attached view:removed', function(){
|
||||
panel._renderCounts();
|
||||
}, panel );
|
||||
},
|
||||
|
||||
// ------------------------------------------------------------------------ listeners
|
||||
@@ -332,7 +336,10 @@ var HistoryPanelEdit = _super.extend(
|
||||
// override to control where the view is added, how/whether it's rendered
|
||||
panel.views.unshift( view );
|
||||
panel.$list().prepend( view.render( 0 ).$el.hide() );
|
||||
view.$el.slideDown( panel.fxSpeed );
|
||||
panel.trigger( 'view:attached', view );
|
||||
view.$el.slideDown( panel.fxSpeed, function(){
|
||||
panel.trigger( 'view:attached:rendered' );
|
||||
});
|
||||
},
|
||||
|
||||
/** In this override, add purgeAllowed and whether tags/annotation editors should be shown */
|
||||
|
||||
@@ -199,7 +199,7 @@ define([], function() {
|
||||
*/
|
||||
|
||||
var Jstree = Backbone.Model.extend({
|
||||
urlRoot: '/api/ftp_files'
|
||||
urlRoot: '/api/remote_files'
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -477,7 +477,10 @@ var ListPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend(
|
||||
// override to control where the view is added, how/whether it's rendered
|
||||
panel.views.push( view );
|
||||
panel.$list().append( view.render( 0 ).$el.hide() );
|
||||
view.$el.slideDown( panel.fxSpeed );
|
||||
panel.trigger( 'view:attached', view );
|
||||
view.$el.slideDown( panel.fxSpeed, function(){
|
||||
panel.trigger( 'view:attached:rendered' );
|
||||
});
|
||||
},
|
||||
|
||||
/** Remove a view from the panel (if found) */
|
||||
@@ -487,6 +490,7 @@ var ListPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend(
|
||||
view = panel.viewFromModel( model );
|
||||
if( !view ){ return undefined; }
|
||||
panel.views = _.without( panel.views, view );
|
||||
panel.trigger( 'view:removed', view );
|
||||
|
||||
// potentially show the empty message if no views left
|
||||
// use anonymous queue here - since remove can happen multiple times
|
||||
@@ -494,6 +498,7 @@ var ListPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend(
|
||||
function( next ){ view.$el.fadeOut( panel.fxSpeed, next ); },
|
||||
function( next ){
|
||||
view.remove();
|
||||
panel.trigger( 'view:removed:rendered' );
|
||||
if( !panel.views.length ){
|
||||
panel._renderEmptyMessage().fadeIn( panel.fxSpeed, next );
|
||||
} else {
|
||||
@@ -872,10 +877,10 @@ var ModelListPanel = ListPanel.extend({
|
||||
this.log( this + '._setUpModelListeners', this.model );
|
||||
// bounce model errors up to the panel
|
||||
this.model.on( 'error', function(){
|
||||
//TODO: namespace?
|
||||
//var args = Array.prototype.slice.call( arguments, 0 );
|
||||
//args[0] = 'model:' + args[0];
|
||||
this.trigger.apply( panel, arguments );
|
||||
var args = Array.prototype.slice.call( arguments, 0 );
|
||||
//args.unshift( 'model:error' );
|
||||
args.unshift( 'error' );
|
||||
this.trigger.apply( this, args );
|
||||
}, this );
|
||||
return this;
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Model, view, and controller objects for Galaxy tools and tool panel.
|
||||
*/
|
||||
|
||||
define( ["libs/underscore", "viz/trackster/util", "mvc/data" ],
|
||||
define( ["libs/underscore", "viz/trackster/util", "mvc/data", "libs/lunr" ],
|
||||
function(_, util, data) {
|
||||
|
||||
/**
|
||||
@@ -14,7 +14,7 @@ var VisibilityMixin = {
|
||||
show: function() {
|
||||
this.set("hidden", false);
|
||||
},
|
||||
|
||||
|
||||
hide: function() {
|
||||
this.set("hidden", true);
|
||||
},
|
||||
@@ -22,7 +22,7 @@ var VisibilityMixin = {
|
||||
toggle: function() {
|
||||
this.set("hidden", !this.get("hidden"));
|
||||
},
|
||||
|
||||
|
||||
is_visible: function() {
|
||||
return !this.attributes.hidden;
|
||||
}
|
||||
@@ -88,7 +88,7 @@ var FloatToolParameter = IntegerToolParameter.extend({
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
/**
|
||||
* A select tool parameter.
|
||||
*/
|
||||
var SelectToolParameter = ToolParameter.extend({
|
||||
@@ -157,7 +157,7 @@ var Tool = Backbone.Model.extend({
|
||||
});
|
||||
tool.get('inputs').remove(incompatible_inputs);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Returns object copy, optionally including only inputs that can be sampled.
|
||||
*/
|
||||
@@ -177,12 +177,12 @@ var Tool = Backbone.Model.extend({
|
||||
|
||||
return copy;
|
||||
},
|
||||
|
||||
|
||||
apply_search_results: function(results) {
|
||||
( _.indexOf(results, this.attributes.id) !== -1 ? this.show() : this.hide() );
|
||||
return this.is_visible();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Set a tool input's value.
|
||||
*/
|
||||
@@ -191,24 +191,24 @@ var Tool = Backbone.Model.extend({
|
||||
return input.get('name') === name;
|
||||
}).set('value', value);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Set many input values at once.
|
||||
*/
|
||||
set_input_values: function(inputs_dict) {
|
||||
var self = this;
|
||||
_.each(_.keys(inputs_dict), function(input_name) {
|
||||
self.set_input_value(input_name, inputs_dict[input_name]);
|
||||
self.set_input_value(input_name, inputs_dict[input_name]);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Run tool; returns a Deferred that resolves to the tool's output(s).
|
||||
*/
|
||||
run: function() {
|
||||
return this._run();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Rerun tool using regions and a target dataset.
|
||||
*/
|
||||
@@ -230,7 +230,7 @@ var Tool = Backbone.Model.extend({
|
||||
});
|
||||
return input_dict;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Run tool; returns a Deferred that resolves to the tool's output(s).
|
||||
* NOTE: this method is a helper method and should not be called directly.
|
||||
@@ -259,7 +259,7 @@ var Tool = Backbone.Model.extend({
|
||||
return response !== "pending";
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Run job and resolve run_deferred to tool outputs.
|
||||
$.when(ss_deferred.go()).then(function(result) {
|
||||
run_deferred.resolve(new data.DatasetCollection().reset(result));
|
||||
@@ -296,20 +296,20 @@ var ToolSection = Backbone.Model.extend({
|
||||
elems: [],
|
||||
open: false
|
||||
},
|
||||
|
||||
|
||||
clear_search_results: function() {
|
||||
_.each(this.attributes.elems, function(elt) {
|
||||
elt.show();
|
||||
});
|
||||
|
||||
|
||||
this.show();
|
||||
this.set("open", false);
|
||||
},
|
||||
|
||||
|
||||
apply_search_results: function(results) {
|
||||
var all_hidden = true,
|
||||
cur_label;
|
||||
_.each(this.attributes.elems, function(elt) {
|
||||
_.each(this.attributes.elems, function(elt) {
|
||||
if (elt instanceof ToolSectionLabel) {
|
||||
cur_label = elt;
|
||||
cur_label.hide();
|
||||
@@ -323,7 +323,7 @@ var ToolSection = Backbone.Model.extend({
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (all_hidden) {
|
||||
this.hide();
|
||||
}
|
||||
@@ -340,6 +340,11 @@ _.extend(ToolSection.prototype, VisibilityMixin);
|
||||
* indicates that query was not run; if not null, results are from search using
|
||||
* query.
|
||||
*/
|
||||
|
||||
/**
|
||||
* TODO: Integrate lunr search here with tools from API instead of making
|
||||
* repeated requests.
|
||||
*/
|
||||
var ToolSearch = Backbone.Model.extend({
|
||||
defaults: {
|
||||
search_hint_string: "search tools",
|
||||
@@ -353,23 +358,23 @@ var ToolSearch = Backbone.Model.extend({
|
||||
// ESC (27) will clear the input field and tool search filters
|
||||
clear_key: 27
|
||||
},
|
||||
|
||||
|
||||
initialize: function() {
|
||||
this.on("change:query", this.do_search);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Do the search and update the results.
|
||||
*/
|
||||
do_search: function() {
|
||||
var query = this.attributes.query;
|
||||
|
||||
|
||||
// If query is too short, do not search.
|
||||
if (query.length < this.attributes.min_chars_for_search) {
|
||||
this.set("results", null);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Do search via AJAX.
|
||||
var q = query + '*';
|
||||
// Stop previous ajax-request
|
||||
@@ -388,12 +393,12 @@ var ToolSearch = Backbone.Model.extend({
|
||||
}, "json" );
|
||||
}, 200 );
|
||||
},
|
||||
|
||||
|
||||
clear_search: function() {
|
||||
this.set("query", "");
|
||||
this.set("results", null);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
_.extend(ToolSearch.prototype, VisibilityMixin);
|
||||
|
||||
@@ -433,7 +438,7 @@ var ToolPanel = Backbone.Model.extend({
|
||||
return new ToolSectionLabel(elt_dict);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return _.map(response, parse_elt);
|
||||
},
|
||||
|
||||
@@ -448,14 +453,14 @@ var ToolPanel = Backbone.Model.extend({
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
apply_search_results: function() {
|
||||
var results = this.get('tool_search').get('results');
|
||||
if (results === null) {
|
||||
this.clear_search_results();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var cur_label = null;
|
||||
this.get('layout').each(function(panel_elt) {
|
||||
if (panel_elt instanceof ToolSectionLabel) {
|
||||
@@ -479,13 +484,13 @@ var ToolPanel = Backbone.Model.extend({
|
||||
});
|
||||
|
||||
/**
|
||||
* View classes for Galaxy tools and tool panel.
|
||||
*
|
||||
* View classes for Galaxy tools and tool panel.
|
||||
*
|
||||
* Views use precompiled Handlebars templates for rendering. Views update as needed
|
||||
* based on (a) model/collection events and (b) user interactions; in this sense,
|
||||
* they are controllers are well and the HTML is the real view in the MVC architecture.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Base view that handles visibility based on model's hidden attribute.
|
||||
*/
|
||||
@@ -496,9 +501,9 @@ var BaseView = Backbone.View.extend({
|
||||
},
|
||||
update_visible: function() {
|
||||
( this.model.attributes.hidden ? this.$el.hide() : this.$el.show() );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Link to a tool.
|
||||
*/
|
||||
@@ -509,7 +514,7 @@ var ToolLinkView = BaseView.extend({
|
||||
// create element
|
||||
var $link = $('<div/>');
|
||||
$link.append(Handlebars.templates.tool_link(this.model.toJSON()));
|
||||
|
||||
|
||||
// open upload dialog for upload tool
|
||||
if (this.model.id === 'upload1') {
|
||||
$link.find('a').on('click', function(e) {
|
||||
@@ -517,7 +522,7 @@ var ToolLinkView = BaseView.extend({
|
||||
Galaxy.upload.show();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// add element
|
||||
this.$el.append($link);
|
||||
return this;
|
||||
@@ -543,7 +548,7 @@ var ToolSectionLabelView = BaseView.extend({
|
||||
var ToolSectionView = BaseView.extend({
|
||||
tagName: 'div',
|
||||
className: 'toolSectionWrapper',
|
||||
|
||||
|
||||
initialize: function() {
|
||||
BaseView.prototype.initialize.call(this);
|
||||
this.model.on("change:open", this.update_open, this);
|
||||
@@ -552,7 +557,7 @@ var ToolSectionView = BaseView.extend({
|
||||
render: function() {
|
||||
// Build using template.
|
||||
this.$el.append( Handlebars.templates.panel_section(this.model.toJSON()) );
|
||||
|
||||
|
||||
// Add tools to section.
|
||||
var section_body = this.$el.find(".toolSectionBody");
|
||||
_.each(this.model.attributes.elems, function(elt) {
|
||||
@@ -572,25 +577,25 @@ var ToolSectionView = BaseView.extend({
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
events: {
|
||||
'click .toolSectionTitle > a': 'toggle'
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Toggle visibility of tool section.
|
||||
*/
|
||||
toggle: function() {
|
||||
this.model.set("open", !this.model.attributes.open);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Update whether section is open or close.
|
||||
*/
|
||||
update_open: function() {
|
||||
(this.model.attributes.open ?
|
||||
this.$el.children(".toolSectionBody").slideDown("fast") :
|
||||
this.$el.children(".toolSectionBody").slideUp("fast")
|
||||
this.$el.children(".toolSectionBody").slideUp("fast")
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -599,13 +604,13 @@ var ToolSearchView = Backbone.View.extend({
|
||||
tagName: 'div',
|
||||
id: 'tool-search',
|
||||
className: 'bar',
|
||||
|
||||
|
||||
events: {
|
||||
'click': 'focus_and_select',
|
||||
'keyup :input': 'query_changed',
|
||||
'click #search-clear-btn': 'clear'
|
||||
},
|
||||
|
||||
|
||||
render: function() {
|
||||
this.$el.append( Handlebars.templates.tool_search(this.model.toJSON()) );
|
||||
if (!this.model.is_visible()) {
|
||||
@@ -614,18 +619,18 @@ var ToolSearchView = Backbone.View.extend({
|
||||
this.$el.find('[title]').tooltip();
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
focus_and_select: function() {
|
||||
this.$el.find(":input").focus().select();
|
||||
},
|
||||
|
||||
|
||||
clear: function() {
|
||||
this.model.clear_search();
|
||||
this.$el.find(":input").val(this.model.attributes.search_hint_string);
|
||||
this.focus_and_select();
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
query_changed: function( evData ) {
|
||||
// check for the 'clear key' (ESC) first
|
||||
if( ( this.model.attributes.clear_key ) &&
|
||||
@@ -644,22 +649,22 @@ var ToolSearchView = Backbone.View.extend({
|
||||
var ToolPanelView = Backbone.View.extend({
|
||||
tagName: 'div',
|
||||
className: 'toolMenu',
|
||||
|
||||
|
||||
/**
|
||||
* Set up view.
|
||||
*/
|
||||
initialize: function() {
|
||||
this.model.get('tool_search').on("change:results", this.handle_search_results, this);
|
||||
},
|
||||
|
||||
|
||||
render: function() {
|
||||
var self = this;
|
||||
|
||||
|
||||
// Render search.
|
||||
var search_view = new ToolSearchView( { model: this.model.get('tool_search') } );
|
||||
search_view.render();
|
||||
self.$el.append(search_view.$el);
|
||||
|
||||
|
||||
// Render panel.
|
||||
this.model.get('layout').each(function(panel_elt) {
|
||||
if (panel_elt instanceof ToolSection) {
|
||||
@@ -678,20 +683,20 @@ var ToolPanelView = Backbone.View.extend({
|
||||
self.$el.append(label_view.$el);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Setup tool link click eventing.
|
||||
self.$el.find("a.tool-link").click(function(e) {
|
||||
// Tool id is always the first class.
|
||||
var
|
||||
var
|
||||
tool_id = $(this).attr('class').split(/\s+/)[0],
|
||||
tool = self.model.get('tools').get(tool_id);
|
||||
|
||||
|
||||
self.trigger("tool_link_click", e, tool);
|
||||
});
|
||||
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
handle_search_results: function() {
|
||||
var results = this.model.get('tool_search').get('results');
|
||||
if (results && results.length === 0) {
|
||||
@@ -708,7 +713,7 @@ var ToolPanelView = Backbone.View.extend({
|
||||
*/
|
||||
var ToolFormView = Backbone.View.extend({
|
||||
className: 'toolForm',
|
||||
|
||||
|
||||
render: function() {
|
||||
this.$el.children().remove();
|
||||
this.$el.append( Handlebars.templates.tool_form(this.model.toJSON()) );
|
||||
@@ -720,22 +725,22 @@ var ToolFormView = Backbone.View.extend({
|
||||
*/
|
||||
var IntegratedToolMenuAndView = Backbone.View.extend({
|
||||
className: 'toolMenuAndView',
|
||||
|
||||
|
||||
initialize: function() {
|
||||
this.tool_panel_view = new ToolPanelView({collection: this.collection});
|
||||
this.tool_form_view = new ToolFormView();
|
||||
},
|
||||
|
||||
|
||||
render: function() {
|
||||
// Render and append tool panel.
|
||||
this.tool_panel_view.render();
|
||||
this.tool_panel_view.$el.css("float", "left");
|
||||
this.$el.append(this.tool_panel_view.$el);
|
||||
|
||||
|
||||
// Append tool form view.
|
||||
this.tool_form_view.$el.hide();
|
||||
this.$el.append(this.tool_form_view.$el);
|
||||
|
||||
|
||||
// On tool link click, show tool.
|
||||
var self = this;
|
||||
this.tool_panel_view.on("tool_link_click", function(e, tool) {
|
||||
@@ -745,7 +750,7 @@ var IntegratedToolMenuAndView = Backbone.View.extend({
|
||||
self.show_tool(tool);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Fetch and display tool.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
/**
|
||||
This is the main class of the tool form plugin. It is referenced as 'app' in all lower level modules.
|
||||
*/
|
||||
define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
|
||||
'mvc/citation/citation-model', 'mvc/citation/citation-view',
|
||||
'mvc/tools', 'mvc/tools/tools-template', 'mvc/tools/tools-content', 'mvc/tools/tools-section', 'mvc/tools/tools-tree'],
|
||||
function(Utils, Deferred, Portlet, Ui, CitationModel, CitationView,
|
||||
Tools, ToolTemplate, ToolContent, ToolSection, ToolTree) {
|
||||
|
||||
// create form view
|
||||
return Backbone.View.extend({
|
||||
// initialize
|
||||
initialize: function(options) {
|
||||
// options
|
||||
this.optionsDefault = {
|
||||
// uses dynamic fields instead of text fields
|
||||
is_dynamic : true,
|
||||
// shows form in narrow view mode
|
||||
narrow : false,
|
||||
// shows errors on start
|
||||
initial_errors : false
|
||||
};
|
||||
|
||||
// configure options
|
||||
this.options = Utils.merge(options, this.optionsDefault);
|
||||
|
||||
// log options
|
||||
console.debug(this.options);
|
||||
|
||||
// link galaxy modal or create one
|
||||
var galaxy = parent.Galaxy;
|
||||
if (galaxy && galaxy.modal) {
|
||||
this.modal = galaxy.modal;
|
||||
} else {
|
||||
this.modal = new Ui.Modal.View();
|
||||
}
|
||||
|
||||
// check if the user is an admin
|
||||
if (galaxy && galaxy.currUser) {
|
||||
this.is_admin = galaxy.currUser.get('is_admin');
|
||||
} else {
|
||||
this.is_admin = false;
|
||||
}
|
||||
|
||||
// link container
|
||||
this.container = this.options.container || 'body';
|
||||
|
||||
// create deferred processing queue handler
|
||||
// this handler reduces the number of requests to the api by filtering redundant requests
|
||||
this.deferred = new Deferred();
|
||||
|
||||
// set element
|
||||
this.setElement('<div/>');
|
||||
|
||||
// add to main element
|
||||
$(this.container).append(this.$el);
|
||||
|
||||
// build this form
|
||||
this.build(this.options);
|
||||
},
|
||||
|
||||
/** Main tool form build function. This function is called once a new model is available.
|
||||
*/
|
||||
build: function(options) {
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
// reset events
|
||||
this.off('refresh');
|
||||
this.off('reset');
|
||||
|
||||
// reset field list, which contains the input field elements
|
||||
this.field_list = {};
|
||||
|
||||
// reset sequential input definition list, which contains the input definitions as provided from the api
|
||||
this.input_list = {};
|
||||
|
||||
// reset input element list, which contains the dom elements of each input element (includes also the input field)
|
||||
this.element_list = {};
|
||||
|
||||
// creates a tree/json data structure from the input form
|
||||
this.tree = new ToolTree(this);
|
||||
|
||||
// request history content and build form
|
||||
this.content = new ToolContent(this);
|
||||
|
||||
// update model data
|
||||
self.options.inputs = options && options.inputs;
|
||||
|
||||
// create ui elements
|
||||
this._renderForm(options);
|
||||
|
||||
// rebuild the underlying data structure
|
||||
this.tree.finalize();
|
||||
|
||||
// show errors on startup
|
||||
if (options.initial_errors) {
|
||||
this._errors(options);
|
||||
}
|
||||
|
||||
// add refresh listener
|
||||
this.on('refresh', function() {
|
||||
// by using/resetting the deferred ajax queue the number of redundant calls is reduced
|
||||
self.deferred.reset();
|
||||
self.deferred.execute(function(){self._updateModel()});
|
||||
});
|
||||
|
||||
// add reset listener
|
||||
this.on('reset', function() {
|
||||
for (var i in this.element_list) {
|
||||
this.element_list[i].reset();
|
||||
}
|
||||
});
|
||||
|
||||
// refresh
|
||||
this.trigger('refresh');
|
||||
},
|
||||
|
||||
/** Shows the final message (usually upon successful job submission)
|
||||
*/
|
||||
reciept: function($el) {
|
||||
$(this.container).empty();
|
||||
$(this.container).append($el);
|
||||
},
|
||||
|
||||
/** Highlight and scroll to input element (currently only used for error notifications)
|
||||
*/
|
||||
highlight: function (input_id, message, silent) {
|
||||
// get input field
|
||||
var input_element = this.element_list[input_id];
|
||||
|
||||
// check input element
|
||||
if (input_element) {
|
||||
// mark error
|
||||
input_element.error(message || 'Please verify this parameter.');
|
||||
|
||||
// scroll to first input element
|
||||
if (!silent) {
|
||||
$(this.container).animate({
|
||||
scrollTop: input_element.$el.offset().top - 20
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** Highlights errors
|
||||
*/
|
||||
_errors: function(options) {
|
||||
// hide previous error statements
|
||||
this.trigger('reset');
|
||||
|
||||
// highlight all errors
|
||||
if (options && options.errors) {
|
||||
var error_messages = this.tree.matchResponse(options.errors);
|
||||
for (var input_id in this.element_list) {
|
||||
var input = this.element_list[input_id];
|
||||
if (error_messages[input_id]) {
|
||||
this.highlight(input_id, error_messages[input_id], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** Renders the UI elements required for the form
|
||||
*/
|
||||
_renderForm: function(options) {
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
// create message view
|
||||
this.message = new Ui.Message();
|
||||
|
||||
// button for version selection
|
||||
var requirements_button = new Ui.ButtonIcon({
|
||||
icon : 'fa-info-circle',
|
||||
title : (!options.narrow && 'Requirements') || null,
|
||||
tooltip : 'Display tool requirements',
|
||||
onclick : function() {
|
||||
if (!this.visible) {
|
||||
this.visible = true;
|
||||
self.message.update({
|
||||
persistent : true,
|
||||
message : ToolTemplate.requirements(options),
|
||||
status : 'info'
|
||||
});
|
||||
} else {
|
||||
this.visible = false;
|
||||
self.message.update({
|
||||
message : ''
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!options.requirements || options.requirements.length == 0) {
|
||||
requirements_button.$el.hide();
|
||||
}
|
||||
|
||||
// button for version selection
|
||||
var versions_button = new Ui.ButtonMenu({
|
||||
icon : 'fa-cubes',
|
||||
title : (!options.narrow && 'Versions') || null,
|
||||
tooltip : 'Select another tool version'
|
||||
});
|
||||
if (options.versions && options.versions.length > 1) {
|
||||
for (var i in options.versions) {
|
||||
var version = options.versions[i];
|
||||
if (version != options.version) {
|
||||
versions_button.addMenu({
|
||||
title : 'Switch to ' + version,
|
||||
version : version,
|
||||
icon : 'fa-cube',
|
||||
onclick : function() {
|
||||
// here we update the tool version (some tools encode the version also in the id)
|
||||
self.options.id = self.options.id.replace(self.options.version, this.version);
|
||||
self.options.version = this.version;
|
||||
|
||||
// rebuild the model and form
|
||||
self.deferred.reset();
|
||||
self.deferred.execute(function(){self._buildModel()});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
versions_button.$el.hide();
|
||||
}
|
||||
|
||||
// button menu
|
||||
var menu_button = new Ui.ButtonMenu({
|
||||
icon : 'fa-caret-down',
|
||||
title : (!options.narrow && 'Options') || null,
|
||||
tooltip : 'View available options'
|
||||
});
|
||||
|
||||
// configure button selection
|
||||
if(options.biostar_url) {
|
||||
// add question option
|
||||
menu_button.addMenu({
|
||||
icon : 'fa-question-circle',
|
||||
title : 'Question?',
|
||||
tooltip : 'Ask a question about this tool (Biostar)',
|
||||
onclick : function() {
|
||||
window.open(options.biostar_url + '/p/new/post/');
|
||||
}
|
||||
});
|
||||
|
||||
// create search button
|
||||
menu_button.addMenu({
|
||||
icon : 'fa-search',
|
||||
title : 'Search',
|
||||
tooltip : 'Search help for this tool (Biostar)',
|
||||
onclick : function() {
|
||||
window.open(options.biostar_url + '/t/' + options.id + '/');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// create share button
|
||||
menu_button.addMenu({
|
||||
icon : 'fa-share',
|
||||
title : 'Share',
|
||||
tooltip : 'Share this tool',
|
||||
onclick : function() {
|
||||
prompt('Copy to clipboard: Ctrl+C, Enter', window.location.origin + galaxy_config.root + 'root?tool_id=' + options.id);
|
||||
}
|
||||
});
|
||||
|
||||
// add admin operations
|
||||
if (this.is_admin) {
|
||||
// create download button
|
||||
menu_button.addMenu({
|
||||
icon : 'fa-download',
|
||||
title : 'Download',
|
||||
tooltip : 'Download this tool',
|
||||
onclick : function() {
|
||||
window.location.href = galaxy_config.root + 'api/tools/' + options.id + '/download';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// create tool form section
|
||||
this.section = new ToolSection.View(self, {
|
||||
inputs : options.inputs
|
||||
});
|
||||
|
||||
// switch to classic tool form mako if the form definition is incompatible
|
||||
if (this.incompatible) {
|
||||
this.$el.hide();
|
||||
$('#tool-form-classic').show();
|
||||
return;
|
||||
}
|
||||
|
||||
// create portlet
|
||||
this.portlet = new Portlet.View({
|
||||
icon : 'fa-wrench',
|
||||
title : '<b>' + options.name + '</b> ' + options.description + ' (Galaxy Tool Version ' + options.version + ')',
|
||||
cls : 'ui-portlet-slim',
|
||||
operations: {
|
||||
requirements : requirements_button,
|
||||
menu : menu_button,
|
||||
versions : versions_button
|
||||
},
|
||||
buttons : this.buttons
|
||||
});
|
||||
|
||||
// remove padding
|
||||
if (options.narrow) {
|
||||
this.portlet.$content.css('padding', '0px');
|
||||
}
|
||||
|
||||
// append message
|
||||
this.portlet.append(this.message.$el, true);
|
||||
|
||||
// append tool section
|
||||
this.portlet.append(this.section.$el);
|
||||
|
||||
// start form
|
||||
this.$el.empty();
|
||||
this.$el.append(this.portlet.$el);
|
||||
|
||||
// append help
|
||||
if (options.help != '') {
|
||||
this.$el.append(ToolTemplate.help(options.help));
|
||||
}
|
||||
|
||||
// append citations
|
||||
if (options.citations) {
|
||||
var $citations = $('<div/>');
|
||||
var citations = new CitationModel.ToolCitationCollection();
|
||||
citations.tool_id = options.id;
|
||||
var citation_list_view = new CitationView.CitationListView({ el: $citations, collection: citations } );
|
||||
citation_list_view.render();
|
||||
citations.fetch();
|
||||
this.$el.append($citations);
|
||||
}
|
||||
|
||||
// show message if available in model
|
||||
if (options.message) {
|
||||
this.message.update({
|
||||
persistent : true,
|
||||
status : 'warning',
|
||||
message : options.message
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,325 @@
|
||||
/**
|
||||
This is the main class of the tool form plugin. It is referenced as 'app' in all lower level modules.
|
||||
*/
|
||||
define(['utils/utils', 'mvc/tools/tools-form-base'],
|
||||
function(Utils, ToolFormBase) {
|
||||
|
||||
// create form view
|
||||
var View = ToolFormBase.extend({
|
||||
initialize: function(options) {
|
||||
// link with node representation in workflow module
|
||||
this.node = workflow.active_node;
|
||||
if (!this.node) {
|
||||
console.debug('FAILED - tools-form-workflow:initialize() - Node not found in workflow.');
|
||||
return;
|
||||
}
|
||||
|
||||
// link actions
|
||||
this.post_job_actions = this.node.post_job_actions || {};
|
||||
|
||||
// initialize parameters
|
||||
this.options = options;
|
||||
|
||||
// set labels
|
||||
this.options.text_enable = 'In Advance';
|
||||
this.options.text_disable = 'At Runtime';
|
||||
|
||||
// configure workflow style
|
||||
this.options.is_dynamic = false;
|
||||
this.options.narrow = true;
|
||||
this.options.initial_errors = true;
|
||||
|
||||
// declare fields as optional
|
||||
Utils.deepeach(options.inputs, function(item) {
|
||||
if (item.type) {
|
||||
item.optional = (['data', 'data_hidden', 'hidden', 'drill_down', 'repeat', 'conditional']).indexOf(item.type) == -1;
|
||||
}
|
||||
});
|
||||
|
||||
// declare conditional fields as not optional
|
||||
Utils.deepeach(options.inputs, function(item) {
|
||||
if (item.type) {
|
||||
if (item.type == 'conditional') {
|
||||
item.test_param.optional = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// load extension
|
||||
var self = this;
|
||||
Utils.get({
|
||||
url : galaxy_config.root + 'api/datatypes',
|
||||
cache : true,
|
||||
success : function(datatypes) {
|
||||
self.datatypes = datatypes;
|
||||
self._makeSections(options.inputs);
|
||||
ToolFormBase.prototype.initialize.call(self, options);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Builds all sub sections
|
||||
*/
|
||||
_makeSections: function(inputs){
|
||||
// for annotation
|
||||
inputs[Utils.uuid()] = {
|
||||
label : 'Annotation / Notes',
|
||||
name : 'annotation',
|
||||
type : 'text',
|
||||
area : true,
|
||||
help : 'Add an annotation or note for this step. It will be shown with the workflow.',
|
||||
value : this.node.annotation
|
||||
}
|
||||
|
||||
// get first output id
|
||||
var output_id = this.node.output_terminals && Object.keys(this.node.output_terminals)[0];
|
||||
if (output_id) {
|
||||
// send email on job completion
|
||||
inputs[Utils.uuid()] = {
|
||||
name : 'pja__' + output_id + '__EmailAction',
|
||||
label : 'Email notification',
|
||||
type : 'boolean',
|
||||
value : String(Boolean(this.post_job_actions['EmailAction' + output_id])),
|
||||
ignore : 'false',
|
||||
help : 'An email notification will be send when the job has completed.',
|
||||
payload : {
|
||||
'host' : window.location.host
|
||||
}
|
||||
};
|
||||
|
||||
// delete non-output files
|
||||
inputs[Utils.uuid()] = {
|
||||
name : 'pja__' + output_id + '__DeleteIntermediatesAction',
|
||||
label : 'Output cleanup',
|
||||
type : 'boolean',
|
||||
value : String(Boolean(this.post_job_actions['DeleteIntermediatesAction' + output_id])),
|
||||
ignore : 'false',
|
||||
help : 'Delete intermediate outputs if they are not used as input for another job.'
|
||||
};
|
||||
|
||||
// add output specific actions
|
||||
for (var i in this.node.output_terminals) {
|
||||
inputs[Utils.uuid()] = this._makeSection(i);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** Builds sub section with step actions/annotation
|
||||
*/
|
||||
_makeSection: function(output_id){
|
||||
// format datatypes
|
||||
var extensions = [];
|
||||
for (key in this.datatypes) {
|
||||
extensions.push({
|
||||
0 : this.datatypes[key],
|
||||
1 : this.datatypes[key]
|
||||
});
|
||||
}
|
||||
|
||||
// sort extensions
|
||||
extensions.sort(function(a, b) {
|
||||
return a.label > b.label ? 1 : a.label < b.label ? -1 : 0;
|
||||
});
|
||||
|
||||
// add additional options
|
||||
extensions.unshift({
|
||||
0 : 'Sequences',
|
||||
1 : 'Sequences'
|
||||
});
|
||||
extensions.unshift({
|
||||
0 : 'Roadmaps',
|
||||
1 : 'Roadmaps'
|
||||
});
|
||||
extensions.unshift({
|
||||
0 : 'Leave unchanged',
|
||||
1 : ''
|
||||
});
|
||||
|
||||
// create custom sub section
|
||||
var input_config = {
|
||||
label : 'Add Actions: \'' + output_id + '\'',
|
||||
type : 'section',
|
||||
inputs : [{
|
||||
action : 'RenameDatasetAction',
|
||||
argument : 'newname',
|
||||
label : 'Rename dataset',
|
||||
type : 'text',
|
||||
value : '',
|
||||
ignore : '',
|
||||
help : 'This action will rename the result dataset. Click <a href="https://wiki.galaxyproject.org/Learn/AdvancedWorkflow/Variables">here</a> for more information.'
|
||||
},{
|
||||
action : 'ChangeDatatypeAction',
|
||||
argument : 'newtype',
|
||||
label : 'Change datatype',
|
||||
type : 'select',
|
||||
ignore : '',
|
||||
options : extensions,
|
||||
help : 'This action will change the datatype of the output to the indicated value.'
|
||||
},{
|
||||
action : 'TagDatasetAction',
|
||||
argument : 'tags',
|
||||
label : 'Tags',
|
||||
type : 'text',
|
||||
value : '',
|
||||
ignore : '',
|
||||
help : 'This action will set tags for the dataset.'
|
||||
},{
|
||||
label : 'Assign columns',
|
||||
type : 'section',
|
||||
inputs : [{
|
||||
action : 'ColumnSetAction',
|
||||
argument : 'chromCol',
|
||||
label : 'Chrom column',
|
||||
type : 'integer',
|
||||
value : '',
|
||||
ignore : ''
|
||||
},{
|
||||
action : 'ColumnSetAction',
|
||||
argument : 'startCol',
|
||||
label : 'Start column',
|
||||
type : 'integer',
|
||||
value : '',
|
||||
ignore : ''
|
||||
},{
|
||||
action : 'ColumnSetAction',
|
||||
argument : 'endCol',
|
||||
label : 'End column',
|
||||
type : 'integer',
|
||||
value : '',
|
||||
ignore : ''
|
||||
},{
|
||||
action : 'ColumnSetAction',
|
||||
argument : 'strandCol',
|
||||
label : 'Strand column',
|
||||
type : 'integer',
|
||||
value : '',
|
||||
ignore : ''
|
||||
},{
|
||||
action : 'ColumnSetAction',
|
||||
argument : 'nameCol',
|
||||
label : 'Name column',
|
||||
type : 'integer',
|
||||
value : '',
|
||||
ignore : ''
|
||||
}],
|
||||
help : 'This action will set column assignments in the output dataset. Blank fields are ignored.'
|
||||
}]
|
||||
};
|
||||
|
||||
// visit input nodes and enrich by name/value pairs from server data
|
||||
var self = this;
|
||||
function visit (head, head_list) {
|
||||
head_list = head_list || [];
|
||||
head_list.push(head);
|
||||
for (var i in head.inputs) {
|
||||
var input = head.inputs[i];
|
||||
if (input.action) {
|
||||
// construct identifier as expected by backend
|
||||
input.name = 'pja__' + output_id + '__' + input.action;
|
||||
if (input.argument) {
|
||||
input.name += '__' + input.argument;
|
||||
}
|
||||
|
||||
// modify names of payload arguments
|
||||
if (input.payload) {
|
||||
for (var p_id in input.payload) {
|
||||
var p = input.payload[p_id];
|
||||
input.payload[input.name + '__' + p_id] = p;
|
||||
delete p;
|
||||
}
|
||||
}
|
||||
|
||||
// access/verify existence of value
|
||||
var d = self.post_job_actions[input.action + output_id];
|
||||
if (d) {
|
||||
// mark as expanded
|
||||
for (var j in head_list) {
|
||||
head_list[j].expand = true;
|
||||
}
|
||||
|
||||
// update input field value
|
||||
if (input.argument) {
|
||||
input.value = d.action_arguments && d.action_arguments[input.argument] || input.value;
|
||||
} else {
|
||||
input.value = 'true';
|
||||
}
|
||||
}
|
||||
}
|
||||
// continue with sub section
|
||||
if (input.inputs) {
|
||||
visit(input, head_list.slice(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(input_config);
|
||||
|
||||
// return final configuration
|
||||
return input_config;
|
||||
},
|
||||
|
||||
/** Builds a new model through api call and recreates the entire form
|
||||
*/
|
||||
_buildModel: function() {
|
||||
Galaxy.modal.show({
|
||||
title : 'Coming soon...',
|
||||
body : 'This feature has not been implemented yet.',
|
||||
buttons : {
|
||||
'Close' : function() {
|
||||
Galaxy.modal.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Request a new model for an already created tool form and updates the form inputs
|
||||
*/
|
||||
_updateModel: function() {
|
||||
// create the request dictionary
|
||||
var self = this;
|
||||
var current_state = this.tree.finalize();
|
||||
|
||||
// log tool state
|
||||
console.debug('tools-form-workflow::_refreshForm() - Refreshing states.');
|
||||
console.debug(current_state);
|
||||
|
||||
// register process
|
||||
var process_id = this.deferred.register();
|
||||
|
||||
// build model url for request
|
||||
var model_url = galaxy_config.root + 'workflow/editor_form_post?tool_id=' + this.options.id + '&__is_dynamic__=False';
|
||||
|
||||
// post job
|
||||
Utils.request({
|
||||
type : 'GET',
|
||||
url : model_url,
|
||||
data : current_state,
|
||||
success : function(data) {
|
||||
// update node in workflow module
|
||||
self.node.update_field_data(data);
|
||||
|
||||
// highlight errors
|
||||
self._errors(data && data.tool_model);
|
||||
|
||||
// process completed
|
||||
self.deferred.done(process_id);
|
||||
|
||||
// log success
|
||||
console.debug('tools-form::_refreshForm() - States refreshed.');
|
||||
console.debug(data);
|
||||
},
|
||||
error : function(response) {
|
||||
// process completed
|
||||
self.deferred.done(process_id);
|
||||
|
||||
// log error
|
||||
console.debug('tools-form::_refreshForm() - Refresh request failed.');
|
||||
console.debug(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
View: View
|
||||
};
|
||||
});
|
||||
@@ -1,81 +1,30 @@
|
||||
/**
|
||||
This is the main class of the tool form plugin. It is referenced as 'app' in all lower level modules.
|
||||
*/
|
||||
define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
|
||||
'mvc/citation/citation-model', 'mvc/citation/citation-view',
|
||||
'mvc/tools', 'mvc/tools/tools-template', 'mvc/tools/tools-content', 'mvc/tools/tools-section', 'mvc/tools/tools-tree', 'mvc/tools/tools-jobs'],
|
||||
function(Utils, Deferred, Portlet, Ui, CitationModel, CitationView,
|
||||
Tools, ToolTemplate, ToolContent, ToolSection, ToolTree, ToolJobs) {
|
||||
define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools/tools-jobs'],
|
||||
function(Utils, Ui, ToolFormBase, ToolJobs) {
|
||||
|
||||
// create form view
|
||||
var View = Backbone.View.extend({
|
||||
// base element
|
||||
container: 'body',
|
||||
|
||||
var View = ToolFormBase.extend({
|
||||
// initialize
|
||||
initialize: function(options) {
|
||||
// log options
|
||||
console.debug(options);
|
||||
|
||||
// link galaxy modal or create one
|
||||
var galaxy = parent.Galaxy;
|
||||
if (galaxy && galaxy.modal) {
|
||||
this.modal = galaxy.modal;
|
||||
} else {
|
||||
this.modal = new Ui.Modal.View();
|
||||
var self = this;
|
||||
this.job_handler = new ToolJobs(this);
|
||||
this.buttons = {
|
||||
execute : new Ui.Button({
|
||||
icon : 'fa-check',
|
||||
tooltip : 'Execute: ' + options.name,
|
||||
title : 'Execute',
|
||||
cls : 'btn btn-primary',
|
||||
floating : 'clear',
|
||||
onclick : function() {
|
||||
self.job_handler.submit();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// check if the user is an admin
|
||||
if (galaxy && galaxy.currUser) {
|
||||
this.is_admin = galaxy.currUser.get('is_admin')
|
||||
} else {
|
||||
this.is_admin = false;
|
||||
}
|
||||
|
||||
// link options
|
||||
this.options = options;
|
||||
|
||||
// create deferred processing queue handler
|
||||
// this handler reduces the number of requests to the api by filtering redundant requests
|
||||
this.deferred = new Deferred();
|
||||
|
||||
// set element
|
||||
this.setElement('<div/>');
|
||||
|
||||
// add to main element
|
||||
$(this.container).append(this.$el);
|
||||
|
||||
// build this form
|
||||
this._buildForm();
|
||||
ToolFormBase.prototype.initialize.call(this, options);
|
||||
},
|
||||
|
||||
/** Shows the final message (usually upon successful job submission)
|
||||
*/
|
||||
reciept: function($el) {
|
||||
$(this.container).empty();
|
||||
$(this.container).append($el);
|
||||
},
|
||||
|
||||
/** Highlight and scroll to input element (currently only used for error notifications)
|
||||
*/
|
||||
highlight: function (input_id, message, silent) {
|
||||
// get input field
|
||||
var input_element = this.element_list[input_id];
|
||||
|
||||
// check input element
|
||||
if (input_element) {
|
||||
// mark error
|
||||
input_element.error(message || 'Please verify this parameter.');
|
||||
|
||||
// scroll to first input element
|
||||
if (!silent) {
|
||||
$(this.container).animate({
|
||||
scrollTop: input_element.$el.offset().top - 20
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/** Builds a new model through api call and recreates the entire form
|
||||
*/
|
||||
_buildModel: function() {
|
||||
@@ -98,31 +47,28 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// register process
|
||||
var process_id = this.deferred.register();
|
||||
|
||||
|
||||
// get initial model
|
||||
Utils.request({
|
||||
type : 'GET',
|
||||
url : model_url,
|
||||
success : function(response) {
|
||||
// link model data update options
|
||||
self.options = response;
|
||||
|
||||
// build form
|
||||
self._buildForm();
|
||||
|
||||
// build new tool form
|
||||
self.build(response);
|
||||
|
||||
// notification
|
||||
self.message.update({
|
||||
status : 'success',
|
||||
message : 'Now you are using \'' + self.options.name + '\' version ' + self.options.version + '.',
|
||||
persistent : false
|
||||
});
|
||||
|
||||
|
||||
// process completed
|
||||
self.deferred.done(process_id);
|
||||
|
||||
|
||||
// log success
|
||||
console.debug('tools-form::initialize() - Initial tool model ready.');
|
||||
console.debug(response);
|
||||
@@ -130,11 +76,11 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
|
||||
error : function(response) {
|
||||
// process completed
|
||||
self.deferred.done(process_id);
|
||||
|
||||
|
||||
// log error
|
||||
console.debug('tools-form::initialize() - Initial tool model request failed.');
|
||||
console.debug(response);
|
||||
|
||||
|
||||
// show error
|
||||
var error_message = response.error || 'Uncaught error.';
|
||||
self.modal.show({
|
||||
@@ -149,7 +95,7 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/** Request a new model for an already created tool form and updates the form inputs
|
||||
*/
|
||||
_updateModel: function() {
|
||||
@@ -163,11 +109,11 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// log tool state
|
||||
console.debug('tools-form::_refreshForm() - Refreshing states.');
|
||||
console.debug(current_state);
|
||||
|
||||
|
||||
// activates/disables spinner for dynamic fields to indicate that they are currently being updated
|
||||
function wait(active) {
|
||||
for (var i in self.input_list) {
|
||||
@@ -182,10 +128,10 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// set wait mode
|
||||
wait(true);
|
||||
|
||||
|
||||
// register process
|
||||
var process_id = this.deferred.register();
|
||||
|
||||
@@ -230,13 +176,13 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// unset wait mode
|
||||
wait(false);
|
||||
|
||||
|
||||
// process completed
|
||||
self.deferred.done(process_id);
|
||||
|
||||
|
||||
// log success
|
||||
console.debug('tools-form::_refreshForm() - States refreshed.');
|
||||
console.debug(new_model);
|
||||
@@ -244,262 +190,12 @@ define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
|
||||
error : function(response) {
|
||||
// process completed
|
||||
self.deferred.done(process_id);
|
||||
|
||||
|
||||
// log error
|
||||
console.debug('tools-form::_refreshForm() - Refresh request failed.');
|
||||
console.debug(response);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Main tool form build function. This function is called once a new model is available.
|
||||
*/
|
||||
_buildForm: function() {
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
// reset events
|
||||
this.off('refresh');
|
||||
this.off('reset');
|
||||
|
||||
// reset field list, which contains the input field elements
|
||||
this.field_list = {};
|
||||
|
||||
// reset sequential input definition list, which contains the input definitions as provided from the api
|
||||
this.input_list = {};
|
||||
|
||||
// reset input element list, which contains the dom elements of each input element (includes also the input field)
|
||||
this.element_list = {};
|
||||
|
||||
// creates a tree/json data structure from the input form
|
||||
this.tree = new ToolTree(this);
|
||||
|
||||
// creates the job handler
|
||||
this.job_handler = new ToolJobs(this);
|
||||
|
||||
// request history content and build form
|
||||
this.content = new ToolContent(this);
|
||||
|
||||
// link model options
|
||||
var options = this.options;
|
||||
|
||||
// create ui elements
|
||||
this._renderForm(options);
|
||||
|
||||
// rebuild the underlying data structure
|
||||
this.tree.finalize();
|
||||
|
||||
// show errors
|
||||
if (options.errors) {
|
||||
var error_messages = this.tree.matchResponse(options.errors);
|
||||
for (var input_id in error_messages) {
|
||||
this.highlight(input_id, error_messages[input_id], true);
|
||||
}
|
||||
}
|
||||
|
||||
// add refresh listener
|
||||
this.on('refresh', function() {
|
||||
// by using/reseting the deferred ajax queue the number of redundant calls is reduced
|
||||
self.deferred.reset();
|
||||
self.deferred.execute(function(){self._updateModel()});
|
||||
});
|
||||
|
||||
// add reset listener
|
||||
this.on('reset', function() {
|
||||
for (var i in this.element_list) {
|
||||
this.element_list[i].reset();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Renders the UI elements required for the form
|
||||
*/
|
||||
_renderForm: function(options) {
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
// create message view
|
||||
this.message = new Ui.Message();
|
||||
|
||||
// button for version selection
|
||||
var requirements_button = new Ui.ButtonIcon({
|
||||
icon : 'fa-info-circle',
|
||||
title : 'Requirements',
|
||||
tooltip : 'Display tool requirements',
|
||||
onclick : function() {
|
||||
if (!this.visible) {
|
||||
this.visible = true;
|
||||
self.message.update({
|
||||
persistent : true,
|
||||
message : ToolTemplate.requirements(options),
|
||||
status : 'info'
|
||||
});
|
||||
} else {
|
||||
this.visible = false;
|
||||
self.message.update({
|
||||
message : ''
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!options.requirements || options.requirements.length == 0) {
|
||||
requirements_button.$el.hide();
|
||||
}
|
||||
|
||||
// button for version selection
|
||||
var versions_button = new Ui.ButtonMenu({
|
||||
icon : 'fa-cubes',
|
||||
title : 'Versions',
|
||||
tooltip : 'Select another tool version'
|
||||
});
|
||||
if (options.versions && options.versions.length > 1) {
|
||||
for (var i in options.versions) {
|
||||
var version = options.versions[i];
|
||||
if (version != options.version) {
|
||||
versions_button.addMenu({
|
||||
title : 'Switch to ' + version,
|
||||
version : version,
|
||||
icon : 'fa-cube',
|
||||
onclick : function() {
|
||||
// here we update the tool version (some tools encode the version also in the id)
|
||||
options.id = options.id.replace(options.version, this.version);
|
||||
options.version = this.version;
|
||||
|
||||
// rebuild the model and form
|
||||
self.deferred.reset();
|
||||
self.deferred.execute(function(){self._buildModel()});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
versions_button.$el.hide();
|
||||
}
|
||||
|
||||
// button menu
|
||||
var menu_button = new Ui.ButtonMenu({
|
||||
icon : 'fa-caret-down',
|
||||
title : 'Options',
|
||||
tooltip : 'View available options'
|
||||
});
|
||||
|
||||
// configure button selection
|
||||
if(options.biostar_url) {
|
||||
// add question option
|
||||
menu_button.addMenu({
|
||||
icon : 'fa-question-circle',
|
||||
title : 'Question?',
|
||||
tooltip : 'Ask a question about this tool (Biostar)',
|
||||
onclick : function() {
|
||||
window.open(options.biostar_url + '/p/new/post/');
|
||||
}
|
||||
});
|
||||
|
||||
// create search button
|
||||
menu_button.addMenu({
|
||||
icon : 'fa-search',
|
||||
title : 'Search',
|
||||
tooltip : 'Search help for this tool (Biostar)',
|
||||
onclick : function() {
|
||||
window.open(options.biostar_url + '/t/' + options.id + '/');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// create share button
|
||||
menu_button.addMenu({
|
||||
icon : 'fa-share',
|
||||
title : 'Share',
|
||||
tooltip : 'Share this tool',
|
||||
onclick : function() {
|
||||
prompt('Copy to clipboard: Ctrl+C, Enter', window.location.origin + galaxy_config.root + 'root?tool_id=' + options.id);
|
||||
}
|
||||
});
|
||||
|
||||
// add admin operations
|
||||
if (this.is_admin) {
|
||||
// create download button
|
||||
menu_button.addMenu({
|
||||
icon : 'fa-download',
|
||||
title : 'Download',
|
||||
tooltip : 'Download this tool',
|
||||
onclick : function() {
|
||||
window.location.href = galaxy_config.root + 'api/tools/' + options.id + '/download';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// create tool form section
|
||||
this.section = new ToolSection.View(self, {
|
||||
inputs : options.inputs,
|
||||
cls : 'ui-table-plain'
|
||||
});
|
||||
|
||||
// switch to classic tool form mako if the form definition is incompatible
|
||||
if (this.incompatible) {
|
||||
this.$el.hide();
|
||||
$('#tool-form-classic').show();
|
||||
return;
|
||||
}
|
||||
|
||||
// create portlet
|
||||
this.portlet = new Portlet.View({
|
||||
icon : 'fa-wrench',
|
||||
title : '<b>' + options.name + '</b> ' + options.description + ' (Galaxy Tool Version ' + options.version + ')',
|
||||
cls : 'ui-portlet-slim',
|
||||
operations: {
|
||||
requirements : requirements_button,
|
||||
menu : menu_button,
|
||||
versions : versions_button
|
||||
},
|
||||
buttons: {
|
||||
execute : new Ui.Button({
|
||||
icon : 'fa-check',
|
||||
tooltip : 'Execute: ' + options.name,
|
||||
title : 'Execute',
|
||||
cls : 'btn btn-primary',
|
||||
floating : 'clear',
|
||||
onclick : function() {
|
||||
self.job_handler.submit();
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// append message
|
||||
this.portlet.append(this.message.$el, true);
|
||||
|
||||
// append tool section
|
||||
this.portlet.append(this.section.$el);
|
||||
|
||||
// start form
|
||||
this.$el.empty();
|
||||
this.$el.append(this.portlet.$el);
|
||||
|
||||
// append help
|
||||
if (options.help != '') {
|
||||
this.$el.append(ToolTemplate.help(options.help));
|
||||
}
|
||||
|
||||
// append citations
|
||||
if (options.citations) {
|
||||
var $citations = $('<div/>');
|
||||
var citations = new CitationModel.ToolCitationCollection();
|
||||
citations.tool_id = options.id;
|
||||
var citation_list_view = new CitationView.CitationListView({ el: $citations, collection: citations } );
|
||||
citation_list_view.render();
|
||||
citations.fetch();
|
||||
this.$el.append($citations);
|
||||
}
|
||||
|
||||
// show message if available in model
|
||||
if (options.message) {
|
||||
this.message.update({
|
||||
persistent : true,
|
||||
status : 'warning',
|
||||
message : options.message
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -10,8 +10,13 @@ define([], function() {
|
||||
// link app
|
||||
this.app = app;
|
||||
|
||||
// set text labels
|
||||
this.text_enable = app.options.text_enable || 'Enable';
|
||||
this.text_disable = app.options.text_disable || 'Disable';
|
||||
|
||||
// link field
|
||||
this.field = options.field;
|
||||
this.default_value = options.default_value;
|
||||
|
||||
// set element
|
||||
this.setElement(this._template(options));
|
||||
@@ -25,9 +30,15 @@ define([], function() {
|
||||
// add field element
|
||||
this.$field.prepend(this.field.$el);
|
||||
|
||||
// start with enabled optional fields
|
||||
// decide wether to expand or collapse optional fields
|
||||
this.field.skip = false;
|
||||
|
||||
var v = this.field.value && this.field.value();
|
||||
this.field.skip = Boolean(options.optional &&
|
||||
((this.default_value === undefined) ||
|
||||
((this.field.validate && !this.field.validate()) || !v ||
|
||||
(v == this.default_value) || (Number(v) == Number(this.default_value)) ||
|
||||
(JSON.stringify(v) == JSON.stringify(this.default_value)))));
|
||||
|
||||
// refresh view
|
||||
this._refresh();
|
||||
|
||||
@@ -46,7 +57,7 @@ define([], function() {
|
||||
*/
|
||||
error: function(text) {
|
||||
this.$error_text.html(text);
|
||||
this.$error.fadeIn();
|
||||
this.$error.show();
|
||||
this.$el.addClass('ui-error');
|
||||
},
|
||||
|
||||
@@ -62,12 +73,14 @@ define([], function() {
|
||||
_refresh: function() {
|
||||
if (!this.field.skip) {
|
||||
this.$field.fadeIn('fast');
|
||||
this.$title_optional.html('Disable');
|
||||
this.app.trigger('refresh');
|
||||
this.$title_optional.html(this.text_disable);
|
||||
} else {
|
||||
this.reset();
|
||||
this.$field.hide();
|
||||
this.$title_optional.html('Enable');
|
||||
this.$title_optional.html(this.text_enable);
|
||||
this.field.value && this.field.value(this.default_value);
|
||||
}
|
||||
this.app.trigger('refresh');
|
||||
},
|
||||
|
||||
/** Main Template
|
||||
@@ -82,7 +95,7 @@ define([], function() {
|
||||
|
||||
// is optional
|
||||
if (options.optional) {
|
||||
tmp += 'Optional: ' + options.label +
|
||||
tmp += options.label +
|
||||
'<span> [<span class="ui-table-form-title-optional"/>]</span>';
|
||||
} else {
|
||||
tmp += options.label;
|
||||
|
||||
@@ -98,7 +98,7 @@ return Backbone.Model.extend({
|
||||
}
|
||||
|
||||
// validate non-optional fields
|
||||
if (!input_def.optional && input_field.validate && !input_field.validate()) {
|
||||
if (!input_def.optional && input_value == null) {
|
||||
this.app.highlight(input_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
This class creates a tool form section and populates it with input elements. It also handles repeat blocks and conditionals by recursively creating new sub sections. New input elements can be plugged in by adding cases to the switch block defined in the _createField() function.
|
||||
*/
|
||||
define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-repeat', 'mvc/tools/tools-select-content', 'mvc/tools/tools-input'],
|
||||
function(Utils, Table, Ui, Repeat, SelectContent, InputElement) {
|
||||
define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/ui/ui-portlet', 'mvc/tools/tools-repeat', 'mvc/tools/tools-select-content', 'mvc/tools/tools-input'],
|
||||
function(Utils, Table, Ui, Portlet, Repeat, SelectContent, InputElement) {
|
||||
|
||||
// create form view
|
||||
var View = Backbone.View.extend({
|
||||
@@ -10,51 +10,54 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
initialize: function(app, options) {
|
||||
// link app
|
||||
this.app = app;
|
||||
|
||||
|
||||
// link inputs
|
||||
this.inputs = options.inputs;
|
||||
|
||||
|
||||
// fix table style
|
||||
options.cls = 'ui-table-plain';
|
||||
|
||||
// add table class for tr tag
|
||||
// this assist in transforming the form into a json structure
|
||||
options.cls_tr = 'section-row';
|
||||
|
||||
// create table
|
||||
this.table = new Table.View(options);
|
||||
|
||||
|
||||
// configure portlet and form table
|
||||
this.setElement(this.table.$el);
|
||||
|
||||
|
||||
// render tool section
|
||||
this.render();
|
||||
},
|
||||
|
||||
|
||||
/** Render section view
|
||||
*/
|
||||
render: function() {
|
||||
// reset table
|
||||
this.table.delAll();
|
||||
|
||||
|
||||
// load settings elements into table
|
||||
for (var i in this.inputs) {
|
||||
this._add(this.inputs[i]);
|
||||
this.add(this.inputs[i]);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/** Add a new input element
|
||||
*/
|
||||
_add: function(input) {
|
||||
add: function(input) {
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
|
||||
// clone definition
|
||||
var input_def = jQuery.extend(true, {}, input);
|
||||
|
||||
|
||||
// create unique id
|
||||
input_def.id = input.id = Utils.uuid();
|
||||
|
||||
|
||||
// add to sequential list of inputs
|
||||
this.app.input_list[input_def.id] = input_def;
|
||||
|
||||
|
||||
// identify field type
|
||||
var type = input_def.type;
|
||||
switch(type) {
|
||||
@@ -66,50 +69,53 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
case 'repeat':
|
||||
this._addRepeat(input_def);
|
||||
break;
|
||||
// customized section
|
||||
case 'section':
|
||||
this._addSection(input_def);
|
||||
break;
|
||||
// default single element row
|
||||
default:
|
||||
this._addRow(input_def);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/** Add a conditional block
|
||||
*/
|
||||
_addConditional: function(input_def) {
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
|
||||
// copy identifier
|
||||
input_def.test_param.id = input_def.id;
|
||||
|
||||
|
||||
// build test parameter
|
||||
var field = this._addRow(input_def.test_param);
|
||||
|
||||
|
||||
// set onchange event for test parameter
|
||||
field.options.onchange = function(value) {
|
||||
// identify the selected case
|
||||
var selectedCase = self.app.tree.matchCase(input_def, value);
|
||||
|
||||
|
||||
// check value in order to hide/show options
|
||||
for (var i in input_def.cases) {
|
||||
// get case
|
||||
var case_def = input_def.cases[i];
|
||||
|
||||
|
||||
// identify subsection name
|
||||
var section_id = input_def.id + '-section-' + i;
|
||||
|
||||
|
||||
// identify row
|
||||
var section_row = self.table.get(section_id);
|
||||
|
||||
|
||||
// check if non-hidden elements exist
|
||||
var nonhidden = false;
|
||||
for (var j in case_def.inputs) {
|
||||
var type = case_def.inputs[j].type;
|
||||
if (type && type !== 'hidden') {
|
||||
if (!case_def.inputs[j].hidden) {
|
||||
nonhidden = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// show/hide sub form
|
||||
if (i == selectedCase && nonhidden) {
|
||||
section_row.fadeIn('fast');
|
||||
@@ -117,71 +123,69 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
section_row.hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// refresh form inputs
|
||||
self.app.trigger('refresh');
|
||||
};
|
||||
|
||||
|
||||
// add conditional sub sections
|
||||
for (var i in input_def.cases) {
|
||||
// create id tag
|
||||
var sub_section_id = input_def.id + '-section-' + i;
|
||||
|
||||
|
||||
// create sub section
|
||||
var sub_section = new View(this.app, {
|
||||
inputs : input_def.cases[i].inputs,
|
||||
cls : 'ui-table-plain'
|
||||
inputs : input_def.cases[i].inputs
|
||||
});
|
||||
|
||||
|
||||
// displays as grouped subsection
|
||||
sub_section.$el.addClass('ui-table-form-section');
|
||||
|
||||
|
||||
// create table row
|
||||
this.table.add(sub_section.$el);
|
||||
|
||||
|
||||
// append to table
|
||||
this.table.append(sub_section_id);
|
||||
}
|
||||
|
||||
|
||||
// trigger refresh on conditional input field after all input elements have been created
|
||||
field.trigger('change');
|
||||
},
|
||||
|
||||
|
||||
/** Add a repeat block
|
||||
*/
|
||||
_addRepeat: function(input_def) {
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
|
||||
// block index
|
||||
var block_index = 0;
|
||||
|
||||
|
||||
// helper function to create new repeat blocks
|
||||
function create (inputs, deleteable) {
|
||||
// create id tag
|
||||
var sub_section_id = input_def.id + '-section-' + (block_index++);
|
||||
|
||||
|
||||
// enable/disable repeat delete button
|
||||
var ondel = null;
|
||||
if (deleteable) {
|
||||
ondel = function() {
|
||||
// delete repeat block
|
||||
repeat.del(sub_section_id);
|
||||
|
||||
|
||||
// retitle repeat block
|
||||
repeat.retitle(input_def.title);
|
||||
|
||||
|
||||
// trigger refresh
|
||||
self.app.trigger('refresh');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// create sub section
|
||||
var sub_section = new View(self.app, {
|
||||
inputs : inputs,
|
||||
cls : 'ui-table-plain'
|
||||
inputs : inputs
|
||||
});
|
||||
|
||||
|
||||
// add tab
|
||||
repeat.add({
|
||||
id : sub_section_id,
|
||||
@@ -189,11 +193,11 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
$el : sub_section.$el,
|
||||
ondel : ondel
|
||||
});
|
||||
|
||||
|
||||
// retitle repeat block
|
||||
repeat.retitle(input_def.title);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// create repeat block element
|
||||
//
|
||||
@@ -208,7 +212,7 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
self.app.trigger('refresh');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//
|
||||
// add parsed/minimum number of repeat blocks
|
||||
//
|
||||
@@ -222,28 +226,85 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
} else {
|
||||
inputs = input_def.inputs;
|
||||
}
|
||||
|
||||
|
||||
// create repeat block
|
||||
create(inputs, i >= n_min);
|
||||
}
|
||||
|
||||
|
||||
// create input field wrapper
|
||||
var input_element = new InputElement(this.app, {
|
||||
label : input_def.title,
|
||||
help : input_def.help,
|
||||
field : repeat
|
||||
label : input_def.title,
|
||||
help : input_def.help,
|
||||
field : repeat
|
||||
});
|
||||
|
||||
|
||||
// displays as grouped subsection
|
||||
input_element.$el.addClass('ui-table-form-section');
|
||||
|
||||
|
||||
// create table row
|
||||
this.table.add(input_element.$el);
|
||||
|
||||
|
||||
// append row to table
|
||||
this.table.append(input_def.id);
|
||||
},
|
||||
|
||||
|
||||
/** Add a customized section
|
||||
*/
|
||||
_addSection: function(input_def) {
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
// create sub section
|
||||
var sub_section = new View(self.app, {
|
||||
inputs : input_def.inputs
|
||||
});
|
||||
|
||||
// delete button
|
||||
var button_visible = new Ui.ButtonIcon({
|
||||
icon : 'fa-eye-slash',
|
||||
tooltip : 'Show/hide section',
|
||||
cls : 'ui-button-icon-plain'
|
||||
});
|
||||
|
||||
// create portlet for sub section
|
||||
var portlet = new Portlet.View({
|
||||
title : input_def.label,
|
||||
cls : 'ui-portlet-section',
|
||||
operations : {
|
||||
button_visible: button_visible
|
||||
}
|
||||
});
|
||||
portlet.append(sub_section.$el);
|
||||
portlet.append($('<div/>').addClass('ui-table-form-info').html(input_def.help));
|
||||
|
||||
// add event handler visibility button
|
||||
var visible = false;
|
||||
portlet.$content.hide();
|
||||
portlet.$header.css('cursor', 'pointer');
|
||||
portlet.$header.on('click', function() {
|
||||
if (visible) {
|
||||
visible = false;
|
||||
portlet.$content.hide();
|
||||
button_visible.setIcon('fa-eye-slash');
|
||||
} else {
|
||||
visible = true;
|
||||
portlet.$content.fadeIn('fast');
|
||||
button_visible.setIcon('fa-eye');
|
||||
}
|
||||
});
|
||||
|
||||
// show sub section if requested
|
||||
if (input_def.expand) {
|
||||
portlet.$header.trigger('click');
|
||||
}
|
||||
|
||||
// create table row
|
||||
this.table.add(portlet.$el);
|
||||
|
||||
// append row to table
|
||||
this.table.append(input_def.id);
|
||||
},
|
||||
|
||||
/** Add a single input field element
|
||||
*/
|
||||
_addRow: function(input_def) {
|
||||
@@ -252,70 +313,86 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
|
||||
// create input field
|
||||
var field = this._createField(input_def);
|
||||
|
||||
|
||||
// add to field list
|
||||
this.app.field_list[id] = field;
|
||||
|
||||
|
||||
// fix default value if missing
|
||||
if (input_def.default_value === undefined) {
|
||||
input_def.default_value = input_def.value;
|
||||
}
|
||||
|
||||
// create input field wrapper
|
||||
var input_element = new InputElement(this.app, {
|
||||
label : input_def.label,
|
||||
optional : input_def.optional,
|
||||
help : input_def.help,
|
||||
field : field
|
||||
label : input_def.label,
|
||||
default_value : input_def.default_value,
|
||||
optional : input_def.optional,
|
||||
help : input_def.help,
|
||||
field : field
|
||||
});
|
||||
|
||||
|
||||
// add to element list
|
||||
this.app.element_list[id] = input_element;
|
||||
|
||||
|
||||
// create table row
|
||||
this.table.add(input_element.$el);
|
||||
|
||||
|
||||
// append to table
|
||||
this.table.append(id);
|
||||
|
||||
|
||||
// hide row if neccessary
|
||||
if (input_def.hidden) {
|
||||
this.table.get(id).hide();
|
||||
}
|
||||
|
||||
// return created field
|
||||
return field;
|
||||
},
|
||||
|
||||
|
||||
/** Returns an input field for a given field type
|
||||
*/
|
||||
_createField: function(input_def) {
|
||||
// field wrapper
|
||||
var field = null;
|
||||
|
||||
|
||||
// identify field type
|
||||
switch(input_def.type) {
|
||||
// text input field
|
||||
case 'text' :
|
||||
field = this._fieldText(input_def);
|
||||
break;
|
||||
|
||||
|
||||
// select field
|
||||
case 'select' :
|
||||
field = this._fieldSelect(input_def);
|
||||
break;
|
||||
|
||||
|
||||
// data selector
|
||||
case 'data':
|
||||
field = this._fieldData(input_def);
|
||||
break;
|
||||
|
||||
|
||||
// collection selector
|
||||
case 'data_collection':
|
||||
field = this._fieldData(input_def);
|
||||
break;
|
||||
|
||||
|
||||
// data column
|
||||
case 'data_column':
|
||||
input_def.error_text = 'Missing columns in referenced dataset.';
|
||||
field = this._fieldSelect(input_def);
|
||||
break;
|
||||
|
||||
|
||||
// hidden field
|
||||
case 'hidden':
|
||||
field = this._fieldHidden(input_def);
|
||||
break;
|
||||
|
||||
|
||||
// hidden data field
|
||||
case 'hidden_data':
|
||||
field = this._fieldHidden(input_def);
|
||||
break;
|
||||
|
||||
// integer field
|
||||
case 'integer':
|
||||
field = this._fieldSlider(input_def);
|
||||
@@ -325,33 +402,33 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
case 'float':
|
||||
field = this._fieldSlider(input_def);
|
||||
break;
|
||||
|
||||
|
||||
// boolean field
|
||||
case 'boolean':
|
||||
field = this._fieldBoolean(input_def);
|
||||
break;
|
||||
|
||||
|
||||
// genome field
|
||||
case 'genomebuild':
|
||||
input_def.searchable = true;
|
||||
field = this._fieldSelect(input_def);
|
||||
break;
|
||||
|
||||
|
||||
// drill down field
|
||||
case 'drill_down':
|
||||
field = this._fieldDrilldown(input_def);
|
||||
break;
|
||||
|
||||
|
||||
// base url field
|
||||
case 'baseurl':
|
||||
field = this._fieldHidden(input_def);
|
||||
break;
|
||||
|
||||
|
||||
// field not found
|
||||
default:
|
||||
// flag
|
||||
this.app.incompatible = true;
|
||||
|
||||
|
||||
// with or without options
|
||||
if (input_def.options) {
|
||||
// assign select field
|
||||
@@ -360,27 +437,33 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
// assign text field
|
||||
field = this._fieldText(input_def);
|
||||
}
|
||||
|
||||
|
||||
// log
|
||||
console.debug('tools-form::_addRow() : Auto matched field type (' + input_def.type + ').');
|
||||
}
|
||||
|
||||
|
||||
// set field value
|
||||
if (input_def.value !== undefined) {
|
||||
field.value(input_def.value);
|
||||
}
|
||||
|
||||
|
||||
// return field element
|
||||
return field;
|
||||
},
|
||||
|
||||
|
||||
/** Data input field
|
||||
*/
|
||||
_fieldData : function(input_def) {
|
||||
if (!this.app.options.is_dynamic) {
|
||||
input_def.info = 'Data input \'' + input_def.name + '\' (' + Utils.textify(input_def.extensions.toString()) + ')';
|
||||
input_def.value = null;
|
||||
return this._fieldHidden(input_def);
|
||||
}
|
||||
var self = this;
|
||||
return new SelectContent.View(this.app, {
|
||||
id : 'field-' + input_def.id,
|
||||
extensions : input_def.extensions,
|
||||
optional : input_def.optional,
|
||||
multiple : input_def.multiple,
|
||||
type : input_def.type,
|
||||
data : input_def.options,
|
||||
@@ -389,10 +472,15 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/** Select/Checkbox/Radio options field
|
||||
*/
|
||||
_fieldSelect : function (input_def) {
|
||||
// show text field in if dynamic fields are disabled e.g. in workflow editor
|
||||
if (!this.app.options.is_dynamic && input_def.is_dynamic) {
|
||||
return this._fieldText(input_def);
|
||||
}
|
||||
|
||||
// configure options fields
|
||||
var options = [];
|
||||
for (var i in input_def.options) {
|
||||
@@ -402,7 +490,7 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
value: option[1]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// identify display type
|
||||
var SelectClass = Ui.Select;
|
||||
switch (input_def.display) {
|
||||
@@ -413,7 +501,7 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
SelectClass = Ui.Radio;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// select field
|
||||
var self = this;
|
||||
return new SelectClass.View({
|
||||
@@ -427,10 +515,16 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/** Drill down options field
|
||||
*/
|
||||
_fieldDrilldown : function (input_def) {
|
||||
// show text field in if dynamic fields are disabled e.g. in workflow editor
|
||||
if (!this.app.options.is_dynamic && input_def.is_dynamic) {
|
||||
return this._fieldText(input_def);
|
||||
}
|
||||
|
||||
// create drill down field
|
||||
var self = this;
|
||||
return new Ui.Drilldown.View({
|
||||
id : 'field-' + input_def.id,
|
||||
@@ -445,42 +539,72 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/tools/tools-rep
|
||||
/** Text input field
|
||||
*/
|
||||
_fieldText : function(input_def) {
|
||||
// field replaces e.g. a select field
|
||||
if (input_def.options) {
|
||||
// show text area if selecting multiple entries is allowed
|
||||
input_def.area = input_def.multiple;
|
||||
|
||||
// validate value
|
||||
if (!Utils.validate(input_def.value)) {
|
||||
input_def.value = '';
|
||||
} else {
|
||||
if (input_def.value instanceof Array) {
|
||||
input_def.value = value.toString();
|
||||
} else {
|
||||
input_def.value = String(input_def.value).replace(/[\[\]'"\s]/g, '');
|
||||
if (input_def.multiple) {
|
||||
input_def.value = input_def.value.replace(/,/g, '\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create input element
|
||||
var self = this;
|
||||
return new Ui.Input({
|
||||
id : 'field-' + input_def.id,
|
||||
area : input_def.area,
|
||||
onchange: function() {
|
||||
id : 'field-' + input_def.id,
|
||||
area : input_def.area,
|
||||
onchange : function() {
|
||||
self.app.trigger('refresh');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/** Slider field
|
||||
*/
|
||||
_fieldSlider: function(input_def) {
|
||||
var self = this;
|
||||
return new Ui.Slider.View({
|
||||
id : 'field-' + input_def.id,
|
||||
precise : input_def.type == 'float',
|
||||
min : input_def.min,
|
||||
max : input_def.max
|
||||
id : 'field-' + input_def.id,
|
||||
precise : input_def.type == 'float',
|
||||
min : input_def.min,
|
||||
max : input_def.max,
|
||||
onchange : function() {
|
||||
self.app.trigger('refresh');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/** Hidden field
|
||||
*/
|
||||
_fieldHidden : function(input_def) {
|
||||
return new Ui.Hidden({
|
||||
id : 'field-' + input_def.id
|
||||
id : 'field-' + input_def.id,
|
||||
info : input_def.info
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/** Boolean field
|
||||
*/
|
||||
_fieldBoolean : function(input_def) {
|
||||
var self = this;
|
||||
return new Ui.RadioButton.View({
|
||||
id : 'field-' + input_def.id,
|
||||
data : [ { label : 'Yes', value : 'true' },
|
||||
{ label : 'No', value : 'false' }]
|
||||
id : 'field-' + input_def.id,
|
||||
data : [ { label : 'Yes', value : 'true' },
|
||||
{ label : 'No', value : 'false' }],
|
||||
onchange : function() {
|
||||
self.app.trigger('refresh');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -36,14 +36,7 @@ var View = Backbone.View.extend({
|
||||
this.list = {};
|
||||
|
||||
// error messages
|
||||
var extensions = options.extensions.toString();
|
||||
if (extensions) {
|
||||
extensions = extensions.replace(/,/g, ', ');
|
||||
var pos = extensions.lastIndexOf(', ');
|
||||
if (pos != -1) {
|
||||
extensions = extensions.substr(0, pos) + ' or ' + extensions.substr(pos+1);
|
||||
}
|
||||
}
|
||||
var extensions = Utils.textify(options.extensions);
|
||||
var hda_error = 'No dataset available.';
|
||||
if (extensions) {
|
||||
hda_error = 'No ' + extensions + ' dataset available.';
|
||||
@@ -57,6 +50,7 @@ var View = Backbone.View.extend({
|
||||
if (this.mode == 'single') {
|
||||
radio_buttons.push({icon: 'fa-file-o', label : 'Single dataset', value : 'single'});
|
||||
this.select_single = new Ui.Select.View({
|
||||
optional : options.optional,
|
||||
error_text : hda_error,
|
||||
onchange : function() {
|
||||
self.trigger('change');
|
||||
@@ -89,6 +83,7 @@ var View = Backbone.View.extend({
|
||||
radio_buttons.push({icon: 'fa-folder-o', label : 'Dataset collection', value : 'collection' });
|
||||
this.select_collection = new Ui.Select.View({
|
||||
error_text : hdca_error,
|
||||
optional : options.optional,
|
||||
onchange : function() {
|
||||
self.trigger('change');
|
||||
}
|
||||
@@ -187,39 +182,39 @@ var View = Backbone.View.extend({
|
||||
},
|
||||
|
||||
/** Return the currently selected dataset values */
|
||||
value : function (dict) {
|
||||
value : function (new_value) {
|
||||
// update current value
|
||||
if (dict && dict.values) {
|
||||
try {
|
||||
// create list
|
||||
var list = [];
|
||||
for (var i in dict.values) {
|
||||
list.push(dict.values[i].id);
|
||||
}
|
||||
|
||||
// identify suitable select field
|
||||
if (dict && dict.values.length > 0 && dict.values[0].src == 'hcda') {
|
||||
this.current = 'collection';
|
||||
this.select_collection.value(list[0]);
|
||||
} else {
|
||||
if (this.mode == 'multiple') {
|
||||
this.current = 'multiple';
|
||||
this.select_multiple.value(list);
|
||||
} else {
|
||||
this.current = 'single';
|
||||
this.select_single.value(list[0]);
|
||||
if (new_value !== undefined) {
|
||||
if (new_value && new_value.values) {
|
||||
try {
|
||||
// create list with values
|
||||
var list = [];
|
||||
for (var i in new_value.values) {
|
||||
list.push(new_value.values[i].id);
|
||||
}
|
||||
|
||||
// identify suitable select field
|
||||
if (new_value && new_value.values.length > 0 && new_value.values[0].src == 'hcda') {
|
||||
this.current = 'collection';
|
||||
this.select_collection.value(list[0]);
|
||||
} else {
|
||||
if (this.mode == 'multiple') {
|
||||
this.current = 'multiple';
|
||||
this.select_multiple.value(list);
|
||||
} else {
|
||||
this.current = 'single';
|
||||
this.select_single.value(list[0]);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.debug('tools-select-content::value() - Skipped.');
|
||||
}
|
||||
this.refresh();
|
||||
|
||||
// check if value has been set
|
||||
var select = this._select();
|
||||
if (!select.validate()) {
|
||||
select.value(select.first());
|
||||
}
|
||||
} catch (err) {
|
||||
console.debug('tools-select-content::value() - Skipped.');
|
||||
} else {
|
||||
this.select_single && this.select_single.value('__null__');
|
||||
this.select_multiple && this.select_multiple.value('__null__');
|
||||
this.select_collection && this.select_collection.value('__null__');
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
// transform into an array
|
||||
@@ -254,6 +249,7 @@ var View = Backbone.View.extend({
|
||||
|
||||
/** Refreshes data selection view */
|
||||
refresh: function() {
|
||||
this.button_type.value(this.current);
|
||||
for (var i in this.list) {
|
||||
var $el = this.list[i].field.$el;
|
||||
if (this.current == i) {
|
||||
|
||||
@@ -4,7 +4,7 @@ define([], function() {
|
||||
// tool form templates
|
||||
return {
|
||||
help: function(content) {
|
||||
return '<div class="toolHelp">' +
|
||||
return '<div class="toolHelp" style="overflow: auto;">' +
|
||||
'<div class="toolHelpBody">' +
|
||||
content +
|
||||
'</div>' +
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
This class maps the tool form dom to an api compatible javascript dictionary.
|
||||
*/
|
||||
// dependencies
|
||||
define([], function() {
|
||||
define(['utils/utils'], function(Utils) {
|
||||
|
||||
// tool form tree
|
||||
return Backbone.Model.extend({
|
||||
@@ -108,22 +108,42 @@ return Backbone.Model.extend({
|
||||
convert(job_input_id, head[input.id + '-section-' + selectedCase]);
|
||||
}
|
||||
break;
|
||||
// handle custom sub sections
|
||||
case 'section':
|
||||
convert('', node);
|
||||
break;
|
||||
default:
|
||||
// get field
|
||||
var field = self.app.field_list[input.id];
|
||||
|
||||
// get and patch field value
|
||||
var value = field.value();
|
||||
if (patch[input.type]) {
|
||||
value = patch[input.type](value);
|
||||
}
|
||||
|
||||
// handle default value
|
||||
if (!field.skip) {
|
||||
if (input.optional && field.validate && !field.validate()) {
|
||||
if (field && field.value) {
|
||||
// get and patch field value
|
||||
var value = field.value();
|
||||
if (patch[input.type]) {
|
||||
value = patch[input.type](value);
|
||||
}
|
||||
|
||||
// validate field value
|
||||
if (field.skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// validate field value
|
||||
if (field.validate && !field.validate()) {
|
||||
value = null;
|
||||
}
|
||||
add (job_input_id, input.id, value);
|
||||
|
||||
// ignore certain values
|
||||
if (input.ignore === undefined || (value !== null && input.ignore != value)) {
|
||||
// add value to submission
|
||||
add (job_input_id, input.id, value);
|
||||
|
||||
// add payload to submission
|
||||
if (input.payload) {
|
||||
for (var p_id in input.payload) {
|
||||
add (p_id, input.id, input.payload[p_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +193,12 @@ var ButtonIcon = Backbone.View.extend({
|
||||
this.disabled = false;
|
||||
},
|
||||
|
||||
// change icon
|
||||
setIcon: function(icon_cls) {
|
||||
this.$('i').removeClass(this.options.icon).addClass(icon_cls);
|
||||
this.options.icon = icon_cls;
|
||||
},
|
||||
|
||||
// template
|
||||
_template: function(options) {
|
||||
// width
|
||||
@@ -412,7 +418,7 @@ var Hidden = Backbone.View.extend({
|
||||
initialize : function(options) {
|
||||
// configure options
|
||||
this.options = options;
|
||||
|
||||
|
||||
// create new element
|
||||
this.setElement(this._template(this.options));
|
||||
|
||||
@@ -425,14 +431,68 @@ var Hidden = Backbone.View.extend({
|
||||
// value
|
||||
value : function (new_val) {
|
||||
if (new_val !== undefined) {
|
||||
this.$el.val(new_val);
|
||||
this.$('hidden').val(new_val);
|
||||
}
|
||||
return this.$el.val();
|
||||
return this.$('hidden').val();
|
||||
},
|
||||
|
||||
// element
|
||||
_template: function(options) {
|
||||
return '<hidden id="' + options.id + '" value="' + options.value + '"/>';
|
||||
var tmpl = '<div id="' + options.id + '" >';
|
||||
if (options.info) {
|
||||
tmpl += '<label>' + options.info + '</label>';
|
||||
}
|
||||
tmpl += '<hidden value="' + options.value + '"/>' +
|
||||
'</div>';
|
||||
return tmpl;
|
||||
}
|
||||
});
|
||||
|
||||
// plugin
|
||||
var CheckButton = Backbone.View.extend({
|
||||
|
||||
// default options
|
||||
optionsDefault: {
|
||||
class_add : 'fa fa-square-o',
|
||||
class_remove : 'fa fa-check-square-o',
|
||||
class_partial : 'fa fa-minus-square-o',
|
||||
value : false
|
||||
},
|
||||
|
||||
// initialize
|
||||
initialize : function(options) {
|
||||
// configure options
|
||||
this.options = Utils.merge(options, this.optionsDefault);
|
||||
|
||||
// create new element
|
||||
this.setElement($('<div/>'));
|
||||
|
||||
// set initial value
|
||||
this.value(Boolean(this.options.value));
|
||||
|
||||
// add event handler
|
||||
var self = this;
|
||||
this.$el.on('click', function() {
|
||||
self.value(!self.current);
|
||||
});
|
||||
},
|
||||
|
||||
// value
|
||||
value : function (new_val) {
|
||||
if (new_val !== undefined) {
|
||||
this.current = new_val;
|
||||
if (new_val) {
|
||||
this.$el.removeClass()
|
||||
.addClass('ui-checkbutton')
|
||||
.addClass(this.options.class_remove);
|
||||
} else {
|
||||
this.$el.removeClass()
|
||||
.addClass('ui-checkbutton')
|
||||
.addClass(this.options.class_add);
|
||||
}
|
||||
this.options.onchange && this.options.onchange(new_val);
|
||||
}
|
||||
return this.current;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -451,6 +511,7 @@ return {
|
||||
RadioButton : Options.RadioButton,
|
||||
Checkbox : Options.Checkbox,
|
||||
Radio : Options.Radio,
|
||||
CheckButton : CheckButton,
|
||||
Searchbox : Searchbox,
|
||||
Select : Select,
|
||||
Hidden : Hidden,
|
||||
|
||||
@@ -137,16 +137,7 @@ var Base = Backbone.View.extend({
|
||||
/** Validate the selected option/options
|
||||
*/
|
||||
validate: function() {
|
||||
var current = this.value();
|
||||
if (!(current instanceof Array)) {
|
||||
current = [current];
|
||||
}
|
||||
for (var i in current) {
|
||||
if ([null, 'null', undefined].indexOf(current[i]) > -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return Utils.validate(this.value());
|
||||
},
|
||||
|
||||
/** Wait message during request processing
|
||||
@@ -191,7 +182,7 @@ var Base = Backbone.View.extend({
|
||||
// get selected values
|
||||
var selected = this.$el.find(':checked');
|
||||
if (selected.length == 0) {
|
||||
return 'null';
|
||||
return '__null__';
|
||||
}
|
||||
|
||||
// return multiple or single value
|
||||
|
||||
@@ -25,6 +25,7 @@ var View = Backbone.View.extend({
|
||||
$content : null,
|
||||
$buttons : null,
|
||||
$operations : null,
|
||||
$header : null,
|
||||
|
||||
// initialize
|
||||
initialize : function(options) {
|
||||
@@ -35,22 +36,16 @@ var View = Backbone.View.extend({
|
||||
this.setElement(this._template(this.options));
|
||||
|
||||
// link content
|
||||
this.$content = this.$el.find('#content');
|
||||
this.$content = this.$el.find('.content');
|
||||
|
||||
// link title
|
||||
this.$title = this.$el.find('#portlet-title-text');
|
||||
this.$title = this.$el.find('.portlet-title-text');
|
||||
|
||||
// set content format
|
||||
var $portlet_content = this.$el.find('#portlet-content');
|
||||
if (!this.options.scrollable) {
|
||||
if (this.options.title) {
|
||||
$portlet_content.addClass('no-scroll');
|
||||
} else {
|
||||
$portlet_content.addClass('no-scroll-no-title');
|
||||
}
|
||||
} else {
|
||||
$portlet_content.addClass('scroll');
|
||||
}
|
||||
// link header
|
||||
this.$header = this.$el.find('.portlet-header');
|
||||
|
||||
// link portlet content (wraps 'content')
|
||||
var $portlet_content = this.$el.find('.portlet-content');
|
||||
|
||||
// set content padding
|
||||
if (this.options.nopadding) {
|
||||
@@ -59,7 +54,7 @@ var View = Backbone.View.extend({
|
||||
}
|
||||
|
||||
// append buttons
|
||||
this.$buttons = $(this.el).find('#buttons');
|
||||
this.$buttons = $(this.el).find('.buttons');
|
||||
if (this.options.buttons) {
|
||||
// link functions
|
||||
var self = this;
|
||||
@@ -72,7 +67,7 @@ var View = Backbone.View.extend({
|
||||
}
|
||||
|
||||
// append operations
|
||||
this.$operations = $(this.el).find('#operations');
|
||||
this.$operations = $(this.el).find('.operations');
|
||||
if (this.options.operations) {
|
||||
// link functions
|
||||
var self = this;
|
||||
@@ -161,27 +156,27 @@ var View = Backbone.View.extend({
|
||||
var tmpl = '<div id="' + options.id + '" class="' + options.cls + '">';
|
||||
|
||||
if (options.title) {
|
||||
tmpl += '<div id="portlet-header" class="portlet-header">' +
|
||||
'<div id="operations" style="float: ' + options.operations_flt + ';"></div>' +
|
||||
tmpl += '<div class="portlet-header">' +
|
||||
'<div class="operations" style="float: ' + options.operations_flt + ';"></div>' +
|
||||
'<div class="portlet-title">';
|
||||
|
||||
if (options.icon)
|
||||
tmpl += '<i class="icon fa ' + options.icon + '"> </i>';
|
||||
|
||||
tmpl += '<span id="portlet-title-text" class="portlet-title-text">' + options.title + '</span>' +
|
||||
tmpl += '<span class="portlet-title-text">' + options.title + '</span>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
tmpl += '<div id="portlet-content" class="portlet-content">';
|
||||
tmpl += '<div class="portlet-content">';
|
||||
|
||||
if (options.placement == 'top') {
|
||||
tmpl += '<div id="buttons" class="buttons"></div>';
|
||||
tmpl += '<div class="buttons"></div>';
|
||||
}
|
||||
|
||||
tmpl += '<div id="content" class="content"></div>';
|
||||
tmpl += '<div class="content"></div>';
|
||||
|
||||
if (options.placement == 'bottom') {
|
||||
tmpl += '<div id="buttons" class="buttons"></div>';
|
||||
tmpl += '<div class="buttons"></div>';
|
||||
}
|
||||
|
||||
tmpl += '</div>' +
|
||||
|
||||
@@ -10,10 +10,12 @@ var View = Backbone.View.extend({
|
||||
id : '',
|
||||
cls : '',
|
||||
error_text : 'No data available',
|
||||
empty_text : 'No selection',
|
||||
visible : true,
|
||||
wait : false,
|
||||
multiple : false,
|
||||
searchable : false
|
||||
searchable : false,
|
||||
optional : false
|
||||
},
|
||||
|
||||
// initialize
|
||||
@@ -96,16 +98,7 @@ var View = Backbone.View.extend({
|
||||
/** Validate the current selection
|
||||
*/
|
||||
validate: function() {
|
||||
var current = this.value();
|
||||
if (!(current instanceof Array)) {
|
||||
current = [current];
|
||||
}
|
||||
for (var i in current) {
|
||||
if ([null, 'null', undefined].indexOf(current[i]) > -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return Utils.validate(this.value());
|
||||
},
|
||||
|
||||
/** Return the label/text of the current selection
|
||||
@@ -190,6 +183,11 @@ var View = Backbone.View.extend({
|
||||
// remove all options
|
||||
this.$select.find('option').remove();
|
||||
|
||||
// add optional field
|
||||
if (this.options.optional && !this.options.multiple) {
|
||||
this.$select.append(this._templateOption({value : '__null__', label : this.options.empty_text}));
|
||||
}
|
||||
|
||||
// add new options
|
||||
for (var key in options) {
|
||||
this.$select.append(this._templateOption(options[key]));
|
||||
@@ -238,7 +236,7 @@ var View = Backbone.View.extend({
|
||||
*/
|
||||
_refresh: function() {
|
||||
// remove placeholder
|
||||
this.$select.find('option[value=null]').remove();
|
||||
this.$select.find('option[value="__undefined__"]').remove();
|
||||
|
||||
// count remaining entries
|
||||
var remaining = this.$select.find('option').length;
|
||||
@@ -247,7 +245,7 @@ var View = Backbone.View.extend({
|
||||
this.disable();
|
||||
|
||||
// append placeholder
|
||||
this.$select.append(this._templateOption({value : 'null', label : this.options.error_text}));
|
||||
this.$select.append(this._templateOption({value : '__undefined__', label : this.options.error_text}));
|
||||
} else {
|
||||
// enable select field
|
||||
this.enable();
|
||||
|
||||
@@ -54,7 +54,7 @@ var View = Backbone.View.extend({
|
||||
}
|
||||
|
||||
// add text field event
|
||||
this.$text.on('change', function () {
|
||||
this.$text.on('input', function () {
|
||||
self.value($(this).val());
|
||||
});
|
||||
|
||||
|
||||
@@ -20,24 +20,24 @@ var View = Backbone.View.extend({
|
||||
initialize : function(model) {
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
|
||||
// create model
|
||||
this.model = model;
|
||||
|
||||
|
||||
// get options
|
||||
this.options = this.model.attributes;
|
||||
|
||||
|
||||
// create new element
|
||||
this.setElement(this._template(this.options));
|
||||
|
||||
|
||||
// add event
|
||||
$(this.el).on('click', this.options.onclick);
|
||||
|
||||
|
||||
// add tooltip
|
||||
if (this.options.tooltip) {
|
||||
$(this.el).tooltip({title: this.options.tooltip, placement: 'bottom'});
|
||||
}
|
||||
|
||||
|
||||
// events
|
||||
this.model.on('change:percentage', function() {
|
||||
self._percentage(self.model.get('percentage'));
|
||||
@@ -45,7 +45,7 @@ var View = Backbone.View.extend({
|
||||
this.model.on('change:status', function() {
|
||||
self._status(self.model.get('status'));
|
||||
});
|
||||
|
||||
|
||||
// unload event
|
||||
var self = this;
|
||||
$(window).on('beforeunload', function() {
|
||||
@@ -58,7 +58,7 @@ var View = Backbone.View.extend({
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// set status
|
||||
_status: function(value) {
|
||||
var $el = this.$el.find('.progress-bar');
|
||||
@@ -69,13 +69,13 @@ var View = Backbone.View.extend({
|
||||
$el.addClass('progress-bar-' + value);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// set percentage
|
||||
_percentage: function(value) {
|
||||
var $el = this.$el.find('.progress-bar');
|
||||
$el.css({ width : value + '%' });
|
||||
},
|
||||
|
||||
|
||||
// template
|
||||
_template: function(options) {
|
||||
return '<div style="float: right">' +
|
||||
|
||||
@@ -6,72 +6,94 @@ return Backbone.View.extend({
|
||||
// options
|
||||
options: {
|
||||
class_add : 'upload-icon-button fa fa-square-o',
|
||||
class_remove : 'upload-icon-button fa fa-check-square-o'
|
||||
class_remove : 'upload-icon-button fa fa-check-square-o',
|
||||
class_partial : 'upload-icon-button fa fa-minus-square-o'
|
||||
},
|
||||
|
||||
|
||||
// render
|
||||
initialize: function(app) {
|
||||
// link app
|
||||
this.app = app;
|
||||
|
||||
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
|
||||
// set template
|
||||
this.setElement(this._template());
|
||||
|
||||
|
||||
// list of rows
|
||||
this.rows = [];
|
||||
|
||||
// load extension
|
||||
Utils.get({
|
||||
url : galaxy_config.root + 'api/ftp_files',
|
||||
url : galaxy_config.root + 'api/remote_files',
|
||||
success : function(ftp_files) { self._fill(ftp_files); },
|
||||
error : function() { self._fill(); }
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// events
|
||||
events: {
|
||||
'mousedown' : function(e) { e.preventDefault(); }
|
||||
},
|
||||
|
||||
|
||||
// fill table
|
||||
_fill: function(ftp_files) {
|
||||
if (ftp_files && ftp_files.length > 0) {
|
||||
// add table
|
||||
this.$el.find('#upload-ftp-content').html($(this._templateTable()));
|
||||
|
||||
|
||||
// add files to table
|
||||
var size = 0;
|
||||
for (key in ftp_files) {
|
||||
this.add(ftp_files[key]);
|
||||
this.rows.push(this._add(ftp_files[key]));
|
||||
size += ftp_files[key].size;
|
||||
}
|
||||
|
||||
|
||||
// update stats
|
||||
this.$el.find('#upload-ftp-number').html(ftp_files.length + ' files');
|
||||
this.$el.find('#upload-ftp-disk').html(Utils.bytesToString (size, true));
|
||||
|
||||
// add event handler to select/unselect all
|
||||
this.$select_all = $('#upload-selectall');
|
||||
this.$select_all.addClass(this.options.class_add);
|
||||
var self = this;
|
||||
this.$select_all.on('click', function() {
|
||||
var add = self.$select_all.hasClass(self.options.class_add);
|
||||
for (key in ftp_files) {
|
||||
var ftp_file = ftp_files[key];
|
||||
var model_index = self._find(ftp_file);
|
||||
if(!model_index && add || model_index && !add) {
|
||||
self.rows[key].trigger('click');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// refresh
|
||||
self._refresh();
|
||||
} else {
|
||||
// add info
|
||||
this.$el.find('#upload-ftp-content').html($(this._templateInfo()));
|
||||
}
|
||||
|
||||
|
||||
// hide spinner
|
||||
this.$el.find('#upload-ftp-wait').hide();
|
||||
},
|
||||
|
||||
|
||||
// add
|
||||
add: function(ftp_file) {
|
||||
_add: function(ftp_file) {
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
|
||||
// create new item
|
||||
var $it = $(this._templateRow(ftp_file));
|
||||
|
||||
|
||||
// identify icon
|
||||
var $icon = $it.find('.icon');
|
||||
|
||||
|
||||
// append to table
|
||||
$(this.el).find('tbody').append($it);
|
||||
|
||||
|
||||
// find model and set initial 'add' icon class
|
||||
var icon_class = '';
|
||||
if (this._find(ftp_file)) {
|
||||
@@ -79,7 +101,7 @@ return Backbone.View.extend({
|
||||
} else {
|
||||
icon_class = this.options.class_add;
|
||||
}
|
||||
|
||||
|
||||
// add icon class
|
||||
$icon.addClass(icon_class);
|
||||
|
||||
@@ -87,10 +109,10 @@ return Backbone.View.extend({
|
||||
$it.on('click', function() {
|
||||
// find model
|
||||
var model_index = self._find(ftp_file);
|
||||
|
||||
|
||||
// update icon
|
||||
$icon.removeClass();
|
||||
|
||||
|
||||
// add model
|
||||
if (!model_index) {
|
||||
// add to uploadbox
|
||||
@@ -100,43 +122,63 @@ return Backbone.View.extend({
|
||||
size : ftp_file.size,
|
||||
path : ftp_file.path
|
||||
}]);
|
||||
|
||||
|
||||
// add new icon class
|
||||
$icon.addClass(self.options.class_remove);
|
||||
} else {
|
||||
// remove
|
||||
self.app.collection.remove(model_index);
|
||||
|
||||
|
||||
// add new icon class
|
||||
$icon.addClass(self.options.class_add);
|
||||
}
|
||||
|
||||
// update select all checkbox
|
||||
self._refresh();
|
||||
});
|
||||
|
||||
// return dom handler
|
||||
return $it;
|
||||
},
|
||||
|
||||
// get model index
|
||||
_find: function(ftp_file) {
|
||||
// check if exists already
|
||||
var filtered = this.app.collection.where({file_path : ftp_file.path});
|
||||
var model_index = null;
|
||||
for (var key in filtered) {
|
||||
var item = filtered[key];
|
||||
if (item.get('status') == 'init' && item.get('file_mode') == 'ftp') {
|
||||
model_index = item.get('id');
|
||||
|
||||
// refresh
|
||||
_refresh: function() {
|
||||
var filtered = this.app.collection.where({file_mode : 'ftp'});
|
||||
this.$select_all.removeClass();
|
||||
if (filtered.length == 0) {
|
||||
this.$select_all.addClass(this.options.class_add);
|
||||
} else {
|
||||
if (filtered.length == this.rows.length) {
|
||||
this.$select_all.addClass(this.options.class_remove);
|
||||
} else {
|
||||
this.$select_all.addClass(this.options.class_partial);
|
||||
}
|
||||
}
|
||||
return model_index;
|
||||
},
|
||||
|
||||
|
||||
// get model index
|
||||
_find: function(ftp_file) {
|
||||
var item = this.app.collection.findWhere({
|
||||
file_path : ftp_file.path,
|
||||
status : 'init',
|
||||
file_mode : 'ftp'
|
||||
});
|
||||
if (item) {
|
||||
return item.get('id');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// template row
|
||||
_templateRow: function(options) {
|
||||
return '<tr class="upload-ftp-row" style="cursor: pointer;">' +
|
||||
return '<tr class="upload-ftp-row">' +
|
||||
'<td><div class="icon"/></td>' +
|
||||
'<td style="width: 200px"><p style="width: inherit; word-wrap: break-word;">' + options.path + '</p></td>' +
|
||||
'<td style="white-space: nowrap;">' + Utils.bytesToString(options.size) + '</td>' +
|
||||
'<td style="white-space: nowrap;">' + options.ctime + '</td>' +
|
||||
'<td class="label"><p>' + options.path + '</p></td>' +
|
||||
'<td class="nonlabel">' + Utils.bytesToString(options.size) + '</td>' +
|
||||
'<td class="nonlabel">' + options.ctime + '</td>' +
|
||||
'</tr>';
|
||||
},
|
||||
|
||||
|
||||
// load table template
|
||||
_templateTable: function() {
|
||||
return '<span style="whitespace: nowrap; float: left;">Available files: </span>' +
|
||||
@@ -149,7 +191,7 @@ return Backbone.View.extend({
|
||||
'<table class="grid" style="float: left;">' +
|
||||
'<thead>' +
|
||||
'<tr>' +
|
||||
'<th></th>' +
|
||||
'<th><div id="upload-selectall"></th>' +
|
||||
'<th>Name</th>' +
|
||||
'<th>Size</th>' +
|
||||
'<th>Created</th>' +
|
||||
@@ -158,14 +200,14 @@ return Backbone.View.extend({
|
||||
'<tbody></tbody>' +
|
||||
'</table>';
|
||||
},
|
||||
|
||||
|
||||
// load table template
|
||||
_templateInfo: function() {
|
||||
return '<div class="upload-ftp-warning warningmessage">' +
|
||||
'Your FTP directory does not contain any files.' +
|
||||
'</div>';
|
||||
},
|
||||
|
||||
|
||||
// load html template
|
||||
_template: function() {
|
||||
return '<div class="upload-ftp">' +
|
||||
@@ -174,7 +216,6 @@ return Backbone.View.extend({
|
||||
'<div id="upload-ftp-content"></div>' +
|
||||
'<div>';
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -14,8 +14,6 @@ var Model = Backbone.Model.extend({
|
||||
file_type : null,
|
||||
file_path : '',
|
||||
percentage : 0,
|
||||
|
||||
// settings
|
||||
space_to_tabs : false,
|
||||
to_posix_lines : true
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ define(['utils/utils',
|
||||
'mvc/upload/upload-settings',
|
||||
'mvc/ui/ui-popover',
|
||||
'mvc/ui/ui-select'],
|
||||
|
||||
|
||||
function( Utils,
|
||||
UploadModel,
|
||||
UploadSettings,
|
||||
@@ -18,7 +18,7 @@ return Backbone.View.extend({
|
||||
options: {
|
||||
padding : 8
|
||||
},
|
||||
|
||||
|
||||
// states
|
||||
status_classes : {
|
||||
init : 'upload-icon-button fa fa-trash-o',
|
||||
@@ -27,33 +27,33 @@ return Backbone.View.extend({
|
||||
success : 'upload-icon-button fa fa-check',
|
||||
error : 'upload-icon-button fa fa-exclamation-triangle'
|
||||
},
|
||||
|
||||
|
||||
// handle for settings popover
|
||||
settings: null,
|
||||
|
||||
|
||||
// genome selector
|
||||
select_genome : null,
|
||||
|
||||
|
||||
// extension selector
|
||||
select_extension : null,
|
||||
|
||||
|
||||
// render
|
||||
initialize: function(app, options) {
|
||||
// link app
|
||||
this.app = app;
|
||||
|
||||
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
|
||||
// create model
|
||||
this.model = new UploadModel.Model(options);
|
||||
|
||||
|
||||
// add upload item
|
||||
this.setElement(this._template(options));
|
||||
|
||||
// link item
|
||||
var it = this.$el;
|
||||
|
||||
|
||||
// append popup to settings icon
|
||||
this.settings = new Popover.View({
|
||||
title : 'Upload configuration',
|
||||
@@ -61,44 +61,57 @@ return Backbone.View.extend({
|
||||
placement : 'bottom'
|
||||
});
|
||||
|
||||
// initialize default genome through default select field
|
||||
var default_genome = this.app.select_genome.value();
|
||||
|
||||
// select genomes
|
||||
this.select_genome = new Select.View({
|
||||
css: 'genome',
|
||||
onchange : function() {
|
||||
self.model.set('genome', self.select_genome.value());
|
||||
onchange : function(genome) {
|
||||
self.model.set('genome', genome);
|
||||
self.app.updateGenome(genome, true);
|
||||
},
|
||||
data: self.app.list_genomes,
|
||||
container: it.find('#genome'),
|
||||
value: self.model.get('genome')
|
||||
value: default_genome
|
||||
});
|
||||
|
||||
// initialize genome
|
||||
this.model.set('genome', self.select_genome.value());
|
||||
|
||||
// initialize genome
|
||||
this.model.set('genome', default_genome);
|
||||
|
||||
// initialize default extension through default select field
|
||||
var default_extension = this.app.select_extension.value();
|
||||
|
||||
// select extension
|
||||
this.select_extension = new Select.View({
|
||||
css: 'extension',
|
||||
onchange : function() {
|
||||
self.model.set('extension', self.select_extension.value());
|
||||
onchange : function(extension) {
|
||||
self.model.set('extension', extension);
|
||||
self.app.updateExtension(extension, true);
|
||||
},
|
||||
data: self.app.list_extensions,
|
||||
container: it.find('#extension'),
|
||||
value: self.model.get('extension')
|
||||
value: default_extension
|
||||
});
|
||||
|
||||
|
||||
// initialize extension
|
||||
this.model.set('extension', self.select_extension.value());
|
||||
this.model.set('extension', default_extension);
|
||||
|
||||
//
|
||||
// ui events
|
||||
//
|
||||
|
||||
|
||||
// handle click event
|
||||
it.find('#symbol').on('click', function() { self._removeRow(); });
|
||||
|
||||
|
||||
// handle extension info popover
|
||||
it.find('#extension-info').on('click' , function(e) { self._showExtensionInfo(); })
|
||||
.on('mousedown', function(e) { e.preventDefault(); });
|
||||
it.find('#extension-info').on('click' , function(e) {
|
||||
self.app.showExtensionInfo({
|
||||
$el : $(e.target),
|
||||
title : self.select_extension.text(),
|
||||
extension : self.select_extension.value()
|
||||
});
|
||||
}).on('mousedown', function(e) { e.preventDefault(); });
|
||||
|
||||
// handle settings popover
|
||||
it.find('#settings').on('click' , function(e) { self._showSettings(); })
|
||||
@@ -109,12 +122,12 @@ return Backbone.View.extend({
|
||||
self.model.set('url_paste', $(e.target).val());
|
||||
self.model.set('file_size', $(e.target).val().length);
|
||||
});
|
||||
|
||||
|
||||
// handle space to tabs button
|
||||
it.find('#space_to_tabs').on('change', function(e) {
|
||||
self.model.set('space_to_tabs', $(e.target).prop('checked'));
|
||||
});
|
||||
|
||||
|
||||
//
|
||||
// model events
|
||||
//
|
||||
@@ -130,6 +143,9 @@ return Backbone.View.extend({
|
||||
this.model.on('change:genome', function() {
|
||||
self._refreshGenome();
|
||||
});
|
||||
this.model.on('change:extension', function() {
|
||||
self._refreshExtension();
|
||||
});
|
||||
this.model.on('change:file_size', function() {
|
||||
self._refreshFileSize();
|
||||
});
|
||||
@@ -140,57 +156,58 @@ return Backbone.View.extend({
|
||||
self.remove();
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// render
|
||||
render: function() {
|
||||
// read model
|
||||
var file_name = this.model.get('file_name');
|
||||
var file_size = this.model.get('file_size');
|
||||
var file_mode = this.model.get('file_mode');
|
||||
|
||||
|
||||
// link item
|
||||
var it = this.$el;
|
||||
|
||||
|
||||
// update title
|
||||
it.find('#title').html(file_name);
|
||||
|
||||
|
||||
// update info
|
||||
it.find('#size').html(Utils.bytesToString (file_size));
|
||||
|
||||
|
||||
// remove mode class
|
||||
it.find('#mode').removeClass()
|
||||
.addClass('mode');
|
||||
|
||||
it.find('#mode')
|
||||
.removeClass()
|
||||
.addClass('mode');
|
||||
|
||||
// activate text field if file is new
|
||||
if (file_mode == 'new') {
|
||||
// get text component
|
||||
var text = it.find('#text');
|
||||
|
||||
|
||||
// get padding
|
||||
var padding = this.options.padding;
|
||||
|
||||
|
||||
// get dimensions
|
||||
var width = it.width() - 2 * padding;
|
||||
var height = it.height() - padding;
|
||||
|
||||
|
||||
// set dimensions
|
||||
text.css('width', width + 'px');
|
||||
text.css('top', height + 'px');
|
||||
it.height(height + text.height() + 2 * padding);
|
||||
|
||||
|
||||
// show text field
|
||||
text.show();
|
||||
|
||||
|
||||
// update icon
|
||||
it.find('#mode').addClass('fa fa-pencil');
|
||||
}
|
||||
|
||||
|
||||
// file from local disk
|
||||
if (file_mode == 'local') {
|
||||
// update icon
|
||||
it.find('#mode').addClass('fa fa-laptop');
|
||||
}
|
||||
|
||||
|
||||
// file from ftp
|
||||
if (file_mode == 'ftp') {
|
||||
// update icon
|
||||
@@ -203,7 +220,7 @@ return Backbone.View.extend({
|
||||
// trigger remove event
|
||||
this.select_genome.remove();
|
||||
this.select_extension.remove();
|
||||
|
||||
|
||||
// call the base class remove method
|
||||
Backbone.View.prototype.remove.apply(this);
|
||||
},
|
||||
@@ -211,14 +228,17 @@ return Backbone.View.extend({
|
||||
//
|
||||
// handle model events
|
||||
//
|
||||
|
||||
// genome
|
||||
_refreshGenome: function() {
|
||||
// update genome info on screen
|
||||
var genome = this.model.get('genome');
|
||||
this.select_genome.value(genome);
|
||||
|
||||
// update extension
|
||||
_refreshExtension: function() {
|
||||
this.select_extension.value(this.model.get('extension'));
|
||||
},
|
||||
|
||||
|
||||
// update genome
|
||||
_refreshGenome: function() {
|
||||
this.select_genome.value(this.model.get('genome'));
|
||||
},
|
||||
|
||||
// progress
|
||||
_refreshInfo: function() {
|
||||
// write error message
|
||||
@@ -229,7 +249,7 @@ return Backbone.View.extend({
|
||||
this.$el.find('#info').hide();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// progress
|
||||
_refreshPercentage : function() {
|
||||
var percentage = parseInt(this.model.get('percentage'));
|
||||
@@ -239,29 +259,29 @@ return Backbone.View.extend({
|
||||
else
|
||||
this.$el.find('#percentage').html('Adding to history...');
|
||||
},
|
||||
|
||||
|
||||
// status
|
||||
_refreshStatus : function() {
|
||||
// get element
|
||||
var it = this.$el;
|
||||
|
||||
|
||||
// identify new status
|
||||
var status = this.model.get('status');
|
||||
var status_class = this.status_classes[status];
|
||||
|
||||
|
||||
// identify symbol and reset classes
|
||||
var sy = this.$el.find('#symbol');
|
||||
sy.removeClass();
|
||||
|
||||
|
||||
// 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('#space_to_tabs').attr('disabled', false);
|
||||
@@ -269,18 +289,18 @@ return Backbone.View.extend({
|
||||
// select fields
|
||||
this.select_genome.disable();
|
||||
this.select_extension.disable();
|
||||
|
||||
|
||||
// default fields
|
||||
it.find('#text-content').attr('disabled', true);
|
||||
it.find('#space_to_tabs').attr('disabled', true);
|
||||
}
|
||||
|
||||
|
||||
// success
|
||||
if (status == 'success') {
|
||||
it.addClass('success');
|
||||
it.find('#percentage').html('100%');
|
||||
}
|
||||
|
||||
|
||||
// error
|
||||
if (status == 'error') {
|
||||
it.addClass('danger');
|
||||
@@ -297,44 +317,17 @@ return Backbone.View.extend({
|
||||
//
|
||||
// handle ui events
|
||||
//
|
||||
|
||||
|
||||
// remove row
|
||||
_removeRow: function() {
|
||||
// get current status
|
||||
var status = this.model.get('status');
|
||||
|
||||
|
||||
// only remove from queue if not in processing line
|
||||
if (status == 'init' || status == 'success' || status == 'error') {
|
||||
this.app.collection.remove(this.model);
|
||||
}
|
||||
},
|
||||
|
||||
// attach file info popup
|
||||
_showExtensionInfo : function() {
|
||||
// initialize
|
||||
var $el = $(this.el).find('#extension-info');
|
||||
var extension = this.model.get('extension');
|
||||
var title = this.select_extension.text();
|
||||
var description = _.findWhere(this.app.list_extensions, {'id': extension});
|
||||
|
||||
// create popup
|
||||
if (!this.extension_popup) {
|
||||
this.extension_popup = new Popover.View({
|
||||
placement: 'bottom',
|
||||
container: $el
|
||||
});
|
||||
}
|
||||
|
||||
// show / hide popup
|
||||
if (!this.extension_popup.visible) {
|
||||
this.extension_popup.title(title);
|
||||
this.extension_popup.empty();
|
||||
this.extension_popup.append(this._templateDescription(description));
|
||||
this.extension_popup.show();
|
||||
} else {
|
||||
this.extension_popup.hide();
|
||||
}
|
||||
},
|
||||
|
||||
// attach file info popup
|
||||
_showSettings : function() {
|
||||
@@ -349,25 +342,12 @@ return Backbone.View.extend({
|
||||
this.settings.hide();
|
||||
}
|
||||
},
|
||||
|
||||
// template
|
||||
_templateDescription: function(options) {
|
||||
if (options.description) {
|
||||
var tmpl = options.description;
|
||||
if (options.description_url) {
|
||||
tmpl += ' (<a href="' + options.description_url + '" target="_blank">read more</a>)';
|
||||
}
|
||||
return tmpl;
|
||||
} else {
|
||||
return 'There is no description available for this file extension.';
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// template
|
||||
_template: function(options) {
|
||||
return '<tr id="upload-item-' + options.id + '" class="upload-item">' +
|
||||
'<td>' +
|
||||
'<div style="position: relative;">' +
|
||||
'<div class="name-column">' +
|
||||
'<div id="mode"></div>' +
|
||||
'<div id="title" class="title"></div>' +
|
||||
'<div id="text" class="text">' +
|
||||
@@ -400,7 +380,7 @@ return Backbone.View.extend({
|
||||
'</td>' +
|
||||
'</tr>';
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -8,35 +8,35 @@ return Backbone.View.extend({
|
||||
class_check : 'upload-icon-button fa fa-check-square-o',
|
||||
class_uncheck : 'upload-icon-button fa fa-square-o'
|
||||
},
|
||||
|
||||
|
||||
// render
|
||||
initialize: function(app) {
|
||||
// link app
|
||||
this.app = app;
|
||||
|
||||
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
|
||||
// set template
|
||||
this.setElement(this._template());
|
||||
|
||||
|
||||
// link model
|
||||
this.model = this.app.model;
|
||||
|
||||
|
||||
// ui event: space to tabs
|
||||
this.$el.find('#upload-space-to-tabs').on('click', function() {
|
||||
self._switchState('#upload-space-to-tabs', 'space_to_tabs');
|
||||
});
|
||||
|
||||
|
||||
// ui event: to posix
|
||||
this.$el.find('#upload-to-posix-lines').on('click', function() {
|
||||
self._switchState('#upload-to-posix-lines', 'to_posix_lines');
|
||||
});
|
||||
|
||||
|
||||
// render
|
||||
this.render();
|
||||
},
|
||||
|
||||
|
||||
// events
|
||||
events: {
|
||||
'mousedown' : function(e) { e.preventDefault(); }
|
||||
@@ -47,7 +47,7 @@ return Backbone.View.extend({
|
||||
// render states
|
||||
this._renderState('#upload-space-to-tabs', this.model.get('space_to_tabs'));
|
||||
this._renderState('#upload-to-posix-lines', this.model.get('to_posix_lines'));
|
||||
|
||||
|
||||
// disable options
|
||||
var $cover = this.$el.find('#upload-settings-cover');
|
||||
if (this.model.get('status') != 'init') {
|
||||
@@ -56,7 +56,7 @@ return Backbone.View.extend({
|
||||
$cover.hide();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// switch state
|
||||
_switchState: function (element_id, parameter_id) {
|
||||
if (this.model.get('status') == 'init') {
|
||||
@@ -65,7 +65,7 @@ return Backbone.View.extend({
|
||||
this._renderState(element_id, checked);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// render state
|
||||
_renderState: function (element_id, checked) {
|
||||
// swith icon class
|
||||
@@ -77,7 +77,7 @@ return Backbone.View.extend({
|
||||
$it.addClass(this.options.class_uncheck);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// load template
|
||||
_template: function() {
|
||||
return '<div class="upload-settings" style="position: relative;">' +
|
||||
|
||||
@@ -6,58 +6,70 @@ define(["utils/utils",
|
||||
"mvc/upload/upload-ftp",
|
||||
"mvc/ui/ui-popover",
|
||||
"mvc/ui/ui-modal",
|
||||
"mvc/ui/ui-select",
|
||||
"utils/uploadbox"],
|
||||
|
||||
|
||||
function( Utils,
|
||||
UploadButton,
|
||||
UploadModel,
|
||||
UploadItem,
|
||||
UploadFtp,
|
||||
Popover,
|
||||
Modal
|
||||
Modal,
|
||||
Select
|
||||
) {
|
||||
|
||||
// galaxy upload
|
||||
return Backbone.View.extend({
|
||||
// options
|
||||
options : {
|
||||
nginx_upload_path : ''
|
||||
nginx_upload_path : ''
|
||||
},
|
||||
|
||||
|
||||
// default for selection fields
|
||||
default_extension : 'auto',
|
||||
default_genome : '?',
|
||||
|
||||
// own modal
|
||||
modal : null,
|
||||
|
||||
|
||||
// button
|
||||
ui_button : null,
|
||||
|
||||
|
||||
// jquery uploadbox plugin
|
||||
uploadbox: null,
|
||||
|
||||
|
||||
// current history
|
||||
current_history: null,
|
||||
|
||||
|
||||
// extension selector
|
||||
select_extension : null,
|
||||
|
||||
// genome selector
|
||||
select_genome: null,
|
||||
|
||||
// current upload size
|
||||
upload_size: 0,
|
||||
|
||||
|
||||
// extension types
|
||||
list_extensions :[],
|
||||
|
||||
|
||||
// genomes
|
||||
list_genomes : [],
|
||||
|
||||
|
||||
// datatype placeholder for auto-detection
|
||||
auto: {
|
||||
id : 'auto',
|
||||
text : 'Auto-detect',
|
||||
description : 'This system will try to detect the file type automatically. If your file is not detected properly as one of the known formats, it most likely means that it has some format problems (e.g., different number of columns on different rows). You can still coerce the system to set your data to the format you think it should be. You can also upload compressed files, which will automatically be decompressed.'
|
||||
},
|
||||
|
||||
|
||||
// collection
|
||||
collection : new UploadModel.Collection(),
|
||||
|
||||
|
||||
// ftp file viewer
|
||||
ftp : null,
|
||||
|
||||
|
||||
// counter
|
||||
counter : {
|
||||
// stats
|
||||
@@ -65,13 +77,13 @@ return Backbone.View.extend({
|
||||
success : 0,
|
||||
error : 0,
|
||||
running : 0,
|
||||
|
||||
|
||||
// reset stats
|
||||
reset : function() {
|
||||
this.announce = this.success = this.error = this.running = 0;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// initialize
|
||||
initialize : function(options) {
|
||||
// link this
|
||||
@@ -81,7 +93,7 @@ return Backbone.View.extend({
|
||||
if (options) {
|
||||
this.options = _.defaults(options, this.options);
|
||||
}
|
||||
|
||||
|
||||
// create model
|
||||
this.ui_button = new UploadButton.Model({
|
||||
icon : 'fa-upload',
|
||||
@@ -91,7 +103,7 @@ return Backbone.View.extend({
|
||||
if (e) {
|
||||
// prevent default
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
// show
|
||||
self.show()
|
||||
}
|
||||
@@ -102,10 +114,10 @@ return Backbone.View.extend({
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// define location
|
||||
$('#left .unified-panel-header-inner').append((new UploadButton.View(this.ui_button)).$el);
|
||||
|
||||
$('.with-upload-button').append((new UploadButton.View(this.ui_button)).$el);
|
||||
|
||||
// load extension
|
||||
var self = this;
|
||||
Utils.get({
|
||||
@@ -119,19 +131,19 @@ return Backbone.View.extend({
|
||||
description_url : datatypes[key].description_url
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// sort
|
||||
self.list_extensions.sort(function(a, b) {
|
||||
return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
|
||||
return a.text > b.text ? 1 : a.text < b.text ? -1 : 0;
|
||||
});
|
||||
|
||||
|
||||
// add auto field
|
||||
if (!self.options.datatypes_disable_auto) {
|
||||
self.list_extensions.unshift(self.auto);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// load genomes
|
||||
Utils.get({
|
||||
url : galaxy_config.root + "api/genomes",
|
||||
@@ -142,32 +154,26 @@ return Backbone.View.extend({
|
||||
text : genomes[key][0]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// sort
|
||||
self.list_genomes.sort(function(a, b) {
|
||||
return a.id > b.id ? 1 : a.id < b.id ? -1 : 0;
|
||||
if (a.id == self.default_genome) { return -1; }
|
||||
if (b.id == self.default_genome) { return 1; }
|
||||
return a.text > b.text ? 1 : a.text < b.text ? -1 : 0;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// events
|
||||
this.collection.on('remove', function(item) {
|
||||
self._eventRemove(item);
|
||||
});
|
||||
this.collection.on('change:genome', function(item) {
|
||||
var genome = item.get('genome');
|
||||
self.collection.each(function(item) {
|
||||
if (item.get('status') == 'init' && item.get('genome') == '?') {
|
||||
item.set('genome', genome);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
//
|
||||
// event triggered by upload button
|
||||
//
|
||||
|
||||
|
||||
// show/hide upload frame
|
||||
show: function () {
|
||||
// wait for galaxy history panel
|
||||
@@ -176,7 +182,7 @@ return Backbone.View.extend({
|
||||
window.setTimeout(function() { self.show() }, 500)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// create modal
|
||||
if (!this.modal) {
|
||||
// make modal
|
||||
@@ -197,10 +203,10 @@ return Backbone.View.extend({
|
||||
width : '900',
|
||||
closing_events : true
|
||||
});
|
||||
|
||||
|
||||
// set element
|
||||
this.setElement('#upload-box');
|
||||
|
||||
|
||||
// file upload
|
||||
var self = this;
|
||||
this.uploadbox = this.$el.uploadbox({
|
||||
@@ -211,34 +217,89 @@ return Backbone.View.extend({
|
||||
error : function(index, file, message) { self._eventError(index, file, message) },
|
||||
complete : function() { self._eventComplete() }
|
||||
});
|
||||
|
||||
|
||||
// add ftp file viewer
|
||||
var button = this.modal.getButton('Choose FTP file');
|
||||
this.ftp = new Popover.View({
|
||||
title : 'FTP files',
|
||||
container : button
|
||||
});
|
||||
|
||||
// select extension
|
||||
this.select_extension = new Select.View({
|
||||
css : 'header-selection',
|
||||
data : this.list_extensions,
|
||||
container : this.$el.parent().find('#header-extension'),
|
||||
value : this.default_extension,
|
||||
onchange : function(extension) {
|
||||
self.updateExtension(extension);
|
||||
}
|
||||
});
|
||||
|
||||
// handle extension info popover
|
||||
self.$el.parent().find('#header-extension-info').on('click' , function(e) {
|
||||
self.showExtensionInfo({
|
||||
$el : $(e.target),
|
||||
title : self.select_extension.text(),
|
||||
extension : self.select_extension.value(),
|
||||
placement : 'top'
|
||||
});
|
||||
}).on('mousedown', function(e) { e.preventDefault(); });
|
||||
|
||||
// genome extension
|
||||
this.select_genome = new Select.View({
|
||||
css : 'header-selection',
|
||||
data : this.list_genomes,
|
||||
container : this.$el.parent().find('#header-genome'),
|
||||
value : this.default_genome,
|
||||
onchange : function(genome) {
|
||||
self.updateGenome(genome);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// show modal
|
||||
this.modal.show();
|
||||
|
||||
|
||||
// refresh
|
||||
this._updateUser();
|
||||
|
||||
|
||||
// setup info
|
||||
this._updateScreen();
|
||||
},
|
||||
|
||||
showExtensionInfo : function(options) {
|
||||
// initialize
|
||||
var self = this;
|
||||
var $el = options.$el;
|
||||
var extension = options.extension;
|
||||
var title = options.title;
|
||||
var description = _.findWhere(self.list_extensions, {'id': extension});
|
||||
|
||||
// create popup
|
||||
this.extension_popup && this.extension_popup.remove();
|
||||
this.extension_popup = new Popover.View({
|
||||
placement: options.placement || 'bottom',
|
||||
container: $el,
|
||||
destroy: true
|
||||
});
|
||||
|
||||
// add content and show popup
|
||||
this.extension_popup.title(title);
|
||||
this.extension_popup.empty();
|
||||
this.extension_popup.append(this._templateDescription(description));
|
||||
this.extension_popup.show();
|
||||
},
|
||||
|
||||
//
|
||||
// events triggered by collection
|
||||
//
|
||||
|
||||
|
||||
// remove item from upload list
|
||||
_eventRemove : function(item) {
|
||||
// update status
|
||||
var status = item.get('status');
|
||||
|
||||
|
||||
// reduce counter
|
||||
if (status == 'success') {
|
||||
this.counter.success--;
|
||||
@@ -247,26 +308,26 @@ return Backbone.View.extend({
|
||||
} else {
|
||||
this.counter.announce--;
|
||||
}
|
||||
|
||||
|
||||
// show on screen info
|
||||
this._updateScreen();
|
||||
|
||||
|
||||
// remove from queue
|
||||
this.uploadbox.remove(item.id);
|
||||
},
|
||||
|
||||
|
||||
//
|
||||
// events triggered by the upload box plugin
|
||||
//
|
||||
|
||||
|
||||
// a new file has been dropped/selected through the uploadbox plugin
|
||||
_eventAnnounce : function(index, file, message) {
|
||||
// update counter
|
||||
this.counter.announce++;
|
||||
|
||||
|
||||
// update screen
|
||||
this._updateScreen();
|
||||
|
||||
|
||||
// create view/model
|
||||
var upload_item = new UploadItem(this, {
|
||||
id : index,
|
||||
@@ -275,25 +336,25 @@ return Backbone.View.extend({
|
||||
file_mode : file.mode,
|
||||
file_path : file.path
|
||||
});
|
||||
|
||||
|
||||
// add to collection
|
||||
this.collection.add(upload_item.model);
|
||||
|
||||
|
||||
// add upload item element to table
|
||||
$(this.el).find('tbody:first').append(upload_item.$el);
|
||||
|
||||
|
||||
// render
|
||||
upload_item.render();
|
||||
},
|
||||
|
||||
|
||||
// the uploadbox plugin is initializing the upload for this file
|
||||
_eventInitialize : function(index, file, message) {
|
||||
// get element
|
||||
var it = this.collection.get(index);
|
||||
|
||||
|
||||
// update status
|
||||
it.set('status', 'running');
|
||||
|
||||
|
||||
// get configuration
|
||||
var file_name = it.get('file_name');
|
||||
var file_path = it.get('file_path');
|
||||
@@ -303,113 +364,113 @@ return Backbone.View.extend({
|
||||
var url_paste = it.get('url_paste');
|
||||
var space_to_tabs = it.get('space_to_tabs');
|
||||
var to_posix_lines = it.get('to_posix_lines');
|
||||
|
||||
|
||||
// validate
|
||||
if (!url_paste && !(file.size > 0))
|
||||
return null;
|
||||
|
||||
|
||||
// configure uploadbox
|
||||
this.uploadbox.configure({url : this.options.nginx_upload_path});
|
||||
|
||||
|
||||
// local files
|
||||
if (file_mode == 'local') {
|
||||
this.uploadbox.configure({paramname : 'files_0|file_data'});
|
||||
} else {
|
||||
this.uploadbox.configure({paramname : null});
|
||||
}
|
||||
|
||||
|
||||
// configure tool
|
||||
tool_input = {};
|
||||
|
||||
|
||||
// new files
|
||||
if (file_mode == 'new') {
|
||||
tool_input['files_0|url_paste'] = url_paste;
|
||||
}
|
||||
|
||||
|
||||
// files from ftp
|
||||
if (file_mode == 'ftp') {
|
||||
tool_input['files_0|ftp_files'] = file_path;
|
||||
}
|
||||
|
||||
|
||||
// add common configuration
|
||||
tool_input['dbkey'] = genome;
|
||||
tool_input['file_type'] = extension;
|
||||
tool_input['files_0|type'] = 'upload_dataset';
|
||||
tool_input['space_to_tabs'] = space_to_tabs;
|
||||
tool_input['to_posix_lines'] = to_posix_lines;
|
||||
|
||||
tool_input['files_0|space_to_tab'] = space_to_tabs && 'Yes' || null;
|
||||
tool_input['files_0|to_posix_lines'] = to_posix_lines && 'Yes' || null;
|
||||
|
||||
// setup data
|
||||
data = {};
|
||||
data['history_id'] = this.current_history;
|
||||
data['tool_id'] = 'upload1';
|
||||
data['inputs'] = JSON.stringify(tool_input);
|
||||
|
||||
|
||||
// return additional data to be send with file
|
||||
return data;
|
||||
},
|
||||
|
||||
|
||||
// progress
|
||||
_eventProgress : function(index, file, percentage) {
|
||||
// set progress for row
|
||||
var it = this.collection.get(index);
|
||||
it.set('percentage', percentage);
|
||||
|
||||
|
||||
// update ui button
|
||||
this.ui_button.set('percentage', this._upload_percentage(percentage, file.size));
|
||||
this.ui_button.set('percentage', this._uploadPercentage(percentage, file.size));
|
||||
},
|
||||
|
||||
|
||||
// success
|
||||
_eventSuccess : function(index, file, message) {
|
||||
// update status
|
||||
var it = this.collection.get(index);
|
||||
it.set('percentage', 100);
|
||||
it.set('status', 'success');
|
||||
|
||||
|
||||
// file size
|
||||
var file_size = it.get('file_size');
|
||||
|
||||
|
||||
// update ui button
|
||||
this.ui_button.set('percentage', this._upload_percentage(100, file_size));
|
||||
|
||||
this.ui_button.set('percentage', this._uploadPercentage(100, file_size));
|
||||
|
||||
// update completed
|
||||
this.upload_completed += file_size * 100;
|
||||
|
||||
|
||||
// update counter
|
||||
this.counter.announce--;
|
||||
this.counter.success++;
|
||||
|
||||
|
||||
// update on screen info
|
||||
this._updateScreen();
|
||||
|
||||
|
||||
// update galaxy history
|
||||
Galaxy.currHistoryPanel.refreshContents();
|
||||
},
|
||||
|
||||
|
||||
// error
|
||||
_eventError : function(index, file, message) {
|
||||
// get element
|
||||
var it = this.collection.get(index);
|
||||
|
||||
|
||||
// update status
|
||||
it.set('percentage', 100);
|
||||
it.set('status', 'error');
|
||||
it.set('info', message);
|
||||
|
||||
|
||||
// update ui button
|
||||
this.ui_button.set('percentage', this._upload_percentage(100, file.size));
|
||||
this.ui_button.set('percentage', this._uploadPercentage(100, file.size));
|
||||
this.ui_button.set('status', 'danger');
|
||||
|
||||
|
||||
// update completed
|
||||
this.upload_completed += file.size * 100;
|
||||
|
||||
|
||||
// update counter
|
||||
this.counter.announce--;
|
||||
this.counter.error++;
|
||||
|
||||
|
||||
// update on screen info
|
||||
this._updateScreen();
|
||||
},
|
||||
|
||||
|
||||
// queue is done
|
||||
_eventComplete: function() {
|
||||
// reset queued upload to initial status
|
||||
@@ -418,16 +479,16 @@ return Backbone.View.extend({
|
||||
item.set('status', 'init');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// update running
|
||||
this.counter.running = 0;
|
||||
this._updateScreen();
|
||||
},
|
||||
|
||||
|
||||
//
|
||||
// events triggered by this view
|
||||
//
|
||||
|
||||
|
||||
_eventFtp: function() {
|
||||
// check if popover is visible
|
||||
if (!this.ftp.visible) {
|
||||
@@ -456,7 +517,7 @@ return Backbone.View.extend({
|
||||
if (this.counter.announce == 0 || this.counter.running > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// reset current size
|
||||
var self = this;
|
||||
this.upload_size = 0;
|
||||
@@ -468,19 +529,19 @@ return Backbone.View.extend({
|
||||
self.upload_size += item.get('file_size');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// reset progress
|
||||
this.ui_button.set('percentage', 0);
|
||||
this.ui_button.set('status', 'success');
|
||||
|
||||
|
||||
// update running
|
||||
this.counter.running = this.counter.announce;
|
||||
this._updateScreen();
|
||||
|
||||
|
||||
// initiate upload procedure in plugin
|
||||
this.uploadbox.start();
|
||||
},
|
||||
|
||||
|
||||
// pause upload process
|
||||
_eventStop : function() {
|
||||
// check
|
||||
@@ -493,32 +554,56 @@ return Backbone.View.extend({
|
||||
|
||||
// request pause
|
||||
this.uploadbox.stop();
|
||||
|
||||
|
||||
// set html content
|
||||
$('#upload-info').html('Queue will pause after completing the current file...');
|
||||
},
|
||||
|
||||
|
||||
// remove all
|
||||
_eventReset : function() {
|
||||
// make sure queue is not running
|
||||
if (this.counter.running == 0){
|
||||
// reset collection
|
||||
this.collection.reset();
|
||||
|
||||
|
||||
// reset counter
|
||||
this.counter.reset();
|
||||
|
||||
|
||||
// show on screen info
|
||||
this._updateScreen();
|
||||
|
||||
|
||||
// remove from queue
|
||||
this.uploadbox.reset();
|
||||
|
||||
|
||||
// reset value for universal type drop-down
|
||||
this.select_extension.value(this.default_extension);
|
||||
this.select_genome.value(this.default_genome);
|
||||
|
||||
// reset button
|
||||
this.ui_button.set('percentage', 0);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// update extension for all models
|
||||
updateExtension: function(extension, defaults_only) {
|
||||
var self = this;
|
||||
this.collection.each(function(item) {
|
||||
if (item.get('status') == 'init' && (item.get('extension') == self.default_extension || !defaults_only)) {
|
||||
item.set('extension', extension);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// update genome for all models
|
||||
updateGenome: function(genome, defaults_only) {
|
||||
var self = this;
|
||||
this.collection.each(function(item) {
|
||||
if (item.get('status') == 'init' && (item.get('genome') == self.default_genome || !defaults_only)) {
|
||||
item.set('genome', genome);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// update uset
|
||||
_updateUser: function() {
|
||||
// backup current history
|
||||
@@ -528,13 +613,13 @@ return Backbone.View.extend({
|
||||
this.current_history = Galaxy.currHistoryPanel.model.get('id');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// set screen
|
||||
_updateScreen: function () {
|
||||
/*
|
||||
update on screen info
|
||||
*/
|
||||
|
||||
|
||||
// check default message
|
||||
if(this.counter.announce == 0){
|
||||
if (this.uploadbox.compatible())
|
||||
@@ -547,20 +632,20 @@ return Backbone.View.extend({
|
||||
else
|
||||
message = 'Please wait...' + this.counter.announce + ' out of ' + this.counter.running + ' remaining.';
|
||||
}
|
||||
|
||||
|
||||
// set html content
|
||||
$('#upload-info').html(message);
|
||||
|
||||
|
||||
/*
|
||||
update button status
|
||||
*/
|
||||
|
||||
|
||||
// update reset button
|
||||
if (this.counter.running == 0 && this.counter.announce + this.counter.success + this.counter.error > 0)
|
||||
this.modal.enableButton('Reset');
|
||||
else
|
||||
this.modal.disableButton('Reset');
|
||||
|
||||
|
||||
// update upload button
|
||||
if (this.counter.running == 0 && this.counter.announce > 0)
|
||||
this.modal.enableButton('Start');
|
||||
@@ -572,7 +657,7 @@ return Backbone.View.extend({
|
||||
this.modal.enableButton('Pause');
|
||||
else
|
||||
this.modal.disableButton('Pause');
|
||||
|
||||
|
||||
// select upload button
|
||||
if (this.counter.running == 0){
|
||||
this.modal.enableButton('Choose local file');
|
||||
@@ -583,14 +668,14 @@ return Backbone.View.extend({
|
||||
this.modal.disableButton('Choose FTP file');
|
||||
this.modal.disableButton('Paste/Fetch data');
|
||||
}
|
||||
|
||||
|
||||
// ftp button
|
||||
if (this.current_user && this.options.ftp_upload_dir && this.options.ftp_upload_site) {
|
||||
this.modal.showButton('Choose FTP file');
|
||||
} else {
|
||||
this.modal.hideButton('Choose FTP file');
|
||||
}
|
||||
|
||||
|
||||
// table visibility
|
||||
if (this.counter.announce + this.counter.success + this.counter.error > 0) {
|
||||
$(this.el).find('#upload-table').show();
|
||||
@@ -600,13 +685,29 @@ return Backbone.View.extend({
|
||||
},
|
||||
|
||||
// calculate percentage of all queued uploads
|
||||
_upload_percentage: function(percentage, size) {
|
||||
_uploadPercentage: function(percentage, size) {
|
||||
return (this.upload_completed + (percentage * size)) / this.upload_size;
|
||||
},
|
||||
|
||||
// template for extensions description
|
||||
_templateDescription: function(options) {
|
||||
if (options.description) {
|
||||
var tmpl = options.description;
|
||||
if (options.description_url) {
|
||||
tmpl += ' (<a href="' + options.description_url + '" target="_blank">read more</a>)';
|
||||
}
|
||||
return tmpl;
|
||||
} else {
|
||||
return 'There is no description available for this file extension.';
|
||||
}
|
||||
},
|
||||
|
||||
// load html template
|
||||
_template: function(id, idInfo) {
|
||||
return '<div id="' + id + '" class="upload-box">' +
|
||||
return '<div class="upload-top">' +
|
||||
'<h6 id="' + idInfo + '" class="upload-info"></h6>' +
|
||||
'</div>' +
|
||||
'<div id="' + id + '" class="upload-box">' +
|
||||
'<table id="upload-table" class="table table-striped" style="display: none;">' +
|
||||
'<thead>' +
|
||||
'<tr>' +
|
||||
@@ -622,7 +723,13 @@ return Backbone.View.extend({
|
||||
'<tbody></tbody>' +
|
||||
'</table>' +
|
||||
'</div>' +
|
||||
'<h6 id="' + idInfo + '" class="upload-info"></h6>';
|
||||
'<div id="upload-header" class="upload-header">' +
|
||||
'<span class="header-title">Type (set all):</span>' +
|
||||
'<span id="header-extension"/>' +
|
||||
'<span id="header-extension-info" class="upload-icon-button fa fa-search"/> ' +
|
||||
'<span class="header-title">Genome (set all):</span>' +
|
||||
'<span id="header-genome"/>' +
|
||||
'</div>';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
(function() {
|
||||
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
templates['panel_section'] = 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 += "<div class=\"toolSectionTitle\" id=\"title_";
|
||||
if (stack1 = helpers.id) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
|
||||
buffer += escapeExpression(stack1)
|
||||
+ "\">\n <a href=\"javascript:void(0)\"><span>";
|
||||
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)
|
||||
+ "</span></a>\n</div>\n<div id=\"";
|
||||
if (stack1 = helpers.id) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
|
||||
buffer += escapeExpression(stack1)
|
||||
templates['panel_section'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
|
||||
var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
|
||||
return "<div class=\"toolSectionTitle\" id=\"title_"
|
||||
+ escapeExpression(((helper = (helper = helpers.id || (depth0 != null ? depth0.id : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"id","hash":{},"data":data}) : helper)))
|
||||
+ "\">\n <a href=\"javascript:void(0)\"><span>"
|
||||
+ escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"name","hash":{},"data":data}) : helper)))
|
||||
+ "</span></a>\n</div>\n<div id=\""
|
||||
+ escapeExpression(((helper = (helper = helpers.id || (depth0 != null ? depth0.id : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"id","hash":{},"data":data}) : helper)))
|
||||
+ "\" class=\"toolSectionBody\" style=\"display: none; \">\n <div class=\"toolSectionBg\"></div>\n<div>";
|
||||
return buffer;
|
||||
});
|
||||
},"useData":true});
|
||||
})();
|
||||
@@ -1,49 +1,26 @@
|
||||
(function() {
|
||||
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
templates['tool_form'] = 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 <div class=\"form-row\">\n <label for=\"";
|
||||
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)
|
||||
+ "\">";
|
||||
if (stack1 = helpers.label) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.label; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
|
||||
buffer += escapeExpression(stack1)
|
||||
templates['tool_form'] = template({"1":function(depth0,helpers,partials,data) {
|
||||
var stack1, helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, buffer = " <div class=\"form-row\">\n <label for=\""
|
||||
+ escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"name","hash":{},"data":data}) : helper)))
|
||||
+ "\">"
|
||||
+ escapeExpression(((helper = (helper = helpers.label || (depth0 != null ? depth0.label : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"label","hash":{},"data":data}) : helper)))
|
||||
+ ":</label>\n <div class=\"form-row-input\">\n ";
|
||||
if (stack1 = helpers.html) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.html; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
|
||||
if(stack1 || stack1 === 0) { buffer += stack1; }
|
||||
buffer += "\n </div>\n <div class=\"toolParamHelp\" style=\"clear: both;\">\n ";
|
||||
if (stack1 = helpers.help) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.help; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
|
||||
buffer += escapeExpression(stack1)
|
||||
+ "\n </div>\n <div style=\"clear: both;\"></div>\n </div>\n ";
|
||||
return buffer;
|
||||
}
|
||||
|
||||
buffer += "<div class=\"toolFormTitle\">";
|
||||
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)
|
||||
+ " (version ";
|
||||
if (stack1 = helpers.version) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.version; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
|
||||
buffer += escapeExpression(stack1)
|
||||
+ ")</div>\n <div class=\"toolFormBody\">\n ";
|
||||
stack1 = helpers.each.call(depth0, depth0.inputs, {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
|
||||
if(stack1 || stack1 === 0) { buffer += stack1; }
|
||||
buffer += "\n </div>\n <div class=\"form-row form-actions\">\n <input type=\"submit\" class=\"btn btn-primary\" name=\"runtool_btn\" value=\"Execute\">\n</div>\n<div class=\"toolHelp\">\n <div class=\"toolHelpBody\">";
|
||||
if (stack1 = helpers.help) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.help; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
|
||||
buffer += escapeExpression(stack1)
|
||||
stack1 = ((helper = (helper = helpers.html || (depth0 != null ? depth0.html : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"html","hash":{},"data":data}) : helper));
|
||||
if (stack1 != null) { buffer += stack1; }
|
||||
return buffer + "\n </div>\n <div class=\"toolParamHelp\" style=\"clear: both;\">\n "
|
||||
+ escapeExpression(((helper = (helper = helpers.help || (depth0 != null ? depth0.help : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"help","hash":{},"data":data}) : helper)))
|
||||
+ "\n </div>\n <div style=\"clear: both;\"></div>\n </div>\n";
|
||||
},"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
|
||||
var stack1, helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, buffer = "<div class=\"toolFormTitle\">"
|
||||
+ escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"name","hash":{},"data":data}) : helper)))
|
||||
+ " (version "
|
||||
+ escapeExpression(((helper = (helper = helpers.version || (depth0 != null ? depth0.version : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"version","hash":{},"data":data}) : helper)))
|
||||
+ ")</div>\n <div class=\"toolFormBody\">\n";
|
||||
stack1 = helpers.each.call(depth0, (depth0 != null ? depth0.inputs : depth0), {"name":"each","hash":{},"fn":this.program(1, data),"inverse":this.noop,"data":data});
|
||||
if (stack1 != null) { buffer += stack1; }
|
||||
return buffer + " </div>\n <div class=\"form-row form-actions\">\n <input type=\"submit\" class=\"btn btn-primary\" name=\"runtool_btn\" value=\"Execute\">\n</div>\n<div class=\"toolHelp\">\n <div class=\"toolHelpBody\">"
|
||||
+ escapeExpression(((helper = (helper = helpers.help || (depth0 != null ? depth0.help : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"help","hash":{},"data":data}) : helper)))
|
||||
+ "</div>\n</div>";
|
||||
return buffer;
|
||||
});
|
||||
},"useData":true});
|
||||
})();
|
||||
@@ -1,35 +1,18 @@
|
||||
(function() {
|
||||
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
templates['tool_link'] = 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 += "<a class=\"";
|
||||
if (stack1 = helpers.id) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
|
||||
buffer += escapeExpression(stack1)
|
||||
+ " tool-link\" href=\"";
|
||||
if (stack1 = helpers.link) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.link; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
|
||||
buffer += escapeExpression(stack1)
|
||||
+ "\" target=\"";
|
||||
if (stack1 = helpers.target) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.target; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
|
||||
buffer += escapeExpression(stack1)
|
||||
+ "\" minsizehint=\"";
|
||||
if (stack1 = helpers.min_width) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.min_width; 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)
|
||||
+ "</a> ";
|
||||
if (stack1 = helpers.description) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.description; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
|
||||
buffer += escapeExpression(stack1);
|
||||
return buffer;
|
||||
});
|
||||
templates['tool_link'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
|
||||
var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
|
||||
return "<a class=\""
|
||||
+ escapeExpression(((helper = (helper = helpers.id || (depth0 != null ? depth0.id : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"id","hash":{},"data":data}) : helper)))
|
||||
+ " tool-link\" href=\""
|
||||
+ escapeExpression(((helper = (helper = helpers.link || (depth0 != null ? depth0.link : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"link","hash":{},"data":data}) : helper)))
|
||||
+ "\" target=\""
|
||||
+ escapeExpression(((helper = (helper = helpers.target || (depth0 != null ? depth0.target : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"target","hash":{},"data":data}) : helper)))
|
||||
+ "\" minsizehint=\""
|
||||
+ escapeExpression(((helper = (helper = helpers.min_width || (depth0 != null ? depth0.min_width : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"min_width","hash":{},"data":data}) : helper)))
|
||||
+ "\">"
|
||||
+ escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"name","hash":{},"data":data}) : helper)))
|
||||
+ "</a> "
|
||||
+ escapeExpression(((helper = (helper = helpers.description || (depth0 != null ? depth0.description : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"description","hash":{},"data":data}) : helper)));
|
||||
},"useData":true});
|
||||
})();
|
||||
@@ -1,20 +1,11 @@
|
||||
(function() {
|
||||
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
templates['tool_search'] = 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 += "<input type=\"text\" name=\"query\" value=\"";
|
||||
if (stack1 = helpers.search_hint_string) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.search_hint_string; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
|
||||
buffer += escapeExpression(stack1)
|
||||
+ "\" id=\"tool-search-query\" autocomplete=\"off\" class=\"search-query parent-width\" />\n<a id=\"search-clear-btn\" title=\"clear search (esc)\"> </a>\n<img src=\"";
|
||||
if (stack1 = helpers.spinner_url) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
|
||||
else { stack1 = depth0.spinner_url; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
|
||||
buffer += escapeExpression(stack1)
|
||||
templates['tool_search'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
|
||||
var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
|
||||
return "<input type=\"text\" name=\"query\" value=\""
|
||||
+ escapeExpression(((helper = (helper = helpers.search_hint_string || (depth0 != null ? depth0.search_hint_string : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"search_hint_string","hash":{},"data":data}) : helper)))
|
||||
+ "\" id=\"tool-search-query\" autocomplete=\"off\" class=\"search-query parent-width\" />\n<a id=\"search-clear-btn\" title=\"clear search (esc)\"> </a>\n<img src=\""
|
||||
+ escapeExpression(((helper = (helper = helpers.spinner_url || (depth0 != null ? depth0.spinner_url : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"spinner_url","hash":{},"data":data}) : helper)))
|
||||
+ "\" id=\"search-spinner\" class=\"search-spinner\"/>";
|
||||
return buffer;
|
||||
});
|
||||
},"useData":true});
|
||||
})();
|
||||
@@ -12,7 +12,6 @@
|
||||
url : '',
|
||||
paramname : 'content',
|
||||
maxfilesize : 2048,
|
||||
maxfilenumber : 50,
|
||||
dragover : function() {},
|
||||
dragleave : function() {},
|
||||
announce : function() {},
|
||||
@@ -129,9 +128,6 @@
|
||||
// add files to queue
|
||||
for (var i = 0; i < files.length; i++)
|
||||
{
|
||||
// check
|
||||
if(queue_length >= opts.maxfilenumber)
|
||||
break;
|
||||
|
||||
// new identifier
|
||||
var index = String(queue_index++);
|
||||
|
||||
@@ -6,6 +6,18 @@
|
||||
// dependencies
|
||||
define(["libs/underscore"], function(_) {
|
||||
|
||||
/** Traverse through json
|
||||
*/
|
||||
function deepeach(dict, callback) {
|
||||
for (var i in dict) {
|
||||
var d = dict[i];
|
||||
if (d && typeof(d) == "object") {
|
||||
callback(d);
|
||||
deepeach(d, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize/escape a string
|
||||
* @param{String} content - Content to be sanitized
|
||||
@@ -14,6 +26,40 @@ function sanitize(content) {
|
||||
return $('<div/>').text(content).html();
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate atomic values or list of values
|
||||
* usually used for selectable options
|
||||
* @param{String} value - Value or list to be validated
|
||||
*/
|
||||
function validate (value) {
|
||||
if (!(value instanceof Array)) {
|
||||
value = [value];
|
||||
}
|
||||
for (var i in value) {
|
||||
if (['__null__', '__undefined__', 'None', null, undefined].indexOf(value[i]) > -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert list to pretty string
|
||||
* @param{String} lst - List of strings to be converted in human readable list sentence
|
||||
*/
|
||||
function textify(lst) {
|
||||
var lst = lst.toString();
|
||||
if (lst) {
|
||||
lst = lst.replace(/,/g, ', ');
|
||||
var pos = lst.lastIndexOf(', ');
|
||||
if (pos != -1) {
|
||||
lst = lst.substr(0, pos) + ' or ' + lst.substr(pos+1);
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Request handler for GET
|
||||
* @param{String} url - Url request is made to
|
||||
@@ -65,7 +111,7 @@ function request (options) {
|
||||
} else {
|
||||
ajaxConfig.url += '&';
|
||||
}
|
||||
ajaxConfig.url = ajaxConfig.url + $.param(ajaxConfig.data);
|
||||
ajaxConfig.url = ajaxConfig.url + $.param(ajaxConfig.data, true);
|
||||
ajaxConfig.data = null;
|
||||
} else {
|
||||
ajaxConfig.dataType = 'json';
|
||||
@@ -212,7 +258,10 @@ return {
|
||||
time: time,
|
||||
wrap: wrap,
|
||||
request: request,
|
||||
sanitize: sanitize
|
||||
sanitize: sanitize,
|
||||
textify: textify,
|
||||
validate: validate,
|
||||
deepeach: deepeach
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@ require(
|
||||
[
|
||||
// load js libraries
|
||||
'utils/utils',
|
||||
'mvc/ui/icon-button',
|
||||
'libs/jquery/jquery.event.drag',
|
||||
'libs/jquery/jquery.event.hover',
|
||||
'libs/jquery/jquery.mousewheel',
|
||||
@@ -32,9 +31,8 @@ require(
|
||||
});
|
||||
|
||||
// trackster viewer
|
||||
define( ["libs/underscore", "base", "viz/trackster/tracks", "viz/visualization"],
|
||||
function(_, base, tracks, visualization) {
|
||||
|
||||
define( ["libs/underscore", "base", "viz/trackster/tracks", "viz/visualization", "mvc/ui/icon-button" ],
|
||||
function(_, base, tracks, visualization, mod_icon_btn) {
|
||||
/**
|
||||
* User interface controls for trackster
|
||||
*/
|
||||
|
||||
+6
-1
@@ -7,8 +7,13 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-bower-install-simple": "^1.1.0",
|
||||
"grunt-contrib-copy": "^0.5.0",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"grunt-exec": "^0.4.6"
|
||||
"grunt-exec": "^0.4.6",
|
||||
"grunt-webpack": "^1.0.8",
|
||||
"i18n-webpack-plugin": "^0.2.7",
|
||||
"webpack": "^1.5.1",
|
||||
"webpack-dev-server": "^1.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<registration converters_path="lib/galaxy/datatypes/converters" display_path="display_applications">
|
||||
<datatype extension="ab1" type="galaxy.datatypes.binary:Ab1" mimetype="application/octet-stream" display_in_upload="true" description="A binary sequence file in 'ab1' format with a '.ab1' file extension. You must manually select this 'File Format' when uploading the file." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Ab1"/>
|
||||
<datatype extension="afg" type="galaxy.datatypes.assembly:Amos" display_in_upload="false" />
|
||||
<datatype extension="arff" type="galaxy.datatypes.text:Arff" mimetype="text/plain" display_in_upload="True" />
|
||||
<datatype extension="asn1" type="galaxy.datatypes.data:GenericAsn1" mimetype="text/plain" display_in_upload="true" />
|
||||
<datatype extension="asn1-binary" type="galaxy.datatypes.binary:GenericAsn1Binary" mimetype="application/octet-stream" display_in_upload="true" />
|
||||
<datatype extension="axt" type="galaxy.datatypes.sequence:Axt" display_in_upload="true" description="blastz pairwise alignment format. Each alignment block in an axt file contains three lines: a summary line and 2 sequence lines. Blocks are separated from one another by blank lines. The summary line contains chromosomal position and size information about the alignment. It consists of 9 required fields." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Axt"/>
|
||||
@@ -112,7 +113,7 @@
|
||||
<converter file="interval_to_bed12_converter.xml" target_datatype="bed12"/>
|
||||
<converter file="interval_to_bgzip_converter.xml" target_datatype="bgzip"/>
|
||||
<converter file="interval_to_tabix_converter.xml" target_datatype="tabix" depends_on="bgzip"/>
|
||||
<converter file="interval_to_bigwig_converter.xml" target_datatype="bigwig"/>
|
||||
<converter file="interval_to_bigwig_converter.xml" target_datatype="bigwig"/>
|
||||
<!-- <display file="ucsc/interval_as_bed.xml" inherit="True" /> -->
|
||||
<display file="ensembl/ensembl_interval_as_bed.xml" inherit="True"/>
|
||||
<display file="gbrowse/gbrowse_interval_as_bed.xml" inherit="True"/>
|
||||
@@ -139,6 +140,9 @@
|
||||
<datatype extension="rgb" type="galaxy.datatypes.images:Rgb" mimetype="image/rgb"/>
|
||||
<datatype extension="pbm" type="galaxy.datatypes.images:Pbm" mimetype="image/pbm"/>
|
||||
<datatype extension="pgm" type="galaxy.datatypes.images:Pgm" mimetype="image/pgm"/>
|
||||
<datatype extension="rna_eps" type="galaxy.datatypes.sequence:RNADotPlotMatrix" mimetype="image/eps" display_in_upload="True"/>
|
||||
<datatype extension="searchgui_archive" type="galaxy.datatypes.binary:CompressedArchive" subclass="True" display_in_upload="True"/>
|
||||
<datatype extension="peptideshaker_archive" type="galaxy.datatypes.binary:CompressedArchive" subclass="True" display_in_upload="True"/>
|
||||
<datatype extension="eps" type="galaxy.datatypes.images:Eps" mimetype="image/eps"/>
|
||||
<datatype extension="rast" type="galaxy.datatypes.images:Rast" mimetype="image/rast"/>
|
||||
<datatype extension="laj" type="galaxy.datatypes.images:Laj"/>
|
||||
@@ -150,11 +154,11 @@
|
||||
<datatype extension="mafcustomtrack" type="galaxy.datatypes.sequence:MafCustomTrack">
|
||||
<display file="ucsc/maf_customtrack.xml" />
|
||||
</datatype>
|
||||
<datatype extension="encodepeak" type="galaxy.datatypes.interval:ENCODEPeak" display_in_upload="True">
|
||||
<converter file="encodepeak_to_tabix_converter.xml" target_datatype="tabix" depends_on="bgzip"/>
|
||||
<converter file="encodepeak_to_bgzip_converter.xml" target_datatype="bgzip"/>
|
||||
<converter file="bed_gff_or_vcf_to_bigwig_converter.xml" target_datatype="bigwig"/>
|
||||
</datatype>
|
||||
<datatype extension="encodepeak" type="galaxy.datatypes.interval:ENCODEPeak" display_in_upload="True">
|
||||
<converter file="encodepeak_to_tabix_converter.xml" target_datatype="tabix" depends_on="bgzip"/>
|
||||
<converter file="encodepeak_to_bgzip_converter.xml" target_datatype="bgzip"/>
|
||||
<converter file="bed_gff_or_vcf_to_bigwig_converter.xml" target_datatype="bigwig"/>
|
||||
</datatype>
|
||||
<datatype extension="pdf" type="galaxy.datatypes.images:Pdf" mimetype="application/pdf"/>
|
||||
<datatype extension="pileup" type="galaxy.datatypes.tabular:Pileup" display_in_upload="true">
|
||||
<converter file="interval_to_bgzip_converter.xml" target_datatype="bgzip"/>
|
||||
@@ -170,17 +174,23 @@
|
||||
<datatype extension="qual454" type="galaxy.datatypes.qualityscore:QualityScore454" display_in_upload="true"/>
|
||||
<datatype extension="Roadmaps" type="galaxy.datatypes.assembly:Roadmaps" display_in_upload="false"/>
|
||||
<datatype extension="sam" type="galaxy.datatypes.tabular:Sam" display_in_upload="true">
|
||||
<converter file="sam_to_bam.xml" target_datatype="bam"/>
|
||||
<converter file="sam_to_bigwig_converter.xml" target_datatype="bigwig"/>
|
||||
</datatype>
|
||||
<converter file="sam_to_bam.xml" target_datatype="bam"/>
|
||||
<converter file="sam_to_bigwig_converter.xml" target_datatype="bigwig"/>
|
||||
</datatype>
|
||||
<datatype extension="scf" type="galaxy.datatypes.binary:Scf" mimetype="application/octet-stream" display_in_upload="true" description="A binary sequence file in 'scf' format with a '.scf' file extension. You must manually select this 'File Format' when uploading the file." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Scf"/>
|
||||
<datatype extension="Sequences" type="galaxy.datatypes.assembly:Sequences" display_in_upload="false"/>
|
||||
<datatype extension="snpeffdb" type="galaxy.datatypes.text:SnpEffDb" display_in_upload="True"/>
|
||||
<datatype extension="snpsiftdbnsfp" type="galaxy.datatypes.txt:SnpSiftDbNSFP" display_in_upload="True"/>
|
||||
<datatype extension="dbnsfp.tabular" type="galaxy.datatypes.tabular:Tabular" subclass="True" display_in_upload="True">
|
||||
<converter file="tabular_to_dbnsfp.xml" target_datatype="snpsiftdbnsfp"/>
|
||||
</datatype>
|
||||
<datatype extension="sff" type="galaxy.datatypes.binary:Sff" mimetype="application/octet-stream" display_in_upload="true" description="A binary file in 'Standard Flowgram Format' with a '.sff' file extension." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Sff"/>
|
||||
<datatype extension="svg" type="galaxy.datatypes.images:Image" mimetype="image/svg+xml"/>
|
||||
<datatype extension="taxonomy" type="galaxy.datatypes.tabular:Taxonomy" display_in_upload="true"/>
|
||||
<datatype extension="tabular" type="galaxy.datatypes.tabular:Tabular" display_in_upload="true" description="Any data in tab delimited format (tabular)." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Tabular_.28tab_delimited.29"/>
|
||||
<datatype extension="twobit" type="galaxy.datatypes.binary:TwoBit" mimetype="application/octet-stream" display_in_upload="true"/>
|
||||
<datatype extension="sqlite" type="galaxy.datatypes.binary:SQlite" mimetype="application/octet-stream" display_in_upload="true"/>
|
||||
<datatype extension="sqlite" type="galaxy.datatypes.binary:SQlite" mimetype="application/octet-stream" display_in_upload="true"/>
|
||||
<datatype extension="gemini.sqlite" type="galaxy.datatypes.binary:GeminiSQLite" mimetype="application/octet-stream" display_in_upload="True" />
|
||||
<datatype extension="txt" type="galaxy.datatypes.data:Text" display_in_upload="true" description="Any text file." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Plain_text"/>
|
||||
<datatype extension="linecount" type="galaxy.datatypes.data:LineCount" display_in_upload="false"/>
|
||||
<datatype extension="memexml" type="galaxy.datatypes.xml:MEMEXml" mimetype="application/xml" display_in_upload="true"/>
|
||||
@@ -270,6 +280,7 @@
|
||||
-->
|
||||
<sniffer type="galaxy.datatypes.tabular:Vcf"/>
|
||||
<sniffer type="galaxy.datatypes.binary:TwoBit"/>
|
||||
<sniffer type="galaxy.datatypes.binary:GeminiSQLite"/>
|
||||
<sniffer type="galaxy.datatypes.binary:SQlite"/>
|
||||
<sniffer type="galaxy.datatypes.binary:Bam"/>
|
||||
<sniffer type="galaxy.datatypes.binary:Sff"/>
|
||||
@@ -298,8 +309,10 @@
|
||||
<sniffer type="galaxy.datatypes.data:Newick"/>
|
||||
<sniffer type="galaxy.datatypes.data:Nexus"/>
|
||||
<sniffer type="galaxy.datatypes.text:Obo"/>
|
||||
<sniffer type="galaxy.datatypes.text:Arff"/>
|
||||
<sniffer type="galaxy.datatypes.text:Ipynb"/>
|
||||
<sniffer type="galaxy.datatypes.text:Json"/>
|
||||
<sniffer type="galaxy.datatypes.sequence:RNADotPlotMatrix"/>
|
||||
<sniffer type="galaxy.datatypes.images:Jpg"/>
|
||||
<sniffer type="galaxy.datatypes.images:Png"/>
|
||||
<sniffer type="galaxy.datatypes.images:Tiff"/>
|
||||
|
||||
+199
-125
@@ -8,7 +8,7 @@
|
||||
|
||||
# Throughout this sample configuration file, except where stated otherwise,
|
||||
# uncommented values override the default if left unset, whereas commented
|
||||
# values are set to the default value. Relative paths are relative to the root
|
||||
# values are set to the default value. Relative paths are relative to the root
|
||||
# Galaxy directory.
|
||||
#
|
||||
# Examples of many of these options are explained in more detail in the wiki:
|
||||
@@ -42,7 +42,8 @@ use_threadpool = True
|
||||
# Number of threads in the web server thread pool.
|
||||
#threadpool_workers = 10
|
||||
|
||||
# Set the number of seconds a thread can work before you should kill it (assuming it will never finish) to 3 hours.
|
||||
# Set the number of seconds a thread can work before you should kill it
|
||||
# (assuming it will never finish) to 3 hours. Default is 600 (10 minutes).
|
||||
threadpool_kill_thread_limit = 10800
|
||||
|
||||
# ---- Filters --------------------------------------------------------------
|
||||
@@ -117,10 +118,10 @@ paste.app_factory = galaxy.web.buildapp:app_factory
|
||||
#database_query_profiling_proxy = False
|
||||
|
||||
# By default, Galaxy will use the same database to track user data and
|
||||
# tool shed install data. There are many situtations in which it is
|
||||
# tool shed install data. There are many situtations in which it is
|
||||
# valuable to seperate these - for instance bootstrapping fresh Galaxy
|
||||
# instances with pretested installs. The following optin can be used to
|
||||
# separate the tool shed install database (all other options listed above
|
||||
# instances with pretested installs. The following optin can be used to
|
||||
# separate the tool shed install database (all other options listed above
|
||||
# but prefixed with install_ are also available).
|
||||
#install_database_connection = sqlite:///./database/universe.sqlite?isolation_level=IMMEDIATE
|
||||
|
||||
@@ -138,26 +139,29 @@ paste.app_factory = galaxy.web.buildapp:app_factory
|
||||
# config/tool_conf.xml does not exist).
|
||||
#tool_config_file = config/tool_conf.xml,config/shed_tool_conf.xml
|
||||
|
||||
# Enable / disable checking if any tools defined in the above non-shed tool_config_files
|
||||
# (i.e., tool_conf.xml) have been migrated from the Galaxy code distribution to the Tool
|
||||
# Shed. This setting should generally be set to False only for development Galaxy environments
|
||||
# that are often rebuilt from scratch where migrated tools do not need to be available in the
|
||||
# Galaxy tool panel. If the following setting remains commented, the default setting will be True.
|
||||
# Enable / disable checking if any tools defined in the above non-shed
|
||||
# tool_config_files (i.e., tool_conf.xml) have been migrated from the Galaxy
|
||||
# code distribution to the Tool Shed. This setting should generally be set to
|
||||
# False only for development Galaxy environments that are often rebuilt from
|
||||
# scratch where migrated tools do not need to be available in the Galaxy tool
|
||||
# panel. If the following setting remains commented, the default setting will
|
||||
# be True.
|
||||
#check_migrate_tools = True
|
||||
|
||||
# Tool config maintained by tool migration scripts. If you use the migration
|
||||
# Tool config maintained by tool migration scripts. If you use the migration
|
||||
# scripts to install tools that have been migrated to the tool shed upon a new
|
||||
# release, they will be added to this tool config file.
|
||||
#migrated_tools_config = migrated_tools_conf.xml
|
||||
#migrated_tools_config = config/migrated_tools_conf.xml
|
||||
|
||||
# File that contains the XML section and tool tags from all tool panel config
|
||||
# files integrated into a single file that defines the tool panel layout. This
|
||||
# file can be changed by the Galaxy administrator to alter the layout of the
|
||||
# tool panel. If not present, Galaxy will create it.
|
||||
# tool panel. If not present, Galaxy will create it.
|
||||
#integrated_tool_panel_config = integrated_tool_panel.xml
|
||||
|
||||
# Default path to the directory containing the tools defined in tool_conf.xml.
|
||||
# Other tool config files must include the tool_path as an attribute in the <toolbox> tag.
|
||||
# Other tool config files must include the tool_path as an attribute in the
|
||||
# <toolbox> tag.
|
||||
#tool_path = tools
|
||||
|
||||
# Path to the directory in which tool dependencies are placed. This is used by
|
||||
@@ -172,33 +176,36 @@ paste.app_factory = galaxy.web.buildapp:app_factory
|
||||
# install from in the admin interface (.sample used if default does not exist).
|
||||
#tool_sheds_config_file = config/tool_sheds_conf.xml
|
||||
|
||||
# If the following option is set to True - Galaxy will monitor individual tools
|
||||
# and tool directories specified in tool_conf.xml for changes and reload these
|
||||
# tools. Watchdog must be installed and available to Galaxy to use this option.
|
||||
# See https://pypi.python.org/pypi/watchdog.
|
||||
# Enable monitoring of tools and tool directories listed in any tool config file
|
||||
# specified in tool_config_file option. If changes are found, tools are
|
||||
# automatically reloaded.
|
||||
# Watchdog ( https://pypi.python.org/pypi/watchdog ) must be installed and
|
||||
# available to Galaxy to use this option.
|
||||
#watch_tools = False
|
||||
|
||||
# Enable automatic polling of relative tool sheds to see if any updates
|
||||
# are available for installed repositories. Ideally only one Galaxy
|
||||
# are available for installed repositories. Ideally only one Galaxy
|
||||
# server process should be able to check for repository updates. The
|
||||
# setting for hours_between_check should be an integer between 1 and 24.
|
||||
#enable_tool_shed_check = False
|
||||
#hours_between_check = 12
|
||||
|
||||
# Enable use of an in-memory registry with bi-directional relationships
|
||||
# between repositories (i.e., in addition to lists of dependencies for a
|
||||
# repository, keep an in-memory registry of dependent items for each repository.
|
||||
# Enable use of an in-memory registry with bi-directional relationships between
|
||||
# repositories (i.e., in addition to lists of dependencies for a repository,
|
||||
# keep an in-memory registry of dependent items for each repository.
|
||||
#manage_dependency_relationships = False
|
||||
|
||||
# XML config file that contains data table entries for the
|
||||
# ToolDataTableManager. This file is manually # maintained by the Galaxy
|
||||
# ToolDataTableManager. This file is manually # maintained by the Galaxy
|
||||
# administrator (.sample used if default does not exist).
|
||||
#tool_data_table_config_path = config/tool_data_table_conf.xml
|
||||
|
||||
# XML config file that contains additional data table entries for the ToolDataTableManager. This file
|
||||
# is automatically generated based on the current installed tool shed repositories that contain valid
|
||||
# tool_data_table_conf.xml.sample files. At the time of installation, these entries are automatically
|
||||
# added to the following file, which is parsed and applied to the ToolDataTableManager at server start up.
|
||||
# XML config file that contains additional data table entries for the
|
||||
# ToolDataTableManager. This file is automatically generated based on the
|
||||
# current installed tool shed repositories that contain valid
|
||||
# tool_data_table_conf.xml.sample files. At the time of installation, these
|
||||
# entries are automatically added to the following file, which is parsed and
|
||||
# applied to the ToolDataTableManager at server start up.
|
||||
#shed_tool_data_table_config = config/shed_tool_data_table_conf.xml
|
||||
|
||||
# Directory where data used by tools is located, see the samples in that
|
||||
@@ -213,24 +220,26 @@ paste.app_factory = galaxy.web.buildapp:app_factory
|
||||
#len_file_path = tool-data/shared/ucsc/chrom
|
||||
|
||||
# Datatypes config file(s), defines what data (file) types are available in
|
||||
# Galaxy (.sample is used if default does not exist). If a datatype appears in
|
||||
# multiple files - the last definition is used (though the first sniffer is used
|
||||
# Galaxy (.sample is used if default does not exist). If a datatype appears in
|
||||
# multiple files, the last definition is used (though the first sniffer is used
|
||||
# so limit sniffer definitions to one file).
|
||||
#datatypes_config_file = config/datatypes_conf.xml
|
||||
|
||||
# Disable the 'Auto-detect' option for file uploads
|
||||
#datatypes_disable_auto = False
|
||||
|
||||
# Visualizations config directory: where to look for individual visualization plugins.
|
||||
# The path is relative to the Galaxy root dir. To use an absolute path begin the path
|
||||
# with '/'. This is a comma separated list. Defaults to "config/plugins/visualizations".
|
||||
# Visualizations config directory: where to look for individual visualization
|
||||
# plugins. The path is relative to the Galaxy root dir. To use an absolute
|
||||
# path begin the path with '/'. This is a comma separated list.
|
||||
# Defaults to "config/plugins/visualizations".
|
||||
#visualization_plugins_directory = config/plugins/visualizations
|
||||
|
||||
# Interactive environment plugins root directory: where to look for interactive environment
|
||||
# plugins. By default none will be loaded. Set to config/plugins/interactive_environments
|
||||
# to load Galaxy's stock plugins (currently just IPython). These will require Docker
|
||||
# to be configured and have security considerations so proceed with caution.
|
||||
#interactive_environment_plugins_directory =
|
||||
# Interactive environment plugins root directory: where to look for interactive
|
||||
# environment plugins. By default none will be loaded. Set to
|
||||
# config/plugins/interactive_environments to load Galaxy's stock plugins
|
||||
# (currently just IPython). These will require Docker to be configured and
|
||||
# have security considerations, so proceed with caution.
|
||||
#interactive_environment_plugins_directory =
|
||||
|
||||
# Each job is given a unique empty directory as its current working directory.
|
||||
# This option defines in what parent directory those directories will be
|
||||
@@ -245,18 +254,20 @@ paste.app_factory = galaxy.web.buildapp:app_factory
|
||||
# used for the cache
|
||||
#template_cache_path = database/compiled_templates
|
||||
|
||||
# Citation related caching. Tool citations information maybe fetched from
|
||||
# external sources such as http://dx.doi.org/ by Galaxy - the following parameters
|
||||
# can be used to control the caching used to store this information.
|
||||
# Citation related caching. Tool citations information maybe fetched from
|
||||
# external sources such as http://dx.doi.org/ by Galaxy - the following
|
||||
# parameters can be used to control the caching used to store this information.
|
||||
#citation_cache_type = file
|
||||
#citation_cache_data_dir = database/citations/data
|
||||
#citation_cache_lock_dir = database/citations/lock
|
||||
|
||||
# External service types config file, defines what types of external_services configurations
|
||||
# are available in Galaxy (.sample is used if default does not exist).
|
||||
# External service types config file, defining what types of external_services
|
||||
# configurations are available in Galaxy (.sample is used if default does not
|
||||
# exist).
|
||||
#external_service_type_config_file = config/external_service_types_conf.xml
|
||||
|
||||
# Path to the directory containing the external_service_types defined in the config.
|
||||
# Path to the directory containing the external_service_types defined in the
|
||||
# config.
|
||||
#external_service_type_path = external_service_types
|
||||
|
||||
# Tools with a number of outputs not known until runtime can write these
|
||||
@@ -314,10 +325,11 @@ paste.app_factory = galaxy.web.buildapp:app_factory
|
||||
|
||||
# -- Mail and notification
|
||||
|
||||
# Galaxy sends mail for various things: Subscribing users to the mailing list
|
||||
# if they request it, emailing password resets, notification from the Galaxy
|
||||
# Sample Tracking system, reporting dataset errors, and sending activation emails.
|
||||
# To do this, it needs to send mail through an SMTP server, which you may define here (host:port).
|
||||
# Galaxy sends mail for various things: subscribing users to the mailing list
|
||||
# if they request it, password resets, notifications from the Galaxy Sample
|
||||
# Tracking system, reporting dataset errors, and sending activation emails.
|
||||
# To do this, it needs to send mail through an SMTP server, which you may
|
||||
# define here (host:port).
|
||||
# Galaxy will automatically try STARTTLS but will continue upon failure.
|
||||
#smtp_server = None
|
||||
|
||||
@@ -335,41 +347,44 @@ paste.app_factory = galaxy.web.buildapp:app_factory
|
||||
#mailing_join_addr = galaxy-announce-join@bx.psu.edu
|
||||
|
||||
# Datasets in an error state include a link to report the error. Those reports
|
||||
# will be sent to this address. Error reports are disabled if no address is set.
|
||||
# Also this email is shown as a contact to user in case of Galaxy misconfiguration and other events user may encounter.
|
||||
# will be sent to this address. Error reports are disabled if no address is
|
||||
# set. Also this email is shown as a contact to user in case of Galaxy
|
||||
# misconfiguration and other events user may encounter.
|
||||
#error_email_to = None
|
||||
|
||||
# Activation email is used as a sender ('from' field) for the account activation mail.
|
||||
# We recommend using string in the following format: Galaxy Project <galaxy-no-reply@example.com>
|
||||
# Activation email is used as a sender ('from' field) for the account
|
||||
# activation mail. We recommend using string in the following format:
|
||||
# Galaxy Project <galaxy-no-reply@example.com>
|
||||
#activation_email = None
|
||||
|
||||
# URL of the support resource for the galaxy instance. Used in activation emails.
|
||||
# URL of the support resource for the galaxy instance. Used in activation
|
||||
# emails.
|
||||
#instance_resource_url = http://wiki.galaxyproject.org/
|
||||
|
||||
# E-mail domains blacklist is used for filtering out users that are using disposable email address
|
||||
# during the registration. If their address domain matches any domain in the BL they are refused the registration.
|
||||
# E-mail domains blacklist is used for filtering out users that are using
|
||||
# disposable email address during the registration. If their address domain
|
||||
# matches any domain in the blacklist, they are refused the registration.
|
||||
#blacklist_file = config/disposable_email_blacklist.conf
|
||||
|
||||
# Registration warning message is used to discourage people from registering multiple accounts. Applies mostly for the main Galaxy instance.
|
||||
# Registration warning message is used to discourage people from registering
|
||||
# multiple accounts. Applies mostly for the main Galaxy instance.
|
||||
# If no message specified the warning box will not be shown.
|
||||
#registration_warning_message = Please register only one account - we provide this service free of charge and have limited computational resources. Multi-accounts are tracked and will be subjected to account termination and data deletion.
|
||||
|
||||
# When users opt to reset passwords, new ones are created, this option
|
||||
# specifies the length of these passwords.
|
||||
#reset_password_length = 15
|
||||
|
||||
|
||||
# -- Account activation
|
||||
|
||||
# This is user account activation feature global flag. If set to "False" the rest of the Account
|
||||
# activation configuration is ignored and user activation is disabled (a.k.a. accounts are active since registration).
|
||||
# Note the activation is also not working in case the smtp server is not defined.
|
||||
# User account activation feature global flag. If set to "False", the rest of
|
||||
# the Account activation configuration is ignored and user activation is
|
||||
# disabled (i.e. accounts are active since registration).
|
||||
# The activation is also not working in case the SMTP server is not defined.
|
||||
#user_activation_on = False
|
||||
|
||||
# Activation grace period. Activation is not forced (login is not disabled) until
|
||||
# grace period has passed. Users under grace period can't run jobs (see inactivity_box_content).
|
||||
# In hours. Default is 3. Enter 0 to disable grace period.
|
||||
# Users with OpenID logins have grace period forever.
|
||||
# Activation grace period (in hours). Activation is not forced (login is not
|
||||
# disabled) until grace period has passed. Users under grace period can't run
|
||||
# jobs (see inactivity_box_content). Default is 3. Enter 0 to disable grace
|
||||
# period.
|
||||
# Users with OpenID logins have grace period forever.
|
||||
#activation_grace_period = 0
|
||||
|
||||
# Used for warning box for inactive accounts (unable to run jobs).
|
||||
@@ -377,10 +392,17 @@ paste.app_factory = galaxy.web.buildapp:app_factory
|
||||
#inactivity_box_content = Your account has not been activated yet. Feel free to browse around and see what's available, but you won't be able to upload data or run jobs until you have verified your email address.
|
||||
|
||||
|
||||
|
||||
# Galaxy Session Timeout
|
||||
# This provides a timeout (in minutes) after which a user will have to log back in.
|
||||
# A duration of 0 disables this feature.
|
||||
#session_duration = 0
|
||||
|
||||
|
||||
# -- Analytics
|
||||
|
||||
# You can enter tracking code here to track visitor's behavior
|
||||
# through your Google Analytics account. Example: UA-XXXXXXXX-Y
|
||||
# through your Google Analytics account. Example: UA-XXXXXXXX-Y
|
||||
#ga_code = None
|
||||
|
||||
# -- Display sites
|
||||
@@ -403,17 +425,19 @@ paste.app_factory = galaxy.web.buildapp:app_factory
|
||||
# them).
|
||||
#display_servers = hgw1.cse.ucsc.edu,hgw2.cse.ucsc.edu,hgw3.cse.ucsc.edu,hgw4.cse.ucsc.edu,hgw5.cse.ucsc.edu,hgw6.cse.ucsc.edu,hgw7.cse.ucsc.edu,hgw8.cse.ucsc.edu,lowepub.cse.ucsc.edu
|
||||
|
||||
# To disable the old-style display applications that are hardcoded into datatype classes,
|
||||
# set enable_old_display_applications = False.
|
||||
# This may be desirable due to using the new-style, XML-defined, display applications that
|
||||
# have been defined for many of the datatypes that have the old-style.
|
||||
# To disable the old-style display applications that are hardcoded into
|
||||
# datatype classes, set enable_old_display_applications = False.
|
||||
# This may be desirable due to using the new-style, XML-defined, display
|
||||
# applications that have been defined for many of the datatypes that have the
|
||||
# old-style.
|
||||
# There is also a potential security concern with the old-style applications,
|
||||
# where a malicious party could provide a link that appears to reference the Galaxy server,
|
||||
# but contains a redirect to a third-party server, tricking a Galaxy user to access said
|
||||
# site.
|
||||
# where a malicious party could provide a link that appears to reference the
|
||||
# Galaxy server, but contains a redirect to a third-party server, tricking a
|
||||
# Galaxy user to access said site.
|
||||
#enable_old_display_applications = True
|
||||
|
||||
# -- Next gen LIMS interface on top of existing Galaxy Sample/Request management code.
|
||||
# -- Next gen LIMS interface on top of existing Galaxy Sample/Request
|
||||
# management code.
|
||||
|
||||
use_nglims = False
|
||||
nglims_config_file = tool-data/nglims.yaml
|
||||
@@ -430,18 +454,20 @@ nglims_config_file = tool-data/nglims.yaml
|
||||
|
||||
# Format string used when showing date and time information.
|
||||
# The string may contain:
|
||||
# - the directives used by Python time.strftime() function (see http://docs.python.org/2/library/time.html#time.strftime ),
|
||||
# - the directives used by Python time.strftime() function (see
|
||||
# http://docs.python.org/2/library/time.html#time.strftime ),
|
||||
# - $locale (complete format string for the server locale),
|
||||
# - $iso8601 (complete format string as specified by ISO 8601 international standard).
|
||||
# - $iso8601 (complete format string as specified by ISO 8601 international
|
||||
# standard).
|
||||
# pretty_datetime_format = $locale (UTC)
|
||||
|
||||
# URL (with schema http/https) of the Galaxy instance as accessible within your local
|
||||
# network - if specified used as a default by pulsar file staging and IPython docker
|
||||
# container for communicating back with Galaxy via the API.
|
||||
# URL (with schema http/https) of the Galaxy instance as accessible within your
|
||||
# local network - if specified used as a default by pulsar file staging and
|
||||
# IPython Docker container for communicating back with Galaxy via the API.
|
||||
#galaxy_infrastructure_url = http://localhost:8080
|
||||
|
||||
# The URL of the page to display in Galaxy's middle pane when loaded. This can be
|
||||
# an absolute or relative URL.
|
||||
# The URL of the page to display in Galaxy's middle pane when loaded. This can
|
||||
# be an absolute or relative URL.
|
||||
#welcome_url = /static/welcome.html
|
||||
|
||||
# The URL linked by the "Galaxy/brand" text.
|
||||
@@ -472,7 +498,7 @@ nglims_config_file = tool-data/nglims.yaml
|
||||
# The URL linked by the "Galaxy Q&A" link in the "Help" menu
|
||||
# The Galaxy Q&A site is under development; when the site is done, this URL
|
||||
# will be set and uncommented.
|
||||
#qa_url =
|
||||
#qa_url =
|
||||
|
||||
# Serve static content, which must be enabled if you're not serving it via a
|
||||
# proxy server. These options should be self explanatory and so are not
|
||||
@@ -529,9 +555,9 @@ nglims_config_file = tool-data/nglims.yaml
|
||||
# will cause modern web browsers to not allow Galaxy to be embedded in
|
||||
# the frames of web applications hosted at other hosts - this can help
|
||||
# prevent a class of attack called clickjacking
|
||||
# (https://www.owasp.org/index.php/Clickjacking). If you configure a
|
||||
# (https://www.owasp.org/index.php/Clickjacking). If you configure a
|
||||
# proxy in front of Galaxy - please ensure this header remains intact
|
||||
# to protect your users. Uncomment and leave empty to not set the
|
||||
# to protect your users. Uncomment and leave empty to not set the
|
||||
# `X-Frame-Options` header.
|
||||
#x_frame_options = SAMEORIGIN
|
||||
|
||||
@@ -547,10 +573,10 @@ nglims_config_file = tool-data/nglims.yaml
|
||||
#nginx_upload_path = False
|
||||
|
||||
# Have Galaxy manage dynamic proxy component for routing requests to other
|
||||
# services based on Galaxy's session cookie. It will attempt to do this by
|
||||
# services based on Galaxy's session cookie. It will attempt to do this by
|
||||
# default though you do need to install node+npm and do an npm install from
|
||||
# `lib/galaxy/web/proxy/js`. It is generally more robust to configure this
|
||||
# externally managing it however Galaxy is managed. If True Galaxy will only
|
||||
# `lib/galaxy/web/proxy/js`. It is generally more robust to configure this
|
||||
# externally managing it however Galaxy is managed. If True Galaxy will only
|
||||
# launch the proxy if it is actually going to be used (e.g. for IPython).
|
||||
#dynamic_proxy_manage=True
|
||||
|
||||
@@ -566,7 +592,8 @@ nglims_config_file = tool-data/nglims.yaml
|
||||
# Enable verbose debugging of Galaxy-managed dynamic proxy.
|
||||
#dynamic_proxy_debug=False
|
||||
|
||||
# The dynamic proxy is proxied by an external proxy (e.g. apache frontend to nodejs to wrap connections in SSL)
|
||||
# The dynamic proxy is proxied by an external proxy (e.g. apache frontend to
|
||||
# nodejs to wrap connections in SSL).
|
||||
#dynamic_proxy_external_proxy=False
|
||||
|
||||
# -- Logging and Debugging
|
||||
@@ -584,28 +611,28 @@ nglims_config_file = tool-data/nglims.yaml
|
||||
# Turn on logging of application events and some user events to the database.
|
||||
#log_events = True
|
||||
|
||||
# Turn on logging of user actions to the database. Actions currently logged are
|
||||
# grid views, tool searches, and use of "recently" used tools menu. The
|
||||
# Turn on logging of user actions to the database. Actions currently logged
|
||||
# are grid views, tool searches, and use of "recently" used tools menu. The
|
||||
# log_events and log_actions functionality will eventually be merged.
|
||||
#log_actions = True
|
||||
|
||||
# Sanitize All HTML Tool Output By default, all tool output served as
|
||||
# 'text/html' will be sanitized thoroughly. This can be disabled if
|
||||
# you have special tools that require unaltered output. WARNING:
|
||||
# Disabling this does make the Galxy instance susceptible to XSS
|
||||
# attacks initiated by your users.
|
||||
# Sanitize all HTML tool output. By default, all tool output served as
|
||||
# 'text/html' will be sanitized thoroughly. This can be disabled if you have
|
||||
# special tools that require unaltered output. WARNING: disabling this does
|
||||
# make the Galaxy instance susceptible to XSS attacks initiated by your users.
|
||||
#sanitize_all_html = True
|
||||
|
||||
# By default Galaxy will serve non-HTML tool output that may potentially
|
||||
# contain browser executable JavaScript content as plain text. This will for
|
||||
# contain browser executable JavaScript content as plain text. This will for
|
||||
# instance cause SVG datasets to not render properly and so may be disabled
|
||||
# by setting the following option to True.
|
||||
#serve_xss_vulnerable_mimetypes = False
|
||||
|
||||
# Set the following to True to use ipython nbconvert to build HTML from IPython
|
||||
# notebooks in Galaxy histories. This process may allow users to execute arbitrary
|
||||
# code or serve arbitrary HTML. If enabled ipython must be available and on Galaxy's
|
||||
# PATH, to do this run `pip install jinja2 pygments ipython` in Galaxy's virtualenv.
|
||||
# Set the following to True to use IPython nbconvert to build HTML from IPython
|
||||
# notebooks in Galaxy histories. This process may allow users to execute
|
||||
# arbitrary code or serve arbitrary HTML. If enabled, IPython must be
|
||||
# available and on Galaxy's PATH, to do this run
|
||||
# `pip install jinja2 pygments ipython` in Galaxy's virtualenv.
|
||||
#trust_ipython_notebook_conversion = False
|
||||
|
||||
# Debug enables access to various config options useful for development and
|
||||
@@ -633,10 +660,6 @@ use_interactive = True
|
||||
# lot of CPU.
|
||||
#use_heartbeat = False
|
||||
|
||||
# Enable the memory debugging interface (careful, negatively impacts server
|
||||
# performance).
|
||||
#use_memdump = False
|
||||
|
||||
# -- Data Libraries
|
||||
|
||||
# These library upload options are described in much more detail in the wiki:
|
||||
@@ -665,7 +688,7 @@ use_interactive = True
|
||||
# if testing shows that Galaxy is able to create files using these formats.
|
||||
# Specific formats can be disabled with this option, separate more than one
|
||||
# format with commas. Available formats are currently 'zip', 'gz', and 'bz2'.
|
||||
#disable_library_comptypes =
|
||||
#disable_library_comptypes =
|
||||
|
||||
# Some sequencer integration features in beta allow you to automatically
|
||||
# transfer datasets. This is done using a lightweight transfer manager which
|
||||
@@ -693,6 +716,8 @@ use_interactive = True
|
||||
# used by the algorithm that encodes and decodes these values. It can be any
|
||||
# string. If left unchanged, anyone could construct a cookie that would grant
|
||||
# them access to others' sessions.
|
||||
# One simple way to generate a value for this is with the shell command:
|
||||
# python -c 'import time; print time.time()' | md5sum | cut -f 1 -d ' '
|
||||
#id_secret = USING THE DEFAULT IS NOT SECURE!
|
||||
|
||||
# User authentication can be delegated to an upstream proxy server (usually
|
||||
@@ -713,6 +738,14 @@ use_interactive = True
|
||||
# *not* include 'HTTP_' at the beginning of the header name.
|
||||
#remote_user_header = HTTP_REMOTE_USER
|
||||
|
||||
# If use_remote_user is enabled, anyone who can log in to the Galaxy host may
|
||||
# impersonate any other user by simply sending the appropriate header. Thus a
|
||||
# secret shared between the upstream proxy server, and Galaxy is required.
|
||||
# If anyone other than the Galaxy user is using the server, then apache/nginx
|
||||
# should pass a value in the header 'GX_SECRET' that is identical to the one
|
||||
# below.
|
||||
#remote_user_secret = USING THE DEFAULT IS NOT SECURE!
|
||||
|
||||
# If use_remote_user is enabled, you can set this to a URL that will log your
|
||||
# users out.
|
||||
#remote_user_logout_href = None
|
||||
@@ -764,11 +797,38 @@ use_interactive = True
|
||||
# -- Beta features
|
||||
|
||||
# Use new tool form
|
||||
#toolform_upgrade = False
|
||||
#toolform_upgrade = True
|
||||
|
||||
# Use new tool form in workflow editor
|
||||
#workflow_toolform_upgrade = False
|
||||
|
||||
# Enable Galaxy to communicate directly with a sequencer
|
||||
#enable_sequencer_communication = False
|
||||
|
||||
# Separate tool command from rest of job script so tool dependencies
|
||||
# don't interfer with metadata generation (this will be the default
|
||||
# in a future release).
|
||||
#enable_beta_tool_command_isolation = False
|
||||
|
||||
|
||||
# Enable beta workflow modules that should not yet be considered part of Galaxy's
|
||||
# stable API.
|
||||
#enable_beta_workflow_modules = False
|
||||
|
||||
# Force usage of Galaxy's beta workflow scheduler under certain circumstances -
|
||||
# this workflow scheduling forces Galaxy to schedule workflows in the background
|
||||
# so initial submission of the workflows is signficantly sped up. This does
|
||||
# however force the user to refresh their history manually to see newly scheduled
|
||||
# steps (for "normal" workflows - steps are still scheduled far in advance of
|
||||
# them being queued and scheduling here doesn't refer to actual cluster job
|
||||
# scheduling).
|
||||
# Workflows containing more than the specified number of steps will always use
|
||||
# the Galaxy's beta workflow scheduling.
|
||||
#force_beta_workflow_scheduled_min_steps=250
|
||||
# Switch to using Galaxy's beta workflow scheduling for all workflows involving
|
||||
# ccollections.
|
||||
#force_beta_workflow_scheduled_for_collections=False
|
||||
|
||||
# Enable authentication via OpenID. Allows users to log in to their Galaxy
|
||||
# account by authenticating with an OpenID provider.
|
||||
#enable_openid = False
|
||||
@@ -781,8 +841,9 @@ use_interactive = True
|
||||
#api_allow_run_as = None
|
||||
|
||||
# Master key that allows many API admin actions to be used without actually
|
||||
# having a defined admin user in the database/config. Only set this if you need
|
||||
# to bootstrap Galaxy, you probably do not want to set this on public servers.
|
||||
# having a defined admin user in the database/config. Only set this if you
|
||||
# need to bootstrap Galaxy, you probably do not want to set this on public
|
||||
# servers.
|
||||
#master_api_key = changethis
|
||||
|
||||
# Enable tool tags (associating tools with tags). This has its own option
|
||||
@@ -790,7 +851,7 @@ use_interactive = True
|
||||
# large servers.
|
||||
#enable_tool_tags = False
|
||||
|
||||
# Enable a feature when running workflows. When enabled, default datasets
|
||||
# Enable a feature when running workflows. When enabled, default datasets
|
||||
# are selected for "Set at Runtime" inputs from the history such that the
|
||||
# same input will not be selected twice, unless there are more inputs than
|
||||
# compatible datasets in the history.
|
||||
@@ -826,7 +887,7 @@ use_interactive = True
|
||||
# File where Data Managers are configured (.sample used if default does not
|
||||
# exist)
|
||||
#data_manager_config_file = config/data_manager_conf.xml
|
||||
# File where Tool Shed based Data Managers are configured
|
||||
# File where Tool Shed based Data Managers are configured
|
||||
#shed_data_manager_config_file = config/shed_data_manager_conf.xml
|
||||
# Directory to store Data Manager based tool-data; defaults to tool_data_path
|
||||
#galaxy_data_manager_data_path = tool-data
|
||||
@@ -853,7 +914,8 @@ use_interactive = True
|
||||
# following option to True or False.
|
||||
#track_jobs_in_database = None
|
||||
|
||||
# This enables splitting of jobs into tasks, if specified by the particular tool config.
|
||||
# This enables splitting of jobs into tasks, if specified by the particular tool
|
||||
# config.
|
||||
# This is a new feature and not recommended for production servers yet.
|
||||
#use_tasked_jobs = False
|
||||
#local_task_queue_workers = 2
|
||||
@@ -904,7 +966,8 @@ use_interactive = True
|
||||
|
||||
# Optional file containing job resource data entry fields definition.
|
||||
# These fields will be presented to users in the tool forms and allow them to
|
||||
# overwrite default job resources such as number of processors, memory, and walltime.
|
||||
# overwrite default job resources such as number of processors, memory and
|
||||
# walltime.
|
||||
#job_resource_params_file = config/job_resource_params_conf.xml
|
||||
|
||||
# If using job concurrency limits (configured in job_config_file), several
|
||||
@@ -918,17 +981,27 @@ use_interactive = True
|
||||
#cache_user_job_count = False
|
||||
|
||||
# ToolBox filtering
|
||||
# Modules from lib/galaxy/tools/filters/ can be specified in the following lines.
|
||||
# tool_* filters will be applied for all users and can not be changed by them.
|
||||
# user_tool_* filters will be shown under user preferences and can be toogled
|
||||
# on and off by runtime
|
||||
#tool_filters
|
||||
#tool_label_filters
|
||||
#tool_section_filters
|
||||
|
||||
# Modules from lib/galaxy/tools/toolbox/filters/ can be specified in
|
||||
# the following lines. tool_* filters will be applied for all users
|
||||
# and can not be changed by them. user_tool_* filters will be shown
|
||||
# under user preferences and can be toogled on and off by
|
||||
# runtime. Example shown below are not real defaults (no custom
|
||||
# filters applied by defualt) but can be enabled with by renaming the
|
||||
# example.py.sample in the filters directory to example.py.
|
||||
|
||||
#tool_filters =
|
||||
#tool_label_filters =
|
||||
#tool_section_filters =
|
||||
#user_tool_filters = examples:restrict_upload_to_admins, examples:restrict_encode
|
||||
#user_tool_section_filters = examples:restrict_text
|
||||
#user_tool_label_filters = examples:restrict_upload_to_admins, examples:restrict_encode
|
||||
|
||||
# The base modules that are searched for modules as described above
|
||||
# can be modified and modules external to Galaxy can be searched by
|
||||
# modifying the following option.
|
||||
#toolbox_filter_base_modules = galaxy.tools.toolbox.filters,galaxy.tools.filters
|
||||
|
||||
# Galaxy Application Internal Message Queue
|
||||
|
||||
# Galaxy uses AMQP internally TODO more documentation on what for.
|
||||
@@ -945,11 +1018,12 @@ use_interactive = True
|
||||
|
||||
# ---- Galaxy External Message Queue -------------------------------------------------
|
||||
|
||||
# Galaxy uses AMQ protocol to receive messages from external sources like
|
||||
# bar code scanners. Galaxy has been tested against RabbitMQ AMQP implementation.
|
||||
# For Galaxy to receive messages from a message queue the RabbitMQ server has
|
||||
# to be set up with a user account and other parameters listed below. The 'host'
|
||||
# and 'port' fields should point to where the RabbitMQ server is running.
|
||||
# Galaxy uses Advanced Message Queuing Protocol (AMQP) to receive messages from
|
||||
# external sources like barcode scanners. Galaxy has been tested against
|
||||
# RabbitMQ AMQP implementation. For Galaxy to receive messages from a message
|
||||
# queue, the RabbitMQ server has to be set up with a user account and other
|
||||
# parameters listed below. The 'host' and 'port' fields should point to where
|
||||
# the RabbitMQ server is running.
|
||||
|
||||
[galaxy_amqp]
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -83,7 +83,7 @@ return Backbone.View.extend({
|
||||
Tools.addZoom({
|
||||
xAxis : this.xAxis,
|
||||
yAxis : this.yAxis,
|
||||
redraw : function() { self.redraw() },
|
||||
redraw : function() { self.redraw(); },
|
||||
svg : d3.select('#' + this.canvas_id)
|
||||
});
|
||||
},
|
||||
|
||||
@@ -108,7 +108,9 @@ return Backbone.View.extend({
|
||||
// append element
|
||||
var $main = $('<div/>');
|
||||
$main.append(Utils.wrap((new Ui.Label({ title : 'Provide a chart title:'})).$el));
|
||||
$main.append(Utils.wrap(this.title.$el));
|
||||
$main.append(this.title.$el);
|
||||
$main.append($('<div/>').addClass('ui-table-form-info').html('This title will appear in the list of \'Saved Visualizations\'. Charts are saved upon creation.'));
|
||||
$main.append(Utils.wrap());
|
||||
$main.append(Utils.wrap(this.types.$el));
|
||||
|
||||
// add tab
|
||||
@@ -189,7 +191,9 @@ return Backbone.View.extend({
|
||||
_refreshTitle: function() {
|
||||
var title = this.chart.get('title');
|
||||
this.portlet.title(title);
|
||||
this.title.value(title);
|
||||
if (this.title.value() != title) {
|
||||
this.title.value(title);
|
||||
}
|
||||
},
|
||||
|
||||
// refresh group
|
||||
|
||||
@@ -254,7 +254,9 @@ return Backbone.View.extend({
|
||||
if (key_text === undefined) {
|
||||
key_text = '';
|
||||
}
|
||||
this.group_key.value(key_text);
|
||||
if (key_text != this.group_key.value()) {
|
||||
this.group_key.value(key_text);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -99,7 +99,6 @@ return Backbone.View.extend({
|
||||
this.portlet = new Portlet.View({
|
||||
icon : 'fa-bar-chart-o',
|
||||
title: 'Viewport',
|
||||
scrollable: false,
|
||||
operations: {
|
||||
edit_button: new Ui.ButtonIcon({
|
||||
icon : 'fa-edit',
|
||||
|
||||
@@ -30,6 +30,14 @@ return Backbone.View.extend({
|
||||
// use full screen for viewer
|
||||
this._fullscreen(this.$el, 100);
|
||||
|
||||
// prevent window scrolling
|
||||
var initial_overflow = $('body').css('overflow');
|
||||
this.$el.on('mouseover', function() {
|
||||
$('body').css('overflow', 'hidden');
|
||||
}).on('mouseout', function() {
|
||||
$('body').css('overflow', initial_overflow);
|
||||
});
|
||||
|
||||
// create container element
|
||||
this._createContainer('div');
|
||||
|
||||
|
||||
@@ -31,9 +31,8 @@ ${h.js( 'libs/jquery/jquery',
|
||||
'libs/backbone/backbone',
|
||||
'libs/d3',
|
||||
'libs/handlebars.runtime',
|
||||
'mvc/ui',
|
||||
'jq-plugins/ui/peek-column-selector',
|
||||
'jq-plugins/ui/pagination',
|
||||
'ui/peek-column-selector',
|
||||
'ui/pagination',
|
||||
'mvc/visualization/visualization-model' )}
|
||||
|
||||
${h.javascript_link( root + 'plugins/visualizations/scatterplot/static/scatterplot-edit.js' )}
|
||||
|
||||
@@ -22,9 +22,13 @@ log_level = DEBUG
|
||||
# database_connection = postgres:///galaxy_test?user=postgres&password=postgres
|
||||
|
||||
# Where dataset files are saved
|
||||
file_path = database/files
|
||||
#file_path = database/files
|
||||
# Temporary storage for additional datasets, this should be shared through the cluster
|
||||
new_file_path = database/tmp
|
||||
#new_file_path = database/tmp
|
||||
|
||||
# Mako templates are compiled as needed and cached for reuse, this directory is
|
||||
# used for the cache
|
||||
#template_cache_path = database/compiled_templates/reports
|
||||
|
||||
# Session support (beaker)
|
||||
use_beaker_session = True
|
||||
@@ -60,10 +64,10 @@ mailing_join_addr = galaxy-user-join@bx.psu.edu
|
||||
use_new_layout = true
|
||||
|
||||
# Serving static files (needed if running standalone)
|
||||
static_enabled = True
|
||||
static_cache_time = 360
|
||||
static_dir = %(here)s/static/
|
||||
static_images_dir = %(here)s/static/images
|
||||
static_favicon_dir = %(here)s/static/favicon.ico
|
||||
static_scripts_dir = %(here)s/static/scripts/
|
||||
static_style_dir = %(here)s/static/june_2007_style/blue
|
||||
# static_enabled = True
|
||||
# static_cache_time = 360
|
||||
# static_dir = %(here)s/static/
|
||||
# static_images_dir = %(here)s/static/images
|
||||
# static_favicon_dir = %(here)s/static/favicon.ico
|
||||
# static_scripts_dir = %(here)s/static/scripts/
|
||||
# static_style_dir = %(here)s/static/june_2007_style/blue
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<tool file="data_source/yeastmine.xml" />
|
||||
<tool file="data_source/worm_modencode.xml" />
|
||||
<tool file="data_source/wormbase.xml" />
|
||||
<tool file="data_source/zebrafishmine.xml" />
|
||||
<tool file="data_source/eupathdb.xml" />
|
||||
<tool file="genomespace/genomespace_file_browser_prod.xml" />
|
||||
<tool file="genomespace/genomespace_importer.xml" />
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<tool file="data_source/worm_modencode.xml" />
|
||||
<tool file="data_source/wormbase.xml" />
|
||||
<tool file="data_source/wormbase_test.xml" />
|
||||
<tool file="data_source/zebrafishmine.xml" />
|
||||
<tool file="data_source/eupathdb.xml" />
|
||||
<tool file="data_source/hbvar.xml" />
|
||||
<tool file="genomespace/genomespace_file_browser_prod.xml" />
|
||||
|
||||
@@ -60,4 +60,9 @@
|
||||
<columns>value, dbkey, name, path</columns>
|
||||
<file path="tool-data/mosaik_index.loc" />
|
||||
</table>
|
||||
<!-- Locations of indexes in the 2bit format -->
|
||||
<table name="twobit" comment_char="#">
|
||||
<columns>value, path</columns>
|
||||
<file path="tool-data/twobit.loc" />
|
||||
</table>
|
||||
</tables>
|
||||
|
||||
@@ -67,6 +67,13 @@ id_secret = changethisinproductiontoo
|
||||
# https://wiki.galaxyproject.org/Admin/Config/ApacheProxy
|
||||
#use_remote_user = False
|
||||
|
||||
# If use_remote_user is enabled, anyone who can log in to the Galaxy host may
|
||||
# impersonate any other user by simply sending the appropriate header. Thus a
|
||||
# secret shared between the upstream proxy server, and Galaxy is required.
|
||||
# If anyone other than the Galaxy user is using the server, then apache/nginx should
|
||||
# pass a value in the header 'GX_SECRET' that is identical the one below
|
||||
#remote_user_secret = changethisinproductiontoo
|
||||
|
||||
# Configuration for debugging middleware
|
||||
debug = true
|
||||
use_lint = false
|
||||
@@ -118,3 +125,6 @@ use_new_layout = true
|
||||
#static_favicon_dir = static/favicon.ico
|
||||
#static_scripts_dir = static/scripts/
|
||||
#static_style_dir = static/style/blue
|
||||
|
||||
# Sentry (getsentry.com) DSN for catching bugs.
|
||||
#sentry_dsn = None
|
||||
|
||||
@@ -8,12 +8,7 @@ build description
|
||||
|
||||
import sys
|
||||
import urllib
|
||||
if sys.version_info[:2] >= ( 2, 5 ):
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
else:
|
||||
from galaxy import eggs
|
||||
import pkg_resources; pkg_resources.require( "elementtree" )
|
||||
from elementtree import ElementTree
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
|
||||
URL = "http://genome.cse.ucsc.edu/cgi-bin/das/dsn"
|
||||
|
||||
@@ -38,7 +33,7 @@ def getbuilds(url):
|
||||
for dsn in tree:
|
||||
build = dsn.find("SOURCE").attrib['id']
|
||||
description = dsn.find("DESCRIPTION").text.replace(" - Genome at UCSC","").replace(" Genome at UCSC","")
|
||||
|
||||
|
||||
fields = description.split(" ")
|
||||
temp = fields[0]
|
||||
for i in range(len(fields)-1):
|
||||
|
||||
@@ -5,12 +5,7 @@ Connects to sites and determines which builds are available at each.
|
||||
|
||||
import sys
|
||||
import urllib
|
||||
if sys.version_info[:2] >= ( 2, 5 ):
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
else:
|
||||
from galaxy import eggs
|
||||
import pkg_resources; pkg_resources.require( "elementtree" )
|
||||
from elementtree import ElementTree
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
|
||||
sites = ['http://genome.ucsc.edu/cgi-bin/',
|
||||
'http://archaea.ucsc.edu/cgi-bin/',
|
||||
@@ -38,7 +33,7 @@ def main():
|
||||
print "#Invalid xml passed back from " + site
|
||||
continue
|
||||
print "#Harvested from",site
|
||||
|
||||
|
||||
for dsn in tree:
|
||||
build = dsn.find("SOURCE").attrib['id']
|
||||
builds.append(build)
|
||||
|
||||
@@ -15,7 +15,7 @@ no_auto = pbs_python
|
||||
bx_python = 0.7.2
|
||||
Cheetah = 2.2.2
|
||||
MarkupSafe = 0.12
|
||||
mercurial = 2.2.3
|
||||
mercurial = 3.2.4
|
||||
MySQL_python = 1.2.3c1
|
||||
PyRods = 3.2.4
|
||||
numpy = 1.6.0
|
||||
@@ -26,7 +26,6 @@ pysam = 0.4.2
|
||||
pysqlite = 2.5.6
|
||||
python_lzo = 1.08_2.03_static
|
||||
PyYAML = 3.10
|
||||
guppy = 0.1.10
|
||||
SQLAlchemy = 0.7.9
|
||||
; msgpack_python = 0.2.4
|
||||
|
||||
@@ -34,14 +33,12 @@ SQLAlchemy = 0.7.9
|
||||
amqp = 1.4.5
|
||||
anyjson = 0.3.3
|
||||
Beaker = 1.4
|
||||
bioblend = 0.4.2
|
||||
bioblend = 0.5.2
|
||||
boto = 2.27.0
|
||||
decorator = 3.1.2
|
||||
docutils = 0.7
|
||||
drmaa = 0.7.6
|
||||
elementtree = 1.2.6_20050316
|
||||
Fabric = 1.7.0
|
||||
GeneTrack = 2.0.0_beta_1
|
||||
importlib = 1.0.3
|
||||
kombu = 3.0.13
|
||||
lrucache = 0.2
|
||||
@@ -60,9 +57,11 @@ poster = 0.8.1
|
||||
python_openid = 2.2.5
|
||||
python_daemon = 1.5.5
|
||||
pytz = 2013.9
|
||||
repoze.lru = 0.6
|
||||
requests = 2.2.1
|
||||
Routes = 1.12.3
|
||||
Routes = 2.1
|
||||
simplejson = 2.1.1
|
||||
six = 1.9.0
|
||||
sqlalchemy_migrate = 0.7.2
|
||||
ssh = 1.7.14
|
||||
SVGFig = 1.1.6
|
||||
@@ -74,7 +73,7 @@ WebOb = 0.8.5
|
||||
wsgiref = 0.1.2
|
||||
Babel = 1.3
|
||||
wchartype = 0.1
|
||||
Whoosh = 0.3.18
|
||||
Whoosh = 2.4.1
|
||||
; fluent_logger = 0.3.3
|
||||
raven = 3.1.8
|
||||
|
||||
@@ -83,7 +82,6 @@ raven = 3.1.8
|
||||
psycopg2 = _9.2.4_static
|
||||
pysqlite = _3.6.17_static
|
||||
MySQL_python = _5.1.41_static
|
||||
GeneTrack = _dev_48da9e998f0caf01c5be731e926f4b0481f658f0
|
||||
pysam = _kanwei_b10f6e722e9a
|
||||
|
||||
; dependency source urls, necessary for scrambling. for an explanation, see
|
||||
|
||||
+3
-10
@@ -7,7 +7,7 @@ import galaxy.model
|
||||
import galaxy.security
|
||||
from galaxy.managers.collections import DatasetCollectionManager
|
||||
import galaxy.quota
|
||||
from galaxy.tags.tag_handler import GalaxyTagHandler
|
||||
from galaxy.managers.tags import GalaxyTagManager
|
||||
from galaxy.visualization.genomes import Genomes
|
||||
from galaxy.visualization.data_providers.registry import DataProviderRegistry
|
||||
from galaxy.visualization.registry import VisualizationsRegistry
|
||||
@@ -58,7 +58,7 @@ class UniverseApplication( object, config.ConfiguresGalaxyMixin ):
|
||||
# Security helper
|
||||
self._configure_security()
|
||||
# Tag handler
|
||||
self.tag_handler = GalaxyTagHandler()
|
||||
self.tag_handler = GalaxyTagManager( self )
|
||||
# Dataset Collection Plugins
|
||||
self.dataset_collections_service = DatasetCollectionManager(self)
|
||||
|
||||
@@ -107,10 +107,8 @@ class UniverseApplication( object, config.ConfiguresGalaxyMixin ):
|
||||
self.quota_agent = galaxy.quota.QuotaAgent( self.model )
|
||||
else:
|
||||
self.quota_agent = galaxy.quota.NoQuotaAgent( self.model )
|
||||
# Heartbeat and memdump for thread / heap profiling
|
||||
# Heartbeat for thread profiling
|
||||
self.heartbeat = None
|
||||
self.memdump = None
|
||||
self.memory_usage = None
|
||||
# Container for OpenID authentication routines
|
||||
if self.config.enable_openid:
|
||||
from galaxy.web.framework import openid_manager
|
||||
@@ -125,11 +123,6 @@ class UniverseApplication( object, config.ConfiguresGalaxyMixin ):
|
||||
self.heartbeat = heartbeat.Heartbeat( fname=self.config.heartbeat_log )
|
||||
self.heartbeat.daemon = True
|
||||
self.heartbeat.start()
|
||||
# Enable the memdump signal catcher if configured and available
|
||||
if self.config.use_memdump:
|
||||
from galaxy.util import memdump
|
||||
if memdump.Memdump:
|
||||
self.memdump = memdump.Memdump()
|
||||
# Transfer manager client
|
||||
if self.config.get_bool( 'enable_beta_job_managers', False ):
|
||||
from galaxy.jobs import transfer_manager
|
||||
|
||||
+16
-8
@@ -83,6 +83,7 @@ class Configuration( object ):
|
||||
# The value of migrated_tools_config is the file reserved for containing only those tools that have been eliminated from the distribution
|
||||
# and moved to the tool shed.
|
||||
self.integrated_tool_panel_config = resolve_path( kwargs.get( 'integrated_tool_panel_config', 'integrated_tool_panel.xml' ), self.root )
|
||||
self.toolbox_filter_base_modules = listify( kwargs.get( "toolbox_filter_base_modules", "galaxy.tools.filters,galaxy.tools.toolbox.filters" ) )
|
||||
self.tool_filters = listify( kwargs.get( "tool_filters", [] ), do_strip=True )
|
||||
self.tool_label_filters = listify( kwargs.get( "tool_label_filters", [] ), do_strip=True )
|
||||
self.tool_section_filters = listify( kwargs.get( "tool_section_filters", [] ), do_strip=True )
|
||||
@@ -135,6 +136,7 @@ class Configuration( object ):
|
||||
self.remote_user_maildomain = kwargs.get( "remote_user_maildomain", None )
|
||||
self.remote_user_header = kwargs.get( "remote_user_header", 'HTTP_REMOTE_USER' )
|
||||
self.remote_user_logout_href = kwargs.get( "remote_user_logout_href", None )
|
||||
self.remote_user_secret = kwargs.get( "remote_user_secret", None )
|
||||
self.require_login = string_as_bool( kwargs.get( "require_login", "False" ) )
|
||||
self.allow_user_creation = string_as_bool( kwargs.get( "allow_user_creation", "True" ) )
|
||||
self.allow_user_deletion = string_as_bool( kwargs.get( "allow_user_deletion", "False" ) )
|
||||
@@ -144,7 +146,6 @@ class Configuration( object ):
|
||||
self.collect_outputs_from = [ x.strip() for x in kwargs.get( 'collect_outputs_from', 'new_file_path,job_working_directory' ).lower().split(',') ]
|
||||
self.template_path = resolve_path( kwargs.get( "template_path", "templates" ), self.root )
|
||||
self.template_cache = resolve_path( kwargs.get( "template_cache_path", "database/compiled_templates" ), self.root )
|
||||
self.workflow_schedulers_config_file = resolve_path( kwargs.get( 'workflow_schedulers_config_file', 'config/workflow_schedulers_conf.xml' ), self.root )
|
||||
self.local_job_queue_workers = int( kwargs.get( "local_job_queue_workers", "5" ) )
|
||||
self.cluster_job_queue_workers = int( kwargs.get( "cluster_job_queue_workers", "3" ) )
|
||||
self.job_queue_cleanup_interval = int( kwargs.get("job_queue_cleanup_interval", "5") )
|
||||
@@ -162,7 +163,6 @@ class Configuration( object ):
|
||||
self.job_walltime_delta = timedelta( 0, s, 0, 0, m, h )
|
||||
self.admin_users = kwargs.get( "admin_users", "" )
|
||||
self.admin_users_list = [u.strip() for u in self.admin_users.split(',') if u]
|
||||
self.reset_password_length = int( kwargs.get('reset_password_length', '15') )
|
||||
self.mailing_join_addr = kwargs.get('mailing_join_addr', 'galaxy-announce-join@bx.psu.edu')
|
||||
self.error_email_to = kwargs.get( 'error_email_to', None )
|
||||
self.activation_email = kwargs.get( 'activation_email', None )
|
||||
@@ -173,6 +173,7 @@ class Configuration( object ):
|
||||
self.instance_resource_url = kwargs.get( 'instance_resource_url', None )
|
||||
self.registration_warning_message = kwargs.get( 'registration_warning_message', None )
|
||||
self.ga_code = kwargs.get( 'ga_code', None )
|
||||
self.session_duration = int(kwargs.get( 'session_duration', 0 ))
|
||||
# Get the disposable email domains blacklist file and its contents
|
||||
self.blacklist_location = kwargs.get( 'blacklist_file', None )
|
||||
self.blacklist_content = None
|
||||
@@ -195,6 +196,7 @@ class Configuration( object ):
|
||||
# Tasked job runner.
|
||||
self.use_tasked_jobs = string_as_bool( kwargs.get( 'use_tasked_jobs', False ) )
|
||||
self.local_task_queue_workers = int(kwargs.get("local_task_queue_workers", 2))
|
||||
self.commands_in_new_shell = string_as_bool( kwargs.get( 'enable_beta_tool_command_isolation', "False" ) )
|
||||
# The transfer manager and deferred job queue
|
||||
self.enable_beta_job_managers = string_as_bool( kwargs.get( 'enable_beta_job_managers', 'False' ) )
|
||||
# These workflow modules should not be considered part of Galaxy's
|
||||
@@ -229,7 +231,6 @@ class Configuration( object ):
|
||||
self.external_chown_script = kwargs.get('external_chown_script', None)
|
||||
self.environment_setup_file = kwargs.get( 'environment_setup_file', None )
|
||||
self.use_heartbeat = string_as_bool( kwargs.get( 'use_heartbeat', 'False' ) )
|
||||
self.use_memdump = string_as_bool( kwargs.get( 'use_memdump', 'False' ) )
|
||||
self.log_actions = string_as_bool( kwargs.get( 'log_actions', 'False' ) )
|
||||
self.log_events = string_as_bool( kwargs.get( 'log_events', 'False' ) )
|
||||
self.sanitize_all_html = string_as_bool( kwargs.get( 'sanitize_all_html', True ) )
|
||||
@@ -321,6 +322,9 @@ class Configuration( object ):
|
||||
# Crummy, but PasteScript does not give you a way to determine this
|
||||
if arg.lower().startswith('--server-name='):
|
||||
self.server_name = arg.split('=', 1)[-1]
|
||||
# Allow explicit override of server name in confg params
|
||||
if "server_name" in kwargs:
|
||||
self.server_name = kwargs.get("server_name")
|
||||
# Store all configured server names
|
||||
self.server_names = []
|
||||
for section in global_conf_parser.sections():
|
||||
@@ -457,6 +461,7 @@ class Configuration( object ):
|
||||
shed_data_manager_config_file=[ 'shed_data_manager_conf.xml', 'config/shed_data_manager_conf.xml' ],
|
||||
shed_tool_data_table_config=[ 'shed_tool_data_table_conf.xml', 'config/shed_tool_data_table_conf.xml' ],
|
||||
tool_sheds_config_file=[ 'config/tool_sheds_conf.xml', 'tool_sheds_conf.xml', 'config/tool_sheds_conf.xml.sample' ],
|
||||
workflow_schedulers_config_file=['config/workflow_schedulers_conf.xml', 'config/workflow_schedulers_conf.xml.sample'],
|
||||
)
|
||||
|
||||
listify_defaults = dict(
|
||||
@@ -709,10 +714,7 @@ class ConfiguresGalaxyMixin:
|
||||
|
||||
from galaxy import tools
|
||||
self.toolbox = tools.ToolBox( tool_configs, self.config.tool_path, self )
|
||||
# Search support for tools
|
||||
import galaxy.tools.search
|
||||
index_help = getattr( self.config, "index_tool_help", True )
|
||||
self.toolbox_search = galaxy.tools.search.ToolBoxSearch( self.toolbox, index_help )
|
||||
self.reindex_tool_search()
|
||||
|
||||
from galaxy.tools.deps import containers
|
||||
galaxy_root_dir = os.path.abspath(self.config.root)
|
||||
@@ -723,7 +725,13 @@ class ConfiguresGalaxyMixin:
|
||||
outputs_to_working_directory=self.config.outputs_to_working_directory,
|
||||
container_image_cache_path=self.config.container_image_cache_path,
|
||||
)
|
||||
self.container_finder = galaxy.tools.deps.containers.ContainerFinder(app_info)
|
||||
self.container_finder = containers.ContainerFinder(app_info)
|
||||
|
||||
def reindex_tool_search( self ):
|
||||
# Call this when tools are added or removed.
|
||||
import galaxy.tools.search
|
||||
index_help = getattr( self.config, "index_tool_help", True )
|
||||
self.toolbox_search = galaxy.tools.search.ToolBoxSearch( self.toolbox, index_help )
|
||||
|
||||
def _configure_tool_data_tables( self, from_shed_config ):
|
||||
from galaxy.tools.data import ToolDataTableManager
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from .types import list
|
||||
from .types import paired
|
||||
|
||||
from galaxy import model
|
||||
|
||||
PLUGIN_CLASSES = [list.ListDatasetCollectionType, paired.PairedDatasetCollectionType]
|
||||
|
||||
@@ -12,3 +12,13 @@ class DatasetCollectionTypesRegistry(object):
|
||||
|
||||
def get( self, plugin_type ):
|
||||
return self.__plugins[ plugin_type ]
|
||||
|
||||
def prototype( self, plugin_type ):
|
||||
plugin_type_object = self.get( plugin_type )
|
||||
if not hasattr( plugin_type_object, 'prototype_elements' ):
|
||||
raise Exception( "Cannot pre-determine structure for collection of type %s" % plugin_type )
|
||||
|
||||
dataset_collection = model.DatasetCollection()
|
||||
elements = [ e for e in plugin_type_object.prototype_elements() ]
|
||||
dataset_collection.elements = elements
|
||||
return dataset_collection
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from ..types import BaseDatasetCollectionType
|
||||
|
||||
from galaxy.model import DatasetCollectionElement
|
||||
from galaxy.model import DatasetCollectionElement, HistoryDatasetAssociation
|
||||
|
||||
FORWARD_IDENTIFIER = "forward"
|
||||
REVERSE_IDENTIFIER = "reverse"
|
||||
@@ -32,3 +32,15 @@ class PairedDatasetCollectionType( BaseDatasetCollectionType ):
|
||||
)
|
||||
yield left_association
|
||||
yield right_association
|
||||
|
||||
def prototype_elements( self ):
|
||||
left_association = DatasetCollectionElement(
|
||||
element=HistoryDatasetAssociation(),
|
||||
element_identifier=FORWARD_IDENTIFIER,
|
||||
)
|
||||
right_association = DatasetCollectionElement(
|
||||
element=HistoryDatasetAssociation(),
|
||||
element_identifier=REVERSE_IDENTIFIER,
|
||||
)
|
||||
yield left_association
|
||||
yield right_association
|
||||
|
||||
@@ -20,7 +20,7 @@ eggs.require( "bx-python" )
|
||||
from bx.seq.twobit import TWOBIT_MAGIC_NUMBER, TWOBIT_MAGIC_NUMBER_SWAP, TWOBIT_MAGIC_SIZE
|
||||
|
||||
from galaxy.util import sqlite
|
||||
from galaxy.datatypes.metadata import MetadataElement,ListParameter,DictParameter
|
||||
from galaxy.datatypes.metadata import MetadataElement, MetadataParameter, ListParameter, DictParameter
|
||||
from galaxy.datatypes import metadata
|
||||
import dataproviders
|
||||
|
||||
@@ -106,6 +106,30 @@ class Ab1( Binary ):
|
||||
|
||||
Binary.register_unsniffable_binary_ext("ab1")
|
||||
|
||||
class CompressedArchive( Binary ):
|
||||
"""
|
||||
Class describing an compressed binary file
|
||||
This class can be sublass'ed to implement archive filetypes that will not be unpacked by upload.py.
|
||||
"""
|
||||
file_ext = "compressed_archive"
|
||||
compressed = True
|
||||
|
||||
def set_peek( self, dataset, is_multi_byte=False ):
|
||||
if not dataset.dataset.purged:
|
||||
dataset.peek = "Compressed binary file"
|
||||
dataset.blurb = data.nice_size( dataset.get_size() )
|
||||
else:
|
||||
dataset.peek = 'file does not exist'
|
||||
dataset.blurb = 'file purged from disk'
|
||||
|
||||
def display_peek( self, dataset ):
|
||||
try:
|
||||
return dataset.peek
|
||||
except:
|
||||
return "Compressed binary file (%s)" % ( data.nice_size( dataset.get_size() ) )
|
||||
|
||||
Binary.register_unsniffable_binary_ext("compressed_archive")
|
||||
|
||||
|
||||
class GenericAsn1Binary( Binary ):
|
||||
"""Class for generic ASN.1 binary format"""
|
||||
@@ -240,10 +264,23 @@ class Bam( Binary ):
|
||||
##$ samtools index
|
||||
##Usage: samtools index <in.bam> [<out.index>]
|
||||
stderr_name = tempfile.NamedTemporaryFile( prefix = "bam_index_stderr" ).name
|
||||
command = 'samtools index %s %s' % ( dataset.file_name, index_file.file_name )
|
||||
proc = subprocess.Popen( args=command, shell=True, stderr=open( stderr_name, 'wb' ) )
|
||||
command = [ 'samtools', 'index', dataset.file_name, index_file.file_name ]
|
||||
proc = subprocess.Popen( args=command, stderr=open( stderr_name, 'wb' ) )
|
||||
exit_code = proc.wait()
|
||||
#Did index succeed?
|
||||
if exit_code == -6:
|
||||
# SIGABRT, most likely samtools 1.0+ which does not accept the index name parameter.
|
||||
dataset_symlink = os.path.join( os.path.dirname( index_file.file_name ),
|
||||
'__dataset_%d_%s' % ( dataset.id, os.path.basename( index_file.file_name ) ) )
|
||||
os.symlink( dataset.file_name, dataset_symlink )
|
||||
try:
|
||||
command = [ 'samtools', 'index', dataset_symlink ]
|
||||
exit_code = subprocess.call( args=command, stderr=open( stderr_name, 'wb' ) )
|
||||
shutil.move( dataset_symlink + '.bai', index_file.file_name )
|
||||
except Exception, e:
|
||||
open( stderr_name, 'ab+' ).write( 'Galaxy attempted to build the BAM index with samtools 1.0+ but failed: %s\n' % e)
|
||||
finally:
|
||||
os.unlink( dataset_symlink )
|
||||
stderr = open( stderr_name ).read().strip()
|
||||
if stderr:
|
||||
if exit_code != 0:
|
||||
@@ -640,8 +677,66 @@ class SQlite ( Binary ):
|
||||
return dataproviders.dataset.SQliteDataDictProvider( dataset_source, **settings )
|
||||
|
||||
|
||||
#Binary.register_sniffable_binary_format("sqlite", "sqlite", SQlite)
|
||||
|
||||
|
||||
class GeminiSQLite( SQlite ):
|
||||
"""Class describing a Gemini Sqlite database """
|
||||
MetadataElement( name="gemini_version", default='0.10.0' , param=MetadataParameter, desc="Gemini Version",
|
||||
readonly=True, visible=True, no_value='0.10.0' )
|
||||
file_ext = "gemini.sqlite"
|
||||
|
||||
def set_meta( self, dataset, overwrite = True, **kwd ):
|
||||
super( GeminiSQLite, self ).set_meta( dataset, overwrite = overwrite, **kwd )
|
||||
try:
|
||||
conn = sqlite.connect( dataset.file_name )
|
||||
c = conn.cursor()
|
||||
tables_query = "SELECT version FROM version"
|
||||
result = c.execute( tables_query ).fetchall()
|
||||
for version, in result:
|
||||
dataset.metadata.gemini_version = version
|
||||
# TODO: Can/should we detect even more attributes, such as use of PED file, what was input annotation type, etc.
|
||||
except Exception, e:
|
||||
log.warn( '%s, set_meta Exception: %s', self, e )
|
||||
|
||||
def sniff( self, filename ):
|
||||
if super( GeminiSQLite, self ).sniff( filename ):
|
||||
gemini_table_names = [ "gene_detailed", "gene_summary", "resources", "sample_genotype_counts", "sample_genotypes", "samples",
|
||||
"variant_impacts", "variants", "version" ]
|
||||
try:
|
||||
conn = sqlite.connect( filename )
|
||||
c = conn.cursor()
|
||||
tables_query = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
|
||||
result = c.execute( tables_query ).fetchall()
|
||||
result = map( lambda x: x[0], result )
|
||||
for table_name in gemini_table_names:
|
||||
if table_name not in result:
|
||||
return False
|
||||
return True
|
||||
except Exception, e:
|
||||
log.warn( '%s, sniff Exception: %s', self, e )
|
||||
return False
|
||||
|
||||
def set_peek( self, dataset, is_multi_byte=False ):
|
||||
if not dataset.dataset.purged:
|
||||
dataset.peek = "Gemini SQLite Database, version %s" % ( dataset.metadata.gemini_version or 'unknown' )
|
||||
dataset.blurb = data.nice_size( dataset.get_size() )
|
||||
else:
|
||||
dataset.peek = 'file does not exist'
|
||||
dataset.blurb = 'file purged from disk'
|
||||
|
||||
def display_peek( self, dataset ):
|
||||
try:
|
||||
return dataset.peek
|
||||
except:
|
||||
return "Gemini SQLite Database, version %s" % ( dataset.metadata.gemini_version or 'unknown' )
|
||||
|
||||
Binary.register_sniffable_binary_format( "gemini.sqlite", "gemini.sqlite", GeminiSQLite )
|
||||
# FIXME: We need to register gemini.sqlite before sqlite, since register_sniffable_binary_format and is_sniffable_binary called in upload.py
|
||||
# ignores sniff order declared in datatypes_conf.xml
|
||||
Binary.register_sniffable_binary_format("sqlite", "sqlite", SQlite)
|
||||
|
||||
|
||||
class Xlsx(Binary):
|
||||
"""Class for Excel 2007 (xlsx) files"""
|
||||
file_ext="xlsx"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<tool id="tabular_to_dbnsfp" name="Convert tabular to dbnsfp" version="1.0.0">
|
||||
<description></description>
|
||||
<command interpreter="python">tabular_to_dbnsfp.py $input $dbnsfp.extra_files_path/dbNSFP.gz</command>
|
||||
<inputs>
|
||||
<param format="tabular" name="input" type="data" label="Choose a dbnsfp tabular file"/>
|
||||
</inputs>
|
||||
<outputs>
|
||||
<data format="snpsiftdbnsfp" name="dbnsfp"/>
|
||||
</outputs>
|
||||
<help>
|
||||
</help>
|
||||
</tool>
|
||||
@@ -3,7 +3,7 @@ Dataproviders that iterate over lines from their sources.
|
||||
"""
|
||||
|
||||
import line
|
||||
import xml.etree.ElementTree as elementtree
|
||||
from xml.etree.ElementTree import Element, iterparse
|
||||
|
||||
_TODO = """
|
||||
"""
|
||||
@@ -30,7 +30,7 @@ class XMLDataProvider( HierarchalDataProvider ):
|
||||
"""
|
||||
Data provider that converts selected XML elements to dictionaries.
|
||||
"""
|
||||
# using elementtree's iterparse method to keep mem down
|
||||
# using xml.etree's iterparse method to keep mem down
|
||||
#TODO: this, however (AFAIK), prevents the use of xpath
|
||||
settings = {
|
||||
'selector' : 'str', #urlencoded
|
||||
@@ -54,7 +54,7 @@ class XMLDataProvider( HierarchalDataProvider ):
|
||||
"""
|
||||
Returns true if the ``element`` matches the ``selector``.
|
||||
|
||||
:param element: an XML ``ElementTree.Element``
|
||||
:param element: an XML ``Element``
|
||||
:param selector: some partial string in the desired tags to return
|
||||
|
||||
Change point for more sophisticated selectors.
|
||||
@@ -64,13 +64,13 @@ class XMLDataProvider( HierarchalDataProvider ):
|
||||
#TODO: fails with '#' - browser thinks it's an anchor - use urlencode
|
||||
#TODO: need removal/replacement of etree namespacing here - then move to string match
|
||||
return bool( ( selector == None )
|
||||
or ( isinstance( element, elementtree.Element ) and selector in element.tag ) )
|
||||
or ( isinstance( element, Element ) and selector in element.tag ) )
|
||||
|
||||
def element_as_dict( self, element ):
|
||||
"""
|
||||
Converts an XML element (its text, tag, and attributes) to dictionary form.
|
||||
|
||||
:param element: an XML ``ElementTree.Element``
|
||||
:param element: an XML ``Element``
|
||||
"""
|
||||
#TODO: Key collision is unlikely here, but still should be better handled
|
||||
return {
|
||||
@@ -84,7 +84,7 @@ class XMLDataProvider( HierarchalDataProvider ):
|
||||
"""
|
||||
Yield all children of element (and their children - recursively)
|
||||
in dictionary form.
|
||||
:param element: an XML ``ElementTree.Element``
|
||||
:param element: an XML ``Element``
|
||||
:param max_depth: the number of generations of descendents to return
|
||||
"""
|
||||
if not isinstance( max_depth, int ) or max_depth >= 1:
|
||||
@@ -99,7 +99,7 @@ class XMLDataProvider( HierarchalDataProvider ):
|
||||
yield child_data
|
||||
|
||||
def __iter__( self ):
|
||||
context = elementtree.iterparse( self.source, events=self.ITERPARSE_ALL_EVENTS )
|
||||
context = iterparse( self.source, events=self.ITERPARSE_ALL_EVENTS )
|
||||
context = iter( context )
|
||||
|
||||
selected_element = None
|
||||
|
||||
@@ -167,23 +167,21 @@ class PopulatedDisplayApplicationLink( object ):
|
||||
class DisplayApplication( object ):
|
||||
@classmethod
|
||||
def from_file( cls, filename, datatypes_registry ):
|
||||
return cls.from_elem( parse_xml( filename ).getroot(), datatypes_registry )
|
||||
return cls.from_elem( parse_xml( filename ).getroot(), datatypes_registry, filename=filename )
|
||||
@classmethod
|
||||
def from_elem( cls, elem, datatypes_registry ):
|
||||
def from_elem( cls, elem, datatypes_registry, filename=None ):
|
||||
att_dict = cls._get_attributes_from_elem( elem )
|
||||
rval = DisplayApplication( att_dict['id'], att_dict['name'], datatypes_registry, att_dict['version'], filename=filename, elem=elem )
|
||||
rval._load_links_from_elem( elem )
|
||||
return rval
|
||||
@classmethod
|
||||
def _get_attributes_from_elem( cls, elem ):
|
||||
display_id = elem.get( 'id', None )
|
||||
assert display_id, "ID tag is required for a Display Application"
|
||||
name = elem.get( 'name', display_id )
|
||||
version = elem.get( 'version', None )
|
||||
rval = DisplayApplication( display_id, name, datatypes_registry, version )
|
||||
for link_elem in elem.findall( 'link' ):
|
||||
link = DisplayApplicationLink.from_elem( link_elem, rval )
|
||||
if link:
|
||||
rval.links[ link.id ] = link
|
||||
for dynamic_links in elem.findall( 'dynamic_links' ):
|
||||
for link in DynamicDisplayApplicationBuilder( dynamic_links, rval, datatypes_registry.build_sites ):
|
||||
rval.links[ link.id ] = link
|
||||
return rval
|
||||
def __init__( self, display_id, name, datatypes_registry, version = None ):
|
||||
return dict( id=display_id, name=name, version=version )
|
||||
def __init__( self, display_id, name, datatypes_registry, version = None, filename=None, elem=None ):
|
||||
self.id = display_id
|
||||
self.name = name
|
||||
self.datatypes_registry = datatypes_registry
|
||||
@@ -191,6 +189,16 @@ class DisplayApplication( object ):
|
||||
version = "1.0.0"
|
||||
self.version = version
|
||||
self.links = odict()
|
||||
self._filename = filename
|
||||
self._elem = elem
|
||||
def _load_links_from_elem( self, elem ):
|
||||
for link_elem in elem.findall( 'link' ):
|
||||
link = DisplayApplicationLink.from_elem( link_elem, self )
|
||||
if link:
|
||||
self.links[ link.id ] = link
|
||||
for dynamic_links in elem.findall( 'dynamic_links' ):
|
||||
for link in DynamicDisplayApplicationBuilder( dynamic_links, self, self.datatypes_registry.build_sites ):
|
||||
self.links[ link.id ] = link
|
||||
def get_link( self, link_name, data, dataset_hash, user_hash, trans, app_kwds ):
|
||||
#returns a link object with data knowledge to generate links
|
||||
return PopulatedDisplayApplicationLink( self.links[ link_name ], data, dataset_hash, user_hash, trans, app_kwds )
|
||||
@@ -200,3 +208,23 @@ class DisplayApplication( object ):
|
||||
if link_value.filter_by_dataset( data, trans ):
|
||||
filtered.links[link_name] = link_value
|
||||
return filtered
|
||||
def reload( self ):
|
||||
if self._filename:
|
||||
elem = parse_xml( self._filename ).getroot()
|
||||
elif self._elem:
|
||||
elem = self._elem
|
||||
else:
|
||||
raise Exception( "Unable to reload DisplayApplication %s." % ( self.name ) )
|
||||
# All toolshed-specific attributes added by e.g the registry will remain
|
||||
attr_dict = self._get_attributes_from_elem( elem )
|
||||
# We will not allow changing the id at this time (we'll need to fix several mappings upstream to handle this case)
|
||||
assert attr_dict.get( 'id' ) == self.id, ValueError( "You cannot reload a Display application where the ID has changed. You will need to restart the server instead." )
|
||||
# clear old links
|
||||
for key in self.links.keys():
|
||||
del self.links[key]
|
||||
# Set new attributes
|
||||
for key, value in attr_dict.iteritems():
|
||||
setattr( self, key, value )
|
||||
# Load new links
|
||||
self._load_links_from_elem( elem )
|
||||
return self
|
||||
|
||||
@@ -20,6 +20,7 @@ from sqlalchemy.orm import object_session
|
||||
|
||||
import galaxy.model
|
||||
from galaxy.util import listify
|
||||
from galaxy.util.object_wrapper import sanitize_lists_to_string
|
||||
from galaxy.util import stringify_dictionary_keys
|
||||
from galaxy.util import string_as_bool
|
||||
from galaxy.util import in_directory
|
||||
@@ -232,6 +233,9 @@ class MetadataParameter( object ):
|
||||
def to_string( self, value ):
|
||||
return str( value )
|
||||
|
||||
def to_safe_string( self, value ):
|
||||
return sanitize_lists_to_string( self.to_string( value ) )
|
||||
|
||||
def make_copy( self, value, target_context = None, source_context = None ):
|
||||
return copy.deepcopy( value )
|
||||
|
||||
@@ -480,6 +484,10 @@ class DictParameter( MetadataParameter ):
|
||||
def to_string( self, value ):
|
||||
return json.dumps( value )
|
||||
|
||||
def to_safe_string( self, value ):
|
||||
# We do not sanitize json dicts
|
||||
return json.safe_dumps( value )
|
||||
|
||||
|
||||
class PythonObjectParameter( MetadataParameter ):
|
||||
|
||||
@@ -510,6 +518,10 @@ class FileParameter( MetadataParameter ):
|
||||
return str( self.spec.no_value )
|
||||
return value.file_name
|
||||
|
||||
def to_safe_string( self, value ):
|
||||
# We do not sanitize file names
|
||||
return self.to_string( value )
|
||||
|
||||
def get_html_field( self, value=None, context=None, other_values=None, **kwd ):
|
||||
context = context or {}
|
||||
other_values = other_values or {}
|
||||
|
||||
@@ -346,7 +346,7 @@ class Registry( object ):
|
||||
"""
|
||||
sniffer_elem_classes = [ e.attrib[ 'type' ] for e in self.sniffer_elems ]
|
||||
sniffers = root.find( 'sniffers' )
|
||||
if sniffers:
|
||||
if sniffers is not None:
|
||||
for elem in sniffers.findall( 'sniffer' ):
|
||||
# Keep a status of the process steps to enable stopping the process of handling the sniffer if necessary.
|
||||
ok = True
|
||||
@@ -529,14 +529,13 @@ class Registry( object ):
|
||||
converter.id = tool_dict[ 'guid' ]
|
||||
break
|
||||
if deactivate:
|
||||
if converter.id in toolbox.tools_by_id:
|
||||
del toolbox.tools_by_id[ converter.id ]
|
||||
toolbox.remove_tool_by_id( converter.id, remove_from_panel=False )
|
||||
if source_datatype in self.datatype_converters:
|
||||
if target_datatype in self.datatype_converters[ source_datatype ]:
|
||||
del self.datatype_converters[ source_datatype ][ target_datatype ]
|
||||
self.log.debug( "Deactivated converter: %s", converter.id )
|
||||
else:
|
||||
toolbox.tools_by_id[ converter.id ] = converter
|
||||
toolbox.register_tool( converter )
|
||||
if source_datatype not in self.datatype_converters:
|
||||
self.datatype_converters[ source_datatype ] = odict()
|
||||
self.datatype_converters[ source_datatype ][ target_datatype ] = converter
|
||||
@@ -622,6 +621,26 @@ class Registry( object ):
|
||||
self.log.debug( "Adding inherited display application '%s' to datatype '%s'" % ( display_app.id, extension ) )
|
||||
d_type1.add_display_application( display_app )
|
||||
|
||||
def reload_display_applications( self, display_application_ids=None ):
|
||||
"""
|
||||
Reloads display applications: by id, or all if no ids provided
|
||||
Returns tuple( [reloaded_ids], [failed_ids] )
|
||||
"""
|
||||
if not display_application_ids:
|
||||
display_application_ids = self.display_applications.keys()
|
||||
elif not isinstance( display_application_ids, list ):
|
||||
display_application_ids = [ display_application_ids ]
|
||||
reloaded = []
|
||||
failed = []
|
||||
for display_application_id in display_application_ids:
|
||||
try:
|
||||
self.display_applications[ display_application_id ].reload()
|
||||
reloaded.append( display_application_id )
|
||||
except Exception, e:
|
||||
self.log.debug( 'Requested to reload display application "%s", but failed: %s.', display_application_id, e )
|
||||
failed.append( display_application_id )
|
||||
return ( reloaded, failed )
|
||||
|
||||
def load_external_metadata_tool( self, toolbox ):
|
||||
"""Adds a tool which is used to set external metadata"""
|
||||
# We need to be able to add a job to the queue to set metadata. The queue will currently only accept jobs with an associated
|
||||
@@ -645,8 +664,7 @@ class Registry( object ):
|
||||
tmp_name = tempfile.NamedTemporaryFile()
|
||||
tmp_name.write( tool_xml_text )
|
||||
tmp_name.flush()
|
||||
set_meta_tool = toolbox.load_tool( tmp_name.name )
|
||||
toolbox.tools_by_id[ set_meta_tool.id ] = set_meta_tool
|
||||
set_meta_tool = toolbox.load_hidden_tool( tmp_name.name )
|
||||
self.set_external_metadata_tool = set_meta_tool
|
||||
self.log.debug( "Loaded external metadata tool: %s", self.set_external_metadata_tool.id )
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ from galaxy.datatypes import metadata
|
||||
from galaxy.datatypes.checkers import is_gzip
|
||||
from galaxy.datatypes.sniff import get_test_fname, get_headers
|
||||
from galaxy.datatypes.metadata import MetadataElement
|
||||
|
||||
from galaxy.datatypes.util.image_util import check_image_type
|
||||
|
||||
try:
|
||||
eggs.require( "bx-python" )
|
||||
@@ -814,3 +814,34 @@ class Lav( data.Text ):
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
class RNADotPlotMatrix( data.Data ):
|
||||
file_ext = "rna_eps"
|
||||
|
||||
def set_peek( self, dataset, is_multi_byte=False ):
|
||||
if not dataset.dataset.purged:
|
||||
dataset.peek = 'RNA Dot Plot format (Postscript derivative)'
|
||||
dataset.blurb = data.nice_size( dataset.get_size() )
|
||||
else:
|
||||
dataset.peek = 'file does not exist'
|
||||
dataset.blurb = 'file purged from disk'
|
||||
|
||||
def sniff(self, filename):
|
||||
"""Determine if the file is in RNA dot plot format."""
|
||||
if check_image_type( filename, ['EPS'] ):
|
||||
seq = False
|
||||
coor = False
|
||||
pairs = False
|
||||
with open( filename ) as handle:
|
||||
for line in handle:
|
||||
line = line.strip()
|
||||
if line:
|
||||
if line.startswith('/sequence'):
|
||||
seq = True
|
||||
elif line.startswith('/coor'):
|
||||
coor = True
|
||||
elif line.startswith('/pairs'):
|
||||
pairs = True
|
||||
if seq and coor and pairs:
|
||||
return True
|
||||
return False
|
||||
@@ -2,22 +2,22 @@
|
||||
""" Clearing house for generic text datatypes that are not XML or tabular.
|
||||
"""
|
||||
|
||||
|
||||
from galaxy.datatypes.data import Text
|
||||
from galaxy.datatypes.data import get_file_peek
|
||||
from galaxy.datatypes.data import nice_size
|
||||
from galaxy.datatypes.metadata import MetadataElement
|
||||
from galaxy import util
|
||||
|
||||
import tempfile
|
||||
import subprocess
|
||||
import json
|
||||
import gzip
|
||||
import os
|
||||
import re
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Json( Text ):
|
||||
file_ext = "json"
|
||||
|
||||
@@ -155,3 +155,224 @@ class Obo( Text ):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Arff( Text ):
|
||||
"""
|
||||
An ARFF (Attribute-Relation File Format) file is an ASCII text file that describes a list of instances sharing a set of attributes.
|
||||
http://weka.wikispaces.com/ARFF
|
||||
"""
|
||||
file_ext = "arff"
|
||||
|
||||
|
||||
"""Add metadata elements"""
|
||||
MetadataElement( name="comment_lines", default=0, desc="Number of comment lines", readonly=True, optional=True, no_value=0 )
|
||||
MetadataElement( name="columns", default=0, desc="Number of columns", readonly=True, visible=True, no_value=0 )
|
||||
|
||||
def set_peek( self, dataset, is_multi_byte=False ):
|
||||
if not dataset.dataset.purged:
|
||||
dataset.peek = get_file_peek( dataset.file_name, is_multi_byte=is_multi_byte )
|
||||
dataset.blurb = "Attribute-Relation File Format (ARFF)"
|
||||
dataset.blurb += ", %s comments, %s attributes" % ( dataset.metadata.comment_lines, dataset.metadata.columns )
|
||||
else:
|
||||
dataset.peek = 'file does not exist'
|
||||
dataset.blurb = 'file purged from disc'
|
||||
|
||||
def sniff( self, filename ):
|
||||
"""
|
||||
Try to guess the Arff filetype.
|
||||
It usually starts with a "format-version:" string and has several stanzas which starts with "id:".
|
||||
"""
|
||||
with open( filename ) as handle:
|
||||
relation_found = False
|
||||
attribute_found = False
|
||||
prefix = ""
|
||||
for line_count, line in enumerate( handle ):
|
||||
if line_count > 1000:
|
||||
# only investigate the first 1000 lines
|
||||
return False
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
start_string = line[:20].upper()
|
||||
if start_string.startswith("@RELATION"):
|
||||
relation_found = True
|
||||
elif start_string.startswith("@ATTRIBUTE"):
|
||||
attribute_found = True
|
||||
elif start_string.startswith("@DATA"):
|
||||
# @DATA should be the last data block
|
||||
if relation_found and attribute_found:
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_meta( self, dataset, **kwd ):
|
||||
"""
|
||||
Trying to count the comment lines and the number of columns included.
|
||||
A typical ARFF data block looks like this:
|
||||
@DATA
|
||||
5.1,3.5,1.4,0.2,Iris-setosa
|
||||
4.9,3.0,1.4,0.2,Iris-setosa
|
||||
"""
|
||||
if dataset.has_data():
|
||||
comment_lines = 0
|
||||
first_real_line = False
|
||||
data_block = False
|
||||
with open( dataset.file_name ) as handle:
|
||||
for line in handle:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith('%') and not first_real_line:
|
||||
comment_lines += 1
|
||||
else:
|
||||
first_real_line = True
|
||||
if data_block:
|
||||
if line.startswith('{'):
|
||||
# Sparse representation
|
||||
"""
|
||||
@data
|
||||
0, X, 0, Y, "class A", {5}
|
||||
or
|
||||
@data
|
||||
{1 X, 3 Y, 4 "class A"}, {5}
|
||||
"""
|
||||
token = line.split('}',1)
|
||||
first_part = token[0]
|
||||
last_column = first_part.split(',')[-1].strip()
|
||||
numeric_value = last_column.split()[0]
|
||||
column_count = int(numeric_value)
|
||||
if len(token) > 1:
|
||||
# we have an additional weight
|
||||
column_count -= 1
|
||||
else:
|
||||
columns = line.strip().split(',')
|
||||
column_count = len(columns)
|
||||
if columns[-1].strip().startswith('{'):
|
||||
# we have an additional weight at the end
|
||||
column_count -= 1
|
||||
|
||||
# We have now the column_count and we know the initial comment lines. So we can terminate here.
|
||||
break
|
||||
if line[:5].upper() == "@DATA":
|
||||
data_block = True
|
||||
dataset.metadata.comment_lines = comment_lines
|
||||
dataset.metadata.columns = column_count
|
||||
|
||||
|
||||
class SnpEffDb( Text ):
|
||||
"""Class describing a SnpEff genome build"""
|
||||
file_ext = "snpeffdb"
|
||||
MetadataElement( name="genome_version", default=None, desc="Genome Version", readonly=True, visible=True, no_value=None )
|
||||
MetadataElement( name="regulation", default=[], desc="Regulation Names", readonly=True, visible=True, no_value=[], optional=True)
|
||||
MetadataElement( name="annotation", default=[], desc="Annotation Names", readonly=True, visible=True, no_value=[], optional=True)
|
||||
|
||||
def __init__( self, **kwd ):
|
||||
Text.__init__( self, **kwd )
|
||||
|
||||
def set_meta( self, dataset, **kwd ):
|
||||
Text.set_meta(self, dataset, **kwd )
|
||||
data_dir = dataset.extra_files_path
|
||||
## search data_dir/genome_version for files
|
||||
regulation_pattern = 'regulation_(.+).bin'
|
||||
# annotation files that are included in snpEff by a flag
|
||||
annotations_dict = {'nextProt.bin' : '-nextprot','motif.bin': '-motif'}
|
||||
regulations = []
|
||||
annotations = []
|
||||
if data_dir and os.path.isdir(data_dir):
|
||||
for root, dirs, files in os.walk(data_dir):
|
||||
for fname in files:
|
||||
if fname.startswith('snpEffectPredictor'):
|
||||
# if snpEffectPredictor.bin download succeeded
|
||||
genome_version = os.path.basename(root)
|
||||
dataset.metadata.genome_version = genome_version
|
||||
else:
|
||||
m = re.match(regulation_pattern,fname)
|
||||
if m:
|
||||
name = m.groups()[0]
|
||||
regulations.append(name)
|
||||
elif fname in annotations_dict:
|
||||
value = annotations_dict[fname]
|
||||
name = value.lstrip('-')
|
||||
annotations.append(name)
|
||||
dataset.metadata.regulation = regulations
|
||||
dataset.metadata.annotation = annotations
|
||||
try:
|
||||
fh = file(dataset.file_name,'w')
|
||||
fh.write("%s\n" % genome_version)
|
||||
if annotations:
|
||||
fh.write("annotations: %s\n" % ','.join(annotations))
|
||||
if regulations:
|
||||
fh.write("regulations: %s\n" % ','.join(regulations))
|
||||
fh.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class SnpSiftDbNSFP( Text ):
|
||||
"""Class describing a dbNSFP database prepared fpr use by SnpSift dbnsfp """
|
||||
MetadataElement( name='reference_name', default='dbSNFP' , desc='Reference Name', readonly=True, visible=True, set_in_upload=True, no_value='dbSNFP' )
|
||||
MetadataElement( name="bgzip", default=None, desc="dbNSFP bgzip", readonly=True, visible=True, no_value=None )
|
||||
MetadataElement( name="index", default=None, desc="Tabix Index File", readonly=True, visible=True, no_value=None)
|
||||
MetadataElement( name="annotation", default=[], desc="Annotation Names", readonly=True, visible=True, no_value=[] )
|
||||
file_ext = "snpsiftdbnsfp"
|
||||
composite_type = 'auto_primary_file'
|
||||
allow_datatype_change = False
|
||||
"""
|
||||
## The dbNSFP file is a tabular file with 1 header line
|
||||
## The first 4 columns are required to be: chrom pos ref alt
|
||||
## These match columns 1,2,4,5 of the VCF file
|
||||
## SnpSift requires the file to be block-gzipped and the indexed with samtools tabix
|
||||
## Example:
|
||||
## Compress using block-gzip algorithm
|
||||
bgzip dbNSFP2.3.txt
|
||||
## Create tabix index
|
||||
tabix -s 1 -b 2 -e 2 dbNSFP2.3.txt.gz
|
||||
"""
|
||||
def __init__( self, **kwd ):
|
||||
Text.__init__( self, **kwd )
|
||||
self.add_composite_file( '%s.grp', description = 'Group File', substitute_name_with_metadata = 'reference_name', is_binary = False )
|
||||
self.add_composite_file( '%s.ti', description = '', substitute_name_with_metadata = 'reference_name', is_binary = False )
|
||||
def init_meta( self, dataset, copy_from=None ):
|
||||
Text.init_meta( self, dataset, copy_from=copy_from )
|
||||
def generate_primary_file( self, dataset = None ):
|
||||
"""
|
||||
This is called only at upload to write the html file
|
||||
cannot rename the datasets here - they come with the default unfortunately
|
||||
"""
|
||||
regenerate_primary_file( self, dataset)
|
||||
def regenerate_primary_file(self,dataset):
|
||||
"""
|
||||
cannot do this until we are setting metadata
|
||||
"""
|
||||
annotations = "dbNSFP Annotations: %s\n" % ','.join(dataset.metadata.annotation)
|
||||
f = open(dataset.file_name,'a')
|
||||
if dataset.metadata.bgzip:
|
||||
bn = dataset.metadata.bgzip
|
||||
f.write(bn)
|
||||
f.write('\n')
|
||||
f.write(annotations)
|
||||
f.close()
|
||||
def set_meta( self, dataset, overwrite=True, **kwd ):
|
||||
try:
|
||||
efp = dataset.extra_files_path
|
||||
if os.path.exists(efp):
|
||||
flist = os.listdir(efp)
|
||||
for i,fname in enumerate(flist):
|
||||
if fname.endswith('.gz'):
|
||||
dataset.metadata.bgzip = fname
|
||||
try:
|
||||
fh = gzip.open(os.path.join(efp,fname),'r')
|
||||
buf = fh.read(5000)
|
||||
lines = buf.splitlines()
|
||||
headers = lines[0].split('\t')
|
||||
dataset.metadata.annotation = headers[4:]
|
||||
except Exception,e:
|
||||
log.warn("set_meta fname: %s %s" % (fname,str(e)))
|
||||
finally:
|
||||
fh.close()
|
||||
if fname.endswith('.tbi'):
|
||||
dataset.metadata.index = fname
|
||||
self.regenerate_primary_file(dataset)
|
||||
except Exception,e:
|
||||
log.warn("set_meta fname: %s %s" % (dataset.file_name if dataset and dataset.file_name else 'Unkwown',str(e)))
|
||||
|
||||
|
||||
@@ -71,6 +71,4 @@ def get_image_ext ( file_path, image ):
|
||||
return 'pbm'
|
||||
if format == 'PGM':
|
||||
return 'pgm'
|
||||
if format == 'EPS':
|
||||
return 'eps'
|
||||
return None
|
||||
|
||||
@@ -385,8 +385,6 @@ class GalaxyConfig( object ):
|
||||
"DRMAA_python": lambda: "sge" in self.config.get( "app:main", "start_job_runners" ).split(","),
|
||||
"drmaa": lambda: "drmaa" in self.config.get( "app:main", "start_job_runners" ).split(","),
|
||||
"pbs_python": lambda: "pbs" in self.config.get( "app:main", "start_job_runners" ).split(","),
|
||||
"threadframe": lambda: self.config.get( "app:main", "use_heartbeat" ),
|
||||
"guppy": lambda: self.config.get( "app:main", "use_memdump" ),
|
||||
"python_openid": lambda: self.config.get( "app:main", "enable_openid" ),
|
||||
"python_daemon": lambda: sys.version_info[:2] >= ( 2, 5 ),
|
||||
"pysam": lambda: check_pysam(),
|
||||
|
||||
@@ -711,7 +711,7 @@ class JobWrapper( object ):
|
||||
self.job_id = job.id
|
||||
self.session_id = job.session_id
|
||||
self.user_id = job.user_id
|
||||
self.tool = queue.app.toolbox.tools_by_id.get( job.tool_id, None )
|
||||
self.tool = queue.app.toolbox.get_tool( job.tool_id, job.tool_version, exact=True )
|
||||
self.queue = queue
|
||||
self.app = queue.app
|
||||
self.sa_session = self.app.model.context
|
||||
@@ -765,6 +765,10 @@ class JobWrapper( object ):
|
||||
def get_parallelism(self):
|
||||
return self.tool.parallelism
|
||||
|
||||
@property
|
||||
def commands_in_new_shell(self):
|
||||
return self.app.config.commands_in_new_shell
|
||||
|
||||
# legacy naming
|
||||
get_job_runner = get_job_runner_url
|
||||
|
||||
@@ -1202,6 +1206,11 @@ class JobWrapper( object ):
|
||||
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 ] )
|
||||
|
||||
# TODO: eliminate overlap with tools/evaluation.py
|
||||
out_collections = dict( [ ( obj.name, obj.dataset_collection_instance ) for obj in job.output_dataset_collection_instances ] )
|
||||
out_collections.update( [ ( obj.name, obj.dataset_collection ) for obj in job.output_dataset_collections ] )
|
||||
|
||||
input_ext = 'data'
|
||||
for _, data in inp_data.items():
|
||||
# For loop odd, but sort simulating behavior in galaxy.tools.actions
|
||||
@@ -1218,6 +1227,12 @@ class JobWrapper( object ):
|
||||
'children': self.tool.collect_child_datasets(out_data, self.working_directory),
|
||||
'primary': self.tool.collect_primary_datasets(out_data, self.working_directory, input_ext)
|
||||
}
|
||||
self.tool.collect_dynamic_collections(
|
||||
out_collections,
|
||||
job_working_directory=self.working_directory,
|
||||
inp_data=inp_data,
|
||||
job=job,
|
||||
)
|
||||
param_dict.update({'__collected_datasets__': collected_datasets})
|
||||
# Certain tools require tasks to be completed after job execution
|
||||
# ( this used to be performed in the "exec_after_process" hook, but hooks are deprecated ).
|
||||
@@ -1768,7 +1783,11 @@ class ComputeEnvironment( object ):
|
||||
|
||||
@abstractmethod
|
||||
def new_file_path( self ):
|
||||
""" Location to dump new files for this job on remote server. """
|
||||
""" Absolute path to dump new files for this job on compute server. """
|
||||
|
||||
@abstractmethod
|
||||
def tool_directory( self ):
|
||||
""" Absolute path to tool files for this job on compute server. """
|
||||
|
||||
@abstractmethod
|
||||
def version_path( self ):
|
||||
@@ -1821,6 +1840,9 @@ class SharedComputeEnvironment( SimpleComputeEnvironment ):
|
||||
def version_path( self ):
|
||||
return self.job_wrapper.get_version_string_path()
|
||||
|
||||
def tool_directory( self ):
|
||||
return os.path.abspath(self.job_wrapper.tool.tool_dir)
|
||||
|
||||
|
||||
class NoopQueue( object ):
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""
|
||||
Actions to be run at job completion. Currently only used in workflows.
|
||||
Actions to be run at job completion (or output hda creation, as in the case of
|
||||
immediate_actions listed below. Currently only used in workflows.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
@@ -420,6 +421,42 @@ class DeleteIntermediatesAction(DefaultJobAction):
|
||||
return "Delete parent datasets of this step created in this workflow that aren't flagged as outputs."
|
||||
|
||||
|
||||
class TagDatasetAction(DefaultJobAction):
|
||||
name = "TagDatasetAction"
|
||||
verbose_name = "Add tag to dataset"
|
||||
|
||||
@classmethod
|
||||
def execute(cls, app, sa_session, action, job, replacement_dict):
|
||||
if action.action_arguments:
|
||||
tags = [t.strip() for t in action.action_arguments.get('tags', '').split(',')]
|
||||
if tags:
|
||||
for dataset_assoc in job.output_datasets:
|
||||
if action.output_name == '' or dataset_assoc.name == action.output_name:
|
||||
app.tag_handler.set_tags_from_list( job.user, dataset_assoc.dataset, tags)
|
||||
sa_session.flush()
|
||||
|
||||
@classmethod
|
||||
def get_config_form(cls, trans):
|
||||
form = """
|
||||
if (pja.action_arguments && pja.action_arguments.tags){
|
||||
p_str += "<label for='pja__"+pja.output_name+"__TagDatasetAction__tags'>Tags:</label>\
|
||||
<input type='text' name='pja__"+pja.output_name+"__TagDatasetAction__tags' value=\\"" + pja.action_arguments.tags.replace(/"/g, """) + "\\"/>";
|
||||
}
|
||||
else{
|
||||
p_str += "<label for='pja__"+pja.output_name+"__TagDatasetAction__tags'>Tags:</label>\
|
||||
<input type='text' name='pja__"+pja.output_name+"__TagDatasetAction__tags' value=''/>";
|
||||
}
|
||||
"""
|
||||
return get_form_template(cls.name, cls.verbose_name, form, "This action will set tags for the dataset.")
|
||||
|
||||
@classmethod
|
||||
def get_short_str(cls, pja):
|
||||
if pja.action_arguments and pja.action_arguments.get('tags', ''):
|
||||
return "Add tag(s) '%s' to '%s'." % (pja.action_arguments['tags'], pja.output_name)
|
||||
else:
|
||||
return "Tag addition action used without a tag specified. No tag will be added."
|
||||
|
||||
|
||||
class ActionBox(object):
|
||||
|
||||
actions = { "RenameDatasetAction": RenameDatasetAction,
|
||||
@@ -428,11 +465,13 @@ class ActionBox(object):
|
||||
"ColumnSetAction": ColumnSetAction,
|
||||
"EmailAction": EmailAction,
|
||||
"DeleteIntermediatesAction": DeleteIntermediatesAction,
|
||||
"TagDatasetAction": TagDatasetAction,
|
||||
}
|
||||
public_actions = ['RenameDatasetAction', 'ChangeDatatypeAction',
|
||||
'ColumnSetAction', 'EmailAction',
|
||||
'DeleteIntermediatesAction']
|
||||
immediate_actions = ['ChangeDatatypeAction', 'RenameDatasetAction']
|
||||
'DeleteIntermediatesAction', 'TagDatasetAction']
|
||||
immediate_actions = ['ChangeDatatypeAction', 'RenameDatasetAction',
|
||||
'TagDatasetAction']
|
||||
|
||||
@classmethod
|
||||
def get_short_str(cls, action):
|
||||
|
||||
@@ -5,6 +5,7 @@ from os.path import abspath
|
||||
|
||||
CAPTURE_RETURN_CODE = "return_code=$?"
|
||||
YIELD_CAPTURED_CODE = 'sh -c "exit $return_code"'
|
||||
DEFAULT_SHELL = "/bin/sh"
|
||||
|
||||
from logging import getLogger
|
||||
log = getLogger( __name__ )
|
||||
@@ -45,28 +46,21 @@ def build_command(
|
||||
if not container:
|
||||
__handle_dependency_resolution(commands_builder, job_wrapper, remote_command_params)
|
||||
|
||||
if container:
|
||||
# Stop now and build command before handling metadata and copying
|
||||
# working directory files back. These should always happen outside
|
||||
# of docker container - no security implications when generating
|
||||
# metadata and means no need for Galaxy to be available to container
|
||||
# and not copying workdir outputs back means on can be more restrictive
|
||||
# of where container can write to in some circumstances.
|
||||
|
||||
local_container_script = join( job_wrapper.working_directory, "container.sh" )
|
||||
fh = file( local_container_script, "w" )
|
||||
fh.write( "#!/bin/sh\n%s" % commands_builder.build() )
|
||||
fh.close()
|
||||
chmod( local_container_script, 0755 )
|
||||
|
||||
compute_container_script = local_container_script
|
||||
if 'working_directory' in remote_command_params:
|
||||
compute_container_script = "/bin/sh %s" % join(remote_command_params['working_directory'], "container.sh")
|
||||
|
||||
run_in_container_command = container.containerize_command(
|
||||
compute_container_script
|
||||
)
|
||||
commands_builder = CommandsBuilder( run_in_container_command )
|
||||
if container or job_wrapper.commands_in_new_shell:
|
||||
externalized_commands = __externalize_commands(job_wrapper, commands_builder, remote_command_params)
|
||||
if container:
|
||||
# Stop now and build command before handling metadata and copying
|
||||
# working directory files back. These should always happen outside
|
||||
# of docker container - no security implications when generating
|
||||
# metadata and means no need for Galaxy to be available to container
|
||||
# and not copying workdir outputs back means on can be more restrictive
|
||||
# of where container can write to in some circumstances.
|
||||
run_in_container_command = container.containerize_command(
|
||||
externalized_commands
|
||||
)
|
||||
commands_builder = CommandsBuilder( run_in_container_command )
|
||||
else:
|
||||
commands_builder = CommandsBuilder( externalized_commands )
|
||||
|
||||
if include_work_dir_outputs:
|
||||
__handle_work_dir_outputs(commands_builder, job_wrapper, runner, remote_command_params)
|
||||
@@ -77,6 +71,20 @@ def build_command(
|
||||
return commands_builder.build()
|
||||
|
||||
|
||||
def __externalize_commands(job_wrapper, commands_builder, remote_command_params, script_name="tool_script.sh"):
|
||||
local_container_script = join( job_wrapper.working_directory, script_name )
|
||||
fh = file( local_container_script, "w" )
|
||||
fh.write( "#!%s\n%s" % (DEFAULT_SHELL, commands_builder.build()))
|
||||
fh.close()
|
||||
chmod( local_container_script, 0755 )
|
||||
|
||||
commands = local_container_script
|
||||
if 'working_directory' in remote_command_params:
|
||||
commands = "%s %s" % (DEFAULT_SHELL, join(remote_command_params['working_directory'], script_name))
|
||||
log.info("Built script [%s] for tool command[%s]" % (local_container_script, commands))
|
||||
return commands
|
||||
|
||||
|
||||
def __handle_version_command(commands_builder, job_wrapper):
|
||||
# Prepend version string
|
||||
write_version_cmd = job_wrapper.write_version_cmd
|
||||
|
||||
@@ -356,7 +356,7 @@ class DataTransfer( object ):
|
||||
for i, step in enumerate( workflow.steps ):
|
||||
job = None
|
||||
if step.type == 'tool' or step.type is None:
|
||||
tool = self.app.toolbox.tools_by_id[ step.tool_id ]
|
||||
tool = self.app.toolbox.get_tool( step.tool_id )
|
||||
def callback( input, value, prefixed_name, prefixed_label ):
|
||||
if isinstance( input, DataToolParameter ):
|
||||
if prefixed_name in step.input_connections_by_name:
|
||||
|
||||
@@ -119,7 +119,7 @@ class JobHandlerQueue( object ):
|
||||
& ( 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:
|
||||
if not self.app.toolbox.has_tool( job.tool_id, job.tool_version, exact=True ):
|
||||
log.warning( "(%s) Tool '%s' removed from tool config, unable to recover job" % ( job.id, job.tool_id ) )
|
||||
self.job_wrapper( job ).fail( 'This tool was disabled before the job completed. Please contact your Galaxy administrator.' )
|
||||
elif job.job_runner_name is not None and job.job_runner_external_id is None:
|
||||
|
||||
@@ -216,7 +216,7 @@ class BaseJobRunner( object ):
|
||||
job_tool = job_wrapper.tool
|
||||
for (joda, dataset) in self._walk_dataset_outputs( job ):
|
||||
if joda and job_tool:
|
||||
hda_tool_output = job_tool.outputs.get( joda.name, None )
|
||||
hda_tool_output = job_tool.find_output_def( joda.name )
|
||||
if hda_tool_output and hda_tool_output.from_work_dir:
|
||||
# Copy from working dir to HDA.
|
||||
# TODO: move instead of copy to save time?
|
||||
|
||||
@@ -708,3 +708,6 @@ class PulsarComputeEnvironment( ComputeEnvironment ):
|
||||
|
||||
def unstructured_path_rewriter( self ):
|
||||
return self.rewriter
|
||||
|
||||
def tool_directory( self ):
|
||||
return self._tool_dir
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
""" Entry point for starting Galaxy without starting as part of a web server.
|
||||
|
||||
Example Usage: Start a job/workflow handler without a web server and with
|
||||
a given name using.
|
||||
|
||||
python lib/galaxy/main.py --server-name handler0
|
||||
|
||||
Start as a daemon with (requires daemonized - install with 'pip install daemonize'):
|
||||
|
||||
python lib/galaxy/main.py -d --daemon-log-file=handler0-daemon.log --pid-file handler0.pid --server-name handler0
|
||||
|
||||
In daemon mode logging of Galaxy (as opposed to this script) is configured via
|
||||
a loggers section in Galaxy's ini file - this can be overridden with sensible
|
||||
defaults logging to a single file with the following:
|
||||
|
||||
python lib/galaxy/main.py -d --server-name handler0 --daemon-log-file=handler0-daemon.log --pid-file handler0.pid --log-file handler0.log
|
||||
|
||||
"""
|
||||
import logging
|
||||
from logging.config import fileConfig
|
||||
|
||||
import functools
|
||||
import os
|
||||
import time
|
||||
import sys
|
||||
|
||||
try:
|
||||
import ConfigParser as configparser
|
||||
except ImportError:
|
||||
import configparser
|
||||
|
||||
try:
|
||||
from daemonize import Daemonize
|
||||
except ImportError:
|
||||
Daemonize = None
|
||||
|
||||
# Vaguely Python 2.6 compatibile ArgumentParser import
|
||||
try:
|
||||
from argparse import ArgumentParser
|
||||
except ImportError:
|
||||
from optparse import OptionParser
|
||||
|
||||
class ArgumentParser(OptionParser):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.delegate = OptionParser(**kwargs)
|
||||
|
||||
def add_argument(self, *args, **kwargs):
|
||||
if "required" in kwargs:
|
||||
del kwargs["required"]
|
||||
return self.delegate.add_option(*args, **kwargs)
|
||||
|
||||
def parse_args(self, args=None):
|
||||
(options, args) = self.delegate.parse_args(args)
|
||||
return options
|
||||
|
||||
REQUIRES_DAEMONIZE_MESSAGE = "Attempted to use Galaxy in daemon mode, but daemonize is unavailable."
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
GALAXY_ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
GALAXY_LIB_DIR = os.path.join(GALAXY_ROOT_DIR, "lib")
|
||||
DEFAULT_INI_APP = "main"
|
||||
DEFAULT_INIS = ["config/galaxy.ini", "universe_wsgi.ini", "config/galaxy.ini.sample"]
|
||||
|
||||
DEFAULT_PID = "galaxy.pid"
|
||||
DEFAULT_VERBOSE = True
|
||||
DESCRIPTION = "Daemonized entry point for Galaxy."
|
||||
|
||||
|
||||
def load_galaxy_app(
|
||||
config_builder,
|
||||
config_env=False,
|
||||
log=None,
|
||||
**kwds
|
||||
):
|
||||
# Allow specification of log so daemon can reuse properly configured one.
|
||||
if log is None:
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# If called in daemon mode, set the ROOT directory and ensure Galaxy is on
|
||||
# sys.path.
|
||||
if config_env:
|
||||
try:
|
||||
os.chdir(GALAXY_ROOT_DIR)
|
||||
except Exception:
|
||||
log.exception("Failed to chdir")
|
||||
raise
|
||||
try:
|
||||
sys.path.append(GALAXY_LIB_DIR)
|
||||
except Exception:
|
||||
log.exception("Failed to add Galaxy to sys.path")
|
||||
raise
|
||||
|
||||
config_builder.setup_logging()
|
||||
from galaxy.util.properties import load_app_properties
|
||||
kwds = config_builder.app_kwds()
|
||||
kwds = load_app_properties(**kwds)
|
||||
from galaxy.app import UniverseApplication
|
||||
app = UniverseApplication(
|
||||
global_conf={"__file__": config_builder.ini_path},
|
||||
**kwds
|
||||
)
|
||||
return app
|
||||
|
||||
|
||||
def app_loop(args, log):
|
||||
try:
|
||||
config_builder = GalaxyConfigBuilder(args)
|
||||
galaxy_app = load_galaxy_app(
|
||||
config_builder,
|
||||
config_env=True,
|
||||
log=log,
|
||||
)
|
||||
except BaseException:
|
||||
log.exception("Failed to initialize Galaxy application")
|
||||
raise
|
||||
sleep = True
|
||||
while sleep:
|
||||
try:
|
||||
time.sleep(5)
|
||||
except KeyboardInterrupt:
|
||||
sleep = False
|
||||
except SystemExit:
|
||||
sleep = False
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
galaxy_app.shutdown()
|
||||
except Exception:
|
||||
log.exception("Failed to shutdown Galaxy application")
|
||||
raise
|
||||
|
||||
|
||||
def absolute_config_path(path, galaxy_root):
|
||||
if path and not os.path.isabs(path):
|
||||
path = os.path.join(galaxy_root, path)
|
||||
return path
|
||||
|
||||
|
||||
def find_ini(supplied_ini, galaxy_root):
|
||||
if supplied_ini:
|
||||
return supplied_ini
|
||||
|
||||
# If not explicitly supplied an ini, check server.ini and then
|
||||
# just restort to sample if that has not been configured.
|
||||
for guess in DEFAULT_INIS:
|
||||
ini_path = os.path.join(galaxy_root, guess)
|
||||
if os.path.exists(ini_path):
|
||||
return ini_path
|
||||
|
||||
return guess
|
||||
|
||||
|
||||
class GalaxyConfigBuilder(object):
|
||||
""" Generate paste-like configuration from supplied command-line arguments.
|
||||
"""
|
||||
|
||||
def __init__(self, args=None, **kwds):
|
||||
ini_path = kwds.get("ini_path", None) or (args and args.ini_path)
|
||||
# If given app_conf_path - use that - else we need to ensure we have an
|
||||
# ini path.
|
||||
if not ini_path:
|
||||
galaxy_root = kwds.get("galaxy_root", GALAXY_ROOT_DIR)
|
||||
ini_path = find_ini(ini_path, galaxy_root)
|
||||
ini_path = absolute_config_path(ini_path, galaxy_root=galaxy_root)
|
||||
self.ini_path = ini_path
|
||||
self.app_name = kwds.get("app") or (args and args.app) or DEFAULT_INI_APP
|
||||
self.log_file = (args and args.log_file)
|
||||
|
||||
@classmethod
|
||||
def populate_options(cls, arg_parser):
|
||||
arg_parser.add_argument("-c", "--ini-path", default=None, help="Galaxy ini config file (defaults to config/galaxy.ini)")
|
||||
arg_parser.add_argument("--app", default=DEFAULT_INI_APP, help="app section in ini file (defaults to main)")
|
||||
arg_parser.add_argument("-d", "--daemonize", default=False, help="Daemonzie process", action="store_true")
|
||||
arg_parser.add_argument("--daemon-log-file", default=None, help="log file for daemon script ")
|
||||
arg_parser.add_argument("--log-file", default=None, help="Galaxy log file (overrides log configuration in ini_path if set)")
|
||||
arg_parser.add_argument("--pid-file", default=DEFAULT_PID, help="pid file (default is %s)" % DEFAULT_PID)
|
||||
arg_parser.add_argument("--server-name", default=None, help="set a galaxy server name")
|
||||
|
||||
def app_kwds(self):
|
||||
config = dict(
|
||||
ini_file=self.ini_path,
|
||||
ini_section="app:%s" % self.app_name,
|
||||
)
|
||||
return config
|
||||
|
||||
def setup_logging(self):
|
||||
# Galaxy will attempt to setup logging if loggers is not present in
|
||||
# ini config file - this handles that loggers block however if present
|
||||
# (the way paste normally would)
|
||||
if not self.ini_path:
|
||||
return
|
||||
raw_config = configparser.ConfigParser()
|
||||
raw_config.read([self.ini_path])
|
||||
if raw_config.has_section('loggers'):
|
||||
config_file = os.path.abspath(self.ini_path)
|
||||
fileConfig(
|
||||
config_file,
|
||||
dict(__file__=config_file, here=os.path.dirname(config_file))
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
arg_parser = ArgumentParser(description=DESCRIPTION)
|
||||
GalaxyConfigBuilder.populate_options(arg_parser)
|
||||
args = arg_parser.parse_args()
|
||||
if args.log_file:
|
||||
os.environ["GALAXY_CONFIG_LOG_DESTINATION"] = os.path.abspath(args.log_file)
|
||||
if args.server_name:
|
||||
os.environ["GALAXY_CONFIG_SERVER_NAME"] = args.server_name
|
||||
pid_file = args.pid_file
|
||||
|
||||
log.setLevel(logging.DEBUG)
|
||||
log.propagate = False
|
||||
if args.daemonize:
|
||||
if Daemonize is None:
|
||||
raise ImportError(REQUIRES_DAEMONIZE_MESSAGE)
|
||||
|
||||
keep_fds = []
|
||||
if args.daemon_log_file:
|
||||
fh = logging.FileHandler(args.daemon_log_file, "w")
|
||||
fh.setLevel(logging.DEBUG)
|
||||
log.addHandler(fh)
|
||||
keep_fds.append(fh.stream.fileno())
|
||||
else:
|
||||
fh = logging.StreamHandler(sys.stderr)
|
||||
fh.setLevel(logging.DEBUG)
|
||||
log.addHandler(fh)
|
||||
|
||||
daemon = Daemonize(
|
||||
app="galaxy",
|
||||
pid=pid_file,
|
||||
action=functools.partial(app_loop, args, log),
|
||||
verbose=DEFAULT_VERBOSE,
|
||||
logger=log,
|
||||
keep_fds=keep_fds,
|
||||
)
|
||||
daemon.start()
|
||||
else:
|
||||
app_loop(args, log)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Mixins for Annotatable model managers and serializers.
|
||||
"""
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
class AnnotatableManagerMixin( object ):
|
||||
#: class of AnnotationAssociation (e.g. HistoryAnnotationAssociation)
|
||||
annotation_assoc = None
|
||||
|
||||
# TODO: most of this seems to be covered by item_attrs.UsesAnnotations
|
||||
# TODO: use these below (serializer/deserializer)
|
||||
def user_annotation( self, trans, item, user ):
|
||||
return item.get_item_annotation_str( self.app.model.context, user, item )
|
||||
|
||||
def owner_annotation( self, trans, item ):
|
||||
return self.user_annotation( trans, item, item.user )
|
||||
|
||||
def delete_annotation( self, trans, item, user ):
|
||||
return item.delete_item_annotation( self.app.model.context, user, item )
|
||||
|
||||
def annotate( self, trans, item, user, annotation ):
|
||||
if annotation is None:
|
||||
self.delete_annotation( self, trans, item, user )
|
||||
return None
|
||||
|
||||
annotation_obj = item.add_item_annotation( self.app.model.context, user, item, annotation )
|
||||
return annotation_obj.annotation
|
||||
|
||||
#def by_user( self, trans, user, **kwargs ):
|
||||
# pass
|
||||
|
||||
|
||||
class AnnotatableSerializerMixin( object ):
|
||||
|
||||
def add_serializers( self ):
|
||||
self.serializers[ 'annotation' ] = self.serialize_annotation
|
||||
|
||||
def serialize_annotation( self, trans, item, key ):
|
||||
"""
|
||||
Get and serialize an `item`'s annotation.
|
||||
"""
|
||||
# user = item.user
|
||||
#TODO: trans
|
||||
user = trans.user
|
||||
sa_session = self.app.model.context
|
||||
returned = item.get_item_annotation_str( sa_session, user, item )
|
||||
return returned
|
||||
|
||||
|
||||
class AnnotatableDeserializerMixin( object ):
|
||||
|
||||
def add_deserializers( self ):
|
||||
self.deserializers[ 'annotation' ] = self.deserialize_annotation
|
||||
|
||||
def deserialize_annotation( self, trans, item, key, val ):
|
||||
"""
|
||||
Make sure `val` is a valid annotation and assign it, deleting any existing
|
||||
if `val` is None.
|
||||
"""
|
||||
val = self.validate.nullable_basestring( key, val )
|
||||
|
||||
sa_session = self.app.model.context
|
||||
#TODO: trans
|
||||
user = trans.user
|
||||
if val is None:
|
||||
item.delete_item_annotation( sa_session, user, item )
|
||||
return None
|
||||
|
||||
annotated_item = item.add_item_annotation( sa_session, user, item, val )
|
||||
return annotated_item.annotation
|
||||
+927
-12
@@ -1,23 +1,51 @@
|
||||
"""
|
||||
Keeps the older BaseController security and fetching methods and also
|
||||
defines a base ModelManager, ModelSerializer, and ModelDeserializer.
|
||||
|
||||
ModelManagers are used for operations on models that occur outside the scope of
|
||||
a single model object, such as:
|
||||
- object creation
|
||||
- object lookup
|
||||
- interactions between 2+ objects of different model classes
|
||||
|
||||
(Since these were to replace model Mixins from
|
||||
web/framework/base/controller.py the rule of thumb used there also generally
|
||||
has been applied here: if it uses the trans or sa_session, put it in a manager
|
||||
and not the model.)
|
||||
|
||||
ModelSerializers allow flexible conversion of model objects to dictionaries.
|
||||
They control what keys are sent, how values are simplified, can remap keys,
|
||||
and allow both predefined and user controlled key sets.
|
||||
|
||||
ModelDeserializers control how a model validates and process an incoming
|
||||
attribute change to a model object.
|
||||
"""
|
||||
# TODO: it may be there's a better way to combine the above three classes
|
||||
# such as: a single flat class, serializers being singletons in the manager, etc.
|
||||
# instead of the three separate classes. With no 'apparent' perfect scheme
|
||||
# I'm opting to just keep them separate.
|
||||
|
||||
import pkg_resources
|
||||
pkg_resources.require( "SQLAlchemy >= 0.4" )
|
||||
import sqlalchemy
|
||||
|
||||
pkg_resources.require("repoze.lru") # used by Routes
|
||||
pkg_resources.require("Routes")
|
||||
import routes
|
||||
|
||||
from galaxy import exceptions
|
||||
|
||||
from galaxy import model
|
||||
from galaxy import web
|
||||
from galaxy.model import tool_shed_install
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
class ModelManager( object ):
|
||||
pass
|
||||
|
||||
|
||||
class ModelSerializer( object ):
|
||||
pass
|
||||
|
||||
|
||||
# ==== accessors from base/controller.py
|
||||
def security_check( trans, item, check_ownership=False, check_accessible=False ):
|
||||
""" Security checks for an item: checks if (a) user owns item or (b) item
|
||||
"""
|
||||
Security checks for an item: checks if (a) user owns item or (b) item
|
||||
is accessible to user. This is a generic method for dealing with objects
|
||||
uniformly from the older controller mixin code - however whenever possible
|
||||
the managers for a particular model should be used to perform security
|
||||
@@ -49,7 +77,10 @@ def security_check( trans, item, check_ownership=False, check_accessible=False )
|
||||
|
||||
|
||||
def get_class( class_name ):
|
||||
""" Returns the class object that a string denotes. Without this method, we'd have to do eval(<class_name>). """
|
||||
"""
|
||||
Returns the class object that a string denotes. Without this method, we'd have
|
||||
to do eval(<class_name>).
|
||||
"""
|
||||
if class_name == 'History':
|
||||
item_class = model.History
|
||||
elif class_name == 'HistoryDatasetAssociation':
|
||||
@@ -118,3 +149,887 @@ def get_object( trans, id, class_name, check_ownership=False, check_accessible=F
|
||||
% ( class_name, getattr( item, 'name', id ) ), type="warning" )
|
||||
return item
|
||||
|
||||
|
||||
# =============================================================================
|
||||
class ModelManager( object ):
|
||||
"""
|
||||
Base class for all model/resource managers.
|
||||
|
||||
Provides common queries and CRUD operations as a (hopefully) light layer
|
||||
over the ORM.
|
||||
"""
|
||||
model_class = object
|
||||
foreign_key_name = None
|
||||
|
||||
def __init__( self, app ):
|
||||
self.app = app
|
||||
|
||||
def session( self ):
|
||||
return self.app.model.context
|
||||
|
||||
def _session_setattr( self, item, attr, val, fn=None, flush=True ):
|
||||
if fn:
|
||||
fn( item, attr, val )
|
||||
else:
|
||||
setattr( item, attr, val )
|
||||
|
||||
self.session().add( item )
|
||||
if flush:
|
||||
self.session().flush()
|
||||
return item
|
||||
|
||||
# .... query foundation wrapper
|
||||
def query( self, trans, eagerloads=True, filters=None, order_by=None, limit=None, offset=None, **kwargs ):
|
||||
"""
|
||||
Return a basic query from model_class, filters, order_by, and limit and offset.
|
||||
|
||||
Set eagerloads to False to disable them for this query.
|
||||
"""
|
||||
query = self.session().query( self.model_class )
|
||||
# joined table loading
|
||||
if eagerloads is False:
|
||||
query = query.enable_eagerloads( False )
|
||||
|
||||
query = self._apply_orm_filters( query, filters )
|
||||
query = self._apply_order_by( query, order_by )
|
||||
query = self._apply_orm_limit_offset( query, limit, offset )
|
||||
return query
|
||||
|
||||
# .... filters
|
||||
def _apply_orm_filters( self, query, filters ):
|
||||
"""
|
||||
Add any filters to the given query.
|
||||
"""
|
||||
if filters is None:
|
||||
return query
|
||||
|
||||
if not isinstance( filters, list ):
|
||||
filters = [ filters ]
|
||||
# note: implicit AND
|
||||
for filter in filters:
|
||||
query = query.filter( filter )
|
||||
return query
|
||||
|
||||
def _munge_filters( self, filtersA, filtersB ):
|
||||
"""
|
||||
Combine two lists into a single list.
|
||||
|
||||
(While allowing them to be None, non-lists, or lists.)
|
||||
"""
|
||||
# TODO: there's nothing specifically filter or model-related here - move to util
|
||||
if filtersA is None:
|
||||
return filtersB
|
||||
if filtersB is None:
|
||||
return filtersA
|
||||
if not isinstance( filtersA, list ):
|
||||
filtersA = [ filtersA ]
|
||||
if not isinstance( filtersB, list ):
|
||||
filtersB = [ filtersB ]
|
||||
return filtersA + filtersB
|
||||
|
||||
# .... order, limit, and offset
|
||||
def _apply_order_by( self, query, order_by ):
|
||||
"""
|
||||
Return the query after adding the order_by clauses.
|
||||
|
||||
Use the manager's default_order_by if order_by is None.
|
||||
"""
|
||||
if order_by is None:
|
||||
return query.order_by( *self._default_order_by() )
|
||||
|
||||
if isinstance( order_by, ( list, tuple ) ):
|
||||
return query.order_by( *order_by )
|
||||
return query.order_by( order_by )
|
||||
|
||||
def _default_order_by( self ):
|
||||
"""
|
||||
Returns a tuple of columns for the default order when getting multiple models.
|
||||
"""
|
||||
return ( self.model_class.create_time, )
|
||||
|
||||
def _apply_orm_limit_offset( self, query, limit, offset ):
|
||||
"""
|
||||
Return the query after applying the given limit and offset (if not None).
|
||||
"""
|
||||
if limit is not None:
|
||||
query = query.limit( limit )
|
||||
if offset is not None:
|
||||
query = query.offset( offset )
|
||||
return query
|
||||
|
||||
# .... query resolution
|
||||
def one( self, trans, **kwargs ):
|
||||
"""
|
||||
Sends kwargs to build the query and returns one and only one model.
|
||||
"""
|
||||
query = self.query( trans, **kwargs )
|
||||
return self._one_with_recast_errors( query )
|
||||
|
||||
def _one_with_recast_errors( self, query ):
|
||||
"""
|
||||
Call sqlalchemy's one and recast errors to serializable errors if any.
|
||||
|
||||
:raises exceptions.ObjectNotFound: if no model is found
|
||||
:raises exceptions.InconsistentDatabase: if more than one model is found
|
||||
"""
|
||||
# overridden to raise serializable errors
|
||||
try:
|
||||
return query.one()
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
raise exceptions.ObjectNotFound( self.model_class.__name__ + ' not found' )
|
||||
except sqlalchemy.orm.exc.MultipleResultsFound:
|
||||
raise exceptions.InconsistentDatabase( 'found more than one ' + self.model_class.__name__ )
|
||||
|
||||
def _one_or_none( self, query ):
|
||||
"""
|
||||
Return the object if found, None if it's not.
|
||||
|
||||
:raises exceptions.InconsistentDatabase: if more than one model is found
|
||||
"""
|
||||
try:
|
||||
return self._one_with_recast_errors( query )
|
||||
except exceptions.ObjectNotFound:
|
||||
return None
|
||||
|
||||
#NOTE: at this layer, all ids are expected to be decoded and in int form
|
||||
def by_id( self, trans, id, **kwargs ):
|
||||
"""
|
||||
Gets a model by primary id.
|
||||
"""
|
||||
id_filter = self.model_class.id == id
|
||||
return self.one( trans, filters=id_filter, **kwargs )
|
||||
|
||||
# .... multirow queries
|
||||
def _orm_list( self, trans, query=None, **kwargs ):
|
||||
"""
|
||||
Sends kwargs to build the query return all models found.
|
||||
"""
|
||||
query = query or self.query( trans, **kwargs )
|
||||
return query.all()
|
||||
|
||||
#def list( self, trans, query=None, filters=None, order_by=None, limit=None, offset=None, **kwargs ):
|
||||
def list( self, trans, filters=None, order_by=None, limit=None, offset=None, **kwargs ):
|
||||
"""
|
||||
Returns all objects matching the given filters
|
||||
"""
|
||||
orm_filters, fn_filters = self._split_filters( filters )
|
||||
if not fn_filters:
|
||||
# if no fn_filtering required, we can use the 'all orm' version with limit offset
|
||||
return self._orm_list( trans, filters=orm_filters, order_by=order_by, limit=limit, offset=offset, **kwargs )
|
||||
|
||||
# fn filters will change the number of items returnable by limit/offset - remove them here from the orm query
|
||||
query = self.query( trans, filters=orm_filters, order_by=order_by, limit=None, offset=None, **kwargs )
|
||||
items = query.all()
|
||||
|
||||
# apply limit, offset after SQL filtering
|
||||
items = self._apply_fn_filters_gen( items, fn_filters )
|
||||
return list( self._apply_fn_limit_offset_gen( items, limit, offset ) )
|
||||
|
||||
def _split_filters( self, filters ):
|
||||
"""
|
||||
Splits `filters` into a tuple of two lists:
|
||||
a list of filters to be added to the SQL query
|
||||
and a list of functional filters to be applied after the SQL query.
|
||||
"""
|
||||
orm_filters, fn_filters = ( [], [] )
|
||||
if filters is None:
|
||||
return ( orm_filters, fn_filters )
|
||||
if not isinstance( filters, list ):
|
||||
filters = [ filters ]
|
||||
for filter_ in filters:
|
||||
if self._is_fn_filter( filter_ ):
|
||||
fn_filters.append( filter_ )
|
||||
else:
|
||||
orm_filters.append( filter_ )
|
||||
return ( orm_filters, fn_filters )
|
||||
|
||||
def _is_fn_filter( self, filter_ ):
|
||||
"""
|
||||
Returns True if `filter_` is a functional filter to be applied after the SQL query.
|
||||
"""
|
||||
return callable( filter_ )
|
||||
|
||||
def _apply_fn_filters_gen( self, items, filters ):
|
||||
"""
|
||||
If all the filter functions in `filters` return True for an item in `items`,
|
||||
yield that item.
|
||||
"""
|
||||
#cpu-expensive
|
||||
for item in items:
|
||||
filter_results = map( lambda f: f( item ), filters )
|
||||
if all( filter_results ):
|
||||
yield item
|
||||
|
||||
def _apply_fn_limit_offset_gen( self, items, limit, offset ):
|
||||
"""
|
||||
Iterate over `items` and begin yielding items after
|
||||
`offset` number of items and stop when we've yielded
|
||||
`limit` number of items.
|
||||
"""
|
||||
# change negative limit, offset to None
|
||||
if limit is not None and limit < 0:
|
||||
limit = None
|
||||
if offset is not None and offset < 0:
|
||||
offset = None
|
||||
|
||||
yielded = 0
|
||||
for i, item in enumerate( items ):
|
||||
if offset is not None and i < offset:
|
||||
continue
|
||||
if limit is not None and yielded >= limit:
|
||||
break
|
||||
yield item
|
||||
yielded += 1
|
||||
|
||||
def by_ids( self, trans, ids, filters=None, **kwargs ):
|
||||
"""
|
||||
Returns an in-order list of models with the matching ids in `ids`.
|
||||
"""
|
||||
ids_filter = self.model_class.id.in_( ids )
|
||||
found = self.list( trans, filters=self._munge_filters( ids_filter, filters ), **kwargs )
|
||||
# TODO: this does not order by the original 'ids' array
|
||||
|
||||
# ...could use get (supposedly since found are in the session, the db won't be hit twice)
|
||||
# return map( self.session().query( self.model_class ).get, ids )
|
||||
|
||||
# ...could implement own version here - slow?
|
||||
return self._order_items_by_id( ids, found )
|
||||
|
||||
def _order_items_by_id( self, ids, items ):
|
||||
"""
|
||||
Given a list of (unique) ids and a list of items having an 'id' attribute,
|
||||
return items that have the given ids in that order.
|
||||
|
||||
If an id in ids is not found or if an item in items doesn't have a given
|
||||
id, they will not be in the returned list.
|
||||
"""
|
||||
ID_ATTR_NAME = 'id'
|
||||
# TODO:?? aside from sqlalx.get mentioned above, I haven't seen an in-SQL way
|
||||
# to make this happen. This may not be the most efficient way either.
|
||||
# NOTE: that this isn't sorting by id - this is matching the order in items to the order in ids
|
||||
# move items list into dict by id
|
||||
item_dict = {}
|
||||
for item in items:
|
||||
item_id = getattr( item, ID_ATTR_NAME, None )
|
||||
if item_id:
|
||||
item_dict[ item_id ] = item
|
||||
# pull from map in order of ids
|
||||
in_order = []
|
||||
for id in ids:
|
||||
if id in item_dict:
|
||||
in_order.append( item_dict[ id ] )
|
||||
return in_order
|
||||
|
||||
def create( self, trans, flush=True, *args, **kwargs ):
|
||||
"""
|
||||
Generically create a new model.
|
||||
"""
|
||||
# override in subclasses
|
||||
item = self.model_class( *args, **kwargs )
|
||||
self.session().add( item )
|
||||
if flush:
|
||||
self.session().flush()
|
||||
return item
|
||||
|
||||
def copy( self, trans, item, **kwargs ):
|
||||
"""
|
||||
Clone or copy an item.
|
||||
"""
|
||||
raise exceptions.NotImplemented( 'Abstract method' )
|
||||
|
||||
def update( self, trans, item, new_values, flush=True, **kwargs ):
|
||||
"""
|
||||
Given a dictionary of new values, update `item` and return it.
|
||||
|
||||
..note: NO validation or deserialization occurs here.
|
||||
"""
|
||||
self.session().add( item )
|
||||
for key, value in new_values.items():
|
||||
if hasattr( item, key ):
|
||||
setattr( item, key, value )
|
||||
if flush:
|
||||
self.session().flush()
|
||||
return item
|
||||
|
||||
# TODO: yagni?
|
||||
def associate( self, trans, associate_with, item, foreign_key_name=None ):
|
||||
"""
|
||||
Generically associate `item` with `associate_with` based on `foreign_key_name`.
|
||||
"""
|
||||
foreign_key_name = foreign_key_name or self.foreign_key_name
|
||||
setattr( associate_with, foreign_key_name, item )
|
||||
return item
|
||||
|
||||
def query_associated( self, trans, associated_model_class, item, foreign_key_name=None ):
|
||||
"""
|
||||
Generically query other items that have been associated with this `item`.
|
||||
"""
|
||||
foreign_key_name = foreign_key_name or self.foreign_key_name
|
||||
foreign_key = getattr( associated_model_class, foreign_key_name )
|
||||
return self.session().query( associated_model_class ).filter( foreign_key == item )
|
||||
|
||||
# a rename of sql DELETE to differentiate from the Galaxy notion of mark_as_deleted
|
||||
# def destroy( self, trans, item, **kwargs ):
|
||||
# return item
|
||||
|
||||
|
||||
# ==== SERIALIZERS/to_dict,from_dict
|
||||
class ModelSerializingError( exceptions.InternalServerError ):
|
||||
"""Thrown when request model values can't be serialized"""
|
||||
pass
|
||||
|
||||
|
||||
class ModelDeserializingError( exceptions.ObjectAttributeInvalidException ):
|
||||
"""Thrown when an incoming value isn't usable by the model
|
||||
(bad type, out of range, etc.)
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ModelSerializer( object ):
|
||||
"""
|
||||
Turns models into JSONable dicts.
|
||||
|
||||
Maintains a map of requestable keys and the Callable() serializer functions
|
||||
that should be called for those keys.
|
||||
E.g. { 'x' : lambda trans, item, key: item.x, ... }
|
||||
|
||||
Note: if a key to serialize is not listed in the Serializer.serializable_keyset
|
||||
or serializers, it will not be returned.
|
||||
|
||||
To serialize call:
|
||||
my_serializer = MySerializer( app )
|
||||
...
|
||||
keys_to_serialize = [ 'id', 'name', 'attr1', 'attr2', ... ]
|
||||
item_dict = MySerializer.serialize( trans, my_item, keys_to_serialize )
|
||||
"""
|
||||
#: 'service' to use for getting urls - use class var to allow overriding when testing
|
||||
url_for = staticmethod( routes.url_for )
|
||||
|
||||
def __init__( self, app ):
|
||||
"""
|
||||
Set up serializer map, any additional serializable keys, and views here.
|
||||
"""
|
||||
self.app = app
|
||||
|
||||
# a list of valid serializable keys that can use the default (string) serializer
|
||||
# this allows us to: 'mention' the key without adding the default serializer
|
||||
# TODO: we may want to eventually error if a key is requested
|
||||
# that is in neither serializable_keyset or serializers
|
||||
self.serializable_keyset = set([])
|
||||
# a map of dictionary keys to the functions (often lambdas) that create the values for those keys
|
||||
self.serializers = {}
|
||||
# add subclass serializers defined there
|
||||
self.add_serializers()
|
||||
# update the keyset by the serializers (removing the responsibility from subclasses)
|
||||
self.serializable_keyset.update( self.serializers.keys() )
|
||||
|
||||
# views are collections of serializable attributes (a named array of keys)
|
||||
# inspired by model.dict_{view}_visible_keys
|
||||
self.views = {}
|
||||
self.default_view = None
|
||||
|
||||
def add_serializers( self ):
|
||||
"""
|
||||
Register a map of attribute keys -> serializing functions that will serialize
|
||||
the attribute.
|
||||
"""
|
||||
# to be overridden in subclasses
|
||||
pass
|
||||
|
||||
def add_view( self, view_name, key_list, include_keys_from=None ):
|
||||
"""
|
||||
Add the list of serializable attributes `key_list` to the serializer's
|
||||
view dictionary under the key `view_name`.
|
||||
|
||||
If `include_keys_from` is a proper view name, extend `key_list` by
|
||||
the list in that view.
|
||||
"""
|
||||
key_list = list( set( key_list + self.views.get( include_keys_from, [] ) ) )
|
||||
self.views[ view_name ] = key_list
|
||||
self.serializable_keyset.update( key_list )
|
||||
return key_list
|
||||
|
||||
def serialize( self, trans, item, keys ):
|
||||
"""
|
||||
Serialize the model `item` to a dictionary.
|
||||
|
||||
Given model `item` and the list `keys`, create and return a dictionary
|
||||
built from each key in `keys` that also exists in `serializers` and
|
||||
values of calling the keyed/named serializers on item.
|
||||
"""
|
||||
# main interface fn for converting a model to a dict
|
||||
returned = {}
|
||||
for key in keys:
|
||||
# check both serializers and serializable keys
|
||||
if key in self.serializers:
|
||||
returned[ key ] = self.serializers[ key ]( trans, item, key )
|
||||
elif key in self.serializable_keyset:
|
||||
returned[ key ] = self.default_serializer( trans, item, key )
|
||||
# ignore bad/unreg keys
|
||||
return returned
|
||||
|
||||
def default_serializer( self, trans, item, key ):
|
||||
"""
|
||||
Serialize the `item`'s attribute named `key`.
|
||||
"""
|
||||
# TODO:?? point of change but not really necessary?
|
||||
return getattr( item, key )
|
||||
|
||||
# serializers for common galaxy objects
|
||||
def serialize_date( self, trans, item, key ):
|
||||
"""
|
||||
Serialize a date attribute of `item`.
|
||||
"""
|
||||
date = getattr( item, key )
|
||||
return date.isoformat() if date is not None else None
|
||||
|
||||
def serialize_id( self, trans, item, key ):
|
||||
"""
|
||||
Serialize an id attribute of `item`.
|
||||
"""
|
||||
id = getattr( item, key )
|
||||
return self.app.security.encode_id( id ) if id is not None else None
|
||||
|
||||
# serializing to a view where a view is a predefied list of keys to serialize
|
||||
def serialize_to_view( self, trans, item, view=None, keys=None, default_view=None ):
|
||||
"""
|
||||
Use a predefined list of keys (the string `view`) and any additional keys
|
||||
listed in `keys`.
|
||||
|
||||
The combinations can be:
|
||||
`view` only: return those keys listed in the named view
|
||||
`keys` only: return those keys listed
|
||||
no `view` or `keys`: use the `default_view` if any
|
||||
`view` and `keys`: combine both into one list of keys
|
||||
"""
|
||||
#TODO: default view + view makes no sense outside the API.index context - move default view there
|
||||
all_keys = []
|
||||
keys = keys or []
|
||||
# chose explicit over concise here
|
||||
if view:
|
||||
if keys:
|
||||
all_keys = self._view_to_keys( view ) + keys
|
||||
else:
|
||||
all_keys = self._view_to_keys( view )
|
||||
else:
|
||||
if keys:
|
||||
all_keys = keys
|
||||
elif default_view:
|
||||
all_keys = self._view_to_keys( default_view )
|
||||
|
||||
return self.serialize( trans, item, all_keys )
|
||||
|
||||
def _view_to_keys( self, view=None ):
|
||||
"""
|
||||
Converts a known view into a list of keys.
|
||||
|
||||
:raises ModelSerializingError: if the view is not listed in `self.views`.
|
||||
"""
|
||||
if view is None:
|
||||
view = self.default_view
|
||||
if view not in self.views:
|
||||
raise ModelSerializingError( 'unknown view', view=view, available_views=self.views )
|
||||
return self.views[ view ][:]
|
||||
|
||||
|
||||
class ModelDeserializer( object ):
|
||||
"""
|
||||
An object that converts an incoming serialized dict into values that can be
|
||||
directly assigned to an item's attributes and assigns them.
|
||||
"""
|
||||
#: the class used to create this deserializer's generically accessible model_manager
|
||||
model_manager_class = None
|
||||
|
||||
#TODO:?? a larger question is: which should be first? Deserialize then validate - or - validate then deserialize?
|
||||
|
||||
def __init__( self, app ):
|
||||
"""
|
||||
Set up deserializers and validator.
|
||||
"""
|
||||
self.app = app
|
||||
|
||||
self.deserializers = {}
|
||||
self.deserializable_keyset = set([])
|
||||
self.add_deserializers()
|
||||
# a sub object that can validate incoming values
|
||||
self.validate = ModelValidator( self.app )
|
||||
|
||||
# create a generically accessible manager for the model this deserializer works with/for
|
||||
self.manager = None
|
||||
if self.model_manager_class:
|
||||
self.manager = self.model_manager_class( self.app )
|
||||
|
||||
def add_deserializers( self ):
|
||||
"""
|
||||
Register a map of attribute keys -> functions that will deserialize data
|
||||
into attributes to be assigned to the item.
|
||||
"""
|
||||
# to be overridden in subclasses
|
||||
pass
|
||||
|
||||
def deserialize( self, trans, item, data, flush=True ):
|
||||
"""
|
||||
Convert an incoming serialized dict into values that can be
|
||||
directly assigned to an item's attributes and assign them
|
||||
"""
|
||||
sa_session = self.app.model.context
|
||||
new_dict = {}
|
||||
for key, val in data.items():
|
||||
if key in self.deserializers:
|
||||
new_dict[ key ] = self.deserializers[ key ]( trans, item, key, val )
|
||||
# !important: don't error on unreg. keys -- many clients will add weird ass keys onto the model
|
||||
|
||||
# TODO:?? add and flush here or in manager?
|
||||
if flush and len( new_dict ):
|
||||
sa_session.add( item )
|
||||
sa_session.flush()
|
||||
|
||||
return new_dict
|
||||
|
||||
# ... common deserializers for primitives
|
||||
def default_deserializer( self, trans, item, key, val ):
|
||||
"""
|
||||
If the incoming `val` is different than the `item` value change it
|
||||
and, in either case, return the value.
|
||||
"""
|
||||
# TODO: sets the item attribute to value (this may not work in all instances)
|
||||
|
||||
# only do the following if val == getattr( item, key )
|
||||
if hasattr( item, key ) and getattr( item, key ) != val:
|
||||
setattr( item, key, val )
|
||||
return val
|
||||
|
||||
def deserialize_basestring( self, trans, item, key, val ):
|
||||
val = self.validate.basestring( key, val )
|
||||
return self.default_deserializer( trans, item, key, val )
|
||||
|
||||
def deserialize_bool( self, trans, item, key, val ):
|
||||
val = self.validate.bool( key, val )
|
||||
return self.default_deserializer( trans, item, key, val )
|
||||
|
||||
def deserialize_int( self, trans, item, key, val, min=None, max=None ):
|
||||
val = self.validate.int_range( key, val, min, max )
|
||||
return self.default_deserializer( trans, item, key, val )
|
||||
|
||||
#def deserialize_date( self, trans, item, key, val ):
|
||||
# #TODO: parse isoformat date into date object
|
||||
|
||||
# ... common deserializers for Galaxy
|
||||
def deserialize_genome_build( self, trans, item, key, val ):
|
||||
"""
|
||||
Make sure `val` is a valid dbkey and assign it.
|
||||
"""
|
||||
val = self.validate.genome_build( trans, key, val )
|
||||
return self.default_deserializer( trans, item, key, val )
|
||||
|
||||
|
||||
class ModelValidator( object ):
|
||||
"""
|
||||
An object that inspects a dictionary (generally meant to be a set of
|
||||
new/updated values for the model) and raises an error if a value is
|
||||
not acceptable.
|
||||
"""
|
||||
|
||||
def __init__( self, app, *args, **kwargs ):
|
||||
self.app = app
|
||||
|
||||
def type( self, key, val, types ):
|
||||
"""
|
||||
Check `val` against the type (or tuple of types) in `types`.
|
||||
|
||||
:raises exceptions.RequestParameterInvalidException: if not an instance.
|
||||
"""
|
||||
if not isinstance( val, types ):
|
||||
msg = 'must be a type: %s' % ( str( types ) )
|
||||
raise exceptions.RequestParameterInvalidException( msg, key=key, val=val )
|
||||
return val
|
||||
|
||||
# validators for primitives and compounds of primitives
|
||||
def basestring( self, key, val ):
|
||||
return self.type( key, val, basestring )
|
||||
|
||||
def bool( self, key, val ):
|
||||
return self.type( key, val, bool )
|
||||
|
||||
def int( self, key, val ):
|
||||
return self.type( key, val, int )
|
||||
|
||||
def nullable_basestring( self, key, val ):
|
||||
"""
|
||||
Must be a basestring or None.
|
||||
"""
|
||||
return self.type( key, val, ( basestring, type( None ) ) )
|
||||
|
||||
def int_range( self, key, val, min=None, max=None ):
|
||||
"""
|
||||
Must be a int between min and max.
|
||||
"""
|
||||
val = self.type( key, val, int )
|
||||
if min is not None and val < min:
|
||||
raise exceptions.RequestParameterInvalidException( "less than minimum", key=key, val=val, min=min )
|
||||
if max is not None and val > max:
|
||||
raise exceptions.RequestParameterInvalidException( "greater than maximum", key=key, val=val, max=max )
|
||||
return val
|
||||
|
||||
def basestring_list( self, key, val ):
|
||||
"""
|
||||
Must be a list of basestrings.
|
||||
"""
|
||||
# TODO: Here's where compound types start becoming a nightmare. Any more or more complex
|
||||
# and should find a different way.
|
||||
val = self.type( key, val, list )
|
||||
return [ self.basestring( key, elem ) for elem in val ]
|
||||
|
||||
# validators for Galaxy
|
||||
def genome_build( self, trans, key, val ):
|
||||
"""
|
||||
Must be a valid genome_build/dbkey/reference/whatever-the-hell-were-calling-them-now.
|
||||
"""
|
||||
# TODO: is this correct?
|
||||
if val is None:
|
||||
return '?'
|
||||
for genome_build_shortname, longname in self.app.genome_builds.get_genome_build_names( trans=trans ):
|
||||
if val == genome_build_shortname:
|
||||
return val
|
||||
raise exceptions.RequestParameterInvalidException( "invalid reference", key=key, val=val )
|
||||
|
||||
# def slug( self, trans, item, key, val ):
|
||||
# """validate slug"""
|
||||
# pass
|
||||
|
||||
|
||||
# ==== Building query filters based on model data
|
||||
class ModelFilterParser( object ):
|
||||
"""
|
||||
Converts string tuples (partially converted query string params) of
|
||||
attr, op, val into either:
|
||||
- ORM based filters (filters that can be applied by the ORM at the SQL
|
||||
level) or
|
||||
- functional filters (filters that use derived values or values not
|
||||
within the SQL tables)
|
||||
These filters can then be applied to queries.
|
||||
|
||||
This abstraction allows 'smarter' application of limit and offset at either the
|
||||
SQL level or the generator/list level based on the presence of functional
|
||||
filters. In other words, if no functional filters are present, limit and offset
|
||||
may be applied at the SQL level. If functional filters are present, limit and
|
||||
offset need to applied at the list level.
|
||||
|
||||
These might be safely be replaced in the future by creating SQLAlchemy
|
||||
hybrid properties or more thoroughly mapping derived values.
|
||||
"""
|
||||
#??: this class kindof 'lives' in both the world of the controllers/param-parsing and to models/orm
|
||||
# (as the model informs how the filter params are parsed)
|
||||
# I have no great idea where this 'belongs', so it's here for now
|
||||
|
||||
#: model class
|
||||
model_class = None
|
||||
|
||||
def __init__( self, app ):
|
||||
"""
|
||||
Set up serializer map, any additional serializable keys, and views here.
|
||||
"""
|
||||
self.app = app
|
||||
|
||||
#: dictionary containing parsing data for ORM/SQLAlchemy-based filters
|
||||
#..note: although kind of a pain in the ass and verbose, opt-in/whitelisting allows more control
|
||||
#: over potentially expensive queries
|
||||
self.orm_filter_parsers = {}
|
||||
|
||||
#: dictionary containing parsing data for functional filters - applied after a query is made
|
||||
self.fn_filter_parsers = {}
|
||||
|
||||
# set up both of the above
|
||||
self._add_parsers()
|
||||
|
||||
def _add_parsers( self ):
|
||||
"""
|
||||
Set up, extend, or alter `orm_filter_parsers` and `fn_filter_parsers`.
|
||||
"""
|
||||
self.orm_filter_parsers.update({
|
||||
# (prob.) applicable to all models
|
||||
'id' : { 'op': ( 'in' ), 'val': self.parse_id_list },
|
||||
# dates can be directly passed through the orm into a filter (no need to parse into datetime object)
|
||||
'create_time' : { 'op': ( 'le', 'ge' ) },
|
||||
'update_time' : { 'op': ( 'le', 'ge' ) },
|
||||
})
|
||||
|
||||
def parse_filters( self, filter_tuple_list ):
|
||||
"""
|
||||
Parse string 3-tuples (attr, op, val) into orm or functional filters.
|
||||
"""
|
||||
parsed = []
|
||||
for ( attr, op, val ) in filter_tuple_list:
|
||||
filter_ = self.parse_filter( attr, op, val )
|
||||
parsed.append( filter_ )
|
||||
return parsed
|
||||
|
||||
def parse_filter( self, attr, op, val ):
|
||||
"""
|
||||
Attempt to parse filter as a custom/fn filter, then an orm filter, and
|
||||
if neither work - raise an error.
|
||||
|
||||
:raises exceptions.RequestParameterInvalidException: if no functional or orm
|
||||
filter can be parsed.
|
||||
"""
|
||||
try:
|
||||
# check for a custom filter
|
||||
fn_filter = self._parse_fn_filter( attr, op, val )
|
||||
if fn_filter is not None:
|
||||
return fn_filter
|
||||
|
||||
# if no custom filter found, try to make an ORM filter
|
||||
#note: have to use explicit is None here, bool( sqlalx.filter ) == False
|
||||
orm_filter = self._parse_orm_filter( attr, op, val )
|
||||
if orm_filter is not None:
|
||||
return orm_filter
|
||||
|
||||
# by convention, assume most val parsers raise ValueError
|
||||
except ValueError, val_err:
|
||||
raise exceptions.RequestParameterInvalidException( 'unparsable value for filter',
|
||||
column=attr, operation=op, value=val )
|
||||
|
||||
# if neither of the above work, raise an error with how-to info
|
||||
#TODO: send back all valid filter keys in exception for added user help
|
||||
raise exceptions.RequestParameterInvalidException( 'bad filter', column=attr, operation=op )
|
||||
|
||||
# ---- fn filters
|
||||
def _parse_fn_filter( self, attr, op, val ):
|
||||
"""
|
||||
"""
|
||||
# fn_filter_list is a dict: fn_filter_list[ attr ] = { 'opname1' : opfn1, 'opname2' : opfn2, etc. }
|
||||
|
||||
# attr, op is a nested dictionary pointing to the filter fn
|
||||
attr_map = self.fn_filter_parsers.get( attr, None )
|
||||
if not attr_map:
|
||||
return None
|
||||
allowed_ops = attr_map.get( 'op' )
|
||||
# allowed ops is a map here, op => fn
|
||||
filter_fn = allowed_ops.get( op, None )
|
||||
if not filter_fn:
|
||||
return None
|
||||
# parse the val from string using the 'val' parser if present (otherwise, leave as string)
|
||||
val_parser = attr_map.get( 'val', None )
|
||||
if val_parser:
|
||||
val = val_parser( val )
|
||||
|
||||
# curry/partial and fold the val in there now
|
||||
return lambda i: filter_fn( i, val )
|
||||
|
||||
# ---- ORM filters
|
||||
def _parse_orm_filter( self, attr, op, val ):
|
||||
"""
|
||||
"""
|
||||
# orm_filter_list is a dict: orm_filter_list[ attr ] = <list of allowed ops>
|
||||
# attr must be a whitelisted column
|
||||
column = self.model_class.table.columns.get( attr )
|
||||
column_map = self.orm_filter_parsers.get( attr, None )
|
||||
if column is None or not column_map:
|
||||
return None
|
||||
# op must be whitelisted: contained in the list orm_filter_list[ attr ][ 'op' ]
|
||||
allowed_ops = column_map.get( 'op' )
|
||||
if op not in allowed_ops:
|
||||
return None
|
||||
op = self._convert_op_string_to_fn( column, op )
|
||||
# parse the val from string using the 'val' parser if present (otherwise, leave as string)
|
||||
val_parser = column_map.get( 'val', None )
|
||||
if val_parser:
|
||||
val = val_parser( val )
|
||||
|
||||
orm_filter = op( val )
|
||||
return orm_filter
|
||||
|
||||
#: these are the easier/shorter string equivalents to the python operator fn names that need '__' around them
|
||||
UNDERSCORED_OPS = ( 'lt', 'le', 'eq', 'ne', 'ge', 'gt' )
|
||||
#UNCHANGED_OPS = ( 'like' )
|
||||
def _convert_op_string_to_fn( self, column, op_string ):
|
||||
"""
|
||||
Convert the query string filter op shorthand into actual ORM usable
|
||||
function names, then return the ORM function.
|
||||
"""
|
||||
# correct op_string to usable function key
|
||||
fn_name = op_string
|
||||
if op_string in self.UNDERSCORED_OPS:
|
||||
fn_name = '__' + op_string + '__'
|
||||
elif op_string == 'in':
|
||||
fn_name = 'in_'
|
||||
|
||||
# get the column fn using the op_string and error if not a callable attr
|
||||
#TODO: special case 'not in' - or disallow?
|
||||
op_fn = getattr( column, fn_name, None )
|
||||
if not op_fn or not callable( op_fn ):
|
||||
return None
|
||||
return op_fn
|
||||
|
||||
# --- more parsers! yay!
|
||||
#TODO: These should go somewhere central - we've got ~6 parser modules/sections now
|
||||
#TODO: to annotatable
|
||||
def _owner_annotation( self, item ):
|
||||
"""
|
||||
Get the annotation by the item's owner.
|
||||
"""
|
||||
if not item.user:
|
||||
return None
|
||||
for annotation in item.annotations:
|
||||
if annotation.user == item.user:
|
||||
return annotation.annotation
|
||||
return None
|
||||
|
||||
def filter_annotation_contains( self, item, val ):
|
||||
"""
|
||||
Test whether `val` is in the owner's annotation.
|
||||
"""
|
||||
owner_annotation = self._owner_annotation( item )
|
||||
if owner_annotation is None:
|
||||
return False
|
||||
return val in owner_annotation
|
||||
|
||||
#TODO: to taggable
|
||||
def _tag_str_gen( self, item ):
|
||||
"""
|
||||
Return a list of strings built from the item's tags.
|
||||
"""
|
||||
#TODO: which user is this? all?
|
||||
for tag in item.tags:
|
||||
tag_str = tag.user_tname
|
||||
if tag.value is not None:
|
||||
tag_str += ":" + tag.user_value
|
||||
yield tag_str
|
||||
|
||||
def filter_has_partial_tag( self, item, val ):
|
||||
"""
|
||||
Return True if any tag partially contains `val`.
|
||||
"""
|
||||
for tag_str in self._tag_str_gen( item ):
|
||||
if val in tag_str:
|
||||
return True
|
||||
return False
|
||||
|
||||
def filter_has_tag( self, item, val ):
|
||||
"""
|
||||
Return True if any tag exactly equals `val`.
|
||||
"""
|
||||
for tag_str in self._tag_str_gen( item ):
|
||||
if val == tag_str:
|
||||
return True
|
||||
return False
|
||||
|
||||
def parse_bool( self, bool_string ):
|
||||
"""
|
||||
Parse a boolean from a string.
|
||||
"""
|
||||
# Be strict here to remove complexity of options (but allow already parsed).
|
||||
if bool_string in ( 'True', True ):
|
||||
return True
|
||||
if bool_string in ( 'False', False ):
|
||||
return False
|
||||
raise ValueError( 'invalid boolean: ' + str( bool_string ) )
|
||||
|
||||
def parse_id_list( self, id_list_string, sep=',' ):
|
||||
"""
|
||||
Split `id_list_string` at `sep`.
|
||||
"""
|
||||
#TODO: move id decoding out
|
||||
id_list = [ self.app.security.decode_id( id_ ) for id_ in id_list_string.split( sep ) ]
|
||||
return id_list
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
from galaxy.dataset_collections.registry import DatasetCollectionTypesRegistry
|
||||
from galaxy.dataset_collections.matching import MatchingCollections
|
||||
from galaxy.dataset_collections.type_description import CollectionTypeDescriptionFactory
|
||||
from galaxy.dataset_collections import builder
|
||||
|
||||
from galaxy import model
|
||||
from galaxy.exceptions import MessageException
|
||||
from galaxy.dataset_collections import builder
|
||||
from galaxy.dataset_collections.matching import MatchingCollections
|
||||
from galaxy.dataset_collections.registry import DatasetCollectionTypesRegistry
|
||||
from galaxy.dataset_collections.type_description import CollectionTypeDescriptionFactory
|
||||
from galaxy.exceptions import ItemAccessibilityException
|
||||
from galaxy.exceptions import MessageException
|
||||
from galaxy.exceptions import RequestParameterInvalidException
|
||||
from galaxy.managers import hdas # TODO: Refactor all mixin use into managers.
|
||||
from galaxy.managers import histories
|
||||
from galaxy.managers import lddas
|
||||
from galaxy.managers import tags
|
||||
from galaxy.managers.collections_util import validate_input_element_identifiers
|
||||
from galaxy.util import validation
|
||||
from galaxy.util import odict
|
||||
|
||||
from galaxy.util import validation
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
@@ -28,16 +26,18 @@ class DatasetCollectionManager( object ):
|
||||
Abstraction for interfacing with dataset collections instance - ideally abstarcts
|
||||
out model and plugin details.
|
||||
"""
|
||||
ELEMENTS_UNINITIALIZED = object()
|
||||
|
||||
def __init__( self, app ):
|
||||
self.type_registry = DatasetCollectionTypesRegistry( app )
|
||||
self.collection_type_descriptions = CollectionTypeDescriptionFactory( self.type_registry )
|
||||
self.model = app.model
|
||||
self.security = app.security
|
||||
self.hda_manager = hdas.HDAManager()
|
||||
self.history_manager = histories.HistoryManager()
|
||||
self.tag_manager = tags.TagsManager( app )
|
||||
self.ldda_manager = lddas.LDDAManager( )
|
||||
|
||||
self.hda_manager = hdas.HDAManager( app )
|
||||
self.history_manager = histories.HistoryManager( app )
|
||||
self.tag_manager = tags.TagManager( app )
|
||||
self.ldda_manager = lddas.LDDAManager( app )
|
||||
|
||||
def create(
|
||||
self,
|
||||
@@ -63,6 +63,7 @@ class DatasetCollectionManager( object ):
|
||||
element_identifiers=element_identifiers,
|
||||
elements=elements,
|
||||
)
|
||||
|
||||
if isinstance( parent, model.History ):
|
||||
dataset_collection_instance = self.model.HistoryDatasetCollectionAssociation(
|
||||
collection=dataset_collection,
|
||||
@@ -72,19 +73,26 @@ class DatasetCollectionManager( object ):
|
||||
for input_name, input_collection in implicit_collection_info[ "implicit_inputs" ]:
|
||||
dataset_collection_instance.add_implicit_input_collection( input_name, input_collection )
|
||||
for output_dataset in implicit_collection_info.get( "outputs" ):
|
||||
output_dataset.hidden_beneath_collection_instance = dataset_collection_instance
|
||||
if isinstance( output_dataset, model.HistoryDatasetCollectionAssociation ):
|
||||
dataset_collection_instance.add_implicit_input_collection( input_name, input_collection )
|
||||
else:
|
||||
# dataset collection, don't need to do anything...
|
||||
pass
|
||||
trans.sa_session.add( output_dataset )
|
||||
|
||||
dataset_collection_instance.implicit_output_name = implicit_collection_info[ "implicit_output_name" ]
|
||||
|
||||
log.debug("Created collection with %d elements" % ( len( dataset_collection_instance.collection.elements ) ) )
|
||||
# Handle setting hid
|
||||
parent.add_dataset_collection( dataset_collection_instance )
|
||||
|
||||
elif isinstance( parent, model.LibraryFolder ):
|
||||
dataset_collection_instance = self.model.LibraryDatasetCollectionAssociation(
|
||||
collection=dataset_collection,
|
||||
folder=parent,
|
||||
name=name,
|
||||
)
|
||||
|
||||
else:
|
||||
message = "Internal logic error - create called with unknown parent type %s" % type( parent )
|
||||
log.exception( message )
|
||||
@@ -92,6 +100,18 @@ class DatasetCollectionManager( object ):
|
||||
|
||||
return self.__persist( dataset_collection_instance )
|
||||
|
||||
def create_dataset_collection(
|
||||
self,
|
||||
trans,
|
||||
collection_type,
|
||||
elements=None,
|
||||
):
|
||||
return self.__create_dataset_collection(
|
||||
trans=trans,
|
||||
collection_type=collection_type,
|
||||
elements=elements,
|
||||
)
|
||||
|
||||
def __create_dataset_collection(
|
||||
self,
|
||||
trans,
|
||||
@@ -113,11 +133,26 @@ class DatasetCollectionManager( object ):
|
||||
elements = self.__load_elements( trans, element_identifiers )
|
||||
# else if elements is set, it better be an ordered dict!
|
||||
|
||||
type_plugin = collection_type_description.rank_type_plugin()
|
||||
dataset_collection = builder.build_collection( type_plugin, elements )
|
||||
if elements is not self.ELEMENTS_UNINITIALIZED:
|
||||
type_plugin = collection_type_description.rank_type_plugin()
|
||||
dataset_collection = builder.build_collection( type_plugin, elements )
|
||||
else:
|
||||
dataset_collection = model.DatasetCollection( populated=False )
|
||||
dataset_collection.collection_type = collection_type
|
||||
return dataset_collection
|
||||
|
||||
def set_collection_elements( self, dataset_collection, dataset_instances ):
|
||||
if dataset_collection.populated:
|
||||
raise Exception("Cannot reset elements of an already populated dataset collection.")
|
||||
|
||||
collection_type = dataset_collection.collection_type
|
||||
collection_type_description = self.collection_type_descriptions.for_collection_type( collection_type )
|
||||
type_plugin = collection_type_description.rank_type_plugin()
|
||||
builder.set_collection_elements( dataset_collection, type_plugin, dataset_instances )
|
||||
dataset_collection.mark_as_populated()
|
||||
|
||||
return dataset_collection
|
||||
|
||||
def delete( self, trans, instance_type, id ):
|
||||
dataset_collection_instance = self.get_dataset_collection_instance( trans, instance_type, id, check_ownership=True )
|
||||
dataset_collection_instance.deleted = True
|
||||
@@ -154,8 +189,6 @@ class DatasetCollectionManager( object ):
|
||||
return source_hdca
|
||||
|
||||
def _set_from_dict( self, trans, dataset_collection_instance, new_data ):
|
||||
# Blatantly stolen from UsesHistoryDatasetAssociationMixin.set_hda_from_dict.
|
||||
|
||||
# send what we can down into the model
|
||||
changed = dataset_collection_instance.set_from_dict( new_data )
|
||||
# the rest (often involving the trans) - do here
|
||||
@@ -163,7 +196,7 @@ class DatasetCollectionManager( object ):
|
||||
dataset_collection_instance.add_item_annotation( trans.sa_session, trans.get_user(), dataset_collection_instance, new_data[ 'annotation' ] )
|
||||
changed[ 'annotation' ] = new_data[ 'annotation' ]
|
||||
if 'tags' in new_data.keys() and trans.get_user():
|
||||
self.tag_manager.set_tags_from_list( trans, dataset_collection_instance, new_data[ 'tags' ], user=trans.user )
|
||||
self.tag_manager.set_tags_from_list( trans.get_user(), dataset_collection_instance, new_data[ 'tags' ] )
|
||||
|
||||
if changed.keys():
|
||||
trans.sa_session.flush()
|
||||
@@ -223,7 +256,7 @@ class DatasetCollectionManager( object ):
|
||||
return elements
|
||||
|
||||
def __load_element( self, trans, element_identifier ):
|
||||
#if not isinstance( element_identifier, dict ):
|
||||
# if not isinstance( element_identifier, dict ):
|
||||
# # Is allowing this to just be the id of an hda too clever? Somewhat
|
||||
# # consistent with other API methods though.
|
||||
# element_identifier = dict( src='hda', id=str( element_identifier ) )
|
||||
@@ -244,7 +277,7 @@ class DatasetCollectionManager( object ):
|
||||
|
||||
if src_type == 'hda':
|
||||
decoded_id = int( trans.app.security.decode_id( encoded_id ) )
|
||||
element = self.hda_manager.get( trans, decoded_id, check_ownership=False )
|
||||
element = self.hda_manager.get_accessible( trans, decoded_id, trans.user )
|
||||
elif src_type == 'ldda':
|
||||
element = self.ldda_manager.get( trans, encoded_id )
|
||||
elif src_type == 'hdca':
|
||||
@@ -278,7 +311,10 @@ class DatasetCollectionManager( object ):
|
||||
def __get_history_collection_instance( self, trans, id, check_ownership=False, check_accessible=True ):
|
||||
instance_id = int( trans.app.security.decode_id( id ) )
|
||||
collection_instance = trans.sa_session.query( trans.app.model.HistoryDatasetCollectionAssociation ).get( instance_id )
|
||||
self.history_manager.secure( trans, collection_instance.history, check_ownership=check_ownership, check_accessible=check_accessible )
|
||||
if check_ownership:
|
||||
self.history_manager.error_unless_owner( trans, collection_instance.history, trans.user )
|
||||
if check_accessible:
|
||||
self.history_manager.error_unless_accessible( trans, collection_instance.history, trans.user )
|
||||
return collection_instance
|
||||
|
||||
def __get_library_collection_instance( self, trans, id, check_ownership=False, check_accessible=True ):
|
||||
|
||||
@@ -28,7 +28,6 @@ def api_payload_to_create_params( payload ):
|
||||
element_identifiers=payload.get( "element_identifiers" ),
|
||||
name=payload.get( "name", None ),
|
||||
)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
@@ -65,9 +64,9 @@ def validate_input_element_identifiers( element_identifiers ):
|
||||
validate_input_element_identifiers( element_identifier[ "element_identifiers" ] )
|
||||
|
||||
|
||||
def dictify_dataset_collection_instance( dataset_colleciton_instance, parent, security, view="element" ):
|
||||
dict_value = dataset_colleciton_instance.to_dict( view=view )
|
||||
encoded_id = security.encode_id( dataset_colleciton_instance.id )
|
||||
def dictify_dataset_collection_instance( dataset_collection_instance, parent, security, view="element" ):
|
||||
dict_value = dataset_collection_instance.to_dict( view=view )
|
||||
encoded_id = security.encode_id( dataset_collection_instance.id )
|
||||
if isinstance( parent, model.History ):
|
||||
encoded_history_id = security.encode_id( parent.id )
|
||||
dict_value[ 'url' ] = web.url_for( 'history_content_typed', history_id=encoded_history_id, id=encoded_id, type="dataset_collection" )
|
||||
@@ -77,7 +76,9 @@ def dictify_dataset_collection_instance( dataset_colleciton_instance, parent, se
|
||||
# TODO: Work in progress - this end-point is not right yet...
|
||||
dict_value[ 'url' ] = web.url_for( 'library_content', library_id=encoded_library_id, id=encoded_id, folder_id=encoded_folder_id )
|
||||
if view == "element":
|
||||
dict_value[ 'elements' ] = map( dictify_element, dataset_colleciton_instance.collection.elements )
|
||||
collection = dataset_collection_instance.collection
|
||||
dict_value[ 'elements' ] = map( dictify_element, collection.elements )
|
||||
dict_value[ 'populated' ] = collection.populated
|
||||
security.encode_all_ids( dict_value, recursive=True ) # TODO: Use Kyle's recusrive formulation of this.
|
||||
return dict_value
|
||||
|
||||
@@ -87,7 +88,9 @@ def dictify_element( element ):
|
||||
object_detials = element.element_object.to_dict()
|
||||
if element.child_collection:
|
||||
# Recursively yield elements for each nested collection...
|
||||
object_detials[ "elements" ] = map( dictify_element, element.child_collection.elements )
|
||||
child_collection = element.child_collection
|
||||
object_detials[ "elements" ] = map( dictify_element, child_collection.elements )
|
||||
object_detials[ "populated" ] = child_collection.populated
|
||||
|
||||
dictified[ "object" ] = object_detials
|
||||
return dictified
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
"""
|
||||
Manager and Serializer for Datasets.
|
||||
"""
|
||||
from galaxy import model
|
||||
from galaxy import exceptions
|
||||
|
||||
from galaxy.managers import base
|
||||
from galaxy.managers import secured
|
||||
from galaxy.managers import deletable
|
||||
from galaxy.managers import users
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
class DatasetManager( base.ModelManager, secured.AccessibleManagerMixin, deletable.PurgableManagerMixin ):
|
||||
"""
|
||||
Manipulate datasets: the components contained in DatasetAssociations/DatasetInstances/HDAs/LDDAs
|
||||
"""
|
||||
model_class = model.Dataset
|
||||
foreign_key_name = 'dataset'
|
||||
|
||||
#TODO:?? get + error_if_uploading is common pattern, should upload check be worked into access/owed?
|
||||
|
||||
def __init__( self, app ):
|
||||
super( DatasetManager, self ).__init__( app )
|
||||
# need for admin test
|
||||
self.user_manager = users.UserManager( app )
|
||||
|
||||
def create( self, trans, flush=True, **kwargs ):
|
||||
"""
|
||||
Create and return a new Dataset object.
|
||||
"""
|
||||
# default to NEW state on new datasets
|
||||
kwargs.update( dict( state=( kwargs.get( 'state', model.Dataset.states.NEW ) ) ) )
|
||||
dataset = model.Dataset( **kwargs )
|
||||
|
||||
self.app.model.context.add( dataset )
|
||||
if flush:
|
||||
self.app.model.context.flush()
|
||||
return dataset
|
||||
|
||||
#def copy( self, dataset, **kwargs ):
|
||||
# """
|
||||
# Clone, update, and return the given dataset.
|
||||
# """
|
||||
# pass
|
||||
|
||||
#def to_hda( self, trans, dataset, history, **kwargs ):
|
||||
# """
|
||||
# Create an hda from this dataset.
|
||||
# """
|
||||
# pass
|
||||
|
||||
#def to_ldda( self, trans, dataset, library_folder, **kwargs ):
|
||||
# """
|
||||
# Create an ldda from this dataset.
|
||||
# """
|
||||
# pass
|
||||
|
||||
#TODO: this may be more conv. somewhere else
|
||||
#TODO: how to allow admin bypass?
|
||||
def error_unless_dataset_purge_allowed( self, trans, item, msg=None ):
|
||||
if not self.app.config.allow_user_dataset_purge:
|
||||
msg = msg or 'This instance does not allow user dataset purging'
|
||||
raise exceptions.ConfigDoesNotAllowException( msg )
|
||||
return item
|
||||
|
||||
def purge( self, trans, dataset, flush=True ):
|
||||
"""
|
||||
Remove the object_store/file for this dataset from storage and mark
|
||||
as purged.
|
||||
|
||||
:raises exceptions.ConfigDoesNotAllowException: if the instance doesn't allow
|
||||
"""
|
||||
self.error_unless_dataset_purge_allowed( trans, dataset )
|
||||
|
||||
# the following also marks dataset as purged and deleted
|
||||
dataset.full_delete()
|
||||
self.app.model.context.add( dataset )
|
||||
if flush:
|
||||
self.app.model.context.flush()
|
||||
return dataset
|
||||
|
||||
# .... accessibility
|
||||
# datasets can implement the accessible interface, but accessibility is checked in an entirely different way
|
||||
# than those resources that have a user attribute (histories, pages, etc.)
|
||||
def is_accessible( self, trans, dataset, user ):
|
||||
"""
|
||||
Is this dataset readable/viewable to user?
|
||||
"""
|
||||
if self.user_manager.is_admin( trans, user ):
|
||||
return True
|
||||
if self.has_access_permission( trans, dataset, user ):
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_access_permission( self, trans, dataset, user ):
|
||||
"""
|
||||
Return T/F if the user has role-based access to the dataset.
|
||||
"""
|
||||
roles = user.all_roles() if user else []
|
||||
return self.app.security_agent.can_access_dataset( roles, dataset )
|
||||
|
||||
#TODO: these need work
|
||||
def _access_permission( self, trans, dataset, user=None, role=None ):
|
||||
"""
|
||||
Return most recent DatasetPermissions for the dataset and user.
|
||||
"""
|
||||
security_agent = self.app.security_agent
|
||||
access_action = security_agent.permitted_actions.DATASET_ACCESS.action
|
||||
|
||||
# get a list of role ids to check access for (defaulting to all_roles)
|
||||
user_roles = [ role ] if role else user.all_roles()
|
||||
user_role_ids = [ r.id for r in user_roles ]
|
||||
query = ( self.app.model.context.query( model.DatasetPermissions )
|
||||
.filter( model.DatasetPermissions.action == access_action )
|
||||
.filter( model.DatasetPermissions.dataset == dataset )
|
||||
.filter( model.DatasetPermissions.role_id.in_( user_role_ids ) ) )
|
||||
#TODO:?? most recent?
|
||||
return query.first()
|
||||
|
||||
def _create_access_permission( self, trans, dataset, role, flush=True ):
|
||||
"""
|
||||
Create a new DatasetPermissions for the given dataset and role.
|
||||
"""
|
||||
access_action = self.app.security_agent.permitted_actions.DATASET_ACCESS.action
|
||||
permission = model.DatasetPermissions( access_action, dataset, role )
|
||||
self.app.model.context.add( permission )
|
||||
if flush:
|
||||
self.app.model.context.flush()
|
||||
return permission
|
||||
|
||||
#def give_access_permission( self, trans, dataset, user, flush=True ):
|
||||
# """
|
||||
# """
|
||||
# # for now, use the user's private role
|
||||
# security_agent = self.app.security_agent
|
||||
# user_role = security_agent.get_private_user_role( user )
|
||||
#
|
||||
# existing_permission = self._access_permission( trans, dataset, role=user_role )
|
||||
# print 'existing access_perm:', existing_permission
|
||||
# if existing_permission:
|
||||
# return dataset
|
||||
#
|
||||
# permission = self._create_access_permission( trans, dataset, user_role )
|
||||
# print 'access_roles:', [ role.name for role in dataset.get_access_roles( trans ) ]
|
||||
#
|
||||
# #access_action = security_agent.permitted_actions.DATASET_ACCESS.action
|
||||
# #access_action = security_agent.get_action( access_action )
|
||||
# #permissions = { access_action : [ user_role ] }
|
||||
# #security_agent.set_dataset_permission( dataset, permissions )
|
||||
#
|
||||
# #self.app.model.context.add( dataset )
|
||||
# #if flush:
|
||||
# # self.app.model.context.flush()
|
||||
#
|
||||
# dbl_chk = self._access_permission( trans, dataset, role=user_role )
|
||||
# print 'access_perm:', dbl_chk
|
||||
#
|
||||
# return dataset
|
||||
|
||||
#def remove_access_permission( self, trans, dataset, user ):
|
||||
# """
|
||||
# """
|
||||
# pass
|
||||
|
||||
# .... manage/modify
|
||||
#def has_manage_permission( self, trans, dataset, user ):
|
||||
# """
|
||||
# """
|
||||
# pass
|
||||
#
|
||||
#def give_manage_permission( self, trans, dataset, user ):
|
||||
# """
|
||||
# """
|
||||
# pass
|
||||
#
|
||||
#def remove_manage_permission( self, trans, dataset, user ):
|
||||
# """
|
||||
# """
|
||||
# pass
|
||||
|
||||
#TODO: implement above for groups
|
||||
#TODO: datatypes?
|
||||
|
||||
# .... data, object_store
|
||||
|
||||
|
||||
class DatasetSerializer( base.ModelSerializer, deletable.PurgableSerializerMixin ):
|
||||
|
||||
def __init__( self, app ):
|
||||
super( DatasetSerializer, self ).__init__( app )
|
||||
|
||||
self.default_view = 'summary'
|
||||
self.add_view( 'summary', [
|
||||
'id',
|
||||
'create_time', 'update_time',
|
||||
'state',
|
||||
'deleted', 'purged', 'purgable',
|
||||
#'object_store_id',
|
||||
#'external_filename',
|
||||
#'extra_files_path',
|
||||
'file_size', 'total_size',
|
||||
'uuid',
|
||||
])
|
||||
# could do visualizations and/or display_apps
|
||||
|
||||
def add_serializers( self ):
|
||||
super( DatasetSerializer, self ).add_serializers()
|
||||
deletable.PurgableSerializerMixin.add_serializers( self )
|
||||
|
||||
self.serializers.update({
|
||||
'id' : self.serialize_id,
|
||||
'create_time' : self.serialize_date,
|
||||
'update_time' : self.serialize_date,
|
||||
'uuid' : lambda t, i, k: str( i.uuid ) if i.uuid else None,
|
||||
})
|
||||
|
||||
|
||||
class DatasetDeserializer( base.ModelDeserializer, deletable.PurgableDeserializerMixin ):
|
||||
model_manager_class = DatasetManager
|
||||
|
||||
def add_deserializers( self ):
|
||||
super( DatasetDeserializer, self ).add_deserializers()
|
||||
deletable.PurgableDeserializerMixin.add_deserializers( self )
|
||||
|
||||
|
||||
# ============================================================================= AKA DatasetInstanceManager
|
||||
class DatasetAssociationManager( base.ModelManager, secured.AccessibleManagerMixin, deletable.PurgableManagerMixin ):
|
||||
"""
|
||||
DatasetAssociation/DatasetInstances are intended to be working
|
||||
proxies to a Dataset, associated with either a library or a
|
||||
user/history (HistoryDatasetAssociation).
|
||||
"""
|
||||
# DA's were meant to be proxies - but were never fully implemented as them
|
||||
# Instead, a dataset association HAS a dataset but contains metadata specific to a library (lda) or user (hda)
|
||||
model_class = model.DatasetInstance
|
||||
|
||||
def __init__( self, app ):
|
||||
super( DatasetAssociationManager, self ).__init__( app )
|
||||
self.dataset_manager = DatasetManager( app )
|
||||
|
||||
def is_accessible( self, trans, dataset_assoc, user ):
|
||||
"""
|
||||
Is this DA accessible to `user`?
|
||||
"""
|
||||
# defer to the dataset
|
||||
return self.dataset_manager.is_accessible( trans, dataset_assoc.dataset, user )
|
||||
|
||||
#def metadata( self, trans, dataset_assoc ):
|
||||
# """
|
||||
# Return the metadata collection.
|
||||
# """
|
||||
# # get metadata
|
||||
# pass
|
||||
|
||||
#def is_being_used( self, trans, dataset_assoc ):
|
||||
# """
|
||||
# """
|
||||
# #TODO: check history_associations, library_associations
|
||||
# pass
|
||||
|
||||
|
||||
class DatasetAssociationSerializer( base.ModelSerializer, deletable.PurgableSerializerMixin ):
|
||||
|
||||
def add_serializers( self ):
|
||||
super( DatasetAssociationSerializer, self ).add_serializers()
|
||||
deletable.PurgableSerializerMixin.add_serializers( self )
|
||||
|
||||
|
||||
class DatasetAssociationDeserializer( base.ModelDeserializer, deletable.PurgableDeserializerMixin ):
|
||||
|
||||
def add_deserializers( self ):
|
||||
super( DatasetAssociationDeserializer, self ).add_deserializers()
|
||||
deletable.PurgableDeserializerMixin.add_deserializers( self )
|
||||
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
Many models in Galaxy are not meant to be removed from the database but only
|
||||
marked as deleted. These models have the boolean attribute 'deleted'.
|
||||
|
||||
Other models are deletable and also may be purged. Most often these are
|
||||
models have some backing/supporting resources that can be removed as well
|
||||
(e.g. Datasets have data files on a drive). Purging these models removes
|
||||
the supporting resources as well. These models also have the boolean
|
||||
attribute 'purged'.
|
||||
"""
|
||||
|
||||
# ---- Deletable and Purgable models
|
||||
class DeletableManagerMixin( object ):
|
||||
"""
|
||||
A mixin/interface for a model that is deletable (i.e. has a 'deleted' attr).
|
||||
|
||||
Many resources in Galaxy can be marked as deleted - meaning (in most cases)
|
||||
that they are no longer needed, should not be displayed, or may be actually
|
||||
removed by an admin/script.
|
||||
"""
|
||||
|
||||
def delete( self, trans, item, flush=True, **kwargs ):
|
||||
"""
|
||||
Mark as deleted and return.
|
||||
"""
|
||||
return self._session_setattr( item, 'deleted', True, flush=flush )
|
||||
|
||||
def undelete( self, trans, item, flush=True, **kwargs ):
|
||||
"""
|
||||
Mark as not deleted and return.
|
||||
"""
|
||||
return self._session_setattr( item, 'deleted', False, flush=flush )
|
||||
|
||||
|
||||
class DeletableSerializerMixin( object ):
|
||||
|
||||
def add_serializers( self ):
|
||||
self.serializable_keyset.add( 'deleted' )
|
||||
|
||||
|
||||
# TODO: these are of questionable value if we don't want to enable users to delete/purge via update
|
||||
class DeletableDeserializerMixin( object ):
|
||||
|
||||
def add_deserializers( self ):
|
||||
self.deserializers[ 'deleted' ] = self.deserialize_deleted
|
||||
|
||||
def deserialize_deleted( self, trans, item, key, val ):
|
||||
"""
|
||||
Delete or undelete `item` based on `val` then return `item.deleted`.
|
||||
"""
|
||||
new_deleted = self.validate.bool( key, val )
|
||||
if new_deleted == item.deleted:
|
||||
return item.deleted
|
||||
# TODO:?? flush=False?
|
||||
if new_deleted:
|
||||
self.manager.delete( trans, item, flush=False )
|
||||
else:
|
||||
self.manager.undelete( trans, item, flush=False )
|
||||
return item.deleted
|
||||
|
||||
|
||||
class DeletableFiltersMixin( object ):
|
||||
|
||||
def _add_parsers( self ):
|
||||
self.orm_filter_parsers.update({
|
||||
'deleted' : { 'op': ( 'eq' ), 'val': self.parse_bool }
|
||||
})
|
||||
|
||||
|
||||
class PurgableManagerMixin( DeletableManagerMixin ):
|
||||
"""
|
||||
A manager interface/mixin for a resource that allows deleting and purging where
|
||||
purging is often removal of some additional, non-db resource (e.g. a dataset's
|
||||
file).
|
||||
"""
|
||||
|
||||
def purge( self, trans, item, flush=True, **kwargs ):
|
||||
"""
|
||||
Mark as purged and return.
|
||||
|
||||
Override this in subclasses to do the additional resource removal.
|
||||
"""
|
||||
return self._session_setattr( item, 'purged', True, flush=flush )
|
||||
|
||||
|
||||
class PurgableSerializerMixin( DeletableSerializerMixin ):
|
||||
|
||||
def add_serializers( self ):
|
||||
DeletableSerializerMixin.add_serializers( self )
|
||||
self.serializable_keyset.add( 'purged' )
|
||||
|
||||
|
||||
class PurgableDeserializerMixin( DeletableDeserializerMixin ):
|
||||
|
||||
def add_deserializers( self ):
|
||||
DeletableDeserializerMixin.add_deserializers( self )
|
||||
self.deserializers[ 'purged' ] = self.deserialize_purged
|
||||
|
||||
def deserialize_purged( self, trans, item, key, val ):
|
||||
"""
|
||||
If `val` is True, purge `item` and return `item.purged`.
|
||||
"""
|
||||
new_purged = self.validate.bool( key, val )
|
||||
if new_purged == item.purged:
|
||||
return item.purged
|
||||
if new_purged:
|
||||
self.manager.purge( trans, item, flush=False )
|
||||
return self.purged
|
||||
|
||||
|
||||
class PurgableFiltersMixin( DeletableFiltersMixin ):
|
||||
|
||||
def _add_parsers( self ):
|
||||
DeletableFiltersMixin._add_parsers( self )
|
||||
self.orm_filter_parsers.update({
|
||||
'purged' : { 'op': ( 'eq' ), 'val': self.parse_bool }
|
||||
})
|
||||
+512
-159
@@ -5,187 +5,196 @@ HistoryDatasetAssociations (HDAs) are datasets contained or created in a
|
||||
history.
|
||||
"""
|
||||
|
||||
import os
|
||||
import gettext
|
||||
|
||||
from galaxy import model
|
||||
from galaxy import exceptions
|
||||
|
||||
from galaxy.managers import base as manager_base
|
||||
from galaxy.managers import histories as history_manager
|
||||
|
||||
import galaxy.web
|
||||
from galaxy import datatypes
|
||||
import galaxy.datatypes.metadata
|
||||
from galaxy import objectstore
|
||||
|
||||
class HDAManager( manager_base.ModelManager ):
|
||||
from galaxy.managers import datasets
|
||||
from galaxy.managers import secured
|
||||
from galaxy.managers import taggable
|
||||
from galaxy.managers import annotatable
|
||||
from galaxy.managers import users
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
class HDAManager( datasets.DatasetAssociationManager, secured.OwnableManagerMixin,
|
||||
taggable.TaggableManagerMixin, annotatable.AnnotatableManagerMixin ):
|
||||
"""
|
||||
Interface/service object for interacting with HDAs.
|
||||
"""
|
||||
model_class = model.HistoryDatasetAssociation
|
||||
foreign_key_name = 'history_dataset_association'
|
||||
|
||||
def __init__( self ):
|
||||
tag_assoc = model.HistoryDatasetAssociationTagAssociation
|
||||
annotation_assoc = model.HistoryDatasetAssociationAnnotationAssociation
|
||||
|
||||
#TODO: move what makes sense into DatasetManager
|
||||
#TODO: which of these are common with LDDAs and can be pushed down into DatasetAssociationManager?
|
||||
|
||||
def __init__( self, app ):
|
||||
"""
|
||||
Set up and initialize other managers needed by hdas.
|
||||
"""
|
||||
self.histories_mgr = history_manager.HistoryManager()
|
||||
super( HDAManager, self ).__init__( app )
|
||||
self.user_manager = users.UserManager( app )
|
||||
|
||||
def get( self, trans, unencoded_id, check_ownership=True, check_accessible=True ):
|
||||
# .... security and permissions
|
||||
def is_accessible( self, trans, hda, user ):
|
||||
"""
|
||||
Get an HDA by its unencoded db id, checking ownership (via its history)
|
||||
or accessibility (via dataset shares/permissions).
|
||||
Override to allow owners (those that own the associated history).
|
||||
"""
|
||||
hda = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( unencoded_id )
|
||||
if hda is None:
|
||||
raise exceptions.ObjectNotFound()
|
||||
hda = self.secure( trans, hda, check_ownership, check_accessible )
|
||||
return hda
|
||||
if self.is_owner( trans, hda, user ):
|
||||
return True
|
||||
return super( HDAManager, self ).is_accessible( trans, hda, user )
|
||||
|
||||
def secure( self, trans, hda, check_ownership=True, check_accessible=True ):
|
||||
"""
|
||||
check ownership (via its history) or accessibility (via dataset
|
||||
shares/permissions).
|
||||
"""
|
||||
# all items are accessible to an admin
|
||||
if trans.user and trans.user_is_admin():
|
||||
return hda
|
||||
if check_ownership:
|
||||
hda = self.check_ownership( trans, hda )
|
||||
if check_accessible:
|
||||
hda = self.check_accessible( trans, hda )
|
||||
return hda
|
||||
|
||||
def can_access_dataset( self, trans, hda ):
|
||||
"""
|
||||
Use security agent to see if current user has access to dataset.
|
||||
"""
|
||||
current_user_roles = trans.get_current_user_roles()
|
||||
return trans.app.security_agent.can_access_dataset( current_user_roles, hda.dataset )
|
||||
|
||||
#TODO: is_owner, is_accessible
|
||||
|
||||
def check_ownership( self, trans, hda ):
|
||||
def is_owner( self, trans, hda, user ):
|
||||
"""
|
||||
Use history to see if current user owns HDA.
|
||||
"""
|
||||
if not trans.user:
|
||||
#if hda.history == trans.history:
|
||||
# return hda
|
||||
raise exceptions.AuthenticationRequired( "Must be logged in to manage Galaxy datasets", type='error' )
|
||||
if trans.user_is_admin():
|
||||
return hda
|
||||
# check for ownership of the containing history and accessibility of the underlying dataset
|
||||
if( self.histories_mgr.is_owner( trans, hda.history )
|
||||
and self.can_access_dataset( trans, hda ) ):
|
||||
return hda
|
||||
raise exceptions.ItemOwnershipException(
|
||||
"HistoryDatasetAssociation is not owned by the current user", type='error' )
|
||||
history = hda.history
|
||||
#TODO: some dup here with historyManager.is_owner but prevents circ import
|
||||
if self.user_manager.is_admin( trans, user ):
|
||||
return True
|
||||
if self.user_manager.is_anonymous( user ) and history == trans.get_history():
|
||||
return True
|
||||
return history.user == user
|
||||
|
||||
def check_accessible( self, trans, hda ):
|
||||
# .... create and copy
|
||||
def create( self, trans, history=None, dataset=None, flush=True, **kwargs ):
|
||||
"""
|
||||
Raise error if HDA is not accessible.
|
||||
"""
|
||||
if trans.user and trans.user_is_admin():
|
||||
return hda
|
||||
# check for access of the containing history...
|
||||
self.histories_mgr.check_accessible( trans, hda.history )
|
||||
# ...then the underlying dataset
|
||||
if self.can_access_dataset( trans, hda ):
|
||||
return hda
|
||||
raise exceptions.ItemAccessibilityException(
|
||||
"HistoryDatasetAssociation is not accessible to the current user", type='error' )
|
||||
Create a new hda optionally passing in it's history and dataset.
|
||||
|
||||
def err_if_uploading( self, trans, hda ):
|
||||
..note: to explicitly set hid to `None` you must pass in `hid=None`, otherwise
|
||||
it will be automatically set.
|
||||
"""
|
||||
if not dataset:
|
||||
kwargs[ 'create_dataset' ] = True
|
||||
hda = super( HDAManager, self ).create( trans, flush=flush,
|
||||
history=history, dataset=dataset, sa_session=self.app.model.context, **kwargs )
|
||||
|
||||
if history:
|
||||
set_hid = not ( 'hid' in kwargs )
|
||||
history.add_dataset( hda )
|
||||
#TODO:?? some internal sanity check here (or maybe in add_dataset) to make sure hids are not duped?
|
||||
|
||||
self.app.model.context.add( hda )
|
||||
if flush:
|
||||
self.app.model.context.flush()
|
||||
return hda
|
||||
|
||||
def copy( self, trans, hda, history=None, **kwargs ):
|
||||
"""
|
||||
Copy and return the given HDA.
|
||||
"""
|
||||
#TODO:?? not using the following as this fn does not set history and COPIES hid (this doesn't seem correct)
|
||||
#return hda.copy()
|
||||
copy = model.HistoryDatasetAssociation(
|
||||
name = hda.name,
|
||||
info = hda.info,
|
||||
blurb = hda.blurb,
|
||||
peek = hda.peek,
|
||||
tool_version= hda.tool_version,
|
||||
extension = hda.extension,
|
||||
dbkey = hda.dbkey,
|
||||
dataset = hda.dataset,
|
||||
visible = hda.visible,
|
||||
deleted = hda.deleted,
|
||||
parent_id = kwargs.get( 'parent_id', None ),
|
||||
)
|
||||
# add_dataset will update the hid to the next avail. in history
|
||||
if history:
|
||||
history.add_dataset( copy )
|
||||
|
||||
copy.copied_from_history_dataset_association = hda
|
||||
copy.set_size()
|
||||
|
||||
#TODO: update from kwargs?
|
||||
|
||||
# Need to set after flushed, as MetadataFiles require dataset.id
|
||||
self.app.model.context.add( copy )
|
||||
self.app.model.context.flush()
|
||||
copy.metadata = hda.metadata
|
||||
|
||||
# In some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
|
||||
if not hda.datatype.copy_safe_peek:
|
||||
copy.set_peek()
|
||||
|
||||
self.app.model.context.flush()
|
||||
return copy
|
||||
|
||||
def copy_ldda( self, trans, history, ldda, **kwargs ):
|
||||
"""
|
||||
Copy this HDA as a LDDA and return.
|
||||
"""
|
||||
return ldda.to_history_dataset_association( history, add_to_history=True )
|
||||
|
||||
#TODO: stub
|
||||
#def is_a_copy( self, trans, hda ):
|
||||
# pass
|
||||
|
||||
#TODO: stub
|
||||
#def copied_from( self, trans, hda ):
|
||||
# pass
|
||||
|
||||
#TODO: stub
|
||||
#def by_user( self, trans, user ):
|
||||
# pass
|
||||
|
||||
def purge( self, trans, hda, flush=True ):
|
||||
"""
|
||||
Purge this HDA and the dataset underlying it.
|
||||
"""
|
||||
# error here if disallowed - before jobs are stopped
|
||||
#TODO: poss. move to DatasetAssociationManager
|
||||
self.dataset_manager.error_unless_dataset_purge_allowed( trans, hda )
|
||||
super( HDAManager, self ).purge( trans, hda, flush=flush )
|
||||
|
||||
# signal to stop the creating job?
|
||||
if hda.creating_job_associations:
|
||||
job = hda.creating_job_associations[0].job
|
||||
job.mark_deleted( self.app.config.track_jobs_in_database )
|
||||
self.app.job_manager.job_stop_queue.put( job.id )
|
||||
|
||||
# more importantly, purge dataset as well
|
||||
if hda.dataset.user_can_purge:
|
||||
self.dataset_manager.purge( trans, hda.dataset )
|
||||
return hda
|
||||
|
||||
def error_if_uploading( self, trans, hda ):
|
||||
"""
|
||||
Raise error if HDA is still uploading.
|
||||
"""
|
||||
if hda.state == trans.model.Dataset.states.UPLOAD:
|
||||
#TODO: may be better added to an overridden get_accessible
|
||||
if hda.state == model.Dataset.states.UPLOAD:
|
||||
raise exceptions.Conflict( "Please wait until this dataset finishes uploading" )
|
||||
return hda
|
||||
|
||||
def get_hda_dict( self, trans, hda ):
|
||||
"""
|
||||
Return full details of this HDA in dictionary form.
|
||||
"""
|
||||
#precondition: the user's access to this hda has already been checked
|
||||
#TODO:?? postcondition: all ids are encoded (is this really what we want at this level?)
|
||||
expose_dataset_path = trans.user_is_admin() or trans.app.config.expose_dataset_path
|
||||
hda_dict = hda.to_dict( view='element', expose_dataset_path=expose_dataset_path )
|
||||
hda_dict[ 'api_type' ] = "file"
|
||||
# .... via history
|
||||
#def by_history_id( self, trans, history_id, filters=None, **kwargs ):
|
||||
# history_id_filter = self.model_class.history_id == history_id
|
||||
# filters = self._munge_filters( history_id_filter, filters )
|
||||
# return self.list( trans, filters=filters, **kwargs )
|
||||
|
||||
# Add additional attributes that depend on trans must be added here rather than at the model level.
|
||||
can_access_hda = trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), hda.dataset )
|
||||
can_access_hda = ( trans.user_is_admin() or can_access_hda )
|
||||
if not can_access_hda:
|
||||
return self.get_inaccessible_hda_dict( trans, hda )
|
||||
hda_dict[ 'accessible' ] = True
|
||||
#def by_history( self, trans, history, filters=None, **kwargs ):
|
||||
# return history.datasets
|
||||
|
||||
#TODO: I'm unclear as to which access pattern is right
|
||||
hda_dict[ 'annotation' ] = hda.get_item_annotation_str( trans.sa_session, hda.history.user, hda )
|
||||
#annotation = getattr( hda, 'annotation', hda.get_item_annotation_str( trans.sa_session, trans.user, hda ) )
|
||||
|
||||
# ---- return here if deleted AND purged OR can't access
|
||||
purged = ( hda.purged or hda.dataset.purged )
|
||||
if ( hda.deleted and purged ):
|
||||
#TODO: to_dict should really go AFTER this - only summary data
|
||||
return trans.security.encode_dict_ids( hda_dict )
|
||||
|
||||
if expose_dataset_path:
|
||||
try:
|
||||
hda_dict[ 'file_name' ] = hda.file_name
|
||||
except objectstore.ObjectNotFound:
|
||||
log.exception( 'objectstore.ObjectNotFound, HDA %s.', hda.id )
|
||||
|
||||
hda_dict[ 'download_url' ] = galaxy.web.url_for( 'history_contents_display',
|
||||
history_id = trans.security.encode_id( hda.history.id ),
|
||||
history_content_id = trans.security.encode_id( hda.id ) )
|
||||
|
||||
# indeces, assoc. metadata files, etc.
|
||||
meta_files = []
|
||||
for meta_type in hda.metadata.spec.keys():
|
||||
if isinstance( hda.metadata.spec[ meta_type ].param, galaxy.datatypes.metadata.FileParameter ):
|
||||
meta_files.append( dict( file_type=meta_type ) )
|
||||
if meta_files:
|
||||
hda_dict[ 'meta_files' ] = meta_files
|
||||
|
||||
# currently, the viz reg is optional - handle on/off
|
||||
if trans.app.visualizations_registry:
|
||||
hda_dict[ 'visualizations' ] = trans.app.visualizations_registry.get_visualizations( trans, hda )
|
||||
else:
|
||||
hda_dict[ 'visualizations' ] = hda.get_visualizations()
|
||||
#TODO: it may also be wiser to remove from here and add as API call that loads the visualizations
|
||||
# when the visualizations button is clicked (instead of preloading/pre-checking)
|
||||
|
||||
# ---- return here if deleted
|
||||
if hda.deleted and not purged:
|
||||
return trans.security.encode_dict_ids( hda_dict )
|
||||
|
||||
return trans.security.encode_dict_ids( hda_dict )
|
||||
|
||||
def get_inaccessible_hda_dict( self, trans, hda ):
|
||||
"""
|
||||
Return truncated serialization of HDA when inaccessible to user.
|
||||
"""
|
||||
return trans.security.encode_dict_ids({
|
||||
'id' : hda.id,
|
||||
'history_id': hda.history.id,
|
||||
'hid' : hda.hid,
|
||||
'name' : hda.name,
|
||||
'state' : hda.state,
|
||||
'deleted' : hda.deleted,
|
||||
'visible' : hda.visible,
|
||||
'accessible': False
|
||||
})
|
||||
|
||||
def get_hda_dict_with_error( self, trans, hda=None, history_id=None, id=None, error_msg='Error' ):
|
||||
"""
|
||||
Return truncated serialization of HDA when error raised getting
|
||||
details.
|
||||
"""
|
||||
return trans.security.encode_dict_ids({
|
||||
'id' : hda.id if hda else id,
|
||||
'history_id': hda.history.id if hda else history_id,
|
||||
'hid' : hda.hid if hda else '(unknown)',
|
||||
'name' : hda.name if hda else '(unknown)',
|
||||
'error' : error_msg,
|
||||
'state' : trans.model.Dataset.states.NEW
|
||||
})
|
||||
# .... associated
|
||||
def creating_job( self, trans, hda ):
|
||||
#TODO: is this needed? Can't you use the hda.creating_job attribute? When is this None?
|
||||
job = None
|
||||
for job_output_assoc in hda.creating_job_associations:
|
||||
job = job_output_assoc.job
|
||||
break
|
||||
return job
|
||||
|
||||
# .... serialization
|
||||
def get_display_apps( self, trans, hda ):
|
||||
"""
|
||||
Return dictionary containing new-style display app urls.
|
||||
@@ -198,7 +207,7 @@ class HDAManager( manager_base.ModelManager ):
|
||||
app_links.append({
|
||||
'target': link_app.url.get( 'target_frame', '_blank' ),
|
||||
'href' : link_app.get_display_url( hda, trans ),
|
||||
'text' : gettext( link_app.name )
|
||||
'text' : gettext.gettext( link_app.name )
|
||||
})
|
||||
if app_links:
|
||||
display_apps.append( dict( label=display_app.name, links=app_links ) )
|
||||
@@ -210,12 +219,12 @@ class HDAManager( manager_base.ModelManager ):
|
||||
Return dictionary containing old-style display app urls.
|
||||
"""
|
||||
display_apps = []
|
||||
if not trans.app.config.enable_old_display_applications:
|
||||
if not self.app.config.enable_old_display_applications:
|
||||
return display_apps
|
||||
|
||||
for display_app in hda.datatype.get_display_types():
|
||||
target_frame, display_links = hda.datatype.get_display_links( hda,
|
||||
display_app, trans.app, trans.request.base )
|
||||
display_app, self.app, trans.request.base )
|
||||
|
||||
if len( display_links ) > 0:
|
||||
display_label = hda.datatype.get_display_label( display_app )
|
||||
@@ -225,17 +234,361 @@ class HDAManager( manager_base.ModelManager ):
|
||||
app_links.append({
|
||||
'target': target_frame,
|
||||
'href' : display_link,
|
||||
'text' : gettext( display_name )
|
||||
'text' : gettext.gettext( display_name )
|
||||
})
|
||||
if app_links:
|
||||
display_apps.append( dict( label=display_label, links=app_links ) )
|
||||
|
||||
return display_apps
|
||||
|
||||
def get_visualizations( self, trans, hda ):
|
||||
"""
|
||||
Return a list of dictionaries with links to visualization pages
|
||||
for those visualizations that apply to this hda.
|
||||
"""
|
||||
# use older system if registry is off in the config
|
||||
if not self.app.visualizations_registry:
|
||||
return hda.get_visualizations()
|
||||
return self.app.visualizations_registry.get_visualizations( trans, hda )
|
||||
|
||||
# =============================================================================
|
||||
class HistorySerializer( manager_base.ModelSerializer ):
|
||||
#TODO: to data provider or Text datatype directly
|
||||
def text_data( self, dataset, preview=True ):
|
||||
"""
|
||||
Get data from text file, truncating if necessary.
|
||||
"""
|
||||
truncated = False
|
||||
dataset_data = None
|
||||
if os.path.exists( dataset.file_name ):
|
||||
if isinstance( dataset.datatype, datatypes.data.Text ):
|
||||
max_peek_size = 1000000 # 1 MB
|
||||
if preview and os.stat( dataset.file_name ).st_size > max_peek_size:
|
||||
dataset_data = open( dataset.file_name ).read( max_peek_size )
|
||||
truncated = True
|
||||
else:
|
||||
dataset_data = open( dataset.file_name ).read( max_peek_size )
|
||||
truncated = False
|
||||
else:
|
||||
# For now, cannot get data from non-text datasets.
|
||||
dataset_data = None
|
||||
return truncated, dataset_data
|
||||
|
||||
# this is a weird syntax and return val
|
||||
def data_conversion_status( self, trans, hda ):
|
||||
"""
|
||||
Returns a message if dataset is not ready to be used in visualization.
|
||||
"""
|
||||
if not hda:
|
||||
return hda.conversion_messages.NO_DATA
|
||||
if hda.state == model.Job.states.ERROR:
|
||||
return hda.conversion_messages.ERROR
|
||||
if hda.state != model.Job.states.OK:
|
||||
return hda.conversion_messages.PENDING
|
||||
return None
|
||||
|
||||
def stop_creating_job( self, hda ):
|
||||
"""
|
||||
Stops an HDA's creating job if all the job's other outputs are deleted.
|
||||
"""
|
||||
RUNNING_STATES = (
|
||||
self.app.model.Job.states.QUEUED,
|
||||
self.app.model.Job.states.RUNNING,
|
||||
self.app.model.Job.states.NEW
|
||||
)
|
||||
if hda.parent_id is None and len( hda.creating_job_associations ) > 0:
|
||||
# Mark associated job for deletion
|
||||
job = hda.creating_job_associations[0].job
|
||||
if job.state in RUNNING_STATES:
|
||||
# Are *all* of the job's other output datasets deleted?
|
||||
if job.check_if_output_datasets_deleted():
|
||||
job.mark_deleted( self.app.config.track_jobs_in_database )
|
||||
self.app.job_manager.job_stop_queue.put( job.id )
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class HDASerializer( datasets.DatasetAssociationSerializer,
|
||||
taggable.TaggableSerializerMixin, annotatable.AnnotatableSerializerMixin ):
|
||||
#TODO: inherit from datasets.DatasetAssociationSerializer
|
||||
#TODO: move what makes sense into DatasetSerializer
|
||||
|
||||
def __init__( self, app ):
|
||||
super( HDASerializer, self ).__init__( app )
|
||||
self.hda_manager = HDAManager( app )
|
||||
|
||||
self.default_view = 'summary'
|
||||
self.add_view( 'summary', [
|
||||
'id', 'name',
|
||||
'history_id', 'hid',
|
||||
# why include if model_class is there?
|
||||
'history_content_type',
|
||||
'dataset_id',
|
||||
'state', 'extension',
|
||||
'deleted', 'purged', 'visible', 'resubmitted',
|
||||
'type', 'url'
|
||||
])
|
||||
self.add_view( 'detailed', [
|
||||
'model_class',
|
||||
'history_id', 'hid',
|
||||
# why include if model_class is there?
|
||||
'hda_ldda',
|
||||
#TODO: accessible needs to go away
|
||||
'accessible',
|
||||
|
||||
# remapped
|
||||
'genome_build', 'misc_info', 'misc_blurb',
|
||||
'file_ext', 'file_size',
|
||||
|
||||
'create_time', 'update_time',
|
||||
'metadata', 'meta_files', 'data_type',
|
||||
'peek',
|
||||
|
||||
#TODO: why is this named uuid!? The hda doesn't have a uuid - it's the underlying dataset's uuid!
|
||||
'uuid',
|
||||
# should be:
|
||||
#'dataset_uuid',
|
||||
|
||||
'file_path',
|
||||
'display_apps',
|
||||
'display_types',
|
||||
'visualizations',
|
||||
|
||||
#'url',
|
||||
'download_url',
|
||||
|
||||
'annotation', 'tags',
|
||||
|
||||
'api_type'
|
||||
], include_keys_from='summary' )
|
||||
|
||||
self.add_view( 'extended', [
|
||||
'tool_version', 'parent_id', 'designation',
|
||||
], include_keys_from='detailed' )
|
||||
|
||||
# keyset returned to create show a dataset where the owner has no access
|
||||
self.add_view( 'inaccessible', [
|
||||
'id', 'name', 'history_id', 'hid', 'history_content_type',
|
||||
'state', 'deleted', 'visible'
|
||||
])
|
||||
|
||||
def add_serializers( self ):
|
||||
super( HDASerializer, self ).add_serializers()
|
||||
taggable.TaggableSerializerMixin.add_serializers( self )
|
||||
annotatable.AnnotatableSerializerMixin.add_serializers( self )
|
||||
|
||||
self.serializers.update({
|
||||
'model_class' : lambda *a: 'HistoryDatasetAssociation',
|
||||
#TODO: accessible needs to go away
|
||||
'accessible' : lambda *a: True,
|
||||
|
||||
'id' : self.serialize_id,
|
||||
'history_id' : self.serialize_id,
|
||||
'history_content_type': lambda *a: 'dataset',
|
||||
'dataset_id' : self.serialize_id,
|
||||
'hda_ldda' : lambda *a: 'hda',
|
||||
|
||||
# remapped
|
||||
'info' : lambda t, i, k: i.info.strip() if isinstance( i.info, basestring ) else i.info,
|
||||
'misc_info' : lambda t, i, k: self.serializers[ 'info' ]( t, i, k ),
|
||||
'misc_blurb' : lambda t, i, k: i.blurb,
|
||||
'genome_build' : lambda t, i, k: i.dbkey,
|
||||
'file_ext' : lambda t, i, k: i.extension,
|
||||
'file_size' : lambda t, i, k: self.serializers[ 'size' ]( t, i, k ),
|
||||
|
||||
'create_time' : self.serialize_date,
|
||||
'update_time' : self.serialize_date,
|
||||
'copied_from_history_dataset_association_id' : self.serialize_id,
|
||||
'copied_from_library_dataset_dataset_association_id': self.serialize_id,
|
||||
|
||||
'resubmitted' : lambda t, i, k: i._state == t.app.model.Dataset.states.RESUBMITTED,
|
||||
|
||||
'meta_files' : self.serialize_meta_files,
|
||||
'file_path' : self.serialize_file_path,
|
||||
|
||||
'size' : lambda t, i, k: int( i.get_size() ),
|
||||
'nice_size' : lambda t, i, k: i.get_size( nice_size=True ),
|
||||
'data_type' : lambda t, i, k: i.datatype.__class__.__module__ + '.' + i.datatype.__class__.__name__,
|
||||
'peek' : lambda t, i, k: i.display_peek() if i.peek and i.peek != 'no peek' else None,
|
||||
|
||||
# currently we send this sub-obj flattened (see serialize and add_flattened_metadata below)
|
||||
#'metadata' : self.serialize_metadata,
|
||||
# make it available here under a different key
|
||||
'metadata_dict' : self.serialize_metadata,
|
||||
|
||||
'parent_id' : self.serialize_id,
|
||||
'annotation' : self.serialize_annotation,
|
||||
'tags' : self.serialize_tags,
|
||||
|
||||
'display_apps' : lambda t, i, k: self.hda_manager.get_display_apps( t, i ),
|
||||
'display_types' : lambda t, i, k: self.hda_manager.get_old_display_applications( t, i ),
|
||||
'visualizations': lambda t, i, k: self.hda_manager.get_visualizations( t, i ),
|
||||
|
||||
'dataset_uuid' : lambda t, i, k: str( i.dataset.uuid ) if i.dataset.uuid else None,
|
||||
'uuid' : lambda t, i, k: self.serializers[ 'dataset_uuid' ]( t, i, k ),
|
||||
|
||||
#'url' : url_for( 'history_content_typed', history_id=encoded_history_id, id=encoded_id, type="dataset" ),
|
||||
#TODO: this intermittently causes a routes.GenerationException - temp use the legacy route to prevent this
|
||||
# see also: https://trello.com/c/5d6j4X5y
|
||||
# see also: https://sentry.galaxyproject.org/galaxy/galaxy-main/group/20769/events/9352883/
|
||||
'url' : lambda t, i, k: self.url_for( 'history_content',
|
||||
history_id=t.security.encode_id( i.history_id ), id=t.security.encode_id( i.id ) ),
|
||||
|
||||
'urls' : self.serialize_urls,
|
||||
'download_url' : lambda t, i, k: self.url_for( 'history_contents_display',
|
||||
history_id=t.security.encode_id( i.history.id ),
|
||||
history_content_id=t.security.encode_id( i.id ) ),
|
||||
|
||||
'api_type' : lambda *a: 'file',
|
||||
'type' : lambda *a: 'file'
|
||||
})
|
||||
|
||||
def serialize( self, trans, hda, keys ):
|
||||
"""
|
||||
Override to add metadata as flattened keys on the serialized HDA.
|
||||
"""
|
||||
# if 'metadata' isn't removed from keys here serialize will retrieve the un-serializable MetadataCollection
|
||||
#TODO: remove these when metadata is sub-object
|
||||
KEYS_HANDLED_SEPARATELY = ( 'metadata', )
|
||||
left_to_handle = self.pluck_from_list( keys, KEYS_HANDLED_SEPARATELY )
|
||||
serialized = super( HDASerializer, self ).serialize( trans, hda, keys )
|
||||
|
||||
if 'metadata' in left_to_handle:
|
||||
# we currently add metadata directly to the dict instead of as a sub-object
|
||||
metadata = self.prefixed_metadata( trans, hda )
|
||||
serialized.update( metadata )
|
||||
return serialized
|
||||
|
||||
#TODO: this is more util/gen. use
|
||||
def pluck_from_list( self, l, elems ):
|
||||
"""
|
||||
Removes found elems from list l and returns list of found elems if found.
|
||||
"""
|
||||
found = []
|
||||
for elem in elems:
|
||||
try:
|
||||
index = l.index( elem )
|
||||
found.append( l.pop( index ) )
|
||||
except ValueError, val_err:
|
||||
pass
|
||||
return found
|
||||
|
||||
def prefixed_metadata( self, trans, hda ):
|
||||
"""
|
||||
Adds (a prefixed version of) the hda metadata to the dict, prefixing each key
|
||||
with 'metadata_'.
|
||||
"""
|
||||
metadata = self.serialize_metadata( trans, hda, 'metadata' )
|
||||
#TODO: this is factored out for removal - metadata should be a sub-object instead:
|
||||
# i.e. 'metadata' : self.serialize_metadata,
|
||||
prefixed = {}
|
||||
for key, val in metadata.items():
|
||||
prefixed_key = 'metadata_' + key
|
||||
prefixed[ prefixed_key ] = val
|
||||
return prefixed
|
||||
|
||||
def serialize_metadata( self, trans, hda, key, excluded=None ):
|
||||
"""
|
||||
Cycle through metadata and return as dictionary.
|
||||
"""
|
||||
# dbkey is a repeat actually (metadata_dbkey == genome_build)
|
||||
#excluded = [ 'dbkey' ] if excluded is None else excluded
|
||||
excluded = [] if excluded is None else excluded
|
||||
|
||||
metadata = {}
|
||||
for name, spec in hda.metadata.spec.items():
|
||||
if name in excluded:
|
||||
continue
|
||||
val = hda.metadata.get( name )
|
||||
#NOTE: no files
|
||||
if isinstance( val, model.MetadataFile ):
|
||||
# only when explicitly set: fetching filepaths can be expensive
|
||||
if not self.app.config.expose_dataset_path:
|
||||
continue
|
||||
val = val.file_name
|
||||
#TODO:? possibly split this off?
|
||||
# If no value for metadata, look in datatype for metadata.
|
||||
elif val is None and hasattr( hda.datatype, name ):
|
||||
val = getattr( hda.datatype, name )
|
||||
metadata[ name ] = val
|
||||
|
||||
return metadata
|
||||
|
||||
# add to serialize_metadata above
|
||||
def serialize_meta_files( self, trans, hda, key ):
|
||||
"""
|
||||
Cycle through meta files and return them as a list of dictionaries.
|
||||
"""
|
||||
meta_files = []
|
||||
for meta_type in hda.metadata.spec.keys():
|
||||
if isinstance( hda.metadata.spec[ meta_type ].param, galaxy.datatypes.metadata.FileParameter ):
|
||||
meta_files.append( dict( file_type=meta_type ) )
|
||||
return meta_files
|
||||
|
||||
#def file_info #TODO
|
||||
#TODO: and to dataset instead (passing through object store)
|
||||
def serialize_file_path( self, trans, hda, key ):
|
||||
"""
|
||||
Return the `file_name` of the HDA if the config exposes it, None otherwise.
|
||||
"""
|
||||
#TODO: allow admin
|
||||
if self.app.config.expose_dataset_path:
|
||||
try:
|
||||
return hda.file_name
|
||||
except objectstore.ObjectNotFound:
|
||||
log.exception( 'objectstore.ObjectNotFound, HDA %s.', hda.id )
|
||||
return None
|
||||
|
||||
def serialize_urls( self, trans, hda, key ):
|
||||
"""
|
||||
Return web controller urls useful for this HDA.
|
||||
"""
|
||||
url_for = self.url_for
|
||||
encoded_id = self.security.encode_id( hda.id )
|
||||
urls = {
|
||||
'purge' : url_for( controller='dataset', action='purge_async', dataset_id=encoded_id ),
|
||||
'display' : url_for( controller='dataset', action='display', dataset_id=encoded_id, preview=True ),
|
||||
'edit' : url_for( controller='dataset', action='edit', dataset_id=encoded_id ),
|
||||
'download' : url_for( controller='dataset', action='display',
|
||||
dataset_id=encoded_id, to_ext=hda.extension ),
|
||||
'report_error' : url_for( controller='dataset', action='errors', id=encoded_id ),
|
||||
'rerun' : url_for( controller='tool_runner', action='rerun', id=encoded_id ),
|
||||
'show_params' : url_for( controller='dataset', action='show_params', dataset_id=encoded_id ),
|
||||
'visualization' : url_for( controller='visualization', action='index',
|
||||
id=encoded_id, model='HistoryDatasetAssociation' ),
|
||||
'meta_download' : url_for( controller='dataset', action='get_metadata_file',
|
||||
hda_id=encoded_id, metadata_name='' ),
|
||||
}
|
||||
return urls
|
||||
|
||||
|
||||
class HDADeserializer( datasets.DatasetAssociationDeserializer,
|
||||
taggable.TaggableDeserializerMixin, annotatable.AnnotatableDeserializerMixin ):
|
||||
"""
|
||||
Interface/service object for serializing HDAs into dictionaries.
|
||||
Interface/service object for validating and deserializing dictionaries into histories.
|
||||
"""
|
||||
pass
|
||||
model_manager_class = HDAManager
|
||||
|
||||
def __init__( self, app ):
|
||||
super( HDADeserializer, self ).__init__( app )
|
||||
self.hda_manager = self.manager
|
||||
|
||||
def add_deserializers( self ):
|
||||
super( HDADeserializer, self ).add_deserializers()
|
||||
taggable.TaggableDeserializerMixin.add_deserializers( self )
|
||||
annotatable.AnnotatableDeserializerMixin.add_deserializers( self )
|
||||
|
||||
self.deserializers.update({
|
||||
'name' : self.deserialize_basestring,
|
||||
'visible' : self.deserialize_bool,
|
||||
|
||||
# remapped
|
||||
'genome_build' : lambda t, i, k, v: self.deserialize_genome_build( t, i, 'dbkey', v ),
|
||||
'misc_info' : lambda t, i, k, v: self.deserialize_basestring( t, i, 'info', v ),
|
||||
|
||||
#TODO: mixin: deletable
|
||||
'deleted' : self.deserialize_bool,
|
||||
# sharable
|
||||
'published' : self.deserialize_bool,
|
||||
'importable' : self.deserialize_bool,
|
||||
|
||||
})
|
||||
self.deserializable_keyset.update( self.deserializers.keys() )
|
||||
|
||||
+301
-165
@@ -5,179 +5,155 @@ Histories are containers for datasets or dataset collections
|
||||
created (or copied) by users over the course of an analysis.
|
||||
"""
|
||||
|
||||
from galaxy import exceptions
|
||||
from galaxy.model import orm
|
||||
|
||||
from galaxy.managers import base as manager_base
|
||||
import galaxy.managers.collections_util
|
||||
|
||||
import galaxy.web
|
||||
from galaxy import model
|
||||
from galaxy.managers import base
|
||||
from galaxy.managers import sharable
|
||||
from galaxy.managers import deletable
|
||||
from galaxy.managers import hdas
|
||||
from galaxy.managers.collections_util import dictify_dataset_collection_instance
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
# =============================================================================
|
||||
class HistoryManager( manager_base.ModelManager ):
|
||||
"""
|
||||
Interface/service object for interacting with HDAs.
|
||||
"""
|
||||
class HistoryManager( sharable.SharableModelManager, deletable.PurgableManagerMixin ):
|
||||
|
||||
#TODO: all the following would be more useful if passed the user instead of defaulting to trans.user
|
||||
def __init__( self, *args, **kwargs ):
|
||||
super( HistoryManager, self ).__init__( *args, **kwargs )
|
||||
model_class = model.History
|
||||
foreign_key_name = 'history'
|
||||
user_share_model = model.HistoryUserShareAssociation
|
||||
|
||||
def get( self, trans, unencoded_id, check_ownership=True, check_accessible=True, deleted=None ):
|
||||
tag_assoc = model.HistoryTagAssociation
|
||||
annotation_assoc = model.HistoryAnnotationAssociation
|
||||
rating_assoc = model.HistoryRatingAssociation
|
||||
|
||||
#TODO: incorporate imp/exp (or alias to)
|
||||
|
||||
def __init__( self, app, *args, **kwargs ):
|
||||
super( HistoryManager, self ).__init__( app, *args, **kwargs )
|
||||
|
||||
self.hda_manager = hdas.HDAManager( app )
|
||||
|
||||
def copy( self, trans, history, user, **kwargs ):
|
||||
"""
|
||||
Get a History from the database by id, verifying ownership.
|
||||
Copy and return the given `history`.
|
||||
"""
|
||||
# this is a replacement for UsesHistoryMixin because mixins are a bad soln/structure
|
||||
history = trans.sa_session.query( trans.app.model.History ).get( unencoded_id )
|
||||
if history is None:
|
||||
raise exceptions.ObjectNotFound()
|
||||
if deleted is True and not history.deleted:
|
||||
raise exceptions.ItemDeletionException( 'History "%s" is not deleted' % ( history.name ), type="error" )
|
||||
elif deleted is False and history.deleted:
|
||||
raise exceptions.ItemDeletionException( 'History "%s" is deleted' % ( history.name ), type="error" )
|
||||
return history.copy( target_user=user, **kwargs )
|
||||
|
||||
history = self.secure( trans, history, check_ownership, check_accessible )
|
||||
return history
|
||||
|
||||
def by_user( self, trans, user=None, include_deleted=False, only_deleted=False ):
|
||||
# .... sharable
|
||||
# overriding to handle anonymous users' current histories in both cases
|
||||
def by_user( self, trans, user, **kwargs ):
|
||||
"""
|
||||
Get all the histories for a given user (defaulting to `trans.user`)
|
||||
ordered by update time and filtered on whether they've been deleted.
|
||||
Get all the histories for a given user (allowing anon users' theirs)
|
||||
ordered by update time.
|
||||
"""
|
||||
# handle default and/or anonymous user (which still may not have a history yet)
|
||||
user = user or trans.user
|
||||
if not user:
|
||||
current_history = trans.get_history()
|
||||
if self.user_manager.is_anonymous( user ):
|
||||
current_history = self.get_current( trans )
|
||||
return [ current_history ] if current_history else []
|
||||
|
||||
history_model = trans.model.History
|
||||
query = ( trans.sa_session.query( history_model )
|
||||
.filter( history_model.user == user )
|
||||
.order_by( orm.desc( history_model.table.c.update_time ) ) )
|
||||
if only_deleted:
|
||||
query = query.filter( history_model.deleted == True )
|
||||
elif not include_deleted:
|
||||
query = query.filter( history_model.deleted == False )
|
||||
return query.all()
|
||||
return super( HistoryManager, self ).by_user( trans, user, **kwargs )
|
||||
|
||||
def secure( self, trans, history, check_ownership=True, check_accessible=True ):
|
||||
"""
|
||||
Checks if (a) user owns item or (b) item is accessible to user.
|
||||
"""
|
||||
# all items are accessible to an admin
|
||||
if trans.user and trans.user_is_admin():
|
||||
return history
|
||||
if check_ownership:
|
||||
history = self.check_ownership( trans, history )
|
||||
if check_accessible:
|
||||
history = self.check_accessible( trans, history )
|
||||
return history
|
||||
|
||||
def is_current( self, trans, history ):
|
||||
"""
|
||||
True if the given history is the user's current history.
|
||||
|
||||
Returns False if the session has no current history.
|
||||
"""
|
||||
if trans.history is None:
|
||||
return False
|
||||
return trans.history == history
|
||||
|
||||
def is_owner( self, trans, history ):
|
||||
def is_owner( self, trans, history, user ):
|
||||
"""
|
||||
True if the current user is the owner of the given history.
|
||||
"""
|
||||
# anon users are only allowed to view their current history
|
||||
if not trans.user:
|
||||
return self.is_current( trans, history )
|
||||
return trans.user == history.user
|
||||
|
||||
def check_ownership( self, trans, history ):
|
||||
"""
|
||||
Raises error if the current user is not the owner of the history.
|
||||
"""
|
||||
if trans.user and trans.user_is_admin():
|
||||
return history
|
||||
if not trans.user and not self.is_current( trans, history ):
|
||||
raise exceptions.AuthenticationRequired( "Must be logged in to manage Galaxy histories", type='error' )
|
||||
if self.is_owner( trans, history ):
|
||||
return history
|
||||
raise exceptions.ItemOwnershipException( "History is not owned by the current user", type='error' )
|
||||
|
||||
def is_accessible( self, trans, history ):
|
||||
"""
|
||||
True if the user can access (read) the current history.
|
||||
"""
|
||||
# admin always have access
|
||||
if trans.user and trans.user_is_admin():
|
||||
if self.user_manager.is_anonymous( user ) and history == self.get_current( trans ):
|
||||
return True
|
||||
# owner has implicit access
|
||||
if self.is_owner( trans, history ):
|
||||
return True
|
||||
# importable and shared histories are always accessible
|
||||
if history.importable:
|
||||
return True
|
||||
if trans.user in history.users_shared_with_dot_users:
|
||||
return True
|
||||
return False
|
||||
return super( HistoryManager, self ).is_owner( trans, history, user )
|
||||
|
||||
def check_accessible( self, trans, history ):
|
||||
#TODO: possibly to sharable
|
||||
def most_recent( self, trans, user, filters=None, **kwargs ):
|
||||
"""
|
||||
Raises error if the current user can't access the history.
|
||||
"""
|
||||
if self.is_accessible( trans, history ):
|
||||
return history
|
||||
raise exceptions.ItemAccessibilityException( "History is not accessible to the current user", type='error' )
|
||||
Return the most recently update history for the user.
|
||||
|
||||
#TODO: bleh...
|
||||
If user is anonymous, return the current history. If the user is anonymous
|
||||
and the current history is deleted, return None.
|
||||
"""
|
||||
#TODO: trans
|
||||
if not user:
|
||||
current_history = self.get_current( trans )
|
||||
return None if ( not current_history or current_history.deleted ) else current_history
|
||||
desc_update_time = self.model_class.table.c.update_time
|
||||
filters = self._munge_filters( filters, self.model_class.user_id == user.id )
|
||||
#TODO: normalize this return value
|
||||
return self.query( trans, filters=filters, order_by=desc_update_time, limit=1, **kwargs ).first()
|
||||
|
||||
# .... purgable
|
||||
def purge( self, trans, history, flush=True, **kwargs ):
|
||||
"""
|
||||
Purge this history and all HDAs, Collections, and Datasets inside this history.
|
||||
"""
|
||||
self.hda_manager.dataset_manager.error_unless_dataset_purge_allowed( trans, history )
|
||||
|
||||
# First purge all the datasets
|
||||
for hda in history.datasets:
|
||||
if not hda.purged:
|
||||
self.hda_manager.purge( trans, hda, flush=True )
|
||||
|
||||
# Now mark the history as purged
|
||||
super( HistoryManager, self ).purge( trans, history, flush=flush, **kwargs )
|
||||
|
||||
# .... current
|
||||
def get_current( self, trans ):
|
||||
"""
|
||||
Return the current history.
|
||||
"""
|
||||
#TODO: trans
|
||||
return trans.get_history()
|
||||
|
||||
def set_current( self, trans, history ):
|
||||
"""
|
||||
Set the current history.
|
||||
"""
|
||||
#TODO: trans
|
||||
trans.set_history( history )
|
||||
return history
|
||||
|
||||
def set_current_by_id( self, trans, history_id ):
|
||||
"""
|
||||
Set the current history by an id.
|
||||
"""
|
||||
return self.set_current( trans, self.by_id( trans, history_id ) )
|
||||
|
||||
# .... serialization
|
||||
#TODO: move to serializer (i.e. history with contents attr)
|
||||
def _get_history_data( self, trans, history ):
|
||||
"""
|
||||
Returns a dictionary containing ``history`` and ``contents``, serialized
|
||||
history and an array of serialized history contents respectively.
|
||||
"""
|
||||
# import here prevents problems related to circular dependecy between histories and hdas managers.
|
||||
import galaxy.managers.hdas
|
||||
hda_mgr = galaxy.managers.hdas.HDAManager()
|
||||
collection_dictifier = galaxy.managers.collections_util.dictify_dataset_collection_instance
|
||||
|
||||
#TODO: instantiate here? really?
|
||||
history_serializer = HistorySerializer( self.app )
|
||||
hda_serializer = hdas.HDASerializer( self.app )
|
||||
history_dictionary = {}
|
||||
contents_dictionaries = []
|
||||
try:
|
||||
history_dictionary = history_serializer.serialize_to_view( trans, history, view='detailed' )
|
||||
|
||||
#for content in history.contents_iter( **contents_kwds ):
|
||||
for content in history.contents_iter( types=[ 'dataset', 'dataset_collection' ] ):
|
||||
hda_dict = {}
|
||||
contents_dict = {}
|
||||
|
||||
if isinstance( content, trans.app.model.HistoryDatasetAssociation ):
|
||||
try:
|
||||
hda_dict = hda_mgr.get_hda_dict( trans, content )
|
||||
except Exception, exc:
|
||||
# don't fail entire list if hda err's, record and move on
|
||||
log.exception( 'Error bootstrapping hda: %s', exc )
|
||||
hda_dict = hda_mgr.get_hda_dict_with_error( trans, content, str( exc ) )
|
||||
if isinstance( content, model.HistoryDatasetAssociation ):
|
||||
contents_dict = hda_serializer.serialize_to_view( trans, content, view='detailed' )
|
||||
|
||||
elif isinstance( content, trans.app.model.HistoryDatasetCollectionAssociation ):
|
||||
elif isinstance( content, model.HistoryDatasetCollectionAssociation ):
|
||||
try:
|
||||
service = trans.app.dataset_collections_service
|
||||
service = self.app.dataset_collections_service
|
||||
dataset_collection_instance = service.get_dataset_collection_instance(
|
||||
trans=trans,
|
||||
instance_type='history',
|
||||
id=trans.security.encode_id( content.id ),
|
||||
id=self.app.security.encode_id( content.id ),
|
||||
)
|
||||
hda_dict = collection_dictifier( dataset_collection_instance,
|
||||
security=trans.security, parent=dataset_collection_instance.history, view="element" )
|
||||
contents_dict = dictify_dataset_collection_instance( dataset_collection_instance,
|
||||
security=self.app.security, parent=dataset_collection_instance.history, view="element" )
|
||||
|
||||
except Exception, exc:
|
||||
log.exception( "Error in history API at listing dataset collection: %s", exc )
|
||||
#TODO: return some dict with the error
|
||||
|
||||
contents_dictionaries.append( hda_dict )
|
||||
|
||||
# re-use the hdas above to get the history data...
|
||||
history_dictionary = self.get_history_dict( trans, history, contents_dictionaries=contents_dictionaries )
|
||||
contents_dictionaries.append( contents_dict )
|
||||
|
||||
except Exception, exc:
|
||||
user_id = str( trans.user.id ) if trans.user else '(anonymous)'
|
||||
@@ -191,53 +167,213 @@ class HistoryManager( manager_base.ModelManager ):
|
||||
'contents' : contents_dictionaries
|
||||
}
|
||||
|
||||
def get_history_dict( self, trans, history, contents_dictionaries=None ):
|
||||
# remove this
|
||||
def get_state_counts( self, trans, history, exclude_deleted=True, exclude_hidden=False ):
|
||||
"""
|
||||
Returns history data in the form of a dictionary.
|
||||
Return a dictionary keyed to possible dataset states and valued with the number
|
||||
of datasets in this history that have those states.
|
||||
"""
|
||||
#TODO: to serializer
|
||||
history_dict = history.to_dict( view='element', value_mapper={ 'id':trans.security.encode_id })
|
||||
history_dict[ 'user_id' ] = None
|
||||
if history.user_id:
|
||||
history_dict[ 'user_id' ] = trans.security.encode_id( history.user_id )
|
||||
#TODO: the default flags above may not make a lot of sense (T,T?)
|
||||
state_counts = {}
|
||||
for state in model.Dataset.states.values():
|
||||
state_counts[ state ] = 0
|
||||
|
||||
history_dict[ 'nice_size' ] = history.get_disk_size( nice_size=True )
|
||||
history_dict[ 'annotation' ] = history.get_item_annotation_str( trans.sa_session, history.user, history )
|
||||
if not history_dict[ 'annotation' ]:
|
||||
history_dict[ 'annotation' ] = ''
|
||||
#TODO:?? collections and coll. states?
|
||||
for hda in history.datasets:
|
||||
if exclude_deleted and hda.deleted:
|
||||
continue
|
||||
if exclude_hidden and not hda.visible:
|
||||
continue
|
||||
state_counts[ hda.state ] = state_counts[ hda.state ] + 1
|
||||
return state_counts
|
||||
|
||||
#TODO: item_slug url
|
||||
if history_dict[ 'importable' ] and history_dict[ 'slug' ]:
|
||||
username_and_slug = ( '/' ).join(( 'u', history.user.username, 'h', history_dict[ 'slug' ] ))
|
||||
history_dict[ 'username_and_slug' ] = username_and_slug
|
||||
# remove this
|
||||
def get_state_ids( self, trans, history ):
|
||||
"""
|
||||
Return a dictionary keyed to possible dataset states and valued with lists
|
||||
containing the ids of each HDA in that state.
|
||||
"""
|
||||
state_ids = {}
|
||||
for state in model.Dataset.states.values():
|
||||
state_ids[ state ] = []
|
||||
|
||||
#TODO: re-add
|
||||
#hda_summaries = hda_dictionaries if hda_dictionaries else self.get_hda_summary_dicts( trans, history )
|
||||
##TODO remove the following in v2
|
||||
#( state_counts, state_ids ) = self._get_hda_state_summaries( trans, hda_summaries )
|
||||
#history_dict[ 'state_details' ] = state_counts
|
||||
#history_dict[ 'state_ids' ] = state_ids
|
||||
#history_dict[ 'state' ] = self._get_history_state_from_hdas( trans, history, state_counts )
|
||||
#TODO:?? collections and coll. states?
|
||||
for hda in history.datasets:
|
||||
#TODO: do not encode ids at this layer
|
||||
encoded_id = self.app.security.encode_id( hda.id )
|
||||
state_ids[ hda.state ].append( encoded_id )
|
||||
return state_ids
|
||||
|
||||
return history_dict
|
||||
#TODO: remove this (is state used/useful?)
|
||||
def get_history_state( self, trans, history ):
|
||||
"""
|
||||
Returns the history state based on the states of the HDAs it contains.
|
||||
"""
|
||||
states = model.Dataset.states
|
||||
|
||||
def most_recent( self, trans, user=None, deleted=False ):
|
||||
user = user or trans.user
|
||||
if not user:
|
||||
return None if trans.history.deleted else trans.history
|
||||
# (default to ERROR)
|
||||
state = states.ERROR
|
||||
|
||||
#TODO: dup with by_user - should return query from there and call first and not all
|
||||
history_model = trans.model.History
|
||||
query = ( trans.sa_session.query( history_model )
|
||||
.filter( history_model.user == user )
|
||||
.order_by( orm.desc( history_model.table.c.update_time ) ) )
|
||||
if not deleted:
|
||||
query = query.filter( history_model.deleted == False )
|
||||
return query.first()
|
||||
#TODO: history_state and state_counts are classically calc'd at the same time
|
||||
# so this is rel. ineff. - if we keep this...
|
||||
hda_state_counts = self.get_state_counts( trans, history, exclude_deleted=False )
|
||||
num_hdas = sum( hda_state_counts.values() )
|
||||
if num_hdas == 0:
|
||||
state = states.NEW
|
||||
|
||||
# =============================================================================
|
||||
class HistorySerializer( manager_base.ModelSerializer ):
|
||||
else:
|
||||
if( ( hda_state_counts[ states.RUNNING ] > 0 )
|
||||
or ( hda_state_counts[ states.SETTING_METADATA ] > 0 )
|
||||
or ( hda_state_counts[ states.UPLOAD ] > 0 ) ):
|
||||
state = states.RUNNING
|
||||
#TODO: this method may be more useful if we *also* polled the histories jobs here too
|
||||
|
||||
elif hda_state_counts[ states.QUEUED ] > 0:
|
||||
state = states.QUEUED
|
||||
|
||||
elif( ( hda_state_counts[ states.ERROR ] > 0 )
|
||||
or ( hda_state_counts[ states.FAILED_METADATA ] > 0 ) ):
|
||||
state = states.ERROR
|
||||
|
||||
elif hda_state_counts[ states.OK ] == num_hdas:
|
||||
state = states.OK
|
||||
|
||||
return state
|
||||
|
||||
|
||||
class HistorySerializer( sharable.SharableModelSerializer, deletable.PurgableSerializerMixin ):
|
||||
"""
|
||||
Interface/service object for serializing histories into dictionaries.
|
||||
"""
|
||||
pass
|
||||
SINGLE_CHAR_ABBR = 'h'
|
||||
|
||||
def __init__( self, app ):
|
||||
super( HistorySerializer, self ).__init__( app )
|
||||
|
||||
self.history_manager = HistoryManager( app )
|
||||
self.hda_manager = hdas.HDAManager( app )
|
||||
self.hda_serializer = hdas.HDASerializer( app )
|
||||
|
||||
self.default_view = 'summary'
|
||||
self.add_view( 'summary', [
|
||||
'id',
|
||||
'model_class',
|
||||
'name',
|
||||
'deleted',
|
||||
#'purged',
|
||||
#'count'
|
||||
'url',
|
||||
#TODO: why these?
|
||||
'published',
|
||||
'annotation',
|
||||
'tags',
|
||||
])
|
||||
self.add_view( 'detailed', [
|
||||
'contents_url',
|
||||
#'hdas',
|
||||
'empty',
|
||||
'size', 'nice_size',
|
||||
'user_id',
|
||||
'create_time', 'update_time',
|
||||
'importable', 'slug', 'username_and_slug',
|
||||
'genome_build',
|
||||
#TODO: remove the next three - instead getting the same info from the 'hdas' list
|
||||
'state',
|
||||
'state_details',
|
||||
'state_ids',
|
||||
# in the Historys' case, each of these views includes the keys from the previous
|
||||
], include_keys_from='summary' )
|
||||
|
||||
#assumes: outgoing to json.dumps and sanitized
|
||||
def add_serializers( self ):
|
||||
super( HistorySerializer, self ).add_serializers()
|
||||
deletable.PurgableSerializerMixin.add_serializers( self )
|
||||
|
||||
self.serializers.update({
|
||||
'model_class' : lambda *a: 'History',
|
||||
'id' : self.serialize_id,
|
||||
'create_time' : self.serialize_date,
|
||||
'update_time' : self.serialize_date,
|
||||
'size' : lambda t, i, k: int( i.get_disk_size() ),
|
||||
'nice_size' : lambda t, i, k: i.get_disk_size( nice_size=True ),
|
||||
'state' : lambda t, i, k: self.history_manager.get_history_state( t, i ),
|
||||
|
||||
'url' : lambda t, i, k: self.url_for( 'history', id=t.security.encode_id( i.id ) ),
|
||||
'contents_url' : lambda t, i, k:
|
||||
self.url_for( 'history_contents', history_id=t.security.encode_id( i.id ) ),
|
||||
|
||||
'empty' : lambda t, i, k: len( i.datasets ) <= 0,
|
||||
'count' : lambda trans, item, key: len( item.datasets ),
|
||||
'hdas' : lambda t, i, k: [ t.security.encode_id( hda.id ) for hda in i.datasets ],
|
||||
'state_details' : lambda t, i, k: self.history_manager.get_state_counts( t, i ),
|
||||
'state_ids' : lambda t, i, k: self.history_manager.get_state_ids( t, i ),
|
||||
'contents' : self.serialize_contents
|
||||
})
|
||||
|
||||
def serialize_contents( self, trans, history, key ):
|
||||
contents_dictionaries = []
|
||||
for content in history.contents_iter( types=[ 'dataset', 'dataset_collection' ] ):
|
||||
contents_dict = {}
|
||||
if isinstance( content, model.HistoryDatasetAssociation ):
|
||||
contents_dict = self.hda_serializer.serialize_to_view( trans, content, view='detailed' )
|
||||
elif isinstance( content, model.HistoryDatasetCollectionAssociation ):
|
||||
contents_dict = self.serialize_collection( trans, content )
|
||||
contents_dictionaries.append( contents_dict )
|
||||
return contents_dictionaries
|
||||
|
||||
def serialize_collection( self, trans, collection ):
|
||||
service = self.app.dataset_collections_service
|
||||
dataset_collection_instance = service.get_dataset_collection_instance(
|
||||
trans=trans,
|
||||
instance_type='history',
|
||||
id=self.security.encode_id( collection.id ),
|
||||
)
|
||||
return dictify_dataset_collection_instance( dataset_collection_instance,
|
||||
security=self.app.security, parent=dataset_collection_instance.history, view="element" )
|
||||
|
||||
|
||||
class HistoryDeserializer( sharable.SharableModelDeserializer, deletable.PurgableDeserializerMixin ):
|
||||
"""
|
||||
Interface/service object for validating and deserializing dictionaries into histories.
|
||||
"""
|
||||
model_manager_class = HistoryManager
|
||||
|
||||
def __init__( self, app ):
|
||||
super( HistoryDeserializer, self ).__init__( app )
|
||||
self.history_manager = self.manager
|
||||
|
||||
def add_deserializers( self ):
|
||||
super( HistoryDeserializer, self ).add_deserializers()
|
||||
deletable.PurgableDeserializerMixin.add_deserializers( self )
|
||||
|
||||
self.deserializers.update({
|
||||
'name' : self.deserialize_basestring,
|
||||
'genome_build' : self.deserialize_genome_build,
|
||||
})
|
||||
|
||||
|
||||
class HistoryFilters( sharable.SharableModelFilters, deletable.PurgableFiltersMixin ):
|
||||
model_class = model.History
|
||||
|
||||
def _add_parsers( self ):
|
||||
super( HistoryFilters, self )._add_parsers()
|
||||
deletable.PurgableFiltersMixin._add_parsers( self )
|
||||
|
||||
self.orm_filter_parsers.update({
|
||||
# history specific
|
||||
'name' : { 'op': ( 'eq', 'contains', 'like' ) },
|
||||
'genome_build' : { 'op': ( 'eq', 'contains', 'like' ) },
|
||||
})
|
||||
|
||||
#TODO: I'm not entirely convinced this (or tags) are a good idea for filters since they involve a/the user
|
||||
self.fn_filter_parsers.update({
|
||||
#TODO: add this in annotatable mixin
|
||||
'annotation' : { 'op': { 'has' : self.filter_annotation_contains, } },
|
||||
#TODO: add this in taggable mixin
|
||||
'tag' : {
|
||||
'op': {
|
||||
'eq' : self.filter_has_tag,
|
||||
'has': self.filter_has_partial_tag,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
from galaxy.managers import base as manager_base
|
||||
|
||||
|
||||
class LDDAManager( manager_base.ModelManager ):
|
||||
""" A fairly sparse manager for LDDAs.
|
||||
class LDDAManager( object ):
|
||||
"""
|
||||
A fairly sparse manager for LDDAs.
|
||||
"""
|
||||
|
||||
def __init__( self ):
|
||||
def __init__( self, app ):
|
||||
"""
|
||||
Set up and initialize other managers needed by lddas.
|
||||
"""
|
||||
pass
|
||||
|
||||
def get( self, trans, id, check_accessible=True ):
|
||||
return manager_base.get_object( trans, id, 'LibraryDatasetDatasetAssociation',
|
||||
check_ownership=False, check_accessible=check_accessible )
|
||||
return manager_base.get_object( trans, id,
|
||||
'LibraryDatasetDatasetAssociation',
|
||||
check_ownership=False,
|
||||
check_accessible=check_accessible )
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
Manager and Serializers for Pages.
|
||||
|
||||
Pages are markup created and saved by users that can contain Galaxy objects
|
||||
(such as datasets) and are often used to describe or present an analysis
|
||||
from within Galaxy.
|
||||
"""
|
||||
|
||||
from galaxy import model
|
||||
from galaxy.managers import sharable
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
class PageManager( sharable.SharableModelManager ):
|
||||
"""
|
||||
"""
|
||||
|
||||
model_class = model.Page
|
||||
foreign_key_name = 'page'
|
||||
user_share_model = model.PageUserShareAssociation
|
||||
|
||||
tag_assoc = model.PageTagAssociation
|
||||
annotation_assoc = model.PageAnnotationAssociation
|
||||
rating_assoc = model.PageRatingAssociation
|
||||
|
||||
def __init__( self, app, *args, **kwargs ):
|
||||
"""
|
||||
"""
|
||||
super( PageManager, self ).__init__( app, *args, **kwargs )
|
||||
|
||||
def copy( self, trans, page, user, **kwargs ):
|
||||
"""
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PageSerializer( sharable.SharableModelSerializer ):
|
||||
"""
|
||||
Interface/service object for serializing pages into dictionaries.
|
||||
"""
|
||||
SINGLE_CHAR_ABBR = 'p'
|
||||
|
||||
def __init__( self, app ):
|
||||
super( PageSerializer, self ).__init__( app )
|
||||
self.page_manager = PageManager( app )
|
||||
|
||||
self.default_view = 'summary'
|
||||
self.add_view( 'summary', [] )
|
||||
self.add_view( 'detailed', [] )
|
||||
|
||||
def add_serializers( self ):
|
||||
super( PageSerializer, self ).add_serializers()
|
||||
self.serializers.update({
|
||||
})
|
||||
|
||||
|
||||
class PageDeserializer( sharable.SharableModelDeserializer ):
|
||||
"""
|
||||
Interface/service object for validating and deserializing dictionaries
|
||||
into pages.
|
||||
"""
|
||||
model_manager_class = PageManager
|
||||
|
||||
def __init__( self, app ):
|
||||
super( PageDeserializer, self ).__init__( app )
|
||||
self.page_manager = self.manager
|
||||
|
||||
def add_deserializers( self ):
|
||||
super( PageDeserializer, self ).add_deserializers()
|
||||
self.deserializers.update({
|
||||
})
|
||||
self.deserializable_keyset.update( self.deserializers.keys() )
|
||||
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
Mixins for Ratable model managers and serializers.
|
||||
"""
|
||||
|
||||
from galaxy import exceptions
|
||||
from galaxy import model
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
#TODO: stub
|
||||
class RatableManagerMixin( object ):
|
||||
#: class of RatingAssociation (e.g. HistoryRatingAssociation)
|
||||
rating_assoc = None
|
||||
|
||||
#TODO: most of this seems to be covered by item_attrs.UsesItemRatings
|
||||
|
||||
#def by_user( self, trans, user, **kwargs ):
|
||||
# pass
|
||||
|
||||
|
||||
class RatableSerializerMixin( object ):
|
||||
|
||||
def add_serializers( self ):
|
||||
self.serializers[ 'user_rating' ] = self.serialize_user_rating
|
||||
self.serializers[ 'community_rating' ] = self.serialize_community_rating
|
||||
|
||||
def serialize_user_rating( self, trans, item, key ):
|
||||
"""
|
||||
"""
|
||||
pass
|
||||
|
||||
def serialize_community_rating( self, trans, item, key ):
|
||||
"""
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RatableDeserializerMixin( object ):
|
||||
|
||||
def add_deserializers( self ):
|
||||
pass
|
||||
#self.deserializers[ 'user_rating' ] = self.deserialize_rating
|
||||
|
||||
#def deserialize_rating( self, trans, item, key, val ):
|
||||
# val = self.validate.int_range( key, val, 0, 5 )
|
||||
# return self.set_rating...( trans, item, val, user=trans.user )
|
||||
@@ -0,0 +1,122 @@
|
||||
"""
|
||||
Accessible models can be read and copied but not modified or deleted.
|
||||
|
||||
Owned models can be modified and deleted.
|
||||
"""
|
||||
|
||||
from galaxy import exceptions
|
||||
from galaxy.managers import base
|
||||
|
||||
|
||||
class AccessibleManagerMixin( object ):
|
||||
"""
|
||||
A security interface to check if a User can read/view an item's.
|
||||
|
||||
This can also be thought of as 'read but not modify' privileges.
|
||||
"""
|
||||
|
||||
# don't want to override by_id since consumers will also want to fetch w/o any security checks
|
||||
def is_accessible( self, trans, item, user ):
|
||||
"""
|
||||
Return True if the item accessible to user.
|
||||
"""
|
||||
# override in subclasses
|
||||
raise exceptions.NotImplemented( "Abstract interface Method" )
|
||||
|
||||
def get_accessible( self, trans, id, user, **kwargs ):
|
||||
"""
|
||||
Return the item with the given id if it's accessible to user,
|
||||
otherwise raise an error.
|
||||
|
||||
:raises exceptions.ItemAccessibilityException:
|
||||
"""
|
||||
item = self.by_id( trans, id )
|
||||
return self.error_unless_accessible( trans, item, user )
|
||||
|
||||
def error_unless_accessible( self, trans, item, user ):
|
||||
"""
|
||||
Raise an error if the item is NOT accessible to user, otherwise return the item.
|
||||
|
||||
:raises exceptions.ItemAccessibilityException:
|
||||
"""
|
||||
if self.is_accessible( trans, item, user ):
|
||||
return item
|
||||
raise exceptions.ItemAccessibilityException( "%s is not accessible by user" % ( self.model_class.__name__ ) )
|
||||
|
||||
# TODO:?? are these even useful?
|
||||
def list_accessible( self, trans, user, **kwargs ):
|
||||
"""
|
||||
Return a list of items accessible to the user, raising an error if ANY
|
||||
are inaccessible.
|
||||
|
||||
:raises exceptions.ItemAccessibilityException:
|
||||
"""
|
||||
raise exceptions.NotImplemented( "Abstract interface Method" )
|
||||
# NOTE: this will be a large, inefficient list if filters are not passed in kwargs
|
||||
# items = ModelManager.list( self, trans, **kwargs )
|
||||
# return [ self.error_unless_accessible( trans, item, user ) for item in items ]
|
||||
|
||||
def filter_accessible( self, trans, user, **kwargs ):
|
||||
"""
|
||||
Return a list of items accessible to the user.
|
||||
"""
|
||||
raise exceptions.NotImplemented( "Abstract interface Method" )
|
||||
# NOTE: this will be a large, inefficient list if filters are not passed in kwargs
|
||||
# items = ModelManager.list( self, trans, **kwargs )
|
||||
# return filter( lambda item: self.is_accessible( trans, item, user ), items )
|
||||
|
||||
|
||||
class OwnableManagerMixin( object ):
|
||||
"""
|
||||
A security interface to check if a User is an item's owner.
|
||||
|
||||
Some resources are associated with the User that created or imported them
|
||||
and these Users can be considered the models' owner.
|
||||
|
||||
This can also be thought of as write/edit privileges.
|
||||
"""
|
||||
|
||||
def is_owner( self, trans, item, user ):
|
||||
"""
|
||||
Return True if user owns the item.
|
||||
"""
|
||||
# override in subclasses
|
||||
raise exceptions.NotImplemented( "Abstract interface Method" )
|
||||
|
||||
def get_owned( self, trans, id, user, **kwargs ):
|
||||
"""
|
||||
Return the item with the given id if owned by the user,
|
||||
otherwise raise an error.
|
||||
|
||||
:raises exceptions.ItemOwnershipException:
|
||||
"""
|
||||
item = self.by_id( trans, id )
|
||||
return self.error_unless_owner( trans, item, user )
|
||||
|
||||
def error_unless_owner( self, trans, item, user ):
|
||||
"""
|
||||
Raise an error if the item is NOT owned by user, otherwise return the item.
|
||||
|
||||
:raises exceptions.ItemAccessibilityException:
|
||||
"""
|
||||
if self.is_owner( trans, item, user ):
|
||||
return item
|
||||
raise exceptions.ItemOwnershipException( "%s is not owned by user" % ( self.model_class.__name__ ) )
|
||||
|
||||
def list_owned( self, trans, user, **kwargs ):
|
||||
"""
|
||||
Return a list of items owned by the user, raising an error if ANY
|
||||
are not.
|
||||
|
||||
:raises exceptions.ItemAccessibilityException:
|
||||
"""
|
||||
raise exceptions.NotImplemented( "Abstract interface Method" )
|
||||
# just alias to by_user (easier/same thing)
|
||||
#return self.by_user( trans, user, **kwargs )
|
||||
|
||||
def filter_owned( self, trans, user, **kwargs ):
|
||||
"""
|
||||
Return a list of items owned by the user.
|
||||
"""
|
||||
# just alias to list_owned
|
||||
return self.list_owned( trans, user, **kwargs )
|
||||
@@ -0,0 +1,404 @@
|
||||
"""
|
||||
Superclass Manager and Serializers for Sharable objects.
|
||||
|
||||
A sharable Galaxy object:
|
||||
has an owner/creator User
|
||||
is sharable with other, specific Users
|
||||
is importable (copyable) by users that have access
|
||||
has a slug which can be used as a link to view the resource
|
||||
can be published effectively making it available to all other Users
|
||||
can be rated
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from galaxy import exceptions
|
||||
|
||||
from galaxy.managers import base
|
||||
from galaxy.managers import secured
|
||||
from galaxy.managers import taggable
|
||||
from galaxy.managers import annotatable
|
||||
from galaxy.managers import ratable
|
||||
from galaxy.managers import users
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
class SharableModelManager( base.ModelManager, secured.OwnableManagerMixin, secured.AccessibleManagerMixin,
|
||||
taggable.TaggableManagerMixin, annotatable.AnnotatableManagerMixin, ratable.RatableManagerMixin ):
|
||||
# e.g. histories, pages, stored workflows, visualizations
|
||||
# base.DeleteableModelMixin? (all four are deletable)
|
||||
|
||||
#: the model used for UserShareAssociations with this model
|
||||
user_share_model = None
|
||||
|
||||
#: the single character abbreviation used in username_and_slug: e.g. 'h' for histories: u/user/h/slug
|
||||
SINGLE_CHAR_ABBR = None
|
||||
|
||||
def __init__( self, app ):
|
||||
super( SharableModelManager, self ).__init__( app )
|
||||
# user manager is needed to check access/ownership/admin
|
||||
self.user_manager = users.UserManager( app )
|
||||
|
||||
# .... has a user
|
||||
def by_user( self, trans, user, filters=None, **kwargs ):
|
||||
"""
|
||||
Return list for all items (of model_class type) associated with the given
|
||||
`user`.
|
||||
"""
|
||||
user_filter = self.model_class.user_id == user.id
|
||||
filters=self._munge_filters( user_filter, filters )
|
||||
return self.list( trans, filters=filters, **kwargs )
|
||||
|
||||
# .... owned model interface
|
||||
def is_owner( self, trans, item, user ):
|
||||
"""
|
||||
Return true if this sharable belongs to `user` (or `user` is an admin).
|
||||
"""
|
||||
if self.user_manager.is_anonymous( user ):
|
||||
# note: this needs to be overridden in the case of anon users and session.current_history
|
||||
return False
|
||||
# ... effectively a good fit to have this here, but not semantically
|
||||
if self.user_manager.is_admin( trans, user ):
|
||||
return True
|
||||
return item.user == user
|
||||
|
||||
# .... accessible interface
|
||||
def is_accessible( self, trans, item, user ):
|
||||
"""
|
||||
If the item is importable, is owned by `user`, or (the valid) `user`
|
||||
is in 'users shared with' list for the item: return True.
|
||||
"""
|
||||
if item.importable:
|
||||
return True
|
||||
# note: owners always have access - checking for accessible implicitly checks for ownership
|
||||
if self.is_owner( trans, item, user ):
|
||||
return True
|
||||
if self.user_manager.is_anonymous( user ):
|
||||
return False
|
||||
if user in item.users_shared_with_dot_users:
|
||||
return True
|
||||
return False
|
||||
|
||||
# .... importable
|
||||
def make_importable( self, trans, item, flush=True ):
|
||||
"""
|
||||
Makes item accessible--viewable and importable--and sets item's slug.
|
||||
Does not flush/commit changes, however. Item must have name, user,
|
||||
importable, and slug attributes.
|
||||
"""
|
||||
self.create_unique_slug( trans, item, flush=False )
|
||||
return self._session_setattr( item, 'importable', True, flush=flush )
|
||||
|
||||
def make_non_importable( self, trans, item, flush=True ):
|
||||
"""
|
||||
Makes item accessible--viewable and importable--and sets item's slug.
|
||||
Does not flush/commit changes, however. Item must have name, user,
|
||||
importable, and slug attributes.
|
||||
"""
|
||||
# item must be unpublished if non-importable
|
||||
if item.published:
|
||||
self.unpublish( trans, item, flush=False )
|
||||
return self._session_setattr( item, 'importable', False, flush=flush )
|
||||
|
||||
# .... published
|
||||
def publish( self, trans, item, flush=True ):
|
||||
"""
|
||||
Set both the importable and published flags on `item` to True.
|
||||
"""
|
||||
# item must be importable to be published
|
||||
if not item.importable:
|
||||
self.make_importable( trans, item, flush=False )
|
||||
return self._session_setattr( item, 'published', True, flush=flush )
|
||||
|
||||
def unpublish( self, trans, item, flush=True ):
|
||||
"""
|
||||
Set the published flag on `item` to False.
|
||||
"""
|
||||
return self._session_setattr( item, 'published', False, flush=flush )
|
||||
|
||||
#def _query_published( self, trans, filters=None, **kwargs ):
|
||||
# """
|
||||
# """
|
||||
# published_filter = self.model_class.published == True
|
||||
# filters = self._munge_filters( published_filter, filters )
|
||||
# return self.list( trans, filters=filters, **kwargs )
|
||||
#
|
||||
#def list_published( self, trans, **kwargs ):
|
||||
# """
|
||||
# """
|
||||
# query = self._query_published( trans, user, **kwargs )
|
||||
# return self.list( trans, query=query, **kwargs )
|
||||
|
||||
# .... user sharing
|
||||
# sharing is often done via a 3rd table btwn a User and an item -> a <Item>UserShareAssociation
|
||||
def get_share_assocs( self, trans, item, user=None ):
|
||||
"""
|
||||
Get the UserShareAssociations for the `item`.
|
||||
|
||||
Optionally send in `user` to test for a single match.
|
||||
"""
|
||||
query = self.query_associated( trans, self.user_share_model, item )
|
||||
if user is not None:
|
||||
query = query.filter_by( user=user )
|
||||
return query.all()
|
||||
|
||||
def share_with( self, trans, item, user, flush=True ):
|
||||
"""
|
||||
Get or create a share for the given user (or users if `user` is a list).
|
||||
"""
|
||||
# allow user to be a list and call recursivly
|
||||
if isinstance( user, list ):
|
||||
return map( lambda user: self.share_with( trans, item, user, flush=False ), user )
|
||||
# get or create
|
||||
existing = self.get_share_assocs( trans, item, user=user )
|
||||
if existing:
|
||||
return existing.pop( 0 )
|
||||
return self._create_user_share_assoc( trans, item, user, flush=flush )
|
||||
|
||||
def _create_user_share_assoc( self, trans, item, user, flush=True ):
|
||||
"""
|
||||
Create a share for the given user.
|
||||
"""
|
||||
user_share_assoc = self.user_share_model()
|
||||
self.app.model.context.add( user_share_assoc )
|
||||
self.associate( trans, user_share_assoc, item )
|
||||
user_share_assoc.user = user
|
||||
|
||||
# ensure an item slug so shared users can access
|
||||
if not item.slug:
|
||||
self.create_unique_slug( trans, item )
|
||||
|
||||
if flush:
|
||||
self.app.model.context.flush()
|
||||
return user_share_assoc
|
||||
|
||||
def unshare_with( self, trans, item, user, flush=True ):
|
||||
"""
|
||||
Delete a user share (or list of shares) from the database.
|
||||
"""
|
||||
if isinstance( user, list ):
|
||||
return map( lambda user: self.unshare_with( trans, item, user, flush=False ), user )
|
||||
# Look for and delete sharing relation for user.
|
||||
user_share_assoc = self.get_share_assocs( trans, item, user=user )[0]
|
||||
self.app.model.context.delete( user_share_assoc )
|
||||
if flush:
|
||||
self.app.model.context.flush()
|
||||
return user_share_assoc
|
||||
|
||||
#def _query_shared_with( self, trans, user, filters=None, **kwargs ):
|
||||
##TODO:
|
||||
# """
|
||||
# """
|
||||
# pass
|
||||
|
||||
#def list_shared_with( self, trans, user, **kwargs ):
|
||||
# """
|
||||
# """
|
||||
# query = self._query_shared_with( trans, user, **kwargs )
|
||||
# return self.list( trans, query=query, **kwargs )
|
||||
|
||||
# .... slugs
|
||||
# slugs are human readable strings often used to link to sharable resources (replacing ids)
|
||||
#TODO: as validator, deserializer, etc. (maybe another object entirely?)
|
||||
def set_slug( self, trans, item, new_slug, flush=True ):
|
||||
"""
|
||||
Validate and set the new slug for `item`.
|
||||
"""
|
||||
# precondition: has been validated
|
||||
if not self.is_valid_slug( new_slug ):
|
||||
raise exceptions.RequestParameterInvalidException( "Invalid slug", slug=new_slug )
|
||||
|
||||
# error if slug is already in use
|
||||
if self._slug_exists( trans, trans.user, new_slug ):
|
||||
raise exceptions.Conflict( "Slug already exists", slug=new_slug )
|
||||
|
||||
item.slug = new_slug
|
||||
if flush:
|
||||
self.app.model.context.flush()
|
||||
return item
|
||||
|
||||
def is_valid_slug( self, slug ):
|
||||
"""
|
||||
Returns true if `slug` is valid.
|
||||
"""
|
||||
VALID_SLUG_RE = re.compile( "^[a-z0-9\-]+$" )
|
||||
return VALID_SLUG_RE.match( slug )
|
||||
|
||||
def _existing_set_of_slugs( self, trans, user ):
|
||||
query = ( self.app.model.context.query( self.model_class.slug )
|
||||
.filter_by( user=user ) )
|
||||
return set( query.all() )
|
||||
|
||||
def _slug_exists( self, trans, user, slug ):
|
||||
query = ( self.app.model.context.query( self.model_class.slug )
|
||||
.filter_by( user=user, slug=slug ) )
|
||||
return query.count() != 0
|
||||
|
||||
def _slugify( self, start_with ):
|
||||
# Replace whitespace with '-'
|
||||
slug_base = re.sub( "\s+", "-", start_with )
|
||||
# Remove all non-alphanumeric characters.
|
||||
slug_base = re.sub( "[^a-zA-Z0-9\-]", "", slug_base )
|
||||
# Remove trailing '-'.
|
||||
if slug_base.endswith('-'):
|
||||
slug_base = slug_base[:-1]
|
||||
return slug_base
|
||||
|
||||
def _default_slug_base( self, trans, item ):
|
||||
# override in subclasses
|
||||
if hasattr( item, 'title' ):
|
||||
return item.title.lower()
|
||||
return item.name.lower()
|
||||
|
||||
def get_unique_slug( self, trans, item ):
|
||||
"""
|
||||
Returns a slug that is unique among user's importable items
|
||||
for item's class.
|
||||
"""
|
||||
cur_slug = item.slug
|
||||
|
||||
# Setup slug base.
|
||||
if cur_slug is None or cur_slug == "":
|
||||
slug_base = self._slugify( self._default_slug_base( trans, item ) )
|
||||
else:
|
||||
slug_base = cur_slug
|
||||
|
||||
# Using slug base, find a slug that is not taken. If slug is taken,
|
||||
# add integer to end.
|
||||
new_slug = slug_base
|
||||
count = 1
|
||||
while ( self.app.model.context.query( item.__class__ )
|
||||
.filter_by( user=item.user, slug=new_slug, importable=True )
|
||||
.count() != 0 ):
|
||||
# Slug taken; choose a new slug based on count. This approach can
|
||||
# handle numerous items with the same name gracefully.
|
||||
new_slug = '%s-%i' % ( slug_base, count )
|
||||
count += 1
|
||||
|
||||
return new_slug
|
||||
|
||||
def create_unique_slug( self, trans, item, flush=True ):
|
||||
"""
|
||||
Set a new, unique slug on the item.
|
||||
"""
|
||||
item.slug = self.get_unique_slug( trans, item )
|
||||
self.app.model.context.add( item )
|
||||
if flush:
|
||||
self.app.model.context.flush()
|
||||
return item
|
||||
|
||||
#def by_slug( self, trans, user, **kwargs ):
|
||||
# """
|
||||
# """
|
||||
# pass
|
||||
|
||||
# .... display
|
||||
#def display_by_username_and_slug( self, trans, username, slug ):
|
||||
# """ Display item by username and slug. """
|
||||
|
||||
#def set_public_username( self, trans, id, username, **kwargs ):
|
||||
# """ Set user's public username and delegate to sharing() """
|
||||
#def sharing( self, trans, id, **kwargs ):
|
||||
# """ Handle item sharing. """
|
||||
|
||||
|
||||
class SharableModelSerializer( base.ModelSerializer,
|
||||
taggable.TaggableSerializerMixin, annotatable.AnnotatableSerializerMixin, ratable.RatableSerializerMixin ):
|
||||
#TODO: stub
|
||||
SINGLE_CHAR_ABBR = None
|
||||
|
||||
def add_serializers( self ):
|
||||
super( SharableModelSerializer, self ).add_serializers()
|
||||
taggable.TaggableSerializerMixin.add_serializers( self )
|
||||
annotatable.AnnotatableSerializerMixin.add_serializers( self )
|
||||
ratable.RatableSerializerMixin.add_serializers( self )
|
||||
|
||||
self.serializers.update({
|
||||
'user_id' : self.serialize_id,
|
||||
'username_and_slug' : self.serialize_username_and_slug
|
||||
})
|
||||
self.serializable_keyset.update([
|
||||
'importable', 'published', 'slug'
|
||||
])
|
||||
|
||||
def serialize_username_and_slug( self, trans, item, key ):
|
||||
if not ( item.user and item.slug and self.SINGLE_CHAR_ABBR ):
|
||||
return None
|
||||
#TODO: self.url_for
|
||||
return ( '/' ).join(( 'u', item.user.username, self.SINGLE_CHAR_ABBR, item.slug ) )
|
||||
|
||||
#def published_url( self, trans, item, key ):
|
||||
# """
|
||||
# """
|
||||
# url = url_for(controller='history', action="display_by_username_and_slug",
|
||||
# username=item.user.username, slug=item.slug )
|
||||
# return url
|
||||
|
||||
# the only ones that needs any fns:
|
||||
# user/user_id
|
||||
# user_shares?
|
||||
# username_and_slug?
|
||||
|
||||
|
||||
class SharableModelDeserializer( base.ModelDeserializer,
|
||||
taggable.TaggableDeserializerMixin, annotatable.AnnotatableDeserializerMixin, ratable.RatableDeserializerMixin ):
|
||||
|
||||
def add_deserializers( self ):
|
||||
super( SharableModelDeserializer, self ).add_deserializers()
|
||||
taggable.TaggableDeserializerMixin.add_deserializers( self )
|
||||
annotatable.AnnotatableDeserializerMixin.add_deserializers( self )
|
||||
ratable.RatableDeserializerMixin.add_deserializers( self )
|
||||
|
||||
self.deserializers.update({
|
||||
'published' : self.deserialize_published,
|
||||
'importable' : self.deserialize_importable,
|
||||
})
|
||||
|
||||
def deserialize_published( self, trans, item, key, val ):
|
||||
"""
|
||||
"""
|
||||
val = self.validate.bool( key, val )
|
||||
if item.published == val:
|
||||
return val
|
||||
|
||||
if val:
|
||||
self.manager.publish( trans, item, flush=False )
|
||||
else:
|
||||
self.manager.unpublish( trans, item, flush=False )
|
||||
return item.published
|
||||
|
||||
def deserialize_importable( self, trans, item, key, val ):
|
||||
"""
|
||||
"""
|
||||
val = self.validate.bool( key, val )
|
||||
if item.importable == val:
|
||||
return val
|
||||
|
||||
if val:
|
||||
self.manager.make_importable( trans, item, flush=False )
|
||||
else:
|
||||
self.manager.make_non_importable( trans, item, flush=False )
|
||||
return item.published
|
||||
|
||||
#def deserialize_slug( self, trans, item, val ):
|
||||
# """
|
||||
# """
|
||||
# #TODO: call manager.set_slug
|
||||
# pass
|
||||
|
||||
#def deserialize_user_shares():
|
||||
|
||||
|
||||
class SharableModelFilters( base.ModelFilterParser ):
|
||||
|
||||
def _add_parsers( self ):
|
||||
super( SharableModelFilters, self )._add_parsers()
|
||||
self.orm_filter_parsers.update({
|
||||
'importable' : { 'op': ( 'eq' ), 'val': self.parse_bool },
|
||||
'published' : { 'op': ( 'eq' ), 'val': self.parse_bool },
|
||||
'slug' : { 'op': ( 'eq', 'contains', 'like' ) },
|
||||
# chose by user should prob. only be available for admin? (most often we'll only need trans.user)
|
||||
#'user' : { 'op': ( 'eq' ), 'val': self.parse_id_list },
|
||||
})
|
||||
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Mixins for Taggable model managers and serializers.
|
||||
"""
|
||||
|
||||
from galaxy import exceptions
|
||||
from galaxy import model
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
class TaggableManagerMixin( object ):
|
||||
#: class of TagAssociation (e.g. HistoryTagAssociation)
|
||||
tag_assoc = None
|
||||
|
||||
#TODO: most of this can be done by delegating to the TagManager?
|
||||
|
||||
#def by_user( self, trans, user, **kwargs ):
|
||||
# pass
|
||||
|
||||
|
||||
class TaggableSerializerMixin( object ):
|
||||
|
||||
def add_serializers( self ):
|
||||
self.serializers[ 'tags' ] = self.serialize_tags
|
||||
|
||||
def serialize_tags( self, trans, item, key ):
|
||||
"""
|
||||
Return tags as a list of strings.
|
||||
"""
|
||||
if not hasattr( item, 'tags' ):
|
||||
return None
|
||||
|
||||
tags_str_list = []
|
||||
for tag in item.tags:
|
||||
tag_str = tag.user_tname
|
||||
if tag.value is not None:
|
||||
tag_str += ":" + tag.user_value
|
||||
tags_str_list.append( tag_str )
|
||||
return tags_str_list
|
||||
|
||||
|
||||
class TaggableDeserializerMixin( object ):
|
||||
|
||||
def add_deserializers( self ):
|
||||
self.deserializers[ 'tags' ] = self.deserialize_tags
|
||||
|
||||
def deserialize_tags( self, trans, item, key, val ):
|
||||
"""
|
||||
Make sure `val` is a valid list of tag strings and assign them.
|
||||
|
||||
Note: this will erase any previous tags.
|
||||
"""
|
||||
new_tags_list = self.validate.basestring_list( key, val )
|
||||
#TODO: have to assume trans.user here...
|
||||
#TODO: trans
|
||||
user = trans.user
|
||||
#TODO: duped from tags manager - de-dupe when moved to taggable mixin
|
||||
tag_handler = self.app.tag_handler
|
||||
tag_handler.delete_item_tags( user, item )
|
||||
new_tags_str = ','.join( new_tags_list )
|
||||
tag_handler.apply_item_tags( user, item, unicode( new_tags_str.encode( 'utf-8' ), 'utf-8' ) )
|
||||
|
||||
#TODO:!! does the creation of new_tags_list mean there are now more and more unused tag rows in the db?
|
||||
return item.tags
|
||||
|
||||
+307
-10
@@ -1,20 +1,317 @@
|
||||
import logging
|
||||
import re
|
||||
|
||||
from sqlalchemy.sql import select
|
||||
from sqlalchemy.sql.expression import func
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
class TagsManager( object ):
|
||||
""" Manages CRUD operations related to tagging objects.
|
||||
# Item-specific information needed to perform tagging.
|
||||
class ItemTagAssocInfo( object ):
|
||||
def __init__( self, item_class, tag_assoc_class, item_id_col ):
|
||||
self.item_class = item_class
|
||||
self.tag_assoc_class = tag_assoc_class
|
||||
self.item_id_col = item_id_col
|
||||
|
||||
|
||||
class TagManager( object ):
|
||||
"""
|
||||
Manages CRUD operations related to tagging objects.
|
||||
"""
|
||||
|
||||
def __init__( self, app ):
|
||||
self.app = app
|
||||
self.tag_handler = app.tag_handler
|
||||
# Minimum tag length.
|
||||
self.min_tag_len = 2
|
||||
# Maximum tag length.
|
||||
self.max_tag_len = 255
|
||||
# Tag separator.
|
||||
self.tag_separators = ',;'
|
||||
# Hierarchy separator.
|
||||
self.hierarchy_separator = '.'
|
||||
# Key-value separator.
|
||||
self.key_value_separators = "=:"
|
||||
# Initialize with known classes - add to this in subclasses.
|
||||
self.item_tag_assoc_info = {}
|
||||
|
||||
def set_tags_from_list( self, trans, item, new_tags_list, user=None ):
|
||||
#precondition: item is already security checked against user
|
||||
#precondition: incoming tags is a list of sanitized/formatted strings
|
||||
user = user or trans.user
|
||||
def set_tags_from_list( self, user, item, new_tags_list ):
|
||||
# precondition: item is already security checked against user
|
||||
# precondition: incoming tags is a list of sanitized/formatted strings
|
||||
|
||||
self.tag_handler.delete_item_tags( trans, user, item )
|
||||
self.delete_item_tags( user, item )
|
||||
new_tags_str = ','.join( new_tags_list )
|
||||
self.tag_handler.apply_item_tags( trans, user, item, unicode( new_tags_str.encode( 'utf-8' ), 'utf-8' ) )
|
||||
trans.sa_session.flush()
|
||||
self.apply_item_tags( user, item, unicode( new_tags_str.encode( 'utf-8' ), 'utf-8' ) )
|
||||
self.app.model.context.flush()
|
||||
return item.tags
|
||||
|
||||
def get_tag_assoc_class( self, item_class ):
|
||||
"""Returns tag association class for item class."""
|
||||
return self.item_tag_assoc_info[item_class.__name__].tag_assoc_class
|
||||
|
||||
def get_id_col_in_item_tag_assoc_table( self, item_class ):
|
||||
"""Returns item id column in class' item-tag association table."""
|
||||
return self.item_tag_assoc_info[item_class.__name__].item_id_col
|
||||
|
||||
def get_community_tags( self, item=None, limit=None ):
|
||||
"""Returns community tags for an item."""
|
||||
# Get item-tag association class.
|
||||
item_class = item.__class__
|
||||
item_tag_assoc_class = self.get_tag_assoc_class( item_class )
|
||||
if not item_tag_assoc_class:
|
||||
return []
|
||||
# Build select statement.
|
||||
cols_to_select = [ item_tag_assoc_class.table.c.tag_id, func.count( '*' ) ]
|
||||
from_obj = item_tag_assoc_class.table.join( item_class.table ).join( self.app.model.Tag.table )
|
||||
where_clause = ( self.get_id_col_in_item_tag_assoc_table( item_class ) == item.id )
|
||||
order_by = [ func.count( "*" ).desc() ]
|
||||
group_by = item_tag_assoc_class.table.c.tag_id
|
||||
# Do query and get result set.
|
||||
query = select( columns=cols_to_select,
|
||||
from_obj=from_obj,
|
||||
whereclause=where_clause,
|
||||
group_by=group_by,
|
||||
order_by=order_by,
|
||||
limit=limit )
|
||||
result_set = self.app.model.context.execute( query )
|
||||
# Return community tags.
|
||||
community_tags = []
|
||||
for row in result_set:
|
||||
tag_id = row[0]
|
||||
community_tags.append( self.get_tag_by_id( tag_id ) )
|
||||
return community_tags
|
||||
|
||||
def get_tool_tags( self ):
|
||||
result_set = self.app.model.context.execute( select( columns=[ self.app.model.ToolTagAssociation.table.c.tag_id ],
|
||||
from_obj=self.app.model.ToolTagAssociation.table ).distinct() )
|
||||
|
||||
tags = []
|
||||
for row in result_set:
|
||||
tag_id = row[0]
|
||||
tags.append( self.get_tag_by_id( tag_id ) )
|
||||
return tags
|
||||
|
||||
def remove_item_tag( self, user, item, tag_name ):
|
||||
"""Remove a tag from an item."""
|
||||
# Get item tag association.
|
||||
item_tag_assoc = self._get_item_tag_assoc( user, item, tag_name )
|
||||
# Remove association.
|
||||
if item_tag_assoc:
|
||||
# Delete association.
|
||||
self.app.model.context.delete( item_tag_assoc )
|
||||
item.tags.remove( item_tag_assoc )
|
||||
return True
|
||||
return False
|
||||
|
||||
def delete_item_tags( self, user, item ):
|
||||
"""Delete tags from an item."""
|
||||
# Delete item-tag associations.
|
||||
for tag in item.tags:
|
||||
self.app.model.context.delete( tag )
|
||||
# Delete tags from item.
|
||||
del item.tags[:]
|
||||
|
||||
def item_has_tag( self, user, item, tag ):
|
||||
"""Returns true if item is has a given tag."""
|
||||
# Get tag name.
|
||||
if isinstance( tag, basestring ):
|
||||
tag_name = tag
|
||||
elif isinstance( tag, self.app.model.Tag ):
|
||||
tag_name = tag.name
|
||||
# Check for an item-tag association to see if item has a given tag.
|
||||
item_tag_assoc = self._get_item_tag_assoc( user, item, tag_name )
|
||||
if item_tag_assoc:
|
||||
return True
|
||||
return False
|
||||
|
||||
def apply_item_tag( self, user, item, name, value=None ):
|
||||
# Use lowercase name for searching/creating tag.
|
||||
lc_name = name.lower()
|
||||
# Get or create item-tag association.
|
||||
item_tag_assoc = self._get_item_tag_assoc( user, item, lc_name )
|
||||
if not item_tag_assoc:
|
||||
# Create item-tag association.
|
||||
# Create tag; if None, skip the tag (and log error).
|
||||
tag = self._get_or_create_tag( lc_name )
|
||||
if not tag:
|
||||
log.warn( "Failed to create tag with name %s" % lc_name )
|
||||
return
|
||||
# Create tag association based on item class.
|
||||
item_tag_assoc_class = self.get_tag_assoc_class( item.__class__ )
|
||||
item_tag_assoc = item_tag_assoc_class()
|
||||
# Add tag to association.
|
||||
item.tags.append( item_tag_assoc )
|
||||
item_tag_assoc.tag = tag
|
||||
item_tag_assoc.user = user
|
||||
# Apply attributes to item-tag association. Strip whitespace from user name and tag.
|
||||
lc_value = None
|
||||
if value:
|
||||
lc_value = value.lower()
|
||||
item_tag_assoc.user_tname = name
|
||||
item_tag_assoc.user_value = value
|
||||
item_tag_assoc.value = lc_value
|
||||
return item_tag_assoc
|
||||
|
||||
def apply_item_tags( self, user, item, tags_str ):
|
||||
"""Apply tags to an item."""
|
||||
# Parse tags.
|
||||
parsed_tags = self.parse_tags( tags_str )
|
||||
# Apply each tag.
|
||||
for name, value in parsed_tags.items():
|
||||
self.apply_item_tag( user, item, name, value )
|
||||
|
||||
def get_tags_str( self, tags ):
|
||||
"""Build a string from an item's tags."""
|
||||
# Return empty string if there are no tags.
|
||||
if not tags:
|
||||
return ""
|
||||
# Create string of tags.
|
||||
tags_str_list = list()
|
||||
for tag in tags:
|
||||
tag_str = tag.user_tname
|
||||
if tag.value is not None:
|
||||
tag_str += ":" + tag.user_value
|
||||
tags_str_list.append( tag_str )
|
||||
return ", ".join( tags_str_list )
|
||||
|
||||
def get_tag_by_id( self, tag_id ):
|
||||
"""Get a Tag object from a tag id."""
|
||||
return self.app.model.context.query( self.app.model.Tag ).filter_by( id=tag_id ).first()
|
||||
|
||||
def get_tag_by_name( self, tag_name ):
|
||||
"""Get a Tag object from a tag name (string)."""
|
||||
if tag_name:
|
||||
return self.app.model.context.query( self.app.model.Tag ).filter_by( name=tag_name.lower() ).first()
|
||||
return None
|
||||
|
||||
def _create_tag( self, tag_str ):
|
||||
"""Create a Tag object from a tag string."""
|
||||
tag_hierarchy = tag_str.split( self.hierarchy_separator )
|
||||
tag_prefix = ""
|
||||
parent_tag = None
|
||||
for sub_tag in tag_hierarchy:
|
||||
# Get or create subtag.
|
||||
tag_name = tag_prefix + self._scrub_tag_name( sub_tag )
|
||||
tag = self.app.model.context.query( self.app.model.Tag ).filter_by( name=tag_name).first()
|
||||
if not tag:
|
||||
tag = self.app.model.Tag( type=0, name=tag_name )
|
||||
# Set tag parent.
|
||||
tag.parent = parent_tag
|
||||
# Update parent and tag prefix.
|
||||
parent_tag = tag
|
||||
tag_prefix = tag.name + self.hierarchy_separator
|
||||
return tag
|
||||
|
||||
def _get_or_create_tag( self, tag_str ):
|
||||
"""Get or create a Tag object from a tag string."""
|
||||
# Scrub tag; if tag is None after being scrubbed, return None.
|
||||
scrubbed_tag_str = self._scrub_tag_name( tag_str )
|
||||
if not scrubbed_tag_str:
|
||||
return None
|
||||
# Get item tag.
|
||||
tag = self.get_tag_by_name( scrubbed_tag_str )
|
||||
# Create tag if necessary.
|
||||
if tag is None:
|
||||
tag = self._create_tag( scrubbed_tag_str )
|
||||
return tag
|
||||
|
||||
def _get_item_tag_assoc( self, user, item, tag_name ):
|
||||
"""
|
||||
Return ItemTagAssociation object for a user, item, and tag string; returns None if there is
|
||||
no such association.
|
||||
"""
|
||||
scrubbed_tag_name = self._scrub_tag_name( tag_name )
|
||||
for item_tag_assoc in item.tags:
|
||||
if ( item_tag_assoc.user == user ) and ( item_tag_assoc.user_tname == scrubbed_tag_name ):
|
||||
return item_tag_assoc
|
||||
return None
|
||||
|
||||
def parse_tags( self, tag_str ):
|
||||
"""
|
||||
Returns a list of raw (tag-name, value) pairs derived from a string; method scrubs tag names and values as well.
|
||||
Return value is a dictionary where tag-names are keys.
|
||||
"""
|
||||
# Gracefully handle None.
|
||||
if not tag_str:
|
||||
return dict()
|
||||
# Split tags based on separators.
|
||||
reg_exp = re.compile( '[' + self.tag_separators + ']' )
|
||||
raw_tags = reg_exp.split( tag_str )
|
||||
# Extract name-value pairs.
|
||||
name_value_pairs = dict()
|
||||
for raw_tag in raw_tags:
|
||||
nv_pair = self._get_name_value_pair( raw_tag )
|
||||
scrubbed_name = self._scrub_tag_name( nv_pair[0] )
|
||||
scrubbed_value = self._scrub_tag_value( nv_pair[1] )
|
||||
name_value_pairs[scrubbed_name] = scrubbed_value
|
||||
return name_value_pairs
|
||||
|
||||
def _scrub_tag_value( self, value ):
|
||||
"""Scrub a tag value."""
|
||||
# Gracefully handle None:
|
||||
if not value:
|
||||
return None
|
||||
# Remove whitespace from value.
|
||||
reg_exp = re.compile( '\s' )
|
||||
scrubbed_value = re.sub( reg_exp, "", value )
|
||||
return scrubbed_value
|
||||
|
||||
def _scrub_tag_name( self, name ):
|
||||
"""Scrub a tag name."""
|
||||
# Gracefully handle None:
|
||||
if not name:
|
||||
return None
|
||||
# Remove whitespace from name.
|
||||
reg_exp = re.compile( '\s' )
|
||||
scrubbed_name = re.sub( reg_exp, "", name )
|
||||
# Ignore starting ':' char.
|
||||
if scrubbed_name.startswith( self.hierarchy_separator ):
|
||||
scrubbed_name = scrubbed_name[1:]
|
||||
# If name is too short or too long, return None.
|
||||
if len( scrubbed_name ) < self.min_tag_len or len( scrubbed_name ) > self.max_tag_len:
|
||||
return None
|
||||
return scrubbed_name
|
||||
|
||||
def _scrub_tag_name_list( self, tag_name_list ):
|
||||
"""Scrub a tag name list."""
|
||||
scrubbed_tag_list = list()
|
||||
for tag in tag_name_list:
|
||||
scrubbed_tag_list.append( self._scrub_tag_name( tag ) )
|
||||
return scrubbed_tag_list
|
||||
|
||||
def _get_name_value_pair( self, tag_str ):
|
||||
"""Get name, value pair from a tag string."""
|
||||
# Use regular expression to parse name, value.
|
||||
reg_exp = re.compile( "[" + self.key_value_separators + "]" )
|
||||
name_value_pair = reg_exp.split( tag_str )
|
||||
# Add empty slot if tag does not have value.
|
||||
if len( name_value_pair ) < 2:
|
||||
name_value_pair.append( None )
|
||||
return name_value_pair
|
||||
|
||||
|
||||
class GalaxyTagManager( TagManager ):
|
||||
def __init__( self, app ):
|
||||
from galaxy import model
|
||||
TagManager.__init__( self, app )
|
||||
self.item_tag_assoc_info["History"] = ItemTagAssocInfo( model.History,
|
||||
model.HistoryTagAssociation,
|
||||
model.HistoryTagAssociation.table.c.history_id )
|
||||
self.item_tag_assoc_info["HistoryDatasetAssociation"] = \
|
||||
ItemTagAssocInfo( model.HistoryDatasetAssociation,
|
||||
model.HistoryDatasetAssociationTagAssociation,
|
||||
model.HistoryDatasetAssociationTagAssociation.table.c.history_dataset_association_id )
|
||||
self.item_tag_assoc_info["Page"] = ItemTagAssocInfo( model.Page,
|
||||
model.PageTagAssociation,
|
||||
model.PageTagAssociation.table.c.page_id )
|
||||
self.item_tag_assoc_info["StoredWorkflow"] = ItemTagAssocInfo( model.StoredWorkflow,
|
||||
model.StoredWorkflowTagAssociation,
|
||||
model.StoredWorkflowTagAssociation.table.c.stored_workflow_id )
|
||||
self.item_tag_assoc_info["Visualization"] = ItemTagAssocInfo( model.Visualization,
|
||||
model.VisualizationTagAssociation,
|
||||
model.VisualizationTagAssociation.table.c.visualization_id )
|
||||
|
||||
|
||||
class CommunityTagManager( TagManager):
|
||||
def __init__( self, app ):
|
||||
TagManager.__init__( self, app )
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
"""
|
||||
Manager and Serializer for Users.
|
||||
"""
|
||||
|
||||
import pkg_resources
|
||||
pkg_resources.require( "SQLAlchemy >= 0.4" )
|
||||
import sqlalchemy
|
||||
|
||||
from galaxy import model
|
||||
from galaxy import exceptions
|
||||
|
||||
from galaxy.managers import base
|
||||
from galaxy.managers import api_keys
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
class UserManager( base.ModelManager ):
|
||||
model_class = model.User
|
||||
foreign_key_name = 'user'
|
||||
|
||||
#TODO: there is quite a bit of functionality around the user (authentication, permissions, quotas, groups/roles)
|
||||
# most of which it may be unneccessary to have here
|
||||
|
||||
#TODO: incorp BaseAPIController.validate_in_users_and_groups
|
||||
#TODO: incorp CreatesUsersMixin
|
||||
#TODO: incorp CreatesApiKeysMixin
|
||||
#TODO: incorporate security/validate_user_input.py
|
||||
#TODO: incorporate UsesFormDefinitionsMixin?
|
||||
|
||||
def create( self, trans, **kwargs ):
|
||||
"""
|
||||
Create a new user.
|
||||
"""
|
||||
#TODO: deserialize and validate here
|
||||
email = kwargs[ 'email' ]
|
||||
username = kwargs[ 'username' ]
|
||||
password = kwargs[ 'password' ]
|
||||
self._error_on_duplicate_email( trans, email )
|
||||
|
||||
user = model.User( email=email, password=password )
|
||||
user.username = username
|
||||
|
||||
if self.app.config.user_activation_on:
|
||||
user.active = False
|
||||
else:
|
||||
# Activation is off, every new user is active by default.
|
||||
user.active = True
|
||||
|
||||
self.app.model.context.add( user )
|
||||
try:
|
||||
self.app.model.context.flush()
|
||||
#TODO:?? flush needed for permissions below? If not, make optional
|
||||
except sqlalchemy.exceptions.IntegrityError, db_err:
|
||||
raise exceptions.Conflict( db_err.message )
|
||||
|
||||
# can throw an sqlalx.IntegrityError if username not unique
|
||||
|
||||
self.app.security_agent.create_private_user_role( user )
|
||||
#TODO: trans
|
||||
if trans.webapp.name == 'galaxy':
|
||||
# We set default user permissions, before we log in and set the default history permissions
|
||||
permissions = self.app.config.new_user_dataset_access_role_default_private
|
||||
self.app.security_agent.user_set_default_permissions( user, default_access_private=permissions )
|
||||
return user
|
||||
|
||||
def _error_on_duplicate_email( self, trans, email ):
|
||||
"""
|
||||
Check for a duplicate email and raise if found.
|
||||
|
||||
:raises exceptions.Conflict: if any are found
|
||||
"""
|
||||
#TODO: remove this check when unique=True is added to the email column
|
||||
if self.by_email( trans, email ) is not None:
|
||||
raise exceptions.Conflict( 'Email must be unique', email=email )
|
||||
|
||||
# ---- filters
|
||||
def by_email( self, trans, email, filters=None, **kwargs ):
|
||||
"""
|
||||
Find a user by their email.
|
||||
"""
|
||||
filters = self._munge_filters( self.model_class.email == email, filters )
|
||||
try:
|
||||
#TODO: use one_or_none
|
||||
return super( UserManager, self ).one( trans, filters=filters, **kwargs )
|
||||
except exceptions.ObjectNotFound, not_found:
|
||||
return None
|
||||
|
||||
def by_email_like( self, trans, email_with_wildcards, filters=None, order_by=None, **kwargs ):
|
||||
"""
|
||||
Find a user searching with SQL wildcards.
|
||||
"""
|
||||
filters = self._munge_filters( self.model_class.email.like( email_with_wildcards ), filters )
|
||||
order_by = order_by or ( model.User.email, )
|
||||
return super( UserManager, self ).list( trans, filters=filters, order_by=order_by, **kwargs )
|
||||
|
||||
# ---- admin
|
||||
def is_admin( self, trans, user ):
|
||||
"""
|
||||
Return True if this user is an admin.
|
||||
"""
|
||||
admin_emails = self._admin_emails( trans )
|
||||
return user and admin_emails and user.email in admin_emails
|
||||
|
||||
def _admin_emails( self, trans ):
|
||||
"""
|
||||
Return a list of admin email addresses from the config file.
|
||||
"""
|
||||
return [ x.strip() for x in self.app.config.get( "admin_users", "" ).split( "," ) ]
|
||||
|
||||
def admins( self, trans, filters=None, **kwargs ):
|
||||
"""
|
||||
Return a list of admin Users.
|
||||
"""
|
||||
filters = self._munge_filters( self.model_class.email.in_( self._admin_emails( trans ) ), filters )
|
||||
return super( UserManager, self ).list( trans, filters=filters, **kwargs )
|
||||
|
||||
def error_unless_admin( self, trans, user, msg="Administrators only", **kwargs ):
|
||||
"""
|
||||
Raise an error if `user` is not an admin.
|
||||
|
||||
:raises exceptions.AdminRequiredException: if `user` is not an admin.
|
||||
"""
|
||||
# useful in admin only methods
|
||||
if not self.is_admin( trans, user ):
|
||||
raise exceptions.AdminRequiredException( msg, **kwargs )
|
||||
return user
|
||||
|
||||
# ---- anonymous
|
||||
def is_anonymous( self, user ):
|
||||
"""
|
||||
Return True if `user` is anonymous.
|
||||
"""
|
||||
# define here for single point of change and make more readable
|
||||
return user is None
|
||||
|
||||
def error_if_anonymous( self, trans, user, msg="Log-in required", **kwargs ):
|
||||
"""
|
||||
Raise an error if `user` is anonymous.
|
||||
"""
|
||||
if user is None:
|
||||
#TODO: code is correct (403), but should be named AuthenticationRequired (401 and 403 are flipped)
|
||||
raise exceptions.AuthenticationFailed( msg, **kwargs )
|
||||
return user
|
||||
|
||||
# ---- current
|
||||
def current_user( self, trans ):
|
||||
# define here for single point of change and make more readable
|
||||
return trans.user
|
||||
|
||||
# ---- api keys
|
||||
def create_api_key( self, trans, user ):
|
||||
"""
|
||||
Create and return an API key for `user`.
|
||||
"""
|
||||
#TODO: seems like this should return the model
|
||||
return api_keys.ApiKeyManager( self.app ).create_api_key( user )
|
||||
|
||||
#TODO: possibly move to ApiKeyManager
|
||||
def valid_api_key( self, trans, user ):
|
||||
"""
|
||||
Return this most recent APIKey for this user or None if none have been created.
|
||||
"""
|
||||
query = ( self.app.model.context.query( model.APIKeys )
|
||||
.filter_by( user=user )
|
||||
.order_by( sqlalchemy.desc( model.APIKeys.create_time ) ) )
|
||||
all = query.all()
|
||||
for a in all:
|
||||
print a.user.username, a.key, a.create_time
|
||||
print all
|
||||
if len( all ):
|
||||
return all[0]
|
||||
return None
|
||||
#return query.first()
|
||||
|
||||
#TODO: possibly move to ApiKeyManager
|
||||
def get_or_create_valid_api_key( self, trans, user ):
|
||||
"""
|
||||
Return this most recent APIKey for this user or create one if none have been
|
||||
created.
|
||||
"""
|
||||
existing = self.valid_api_key( trans, user )
|
||||
if existing:
|
||||
return existing
|
||||
return self.create_api_key( self, trans, user )
|
||||
|
||||
# ---- roles
|
||||
def private_role( self, trans, user ):
|
||||
"""
|
||||
Return the security agent's private role for `user`.
|
||||
"""
|
||||
#TODO: not sure we need to go through sec agent... it's just the first role of type private
|
||||
return self.app.security_agent.get_private_user_role( user )
|
||||
|
||||
|
||||
class UserSerializer( base.ModelSerializer ):
|
||||
|
||||
def __init__( self ):
|
||||
"""
|
||||
Convert a User and associated data to a dictionary representation.
|
||||
"""
|
||||
super( UserSerializer, self ).__init__()
|
||||
|
||||
self.default_view = 'summary'
|
||||
self.add_view( 'summary', [
|
||||
'id', 'email', 'username'
|
||||
])
|
||||
self.add_view( 'detailed', [
|
||||
'update_time',
|
||||
'create_time',
|
||||
'total_disk_usage',
|
||||
'nice_total_disk_usage',
|
||||
'deleted',
|
||||
'purged',
|
||||
'active'
|
||||
], include_keys_from='summary' )
|
||||
#self.add_view( 'summary', [
|
||||
# 'preferences',
|
||||
# # all tags
|
||||
# 'tags',
|
||||
# # all annotations
|
||||
# 'annotations'
|
||||
#], include_keys_from='detailed' )
|
||||
|
||||
def add_serializers( self ):
|
||||
self.serializers.update({
|
||||
'id' : self.serialize_id,
|
||||
'create_time' : self.serialize_date,
|
||||
'update_time' : self.serialize_date,
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
Manager and Serializers for Visualizations.
|
||||
|
||||
Visualizations are saved configurations/variables used to
|
||||
reproduce a specific view in a Galaxy visualization.
|
||||
"""
|
||||
|
||||
from galaxy import model
|
||||
from galaxy.managers import sharable
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
class VisualizationManager( sharable.SharableModelManager ):
|
||||
"""
|
||||
Handle operations outside and between visualizations and other models.
|
||||
"""
|
||||
|
||||
# TODO: revisions
|
||||
|
||||
model_class = model.Visualization
|
||||
foreign_key_name = 'visualization'
|
||||
user_share_model = model.VisualizationUserShareAssociation
|
||||
|
||||
tag_assoc = model.VisualizationTagAssociation
|
||||
annotation_assoc = model.VisualizationAnnotationAssociation
|
||||
rating_assoc = model.VisualizationRatingAssociation
|
||||
|
||||
def __init__( self, app, *args, **kwargs ):
|
||||
"""
|
||||
"""
|
||||
super( VisualizationManager, self ).__init__( app, *args, **kwargs )
|
||||
|
||||
#def copy( self, trans, visualization, user, **kwargs ):
|
||||
# """
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
class VisualizationSerializer( sharable.SharableModelSerializer ):
|
||||
"""
|
||||
Interface/service object for serializing visualizations into dictionaries.
|
||||
"""
|
||||
SINGLE_CHAR_ABBR = 'v'
|
||||
|
||||
def __init__( self, app ):
|
||||
super( VisualizationSerializer, self ).__init__( app )
|
||||
self.visualizations_manager = VisualizationManager( app )
|
||||
|
||||
self.default_view = 'summary'
|
||||
self.add_view( 'summary', [] )
|
||||
self.add_view( 'detailed', [] )
|
||||
|
||||
def add_serializers( self ):
|
||||
super( VisualizationSerializer, self ).add_serializers()
|
||||
self.serializers.update({
|
||||
})
|
||||
|
||||
|
||||
class VisualizationDeserializer( sharable.SharableModelDeserializer ):
|
||||
"""
|
||||
Interface/service object for validating and deserializing
|
||||
dictionaries into visualizations.
|
||||
"""
|
||||
model_manager_class = VisualizationManager
|
||||
|
||||
def __init__( self, app ):
|
||||
super( VisualizationDeserializer, self ).__init__( app )
|
||||
self.visualization_manager = self.manager
|
||||
|
||||
def add_deserializers( self ):
|
||||
super( VisualizationDeserializer, self ).add_deserializers()
|
||||
self.deserializers.update({
|
||||
})
|
||||
self.deserializable_keyset.update( self.deserializers.keys() )
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user