mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge.
This commit is contained in:
@@ -9,4 +9,4 @@ a4113cc1cb5eaa68091c9a73375f00555b66dd11 release_2013.01.13
|
||||
9e53251b0b7e93b9563008a2b112f2e815a04bbc release_2014.04.14
|
||||
68a8b0397947c732b28207d465d3f3c4e2a7a8a0 latest_2014.04.14
|
||||
7e257c7b10badb65772b1528cb61d58175a42e47 release_2014.06.02
|
||||
9b78595ec1142d75930dbfffd8baeaa689cb4c6c latest_2014.06.02
|
||||
8a863a311a6c9f14b302799bffcf94df9186fef7 latest_2014.06.02
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
// dependencies
|
||||
define(['mvc/ui/ui-modal', 'mvc/ui/ui-portlet', 'plugin/library/ui', 'utils/utils',
|
||||
'plugin/library/jobs', 'plugin/library/datasets', 'plugin/library/storage',
|
||||
'plugin/library/jobs', 'plugin/library/datasets', 'plugin/library/storage', 'plugin/library/deferred',
|
||||
'plugin/views/viewer', 'plugin/views/editor',
|
||||
'plugin/models/config', 'plugin/models/chart',
|
||||
'plugin/charts/types'],
|
||||
function( Modal, Portlet, Ui, Utils, Jobs, Datasets, Storage,
|
||||
function( Modal, Portlet, Ui, Utils, Jobs, Datasets, Storage, Deferred,
|
||||
ViewerView, EditorView,
|
||||
Config, Chart, Types
|
||||
) {
|
||||
|
||||
// widget
|
||||
return Backbone.View.extend(
|
||||
{
|
||||
/**
|
||||
* Main application class.
|
||||
*/
|
||||
return Backbone.View.extend({
|
||||
// initialize
|
||||
initialize: function(options)
|
||||
{
|
||||
initialize: function(options){
|
||||
// deactivate all debugs outputs
|
||||
//window.console.debug = function() {};
|
||||
|
||||
// link options
|
||||
this.options = options;
|
||||
|
||||
|
||||
// link galaxy modal or create one
|
||||
if (Galaxy && Galaxy.modal) {
|
||||
this.modal = Galaxy.modal;
|
||||
@@ -41,6 +41,7 @@ return Backbone.View.extend(
|
||||
this.jobs = new Jobs(this);
|
||||
this.datasets = new Datasets(this);
|
||||
this.storage = new Storage(this);
|
||||
this.deferred = new Deferred();
|
||||
|
||||
//
|
||||
// views
|
||||
@@ -62,7 +63,7 @@ return Backbone.View.extend(
|
||||
|
||||
// draw chart
|
||||
var self = this;
|
||||
this.chart.deferred.execute(function() {
|
||||
this.deferred.execute(function() {
|
||||
self.chart.trigger('redraw');
|
||||
});
|
||||
}
|
||||
@@ -100,19 +101,6 @@ return Backbone.View.extend(
|
||||
console.debug('FAILED App:chartPath() - Invalid format: ' + chart_type);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
// execute command
|
||||
execute: function(options) {
|
||||
},
|
||||
|
||||
// unload
|
||||
onunload: function() {
|
||||
},
|
||||
|
||||
// log
|
||||
log: function(location, message) {
|
||||
console.log(location + ' ' + message);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,9 +1,10 @@
|
||||
// dependencies
|
||||
define(['plugin/charts/jqplot/common/plot-config', 'plugin/charts/tools'], function(configmaker, Tools) {
|
||||
|
||||
// widget
|
||||
return Backbone.View.extend(
|
||||
{
|
||||
/**
|
||||
* This is the common wrapper for jqplot based visualizations.
|
||||
*/
|
||||
return Backbone.View.extend({
|
||||
// initialize
|
||||
initialize: function(app, options) {
|
||||
// get parameters
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// dependencies
|
||||
define(['plugin/charts/tools'], function(Tools) {
|
||||
|
||||
// widget
|
||||
return Backbone.View.extend(
|
||||
{
|
||||
/**
|
||||
* This is the common wrapper for nvd3 based visualizations.
|
||||
*/
|
||||
return Backbone.View.extend({
|
||||
// initialize
|
||||
initialize: function(app, options) {
|
||||
// get parameters
|
||||
@@ -97,6 +98,7 @@ return Backbone.View.extend(
|
||||
}
|
||||
});
|
||||
|
||||
// return
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ return Backbone.View.extend(
|
||||
chart.state('ok', 'Pie chart has been drawn.');
|
||||
|
||||
// unregister process
|
||||
chart.deferred.done(process_id);
|
||||
app.deferred.done(process_id);
|
||||
}
|
||||
|
||||
// request data
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
define(['utils/utils'], function(Utils) {
|
||||
|
||||
// render
|
||||
function panelHelper (app, options)
|
||||
{
|
||||
function panelHelper (app, options) {
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
@@ -42,7 +41,7 @@ function panelHelper (app, options)
|
||||
}
|
||||
|
||||
// unregister process
|
||||
chart.deferred.done(process_id);
|
||||
app.deferred.done(process_id);
|
||||
} catch (err) {
|
||||
// log
|
||||
console.debug('FAILED: Tools::panelHelper() - ' + err);
|
||||
@@ -51,7 +50,7 @@ function panelHelper (app, options)
|
||||
chart.state('failed', err);
|
||||
|
||||
// unregister process
|
||||
chart.deferred.done(process_id);
|
||||
app.deferred.done(process_id);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// dependencies
|
||||
define(['utils/utils'], function(Utils) {
|
||||
|
||||
// widget
|
||||
/**
|
||||
* This class handles, formats and caches datasets.
|
||||
*/
|
||||
return Backbone.Collection.extend(
|
||||
{
|
||||
// list of datasets
|
||||
@@ -11,8 +13,7 @@ return Backbone.Collection.extend(
|
||||
cache: {},
|
||||
|
||||
// initialize
|
||||
initialize: function(app, options)
|
||||
{
|
||||
initialize: function(app, options){
|
||||
// link app
|
||||
this.app = app;
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// dependencies
|
||||
define(['utils/utils'], function(Utils) {
|
||||
|
||||
// deferred process handler
|
||||
return Backbone.Model.extend(
|
||||
{
|
||||
/**
|
||||
* This class handles deferred processes. It makes it easy to handle multiple and overlapping sets of deferred processes.
|
||||
*/
|
||||
return Backbone.Model.extend({
|
||||
// queue
|
||||
queue: [],
|
||||
|
||||
@@ -14,8 +15,7 @@ return Backbone.Model.extend(
|
||||
counter: 0,
|
||||
|
||||
// initialize
|
||||
initialize: function()
|
||||
{
|
||||
initialize: function(){
|
||||
// loop through queue and check states
|
||||
this.on('refresh', function() {
|
||||
for (var index in this.queue) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// dependencies
|
||||
define(['utils/utils'], function(Utils) {
|
||||
|
||||
// widget
|
||||
return Backbone.Model.extend(
|
||||
{
|
||||
/**
|
||||
* This class handles job submissions to the charts tool.
|
||||
*/
|
||||
return Backbone.Model.extend({
|
||||
// initialize
|
||||
initialize: function(app, options) {
|
||||
// link app
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// dependencies
|
||||
/**
|
||||
* This class enables users to export/download a chart as PNG, SVG or PDF.
|
||||
*/
|
||||
define(["libs/underscore"], function(_) {
|
||||
//
|
||||
// PNG export
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// dependencies
|
||||
define(['utils/utils', 'plugin/models/chart', 'plugin/models/group'], function(Utils, Chart, Group) {
|
||||
define(['utils/utils', 'plugin/models/chart', 'plugin/models/group', 'mvc/visualization/visualization-model'], function(Utils, Chart, Group) {
|
||||
|
||||
// collection
|
||||
return Backbone.Model.extend(
|
||||
{
|
||||
/**
|
||||
* This class saves and loads a chart through the api.
|
||||
*/
|
||||
return Backbone.Model.extend({
|
||||
// viz model
|
||||
vis: null,
|
||||
|
||||
@@ -44,7 +45,6 @@ return Backbone.Model.extend(
|
||||
|
||||
// pack and save nested chart model
|
||||
save: function() {
|
||||
|
||||
// link chart
|
||||
var chart = this.app.chart;
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// dependencies
|
||||
define(['utils/utils'],
|
||||
function(Utils) {
|
||||
define(['utils/utils'], function(Utils) {
|
||||
|
||||
// return button menu
|
||||
/**
|
||||
* This class creates a button with dropdown menu. It extends the functionality of the Ui.ButtonIcon class.
|
||||
*/
|
||||
return Backbone.View.extend({
|
||||
// main options
|
||||
optionsDefault: {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// dependencies
|
||||
define(['utils/utils'], function(Utils) {
|
||||
|
||||
// plugin
|
||||
var View = Backbone.View.extend(
|
||||
{
|
||||
/**
|
||||
* This class creates/wraps a default html select field as backbone class.
|
||||
*/
|
||||
var View = Backbone.View.extend({
|
||||
// options
|
||||
optionsDefault : {
|
||||
id : '',
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
define(['plugin/library/ui-table', 'plugin/library/ui', 'utils/utils'],
|
||||
function(Table, Ui, Utils) {
|
||||
|
||||
// widget
|
||||
var View = Backbone.View.extend(
|
||||
{
|
||||
/**
|
||||
* This class takes a dictionary as input an creates an input form. It uses the Ui.Table element to organize and format the form elements.
|
||||
*/
|
||||
var View = Backbone.View.extend({
|
||||
// options
|
||||
optionsDefault: {
|
||||
title : '',
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// dependencies
|
||||
define(['utils/utils'], function(Utils) {
|
||||
|
||||
var View = Backbone.View.extend(
|
||||
{
|
||||
/**
|
||||
* This class creates a ui table element.
|
||||
*/
|
||||
var View = Backbone.View.extend({
|
||||
// current row
|
||||
row: null,
|
||||
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
define(['utils/utils', 'plugin/library/ui-select', 'plugin/library/ui-button-menu', 'mvc/ui/ui-modal'],
|
||||
function(Utils, Select, ButtonMenu, Modal) {
|
||||
|
||||
/**
|
||||
* This class contains backbone wrappers for basic ui elements such as Images, Labels, Buttons, Input fields etc.
|
||||
*/
|
||||
|
||||
// plugin
|
||||
var Image = Backbone.View.extend(
|
||||
{
|
||||
var Image = Backbone.View.extend({
|
||||
// options
|
||||
optionsDefault: {
|
||||
url : '',
|
||||
@@ -27,8 +30,7 @@ var Image = Backbone.View.extend(
|
||||
});
|
||||
|
||||
// plugin
|
||||
var Label = Backbone.View.extend(
|
||||
{
|
||||
var Label = Backbone.View.extend({
|
||||
// options
|
||||
optionsDefault: {
|
||||
title : '',
|
||||
@@ -61,8 +63,7 @@ var Label = Backbone.View.extend(
|
||||
});
|
||||
|
||||
// plugin
|
||||
var Icon = Backbone.View.extend(
|
||||
{
|
||||
var Icon = Backbone.View.extend({
|
||||
// options
|
||||
optionsDefault: {
|
||||
float : 'right',
|
||||
@@ -95,8 +96,7 @@ var Icon = Backbone.View.extend(
|
||||
});
|
||||
|
||||
// plugin
|
||||
var Button = Backbone.View.extend(
|
||||
{
|
||||
var Button = Backbone.View.extend({
|
||||
// options
|
||||
optionsDefault: {
|
||||
id : null,
|
||||
@@ -134,8 +134,7 @@ var Button = Backbone.View.extend(
|
||||
});
|
||||
|
||||
// plugin
|
||||
var ButtonIcon = Backbone.View.extend(
|
||||
{
|
||||
var ButtonIcon = Backbone.View.extend({
|
||||
// options
|
||||
optionsDefault: {
|
||||
id : null,
|
||||
@@ -188,8 +187,7 @@ var ButtonIcon = Backbone.View.extend(
|
||||
});
|
||||
|
||||
// plugin
|
||||
var Anchor = Backbone.View.extend(
|
||||
{
|
||||
var Anchor = Backbone.View.extend({
|
||||
// options
|
||||
optionsDefault: {
|
||||
title : '',
|
||||
@@ -215,8 +213,7 @@ var Anchor = Backbone.View.extend(
|
||||
});
|
||||
|
||||
// plugin
|
||||
var Message = Backbone.View.extend(
|
||||
{
|
||||
var Message = Backbone.View.extend({
|
||||
// options
|
||||
optionsDefault: {
|
||||
message : '',
|
||||
@@ -268,8 +265,7 @@ var Message = Backbone.View.extend(
|
||||
});
|
||||
|
||||
// plugin
|
||||
var Searchbox = Backbone.View.extend(
|
||||
{
|
||||
var Searchbox = Backbone.View.extend({
|
||||
// options
|
||||
optionsDefault: {
|
||||
onclick : null,
|
||||
@@ -308,8 +304,7 @@ var Searchbox = Backbone.View.extend(
|
||||
});
|
||||
|
||||
// plugin
|
||||
var Input = Backbone.View.extend(
|
||||
{
|
||||
var Input = Backbone.View.extend({
|
||||
// options
|
||||
optionsDefault: {
|
||||
value : '',
|
||||
@@ -362,8 +357,7 @@ var Input = Backbone.View.extend(
|
||||
});
|
||||
|
||||
// plugin
|
||||
var Textarea = Backbone.View.extend(
|
||||
{
|
||||
var Textarea = Backbone.View.extend({
|
||||
// options
|
||||
optionsDefault: {
|
||||
value : '',
|
||||
@@ -416,8 +410,7 @@ var Textarea = Backbone.View.extend(
|
||||
});
|
||||
|
||||
// plugin
|
||||
var RadioButton = Backbone.View.extend(
|
||||
{
|
||||
var RadioButton = Backbone.View.extend({
|
||||
// options
|
||||
optionsDefault: {
|
||||
value : '',
|
||||
@@ -503,19 +496,19 @@ var RadioButton = Backbone.View.extend(
|
||||
|
||||
// return
|
||||
return {
|
||||
Label : Label,
|
||||
Button : Button,
|
||||
Icon : Icon,
|
||||
ButtonIcon : ButtonIcon,
|
||||
Input : Input,
|
||||
Anchor : Anchor,
|
||||
Message : Message,
|
||||
Searchbox : Searchbox,
|
||||
Select : Select,
|
||||
ButtonMenu : ButtonMenu,
|
||||
Modal: Modal,
|
||||
Textarea: Textarea,
|
||||
Image: Image,
|
||||
RadioButton: RadioButton
|
||||
Anchor : Anchor,
|
||||
Button : Button,
|
||||
ButtonIcon : ButtonIcon,
|
||||
ButtonMenu : ButtonMenu,
|
||||
Icon : Icon,
|
||||
Image : Image,
|
||||
Input : Input,
|
||||
Label : Label,
|
||||
Message : Message,
|
||||
Modal : Modal,
|
||||
RadioButton : RadioButton,
|
||||
Searchbox : Searchbox,
|
||||
Select : Select,
|
||||
Textarea : Textarea
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
// dependencies
|
||||
define(['plugin/models/groups', 'plugin/library/deferred', 'mvc/visualization/visualization-model'],
|
||||
function(Groups, Deferred) {
|
||||
|
||||
define(['plugin/models/groups'], function(Groups) {
|
||||
|
||||
// model
|
||||
return Backbone.Model.extend(
|
||||
{
|
||||
return Backbone.Model.extend({
|
||||
// defaults
|
||||
defaults : {
|
||||
id : null,
|
||||
@@ -23,7 +20,6 @@ return Backbone.Model.extend(
|
||||
initialize: function(options) {
|
||||
this.groups = new Groups();
|
||||
this.settings = new Backbone.Model();
|
||||
this.deferred = new Deferred();
|
||||
},
|
||||
|
||||
// reset
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
define([], function() {
|
||||
|
||||
// model
|
||||
return Backbone.Model.extend(
|
||||
{
|
||||
return Backbone.Model.extend({
|
||||
// options
|
||||
defaults : {
|
||||
query_limit : 500,
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
define([], function() {
|
||||
|
||||
// model
|
||||
return Backbone.Model.extend(
|
||||
{
|
||||
return Backbone.Model.extend({
|
||||
// options
|
||||
defaults : {
|
||||
key : 'Data label',
|
||||
@@ -11,8 +10,7 @@ return Backbone.Model.extend(
|
||||
},
|
||||
|
||||
// reset
|
||||
reset: function()
|
||||
{
|
||||
reset: function(){
|
||||
this.clear({silent: true}).set(this.defaults);
|
||||
this.trigger('reset', this);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
define(['plugin/models/group'], function(Group) {
|
||||
|
||||
// collection
|
||||
return Backbone.Collection.extend(
|
||||
{
|
||||
return Backbone.Collection.extend({
|
||||
model: Group
|
||||
});
|
||||
|
||||
|
||||
@@ -4,18 +4,13 @@ define(['mvc/ui/ui-tabs', 'plugin/library/ui', 'mvc/ui/ui-portlet', 'utils/utils
|
||||
'plugin/views/group', 'plugin/views/settings', 'plugin/views/types'],
|
||||
function(Tabs, Ui, Portlet, Utils, Chart, Group, GroupView, SettingsView, TypesView) {
|
||||
|
||||
// widget
|
||||
return Backbone.View.extend(
|
||||
{
|
||||
// defaults options
|
||||
optionsDefault: {
|
||||
header : true,
|
||||
content : 'No content available.'
|
||||
},
|
||||
|
||||
/**
|
||||
* The charts editor holds the tabs for selecting chart types, chart configuration
|
||||
* and data group selections.
|
||||
*/
|
||||
return Backbone.View.extend({
|
||||
// initialize
|
||||
initialize: function(app, options)
|
||||
{
|
||||
initialize: function(app, options){
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
@@ -25,9 +20,6 @@ return Backbone.View.extend(
|
||||
// get current chart object
|
||||
this.chart = this.app.chart;
|
||||
|
||||
// configure options
|
||||
this.options = Utils.merge(options, this.optionsDefault);
|
||||
|
||||
// message element
|
||||
this.message = new Ui.Message();
|
||||
|
||||
@@ -49,7 +41,7 @@ return Backbone.View.extend(
|
||||
tooltip : 'Return to Viewer',
|
||||
title : 'Cancel',
|
||||
onclick : function() {
|
||||
// show viewport
|
||||
// show viewer/viewport
|
||||
self.app.go('viewer');
|
||||
|
||||
// reset chart
|
||||
@@ -200,7 +192,7 @@ return Backbone.View.extend(
|
||||
this.title.value(title);
|
||||
},
|
||||
|
||||
// update
|
||||
// refresh group
|
||||
_refreshGroupKey: function() {
|
||||
var self = this;
|
||||
var counter = 0;
|
||||
@@ -213,7 +205,7 @@ return Backbone.View.extend(
|
||||
});
|
||||
},
|
||||
|
||||
// new group
|
||||
// add group model
|
||||
_addGroupModel: function() {
|
||||
var group = new Group({
|
||||
id : Utils.uuid()
|
||||
@@ -222,7 +214,7 @@ return Backbone.View.extend(
|
||||
return group;
|
||||
},
|
||||
|
||||
// add group
|
||||
// add group tab
|
||||
_addGroup: function(group) {
|
||||
// link this
|
||||
var self = this;
|
||||
@@ -319,7 +311,7 @@ return Backbone.View.extend(
|
||||
|
||||
// wait until chart is ready
|
||||
var self = this;
|
||||
this.chart.deferred.execute(function() {
|
||||
this.app.deferred.execute(function() {
|
||||
// save
|
||||
self.app.storage.save();
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
define(['plugin/library/ui-table', 'plugin/library/ui', 'utils/utils'],
|
||||
function(Table, Ui, Utils) {
|
||||
|
||||
// widget
|
||||
return Backbone.View.extend(
|
||||
{
|
||||
/**
|
||||
* This class renders the data group selection fields.
|
||||
*/
|
||||
return Backbone.View.extend({
|
||||
// initialize
|
||||
initialize: function(app, options) {
|
||||
// link app
|
||||
@@ -59,7 +60,7 @@ return Backbone.View.extend(
|
||||
this._refreshGroup();
|
||||
},
|
||||
|
||||
// update dataset
|
||||
// update group selection table
|
||||
_refreshTable: function() {
|
||||
// identify datasets
|
||||
var dataset_id = this.chart.get('dataset_id');
|
||||
@@ -116,7 +117,7 @@ return Backbone.View.extend(
|
||||
this.chart.state('wait', 'Loading metadata...');
|
||||
|
||||
// register process
|
||||
var process_id = this.chart.deferred.register();
|
||||
var process_id = this.app.deferred.register();
|
||||
|
||||
// request dictionary
|
||||
var request_dictionary = {
|
||||
@@ -131,7 +132,7 @@ return Backbone.View.extend(
|
||||
self.chart.state('ok', 'Metadata initialized...');
|
||||
|
||||
// unregister
|
||||
self.chart.deferred.done(process_id);
|
||||
self.app.deferred.done(process_id);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
define(['plugin/library/ui', 'plugin/library/ui-table-form', 'utils/utils'],
|
||||
function(Ui, TableForm, Utils) {
|
||||
|
||||
// widget
|
||||
return Backbone.View.extend(
|
||||
{
|
||||
/**
|
||||
* This class renders the chart configuration form.
|
||||
*/
|
||||
return Backbone.View.extend({
|
||||
// initialize
|
||||
initialize: function(app, options) {
|
||||
// link app
|
||||
@@ -36,7 +37,7 @@ return Backbone.View.extend(
|
||||
// get settings
|
||||
var chart_definition = this.chart.definition;
|
||||
|
||||
// check if dataset is available
|
||||
// check if chart definition is available
|
||||
if (!chart_definition) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// dependencies
|
||||
define(['utils/utils', 'plugin/library/ui'], function(Utils, Ui) {
|
||||
|
||||
return Backbone.View.extend(
|
||||
{
|
||||
/**
|
||||
* This class renders the chart type selection grid.
|
||||
*/
|
||||
return Backbone.View.extend({
|
||||
// defaults options
|
||||
optionsDefault: {
|
||||
onchange : null,
|
||||
@@ -16,7 +19,6 @@ return Backbone.View.extend(
|
||||
|
||||
// initialize
|
||||
initialize : function(app, options) {
|
||||
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// dependencies
|
||||
define(['utils/utils', 'plugin/library/ui', 'mvc/ui/ui-portlet',
|
||||
'plugin/models/group', 'plugin/views/viewport', 'plugin/library/screenshot'],
|
||||
function(Utils, Ui, Portlet, Group, ViewportView, Screenshot) {
|
||||
'plugin/views/viewport', 'plugin/library/screenshot'],
|
||||
function(Utils, Ui, Portlet, ViewportView, Screenshot) {
|
||||
|
||||
// widget
|
||||
return Backbone.View.extend(
|
||||
{
|
||||
/**
|
||||
* This class renders the chart viewer which encapsulates the chart viewport.
|
||||
*/
|
||||
return Backbone.View.extend({
|
||||
// initialize
|
||||
initialize: function(app, options)
|
||||
{
|
||||
initialize: function(app, options){
|
||||
// link app
|
||||
this.app = app;
|
||||
|
||||
@@ -152,7 +152,7 @@ return Backbone.View.extend(
|
||||
// wait for chart to be ready
|
||||
_wait: function(chart, callback) {
|
||||
// get chart
|
||||
if (chart.deferred.ready()) {
|
||||
if (this.app.deferred.ready()) {
|
||||
callback();
|
||||
} else {
|
||||
this.message.update({message: 'Your chart is currently being processed. Please wait and try again.'});
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
define(['mvc/ui/ui-portlet', 'plugin/library/ui', 'utils/utils'],
|
||||
function(Portlet, Ui, Utils) {
|
||||
|
||||
// widget
|
||||
/**
|
||||
* The viewport creates and manages the dom elements used by the visualization plugins to draw the chart.
|
||||
* Additionally, this class creates default request strings and request dictionaries parsed to the visualization plugins.
|
||||
* This is the last class of the charts core classes before handing control over to the visualization plugins.
|
||||
*/
|
||||
return Backbone.View.extend({
|
||||
|
||||
// list of canvas elements
|
||||
@@ -26,7 +30,7 @@ return Backbone.View.extend({
|
||||
// use full screen for viewer
|
||||
this._fullscreen(this.$el, 100);
|
||||
|
||||
// create canvas element
|
||||
// create container element
|
||||
this._createContainer('div');
|
||||
|
||||
// events
|
||||
@@ -126,7 +130,7 @@ return Backbone.View.extend({
|
||||
var self = this;
|
||||
|
||||
// register process
|
||||
var process_id = chart.deferred.register();
|
||||
var process_id = this.app.deferred.register();
|
||||
|
||||
// identify chart type
|
||||
var chart_type = chart.get('type');
|
||||
@@ -171,7 +175,7 @@ return Backbone.View.extend({
|
||||
});
|
||||
},
|
||||
function() {
|
||||
chart.deferred.done(process_id);
|
||||
this.app.deferred.done(process_id);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -38,7 +38,7 @@ bioblend = 0.4.2
|
||||
boto = 2.27.0
|
||||
decorator = 3.1.2
|
||||
docutils = 0.7
|
||||
drmaa = 0.6
|
||||
drmaa = 0.7.6
|
||||
elementtree = 1.2.6_20050316
|
||||
Fabric = 1.7.0
|
||||
GeneTrack = 2.0.0_beta_1
|
||||
|
||||
@@ -102,6 +102,12 @@
|
||||
<!-- Warning: Local slot count doesn't tie up additional worker threads, to prevent over
|
||||
allocating machine define a second local runner with different name and fewer workers
|
||||
to run this destination. -->
|
||||
<param id="embed_metadata_in_job">True</param>
|
||||
<!-- Above parameter will be default (with no option to set
|
||||
to False) in an upcoming release of Galaxy, but you can
|
||||
try it early - it will slightly speed up local jobs by
|
||||
embedding metadata calculation in job script itself.
|
||||
-->
|
||||
<job_metrics />
|
||||
<!-- Above element demonstrates embedded job metrics definition - see
|
||||
job_metrics_conf.xml.sample for full documentation on possible nested
|
||||
@@ -148,6 +154,11 @@
|
||||
are available to all jobs and $job_directory is also available for
|
||||
Pulsar jobs.
|
||||
-->
|
||||
<!-- One can run docker using volumes-from tag by setting the following
|
||||
parameter. For more information on volumes-from check out the following
|
||||
docker tutorial. https://docs.docker.com/userguide/dockervolumes/
|
||||
-->
|
||||
<!-- <param id="docker_volumes_from">parent_container_name</param> -->
|
||||
<!-- Control memory allocatable by docker container with following option:
|
||||
-->
|
||||
<!-- <param id="docker_memory">24G</param> -->
|
||||
|
||||
@@ -12,7 +12,6 @@ from galaxy.visualization.genomes import Genomes
|
||||
from galaxy.visualization.data_providers.registry import DataProviderRegistry
|
||||
from galaxy.visualization.registry import VisualizationsRegistry
|
||||
from galaxy.tools.imp_exp import load_history_imp_exp_tools
|
||||
from galaxy.tools.genome_index import load_genome_index_tools
|
||||
from galaxy.sample_tracking import external_service_types
|
||||
from galaxy.openid.providers import OpenIDProviders
|
||||
from galaxy.tools.data_manager.manager import DataManagers
|
||||
@@ -93,8 +92,6 @@ class UniverseApplication( object, config.ConfiguresGalaxyMixin ):
|
||||
self.datatypes_registry.load_external_metadata_tool( self.toolbox )
|
||||
# Load history import/export tools.
|
||||
load_history_imp_exp_tools( self.toolbox )
|
||||
# Load genome indexer tool.
|
||||
load_genome_index_tools( self.toolbox )
|
||||
# visualizations registry: associates resources with visualizations, controls how to render
|
||||
self.visualizations_registry = None
|
||||
if self.config.visualization_plugins_directory:
|
||||
|
||||
@@ -126,10 +126,21 @@ class MetadataCollection( object ):
|
||||
rval[key] = self.spec[key].param.make_copy( value, target_context=self, source_context=to_copy )
|
||||
return rval
|
||||
|
||||
def from_JSON_dict( self, filename, path_rewriter=None ):
|
||||
def from_JSON_dict( self, filename=None, path_rewriter=None, json_dict=None ):
|
||||
dataset = self.parent
|
||||
log.debug( 'loading metadata from file for: %s %s' % ( dataset.__class__.__name__, dataset.id ) )
|
||||
JSONified_dict = json.load( open( filename ) )
|
||||
if filename is not None:
|
||||
log.debug( 'loading metadata from file for: %s %s' % ( dataset.__class__.__name__, dataset.id ) )
|
||||
JSONified_dict = json.load( open( filename ) )
|
||||
elif json_dict is not None:
|
||||
log.debug( 'loading metadata from dict for: %s %s' % ( dataset.__class__.__name__, dataset.id ) )
|
||||
if isinstance( json_dict, basestring ):
|
||||
JSONified_dict = json.loads( json_dict )
|
||||
elif isinstance( json_dict, dict ):
|
||||
JSONified_dict = json_dict
|
||||
else:
|
||||
raise ValueError( "json_dict must be either a dictionary or a string, got %s." % ( type( json_dict ) ) )
|
||||
else:
|
||||
raise ValueError( "You must provide either a filename or a json_dict" )
|
||||
for name, spec in self.spec.items():
|
||||
if name in JSONified_dict:
|
||||
from_ext_kwds = {}
|
||||
@@ -143,13 +154,15 @@ class MetadataCollection( object ):
|
||||
#metadata associated with our dataset, we'll delete it from our dataset's metadata dict
|
||||
del dataset._metadata[ name ]
|
||||
|
||||
def to_JSON_dict( self, filename ):
|
||||
def to_JSON_dict( self, filename=None ):
|
||||
#galaxy.model.customtypes.json_encoder.encode()
|
||||
meta_dict = {}
|
||||
dataset_meta_dict = self.parent._metadata
|
||||
for name, spec in self.spec.items():
|
||||
if name in dataset_meta_dict:
|
||||
meta_dict[ name ] = spec.param.to_external_value( dataset_meta_dict[ name ] )
|
||||
if filename is None:
|
||||
return json.dumps( meta_dict )
|
||||
json.dump( meta_dict, open( filename, 'wb+' ) )
|
||||
|
||||
def __getstate__( self ):
|
||||
|
||||
@@ -1181,17 +1181,21 @@ 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 ] )
|
||||
input_ext = 'data'
|
||||
for _, data in inp_data.items():
|
||||
# For loop odd, but sort simulating behavior in galaxy.tools.actions
|
||||
if not data:
|
||||
continue
|
||||
input_ext = data.ext
|
||||
|
||||
param_dict = dict( [ ( p.name, p.value ) for p in job.parameters ] ) # why not re-use self.param_dict here? ##dunno...probably should, this causes tools.parameters.basic.UnvalidatedValue to be used in following methods instead of validated and transformed values during i.e. running workflows
|
||||
param_dict = self.tool.params_from_strings( param_dict, self.app )
|
||||
# Check for and move associated_files
|
||||
self.tool.collect_associated_files(out_data, self.working_directory)
|
||||
gitd = self.sa_session.query( model.GenomeIndexToolData ).filter_by( job=job ).first()
|
||||
if gitd:
|
||||
self.tool.collect_associated_files({'': gitd}, self.working_directory)
|
||||
# Create generated output children and primary datasets and add to param_dict
|
||||
collected_datasets = {
|
||||
'children': self.tool.collect_child_datasets(out_data, self.working_directory),
|
||||
'primary': self.tool.collect_primary_datasets(out_data, self.working_directory)
|
||||
'primary': self.tool.collect_primary_datasets(out_data, self.working_directory, input_ext)
|
||||
}
|
||||
param_dict.update({'__collected_datasets__': collected_datasets})
|
||||
# Certain tools require tasks to be completed after job execution
|
||||
@@ -1241,7 +1245,6 @@ class JobWrapper( object ):
|
||||
self.external_output_metadata.cleanup_external_metadata( self.sa_session )
|
||||
galaxy.tools.imp_exp.JobExportHistoryArchiveWrapper( self.job_id ).cleanup_after_job( self.sa_session )
|
||||
galaxy.tools.imp_exp.JobImportHistoryArchiveWrapper( self.app, self.job_id ).cleanup_after_job()
|
||||
galaxy.tools.genome_index.GenomeIndexToolWrapper( self.job_id ).postprocessing( self.sa_session, self.app )
|
||||
if delete_files:
|
||||
self.app.object_store.delete(self.get_job(), base_dir='job_work', entire_dir=True, dir_only=True, extra_dir=str(self.job_id))
|
||||
except:
|
||||
@@ -1344,10 +1347,8 @@ class JobWrapper( object ):
|
||||
dataset_path_rewriter = self.dataset_path_rewriter
|
||||
|
||||
job = self.get_job()
|
||||
# Job output datasets are combination of history, library, jeha and gitd datasets.
|
||||
# Job output datasets are combination of history, library, and jeha datasets.
|
||||
special = self.sa_session.query( model.JobExportHistoryArchive ).filter_by( job=job ).first()
|
||||
if not special:
|
||||
special = self.sa_session.query( model.GenomeIndexToolData ).filter_by( job=job ).first()
|
||||
false_path = None
|
||||
|
||||
results = []
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
"""
|
||||
Module for managing genome transfer jobs.
|
||||
"""
|
||||
from __future__ import with_statement
|
||||
|
||||
import logging, shutil, gzip, bz2, zipfile, tempfile, tarfile, sys, os
|
||||
|
||||
from galaxy import eggs
|
||||
from sqlalchemy import and_
|
||||
from data_transfer import *
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
__all__ = [ 'GenomeIndexPlugin' ]
|
||||
|
||||
class GenomeIndexPlugin( DataTransfer ):
|
||||
|
||||
def __init__( self, app ):
|
||||
super( GenomeIndexPlugin, self ).__init__( app )
|
||||
self.app = app
|
||||
self.tool = app.toolbox.tools_by_id['__GENOME_INDEX__']
|
||||
self.sa_session = app.model.context.current
|
||||
|
||||
def create_job( self, trans, path, indexes, dbkey, intname ):
|
||||
params = dict( user=trans.user.id, path=path, indexes=indexes, dbkey=dbkey, intname=intname )
|
||||
deferred = trans.app.model.DeferredJob( state = self.app.model.DeferredJob.states.NEW, plugin = 'GenomeIndexPlugin', params = params )
|
||||
self.sa_session.add( deferred )
|
||||
self.sa_session.flush()
|
||||
log.debug( 'Job created, id %d' % deferred.id )
|
||||
return deferred.id
|
||||
|
||||
def check_job( self, job ):
|
||||
log.debug( 'Job check' )
|
||||
return 'ready'
|
||||
|
||||
def run_job( self, job ):
|
||||
incoming = dict( path=os.path.abspath( job.params[ 'path' ] ), indexer=job.params[ 'indexes' ][0], user=job.params[ 'user' ] )
|
||||
indexjob = self.tool.execute( self, set_output_hid=False, history=None, incoming=incoming, transfer=None, deferred=job )
|
||||
job.params[ 'indexjob' ] = indexjob[0].id
|
||||
job.state = self.app.model.DeferredJob.states.RUNNING
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.flush()
|
||||
return self.app.model.DeferredJob.states.RUNNING
|
||||
@@ -1,250 +0,0 @@
|
||||
"""
|
||||
Module for managing genome transfer jobs.
|
||||
"""
|
||||
from __future__ import with_statement
|
||||
|
||||
import logging, shutil, gzip, bz2, zipfile, tempfile, tarfile, sys
|
||||
|
||||
from galaxy import eggs
|
||||
from sqlalchemy import and_
|
||||
|
||||
from galaxy.util.odict import odict
|
||||
from galaxy.workflow.modules import module_factory
|
||||
from galaxy.jobs.actions.post import ActionBox
|
||||
|
||||
from galaxy.tools.parameters import visit_input_values
|
||||
from galaxy.tools.parameters.basic import DataToolParameter
|
||||
from galaxy.tools.data import ToolDataTableManager
|
||||
|
||||
from galaxy.datatypes.checkers import *
|
||||
from galaxy.datatypes.sequence import Fasta
|
||||
from data_transfer import *
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
__all__ = [ 'GenomeTransferPlugin' ]
|
||||
|
||||
class GenomeTransferPlugin( DataTransfer ):
|
||||
|
||||
locations = {}
|
||||
|
||||
def __init__( self, app ):
|
||||
super( GenomeTransferPlugin, self ).__init__( app )
|
||||
self.app = app
|
||||
self.tool = app.toolbox.tools_by_id['__GENOME_INDEX__']
|
||||
self.sa_session = app.model.context.current
|
||||
tdtman = ToolDataTableManager( app.config.tool_data_path )
|
||||
xmltree = tdtman.load_from_config_file( app.config.tool_data_table_config_path, app.config.tool_data_path )
|
||||
for node in xmltree:
|
||||
table = node.get('name')
|
||||
location = node.findall('file')[0].get('path')
|
||||
self.locations[table] = location
|
||||
|
||||
def create_job( self, trans, url, dbkey, intname, indexes ):
|
||||
job = trans.app.transfer_manager.new( protocol='http', url=url )
|
||||
params = dict( user=trans.user.id, transfer_job_id=job.id, protocol='http', type='init_transfer', url=url, dbkey=dbkey, indexes=indexes, intname=intname, liftover=None )
|
||||
deferred = trans.app.model.DeferredJob( state = self.app.model.DeferredJob.states.NEW, plugin = 'GenomeTransferPlugin', params = params )
|
||||
self.sa_session.add( deferred )
|
||||
self.sa_session.flush()
|
||||
return deferred.id
|
||||
|
||||
def check_job( self, job ):
|
||||
if job.params['type'] == 'init_transfer':
|
||||
if not hasattr(job, 'transfer_job'):
|
||||
job.transfer_job = self.sa_session.query( self.app.model.TransferJob ).get( int( job.params[ 'transfer_job_id' ] ) )
|
||||
else:
|
||||
self.sa_session.refresh( job.transfer_job )
|
||||
if job.transfer_job.state == 'done':
|
||||
transfer = job.transfer_job
|
||||
transfer.state = 'downloaded'
|
||||
job.params['type'] = 'extract_transfer'
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.add( transfer )
|
||||
self.sa_session.flush()
|
||||
return self.job_states.READY
|
||||
elif job.transfer_job.state == 'running':
|
||||
return self.job_states.WAIT
|
||||
elif job.transfer_job.state == 'new':
|
||||
assert job.params[ 'protocol' ] in [ 'http', 'ftp', 'https' ], 'Unknown protocol %s' % job.params[ 'protocol' ]
|
||||
self.app.transfer_manager.run( job.transfer_job )
|
||||
self.sa_session.add( job.transfer_job )
|
||||
self.sa_session.flush()
|
||||
return self.job_states.WAIT
|
||||
else:
|
||||
log.error( "An error occurred while downloading from %s" % job.params[ 'url' ] )
|
||||
return self.job_states.INVALID
|
||||
elif job.params[ 'type' ] == 'extract_transfer':
|
||||
return self.job_states.READY
|
||||
|
||||
def get_job_status( self, jobid ):
|
||||
job = self.sa_session.query( self.app.model.DeferredJob ).get( int( jobid ) )
|
||||
if 'transfer_job_id' in job.params:
|
||||
if not hasattr( job, 'transfer_job' ):
|
||||
job.transfer_job = self.sa_session.query( self.app.model.TransferJob ).get( int( job.params[ 'transfer_job_id' ] ) )
|
||||
else:
|
||||
self.sa_session.refresh( job.transfer_job )
|
||||
return job
|
||||
|
||||
def run_job( self, job ):
|
||||
params = job.params
|
||||
dbkey = params[ 'dbkey' ]
|
||||
if not hasattr( job, 'transfer_job' ):
|
||||
job.transfer_job = self.sa_session.query( self.app.model.TransferJob ).get( int( job.params[ 'transfer_job_id' ] ) )
|
||||
else:
|
||||
self.sa_session.refresh( job.transfer_job )
|
||||
transfer = job.transfer_job
|
||||
if params[ 'type' ] == 'extract_transfer':
|
||||
CHUNK_SIZE = 2**20
|
||||
destpath = os.path.join( self.app.config.get( 'genome_data_path', 'tool-data/genome' ), job.params[ 'dbkey' ], 'seq' )
|
||||
destfile = '%s.fa' % job.params[ 'dbkey' ]
|
||||
destfilepath = os.path.join( destpath, destfile )
|
||||
tmpprefix = '%s_%s_download_unzip_' % ( job.params['dbkey'], job.params[ 'transfer_job_id' ] )
|
||||
tmppath = os.path.dirname( os.path.abspath( transfer.path ) )
|
||||
if not os.path.exists( destpath ):
|
||||
os.makedirs( destpath )
|
||||
protocol = job.params[ 'protocol' ]
|
||||
data_type = self._check_compress( transfer.path )
|
||||
if data_type is None:
|
||||
sniffer = Fasta()
|
||||
if sniffer.sniff( transfer.path ):
|
||||
data_type = 'fasta'
|
||||
fd, uncompressed = tempfile.mkstemp( prefix=tmpprefix, dir=tmppath, text=False )
|
||||
if data_type in [ 'tar.gzip', 'tar.bzip' ]:
|
||||
fp = open( transfer.path, 'r' )
|
||||
tar = tarfile.open( mode = 'r:*', bufsize = CHUNK_SIZE, fileobj = fp )
|
||||
files = tar.getmembers()
|
||||
for filename in files:
|
||||
z = tar.extractfile(filename)
|
||||
while 1:
|
||||
try:
|
||||
chunk = z.read( CHUNK_SIZE )
|
||||
except IOError:
|
||||
os.close( fd )
|
||||
log.error( 'Problem decompressing compressed data' )
|
||||
exit()
|
||||
if not chunk:
|
||||
break
|
||||
os.write( fd, chunk )
|
||||
os.write( fd, '\n' )
|
||||
os.close( fd )
|
||||
tar.close()
|
||||
fp.close()
|
||||
elif data_type == 'gzip':
|
||||
compressed = gzip.open( transfer.path, mode = 'rb' )
|
||||
while 1:
|
||||
try:
|
||||
chunk = compressed.read( CHUNK_SIZE )
|
||||
except IOError:
|
||||
compressed.close()
|
||||
log.error( 'Problem decompressing compressed data' )
|
||||
exit()
|
||||
if not chunk:
|
||||
break
|
||||
os.write( fd, chunk )
|
||||
os.close( fd )
|
||||
compressed.close()
|
||||
elif data_type == 'bzip':
|
||||
compressed = bz2.BZ2File( transfer.path, mode = 'r' )
|
||||
while 1:
|
||||
try:
|
||||
chunk = compressed.read( CHUNK_SIZE )
|
||||
except IOError:
|
||||
compressed.close()
|
||||
log.error( 'Problem decompressing compressed data' )
|
||||
exit()
|
||||
if not chunk:
|
||||
break
|
||||
os.write( fd, chunk )
|
||||
os.close( fd )
|
||||
compressed.close()
|
||||
elif data_type == 'zip':
|
||||
uncompressed_name = None
|
||||
unzipped = False
|
||||
z = zipfile.ZipFile( transfer.path )
|
||||
z.debug = 3
|
||||
for name in z.namelist():
|
||||
if name.endswith('/'):
|
||||
continue
|
||||
zipped_file = z.open( name )
|
||||
while 1:
|
||||
try:
|
||||
chunk = zipped_file.read( CHUNK_SIZE )
|
||||
except IOError:
|
||||
os.close( fd )
|
||||
log.error( 'Problem decompressing zipped data' )
|
||||
return self.app.model.DeferredJob.states.INVALID
|
||||
if not chunk:
|
||||
break
|
||||
os.write( fd, chunk )
|
||||
zipped_file.close()
|
||||
os.close( fd )
|
||||
z.close()
|
||||
elif data_type == 'fasta':
|
||||
uncompressed = transfer.path
|
||||
else:
|
||||
job.state = self.app.model.DeferredJob.states.INVALID
|
||||
log.error( "Unrecognized compression format for file %s." % transfer.path )
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.flush()
|
||||
return
|
||||
shutil.move( uncompressed, destfilepath )
|
||||
if os.path.exists( transfer.path ):
|
||||
os.remove( transfer.path )
|
||||
os.chmod( destfilepath, 0644 )
|
||||
fastaline = '\t'.join( [ dbkey, dbkey, params[ 'intname' ], os.path.abspath( destfilepath ) ] )
|
||||
self._add_line( 'all_fasta', fastaline )
|
||||
if params[ 'indexes' ] is not None:
|
||||
job.state = self.app.model.DeferredJob.states.WAITING
|
||||
job.params[ 'indexjobs' ] = []
|
||||
else:
|
||||
job.state = self.app.model.DeferredJob.states.OK
|
||||
job.params[ 'type' ] = 'finish_transfer'
|
||||
transfer.path = os.path.abspath(destfilepath)
|
||||
transfer.state = 'done'
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.add( transfer )
|
||||
if transfer.state == 'done':
|
||||
if params[ 'indexes' ] is not None:
|
||||
for indexer in params[ 'indexes' ]:
|
||||
incoming = dict(indexer=indexer, dbkey=params[ 'dbkey' ], intname=params[ 'intname' ], path=transfer.path, user=params['user'] )
|
||||
deferred = self.tool.execute( self, set_output_hid=False, history=None, incoming=incoming, transfer=transfer, deferred=job )
|
||||
job.params[ 'indexjobs' ].append( deferred[0].id )
|
||||
else:
|
||||
job.state = self.app.model.DeferredJob.states.OK
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.flush()
|
||||
return self.app.model.DeferredJob.states.OK
|
||||
|
||||
def _check_compress( self, filepath ):
|
||||
retval = ''
|
||||
if tarfile.is_tarfile( filepath ):
|
||||
retval = 'tar.'
|
||||
if check_zip( filepath ):
|
||||
return 'zip'
|
||||
is_bzipped, is_valid = check_bz2( filepath )
|
||||
if is_bzipped and is_valid:
|
||||
return retval + 'bzip'
|
||||
is_gzipped, is_valid = check_gzip( filepath )
|
||||
if is_gzipped and is_valid:
|
||||
return retval + 'gzip'
|
||||
return None
|
||||
|
||||
def _add_line( self, locfile, newline ):
|
||||
filepath = self.locations[ locfile ]
|
||||
origlines = []
|
||||
output = []
|
||||
comments = []
|
||||
with open( filepath, 'r' ) as destfile:
|
||||
for line in destfile:
|
||||
if line.startswith( '#' ):
|
||||
comments.append( line.strip() )
|
||||
else:
|
||||
origlines.append( line.strip() )
|
||||
if newline not in origlines:
|
||||
origlines.append( newline )
|
||||
output.extend( comments )
|
||||
origlines.sort()
|
||||
output.extend( origlines )
|
||||
with open( filepath, 'w+' ) as destfile:
|
||||
destfile.write( '\n'.join( output ) )
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
"""
|
||||
Module for managing genome transfer jobs.
|
||||
"""
|
||||
from __future__ import with_statement
|
||||
|
||||
import logging, shutil, gzip, tempfile, sys
|
||||
|
||||
from galaxy import eggs
|
||||
from sqlalchemy import and_
|
||||
|
||||
from galaxy.util.odict import odict
|
||||
from galaxy.workflow.modules import module_factory
|
||||
from galaxy.jobs.actions.post import ActionBox
|
||||
|
||||
from galaxy.tools.parameters import visit_input_values
|
||||
from galaxy.tools.parameters.basic import DataToolParameter
|
||||
|
||||
from galaxy.datatypes.checkers import *
|
||||
|
||||
from data_transfer import *
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
__all__ = [ 'LiftOverTransferPlugin' ]
|
||||
|
||||
class LiftOverTransferPlugin( DataTransfer ):
|
||||
|
||||
locations = {}
|
||||
|
||||
def __init__( self, app ):
|
||||
super( LiftOverTransferPlugin, self ).__init__( app )
|
||||
self.app = app
|
||||
self.sa_session = app.model.context.current
|
||||
|
||||
def create_job( self, trans, url, dbkey, from_genome, to_genome, destfile, parentjob ):
|
||||
job = trans.app.transfer_manager.new( protocol='http', url=url )
|
||||
params = dict( user=trans.user.id, transfer_job_id=job.id, protocol='http',
|
||||
type='init_transfer', dbkey=dbkey, from_genome=from_genome,
|
||||
to_genome=to_genome, destfile=destfile, parentjob=parentjob )
|
||||
deferred = trans.app.model.DeferredJob( state = self.app.model.DeferredJob.states.NEW, plugin = 'LiftOverTransferPlugin', params = params )
|
||||
self.sa_session.add( deferred )
|
||||
self.sa_session.flush()
|
||||
return deferred.id
|
||||
|
||||
def check_job( self, job ):
|
||||
if job.params['type'] == 'init_transfer':
|
||||
if not hasattr(job, 'transfer_job'):
|
||||
job.transfer_job = self.sa_session.query( self.app.model.TransferJob ).get( int( job.params[ 'transfer_job_id' ] ) )
|
||||
else:
|
||||
self.sa_session.refresh( job.transfer_job )
|
||||
if job.transfer_job.state == 'done':
|
||||
transfer = job.transfer_job
|
||||
transfer.state = 'downloaded'
|
||||
job.params['type'] = 'extract_transfer'
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.add( transfer )
|
||||
self.sa_session.flush()
|
||||
return self.job_states.READY
|
||||
elif job.transfer_job.state == 'running':
|
||||
return self.job_states.WAIT
|
||||
elif job.transfer_job.state == 'new':
|
||||
assert job.params[ 'protocol' ] in [ 'http', 'ftp', 'https' ], 'Unknown protocol %s' % job.params[ 'protocol' ]
|
||||
ready = True
|
||||
parent = self.sa_session.query( self.app.model.DeferredJob ).get( int( job.params[ 'parentjob' ] ) )
|
||||
if not hasattr( parent, 'transfer_job' ):
|
||||
parent.transfer_job = self.sa_session.query( self.app.model.TransferJob ).get( int( parent.params[ 'transfer_job_id' ] ) )
|
||||
if parent.transfer_job.state not in [ 'ok', 'error', 'done' ]:
|
||||
ready = False
|
||||
for lo_job in parent.params[ 'liftover' ]:
|
||||
liftoverjob = self.sa_session.query( self.app.model.TransferJob ).get( int( lo_job ) )
|
||||
if liftoverjob:
|
||||
if liftoverjob.state not in [ 'ok', 'error', 'new', 'done' ]:
|
||||
ready = False
|
||||
if ready:
|
||||
self.app.transfer_manager.run( job.transfer_job )
|
||||
self.sa_session.add( job.transfer_job )
|
||||
self.sa_session.flush()
|
||||
return self.job_states.WAIT
|
||||
else:
|
||||
log.error( "An error occurred while downloading from %s" % job.transfer_job.params[ 'url' ] )
|
||||
return self.job_states.INVALID
|
||||
elif job.params[ 'type' ] == 'extract_transfer':
|
||||
return self.job_states.READY
|
||||
|
||||
def get_job_status( self, jobid ):
|
||||
job = self.sa_session.query( self.app.model.DeferredJob ).get( int( jobid ) )
|
||||
return job
|
||||
|
||||
def run_job( self, job ):
|
||||
params = job.params
|
||||
dbkey = params[ 'dbkey' ]
|
||||
source = params[ 'from_genome' ]
|
||||
target = params[ 'to_genome' ]
|
||||
if not hasattr( job, 'transfer_job' ):
|
||||
job.transfer_job = self.sa_session.query( self.app.model.TransferJob ).get( int( job.params[ 'transfer_job_id' ] ) )
|
||||
else:
|
||||
self.sa_session.refresh( job.transfer_job )
|
||||
transfer = job.transfer_job
|
||||
if params[ 'type' ] == 'extract_transfer':
|
||||
CHUNK_SIZE = 2**20
|
||||
destpath = os.path.join( self.app.config.get( 'genome_data_path', 'tool-data/genome' ), source, 'liftOver' )
|
||||
if not os.path.exists( destpath ):
|
||||
os.makedirs( destpath )
|
||||
destfile = job.params[ 'destfile' ]
|
||||
destfilepath = os.path.join( destpath, destfile )
|
||||
tmpprefix = '%s_%s_download_unzip_' % ( job.params['dbkey'], job.params[ 'transfer_job_id' ] )
|
||||
tmppath = os.path.dirname( os.path.abspath( transfer.path ) )
|
||||
if not os.path.exists( destpath ):
|
||||
os.makedirs( destpath )
|
||||
fd, uncompressed = tempfile.mkstemp( prefix=tmpprefix, dir=tmppath, text=False )
|
||||
chain = gzip.open( transfer.path, 'rb' )
|
||||
while 1:
|
||||
try:
|
||||
chunk = chain.read( CHUNK_SIZE )
|
||||
except IOError:
|
||||
os.close( fd )
|
||||
log.error( 'Problem decompressing compressed data' )
|
||||
exit()
|
||||
if not chunk:
|
||||
break
|
||||
os.write( fd, chunk )
|
||||
os.close( fd )
|
||||
chain.close()
|
||||
# Replace the gzipped file with the decompressed file if it's safe to do so
|
||||
shutil.move( uncompressed, destfilepath )
|
||||
os.remove( transfer.path )
|
||||
os.chmod( destfilepath, 0644 )
|
||||
locline = '\t'.join( [ source, target, os.path.abspath( destfilepath ) ] )
|
||||
self._add_line( locline )
|
||||
job.state = self.app.model.DeferredJob.states.OK
|
||||
job.params[ 'type' ] = 'finish_transfer'
|
||||
transfer.path = os.path.abspath(destfilepath)
|
||||
transfer.state = 'done'
|
||||
parentjob = self.sa_session.query( self.app.model.DeferredJob ).get( int( job.params[ 'parentjob' ] ) )
|
||||
finished = True
|
||||
for i in parentjob.params[ 'liftover' ]:
|
||||
sibling = self.sa_session.query( self.app.model.DeferredJob ).get( int( i ) )
|
||||
if sibling.state not in [ 'done', 'ok', 'error' ]:
|
||||
finished = False
|
||||
if finished:
|
||||
parentjob.state = self.app.model.DeferredJob.states.OK
|
||||
self.sa_session.add( parentjob )
|
||||
self.sa_session.add( job )
|
||||
self.sa_session.add( transfer )
|
||||
self.sa_session.flush()
|
||||
return self.app.model.DeferredJob.states.OK
|
||||
|
||||
def _add_line( self, newline ):
|
||||
filepath = 'tool-data/liftOver.loc'
|
||||
origlines = []
|
||||
with open( filepath, 'r' ) as destfile:
|
||||
for line in destfile:
|
||||
origlines.append( line.strip() )
|
||||
if newline not in origlines:
|
||||
origlines.append( newline )
|
||||
with open( filepath, 'w+' ) as destfile:
|
||||
destfile.write( '\n'.join( origlines ) )
|
||||
|
||||
@@ -83,7 +83,7 @@ class JobRunnerMapper( object ):
|
||||
if "job" in function_arg_names or "user" in function_arg_names or "user_email" in function_arg_names or "resource_params" in function_arg_names:
|
||||
job = self.job_wrapper.get_job()
|
||||
history = job.history
|
||||
user = history and history.user
|
||||
user = job.user
|
||||
user_email = user and str(user.email)
|
||||
|
||||
if "job" in function_arg_names:
|
||||
|
||||
@@ -15,4 +15,4 @@ def seconds_to_str( value ):
|
||||
elif value < 3600:
|
||||
return "%s minutes" % ( value / 60 )
|
||||
else:
|
||||
return "%s days and %s minutes" % ( value / 3600, ( value % 3600 ) / 60 )
|
||||
return "%s hours and %s minutes" % ( value / 3600, ( value % 3600 ) / 60 )
|
||||
|
||||
@@ -1,4 +1,33 @@
|
||||
""" 'Business logic' independent of web transactions/user context (trans)
|
||||
should be pushed into models - but logic that requires the context trans
|
||||
should be placed under this module.
|
||||
"""
|
||||
Classes that manage resources (models, tools, etc.) by using the current
|
||||
Transaction.
|
||||
|
||||
Encapsulates the intersection of trans (or trans.sa_session), models,
|
||||
and Controllers.
|
||||
|
||||
Responsibilities:
|
||||
model operations that involve the trans/sa_session (CRUD)
|
||||
security:
|
||||
ownership, accessibility
|
||||
common aspect-oriented operations via new mixins:
|
||||
sharable, annotatable, tagable, ratable
|
||||
|
||||
Not responsible for:
|
||||
encoding/decoding ids
|
||||
any http gobblygook
|
||||
formatting of returned data (always python structures)
|
||||
formatting of raised errors
|
||||
|
||||
The goal is to have Controllers only handle:
|
||||
query-string/payload parsing and encoding/decoding ids
|
||||
http
|
||||
return formatting
|
||||
|
||||
and:
|
||||
control, improve namespacing in Controllers
|
||||
DRY for Controller ops (define here - use in both UI/API Controllers)
|
||||
|
||||
In other words, 'Business logic' independent of web transactions/user context
|
||||
(trans) should be pushed into models - but logic that requires the context
|
||||
trans should be placed under this module.
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
class ModelManager( object ):
|
||||
pass
|
||||
|
||||
class ModelSerializer( object ):
|
||||
pass
|
||||
+179
-5
@@ -1,16 +1,35 @@
|
||||
"""
|
||||
Manager and Serializer for HDAs.
|
||||
|
||||
HistoryDatasetAssociations (HDAs) are datasets contained or created in a
|
||||
history.
|
||||
"""
|
||||
|
||||
from galaxy import exceptions
|
||||
from ..managers import histories
|
||||
|
||||
from galaxy.managers import base as manager_base
|
||||
from galaxy.managers import histories as history_manager
|
||||
|
||||
class HDAManager( object ):
|
||||
import galaxy.web
|
||||
import galaxy.datatypes.metadata
|
||||
from galaxy import objectstore
|
||||
|
||||
class HDAManager( manager_base.ModelManager ):
|
||||
"""
|
||||
Interface/service object for interacting with HDAs.
|
||||
"""
|
||||
|
||||
def __init__( self ):
|
||||
self.histories_mgr = histories.HistoryManager()
|
||||
"""
|
||||
Set up and initialize other managers needed by hdas.
|
||||
"""
|
||||
self.histories_mgr = history_manager.HistoryManager()
|
||||
|
||||
def get( self, trans, unencoded_id, check_ownership=True, check_accessible=True ):
|
||||
"""
|
||||
Get an HDA by its unencoded db id, checking ownership (via its history)
|
||||
or accessibility (via dataset shares/permissions).
|
||||
"""
|
||||
# this is a replacement for UsesHistoryDatasetAssociationMixin because mixins are a bad soln/structure
|
||||
hda = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( unencoded_id )
|
||||
if hda is None:
|
||||
raise exceptions.ObjectNotFound()
|
||||
@@ -19,7 +38,8 @@ class HDAManager( object ):
|
||||
|
||||
def secure( self, trans, hda, check_ownership=True, check_accessible=True ):
|
||||
"""
|
||||
checks if (a) user owns item or (b) item is accessible to user.
|
||||
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():
|
||||
@@ -31,12 +51,18 @@ class HDAManager( object ):
|
||||
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 ):
|
||||
"""
|
||||
Use history to see if current user owns HDA.
|
||||
"""
|
||||
if not trans.user:
|
||||
#if hda.history == trans.history:
|
||||
# return hda
|
||||
@@ -51,6 +77,9 @@ class HDAManager( object ):
|
||||
"HistoryDatasetAssociation is not owned by the current user", type='error' )
|
||||
|
||||
def check_accessible( self, trans, hda ):
|
||||
"""
|
||||
Raise error if HDA is not accessible.
|
||||
"""
|
||||
if trans.user and trans.user_is_admin():
|
||||
return hda
|
||||
# check for access of the containing history...
|
||||
@@ -62,6 +91,151 @@ class HDAManager( object ):
|
||||
"HistoryDatasetAssociation is not accessible to the current user", type='error' )
|
||||
|
||||
def err_if_uploading( self, trans, hda ):
|
||||
"""
|
||||
Raise error if HDA is still uploading.
|
||||
"""
|
||||
if hda.state == trans.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"
|
||||
|
||||
# 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
|
||||
|
||||
#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
|
||||
})
|
||||
|
||||
def get_display_apps( self, trans, hda ):
|
||||
"""
|
||||
Return dictionary containing new-style display app urls.
|
||||
"""
|
||||
display_apps = []
|
||||
for display_app in hda.get_display_applications( trans ).itervalues():
|
||||
|
||||
app_links = []
|
||||
for link_app in display_app.links.itervalues():
|
||||
app_links.append({
|
||||
'target': link_app.url.get( 'target_frame', '_blank' ),
|
||||
'href' : link_app.get_display_url( hda, trans ),
|
||||
'text' : gettext( link_app.name )
|
||||
})
|
||||
if app_links:
|
||||
display_apps.append( dict( label=display_app.name, links=app_links ) )
|
||||
|
||||
return display_apps
|
||||
|
||||
def get_old_display_applications( self, trans, hda ):
|
||||
"""
|
||||
Return dictionary containing old-style display app urls.
|
||||
"""
|
||||
display_apps = []
|
||||
if not trans.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 )
|
||||
|
||||
if len( display_links ) > 0:
|
||||
display_label = hda.datatype.get_display_label( display_app )
|
||||
|
||||
app_links = []
|
||||
for display_name, display_link in display_links:
|
||||
app_links.append({
|
||||
'target': target_frame,
|
||||
'href' : display_link,
|
||||
'text' : gettext( display_name )
|
||||
})
|
||||
if app_links:
|
||||
display_apps.append( dict( label=display_label, links=app_links ) )
|
||||
|
||||
return display_apps
|
||||
|
||||
|
||||
# =============================================================================
|
||||
class HistorySerializer( manager_base.ModelSerializer ):
|
||||
"""
|
||||
Interface/service object for serializing HDAs into dictionaries.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -1,9 +1,32 @@
|
||||
"""
|
||||
Manager and Serializer for histories.
|
||||
|
||||
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.hdas
|
||||
|
||||
import galaxy.web
|
||||
import galaxy.dataset_collections.util
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
# =============================================================================
|
||||
class HistoryManager( manager_base.ModelManager ):
|
||||
"""
|
||||
Interface/service object for interacting with HDAs.
|
||||
"""
|
||||
|
||||
class HistoryManager( object ):
|
||||
#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 )
|
||||
|
||||
def get( self, trans, unencoded_id, check_ownership=True, check_accessible=True, deleted=None ):
|
||||
"""
|
||||
@@ -44,7 +67,7 @@ class HistoryManager( object ):
|
||||
|
||||
def secure( self, trans, history, check_ownership=True, check_accessible=True ):
|
||||
"""
|
||||
checks if (a) user owns item or (b) item is accessible to user.
|
||||
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():
|
||||
@@ -56,15 +79,28 @@ class HistoryManager( object ):
|
||||
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 ):
|
||||
"""
|
||||
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 ):
|
||||
@@ -74,6 +110,9 @@ class HistoryManager( object ):
|
||||
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():
|
||||
return True
|
||||
@@ -88,6 +127,103 @@ class HistoryManager( object ):
|
||||
return False
|
||||
|
||||
def check_accessible( self, trans, history ):
|
||||
"""
|
||||
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' )
|
||||
|
||||
#TODO: bleh...
|
||||
def _get_history_data( self, trans, history ):
|
||||
"""
|
||||
Returns a dictionary containing ``history`` and ``contents``, serialized
|
||||
history and an array of serialized history contents respectively.
|
||||
"""
|
||||
hda_mgr = galaxy.managers.hdas.HDAManager()
|
||||
collection_dictifier = galaxy.dataset_collections.util.dictify_dataset_collection_instance
|
||||
|
||||
history_dictionary = {}
|
||||
contents_dictionaries = []
|
||||
try:
|
||||
#for content in history.contents_iter( **contents_kwds ):
|
||||
for content in history.contents_iter( types=[ 'dataset', 'dataset_collection' ] ):
|
||||
hda_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 ) )
|
||||
|
||||
elif isinstance( content, trans.app.model.HistoryDatasetCollectionAssociation ):
|
||||
try:
|
||||
service = trans.app.dataset_collections_service
|
||||
dataset_collection_instance = service.get_dataset_collection_instance(
|
||||
trans=trans,
|
||||
instance_type='history',
|
||||
id=trans.security.encode_id( content.id ),
|
||||
)
|
||||
hda_dict = collection_dictifier( dataset_collection_instance,
|
||||
security=trans.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 )
|
||||
|
||||
except Exception, exc:
|
||||
user_id = str( trans.user.id ) if trans.user else '(anonymous)'
|
||||
log.exception( 'Error bootstrapping history for user %s: %s', user_id, str( exc ) )
|
||||
message = ( 'An error occurred getting the history data from the server. '
|
||||
+ 'Please contact a Galaxy administrator if the problem persists.' )
|
||||
history_dictionary[ 'error' ] = message
|
||||
|
||||
return {
|
||||
'history' : history_dictionary,
|
||||
'contents' : contents_dictionaries
|
||||
}
|
||||
|
||||
def get_history_dict( self, trans, history, contents_dictionaries=None ):
|
||||
"""
|
||||
Returns history data in the form of a dictionary.
|
||||
"""
|
||||
#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 )
|
||||
|
||||
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: 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
|
||||
|
||||
#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 )
|
||||
|
||||
return history_dict
|
||||
|
||||
|
||||
# =============================================================================
|
||||
class HistorySerializer( manager_base.ModelSerializer ):
|
||||
"""
|
||||
Interface/service object for serializing histories into dictionaries.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -68,7 +68,9 @@ class ToolShedRepository( object ):
|
||||
|
||||
@property
|
||||
def can_deactivate( self ):
|
||||
return self.status not in [ self.installation_status.DEACTIVATED, self.installation_status.UNINSTALLED ]
|
||||
return self.status not in [ self.installation_status.DEACTIVATED,
|
||||
self.installation_status.ERROR,
|
||||
self.installation_status.UNINSTALLED ]
|
||||
|
||||
@property
|
||||
def can_reinstall_or_activate( self ):
|
||||
|
||||
@@ -11,6 +11,7 @@ import pipes
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import string
|
||||
import tempfile
|
||||
import threading
|
||||
import traceback
|
||||
@@ -214,8 +215,7 @@ class ToolBox( object, Dictifiable ):
|
||||
config_elems = []
|
||||
else:
|
||||
parsing_shed_tool_conf = False
|
||||
# Default to backward compatible config setting.
|
||||
tool_path = self.tool_root_dir
|
||||
tool_path = self.__resolve_tool_path(tool_path, config_filename)
|
||||
# Only load the panel_dict under certain conditions.
|
||||
load_panel_dict = not self.integrated_tool_panel_config_has_contents
|
||||
for _, elem in enumerate( root ):
|
||||
@@ -243,6 +243,17 @@ class ToolBox( object, Dictifiable ):
|
||||
return shed_config_dict
|
||||
return default
|
||||
|
||||
def __resolve_tool_path(self, tool_path, config_filename):
|
||||
if not tool_path:
|
||||
# Default to backward compatible config setting.
|
||||
tool_path = self.tool_root_dir
|
||||
else:
|
||||
# Allow use of __tool_conf_dir__ in toolbox config files.
|
||||
tool_conf_dir = os.path.dirname(config_filename)
|
||||
tool_path_vars = {"tool_conf_dir": tool_conf_dir}
|
||||
tool_path = string.Template(tool_path).safe_substitute(tool_path_vars)
|
||||
return tool_path
|
||||
|
||||
def __add_tool_to_tool_panel( self, tool, panel_component, section=False ):
|
||||
# See if a version of this tool is already loaded into the tool panel. The value of panel_component
|
||||
# will be a ToolSection (if the value of section=True) or self.tool_panel (if section=False).
|
||||
@@ -2496,13 +2507,16 @@ class Tool( object, Dictifiable ):
|
||||
incoming_value = get_incoming_value( incoming, key, None )
|
||||
value, error = check_param( trans, input, incoming_value, context, source=source )
|
||||
# If a callback was provided, allow it to process the value
|
||||
input_name = input.name
|
||||
if item_callback:
|
||||
old_value = state.get( input.name, None )
|
||||
old_value = state.get( input_name, None )
|
||||
value, error = item_callback( trans, key, input, value, error, old_value, context )
|
||||
if error:
|
||||
errors[ input.name ] = error
|
||||
state[ input.name ] = value
|
||||
state.update( self.__meta_properties_for_state( key, incoming, incoming_value, value ) )
|
||||
errors[ input_name ] = error
|
||||
|
||||
state[ input_name ] = value
|
||||
meta_properties = self.__meta_properties_for_state( key, incoming, incoming_value, value, input_name )
|
||||
state.update( meta_properties )
|
||||
return errors
|
||||
|
||||
def __remove_meta_properties( self, incoming ):
|
||||
@@ -2516,12 +2530,17 @@ class Tool( object, Dictifiable ):
|
||||
del result[ key ]
|
||||
return result
|
||||
|
||||
def __meta_properties_for_state( self, key, incoming, incoming_val, state_val ):
|
||||
def __meta_properties_for_state( self, key, incoming, incoming_val, state_val, input_name ):
|
||||
meta_properties = {}
|
||||
multirun_key = "%s|__multirun__" % key
|
||||
if multirun_key in incoming:
|
||||
multi_value = incoming[ multirun_key ]
|
||||
meta_properties[ multirun_key ] = multi_value
|
||||
meta_property_suffixes = [
|
||||
"__multirun__",
|
||||
"__collection_multirun__",
|
||||
]
|
||||
for meta_property_suffix in meta_property_suffixes:
|
||||
multirun_key = "%s|%s" % ( key, meta_property_suffix )
|
||||
if multirun_key in incoming:
|
||||
multi_value = incoming[ multirun_key ]
|
||||
meta_properties[ "%s|%s" % ( input_name, meta_property_suffix ) ] = multi_value
|
||||
return meta_properties
|
||||
|
||||
@property
|
||||
@@ -2915,12 +2934,12 @@ class Tool( object, Dictifiable ):
|
||||
self.sa_session.flush()
|
||||
return children
|
||||
|
||||
def collect_primary_datasets( self, output, job_working_directory ):
|
||||
def collect_primary_datasets( self, output, job_working_directory, input_ext ):
|
||||
"""
|
||||
Find any additional datasets generated by a tool and attach (for
|
||||
cases where number of outputs is not known in advance).
|
||||
"""
|
||||
return output_collect.collect_primary_datatasets( self, output, job_working_directory )
|
||||
return output_collect.collect_primary_datasets( self, output, job_working_directory, input_ext )
|
||||
|
||||
def to_dict( self, trans, link_details=False, io_details=False ):
|
||||
""" Returns dict of tool. """
|
||||
@@ -3194,7 +3213,9 @@ class DataManagerTool( OutputParameterJSONTool ):
|
||||
|
||||
# Populate tool_type to ToolClass mappings
|
||||
tool_types = {}
|
||||
for tool_class in [ Tool, DataDestinationTool, SetMetadataTool, DataSourceTool, AsyncDataSourceTool, DataManagerTool ]:
|
||||
for tool_class in [ Tool, SetMetadataTool, OutputParameterJSONTool,
|
||||
DataManagerTool, DataSourceTool, AsyncDataSourceTool,
|
||||
DataDestinationTool ]:
|
||||
tool_types[ tool_class.tool_type ] = tool_class
|
||||
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import tempfile
|
||||
from __init__ import ToolAction
|
||||
from galaxy.util.odict import odict
|
||||
from galaxy.tools.genome_index import *
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
class GenomeIndexToolAction( ToolAction ):
|
||||
"""Tool action used for exporting a history to an archive. """
|
||||
|
||||
def execute( self, tool, trans, *args, **kwargs ):
|
||||
#
|
||||
# Get genome to index.
|
||||
#
|
||||
incoming = kwargs['incoming']
|
||||
#
|
||||
# Create the job and output dataset objects
|
||||
#
|
||||
job = trans.app.model.Job()
|
||||
job.tool_id = tool.id
|
||||
job.user_id = incoming['user']
|
||||
start_job_state = job.state # should be job.states.NEW
|
||||
job.state = job.states.WAITING # we need to set job state to something other than NEW,
|
||||
# or else when tracking jobs in db it will be picked up
|
||||
# before we have added input / output parameters
|
||||
trans.sa_session.add( job )
|
||||
|
||||
# Create dataset that will serve as archive.
|
||||
temp_dataset = trans.app.model.Dataset( state=trans.app.model.Dataset.states.NEW )
|
||||
trans.sa_session.add( temp_dataset )
|
||||
|
||||
trans.sa_session.flush() # ensure job.id and archive_dataset.id are available
|
||||
trans.app.object_store.create( temp_dataset ) # set the object store id, create dataset (because galaxy likes having datasets)
|
||||
|
||||
#
|
||||
# Setup job and job wrapper.
|
||||
#
|
||||
|
||||
# Add association for keeping track of index jobs, transfer jobs, and so on.
|
||||
user = trans.sa_session.query( trans.app.model.User ).get( int( incoming['user'] ) )
|
||||
assoc = trans.app.model.GenomeIndexToolData( job=job, dataset=temp_dataset, fasta_path=incoming['path'], \
|
||||
indexer=incoming['indexer'], user=user, \
|
||||
deferred_job=kwargs['deferred'], transfer_job=kwargs['transfer'] )
|
||||
trans.sa_session.add( assoc )
|
||||
|
||||
job_wrapper = GenomeIndexToolWrapper( job )
|
||||
cmd_line = job_wrapper.setup_job( assoc )
|
||||
|
||||
#
|
||||
# Add parameters to job_parameter table.
|
||||
#
|
||||
incoming[ '__GENOME_INDEX_COMMAND__' ] = cmd_line
|
||||
for name, value in tool.params_to_strings( incoming, trans.app ).iteritems():
|
||||
job.add_parameter( name, value )
|
||||
|
||||
job.state = start_job_state # job inputs have been configured, restore initial job state
|
||||
job.set_handler(tool.get_job_handler(None))
|
||||
trans.sa_session.flush()
|
||||
|
||||
|
||||
# Queue the job for execution
|
||||
trans.app.job_queue.put( job.id, tool.id )
|
||||
log.info( "Added genome index job to the job queue, id: %s" % str( job.id ) )
|
||||
|
||||
return job, odict()
|
||||
|
||||
@@ -190,20 +190,32 @@ class DockerContainer(Container):
|
||||
volumes_raw = self.__expand_str(self.destination_info.get("docker_volumes", "$defaults"))
|
||||
# TODO: Remove redundant volumes...
|
||||
volumes = docker_util.DockerVolume.volumes_from_str(volumes_raw)
|
||||
return docker_util.build_docker_run_command(
|
||||
command,
|
||||
self.container_id,
|
||||
volumes=volumes,
|
||||
env_directives=env_directives,
|
||||
working_directory=working_directory,
|
||||
volumes_from = self.destination_info.get("docker_volumes_from", docker_util.DEFAULT_VOLUMES_FROM)
|
||||
|
||||
docker_host_props = dict(
|
||||
docker_cmd=prop("cmd", docker_util.DEFAULT_DOCKER_COMMAND),
|
||||
sudo=asbool(prop("sudo", docker_util.DEFAULT_SUDO)),
|
||||
sudo_cmd=prop("sudo_cmd", docker_util.DEFAULT_SUDO_COMMAND),
|
||||
host=prop("host", docker_util.DEFAULT_HOST),
|
||||
net=prop("net", "none") # By default, docker instance has networking disabled
|
||||
)
|
||||
|
||||
cache_command = docker_util.build_docker_cache_command(self.container_id, **docker_host_props)
|
||||
run_command = docker_util.build_docker_run_command(
|
||||
command,
|
||||
self.container_id,
|
||||
volumes=volumes,
|
||||
volumes_from=volumes_from,
|
||||
env_directives=env_directives,
|
||||
working_directory=working_directory,
|
||||
net=prop("net", "none"), # By default, docker instance has networking disabled
|
||||
**docker_host_props
|
||||
)
|
||||
return "%s\n%s" % (cache_command, run_command)
|
||||
|
||||
def __expand_str(self, value):
|
||||
if not value:
|
||||
return value
|
||||
|
||||
template = string.Template(value)
|
||||
variables = dict()
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ DEFAULT_VOLUME_MOUNT_TYPE = "rw"
|
||||
DEFAULT_WORKING_DIRECTORY = None
|
||||
DEFAULT_NET = None
|
||||
DEFAULT_MEMORY = None
|
||||
DEFAULT_VOLUMES_FROM = None
|
||||
|
||||
|
||||
class DockerVolume(object):
|
||||
@@ -49,32 +50,48 @@ class DockerVolume(object):
|
||||
return ":".join([self.from_path, self.to_path, self.how])
|
||||
|
||||
|
||||
def build_docker_cache_command(
|
||||
image,
|
||||
docker_cmd=DEFAULT_DOCKER_COMMAND,
|
||||
sudo=DEFAULT_SUDO,
|
||||
sudo_cmd=DEFAULT_SUDO_COMMAND,
|
||||
host=DEFAULT_HOST,
|
||||
):
|
||||
inspect_command_parts = __docker_prefix(docker_cmd, sudo, sudo_cmd, host)
|
||||
inspect_command_parts.extend(["inspect", image])
|
||||
inspect_image_command = " ".join(inspect_command_parts)
|
||||
|
||||
pull_command_parts = __docker_prefix(docker_cmd, sudo, sudo_cmd, host)
|
||||
pull_command_parts.extend(["pull", image])
|
||||
pull_image_command = " ".join(pull_command_parts)
|
||||
cache_command = "%s > /dev/null 2>&1\n[ $? -ne 0 ] && %s > /dev/null 2>&1\n" % (inspect_image_command, pull_image_command)
|
||||
return cache_command
|
||||
|
||||
|
||||
def build_docker_run_command(
|
||||
container_command,
|
||||
image,
|
||||
tag=None,
|
||||
docker_cmd=DEFAULT_DOCKER_COMMAND,
|
||||
volumes=[],
|
||||
volumes_from=DEFAULT_VOLUMES_FROM,
|
||||
memory=DEFAULT_MEMORY,
|
||||
env_directives=[],
|
||||
working_directory=DEFAULT_WORKING_DIRECTORY,
|
||||
name=None,
|
||||
net=DEFAULT_NET,
|
||||
docker_cmd=DEFAULT_DOCKER_COMMAND,
|
||||
sudo=DEFAULT_SUDO,
|
||||
sudo_cmd=DEFAULT_SUDO_COMMAND,
|
||||
name=None,
|
||||
host=DEFAULT_HOST,
|
||||
net=DEFAULT_NET,
|
||||
):
|
||||
command_parts = []
|
||||
if sudo:
|
||||
command_parts.append(sudo_cmd)
|
||||
command_parts.append(docker_cmd)
|
||||
if host:
|
||||
command_parts.append(["-H", host])
|
||||
command_parts = __docker_prefix(docker_cmd, sudo, sudo_cmd, host)
|
||||
command_parts.append("run")
|
||||
for env_directive in env_directives:
|
||||
command_parts.extend(["-e", env_directive])
|
||||
for volume in volumes:
|
||||
command_parts.extend(["-v", str(volume)])
|
||||
if volumes_from:
|
||||
command_parts.extend(["--volumes-from", str(volumes_from)])
|
||||
if memory:
|
||||
command_parts.extend(["-m", memory])
|
||||
if name:
|
||||
@@ -89,3 +106,15 @@ def build_docker_run_command(
|
||||
command_parts.append(full_image)
|
||||
command_parts.append(container_command)
|
||||
return " ".join(command_parts)
|
||||
|
||||
|
||||
def __docker_prefix(docker_cmd, sudo, sudo_cmd, host):
|
||||
""" Prefix to issue a docker command.
|
||||
"""
|
||||
command_parts = []
|
||||
if sudo:
|
||||
command_parts.append(sudo_cmd)
|
||||
command_parts.append(docker_cmd)
|
||||
if host:
|
||||
command_parts.append(["-H", host])
|
||||
return command_parts
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import tarfile
|
||||
import tempfile
|
||||
|
||||
from galaxy import model, util
|
||||
from galaxy.web.framework.helpers import to_unicode
|
||||
from galaxy.model.item_attrs import UsesAnnotations
|
||||
from galaxy.util.json import *
|
||||
from galaxy.web.base.controller import UsesHistoryMixin
|
||||
from galaxy.tools.data import ToolDataTableManager
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def load_genome_index_tools( toolbox ):
|
||||
""" Adds tools for indexing genomes via the main job runner. """
|
||||
# Create XML for loading the tool.
|
||||
tool_xml_text = """
|
||||
<tool id="__GENOME_INDEX__" name="Index Genome" version="0.1" tool_type="genome_index">
|
||||
<type class="GenomeIndexTool" module="galaxy.tools"/>
|
||||
<action module="galaxy.tools.actions.index_genome" class="GenomeIndexToolAction"/>
|
||||
<command>$__GENOME_INDEX_COMMAND__ $output_file $output_file.files_path "$__app__.config.rsync_url" "$__app__.config.tool_data_path"</command>
|
||||
<inputs>
|
||||
<param name="__GENOME_INDEX_COMMAND__" type="hidden"/>
|
||||
</inputs>
|
||||
<outputs>
|
||||
<data format="txt" name="output_file"/>
|
||||
</outputs>
|
||||
<stdio>
|
||||
<exit_code range="1:" err_level="fatal" />
|
||||
</stdio>
|
||||
</tool>
|
||||
"""
|
||||
|
||||
# Load index tool.
|
||||
tmp_name = tempfile.NamedTemporaryFile()
|
||||
tmp_name.write( tool_xml_text )
|
||||
tmp_name.flush()
|
||||
genome_index_tool = toolbox.load_tool( tmp_name.name )
|
||||
toolbox.tools_by_id[ genome_index_tool.id ] = genome_index_tool
|
||||
log.debug( "Loaded genome index tool: %s", genome_index_tool.id )
|
||||
|
||||
class GenomeIndexToolWrapper( object ):
|
||||
""" Provides support for performing jobs that index a genome. """
|
||||
def __init__( self, job_id ):
|
||||
self.locations = dict()
|
||||
self.job_id = job_id
|
||||
|
||||
def setup_job( self, genobj ):
|
||||
""" Perform setup for job to index a genome and return an archive. Method generates
|
||||
attribute files, sets the corresponding attributes in the associated database
|
||||
object, and returns a command line for running the job. The command line
|
||||
includes the command, inputs, and options; it does not include the output
|
||||
file because it must be set at runtime. """
|
||||
|
||||
#
|
||||
# Create and return command line for running tool.
|
||||
#
|
||||
scriptpath = os.path.join( os.path.abspath( os.getcwd() ), "lib/galaxy/tools/genome_index/index_genome.py" )
|
||||
return "python %s %s %s" % ( scriptpath, genobj.indexer, genobj.fasta_path )
|
||||
|
||||
def postprocessing( self, sa_session, app ):
|
||||
""" Finish the job, move the finished indexes to their final resting place,
|
||||
and update the .loc files where applicable. """
|
||||
gitd = sa_session.query( model.GenomeIndexToolData ).filter_by( job_id=self.job_id ).first()
|
||||
indexdirs = dict( bfast='bfast_index', bowtie='bowtie_index', bowtie2='bowtie2_index',
|
||||
bwa='bwa_index', perm='perm_%s_index', picard='srma_index', sam='sam_index' )
|
||||
|
||||
|
||||
if gitd:
|
||||
fp = open( gitd.dataset.get_file_name(), 'r' )
|
||||
deferred = sa_session.query( model.DeferredJob ).filter_by( id=gitd.deferred_job_id ).first()
|
||||
try:
|
||||
logloc = json.load( fp )
|
||||
except ValueError:
|
||||
deferred.state = app.model.DeferredJob.states.ERROR
|
||||
sa_session.add( deferred )
|
||||
sa_session.flush()
|
||||
log.debug( 'Indexing job failed, setting deferred job state to error.' )
|
||||
return False
|
||||
finally:
|
||||
fp.close()
|
||||
destination = None
|
||||
tdtman = ToolDataTableManager( app.config.tool_data_path )
|
||||
xmltree = tdtman.load_from_config_file( app.config.tool_data_table_config_path, app.config.tool_data_path )
|
||||
for node in xmltree:
|
||||
table = node.get('name')
|
||||
location = node.findall('file')[0].get('path')
|
||||
self.locations[table] = os.path.abspath( location )
|
||||
locbase = os.path.abspath( os.path.split( self.locations['all_fasta'] )[0] )
|
||||
params = deferred.params
|
||||
dbkey = params[ 'dbkey' ]
|
||||
basepath = os.path.join( os.path.abspath( app.config.genome_data_path ), dbkey )
|
||||
intname = params[ 'intname' ]
|
||||
indexer = gitd.indexer
|
||||
workingdir = os.path.abspath( gitd.dataset.extra_files_path )
|
||||
location = []
|
||||
indexdata = gitd.dataset.extra_files_path
|
||||
if indexer == '2bit':
|
||||
indexdata = os.path.join( workingdir, '%s.2bit' % dbkey )
|
||||
destination = os.path.join( basepath, 'seq', '%s.2bit' % dbkey )
|
||||
location.append( dict( line='\t'.join( [ 'seq', dbkey, destination ] ), file= os.path.join( locbase, 'alignseq.loc' ) ) )
|
||||
elif indexer == 'bowtie':
|
||||
self._ex_tar( workingdir, 'cs.tar' )
|
||||
destination = os.path.join( basepath, 'bowtie_index' )
|
||||
for var in [ 'nt', 'cs' ]:
|
||||
for line in logloc[ var ]:
|
||||
idx = line
|
||||
if var == 'nt':
|
||||
locfile = self.locations[ 'bowtie_indexes' ]
|
||||
locdir = os.path.join( destination, idx )
|
||||
else:
|
||||
locfile = self.locations[ 'bowtie_indexes_color' ]
|
||||
locdir = os.path.join( destination, var, idx )
|
||||
location.append( dict( line='\t'.join( [ dbkey, dbkey, intname, locdir ] ), file=locfile ) )
|
||||
elif indexer == 'bowtie2':
|
||||
destination = os.path.join( basepath, 'bowtie2_index' )
|
||||
for line in logloc[ 'nt' ]:
|
||||
idx = line
|
||||
locfile = self.locations[ 'bowtie2_indexes' ]
|
||||
locdir = os.path.join( destination, idx )
|
||||
location.append( dict( line='\t'.join( [ dbkey, dbkey, intname, locdir ] ), file=locfile ) )
|
||||
elif indexer == 'bwa':
|
||||
self._ex_tar( workingdir, 'cs.tar' )
|
||||
destination = os.path.join( basepath, 'bwa_index' )
|
||||
for var in [ 'nt', 'cs' ]:
|
||||
for line in logloc[ var ]:
|
||||
idx = line
|
||||
if var == 'nt':
|
||||
locfile = self.locations[ 'bwa_indexes' ]
|
||||
locdir = os.path.join( destination, idx )
|
||||
else:
|
||||
locfile = self.locations[ 'bwa_indexes_color' ]
|
||||
locdir = os.path.join( destination, var, idx )
|
||||
location.append( dict( line='\t'.join( [ dbkey, dbkey, intname, locdir ] ), file=locfile ) )
|
||||
elif indexer == 'perm':
|
||||
self._ex_tar( workingdir, 'cs.tar' )
|
||||
destination = os.path.join( basepath, 'perm_index' )
|
||||
for var in [ 'nt', 'cs' ]:
|
||||
for line in logloc[ var ]:
|
||||
idx = line.pop()
|
||||
if var == 'nt':
|
||||
locfile = self.locations[ 'perm_base_indexes' ]
|
||||
locdir = os.path.join( destination, idx )
|
||||
else:
|
||||
locfile = self.locations[ 'perm_color_indexes' ]
|
||||
locdir = os.path.join( destination, var, idx )
|
||||
line.append( locdir )
|
||||
location.append( dict( line='\t'.join( line ), file=locfile ) )
|
||||
elif indexer == 'picard':
|
||||
destination = os.path.join( basepath, 'srma_index' )
|
||||
for var in [ 'nt' ]:
|
||||
for line in logloc[ var ]:
|
||||
idx = line
|
||||
locfile = self.locations[ 'picard_indexes' ]
|
||||
locdir = os.path.join( destination, idx )
|
||||
location.append( dict( line='\t'.join( [ dbkey, dbkey, intname, locdir ] ), file=locfile ) )
|
||||
elif indexer == 'sam':
|
||||
destination = os.path.join( basepath, 'sam_index' )
|
||||
for var in [ 'nt' ]:
|
||||
for line in logloc[ var ]:
|
||||
locfile = self.locations[ 'sam_fa_indexes' ]
|
||||
locdir = os.path.join( destination, line )
|
||||
location.append( dict( line='\t'.join( [ 'index', dbkey, locdir ] ), file=locfile ) )
|
||||
|
||||
if destination is not None and os.path.exists( os.path.split( destination )[0] ) and not os.path.exists( destination ):
|
||||
log.debug( 'Moving %s to %s' % ( indexdata, destination ) )
|
||||
shutil.move( indexdata, destination )
|
||||
if indexer not in [ '2bit' ]:
|
||||
genome = '%s.fa' % dbkey
|
||||
target = os.path.join( destination, genome )
|
||||
fasta = os.path.abspath( os.path.join( basepath, 'seq', genome ) )
|
||||
self._check_link( fasta, target )
|
||||
if os.path.exists( os.path.join( destination, 'cs' ) ):
|
||||
target = os.path.join( destination, 'cs', genome )
|
||||
fasta = os.path.abspath( os.path.join( basepath, 'seq', genome ) )
|
||||
self._check_link( fasta, target )
|
||||
for line in location:
|
||||
self._add_line( line[ 'file' ], line[ 'line' ] )
|
||||
deferred.state = app.model.DeferredJob.states.OK
|
||||
sa_session.add( deferred )
|
||||
sa_session.flush()
|
||||
|
||||
|
||||
def _check_link( self, targetfile, symlink ):
|
||||
target = os.path.relpath( targetfile, os.path.dirname( symlink ) )
|
||||
filename = os.path.basename( targetfile )
|
||||
if not os.path.exists( targetfile ): # this should never happen.
|
||||
raise Exception, "%s not found. Unable to proceed without a FASTA file. Aborting." % targetfile
|
||||
if os.path.exists( symlink ) and os.path.islink( symlink ):
|
||||
if os.path.realpath( symlink ) == os.path.abspath( targetfile ): # symlink exists, points to the correct FASTA file.
|
||||
return
|
||||
else: # no it doesn't. Make a new one, and this time do it right.
|
||||
os.remove( symlink )
|
||||
os.symlink( target, symlink )
|
||||
return
|
||||
elif not os.path.exists( symlink ): # no symlink to the FASTA file. Create one.
|
||||
os.symlink( target, symlink )
|
||||
return
|
||||
elif os.path.exists( symlink ) and not os.path.islink( symlink ):
|
||||
if self._hash_file( targetfile ) == self._hash_file( symlink ): # files are identical. No need to panic.
|
||||
return
|
||||
else:
|
||||
if os.path.getsize( symlink ) == 0: # somehow an empty file got copied instead of the symlink. Delete with extreme prejudice.
|
||||
os.remove( symlink )
|
||||
os.symlink( target, symlink )
|
||||
return
|
||||
else:
|
||||
raise Exception, "Regular file %s exists, is not empty, contents do not match %s." % ( symlink, targetfile )
|
||||
|
||||
def _hash_file( self, filename ):
|
||||
import hashlib
|
||||
md5 = hashlib.md5()
|
||||
with open( filename, 'rb' ) as f:
|
||||
for chunk in iter( lambda: f.read( 8192 ), '' ):
|
||||
md5.update( chunk )
|
||||
return md5.digest()
|
||||
|
||||
|
||||
def _ex_tar( self, directory, filename ):
|
||||
fh = tarfile.open( os.path.join( directory, filename ) )
|
||||
fh.extractall( path=directory )
|
||||
fh.close()
|
||||
os.remove( os.path.join( directory, filename ) )
|
||||
|
||||
def _add_line( self, locfile, newline ):
|
||||
filepath = locfile
|
||||
origlines = []
|
||||
output = []
|
||||
comments = []
|
||||
with open( filepath, 'r' ) as destfile:
|
||||
for line in destfile:
|
||||
origlines.append( line.strip() )
|
||||
if newline not in origlines:
|
||||
origlines.append( newline )
|
||||
with open( filepath, 'w+' ) as destfile:
|
||||
origlines.append( '' )
|
||||
destfile.write( '\n'.join( origlines ) )
|
||||
@@ -1,327 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Export a history to an archive file using attribute files.
|
||||
|
||||
usage: %prog history_attrs dataset_attrs job_attrs out_file
|
||||
-G, --gzip: gzip archive file
|
||||
"""
|
||||
from __future__ import with_statement
|
||||
|
||||
import json
|
||||
import optparse
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
|
||||
class ManagedIndexer():
|
||||
def __init__( self, output_file, infile, workingdir, rsync_url, tooldata ):
|
||||
self.tooldatapath = os.path.abspath( tooldata )
|
||||
self.workingdir = os.path.abspath( workingdir )
|
||||
self.outfile = open( os.path.abspath( output_file ), 'w' )
|
||||
self.basedir = os.path.split( self.workingdir )[0]
|
||||
self.fasta = os.path.abspath( infile )
|
||||
self.locations = dict( nt=[], cs=[] )
|
||||
self.log = []
|
||||
self.rsync_opts = '-aclSzq'
|
||||
self.rsync_url = rsync_url
|
||||
self.indexers = {
|
||||
'bwa': '_bwa',
|
||||
'bowtie': '_bowtie',
|
||||
'bowtie2': '_bowtie2',
|
||||
'2bit': '_twobit',
|
||||
'perm': '_perm',
|
||||
'bfast': '_bfast',
|
||||
'picard': '_picard',
|
||||
'sam': '_sam'
|
||||
}
|
||||
if not os.path.exists( self.workingdir ):
|
||||
os.makedirs( self.workingdir )
|
||||
self.logfile = open( os.path.join( self.workingdir, 'ManagedIndexer.log' ), 'w+' )
|
||||
|
||||
def run_indexer( self, indexer ):
|
||||
self.fapath = self.fasta
|
||||
self.fafile = os.path.basename( self.fapath )
|
||||
self.genome = os.path.splitext( self.fafile )[0]
|
||||
with WithChDir( self.basedir ):
|
||||
if indexer not in self.indexers:
|
||||
sys.stderr.write( 'The requested indexing function does not exist' )
|
||||
exit(127)
|
||||
else:
|
||||
with WithChDir( self.workingdir ):
|
||||
self._log( 'Running indexer %s.' % indexer )
|
||||
result = getattr( self, self.indexers[ indexer ] )()
|
||||
if result in [ None, False ]:
|
||||
sys.stderr.write( 'Error running indexer %s, %s' % ( indexer, result ) )
|
||||
self._flush_files()
|
||||
exit(1)
|
||||
else:
|
||||
self._log( self.locations )
|
||||
self._log( 'Indexer %s completed successfully.' % indexer )
|
||||
self._flush_files()
|
||||
exit(0)
|
||||
|
||||
def _check_link( self ):
|
||||
self._log( 'Checking symlink to %s' % self.fafile )
|
||||
if not os.path.exists( self.fafile ):
|
||||
self._log( 'Symlink not found, creating' )
|
||||
os.symlink( os.path.relpath( self.fapath ), self.fafile )
|
||||
|
||||
def _do_rsync( self, idxpath ):
|
||||
self._log( 'Trying rsync at %s/%s%s' % ( self.rsync_url, self.genome, idxpath ) )
|
||||
result = subprocess.call( shlex.split( 'rsync %s %s/%s%s .' % ( self.rsync_opts, self.rsync_url, self.genome, idxpath ) ), stderr=self.logfile )
|
||||
if result != 0:
|
||||
self._log( 'Rsync failed or index not found. Generating.' )
|
||||
else:
|
||||
self._log( 'Rsync succeeded.' )
|
||||
return result
|
||||
|
||||
def _flush_files( self ):
|
||||
json.dump( self.locations, self.outfile )
|
||||
self.outfile.close()
|
||||
self.logfile.close()
|
||||
|
||||
def _log( self, stuff ):
|
||||
timestamp = time.strftime('%Y-%m-%d %H:%M:%S %z')
|
||||
self.logfile.write( "[%s] %s\n" % (timestamp, stuff) )
|
||||
|
||||
def _bwa( self ):
|
||||
result = self._do_rsync( '/bwa_index/' )
|
||||
if result == 0:
|
||||
self.locations[ 'nt' ].append( self.fafile )
|
||||
return self._bwa_cs()
|
||||
else:
|
||||
self._check_link()
|
||||
command = shlex.split( 'bwa index -a bwtsw %s' % self.fafile )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result != 0:
|
||||
newcommand = shlex.split( 'bwa index -c %s' % self.fafile )
|
||||
result = call( newcommand, stderr=self.logfile, stdout=self.logfile )
|
||||
if result == 0:
|
||||
self.locations[ 'nt' ].append( self.fafile )
|
||||
os.remove( self.fafile )
|
||||
return self._bwa_cs()
|
||||
else:
|
||||
self._log( 'BWA (base) exited with code %s' % result )
|
||||
return False
|
||||
|
||||
def _bwa_cs( self ):
|
||||
if not os.path.exists( os.path.join( self.workingdir, 'cs' ) ):
|
||||
os.makedirs( 'cs' )
|
||||
with WithChDir( 'cs' ):
|
||||
self._check_link()
|
||||
command = shlex.split( 'bwa index -a bwtsw -c %s' % self.fafile )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result != 0:
|
||||
newcommand = shlex.split( 'bwa index -c %s' % self.fafile )
|
||||
result = call( newcommand, stderr=self.logfile, stdout=self.logfile )
|
||||
if result == 0:
|
||||
self.locations[ 'cs' ].append( self.fafile )
|
||||
os.remove( self.fafile )
|
||||
else:
|
||||
self._log( 'BWA (color) exited with code %s' % result )
|
||||
return False
|
||||
else:
|
||||
self.locations[ 'cs' ].append( self.fafile )
|
||||
os.remove( self.fafile )
|
||||
else:
|
||||
self.locations[ 'cs' ].append( self.fafile )
|
||||
temptar = tarfile.open( 'cs.tar', 'w' )
|
||||
temptar.add( 'cs' )
|
||||
temptar.close()
|
||||
shutil.rmtree( 'cs' )
|
||||
return True
|
||||
|
||||
|
||||
def _bowtie( self ):
|
||||
result = self._do_rsync( '/bowtie_index/' )
|
||||
if result == 0:
|
||||
self.locations[ 'nt' ].append( self.genome )
|
||||
return self._bowtie_cs()
|
||||
else:
|
||||
self._check_link()
|
||||
command = shlex.split( 'bowtie-build -f %s %s' % ( self.fafile, self.genome ) )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result == 0:
|
||||
self.locations[ 'nt' ].append( self.genome )
|
||||
os.remove( self.fafile )
|
||||
return self._bowtie_cs()
|
||||
else:
|
||||
self._log( 'Bowtie (base) exited with code %s' % result )
|
||||
return False
|
||||
|
||||
def _bowtie_cs( self ):
|
||||
indexdir = os.path.join( os.getcwd(), 'cs' )
|
||||
if not ( os.path.exists( indexdir ) ):
|
||||
os.makedirs( indexdir )
|
||||
with WithChDir( indexdir ):
|
||||
self._check_link()
|
||||
command = shlex.split( 'bowtie-build -C -f %s %s' % ( self.fafile, self.genome ) )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result == 0:
|
||||
self.locations[ 'cs' ].append( self.genome )
|
||||
else:
|
||||
self._log( 'Bowtie (color) exited with code %s' % result )
|
||||
return False
|
||||
os.remove( os.path.join( indexdir, self.fafile ) )
|
||||
else:
|
||||
self.locations[ 'cs' ].append( self.genome )
|
||||
temptar = tarfile.open( 'cs.tar', 'w' )
|
||||
temptar.add( 'cs' )
|
||||
temptar.close()
|
||||
shutil.rmtree( 'cs' )
|
||||
return True
|
||||
|
||||
|
||||
def _bowtie2( self ):
|
||||
result = self._do_rsync( '/bowtie2_index/' )
|
||||
if result == 0:
|
||||
self.locations[ 'nt' ].append( self.fafile )
|
||||
return True
|
||||
ref_base = os.path.splitext(self.fafile)[0]
|
||||
self._check_link()
|
||||
command = shlex.split( 'bowtie2-build %s %s' % ( self.fafile, ref_base ) )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result == 0:
|
||||
self.locations[ 'nt' ].append( ref_base )
|
||||
os.remove( self.fafile )
|
||||
return True
|
||||
else:
|
||||
self._log( 'Bowtie2 exited with code %s' % result )
|
||||
return False
|
||||
|
||||
def _twobit( self ):
|
||||
"""Index reference files using 2bit for random access.
|
||||
"""
|
||||
result = self._do_rsync( '/seq/%s.2bit' % self.genome )
|
||||
if result == 0:
|
||||
self.locations['nt'].append( "%s.2bit" % self.genome )
|
||||
return True
|
||||
else:
|
||||
out_file = "%s.2bit" % self.genome
|
||||
self._check_link()
|
||||
command = shlex.split( 'faToTwoBit %s %s' % ( self.fafile, out_file ) )
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result == 0:
|
||||
self.locations['nt'].append( out_file )
|
||||
os.remove( self.fafile )
|
||||
return True
|
||||
else:
|
||||
self._log( 'faToTwoBit exited with code %s' % result )
|
||||
return False
|
||||
|
||||
def _perm( self ):
|
||||
result = self._do_rsync( '/perm_index/' )
|
||||
self._check_link()
|
||||
genome = self.genome
|
||||
read_length = 50
|
||||
for seed in [ 'F3', 'F4' ]:
|
||||
key = '%s_%s_%s' % (self.genome, seed, read_length)
|
||||
desc = '%s: seed=%s, read length=%s' % (self.genome, seed, read_length)
|
||||
index = "%s_base_%s_%s.index" % (self.genome, seed, read_length)
|
||||
if not os.path.exists( index ):
|
||||
command = shlex.split("PerM %s %s --readFormat fastq --seed %s -m -s %s" % (self.fafile, read_length, seed, index))
|
||||
result = subprocess.call( command )
|
||||
if result != 0:
|
||||
self._log( 'PerM (base) exited with code %s' % result )
|
||||
return False
|
||||
self.locations[ 'nt' ].append( [ key, desc, index ] )
|
||||
os.remove( self.fafile )
|
||||
return self._perm_cs()
|
||||
|
||||
def _perm_cs( self ):
|
||||
genome = self.genome
|
||||
read_length = 50
|
||||
if not os.path.exists( 'cs' ):
|
||||
os.makedirs( 'cs' )
|
||||
with WithChDir( 'cs' ):
|
||||
self._check_link()
|
||||
for seed in [ 'F3', 'F4' ]:
|
||||
key = '%s_%s_%s' % (genome, seed, read_length)
|
||||
desc = '%s: seed=%s, read length=%s' % (genome, seed, read_length)
|
||||
index = "%s_color_%s_%s.index" % (genome, seed, read_length)
|
||||
if not os.path.exists( index ):
|
||||
command = shlex.split("PerM %s %s --readFormat csfastq --seed %s -m -s %s" % (self.fafile, read_length, seed, index))
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result != 0:
|
||||
self._log( 'PerM (color) exited with code %s' % result )
|
||||
return False
|
||||
self.locations[ 'cs' ].append( [ key, desc, index ] )
|
||||
os.remove( self.fafile )
|
||||
temptar = tarfile.open( 'cs.tar', 'w' )
|
||||
temptar.add( 'cs' )
|
||||
temptar.close()
|
||||
shutil.rmtree( 'cs' )
|
||||
return True
|
||||
|
||||
def _picard( self ):
|
||||
result = self._do_rsync( '/srma_index/' )
|
||||
if result == 0 and os.path.exists( '%s.dict' % self.genome):
|
||||
self.locations[ 'nt' ].append( self.fafile )
|
||||
return True
|
||||
local_ref = self.fafile
|
||||
srma = os.path.abspath( os.path.join( self.tooldatapath, 'shared/jars/picard/CreateSequenceDictionary.jar' ) )
|
||||
genome = os.path.splitext( self.fafile )[0]
|
||||
self._check_link()
|
||||
if not os.path.exists( '%s.fai' % self.fafile ) and not os.path.exists( '%s.fai' % self.genome ):
|
||||
command = shlex.split( 'samtools faidx %s' % self.fafile )
|
||||
subprocess.call( command, stderr=self.logfile )
|
||||
command = shlex.split( "java -jar %s R=%s O=%s.dict URI=%s" \
|
||||
% ( srma, local_ref, genome, local_ref ) )
|
||||
if not os.path.exists( '%s.dict' % self.genome ):
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
self._log( ' '.join( command ) )
|
||||
if result != 0:
|
||||
self._log( 'Picard exited with code %s' % result )
|
||||
return False
|
||||
self.locations[ 'nt' ].append( self.fafile )
|
||||
os.remove( self.fafile )
|
||||
return True
|
||||
|
||||
def _sam( self ):
|
||||
local_ref = self.fafile
|
||||
local_file = os.path.splitext( self.fafile )[ 0 ]
|
||||
print 'Trying rsync'
|
||||
result = self._do_rsync( '/sam_index/' )
|
||||
if result == 0 and ( os.path.exists( '%s.fai' % self.fafile ) or os.path.exists( '%s.fai' % self.genome ) ):
|
||||
self.locations[ 'nt' ].append( '%s.fai' % local_ref )
|
||||
return True
|
||||
self._check_link()
|
||||
print 'Trying indexer'
|
||||
command = shlex.split("samtools faidx %s" % local_ref)
|
||||
result = subprocess.call( command, stderr=self.logfile, stdout=self.logfile )
|
||||
if result != 0:
|
||||
self._log( 'SAM exited with code %s' % result )
|
||||
return False
|
||||
else:
|
||||
self.locations[ 'nt' ].append( '%s.fai' % local_ref )
|
||||
os.remove( local_ref )
|
||||
return True
|
||||
|
||||
class WithChDir():
|
||||
def __init__( self, target ):
|
||||
self.working = target
|
||||
self.previous = os.getcwd()
|
||||
def __enter__( self ):
|
||||
os.chdir( self.working )
|
||||
def __exit__( self, *args ):
|
||||
os.chdir( self.previous )
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Parse command line.
|
||||
parser = optparse.OptionParser()
|
||||
(options, args) = parser.parse_args()
|
||||
indexer, infile, outfile, working_dir, rsync_url, tooldata = args
|
||||
|
||||
# Create archive.
|
||||
idxobj = ManagedIndexer( outfile, infile, working_dir, rsync_url, tooldata )
|
||||
returncode = idxobj.run_indexer( indexer )
|
||||
if not returncode:
|
||||
exit(1)
|
||||
exit(0)
|
||||
@@ -14,7 +14,7 @@ DATASET_ID_TOKEN = "DATASET_ID"
|
||||
DEFAULT_EXTRA_FILENAME_PATTERN = r"primary_DATASET_ID_(?P<designation>[^_]+)_(?P<visible>[^_]+)_(?P<ext>[^_]+)(_(?P<dbkey>[^_]+))?"
|
||||
|
||||
|
||||
def collect_primary_datatasets( tool, output, job_working_directory ):
|
||||
def collect_primary_datasets( tool, output, job_working_directory, input_ext ):
|
||||
app = tool.app
|
||||
sa_session = tool.sa_session
|
||||
new_primary_datasets = {}
|
||||
@@ -66,6 +66,8 @@ def collect_primary_datatasets( tool, output, job_working_directory ):
|
||||
designation = fields_match.designation
|
||||
visible = fields_match.visible
|
||||
ext = fields_match.ext
|
||||
if ext == "input":
|
||||
ext = input_ext
|
||||
dbkey = fields_match.dbkey
|
||||
# Create new primary dataset
|
||||
primary_data = app.model.HistoryDatasetAssociation( extension=ext,
|
||||
@@ -98,13 +100,36 @@ def collect_primary_datatasets( tool, output, job_working_directory ):
|
||||
sa_session.flush()
|
||||
primary_data.state = outdata.state
|
||||
#add tool/metadata provided information
|
||||
new_primary_datasets_attributes = new_primary_datasets.get( os.path.split( filename )[-1] )
|
||||
new_primary_datasets_attributes = new_primary_datasets.get( os.path.split( filename )[-1], {} )
|
||||
if new_primary_datasets_attributes:
|
||||
dataset_att_by_name = dict( ext='extension' )
|
||||
for att_set in [ 'name', 'info', 'ext', 'dbkey' ]:
|
||||
dataset_att_name = dataset_att_by_name.get( att_set, att_set )
|
||||
setattr( primary_data, dataset_att_name, new_primary_datasets_attributes.get( att_set, getattr( primary_data, dataset_att_name ) ) )
|
||||
primary_data.set_meta()
|
||||
extra_files_path = new_primary_datasets_attributes.get( 'extra_files', None )
|
||||
if extra_files_path:
|
||||
extra_files_path_joined = os.path.join( job_working_directory, extra_files_path )
|
||||
for root, dirs, files in os.walk( extra_files_path_joined ):
|
||||
extra_dir = os.path.join( primary_data.extra_files_path, root.replace( extra_files_path_joined, '', 1 ).lstrip( os.path.sep ) )
|
||||
for f in files:
|
||||
app.object_store.update_from_file( primary_data.dataset,
|
||||
extra_dir=extra_dir,
|
||||
alt_name=f,
|
||||
file_name=os.path.join( root, f ),
|
||||
create=True,
|
||||
dir_only=True,
|
||||
preserve_symlinks=True
|
||||
)
|
||||
# FIXME:
|
||||
# since these are placed into the job working dir, let the standard
|
||||
# Galaxy cleanup methods handle this (for now?)
|
||||
# there was an extra_files_path dir, attempt to remove it
|
||||
#shutil.rmtree( extra_files_path_joined )
|
||||
metadata_dict = new_primary_datasets_attributes.get( 'metadata', None )
|
||||
if metadata_dict:
|
||||
primary_data.metadata.from_JSON_dict( json_dict=metadata_dict )
|
||||
else:
|
||||
primary_data.set_meta()
|
||||
primary_data.set_peek()
|
||||
sa_session.add( primary_data )
|
||||
sa_session.flush()
|
||||
|
||||
@@ -10,7 +10,6 @@ import binascii
|
||||
import collections
|
||||
import errno
|
||||
import grp
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
@@ -24,6 +23,8 @@ import sys
|
||||
import tempfile
|
||||
import threading
|
||||
|
||||
from galaxy.util import json
|
||||
|
||||
from email.MIMEText import MIMEText
|
||||
|
||||
from os.path import relpath
|
||||
@@ -312,8 +313,8 @@ def shrink_string_by_size( value, size, join_by="..", left_larger=True, beginnin
|
||||
|
||||
def pretty_print_json(json_data, is_json_string=False):
|
||||
if is_json_string:
|
||||
json_data = json.loads(json_data)
|
||||
return json.dumps(json_data, sort_keys=True, indent=4 * ' ')
|
||||
json_data = json.from_json_string(json_data)
|
||||
return json.to_json_string(json_data, sort_keys=True, indent=4)
|
||||
|
||||
# characters that are valid
|
||||
valid_chars = set(string.letters + string.digits + " -=_.()/+*^,:?!")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from galaxy import web, util
|
||||
from galaxy.web.base.controller import BaseAPIController
|
||||
from galaxy.web.framework.helpers import is_true
|
||||
from galaxy.webapps.galaxy.controllers.data_admin import build_param_dict as massage
|
||||
|
||||
def get_id( base, format ):
|
||||
if format:
|
||||
@@ -41,77 +40,3 @@ class GenomesController( BaseAPIController ):
|
||||
else:
|
||||
rval = self.app.genomes.chroms( trans, dbkey=id, num=num, chrom=chrom, low=low )
|
||||
return rval
|
||||
|
||||
@web.expose_api
|
||||
def create( self, trans, payload, **kwd ):
|
||||
"""
|
||||
POST /api/genomes
|
||||
Download and/or index a genome.
|
||||
|
||||
Parameters::
|
||||
liftover None or array of liftover url partial paths
|
||||
dbkey DB key of the build to download
|
||||
indexers POST array of indexers to run after downloading (indexers[] = first, indexers[] = second, ...)
|
||||
longname a more descriptive name for the genome
|
||||
func Allowed values:
|
||||
'download' Download and index
|
||||
'index' Index only
|
||||
Returns::
|
||||
If no error:
|
||||
dict( status: 'ok', job: <job ID> )
|
||||
|
||||
If error:
|
||||
dict( status: 'error', error: <error message> )
|
||||
|
||||
"""
|
||||
#??: Planned?
|
||||
#Parameters::
|
||||
# dbkey DB key of the build to download, ignored unless 'UCSC' is specified as the source
|
||||
# ncbi_name NCBI's genome identifier, ignored unless NCBI is specified as the source
|
||||
# ensembl_dbkey Ensembl's genome identifier, ignored unless Ensembl is specified as the source
|
||||
# url_dbkey DB key to use for this build, ignored unless URL is specified as the source
|
||||
# source Data source for this build. Can be: UCSC, Ensembl, NCBI, URL
|
||||
# indexers POST array of indexers to run after downloading (indexers[] = first, indexers[] = second, ...)
|
||||
# func Allowed values:
|
||||
# 'download' Download and index
|
||||
# 'index' Index only
|
||||
params = util.Params( payload )
|
||||
paramdict = massage( params, trans )
|
||||
func = params.get( 'func', 'download' )
|
||||
if func == 'download':
|
||||
url = paramdict[ 'url' ]
|
||||
liftover = paramdict[ 'liftover' ]
|
||||
dbkey = paramdict[ 'dbkey' ]
|
||||
indexers = paramdict[ 'indexers' ]
|
||||
longname = paramdict[ 'longname' ]
|
||||
jobid = trans.app.job_manager.deferred_job_queue.plugins['GenomeTransferPlugin'].create_job( trans, url, dbkey, longname, indexers )
|
||||
chainjob = []
|
||||
if liftover is not None:
|
||||
for chain in liftover:
|
||||
liftover_url = u'ftp://hgdownload.cse.ucsc.edu%s' % chain[0]
|
||||
from_genome = chain[1]
|
||||
to_genome = chain[2]
|
||||
destfile = liftover_url.split('/')[-1].replace('.gz', '')
|
||||
lochain = trans.app.job_manager.deferred_job_queue.plugins['LiftOverTransferPlugin'].create_job( trans, liftover_url, dbkey, from_genome, to_genome, destfile, jobid )
|
||||
chainjob.append( lochain )
|
||||
job = trans.app.job_manager.deferred_job_queue.plugins['GenomeTransferPlugin'].get_job_status( jobid )
|
||||
job.params['liftover'] = chainjob
|
||||
trans.app.model.context.current.flush()
|
||||
return dict( status='ok', job=jobid )
|
||||
elif func == 'index':
|
||||
dbkey = paramdict[ 'dbkey' ]
|
||||
indexer = [ params.get( 'indexer', None ) ]
|
||||
longname = None
|
||||
path = None
|
||||
for build in trans.app.tool_data_tables.data_tables[ 'all_fasta' ].data:
|
||||
if build[0] == dbkey:
|
||||
longname = build[2]
|
||||
path = build[3]
|
||||
break
|
||||
if longname is not None and indexer is not None and path is not None:
|
||||
jobid = trans.app.job_manager.deferred_job_queue.plugins['GenomeIndexPlugin'].create_job( trans, path, indexer, dbkey, longname )
|
||||
return dict( status='ok', job=jobid )
|
||||
else:
|
||||
return dict( status='error', error='Build not %s found in tool data table.' % dbkey )
|
||||
else:
|
||||
return dict( status='error', error='Unkown function selected.' )
|
||||
|
||||
@@ -201,7 +201,7 @@ class HistoriesController( BaseAPIController, UsesHistoryMixin, UsesTagsMixin,
|
||||
delete( self, trans, id, **kwd )
|
||||
* DELETE /api/histories/{id}
|
||||
delete the history with the given ``id``
|
||||
.. note:: Currently does not stop any active jobs in the history.
|
||||
.. note:: Stops all active jobs in the history if purge is set.
|
||||
|
||||
:type id: str
|
||||
:param id: the encoded id of the history to delete
|
||||
@@ -235,6 +235,10 @@ class HistoriesController( BaseAPIController, UsesHistoryMixin, UsesTagsMixin,
|
||||
for hda in history.datasets:
|
||||
if hda.purged:
|
||||
continue
|
||||
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 )
|
||||
hda.purged = True
|
||||
trans.sa_session.add( hda )
|
||||
trans.sa_session.flush()
|
||||
|
||||
@@ -92,7 +92,7 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
|
||||
else:
|
||||
types = [ 'dataset', "dataset_collection" ]
|
||||
|
||||
contents_kwds = {'types': types}
|
||||
contents_kwds = { 'types': types }
|
||||
if ids:
|
||||
ids = map( lambda id: trans.security.decode_id( id ), ids.split( ',' ) )
|
||||
contents_kwds[ 'ids' ] = ids
|
||||
@@ -110,14 +110,14 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
|
||||
details = util.listify( details )
|
||||
|
||||
for content in history.contents_iter( **contents_kwds ):
|
||||
if isinstance(content, trans.app.model.HistoryDatasetAssociation):
|
||||
if isinstance( content, trans.app.model.HistoryDatasetAssociation ):
|
||||
encoded_content_id = trans.security.encode_id( content.id )
|
||||
detailed = details == 'all' or ( encoded_content_id in details )
|
||||
if detailed:
|
||||
rval.append( self._detailed_hda_dict( trans, content ) )
|
||||
else:
|
||||
rval.append( self._summary_hda_dict( trans, history_id, content ) )
|
||||
elif isinstance(content, trans.app.model.HistoryDatasetCollectionAssociation):
|
||||
elif isinstance( content, trans.app.model.HistoryDatasetCollectionAssociation ):
|
||||
rval.append( self.__collection_dict( trans, content ) )
|
||||
return rval
|
||||
|
||||
@@ -149,7 +149,8 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
|
||||
}
|
||||
|
||||
def __collection_dict( self, trans, dataset_collection_instance, view="collection" ):
|
||||
return dictify_dataset_collection_instance( dataset_collection_instance, security=trans.security, parent=dataset_collection_instance.history, view=view )
|
||||
return dictify_dataset_collection_instance( dataset_collection_instance,
|
||||
security=trans.security, parent=dataset_collection_instance.history, view=view )
|
||||
|
||||
def _detailed_hda_dict( self, trans, hda ):
|
||||
"""
|
||||
@@ -201,8 +202,7 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
|
||||
)
|
||||
return self.__collection_dict( trans, dataset_collection_instance, view="element" )
|
||||
except Exception, e:
|
||||
msg = "Error in history API at listing dataset collection: %s" % ( str(e) )
|
||||
log.error( msg, exc_info=True )
|
||||
log.exception( "Error in history API at listing dataset collection: %s", e )
|
||||
trans.response.status = 500
|
||||
return msg
|
||||
|
||||
|
||||
@@ -102,7 +102,6 @@ class AdminToolshed( AdminGalaxy ):
|
||||
action='reset_to_install',
|
||||
**kwd ) )
|
||||
if operation == "purge":
|
||||
kwd[ 'purge_repository' ] = True
|
||||
return trans.response.send_redirect( web.url_for( controller='admin_toolshed',
|
||||
action='purge_repository',
|
||||
**kwd ) )
|
||||
@@ -1270,14 +1269,21 @@ class AdminToolshed( AdminGalaxy ):
|
||||
if repository_id is not None:
|
||||
repository = suc.get_installed_tool_shed_repository( trans.app, repository_id )
|
||||
if repository:
|
||||
if kwd.get( 'purge_repository', False ):
|
||||
irm = install_manager.InstallRepositoryManager( trans.app )
|
||||
purge_status, purge_message = irm.purge_repository( trans.app, repository )
|
||||
if purge_status == 'ok':
|
||||
new_kwd[ 'status' ] = "done"
|
||||
if repository.is_new:
|
||||
if kwd.get( 'purge_repository_button', False ):
|
||||
irm = trans.app.installed_repository_manager
|
||||
purge_status, purge_message = irm.purge_repository( repository )
|
||||
if purge_status == 'ok':
|
||||
new_kwd[ 'status' ] = "done"
|
||||
else:
|
||||
new_kwd[ 'status' ] = 'error'
|
||||
new_kwd[ 'message' ] = purge_message
|
||||
else:
|
||||
new_kwd[ 'status' ] = 'error'
|
||||
new_kwd[ 'message' ] = purge_message
|
||||
return trans.fill_template( 'admin/tool_shed_repository/purge_repository_confirmation.mako',
|
||||
repository=repository )
|
||||
else:
|
||||
new_kwd[ 'status' ] = 'error'
|
||||
new_kwd[ 'message' ] = 'Repositories must have a <b>New</b> status in order to be purged.'
|
||||
else:
|
||||
new_kwd[ 'status' ] = 'error'
|
||||
new_kwd[ 'message' ] = 'Cannot locate the database record for the repository with encoded id %s.' % str( repository_id )
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
import ftplib
|
||||
import json
|
||||
import sys
|
||||
from galaxy import model, util
|
||||
from galaxy.jobs import transfer_manager
|
||||
from galaxy.model.orm import *
|
||||
from galaxy.web.base.controller import *
|
||||
from galaxy.web.framework.helpers import grids, iff, time_ago
|
||||
from library_common import get_comptypes, lucene_search, whoosh_search
|
||||
|
||||
|
||||
# Older py compatibility
|
||||
try:
|
||||
set()
|
||||
except:
|
||||
from sets import Set as set
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
class DataAdmin( BaseUIController ):
|
||||
jobstyles = dict(
|
||||
done='panel-done-message',
|
||||
waiting='state-color-waiting',
|
||||
running='state-color-running',
|
||||
downloaded='state-color-running',
|
||||
new='state-color-new',
|
||||
ok='panel-done-message',
|
||||
error='panel-error-message',
|
||||
queued='state-color-waiting'
|
||||
)
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def manage_data( self, trans, **kwd ):
|
||||
if trans.app.config.get_bool( 'enable_beta_job_managers', False ) == False:
|
||||
return trans.fill_template( '/admin/data_admin/generic_error.mako', message='This feature requires that enable_beta_job_managers be set to True in your Galaxy configuration.' )
|
||||
if 'all_fasta' not in trans.app.tool_data_tables.data_tables:
|
||||
return trans.fill_template( '/admin/data_admin/generic_error.mako', message='The local data manager requires that an all_fasta entry exists in your tool_data_table_conf.xml.' )
|
||||
indextable = {}
|
||||
dbkeys = []
|
||||
labels = { 'bowtie_indexes': 'Bowtie', 'bowtie2_indexes': 'Bowtie 2', 'bwa_indexes': 'BWA', 'srma_indexes': 'Picard', 'sam_fa_indexes': 'SAM', 'perm_base_indexes': 'PerM' }
|
||||
tablenames = { 'Bowtie': 'bowtie_indexes', 'Bowtie 2': 'bowtie2_indexes', 'BWA': 'bwa_indexes', 'Picard': 'srma_indexes', 'SAM': 'sam_fa_indexes', 'PerM': 'perm_base_indexes' }
|
||||
indexfuncs = dict( bowtie_indexes='bowtie', bowtie2_indexes='bowtie2', bwa_indexes='bwa', srma_indexes='picard', sam_fa_indexes='sam', perm_base_indexes='perm' )
|
||||
for genome in trans.app.tool_data_tables.data_tables[ 'all_fasta' ].data:
|
||||
dbkey = genome[0]
|
||||
dbkeys.append( dbkey )
|
||||
indextable[ dbkey ] = dict( indexes=dict(), name=genome[2], path=genome[3] )
|
||||
for genome in indextable:
|
||||
for label in labels:
|
||||
indextable[ genome ][ 'indexes' ][ label ] = 'Generate'
|
||||
if label not in trans.app.tool_data_tables.data_tables:
|
||||
indextable[ genome ][ 'indexes' ][ label ] = 'Disabled'
|
||||
else:
|
||||
for row in trans.app.tool_data_tables.data_tables[ label ].data:
|
||||
if genome in row or row[0].startswith( genome ):
|
||||
indextable[ genome ][ 'indexes' ][ label ] = 'Generated'
|
||||
jobgrid = []
|
||||
sa_session = trans.app.model.context.current
|
||||
jobs = sa_session.query( model.GenomeIndexToolData ).order_by( model.GenomeIndexToolData.created_time.desc() ).filter_by( user_id=trans.get_user().id ).group_by( model.GenomeIndexToolData.deferred ).limit( 20 ).all()
|
||||
prevjobid = 0
|
||||
for job in jobs:
|
||||
if prevjobid == job.deferred.id:
|
||||
continue
|
||||
prevjobid = job.deferred.id
|
||||
state = job.deferred.state
|
||||
params = job.deferred.params
|
||||
if job.transfer is not None:
|
||||
jobtype = 'download'
|
||||
else:
|
||||
jobtype = 'index'
|
||||
indexers = ', '.join( params['indexes'] )
|
||||
jobgrid.append( dict( jobtype=jobtype, indexers=indexers, rowclass=state, deferred=job.deferred.id, state=state, intname=job.deferred.params[ 'intname' ], dbkey=job.deferred.params[ 'dbkey' ] ) )
|
||||
styles = dict( Generate=self.jobstyles['new'], Generated=self.jobstyles['ok'], Disabled=self.jobstyles['error'] )
|
||||
return trans.fill_template( '/admin/data_admin/local_data.mako', jobgrid=jobgrid, indextable=indextable, labels=labels, dbkeys=dbkeys, styles=styles, indexfuncs=indexfuncs )
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def add_genome( self, trans, **kwd ):
|
||||
if trans.app.config.get_bool( 'enable_beta_job_managers', False ) == False:
|
||||
return trans.fill_template( '/admin/data_admin/generic_error.mako', message='This feature requires that enable_beta_job_managers be set to True in your Galaxy configuration.' )
|
||||
dbkeys = trans.ucsc_builds
|
||||
ensemblkeys = trans.ensembl_builds
|
||||
ncbikeys = trans.ncbi_builds
|
||||
return trans.fill_template( '/admin/data_admin/data_form.mako', dbkeys=dbkeys, ensembls=ensemblkeys, ncbi=ncbikeys )
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def genome_search( self, trans, **kwd ):
|
||||
results = list()
|
||||
ncbikeys = trans.ncbi_builds
|
||||
params = util.Params( kwd )
|
||||
search = params.get( 'q', None )
|
||||
limit = params.get( 'limit', None )
|
||||
if search is not None:
|
||||
query = search.lower()
|
||||
for row in ncbikeys:
|
||||
if query in row[ 'name' ].lower() or query in row[ 'dbkey' ].lower():
|
||||
result = '|'.join( [ ': '.join( [ row[ 'dbkey' ], row[ 'name' ] ] ), row[ 'dbkey' ] ] )
|
||||
results.append( result )
|
||||
if len( results ) >= limit:
|
||||
break
|
||||
return trans.fill_template( '/admin/data_admin/ajax_status.mako', json='\n'.join( results ) )
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def index_build( self, trans, **kwd ):
|
||||
"""Index a previously downloaded genome."""
|
||||
params = util.Params( kwd )
|
||||
path = os.path.abspath( params.get( 'path', None ) )
|
||||
indexes = [ params.get( 'indexes', None ) ]
|
||||
dbkey = params.get( 'dbkey', None )
|
||||
intname = params.get( 'longname', None )
|
||||
indexjob = trans.app.job_manager.deferred_job_queue.plugins['GenomeIndexPlugin'].create_job( trans, path, indexes, dbkey, intname )
|
||||
return indexjob
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def download_build( self, trans, **kwd ):
|
||||
"""Download a genome from a remote source and add it to the library."""
|
||||
params = util.Params( kwd )
|
||||
paramdict = build_param_dict( params, trans )
|
||||
if paramdict[ 'status' ] == 'error':
|
||||
return trans.fill_template( '/admin/data_admin/generic_error.mako', message=paramdict[ 'message' ] )
|
||||
url = paramdict[ 'url' ]
|
||||
liftover = paramdict[ 'liftover' ]
|
||||
dbkey = paramdict[ 'dbkey' ]
|
||||
indexers = paramdict[ 'indexers' ]
|
||||
longname = paramdict[ 'longname' ]
|
||||
dbkeys = dict()
|
||||
protocol = 'http'
|
||||
if url is None:
|
||||
return trans.fill_template( '/admin/data_admin/generic_error.mako', message='Unable to generate a valid URL with the specified parameters.' )
|
||||
jobid = trans.app.job_manager.deferred_job_queue.plugins['GenomeTransferPlugin'].create_job( trans, url, dbkey, longname, indexers )
|
||||
chainjob = []
|
||||
if liftover is not None:
|
||||
for chain in liftover:
|
||||
liftover_url = u'ftp://hgdownload.cse.ucsc.edu%s' % chain[0]
|
||||
from_genome = chain[1]
|
||||
to_genome = chain[2]
|
||||
destfile = liftover_url.split('/')[-1].replace('.gz', '')
|
||||
lochain = trans.app.job_manager.deferred_job_queue.plugins['LiftOverTransferPlugin'].create_job( trans, liftover_url, dbkey, from_genome, to_genome, destfile, jobid )
|
||||
chainjob.append( lochain )
|
||||
job = trans.app.job_manager.deferred_job_queue.plugins['GenomeTransferPlugin'].get_job_status( jobid )
|
||||
job.params['liftover'] = chainjob
|
||||
trans.app.model.context.current.add( job )
|
||||
trans.app.model.context.current.flush()
|
||||
return trans.response.send_redirect( web.url_for( controller='data_admin',
|
||||
action='monitor_status',
|
||||
job=jobid ) )
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def monitor_status( self, trans, **kwd ):
|
||||
params = util.Params( kwd )
|
||||
jobid = params.get( 'job', '' )
|
||||
deferred = trans.app.model.context.current.query( model.DeferredJob ).filter_by( id=jobid ).first()
|
||||
if deferred is None:
|
||||
return trans.fill_template( '/admin/data_admin/generic_error.mako', message='Invalid genome downloader job specified.' )
|
||||
gname = deferred.params[ 'intname' ]
|
||||
indexers = ', '.join( deferred.params[ 'indexes' ] )
|
||||
jobs = self._get_jobs( deferred, trans )
|
||||
jsonjobs = json.dumps( jobs )
|
||||
return trans.fill_template( '/admin/data_admin/download_status.mako', name=gname, indexers=indexers, mainjob=jobid, jobs=jobs, jsonjobs=jsonjobs )
|
||||
|
||||
@web.expose
|
||||
@web.require_admin
|
||||
def get_jobs( self, trans, **kwd ):
|
||||
sa_session = trans.app.model.context.current
|
||||
jobs = []
|
||||
params = util.Params( kwd )
|
||||
jobid = params.get( 'jobid', '' )
|
||||
job = sa_session.query( model.DeferredJob ).filter_by( id=jobid ).first()
|
||||
jobs = self._get_jobs( job, trans )
|
||||
return trans.fill_template( '/admin/data_admin/ajax_status.mako', json=json.dumps( jobs ) )
|
||||
|
||||
def _get_job( self, jobid, jobtype, trans ):
|
||||
sa = trans.app.model.context.current
|
||||
if jobtype == 'liftover':
|
||||
liftoverjob = sa.query( model.DeferredJob ).filter_by( id=jobid ).first()
|
||||
job = sa.query( model.TransferJob ).filter_by( id=liftoverjob.params[ 'transfer_job_id' ] ).first()
|
||||
joblabel = 'Download liftOver (%s to %s)' % ( liftoverjob.params[ 'from_genome' ], liftoverjob.params[ 'to_genome' ] )
|
||||
elif jobtype == 'transfer':
|
||||
job = sa.query( model.TransferJob ).filter_by( id=jobid ).first()
|
||||
joblabel = 'Download Genome'
|
||||
elif jobtype == 'deferred':
|
||||
job = sa.query( model.DeferredJob ).filter_by( id=jobid ).first()
|
||||
joblabel = 'Main Controller'
|
||||
elif jobtype == 'index':
|
||||
job = sa.query( model.Job ).filter_by( id=jobid.job_id ).first()
|
||||
joblabel = 'Index Genome (%s)' % jobid.indexer
|
||||
return dict( status=job.state, jobid=job.id, style=self.jobstyles[job.state], type=jobtype, label=joblabel )
|
||||
|
||||
def _get_jobs( self, deferredjob, trans ):
|
||||
jobs = []
|
||||
idxjobs = []
|
||||
sa_session = trans.app.model.context.current
|
||||
job = sa_session.query( model.GenomeIndexToolData ).filter_by( deferred=deferredjob ).first()
|
||||
jobs.append( self._get_job( deferredjob.id, 'deferred', trans ) )
|
||||
if 'transfer_job_id' in deferredjob.params: #hasattr( job, 'transfer' ) and job.transfer is not None: # This is a transfer job, check for indexers
|
||||
jobs.append( self._get_job( deferredjob.params[ 'transfer_job_id' ], 'transfer', trans ) )
|
||||
if hasattr( job, 'deferred' ):
|
||||
idxjobs = sa_session.query( model.GenomeIndexToolData ).filter_by( deferred=job.deferred, transfer=job.transfer ).all()
|
||||
if deferredjob.params.has_key( 'liftover' ) and deferredjob.params[ 'liftover' ] is not None:
|
||||
for jobid in deferredjob.params[ 'liftover' ]:
|
||||
jobs.append( self._get_job( jobid, 'liftover', trans ) )
|
||||
for idxjob in idxjobs:
|
||||
jobs.append( self._get_job( idxjob, 'index', trans ) )
|
||||
return jobs
|
||||
|
||||
def build_param_dict( params, trans ):
|
||||
|
||||
source = params.get('source', '')
|
||||
longname = params.get('longname', None)
|
||||
if not isinstance( params.get( 'indexers', None ), list ):
|
||||
indexers = [ params.get( 'indexers', None ) ]
|
||||
else:
|
||||
indexers = params.get( 'indexers', None )
|
||||
if indexers is not None:
|
||||
if indexers == [None]:
|
||||
indexers = None
|
||||
url = None
|
||||
liftover = None
|
||||
newlift = []
|
||||
dbkey = params.get( 'dbkey', None )
|
||||
dbkeys = dict()
|
||||
protocol = 'http'
|
||||
|
||||
if source == 'NCBI':
|
||||
build = params.get('ncbi_name', '')
|
||||
dbkey = build.split( ': ' )[0]
|
||||
longname = build.split( ': ' )[-1]
|
||||
url = 'http://togows.dbcls.jp/entry/ncbi-nucleotide/%s.fasta' % dbkey
|
||||
elif source == 'URL':
|
||||
dbkey = params.get( 'url_dbkey', '' )
|
||||
url = params.get( 'url', None )
|
||||
longname = params.get( 'longname', None )
|
||||
elif source == 'UCSC':
|
||||
longname = None
|
||||
for build in trans.ucsc_builds:
|
||||
if dbkey == build[0]:
|
||||
dbkey = build[0]
|
||||
longname = build[1]
|
||||
break
|
||||
if dbkey == '?':
|
||||
return dict( status='error', message='An invalid build was specified.' )
|
||||
ftp = ftplib.FTP('hgdownload.cse.ucsc.edu')
|
||||
ftp.login('anonymous', trans.get_user().email)
|
||||
checker = []
|
||||
liftover = []
|
||||
newlift = []
|
||||
ftp.retrlines('NLST /goldenPath/%s/liftOver/*.chain.gz' % dbkey, liftover.append)
|
||||
try:
|
||||
for chain in liftover:
|
||||
lifts = []
|
||||
fname = chain.split( '/' )[-1]
|
||||
organisms = fname.replace( '.over.chain.gz', '' ).split( 'To' )
|
||||
lifts.append( [ organisms[0], organisms[1][0].lower() + organisms[1][1:] ] )
|
||||
lifts.append( [ organisms[1][0].lower() + organisms[1][1:], organisms[0] ] )
|
||||
for organism in lifts:
|
||||
remotepath = '/goldenPath/%s/liftOver/%sTo%s.over.chain.gz' % ( organism[0], organism[0], organism[1][0].upper() + organism[1][1:] )
|
||||
localfile = '%sTo%s.over.chain' % ( organism[0], organism[1][0].upper() + organism[1][1:] )
|
||||
localpath = os.path.join( trans.app.config.get( 'genome_data_path', 'tool-data/genome' ), organism[0], 'liftOver', localfile )
|
||||
if not os.path.exists( localpath ) or os.path.getsize( localpath ) == 0:
|
||||
newlift.append( [ remotepath, organism[0], organism[1] ] )
|
||||
except:
|
||||
newlift = None
|
||||
pass
|
||||
ftp.retrlines('NLST /goldenPath/%s/bigZips/' % dbkey, checker.append)
|
||||
ftp.quit()
|
||||
for filename in [ dbkey, 'chromFa' ]:
|
||||
for extension in [ '.tar.gz', '.tar.bz2', '.zip', '.fa.gz', '.fa.bz2' ]:
|
||||
testfile = '/goldenPath/%s/bigZips/%s%s' % ( dbkey, filename, extension )
|
||||
if testfile in checker:
|
||||
url = 'ftp://hgdownload.cse.ucsc.edu%s' % testfile
|
||||
break;
|
||||
else:
|
||||
continue
|
||||
if url is None:
|
||||
message = 'The genome %s was not found on the UCSC server.' % dbkey
|
||||
status = 'error'
|
||||
return dict( status=status, message=message )
|
||||
|
||||
elif source == 'Ensembl':
|
||||
dbkey = params.get( 'ensembl_dbkey', None )
|
||||
if dbkey == '?':
|
||||
return dict( status='error', message='An invalid build was specified.' )
|
||||
for build in trans.ensembl_builds:
|
||||
if build[ 'dbkey' ] == dbkey:
|
||||
dbkey = build[ 'dbkey' ]
|
||||
release = build[ 'release' ]
|
||||
pathname = '_'.join( build[ 'name' ].split(' ')[0:2] )
|
||||
longname = build[ 'name' ].replace('_', ' ')
|
||||
break
|
||||
url = 'ftp://ftp.ensembl.org/pub/release-%s/fasta/%s/dna/%s.%s.%s.dna.toplevel.fa.gz' % ( release, pathname.lower(), pathname, dbkey, release )
|
||||
|
||||
params = dict( status='ok', dbkey=dbkey, datatype='fasta', url=url, user=trans.user.id, liftover=newlift, longname=longname, indexers=indexers )
|
||||
|
||||
return params
|
||||
@@ -4,6 +4,7 @@ from cgi import escape
|
||||
import galaxy.util
|
||||
from galaxy import model
|
||||
from galaxy import web
|
||||
from galaxy import managers
|
||||
from galaxy.datatypes.data import nice_size
|
||||
from galaxy.model.item_attrs import UsesAnnotations, UsesItemRatings
|
||||
from galaxy.model.orm import and_, eagerload_all, func
|
||||
@@ -194,6 +195,12 @@ class HistoryAllPublishedGrid( grids.Grid ):
|
||||
class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesItemRatings,
|
||||
UsesHistoryMixin, UsesHistoryDatasetAssociationMixin, ExportsHistoryMixin,
|
||||
ImportsHistoryMixin ):
|
||||
def __init__( self, app ):
|
||||
super( HistoryController, self ).__init__( app )
|
||||
self.mgrs = util.bunch.Bunch(
|
||||
histories=managers.histories.HistoryManager()
|
||||
)
|
||||
|
||||
@web.expose
|
||||
def index( self, trans ):
|
||||
return ""
|
||||
@@ -204,6 +211,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
|
||||
trans.response.set_content_type( 'text/xml' )
|
||||
return trans.fill_template( "/history/list_as_xml.mako" )
|
||||
|
||||
# ......................................................................... lists
|
||||
stored_list_grid = HistoryListGrid()
|
||||
shared_list_grid = SharedHistoryListGrid()
|
||||
published_list_grid = HistoryAllPublishedGrid()
|
||||
@@ -437,6 +445,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
|
||||
# Render the list view
|
||||
return self.shared_list_grid( trans, status=status, message=message, **kwargs )
|
||||
|
||||
# ......................................................................... html
|
||||
@web.expose
|
||||
def display_structured( self, trans, id=None ):
|
||||
"""
|
||||
@@ -511,333 +520,6 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
|
||||
#
|
||||
return trans.fill_template( "history/display_structured.mako", items=items, history=history )
|
||||
|
||||
@web.expose
|
||||
def delete_hidden_datasets( self, trans ):
|
||||
"""
|
||||
This method deletes all hidden datasets in the current history.
|
||||
"""
|
||||
count = 0
|
||||
for hda in trans.history.datasets:
|
||||
if not hda.visible and not hda.deleted and not hda.purged:
|
||||
hda.mark_deleted()
|
||||
count += 1
|
||||
trans.sa_session.add( hda )
|
||||
trans.log_event( "HDA id %s has been deleted" % hda.id )
|
||||
trans.sa_session.flush()
|
||||
return trans.show_ok_message( "%d hidden datasets have been deleted" % count, refresh_frames=['history'] )
|
||||
|
||||
@web.expose
|
||||
def purge_deleted_datasets( self, trans ):
|
||||
count = 0
|
||||
if trans.app.config.allow_user_dataset_purge:
|
||||
for hda in trans.history.datasets:
|
||||
if not hda.deleted or hda.purged:
|
||||
continue
|
||||
if trans.user:
|
||||
trans.user.total_disk_usage -= hda.quota_amount( trans.user )
|
||||
hda.purged = True
|
||||
trans.sa_session.add( hda )
|
||||
trans.log_event( "HDA id %s has been purged" % hda.id )
|
||||
trans.sa_session.flush()
|
||||
if hda.dataset.user_can_purge:
|
||||
try:
|
||||
hda.dataset.full_delete()
|
||||
trans.log_event( "Dataset id %s has been purged upon the the purge of HDA id %s" % ( hda.dataset.id, hda.id ) )
|
||||
trans.sa_session.add( hda.dataset )
|
||||
except:
|
||||
log.exception( 'Unable to purge dataset (%s) on purge of hda (%s):' % ( hda.dataset.id, hda.id ) )
|
||||
count += 1
|
||||
return trans.show_ok_message( "%d datasets have been deleted permanently" % count, refresh_frames=['history'] )
|
||||
|
||||
#TODO: use api instead
|
||||
@web.expose
|
||||
def delete_current( self, trans, purge=False ):
|
||||
"""Delete just the active history -- this does not require a logged in user."""
|
||||
history = trans.get_history()
|
||||
if history.users_shared_with:
|
||||
return trans.show_error_message( "History (%s) has been shared with others, unshare it before deleting it. " % history.name )
|
||||
if not history.deleted:
|
||||
history.deleted = True
|
||||
trans.sa_session.add( history )
|
||||
trans.sa_session.flush()
|
||||
trans.log_event( "History id %d marked as deleted" % history.id )
|
||||
if purge and trans.app.config.allow_user_dataset_purge:
|
||||
for hda in history.datasets:
|
||||
if trans.user:
|
||||
trans.user.total_disk_usage -= hda.quota_amount( trans.user )
|
||||
hda.purged = True
|
||||
trans.sa_session.add( hda )
|
||||
trans.log_event( "HDA id %s has been purged" % hda.id )
|
||||
trans.sa_session.flush()
|
||||
if hda.dataset.user_can_purge:
|
||||
try:
|
||||
hda.dataset.full_delete()
|
||||
trans.log_event( "Dataset id %s has been purged upon the the purge of HDA id %s" % ( hda.dataset.id, hda.id ) )
|
||||
trans.sa_session.add( hda.dataset )
|
||||
except:
|
||||
log.exception( 'Unable to purge dataset (%s) on purge of hda (%s):' % ( hda.dataset.id, hda.id ) )
|
||||
history.purged = True
|
||||
self.sa_session.add( history )
|
||||
self.sa_session.flush()
|
||||
for hda in history.datasets:
|
||||
# Not all datasets have jobs associated with them (e.g., datasets imported from libraries).
|
||||
if hda.creating_job_associations:
|
||||
# HDA has associated job, so try marking it deleted.
|
||||
job = hda.creating_job_associations[0].job
|
||||
if job.history_id == history.id and job.state in [ trans.app.model.Job.states.QUEUED, trans.app.model.Job.states.RUNNING, trans.app.model.Job.states.NEW ]:
|
||||
# No need to check other outputs since the job's parent history is this history
|
||||
job.mark_deleted( trans.app.config.track_jobs_in_database )
|
||||
trans.app.job_manager.job_stop_queue.put( job.id )
|
||||
# Regardless of whether it was previously deleted, get or create default history.
|
||||
trans.get_or_create_default_history()
|
||||
return trans.show_ok_message( "History deleted, a new history is active", refresh_frames=['history'] )
|
||||
|
||||
@web.expose
|
||||
def unhide_datasets( self, trans, current=False, ids=None ):
|
||||
"""Unhide the datasets in the active history -- this does not require a logged in user."""
|
||||
if not ids and galaxy.util.string_as_bool( current ):
|
||||
histories = [ trans.get_history() ]
|
||||
refresh_frames = ['history']
|
||||
else:
|
||||
raise NotImplementedError( "You can currently only unhide all the datasets of the current history." )
|
||||
for history in histories:
|
||||
history.unhide_datasets()
|
||||
trans.sa_session.add( history )
|
||||
trans.sa_session.flush()
|
||||
return trans.show_ok_message( "Your datasets have been unhidden.", refresh_frames=refresh_frames )
|
||||
#TODO: used in index.mako
|
||||
|
||||
@web.expose
|
||||
def resume_paused_jobs( self, trans, current=False, ids=None ):
|
||||
"""Resume paused jobs the active history -- this does not require a logged in user."""
|
||||
if not ids and galaxy.util.string_as_bool( current ):
|
||||
histories = [ trans.get_history() ]
|
||||
refresh_frames = ['history']
|
||||
else:
|
||||
raise NotImplementedError( "You can currently only resume all the datasets of the current history." )
|
||||
for history in histories:
|
||||
history.resume_paused_jobs()
|
||||
trans.sa_session.add( history )
|
||||
trans.sa_session.flush()
|
||||
return trans.show_ok_message( "Your jobs have been resumed.", refresh_frames=refresh_frames )
|
||||
#TODO: used in index.mako
|
||||
|
||||
@web.expose
|
||||
@web.require_login( "rate items" )
|
||||
@web.json
|
||||
def rate_async( self, trans, id, rating ):
|
||||
""" Rate a history asynchronously and return updated community data. """
|
||||
history = self.get_history( trans, id, check_ownership=False, check_accessible=True )
|
||||
if not history:
|
||||
return trans.show_error_message( "The specified history does not exist." )
|
||||
# Rate history.
|
||||
history_rating = self.rate_item( trans.sa_session, trans.get_user(), history, rating )
|
||||
return self.get_ave_item_rating_data( trans.sa_session, history )
|
||||
#TODO: used in display_base.mako
|
||||
|
||||
@web.expose
|
||||
# TODO: Remove require_login when users are warned that, if they are not
|
||||
# logged in, this will remove their current history.
|
||||
@web.require_login( "use Galaxy histories" )
|
||||
def import_archive( self, trans, **kwargs ):
|
||||
""" Import a history from a file archive. """
|
||||
# Set archive source and type.
|
||||
archive_file = kwargs.get( 'archive_file', None )
|
||||
archive_url = kwargs.get( 'archive_url', None )
|
||||
archive_source = None
|
||||
if archive_file:
|
||||
archive_source = archive_file
|
||||
archive_type = 'file'
|
||||
elif archive_url:
|
||||
archive_source = archive_url
|
||||
archive_type = 'url'
|
||||
# If no source to create archive from, show form to upload archive or specify URL.
|
||||
if not archive_source:
|
||||
return trans.show_form(
|
||||
web.FormBuilder( web.url_for(controller='history', action='import_archive'), "Import a History from an Archive", submit_text="Submit" ) \
|
||||
.add_input( "text", "Archived History URL", "archive_url", value="", error=None )
|
||||
# TODO: add support for importing via a file.
|
||||
#.add_input( "file", "Archived History File", "archive_file", value=None, error=None )
|
||||
)
|
||||
self.queue_history_import( trans, archive_type=archive_type, archive_source=archive_source )
|
||||
return trans.show_message( "Importing history from '%s'. \
|
||||
This history will be visible when the import is complete" % archive_source )
|
||||
#TODO: used in this file and index.mako
|
||||
|
||||
@web.expose
|
||||
def export_archive( self, trans, id=None, gzip=True, include_hidden=False, include_deleted=False ):
|
||||
""" Export a history to an archive. """
|
||||
#
|
||||
# Get history to export.
|
||||
#
|
||||
if id:
|
||||
history = self.get_history( trans, id, check_ownership=False, check_accessible=True )
|
||||
else:
|
||||
# Use current history.
|
||||
history = trans.history
|
||||
id = trans.security.encode_id( history.id )
|
||||
|
||||
if not history:
|
||||
return trans.show_error_message( "This history does not exist or you cannot export this history." )
|
||||
|
||||
#
|
||||
# If history has already been exported and it has not changed since export, stream it.
|
||||
#
|
||||
jeha = history.latest_export
|
||||
if jeha and jeha.up_to_date:
|
||||
if jeha.ready:
|
||||
return self.serve_ready_history_export( trans, jeha )
|
||||
elif jeha.preparing:
|
||||
return trans.show_message( "Still exporting history %(n)s; please check back soon. Link: <a href='%(s)s'>%(s)s</a>" \
|
||||
% ( { 'n' : history.name, 's' : url_for( controller='history', action="export_archive", id=id, qualified=True ) } ) )
|
||||
|
||||
self.queue_history_export( trans, history, gzip=gzip, include_hidden=include_hidden, include_deleted=include_deleted )
|
||||
url = url_for( controller='history', action="export_archive", id=id, qualified=True )
|
||||
return trans.show_message( "Exporting History '%(n)s'. Use this link to download \
|
||||
the archive or import it to another Galaxy server: \
|
||||
<a href='%(u)s'>%(u)s</a>" % ( { 'n' : history.name, 'u' : url } ) )
|
||||
#TODO: used in this file and index.mako
|
||||
|
||||
@web.expose
|
||||
@web.json
|
||||
@web.require_login( "get history name and link" )
|
||||
def get_name_and_link_async( self, trans, id=None ):
|
||||
""" Returns history's name and link. """
|
||||
history = self.get_history( trans, id, False )
|
||||
if self.create_item_slug( trans.sa_session, history ):
|
||||
trans.sa_session.flush()
|
||||
return_dict = {
|
||||
"name" : history.name,
|
||||
"link" : url_for(controller='history', action="display_by_username_and_slug",
|
||||
username=history.user.username, slug=history.slug ) }
|
||||
return return_dict
|
||||
#TODO: used in page/editor.mako
|
||||
|
||||
@web.expose
|
||||
@web.require_login( "set history's accessible flag" )
|
||||
def set_accessible_async( self, trans, id=None, accessible=False ):
|
||||
""" Set history's importable attribute and slug. """
|
||||
history = self.get_history( trans, id, True )
|
||||
# Only set if importable value would change; this prevents a change in the update_time unless attribute really changed.
|
||||
importable = accessible in ['True', 'true', 't', 'T'];
|
||||
if history and history.importable != importable:
|
||||
if importable:
|
||||
self._make_item_accessible( trans.sa_session, history )
|
||||
else:
|
||||
history.importable = importable
|
||||
trans.sa_session.flush()
|
||||
return
|
||||
#TODO: used in page/editor.mako
|
||||
|
||||
@web.expose
|
||||
def get_item_content_async( self, trans, id ):
|
||||
""" Returns item content in HTML format. """
|
||||
|
||||
history = self.get_history( trans, id, False, True )
|
||||
if history is None:
|
||||
raise web.httpexceptions.HTTPNotFound()
|
||||
|
||||
# Get datasets.
|
||||
datasets = self.get_history_datasets( trans, history )
|
||||
# Get annotations.
|
||||
history.annotation = self.get_item_annotation_str( trans.sa_session, history.user, history )
|
||||
for dataset in datasets:
|
||||
dataset.annotation = self.get_item_annotation_str( trans.sa_session, history.user, dataset )
|
||||
return trans.stream_template_mako( "/history/item_content.mako", item = history, item_data = datasets )
|
||||
#TODO: used in embed_base.mako
|
||||
|
||||
@web.expose
|
||||
def name_autocomplete_data( self, trans, q=None, limit=None, timestamp=None ):
|
||||
"""Return autocomplete data for history names"""
|
||||
user = trans.get_user()
|
||||
if not user:
|
||||
return
|
||||
|
||||
ac_data = ""
|
||||
for history in ( trans.sa_session.query( model.History )
|
||||
.filter_by( user=user )
|
||||
.filter( func.lower( model.History.name ).like(q.lower() + "%") ) ):
|
||||
ac_data = ac_data + history.name + "\n"
|
||||
return ac_data
|
||||
#TODO: used in grid_base.mako
|
||||
|
||||
@web.expose
|
||||
def imp( self, trans, id=None, confirm=False, **kwd ):
|
||||
"""Import another user's history via a shared URL"""
|
||||
msg = ""
|
||||
user = trans.get_user()
|
||||
user_history = trans.get_history()
|
||||
# Set referer message
|
||||
if 'referer' in kwd:
|
||||
referer = kwd['referer']
|
||||
else:
|
||||
referer = trans.request.referer
|
||||
if referer is not "":
|
||||
referer_message = "<a href='%s'>return to the previous page</a>" % referer
|
||||
else:
|
||||
referer_message = "<a href='%s'>go to Galaxy's start page</a>" % url_for( '/' )
|
||||
|
||||
# include all datasets when copying?
|
||||
all_datasets = util.string_as_bool( kwd.get( 'all_datasets', False ) )
|
||||
|
||||
# Do import.
|
||||
if not id:
|
||||
return trans.show_error_message( "You must specify a history you want to import.<br>You can %s." % referer_message, use_panels=True )
|
||||
import_history = self.get_history( trans, id, check_ownership=False, check_accessible=False )
|
||||
if not import_history:
|
||||
return trans.show_error_message( "The specified history does not exist.<br>You can %s." % referer_message, use_panels=True )
|
||||
# History is importable if user is admin or it's accessible. TODO: probably want to have app setting to enable admin access to histories.
|
||||
if not trans.user_is_admin() and not self.security_check( trans, import_history, check_ownership=False, check_accessible=True ):
|
||||
return trans.show_error_message( "You cannot access this history.<br>You can %s." % referer_message, use_panels=True )
|
||||
if user:
|
||||
#dan: I can import my own history.
|
||||
#if import_history.user_id == user.id:
|
||||
# return trans.show_error_message( "You cannot import your own history.<br>You can %s." % referer_message, use_panels=True )
|
||||
new_history = import_history.copy( target_user=user, all_datasets=all_datasets )
|
||||
new_history.name = "imported: " + new_history.name
|
||||
new_history.user_id = user.id
|
||||
galaxy_session = trans.get_galaxy_session()
|
||||
try:
|
||||
association = trans.sa_session.query( trans.app.model.GalaxySessionToHistoryAssociation ) \
|
||||
.filter_by( session_id=galaxy_session.id, history_id=new_history.id ) \
|
||||
.first()
|
||||
except:
|
||||
association = None
|
||||
new_history.add_galaxy_session( galaxy_session, association=association )
|
||||
trans.sa_session.add( new_history )
|
||||
trans.sa_session.flush()
|
||||
# Set imported history to be user's current history.
|
||||
trans.set_history( new_history )
|
||||
return trans.show_ok_message(
|
||||
message="""History "%s" has been imported. <br>You can <a href="%s" onclick="parent.window.location='%s';">start using this history</a> or %s."""
|
||||
% ( new_history.name, web.url_for( '/' ), web.url_for( '/' ), referer_message ), use_panels=True )
|
||||
|
||||
elif not user_history or not user_history.datasets or confirm:
|
||||
#TODO:?? should anon-users be allowed to include deleted datasets when importing?
|
||||
#new_history = import_history.copy( activatable=include_deleted )
|
||||
new_history = import_history.copy()
|
||||
new_history.name = "imported: " + new_history.name
|
||||
new_history.user_id = None
|
||||
galaxy_session = trans.get_galaxy_session()
|
||||
try:
|
||||
association = trans.sa_session.query( trans.app.model.GalaxySessionToHistoryAssociation ) \
|
||||
.filter_by( session_id=galaxy_session.id, history_id=new_history.id ) \
|
||||
.first()
|
||||
except:
|
||||
association = None
|
||||
new_history.add_galaxy_session( galaxy_session, association=association )
|
||||
trans.sa_session.add( new_history )
|
||||
trans.sa_session.flush()
|
||||
trans.set_history( new_history )
|
||||
return trans.show_ok_message(
|
||||
message="""History "%s" has been imported. <br>You can <a href="%s">start using this history</a> or %s."""
|
||||
% ( new_history.name, web.url_for( '/' ), referer_message ), use_panels=True )
|
||||
return trans.show_warn_message( """
|
||||
Warning! If you import this history, you will lose your current
|
||||
history. <br>You can <a href="%s">continue and import this history</a> or %s.
|
||||
""" % ( web.url_for(controller='history', action='imp', id=id, confirm=True, referer=trans.request.referer ), referer_message ), use_panels=True )
|
||||
#TODO: used in history/view, display, embed
|
||||
|
||||
@web.expose
|
||||
def view( self, trans, id=None, show_deleted=False, show_hidden=False, use_panels=True ):
|
||||
"""
|
||||
@@ -870,22 +552,9 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
|
||||
+ " or the owner of this history has not made it accessible." )
|
||||
|
||||
# include all datasets: hidden, deleted, and purged
|
||||
hdas = self.get_history_datasets( trans, history_to_view,
|
||||
show_deleted=True, show_hidden=True, show_purged=True )
|
||||
for hda in hdas:
|
||||
hda_dict = {}
|
||||
try:
|
||||
hda_dict = self.get_hda_dict( trans, hda )
|
||||
|
||||
except Exception, exc:
|
||||
# don't fail entire list if hda err's, record and move on
|
||||
log.error( 'Error bootstrapping hda %d: %s', hda.id, str( exc ), exc_info=True )
|
||||
hda_dict = self.get_hda_dict_with_error( trans, hda, str( exc ) )
|
||||
|
||||
hda_dictionaries.append( hda_dict )
|
||||
|
||||
# re-use the hdas above to get the history data...
|
||||
history_dictionary = self.get_history_dict( trans, history_to_view, hda_dictionaries=hda_dictionaries )
|
||||
history_data = self.mgrs.histories._get_history_data( trans, history_to_view )
|
||||
history_dictionary = history_data[ 'history' ]
|
||||
hda_dictionaries = history_data[ 'contents' ]
|
||||
|
||||
except Exception, exc:
|
||||
user_id = str( trans.user.id ) if trans.user else '(anonymous)'
|
||||
@@ -911,12 +580,6 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
|
||||
# Security check raises error if user cannot access history.
|
||||
self.security_check( trans, history, False, True)
|
||||
|
||||
# Get datasets and annotations
|
||||
datasets = self.get_history_datasets( trans, history )
|
||||
history.annotation = self.get_item_annotation_str( trans.sa_session, history.user, history )
|
||||
for dataset in datasets:
|
||||
dataset.annotation = self.get_item_annotation_str( trans.sa_session, history.user, dataset )
|
||||
|
||||
# Get rating data.
|
||||
user_item_rating = 0
|
||||
if trans.get_user():
|
||||
@@ -928,20 +591,22 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
|
||||
ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, history )
|
||||
|
||||
# create ownership flag for template, dictify models
|
||||
# note: adding original annotation since this is published - get_dict returns user-based annos
|
||||
user_is_owner = trans.user == history.user
|
||||
hda_dicts = []
|
||||
for hda in datasets:
|
||||
hda_dict = self.get_hda_dict( trans, hda )
|
||||
hda_dict[ 'annotation' ] = hda.annotation
|
||||
hda_dicts.append( hda_dict )
|
||||
history_dict = self.get_history_dict( trans, history, hda_dictionaries=hda_dicts )
|
||||
history_dict[ 'annotation' ] = history.annotation
|
||||
history_data = self.mgrs.histories._get_history_data( trans, history )
|
||||
history_dict = history_data[ 'history' ]
|
||||
hda_dicts = history_data[ 'contents' ]
|
||||
|
||||
return trans.stream_template_mako( "history/display.mako", item=history, item_data=datasets,
|
||||
history_dict[ 'annotation' ] = self.get_item_annotation_str( trans.sa_session, history.user, history )
|
||||
# note: adding original annotation since this is published - get_dict returns user-based annos
|
||||
#for hda_dict in hda_dicts:
|
||||
# hda_dict[ 'annotation' ] = hda.annotation
|
||||
# dataset.annotation = self.get_item_annotation_str( trans.sa_session, history.user, dataset )
|
||||
|
||||
return trans.stream_template_mako( "history/display.mako", item=history, item_data=[],
|
||||
user_is_owner=user_is_owner, history_dict=history_dict, hda_dicts=hda_dicts,
|
||||
user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings )
|
||||
|
||||
# ......................................................................... sharing & publishing
|
||||
@web.expose
|
||||
@web.require_login( "share Galaxy histories" )
|
||||
def sharing( self, trans, id=None, histories=[], **kwargs ):
|
||||
@@ -1283,6 +948,334 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
|
||||
msg += send_to_err
|
||||
return self.sharing( trans, histories=shared_histories, msg=msg )
|
||||
|
||||
# ......................................................................... actions/orig. async
|
||||
@web.expose
|
||||
def delete_hidden_datasets( self, trans ):
|
||||
"""
|
||||
This method deletes all hidden datasets in the current history.
|
||||
"""
|
||||
count = 0
|
||||
for hda in trans.history.datasets:
|
||||
if not hda.visible and not hda.deleted and not hda.purged:
|
||||
hda.mark_deleted()
|
||||
count += 1
|
||||
trans.sa_session.add( hda )
|
||||
trans.log_event( "HDA id %s has been deleted" % hda.id )
|
||||
trans.sa_session.flush()
|
||||
return trans.show_ok_message( "%d hidden datasets have been deleted" % count, refresh_frames=['history'] )
|
||||
|
||||
@web.expose
|
||||
def purge_deleted_datasets( self, trans ):
|
||||
count = 0
|
||||
if trans.app.config.allow_user_dataset_purge:
|
||||
for hda in trans.history.datasets:
|
||||
if not hda.deleted or hda.purged:
|
||||
continue
|
||||
if trans.user:
|
||||
trans.user.total_disk_usage -= hda.quota_amount( trans.user )
|
||||
hda.purged = True
|
||||
trans.sa_session.add( hda )
|
||||
trans.log_event( "HDA id %s has been purged" % hda.id )
|
||||
trans.sa_session.flush()
|
||||
if hda.dataset.user_can_purge:
|
||||
try:
|
||||
hda.dataset.full_delete()
|
||||
trans.log_event( "Dataset id %s has been purged upon the the purge of HDA id %s" % ( hda.dataset.id, hda.id ) )
|
||||
trans.sa_session.add( hda.dataset )
|
||||
except:
|
||||
log.exception( 'Unable to purge dataset (%s) on purge of hda (%s):' % ( hda.dataset.id, hda.id ) )
|
||||
count += 1
|
||||
return trans.show_ok_message( "%d datasets have been deleted permanently" % count, refresh_frames=['history'] )
|
||||
|
||||
#TODO: use api instead
|
||||
@web.expose
|
||||
def delete_current( self, trans, purge=False ):
|
||||
"""Delete just the active history -- this does not require a logged in user."""
|
||||
history = trans.get_history()
|
||||
if history.users_shared_with:
|
||||
return trans.show_error_message( "History (%s) has been shared with others, unshare it before deleting it. " % history.name )
|
||||
if not history.deleted:
|
||||
history.deleted = True
|
||||
trans.sa_session.add( history )
|
||||
trans.sa_session.flush()
|
||||
trans.log_event( "History id %d marked as deleted" % history.id )
|
||||
if purge and trans.app.config.allow_user_dataset_purge:
|
||||
for hda in history.datasets:
|
||||
if trans.user:
|
||||
trans.user.total_disk_usage -= hda.quota_amount( trans.user )
|
||||
hda.purged = True
|
||||
trans.sa_session.add( hda )
|
||||
trans.log_event( "HDA id %s has been purged" % hda.id )
|
||||
trans.sa_session.flush()
|
||||
if hda.dataset.user_can_purge:
|
||||
try:
|
||||
hda.dataset.full_delete()
|
||||
trans.log_event( "Dataset id %s has been purged upon the the purge of HDA id %s" % ( hda.dataset.id, hda.id ) )
|
||||
trans.sa_session.add( hda.dataset )
|
||||
except:
|
||||
log.exception( 'Unable to purge dataset (%s) on purge of hda (%s):' % ( hda.dataset.id, hda.id ) )
|
||||
history.purged = True
|
||||
self.sa_session.add( history )
|
||||
self.sa_session.flush()
|
||||
for hda in history.datasets:
|
||||
# Not all datasets have jobs associated with them (e.g., datasets imported from libraries).
|
||||
if hda.creating_job_associations:
|
||||
# HDA has associated job, so try marking it deleted.
|
||||
job = hda.creating_job_associations[0].job
|
||||
if job.history_id == history.id and job.state in [ trans.app.model.Job.states.QUEUED, trans.app.model.Job.states.RUNNING, trans.app.model.Job.states.NEW ]:
|
||||
# No need to check other outputs since the job's parent history is this history
|
||||
job.mark_deleted( trans.app.config.track_jobs_in_database )
|
||||
trans.app.job_manager.job_stop_queue.put( job.id )
|
||||
# Regardless of whether it was previously deleted, get or create default history.
|
||||
trans.get_or_create_default_history()
|
||||
return trans.show_ok_message( "History deleted, a new history is active", refresh_frames=['history'] )
|
||||
|
||||
@web.expose
|
||||
def unhide_datasets( self, trans, current=False, ids=None ):
|
||||
"""Unhide the datasets in the active history -- this does not require a logged in user."""
|
||||
if not ids and galaxy.util.string_as_bool( current ):
|
||||
histories = [ trans.get_history() ]
|
||||
refresh_frames = ['history']
|
||||
else:
|
||||
raise NotImplementedError( "You can currently only unhide all the datasets of the current history." )
|
||||
for history in histories:
|
||||
history.unhide_datasets()
|
||||
trans.sa_session.add( history )
|
||||
trans.sa_session.flush()
|
||||
return trans.show_ok_message( "Your datasets have been unhidden.", refresh_frames=refresh_frames )
|
||||
#TODO: used in index.mako
|
||||
|
||||
@web.expose
|
||||
def resume_paused_jobs( self, trans, current=False, ids=None ):
|
||||
"""Resume paused jobs the active history -- this does not require a logged in user."""
|
||||
if not ids and galaxy.util.string_as_bool( current ):
|
||||
histories = [ trans.get_history() ]
|
||||
refresh_frames = ['history']
|
||||
else:
|
||||
raise NotImplementedError( "You can currently only resume all the datasets of the current history." )
|
||||
for history in histories:
|
||||
history.resume_paused_jobs()
|
||||
trans.sa_session.add( history )
|
||||
trans.sa_session.flush()
|
||||
return trans.show_ok_message( "Your jobs have been resumed.", refresh_frames=refresh_frames )
|
||||
#TODO: used in index.mako
|
||||
|
||||
@web.expose
|
||||
@web.require_login( "rate items" )
|
||||
@web.json
|
||||
def rate_async( self, trans, id, rating ):
|
||||
""" Rate a history asynchronously and return updated community data. """
|
||||
history = self.get_history( trans, id, check_ownership=False, check_accessible=True )
|
||||
if not history:
|
||||
return trans.show_error_message( "The specified history does not exist." )
|
||||
# Rate history.
|
||||
history_rating = self.rate_item( trans.sa_session, trans.get_user(), history, rating )
|
||||
return self.get_ave_item_rating_data( trans.sa_session, history )
|
||||
#TODO: used in display_base.mako
|
||||
|
||||
@web.expose
|
||||
# TODO: Remove require_login when users are warned that, if they are not
|
||||
# logged in, this will remove their current history.
|
||||
@web.require_login( "use Galaxy histories" )
|
||||
def import_archive( self, trans, **kwargs ):
|
||||
""" Import a history from a file archive. """
|
||||
# Set archive source and type.
|
||||
archive_file = kwargs.get( 'archive_file', None )
|
||||
archive_url = kwargs.get( 'archive_url', None )
|
||||
archive_source = None
|
||||
if archive_file:
|
||||
archive_source = archive_file
|
||||
archive_type = 'file'
|
||||
elif archive_url:
|
||||
archive_source = archive_url
|
||||
archive_type = 'url'
|
||||
# If no source to create archive from, show form to upload archive or specify URL.
|
||||
if not archive_source:
|
||||
return trans.show_form(
|
||||
web.FormBuilder( web.url_for(controller='history', action='import_archive'), "Import a History from an Archive", submit_text="Submit" ) \
|
||||
.add_input( "text", "Archived History URL", "archive_url", value="", error=None )
|
||||
# TODO: add support for importing via a file.
|
||||
#.add_input( "file", "Archived History File", "archive_file", value=None, error=None )
|
||||
)
|
||||
self.queue_history_import( trans, archive_type=archive_type, archive_source=archive_source )
|
||||
return trans.show_message( "Importing history from '%s'. \
|
||||
This history will be visible when the import is complete" % archive_source )
|
||||
#TODO: used in this file and index.mako
|
||||
|
||||
@web.expose
|
||||
def export_archive( self, trans, id=None, gzip=True, include_hidden=False, include_deleted=False ):
|
||||
""" Export a history to an archive. """
|
||||
#
|
||||
# Get history to export.
|
||||
#
|
||||
if id:
|
||||
history = self.get_history( trans, id, check_ownership=False, check_accessible=True )
|
||||
else:
|
||||
# Use current history.
|
||||
history = trans.history
|
||||
id = trans.security.encode_id( history.id )
|
||||
|
||||
if not history:
|
||||
return trans.show_error_message( "This history does not exist or you cannot export this history." )
|
||||
|
||||
#
|
||||
# If history has already been exported and it has not changed since export, stream it.
|
||||
#
|
||||
jeha = history.latest_export
|
||||
if jeha and jeha.up_to_date:
|
||||
if jeha.ready:
|
||||
return self.serve_ready_history_export( trans, jeha )
|
||||
elif jeha.preparing:
|
||||
return trans.show_message( "Still exporting history %(n)s; please check back soon. Link: <a href='%(s)s'>%(s)s</a>" \
|
||||
% ( { 'n' : history.name, 's' : url_for( controller='history', action="export_archive", id=id, qualified=True ) } ) )
|
||||
|
||||
self.queue_history_export( trans, history, gzip=gzip, include_hidden=include_hidden, include_deleted=include_deleted )
|
||||
url = url_for( controller='history', action="export_archive", id=id, qualified=True )
|
||||
return trans.show_message( "Exporting History '%(n)s'. Use this link to download \
|
||||
the archive or import it to another Galaxy server: \
|
||||
<a href='%(u)s'>%(u)s</a>" % ( { 'n' : history.name, 'u' : url } ) )
|
||||
#TODO: used in this file and index.mako
|
||||
|
||||
@web.expose
|
||||
@web.json
|
||||
@web.require_login( "get history name and link" )
|
||||
def get_name_and_link_async( self, trans, id=None ):
|
||||
""" Returns history's name and link. """
|
||||
history = self.get_history( trans, id, False )
|
||||
if self.create_item_slug( trans.sa_session, history ):
|
||||
trans.sa_session.flush()
|
||||
return_dict = {
|
||||
"name" : history.name,
|
||||
"link" : url_for(controller='history', action="display_by_username_and_slug",
|
||||
username=history.user.username, slug=history.slug ) }
|
||||
return return_dict
|
||||
#TODO: used in page/editor.mako
|
||||
|
||||
@web.expose
|
||||
@web.require_login( "set history's accessible flag" )
|
||||
def set_accessible_async( self, trans, id=None, accessible=False ):
|
||||
""" Set history's importable attribute and slug. """
|
||||
history = self.get_history( trans, id, True )
|
||||
# Only set if importable value would change; this prevents a change in the update_time unless attribute really changed.
|
||||
importable = accessible in ['True', 'true', 't', 'T'];
|
||||
if history and history.importable != importable:
|
||||
if importable:
|
||||
self._make_item_accessible( trans.sa_session, history )
|
||||
else:
|
||||
history.importable = importable
|
||||
trans.sa_session.flush()
|
||||
return
|
||||
#TODO: used in page/editor.mako
|
||||
|
||||
@web.expose
|
||||
def get_item_content_async( self, trans, id ):
|
||||
""" Returns item content in HTML format. """
|
||||
|
||||
history = self.get_history( trans, id, False, True )
|
||||
if history is None:
|
||||
raise web.httpexceptions.HTTPNotFound()
|
||||
|
||||
# Get datasets.
|
||||
datasets = self.get_history_datasets( trans, history )
|
||||
# Get annotations.
|
||||
history.annotation = self.get_item_annotation_str( trans.sa_session, history.user, history )
|
||||
for dataset in datasets:
|
||||
dataset.annotation = self.get_item_annotation_str( trans.sa_session, history.user, dataset )
|
||||
return trans.stream_template_mako( "/history/item_content.mako", item = history, item_data = datasets )
|
||||
#TODO: used in embed_base.mako
|
||||
|
||||
@web.expose
|
||||
def name_autocomplete_data( self, trans, q=None, limit=None, timestamp=None ):
|
||||
"""Return autocomplete data for history names"""
|
||||
user = trans.get_user()
|
||||
if not user:
|
||||
return
|
||||
|
||||
ac_data = ""
|
||||
for history in ( trans.sa_session.query( model.History )
|
||||
.filter_by( user=user )
|
||||
.filter( func.lower( model.History.name ).like(q.lower() + "%") ) ):
|
||||
ac_data = ac_data + history.name + "\n"
|
||||
return ac_data
|
||||
#TODO: used in grid_base.mako
|
||||
|
||||
@web.expose
|
||||
def imp( self, trans, id=None, confirm=False, **kwd ):
|
||||
"""Import another user's history via a shared URL"""
|
||||
msg = ""
|
||||
user = trans.get_user()
|
||||
user_history = trans.get_history()
|
||||
# Set referer message
|
||||
if 'referer' in kwd:
|
||||
referer = kwd['referer']
|
||||
else:
|
||||
referer = trans.request.referer
|
||||
if referer is not "":
|
||||
referer_message = "<a href='%s'>return to the previous page</a>" % referer
|
||||
else:
|
||||
referer_message = "<a href='%s'>go to Galaxy's start page</a>" % url_for( '/' )
|
||||
|
||||
# include all datasets when copying?
|
||||
all_datasets = util.string_as_bool( kwd.get( 'all_datasets', False ) )
|
||||
|
||||
# Do import.
|
||||
if not id:
|
||||
return trans.show_error_message( "You must specify a history you want to import.<br>You can %s." % referer_message, use_panels=True )
|
||||
import_history = self.get_history( trans, id, check_ownership=False, check_accessible=False )
|
||||
if not import_history:
|
||||
return trans.show_error_message( "The specified history does not exist.<br>You can %s." % referer_message, use_panels=True )
|
||||
# History is importable if user is admin or it's accessible. TODO: probably want to have app setting to enable admin access to histories.
|
||||
if not trans.user_is_admin() and not self.security_check( trans, import_history, check_ownership=False, check_accessible=True ):
|
||||
return trans.show_error_message( "You cannot access this history.<br>You can %s." % referer_message, use_panels=True )
|
||||
if user:
|
||||
#dan: I can import my own history.
|
||||
#if import_history.user_id == user.id:
|
||||
# return trans.show_error_message( "You cannot import your own history.<br>You can %s." % referer_message, use_panels=True )
|
||||
new_history = import_history.copy( target_user=user, all_datasets=all_datasets )
|
||||
new_history.name = "imported: " + new_history.name
|
||||
new_history.user_id = user.id
|
||||
galaxy_session = trans.get_galaxy_session()
|
||||
try:
|
||||
association = trans.sa_session.query( trans.app.model.GalaxySessionToHistoryAssociation ) \
|
||||
.filter_by( session_id=galaxy_session.id, history_id=new_history.id ) \
|
||||
.first()
|
||||
except:
|
||||
association = None
|
||||
new_history.add_galaxy_session( galaxy_session, association=association )
|
||||
trans.sa_session.add( new_history )
|
||||
trans.sa_session.flush()
|
||||
# Set imported history to be user's current history.
|
||||
trans.set_history( new_history )
|
||||
return trans.show_ok_message(
|
||||
message="""History "%s" has been imported. <br>You can <a href="%s" onclick="parent.window.location='%s';">start using this history</a> or %s."""
|
||||
% ( new_history.name, web.url_for( '/' ), web.url_for( '/' ), referer_message ), use_panels=True )
|
||||
|
||||
elif not user_history or not user_history.datasets or confirm:
|
||||
#TODO:?? should anon-users be allowed to include deleted datasets when importing?
|
||||
#new_history = import_history.copy( activatable=include_deleted )
|
||||
new_history = import_history.copy()
|
||||
new_history.name = "imported: " + new_history.name
|
||||
new_history.user_id = None
|
||||
galaxy_session = trans.get_galaxy_session()
|
||||
try:
|
||||
association = trans.sa_session.query( trans.app.model.GalaxySessionToHistoryAssociation ) \
|
||||
.filter_by( session_id=galaxy_session.id, history_id=new_history.id ) \
|
||||
.first()
|
||||
except:
|
||||
association = None
|
||||
new_history.add_galaxy_session( galaxy_session, association=association )
|
||||
trans.sa_session.add( new_history )
|
||||
trans.sa_session.flush()
|
||||
trans.set_history( new_history )
|
||||
return trans.show_ok_message(
|
||||
message="""History "%s" has been imported. <br>You can <a href="%s">start using this history</a> or %s."""
|
||||
% ( new_history.name, web.url_for( '/' ), referer_message ), use_panels=True )
|
||||
return trans.show_warn_message( """
|
||||
Warning! If you import this history, you will lose your current
|
||||
history. <br>You can <a href="%s">continue and import this history</a> or %s.
|
||||
""" % ( web.url_for(controller='history', action='imp', id=id, confirm=True, referer=trans.request.referer ), referer_message ), use_panels=True )
|
||||
#TODO: used in history/view, display, embed
|
||||
|
||||
@web.expose
|
||||
@web.require_login( "rename histories" )
|
||||
def rename( self, trans, id=None, name=None, **kwd ):
|
||||
@@ -1361,7 +1354,8 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
|
||||
name += " (active items only)"
|
||||
new_history = history.copy( name=name, target_user=user )
|
||||
if len( histories ) == 1:
|
||||
msg = 'New history "<a href="%s" target="_top">%s</a>" has been created.' % ( url_for( controller="history", action="switch_to_history", hist_id=trans.security.encode_id( new_history.id ) ) , new_history.name )
|
||||
switch_url = url_for( controller="history", action="switch_to_history", hist_id=trans.security.encode_id( new_history.id ) )
|
||||
msg = 'New history "<a href="%s" target="_top">%s</a>" has been created.' % ( switch_url, new_history.name )
|
||||
else:
|
||||
msg = 'Copied and created %d new histories.' % len( histories )
|
||||
return trans.show_ok_message( msg )
|
||||
|
||||
@@ -7,20 +7,28 @@ import urllib
|
||||
|
||||
from paste.httpexceptions import HTTPNotFound, HTTPBadGateway
|
||||
|
||||
from galaxy.web.base.controller import BaseUIController, UsesHistoryDatasetAssociationMixin, UsesHistoryMixin
|
||||
from galaxy import managers
|
||||
|
||||
from galaxy import web
|
||||
from galaxy.web import url_for
|
||||
from galaxy.model.item_attrs import UsesAnnotations
|
||||
from galaxy import util
|
||||
from galaxy.util import listify, Params, string_as_bool, string_as_bool_or_none
|
||||
from galaxy.web.base.controller import BaseUIController, UsesHistoryDatasetAssociationMixin, UsesHistoryMixin
|
||||
from galaxy.util.json import to_json_string
|
||||
|
||||
from galaxy.util.debugging import SimpleProfiler
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
class RootController( BaseUIController, UsesHistoryMixin, UsesHistoryDatasetAssociationMixin, UsesAnnotations ):
|
||||
"""Controller class that maps to the url root of Galaxy (i.e. '/')."""
|
||||
"""
|
||||
Controller class that maps to the url root of Galaxy (i.e. '/').
|
||||
"""
|
||||
def __init__( self, app ):
|
||||
super( RootController, self ).__init__( app )
|
||||
self.mgrs = util.bunch.Bunch(
|
||||
histories=managers.histories.HistoryManager()
|
||||
)
|
||||
|
||||
@web.expose
|
||||
def default(self, trans, target1=None, target2=None, **kwd):
|
||||
@@ -100,42 +108,6 @@ class RootController( BaseUIController, UsesHistoryMixin, UsesHistoryDatasetAsso
|
||||
show_deleted=string_as_bool( show_deleted ),
|
||||
show_hidden=string_as_bool( show_hidden ) )
|
||||
|
||||
def _get_current_history_data( self, trans ):
|
||||
history_dictionary = {}
|
||||
hda_dictionaries = []
|
||||
|
||||
try:
|
||||
history = trans.get_history( create=True )
|
||||
hdas = self.get_history_datasets( trans, history,
|
||||
show_deleted=True, show_hidden=True, show_purged=True )
|
||||
|
||||
for hda in hdas:
|
||||
hda_dict = {}
|
||||
try:
|
||||
hda_dict = self.get_hda_dict( trans, hda )
|
||||
|
||||
except Exception, exc:
|
||||
# don't fail entire list if hda err's, record and move on
|
||||
log.error( 'Error bootstrapping hda %d: %s', hda.id, str( exc ), exc_info=True )
|
||||
hda_dict = self.get_hda_dict_with_error( trans, hda, str( exc ) )
|
||||
|
||||
hda_dictionaries.append( hda_dict )
|
||||
|
||||
# re-use the hdas above to get the history data...
|
||||
history_dictionary = self.get_history_dict( trans, history, hda_dictionaries=hda_dictionaries )
|
||||
|
||||
except Exception, exc:
|
||||
user_id = str( trans.user.id ) if trans.user else '(anonymous)'
|
||||
log.exception( 'Error bootstrapping history for user %s: %s', user_id, str( exc ) )
|
||||
message = ( 'An error occurred getting the history data from the server. '
|
||||
+ 'Please contact a Galaxy administrator if the problem persists.' )
|
||||
history_dictionary[ 'error' ] = message
|
||||
|
||||
return {
|
||||
'history' : history_dictionary,
|
||||
'hdas' : hda_dictionaries
|
||||
}
|
||||
|
||||
@web.expose
|
||||
def history( self, trans, as_xml=False, show_deleted=None, show_hidden=None, **kwd ):
|
||||
"""
|
||||
@@ -154,11 +126,11 @@ class RootController( BaseUIController, UsesHistoryMixin, UsesHistoryDatasetAsso
|
||||
show_hidden = string_as_bool_or_none( show_hidden )
|
||||
|
||||
history_dictionary = {}
|
||||
hda_dictionaries = []
|
||||
hda_dictionaries = []
|
||||
try:
|
||||
history_data = self._get_current_history_data( trans )
|
||||
history_data = self.mgrs.histories._get_history_data( trans, trans.get_history( create=True ) )
|
||||
history_dictionary = history_data[ 'history' ]
|
||||
hda_dictionaries = history_data[ 'hdas' ]
|
||||
hda_dictionaries = history_data[ 'contents' ]
|
||||
|
||||
except Exception, exc:
|
||||
user_id = str( trans.user.id ) if trans.user else '(anonymous)'
|
||||
@@ -170,6 +142,7 @@ class RootController( BaseUIController, UsesHistoryMixin, UsesHistoryDatasetAsso
|
||||
history = history_dictionary, hdas = hda_dictionaries,
|
||||
show_deleted=show_deleted, show_hidden=show_hidden )
|
||||
|
||||
|
||||
## ---- Dataset display / editing ----------------------------------------
|
||||
@web.expose
|
||||
def display( self, trans, id=None, hid=None, tofile=None, toext=".txt", encoded_id=None, **kwd ):
|
||||
|
||||
@@ -7,6 +7,7 @@ from time import strftime
|
||||
from galaxy import eggs
|
||||
from galaxy import util
|
||||
from galaxy import web
|
||||
from galaxy.model.orm import and_
|
||||
from galaxy.web.base.controller import BaseAPIController
|
||||
from galaxy.web.base.controller import HTTPBadRequest
|
||||
from galaxy.web.framework.helpers import time_ago
|
||||
@@ -297,16 +298,28 @@ class RepositoriesController( BaseAPIController ):
|
||||
return return_dict
|
||||
|
||||
@web.expose_api_anonymous
|
||||
def index( self, trans, deleted=False, **kwd ):
|
||||
def index( self, trans, deleted=False, owner=None, name=None, **kwd ):
|
||||
"""
|
||||
GET /api/repositories
|
||||
|
||||
:param deleted: True/False, displays repositories that are or are not set to deleted.
|
||||
:param owner: the owner's public username.
|
||||
:param name: the repository name.
|
||||
|
||||
Displays a collection (list) of repositories.
|
||||
"""
|
||||
# Example URL: http://localhost:9009/api/repositories
|
||||
repository_dicts = []
|
||||
deleted = util.asbool( deleted )
|
||||
clause_list = [ and_( trans.app.model.Repository.table.c.deprecated == False,
|
||||
trans.app.model.Repository.table.c.deleted == deleted ) ]
|
||||
if owner is not None:
|
||||
clause_list.append( and_( trans.app.model.User.table.c.username == owner,
|
||||
trans.app.model.Repository.table.c.user_id == trans.app.model.User.table.c.id ) )
|
||||
if name is not None:
|
||||
clause_list.append( trans.app.model.Repository.table.c.name == name )
|
||||
for repository in trans.sa_session.query( trans.app.model.Repository ) \
|
||||
.filter( trans.app.model.Repository.table.c.deleted == deleted ) \
|
||||
.filter( *clause_list ) \
|
||||
.order_by( trans.app.model.Repository.table.c.name ):
|
||||
repository_dict = repository.to_dict( view='collection',
|
||||
value_mapper=self.__get_value_mapper( trans ) )
|
||||
|
||||
@@ -65,6 +65,8 @@ class Configuration( object ):
|
||||
self.tool_dependency_dir = None
|
||||
self.use_tool_dependencies = False
|
||||
self.update_integrated_tool_panel = False
|
||||
# Galaxy flavor Docker Image
|
||||
self.enable_galaxy_flavor_docker_image = string_as_bool( kwargs.get( "enable_galaxy_flavor_docker_image", "False" ) )
|
||||
self.use_remote_user = string_as_bool( kwargs.get( "use_remote_user", "False" ) )
|
||||
self.user_activation_on = kwargs.get( 'user_activation_on', None )
|
||||
self.activation_grace_period = kwargs.get( 'activation_grace_period', None )
|
||||
|
||||
@@ -55,6 +55,7 @@ class RepositoryController( BaseUIController, ratings_util.ItemRatings ):
|
||||
datatypes_grid = repository_grids.DatatypesGrid()
|
||||
deprecated_repositories_i_own_grid = repository_grids.DeprecatedRepositoriesIOwnGrid()
|
||||
email_alerts_repository_grid = repository_grids.EmailAlertsRepositoryGrid()
|
||||
docker_image_grid = repository_grids.DockerImageGrid()
|
||||
install_matched_repository_grid = repository_grids.InstallMatchedRepositoryGrid()
|
||||
matched_repository_grid = repository_grids.MatchedRepositoryGrid()
|
||||
my_writable_repositories_grid = repository_grids.MyWritableRepositoriesGrid()
|
||||
@@ -985,6 +986,62 @@ class RepositoryController( BaseUIController, ratings_util.ItemRatings ):
|
||||
# Do all we can to eliminate spam.
|
||||
return trans.show_error_message( "You must be logged in to contact the owner of a repository." )
|
||||
|
||||
@web.expose
|
||||
def create_galaxy_docker_image( self, trans, **kwd ):
|
||||
message = kwd.get( 'message', '' )
|
||||
status = kwd.get( 'status', 'done' )
|
||||
repository_ids = util.listify( kwd.get( 'id', '' ) )
|
||||
if 'operation' in kwd:
|
||||
if repository_ids:
|
||||
operation = kwd[ 'operation' ].lower()
|
||||
if operation == "include in docker image":
|
||||
repository_tups = []
|
||||
for repository_id in repository_ids:
|
||||
repository = suc.get_repository_by_id( trans.app, repository_id )
|
||||
repository_tups.append( ( str( repository.name ),
|
||||
str( repository.user.username ),
|
||||
str( repository.type ) ) )
|
||||
return trans.fill_template( "/webapps/tool_shed/repository/docker_image_repositories.mako",
|
||||
id=','.join( repository_ids ),
|
||||
repository_tups=repository_tups,
|
||||
message=message,
|
||||
status=status )
|
||||
else:
|
||||
# This can only occur when there is a multi-select grid with check boxes and an operation,
|
||||
# and the user clicked the operation button without checking any of the check boxes.
|
||||
kwd[ 'message' ] = "No items were selected."
|
||||
kwd[ 'status' ] = 'error'
|
||||
elif kwd.get( 'create_docker_image_button', False ):
|
||||
tmp_image_dir = tempfile.mkdtemp( prefix="tmp-toolshed-cdidir" )
|
||||
docker_file_name = 'Dockerfile'
|
||||
docker_file_path = os.path.join( tmp_image_dir, docker_file_name )
|
||||
tool_shed_url = tool_shed_url = web.url_for( '/', qualified=True )
|
||||
repository_string = ''
|
||||
for repository_id in repository_ids:
|
||||
repository = suc.get_repository_by_id( trans.app, repository_id )
|
||||
template = basic_util.SELECTED_REPOSITORIES_TEMPLATE
|
||||
repository_template = \
|
||||
string.Template( template ).safe_substitute( tool_shed_url=tool_shed_url,
|
||||
repository_owner=str( repository.user.username ) ,
|
||||
repository_name=str( repository.name ) )
|
||||
repository_string = '%s\n%s' % ( repository_string, repository_template )
|
||||
template = basic_util.DOCKER_IMAGE_TEMPLATE
|
||||
docker_image_template = \
|
||||
string.Template( template ).safe_substitute( selected_repositories=repository_string )
|
||||
docker_image_string = docker_image_template
|
||||
trans.response.set_content_type( 'application/text/plain' )
|
||||
trans.response.headers[ "Content-Disposition" ] = 'attachment; filename="%s"' % docker_file_name
|
||||
opened_file = open( docker_file_path, "w" )
|
||||
opened_file.write( docker_image_string )
|
||||
opened_file.close()
|
||||
opened_file = open( docker_file_path, "r" )
|
||||
# Make sure the file is removed from disk after the contents have been downloaded.
|
||||
os.unlink( docker_file_path )
|
||||
docker_file_path, docker_file_name = os.path.split( docker_file_path )
|
||||
basic_util.remove_dir( docker_file_path )
|
||||
return opened_file
|
||||
return self.docker_image_grid( trans, **kwd )
|
||||
|
||||
@web.expose
|
||||
def create_repository( self, trans, **kwd ):
|
||||
message = kwd.get( 'message', '' )
|
||||
|
||||
@@ -194,9 +194,7 @@ class InstalledRepositoryGrid( grids.Grid ):
|
||||
grids.GridOperation( label="Deactivate or uninstall",
|
||||
condition=( lambda item: \
|
||||
not item.deleted and \
|
||||
item.status not in \
|
||||
[ tool_shed_install.ToolShedRepository.installation_status.ERROR,
|
||||
tool_shed_install.ToolShedRepository.installation_status.NEW ] ),
|
||||
item.status != tool_shed_install.ToolShedRepository.installation_status.NEW ),
|
||||
allow_multiple=False,
|
||||
url_args=dict( controller='admin_toolshed',
|
||||
action='browse_repositories',
|
||||
|
||||
@@ -44,8 +44,9 @@ class InstallToolDependencyManager( object ):
|
||||
|
||||
def __init__( self, app ):
|
||||
self.app = app
|
||||
self.INSTALL_ACTIONS = [ 'download_binary', 'download_by_url', 'download_file', 'setup_perl_environmnet',
|
||||
'setup_r_environmnet', 'setup_ruby_environmnet', 'shell_command' ]
|
||||
self.INSTALL_ACTIONS = [ 'download_binary', 'download_by_url', 'download_file',
|
||||
'setup_perl_environment', 'setup_python_environment',
|
||||
'setup_r_environment', 'setup_ruby_environment', 'shell_command' ]
|
||||
|
||||
def format_traceback( self ):
|
||||
ex_type, ex, tb = sys.exc_info()
|
||||
|
||||
@@ -718,7 +718,7 @@ class InstalledRepositoryManager( object ):
|
||||
if installed_repository_dict[ 'display_path' ]:
|
||||
datatype_util.load_installed_display_applications( self.app, installed_repository_dict, deactivate=deactivate )
|
||||
|
||||
def purge_repository( self, app, repository ):
|
||||
def purge_repository( self, repository ):
|
||||
"""Purge a repository with status New (a white ghost) from the database."""
|
||||
sa_session = self.app.model.context.current
|
||||
status = 'ok'
|
||||
@@ -732,24 +732,26 @@ class InstalledRepositoryManager( object ):
|
||||
# Purge this repository's associated tool versions.
|
||||
if repository.tool_versions:
|
||||
for tool_version in repository.tool_versions:
|
||||
try:
|
||||
tool_version_association = tool_version.parent_tool_association
|
||||
sa_session.delete( tool_version_association )
|
||||
sa_session.flush()
|
||||
except Exception, e:
|
||||
status = 'error'
|
||||
message = 'Error attempting to purge tool_versions for the repository named %s with status %s: %s.' % \
|
||||
( str( repository.name ), str( repository.status ), str( e ) )
|
||||
return status, message
|
||||
try:
|
||||
tool_version_association = tool_version.child_tool_association
|
||||
sa_session.delete( tool_version_association )
|
||||
sa_session.flush()
|
||||
except Exception, e:
|
||||
status = 'error'
|
||||
message = 'Error attempting to purge tool_versions for the repository named %s with status %s: %s.' % \
|
||||
( str( repository.name ), str( repository.status ), str( e ) )
|
||||
return status, message
|
||||
if tool_version.parent_tool_association:
|
||||
for tool_version_association in tool_version.parent_tool_association:
|
||||
try:
|
||||
sa_session.delete( tool_version_association )
|
||||
sa_session.flush()
|
||||
except Exception, e:
|
||||
status = 'error'
|
||||
message = 'Error attempting to purge tool_versions for the repository named %s with status %s: %s.' % \
|
||||
( str( repository.name ), str( repository.status ), str( e ) )
|
||||
return status, message
|
||||
if tool_version.child_tool_association:
|
||||
for tool_version_association in tool_version.child_tool_association:
|
||||
try:
|
||||
sa_session.delete( tool_version_association )
|
||||
sa_session.flush()
|
||||
except Exception, e:
|
||||
status = 'error'
|
||||
message = 'Error attempting to purge tool_versions for the repository named %s with status %s: %s.' % \
|
||||
( str( repository.name ), str( repository.status ), str( e ) )
|
||||
return status, message
|
||||
try:
|
||||
sa_session.delete( tool_version )
|
||||
sa_session.flush()
|
||||
|
||||
@@ -124,7 +124,7 @@ class EnvManager( object ):
|
||||
Parse an XML tag set to discover all child repository dependency tags and define the path to an env.sh file associated
|
||||
with the repository (this requires the repository dependency to be in an installed state). The received action_dict
|
||||
will be updated with these discovered paths and returned to the caller. This method handles tool dependency definition
|
||||
tag sets <setup_r_environment>, <setup_ruby_environment> and <setup_perl_environment>.
|
||||
tag sets <setup_r_environment>, <setup_ruby_environment>, <setup_python_environment> and <setup_perl_environment>.
|
||||
"""
|
||||
# An example elem is:
|
||||
# <action type="setup_perl_environment">
|
||||
|
||||
@@ -54,6 +54,7 @@ class StepManager( object ):
|
||||
setup_perl_environment=step_handler.SetupPerlEnvironment( self.app ),
|
||||
setup_r_environment=step_handler.SetupREnvironment( self.app ),
|
||||
setup_ruby_environment=step_handler.SetupRubyEnvironment( self.app ),
|
||||
setup_python_environment=step_handler.SetupPythonEnvironment( self.app ),
|
||||
setup_virtualenv=step_handler.SetupVirtualEnv( self.app ),
|
||||
shell_command=step_handler.ShellCommand( self.app ),
|
||||
template_command=step_handler.TemplateCommand( self.app ) )
|
||||
|
||||
@@ -8,6 +8,7 @@ import tarfile
|
||||
import time
|
||||
import urllib2
|
||||
import zipfile
|
||||
import hashlib
|
||||
|
||||
from galaxy.util import asbool
|
||||
from galaxy.util.template import fill_template
|
||||
@@ -139,10 +140,29 @@ class CompressedFile( object ):
|
||||
class Download( object ):
|
||||
|
||||
def url_download( self, install_dir, downloaded_file_name, download_url, extract=True ):
|
||||
"""
|
||||
The given download_url can have an extension like #md5# or #sha256#.
|
||||
This indicates a checksum which will be chekced after download.
|
||||
If the checksum does not match an exception is thrown.
|
||||
|
||||
https://pypi.python.org/packages/source/k/khmer/khmer-1.0.tar.gz#md5#b60639a8b2939836f66495b9a88df757
|
||||
"""
|
||||
|
||||
file_path = os.path.join( install_dir, downloaded_file_name )
|
||||
src = None
|
||||
dst = None
|
||||
checksum = None
|
||||
sha256 = False
|
||||
md5 = False
|
||||
# Set a timer so we don't sit here forever.
|
||||
|
||||
if '#md5#' in download_url:
|
||||
md5 = True
|
||||
download_url, checksum = download_url.split('#md5#')
|
||||
elif '#sha256#' in download_url:
|
||||
sha256 = True
|
||||
download_url, checksum = download_url.split('#sha256#')
|
||||
|
||||
start_time = time.time()
|
||||
try:
|
||||
src = urllib2.urlopen( download_url )
|
||||
@@ -166,6 +186,18 @@ class Download( object ):
|
||||
src.close()
|
||||
if dst:
|
||||
dst.close()
|
||||
|
||||
try:
|
||||
if sha256:
|
||||
downloaded_checksum = hashlib.sha256(open(file_path, 'rb').read()).hexdigest()
|
||||
elif md5:
|
||||
downloaded_checksum = hashlib.md5(open(file_path, 'rb').read()).hexdigest()
|
||||
|
||||
if checksum and downloaded_checksum != checksum:
|
||||
raise Exception( 'Given checksum does not match with the one from the downloaded file (%s).' % (downloaded_checksum) )
|
||||
except Exception, e:
|
||||
raise
|
||||
|
||||
if extract:
|
||||
if tarfile.is_tarfile( file_path ) or ( zipfile.is_zipfile( file_path ) and not file_path.endswith( '.jar' ) ):
|
||||
archive = CompressedFile( file_path )
|
||||
@@ -174,6 +206,7 @@ class Download( object ):
|
||||
extraction_path = os.path.abspath( install_dir )
|
||||
else:
|
||||
extraction_path = os.path.abspath( install_dir )
|
||||
|
||||
return extraction_path
|
||||
|
||||
|
||||
@@ -1390,6 +1423,139 @@ class SetupRubyEnvironment( Download, RecipeStep ):
|
||||
return action_dict
|
||||
|
||||
|
||||
class SetupPythonEnvironment( Download, RecipeStep ):
|
||||
|
||||
def __init__( self, app ):
|
||||
self.app = app
|
||||
self.type = 'setup_python_environment'
|
||||
|
||||
def execute_step( self, tool_dependency, package_name, actions, action_dict, filtered_actions, env_file_builder,
|
||||
install_environment, work_dir, current_dir=None, initial_download=False ):
|
||||
"""
|
||||
Initialize the environment for installing Python packages. The class is called during the initial
|
||||
download stage when installing packages, so the value of initial_download will generally be True.
|
||||
However, the parameter value allows this class to also be used in the second stage of the installation,
|
||||
although it may never be necessary. If initial_download is True, the recipe steps will be filtered
|
||||
and returned and the installation directory (i.e., dir) will be defined and returned. If we're not
|
||||
in the initial download stage, these actions will not occur, and None values will be returned for them.
|
||||
|
||||
Warning: easy_install is configured that it will not be install any dependency, the tool developer needs
|
||||
to specify every dependency explicitly
|
||||
"""
|
||||
# <action type="setup_python_environment">
|
||||
# <repository name="package_python_2_7" owner="bgruening">
|
||||
# <package name="python" version="2.7" />
|
||||
# </repository>
|
||||
# <!-- allow downloading and installing a Python package from https://pypi.python.org/ -->
|
||||
# <package>pysam.tar.gz</package>
|
||||
# <package>http://url-to-some-python-package.de/pysam.tar.gz</package>
|
||||
# </action>
|
||||
dir = None
|
||||
if initial_download:
|
||||
filtered_actions = actions[ 1: ]
|
||||
env_shell_file_paths = action_dict.get( 'env_shell_file_paths', None )
|
||||
if env_shell_file_paths is None:
|
||||
log.debug( 'Missing Python environment, make sure your specified Python installation exists.' )
|
||||
if initial_download:
|
||||
return tool_dependency, filtered_actions, dir
|
||||
return tool_dependency, None, None
|
||||
else:
|
||||
install_environment.add_env_shell_file_paths( env_shell_file_paths )
|
||||
log.debug( 'Handling setup_python_environment for tool dependency %s with install_environment.env_shell_file_paths:\n%s' % \
|
||||
( str( tool_dependency.name ), str( install_environment.env_shell_file_paths ) ) )
|
||||
dir = os.path.curdir
|
||||
current_dir = os.path.abspath( os.path.join( work_dir, dir ) )
|
||||
with lcd( current_dir ):
|
||||
with settings( warn_only=True ):
|
||||
python_package_tups = action_dict.get( 'python_package_tups', [] )
|
||||
for python_package_tup in python_package_tups:
|
||||
package, package_version = python_package_tup
|
||||
package_path = os.path.join( install_environment.tool_shed_repository_install_dir, package )
|
||||
if os.path.isfile( package_path ):
|
||||
# we assume a local shipped python package
|
||||
|
||||
cmd = r'''PATH=$PATH:$PYTHONHOME/bin; export PATH;
|
||||
export PYTHONPATH=$PYTHONPATH:$INSTALL_DIR;
|
||||
easy_install --no-deps --install-dir $INSTALL_DIR --script-dir $INSTALL_DIR/bin %s
|
||||
''' % ( package_path )
|
||||
elif package.find( '://' ) != -1:
|
||||
# We assume a URL to a python package.
|
||||
url = package
|
||||
package_name = url.split( '/' )[ -1 ]
|
||||
self.url_download( work_dir, package_name, url, extract=False )
|
||||
|
||||
cmd = r'''PATH=$PATH:$PYTHONHOME/bin; export PATH;
|
||||
export PYTHONPATH=$PYTHONPATH:$INSTALL_DIR;
|
||||
easy_install --no-deps --install-dir $INSTALL_DIR --script-dir $INSTALL_DIR/bin %s
|
||||
''' % ( package_name )
|
||||
else:
|
||||
pass
|
||||
# pypi can be implemented or for > python3.4 we can use the build-in system
|
||||
cmd = install_environment.build_command( basic_util.evaluate_template( cmd, install_environment ) )
|
||||
return_code = install_environment.handle_command( tool_dependency=tool_dependency,
|
||||
cmd=cmd,
|
||||
return_output=False )
|
||||
if return_code:
|
||||
if initial_download:
|
||||
return tool_dependency, filtered_actions, dir
|
||||
return tool_dependency, None, None
|
||||
# Pull in python dependencies (runtime).
|
||||
env_file_builder.handle_action_shell_file_paths( action_dict )
|
||||
env_file_builder.append_line( name="PYTHONPATH",
|
||||
action="prepend_to",
|
||||
value= os.path.join( install_environment.install_dir, 'lib', 'python') )
|
||||
env_file_builder.append_line( name="PATH",
|
||||
action="prepend_to",
|
||||
value=os.path.join( install_environment.install_dir, 'bin' ) )
|
||||
return_code = env_file_builder.return_code
|
||||
if return_code:
|
||||
if initial_download:
|
||||
return tool_dependency, filtered_actions, dir
|
||||
return tool_dependency, None, None
|
||||
if initial_download:
|
||||
return tool_dependency, filtered_actions, dir
|
||||
return tool_dependency, None, None
|
||||
|
||||
def prepare_step( self, tool_dependency, action_elem, action_dict, install_environment, is_binary_download ):
|
||||
# setup a Python environment.
|
||||
# <action type="setup_python_environment">
|
||||
# <repository name="package_python_2_7" owner="bgruening">
|
||||
# <package name="python" version="2.7" />
|
||||
# </repository>
|
||||
# <!-- allow downloading and installing an Python package from https://pypi.org/ -->
|
||||
# <package>pysam.tar.gz</package>
|
||||
# <package>http://url-to-some-python-package.de/pysam.tar.gz</package>
|
||||
# </action>
|
||||
# Discover all child repository dependency tags and define the path to an env.sh file
|
||||
# associated with each repository. This will potentially update the value of the
|
||||
# 'env_shell_file_paths' entry in action_dict.
|
||||
all_env_shell_file_paths = []
|
||||
env_manager = EnvManager( self.app )
|
||||
action_dict = env_manager.get_env_shell_file_paths_from_setup_environment_elem( all_env_shell_file_paths,
|
||||
action_elem,
|
||||
action_dict )
|
||||
python_package_tups = []
|
||||
for env_elem in action_elem:
|
||||
if env_elem.tag == 'package':
|
||||
#A valid package definitions can be:
|
||||
# pysam.tar.gz -> locally shipped tarball
|
||||
# ftp://ftp.gruening.de/pysam.tar.gz -> online tarball
|
||||
python_token = env_elem.text.strip().split( '=' )
|
||||
if len( python_token ) == 2:
|
||||
# version string
|
||||
package_name = python_token[ 0 ]
|
||||
package_version = python_token[ 1 ]
|
||||
python_package_tups.append( ( package_name, package_version ) )
|
||||
else:
|
||||
# package name for pypi.org without version number
|
||||
package = env_elem.text.strip()
|
||||
python_package_tups.append( ( package, None ) )
|
||||
if python_package_tups:
|
||||
action_dict[ 'python_package_tups' ] = python_package_tups
|
||||
return action_dict
|
||||
|
||||
|
||||
|
||||
class SetupVirtualEnv( Download, RecipeStep ):
|
||||
|
||||
def __init__( self, app ):
|
||||
|
||||
@@ -19,6 +19,7 @@ class UpdateRepositoryManager( object ):
|
||||
self.running = True
|
||||
self.sleeper = Sleeper()
|
||||
self.restarter = threading.Thread( target=self.__restarter )
|
||||
self.restarter.daemon = True
|
||||
self.restarter.start()
|
||||
self.seconds_to_sleep = int( app.config.hours_between_check * 3600 )
|
||||
|
||||
|
||||
@@ -329,6 +329,26 @@ class RepositoryGrid( grids.Grid ):
|
||||
.outerjoin( model.Category.table )
|
||||
|
||||
|
||||
class DockerImageGrid( RepositoryGrid ):
|
||||
columns = [
|
||||
RepositoryGrid.NameColumn( "Name",
|
||||
key="name",
|
||||
link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ),
|
||||
attach_popup=False ),
|
||||
RepositoryGrid.DescriptionColumn( "Synopsis",
|
||||
key="description",
|
||||
attach_popup=False ),
|
||||
RepositoryGrid.UserColumn( "Owner",
|
||||
model_class=model.User,
|
||||
link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ),
|
||||
attach_popup=False,
|
||||
key="User.username" ),
|
||||
RepositoryGrid.EmailAlertsColumn( "Alert", attach_popup=False ),
|
||||
]
|
||||
operations = [ grids.GridOperation( "Include in Docker image", allow_multiple=True ) ]
|
||||
show_item_checkboxes = True
|
||||
|
||||
|
||||
class EmailAlertsRepositoryGrid( RepositoryGrid ):
|
||||
columns = [
|
||||
RepositoryGrid.NameColumn( "Name",
|
||||
|
||||
@@ -20,6 +20,33 @@ NO_OUTPUT_TIMEOUT = 1200.0
|
||||
MAXDIFFSIZE = 8000
|
||||
MAX_DISPLAY_SIZE = 32768
|
||||
|
||||
DOCKER_IMAGE_TEMPLATE = '''
|
||||
# Galaxy Docker image
|
||||
|
||||
FROM bgruening/galaxy-stable
|
||||
|
||||
MAINTAINER Bjoern A. Gruning, bjoern.gruening@gmail.com
|
||||
|
||||
RUN sed -i 's|brand.*|brand = deepTools|g' ~/galaxy-central/universe_wsgi.ini
|
||||
|
||||
WORKDIR /galaxy-central
|
||||
|
||||
${selected_repositories}
|
||||
|
||||
# Mark one folder as imported from the host.
|
||||
VOLUME ["/export/"]
|
||||
|
||||
# Expose port 80 to the host
|
||||
EXPOSE :80
|
||||
|
||||
# Autostart script that is invoked during container start
|
||||
CMD ["/usr/bin/startup"]
|
||||
'''
|
||||
|
||||
SELECTED_REPOSITORIES_TEMPLATE = '''
|
||||
RUN service postgresql start && service apache2 start && ./run.sh --daemon && sleep 120 && python ./scripts/api/install_tool_shed_repositories.py --api admin -l http://localhost:8080 --url ${tool_shed_url} -o ${repository_owner} --name ${repository_name} --tool-deps --repository-deps --panel-section-name 'Docker'
|
||||
'''
|
||||
|
||||
def evaluate_template( text, install_environment ):
|
||||
"""
|
||||
Substitute variables defined in XML blocks from dependencies file. The value of the received
|
||||
|
||||
@@ -868,6 +868,20 @@ def generate_package_dependency_metadata( app, elem, valid_tool_dependencies_dic
|
||||
repository_elem=sub_action_elem,
|
||||
only_if_compiling_contained_td=True,
|
||||
updating_installed_repository=False )
|
||||
elif action_elem.tag == 'action':
|
||||
# <action type="set_environment_for_install">
|
||||
# <repository changeset_revision="b107b91b3574" name="package_readline_6_2" owner="devteam" prior_installation_required="True" toolshed="http://localhost:9009">
|
||||
# <package name="readline" version="6.2" />
|
||||
# </repository>
|
||||
# </action>
|
||||
for sub_action_elem in action_elem:
|
||||
if sub_action_elem.tag == 'repository':
|
||||
# We have a complex repository dependency.
|
||||
repository_dependency_tup, repository_dependency_is_valid, error_message = \
|
||||
handle_repository_elem( app=app,
|
||||
repository_elem=sub_action_elem,
|
||||
only_if_compiling_contained_td=True,
|
||||
updating_installed_repository=False )
|
||||
if requirements_dict:
|
||||
dependency_key = '%s/%s' % ( package_name, package_version )
|
||||
if repository_dependency_is_valid:
|
||||
|
||||
+41
-10
@@ -31,16 +31,27 @@ import pkg_resources
|
||||
import galaxy.model.mapping # need to load this before we unpickle, in order to setup properties assigned by the mappers
|
||||
galaxy.model.Job() # this looks REAL stupid, but it is REQUIRED in order for SA to insert parameters into the classes defined by the mappers --> it appears that instantiating ANY mapper'ed class would suffice here
|
||||
from galaxy.util import stringify_dictionary_keys
|
||||
from galaxy.util.json import from_json_string
|
||||
from sqlalchemy.orm import clear_mappers
|
||||
from galaxy.objectstore import build_object_store_from_config
|
||||
from galaxy import config
|
||||
import ConfigParser
|
||||
|
||||
|
||||
def set_meta_with_tool_provided( dataset_instance, file_dict, set_meta_kwds ):
|
||||
# This method is somewhat odd, in that we set the metadata attributes from tool,
|
||||
# then call set_meta, then set metadata attributes from tool again.
|
||||
# This is intentional due to interplay of overwrite kwd, the fact that some metadata
|
||||
# parameters may rely on the values of others, and that we are accepting the
|
||||
# values provided by the tool as Truth.
|
||||
for metadata_name, metadata_value in file_dict.get( 'metadata', {} ).iteritems():
|
||||
setattr( dataset_instance.metadata, metadata_name, metadata_value )
|
||||
dataset_instance.datatype.set_meta( dataset_instance, **set_meta_kwds )
|
||||
for metadata_name, metadata_value in file_dict.get( 'metadata', {} ).iteritems():
|
||||
setattr( dataset_instance.metadata, metadata_name, metadata_value )
|
||||
|
||||
def __main__():
|
||||
file_path = sys.argv.pop( 1 )
|
||||
tmp_dir = sys.argv.pop( 1 )
|
||||
tool_job_working_directory = tmp_dir = sys.argv.pop( 1 ) #this is also the job_working_directory now
|
||||
galaxy.model.Dataset.file_path = file_path
|
||||
galaxy.datatypes.metadata.MetadataTempFile.tmp_dir = tmp_dir
|
||||
|
||||
@@ -77,15 +88,19 @@ def __main__():
|
||||
galaxy.model.set_datatypes_registry( datatypes_registry )
|
||||
|
||||
job_metadata = sys.argv.pop( 1 )
|
||||
ext_override = dict()
|
||||
existing_job_metadata_dict = {}
|
||||
new_job_metadata_dict = {}
|
||||
if job_metadata != "None" and os.path.exists( job_metadata ):
|
||||
for line in open( job_metadata, 'r' ):
|
||||
try:
|
||||
line = stringify_dictionary_keys( from_json_string( line ) )
|
||||
assert line['type'] == 'dataset'
|
||||
ext_override[line['dataset_id']] = line['ext']
|
||||
line = stringify_dictionary_keys( json.loads( line ) )
|
||||
if line['type'] == 'dataset':
|
||||
existing_job_metadata_dict[ line['dataset_id'] ] = line
|
||||
elif line['type'] == 'new_primary_dataset':
|
||||
new_job_metadata_dict[ line[ 'filename' ] ] = line
|
||||
except:
|
||||
continue
|
||||
|
||||
for filenames in sys.argv[1:]:
|
||||
fields = filenames.split( ',' )
|
||||
filename_in = fields.pop( 0 )
|
||||
@@ -100,12 +115,13 @@ def __main__():
|
||||
override_metadata = fields.pop( 0 )
|
||||
else:
|
||||
override_metadata = None
|
||||
set_meta_kwds = stringify_dictionary_keys( json.load( open( filename_kwds ) ) ) # load kwds; need to ensure our keywords are not unicode
|
||||
try:
|
||||
dataset = cPickle.load( open( filename_in ) ) # load DatasetInstance
|
||||
if dataset_filename_override:
|
||||
dataset.dataset.external_filename = dataset_filename_override
|
||||
if ext_override.get( dataset.dataset.id, None ):
|
||||
dataset.extension = ext_override[ dataset.dataset.id ]
|
||||
if dataset.dataset.id in existing_job_metadata_dict:
|
||||
dataset.extension = existing_job_metadata_dict[ dataset.dataset.id ].get( 'ext', dataset.extension )
|
||||
# Metadata FileParameter types may not be writable on a cluster node, and are therefore temporarily substituted with MetadataTempFiles
|
||||
if override_metadata:
|
||||
override_metadata = json.load( open( override_metadata ) )
|
||||
@@ -113,12 +129,27 @@ def __main__():
|
||||
if galaxy.datatypes.metadata.MetadataTempFile.is_JSONified_value( metadata_file_override ):
|
||||
metadata_file_override = galaxy.datatypes.metadata.MetadataTempFile.from_JSON( metadata_file_override )
|
||||
setattr( dataset.metadata, metadata_name, metadata_file_override )
|
||||
kwds = stringify_dictionary_keys( json.load( open( filename_kwds ) ) ) # load kwds; need to ensure our keywords are not unicode
|
||||
dataset.datatype.set_meta( dataset, **kwds )
|
||||
file_dict = existing_job_metadata_dict.get( dataset.dataset.id, {} )
|
||||
set_meta_with_tool_provided( dataset, file_dict, set_meta_kwds )
|
||||
dataset.metadata.to_JSON_dict( filename_out ) # write out results of set_meta
|
||||
json.dump( ( True, 'Metadata has been set successfully' ), open( filename_results_code, 'wb+' ) ) # setting metadata has succeeded
|
||||
except Exception, e:
|
||||
json.dump( ( False, str( e ) ), open( filename_results_code, 'wb+' ) ) # setting metadata has failed somehow
|
||||
|
||||
for i, ( filename, file_dict ) in enumerate( new_job_metadata_dict.iteritems(), start=1 ):
|
||||
new_dataset = galaxy.model.Dataset( id=-i, external_filename=os.path.join( tool_job_working_directory, file_dict[ 'filename' ] ) )
|
||||
extra_files = file_dict.get( 'extra_files', None )
|
||||
if extra_files is not None:
|
||||
new_dataset._extra_files_path = os.path.join( tool_job_working_directory, extra_files )
|
||||
new_dataset.state = new_dataset.states.OK
|
||||
new_dataset_instance = galaxy.model.HistoryDatasetAssociation( id=-i, dataset=new_dataset, extension=file_dict.get( 'ext', 'data' ) )
|
||||
set_meta_with_tool_provided( new_dataset_instance, file_dict, set_meta_kwds )
|
||||
file_dict[ 'metadata' ] = json.loads( new_dataset_instance.metadata.to_JSON_dict() ) #storing metadata in external form, need to turn back into dict, then later jsonify
|
||||
if existing_job_metadata_dict or new_job_metadata_dict:
|
||||
with open( job_metadata, 'wb' ) as job_metadata_fh:
|
||||
for value in existing_job_metadata_dict.values() + new_job_metadata_dict.values():
|
||||
job_metadata_fh.write( "%s\n" % ( json.dumps( value ) ) )
|
||||
|
||||
clear_mappers()
|
||||
# Shut down any additional threads that might have been created via the ObjectStore
|
||||
object_store.shutdown()
|
||||
|
||||
@@ -306,7 +306,7 @@ var BaseInputTerminal = Terminal.extend( {
|
||||
inputFilled = false;
|
||||
} else {
|
||||
var firstOutput = this.connectors[ 0 ].handle1;
|
||||
if( firstOutput === null ){
|
||||
if( ! firstOutput ){
|
||||
inputFilled = false;
|
||||
} else {
|
||||
if( firstOutput.isDataCollectionInput || firstOutput.isMappedOver() || firstOutput.datatypes.indexOf( "input_collection" ) > 0 ) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
define([
|
||||
"mvc/dataset/hda-model",
|
||||
"mvc/dataset/hda-base"
|
||||
], function( hdaModel, hdaBase ){
|
||||
"mvc/history/history-content-base-view",
|
||||
"utils/localization"
|
||||
], function( historyContentBaseView, _l ){
|
||||
/* global Backbone, LoggableMixin */
|
||||
//==============================================================================
|
||||
/** @class Read only view for HistoryDatasetCollectionAssociation.
|
||||
@@ -12,10 +12,11 @@ define([
|
||||
* @borrows LoggableMixin#log as #log
|
||||
* @constructs
|
||||
*/
|
||||
var DatasetCollectionBaseView = hdaBase.HistoryContentBaseView.extend({
|
||||
var DatasetCollectionBaseView = historyContentBaseView.HistoryContentBaseView.extend({
|
||||
className : "dataset hda history-panel-hda",
|
||||
id : function(){ return 'hdca-' + this.model.get( 'id' ); },
|
||||
|
||||
/** */
|
||||
initialize : function( attributes ){
|
||||
if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }
|
||||
this.log( this + '.initialize:', attributes );
|
||||
@@ -28,6 +29,7 @@ var DatasetCollectionBaseView = hdaBase.HistoryContentBaseView.extend({
|
||||
this.expanded = attributes.expanded || false;
|
||||
},
|
||||
|
||||
/** */
|
||||
render : function( fade ){
|
||||
var $newRender = this._buildNewRender();
|
||||
|
||||
@@ -35,53 +37,47 @@ var DatasetCollectionBaseView = hdaBase.HistoryContentBaseView.extend({
|
||||
return this;
|
||||
},
|
||||
|
||||
// main template for folder browsing
|
||||
templateSkeleton : function (){
|
||||
return [
|
||||
'<div class="dataset hda">',
|
||||
'<div class="dataset-warnings">',
|
||||
'<% if ( deleted ) { %>',
|
||||
'<div class="dataset-deleted-msg warningmessagesmall"><strong>',
|
||||
_l( 'This dataset has been deleted.' ),
|
||||
'</div>',
|
||||
'<% } %>',
|
||||
'<% if ( ! visible ) { %>',
|
||||
'<div class="dataset-hidden-msg warningmessagesmall"><strong>',
|
||||
_l( 'This dataset has been hidden.' ),
|
||||
'</div>',
|
||||
'<% } %>',
|
||||
'</div>',
|
||||
'<div class="dataset-selector"><span class="fa fa-2x fa-square-o"></span></div>',
|
||||
'<div class="dataset-primary-actions"></div>',
|
||||
'<div class="dataset-title-bar clear" tabindex="0">',
|
||||
'<span class="dataset-state-icon state-icon"></span>',
|
||||
'<div class="dataset-title">',
|
||||
'<span class="hda-hid"><%= hid %></span> ',
|
||||
'<span class="dataset-name"><%= name %></span>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
'<div class="dataset-body"></div>',
|
||||
'</div>'
|
||||
].join( '' );
|
||||
},
|
||||
|
||||
templateBody : function() {
|
||||
return [
|
||||
'<div class="dataset-body">',
|
||||
'<div class="dataset-summary">',
|
||||
'A dataset collection.',
|
||||
'</div>'
|
||||
].join( '' );
|
||||
},
|
||||
|
||||
/** */
|
||||
_buildNewRender : function(){
|
||||
var $newRender = $( _.template(this.templateSkeleton(), this.model.toJSON() ) );
|
||||
var $newRender = $( DatasetCollectionBaseView.templates.skeleton( this.model.toJSON() ) );
|
||||
$newRender.find( '.dataset-primary-actions' ).append( this._render_titleButtons() );
|
||||
$newRender.children( '.dataset-body' ).replaceWith( this._render_body() );
|
||||
this._setUpBehaviors( $newRender );
|
||||
return $newRender;
|
||||
},
|
||||
|
||||
/** */
|
||||
_queueNewRender : function( $newRender, fade ) {
|
||||
fade = ( fade === undefined )?( true ):( fade );
|
||||
var view = this;
|
||||
|
||||
// fade the old render out (if desired)
|
||||
if( fade ){
|
||||
$( view ).queue( function( next ){ this.$el.fadeOut( view.fxSpeed, next ); });
|
||||
}
|
||||
// empty the old render, update to any new HDA state, swap in the new render contents, handle multi-select
|
||||
$( view ).queue( function( next ){
|
||||
this.$el.empty()
|
||||
.attr( 'class', view.className ).addClass( 'state-' + view.model.get( 'state' ) )
|
||||
.append( $newRender.children() );
|
||||
if( this.selectable ){ this.showSelector( 0 ); }
|
||||
next();
|
||||
});
|
||||
// fade the new in
|
||||
if( fade ){
|
||||
$( view ).queue( function( next ){ this.$el.fadeIn( view.fxSpeed, next ); });
|
||||
}
|
||||
// trigger an event to know we're ready
|
||||
$( view ).queue( function( next ){
|
||||
this.trigger( 'rendered', view );
|
||||
if( this.model.inReadyState() ){
|
||||
this.trigger( 'rendered:ready', view );
|
||||
}
|
||||
if( this.draggable ){ this.draggableOn(); }
|
||||
next();
|
||||
});
|
||||
},
|
||||
|
||||
// ................................................................................ titlebar buttons
|
||||
/** Render icon-button group for the common, most easily accessed actions.
|
||||
* @returns {jQuery} rendered DOM
|
||||
@@ -134,6 +130,31 @@ var DatasetCollectionBaseView = hdaBase.HistoryContentBaseView.extend({
|
||||
'click .dataset-selector' : 'toggleSelect'
|
||||
},
|
||||
|
||||
/** Show or hide the body/details of history content.
|
||||
* note: if the model does not have detailed data, fetch that data before showing the body
|
||||
* @param {Event} event the event that triggered this (@link HDABaseView#events)
|
||||
* @param {Boolean} expanded if true, expand; if false, collapse
|
||||
* @fires body-expanded when a body has been expanded
|
||||
* @fires body-collapsed when a body has been collapsed
|
||||
*/
|
||||
toggleBodyVisibility : function( event, expand ){
|
||||
// bail (with propagation) if keydown and not space or enter
|
||||
var KEYCODE_SPACE = 32, KEYCODE_RETURN = 13;
|
||||
if( event && ( event.type === 'keydown' )
|
||||
&& !( event.keyCode === KEYCODE_SPACE || event.keyCode === KEYCODE_RETURN ) ){
|
||||
return true;
|
||||
}
|
||||
|
||||
var $body = this.$el.find( '.dataset-body' );
|
||||
expand = ( expand === undefined )?( !$body.is( ':visible' ) ):( expand );
|
||||
if( expand ){
|
||||
this.expandBody();
|
||||
} else {
|
||||
this.collapseBody();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/** Render and show the full, detailed body of this view including extra data and controls.
|
||||
* @fires body-expanded when a body has been expanded
|
||||
*/
|
||||
@@ -162,13 +183,12 @@ var DatasetCollectionBaseView = hdaBase.HistoryContentBaseView.extend({
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/** Render an 'ok' collection.
|
||||
* @param {jQuery} parent DOM to which to append this body
|
||||
*/
|
||||
_render_body_ok : function(){
|
||||
// most common state renderer and the most complicated
|
||||
var $body = $( _.template( this.templateBody(), this.model.toJSON() ) );
|
||||
var $body = $( DatasetCollectionBaseView.templates.body( this.model.toJSON() ) );
|
||||
|
||||
// return shortened form if del'd (no display apps or peek?)
|
||||
if( this.model.get( 'deleted' ) ){
|
||||
@@ -176,13 +196,133 @@ var DatasetCollectionBaseView = hdaBase.HistoryContentBaseView.extend({
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
},
|
||||
|
||||
// ......................................................................... selection
|
||||
/** display a (fa-icon) checkbox on the left of the hda that fires events when checked
|
||||
* Note: this also hides the primary actions
|
||||
*/
|
||||
showSelector : function(){
|
||||
// make sure selected state is represented properly
|
||||
if( this.selected ){
|
||||
this.select( null, true );
|
||||
}
|
||||
|
||||
this.selectable = true;
|
||||
this.trigger( 'selectable', true, this );
|
||||
|
||||
this.$( '.dataset-primary-actions' ).hide();
|
||||
this.$( '.dataset-selector' ).show();
|
||||
},
|
||||
|
||||
/** remove the selection checkbox */
|
||||
hideSelector : function(){
|
||||
// reverse the process from showSelect
|
||||
this.selectable = false;
|
||||
this.trigger( 'selectable', false, this );
|
||||
|
||||
this.$( '.dataset-selector' ).hide();
|
||||
this.$( '.dataset-primary-actions' ).show();
|
||||
},
|
||||
|
||||
toggleSelector : function(){
|
||||
if( !this.$el.find( '.dataset-selector' ).is( ':visible' ) ){
|
||||
this.showSelector();
|
||||
} else {
|
||||
this.hideSelector();
|
||||
}
|
||||
},
|
||||
|
||||
/** event handler for selection (also programmatic selection)
|
||||
*/
|
||||
select : function( event ){
|
||||
// switch icon, set selected, and trigger event
|
||||
this.$el.find( '.dataset-selector span' )
|
||||
.removeClass( 'fa-square-o' ).addClass( 'fa-check-square-o' );
|
||||
if( !this.selected ){
|
||||
this.trigger( 'selected', this, event );
|
||||
this.selected = true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/** event handler for clearing selection (also programmatic deselection)
|
||||
*/
|
||||
deselect : function( event ){
|
||||
// switch icon, set selected, and trigger event
|
||||
this.$el.find( '.dataset-selector span' )
|
||||
.removeClass( 'fa-check-square-o' ).addClass( 'fa-square-o' );
|
||||
if( this.selected ){
|
||||
this.trigger( 'de-selected', this, event );
|
||||
this.selected = false;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
toggleSelect : function( event ){
|
||||
if( this.selected ){
|
||||
this.deselect( event );
|
||||
} else {
|
||||
this.select( event );
|
||||
}
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'HDCABaseView(' + modelString + ')';
|
||||
}
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
DatasetCollectionBaseView : DatasetCollectionBaseView
|
||||
//------------------------------------------------------------------------------ TEMPLATES
|
||||
//TODO: possibly break these out into a sep. module
|
||||
var skeletonTemplate = _.template([
|
||||
'<div class="dataset hda">',
|
||||
'<div class="dataset-warnings">',
|
||||
'<% if ( collection.deleted ) { %>',
|
||||
'<div class="dataset-deleted-msg warningmessagesmall"><strong>',
|
||||
_l( 'This collection has been deleted.' ),
|
||||
'</div>',
|
||||
'<% } %>',
|
||||
'<% if ( !collection.visible ) { %>',
|
||||
'<div class="dataset-hidden-msg warningmessagesmall"><strong>',
|
||||
_l( 'This collection has been hidden.' ),
|
||||
'</div>',
|
||||
'<% } %>',
|
||||
'</div>',
|
||||
'<div class="dataset-selector"><span class="fa fa-2x fa-square-o"></span></div>',
|
||||
'<div class="dataset-primary-actions"></div>',
|
||||
'<div class="dataset-title-bar clear" tabindex="0">',
|
||||
'<span class="dataset-state-icon state-icon"></span>',
|
||||
'<div class="dataset-title">',
|
||||
'<span class="hda-hid"><%= collection.hid %></span> ',
|
||||
'<span class="dataset-name"><%= collection.name %></span>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
'<div class="dataset-body"></div>',
|
||||
'</div>'
|
||||
].join( '' ));
|
||||
|
||||
var bodyTemplate = _.template([
|
||||
'<div class="dataset-body">',
|
||||
'<div class="dataset-summary">',
|
||||
_l( 'A dataset collection.' ),
|
||||
'</div>'
|
||||
].join( '' ));
|
||||
|
||||
DatasetCollectionBaseView.templates = {
|
||||
// we override here in order to pass the localizer (_L) into the template scope - since we use it as a fn within
|
||||
skeleton : function( collectionJSON ){
|
||||
return skeletonTemplate({ _l: _l, collection: collectionJSON });
|
||||
},
|
||||
body : function( collectionJSON ){
|
||||
return bodyTemplate({ _l: _l, collection: collectionJSON });
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
DatasetCollectionBaseView : DatasetCollectionBaseView
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
define([
|
||||
"mvc/dataset/hda-model",
|
||||
"mvc/collection/dataset-collection-base",
|
||||
], function( hdaModel, datasetCollectionBase ){
|
||||
"utils/localization"
|
||||
], function( hdaModel, datasetCollectionBase, _l ){
|
||||
//==============================================================================
|
||||
/** @class Editing view for HistoryDatasetCollectionAssociation.
|
||||
* @name DatasetCollectionEditView
|
||||
@@ -57,13 +58,18 @@ var DatasetCollectionEditView = datasetCollectionBase.DatasetCollectionBaseView.
|
||||
}
|
||||
deleteBtnData.faIcon = 'fa-times';
|
||||
return faIconButton( deleteBtnData );
|
||||
}
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'HDCAEditView(' + modelString + ')';
|
||||
}
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
DatasetCollectionEditView : DatasetCollectionEditView
|
||||
};
|
||||
|
||||
return {
|
||||
DatasetCollectionEditView : DatasetCollectionEditView
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
define([
|
||||
"mvc/history/history-content-base",
|
||||
"utils/localization"
|
||||
], function( historyContent, _l ){
|
||||
//==============================================================================
|
||||
var HistoryDatasetCollectionAssociation = historyContent.HistoryContent.extend(
|
||||
/** @lends HistoryDatasetCollectionAssociation.prototype */{
|
||||
/** default attributes for a model */
|
||||
defaults : {
|
||||
// parent (containing) history
|
||||
history_id : null,
|
||||
// often used with tagging
|
||||
model_class : 'HistoryDatasetCollectionAssociation',
|
||||
history_content_type : 'dataset_collection',
|
||||
hid : 0,
|
||||
|
||||
id : null,
|
||||
name : '(unnamed dataset collection)',
|
||||
// one of HistoryDatasetAssociation.STATES, calling them all 'ok' for now.
|
||||
state : 'ok',
|
||||
|
||||
accessible : true,
|
||||
deleted : false,
|
||||
visible : true,
|
||||
|
||||
purged : false, // Purged doesn't make sense for collections - at least right now.
|
||||
|
||||
tags : [],
|
||||
annotation : ''
|
||||
},
|
||||
urls : function(){
|
||||
},
|
||||
|
||||
inReadyState : function(){
|
||||
return true; // TODO
|
||||
},
|
||||
|
||||
// ........................................................................ search
|
||||
/** what attributes of an collection will be used in a text search */
|
||||
searchAttributes : [
|
||||
'name'
|
||||
],
|
||||
|
||||
/** our attr keys don't often match the labels we display to the user - so, when using
|
||||
* attribute specifiers ('name="bler"') in a term, allow passing in aliases for the
|
||||
* following attr keys.
|
||||
*/
|
||||
searchAliases : {
|
||||
title : 'name'
|
||||
// TODO: Add tag...
|
||||
},
|
||||
|
||||
// ........................................................................ misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var nameAndId = this.get( 'id' ) || '';
|
||||
if( this.get( 'name' ) ){
|
||||
nameAndId = this.get( 'hid' ) + ' :"' + this.get( 'name' ) + '",' + nameAndId;
|
||||
}
|
||||
return 'HDCA-' + this.get( 'collection_type' ) + '(' + nameAndId + ')';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
HistoryDatasetCollectionAssociation : HistoryDatasetCollectionAssociation
|
||||
};
|
||||
});
|
||||
@@ -1,152 +1,10 @@
|
||||
define([
|
||||
"mvc/dataset/hda-model",
|
||||
"mvc/base-mvc",
|
||||
"mvc/history/history-content-base-view",
|
||||
"mvc/data",
|
||||
"utils/localization"
|
||||
], function( hdaModel, baseMVC, dataset, _l ){
|
||||
], function( hdaModel, historyContentBaseView, dataset, _l ){
|
||||
/* global Backbone */
|
||||
|
||||
/** @class Read only view for history content views to extend.
|
||||
* @name HistoryContentBaseView
|
||||
*
|
||||
* @augments Backbone.View
|
||||
* @borrows LoggableMixin#logger as #logger
|
||||
* @borrows LoggableMixin#log as #log
|
||||
* @constructs
|
||||
*/
|
||||
var HistoryContentBaseView = Backbone.View.extend( baseMVC.LoggableMixin ).extend(
|
||||
/** @lends HistoryContentBaseView.prototype */{
|
||||
tagName : "div",
|
||||
fxSpeed : 'fast',
|
||||
|
||||
_queueNewRender : function( $newRender, fade ) {
|
||||
fade = ( fade === undefined )?( true ):( fade );
|
||||
var view = this;
|
||||
|
||||
// fade the old render out (if desired)
|
||||
if( fade ){
|
||||
$( view ).queue( function( next ){ this.$el.fadeOut( view.fxSpeed, next ); });
|
||||
}
|
||||
// empty the old render, update to any new HDA state, swap in the new render contents, handle multi-select
|
||||
$( view ).queue( function( next ){
|
||||
this.$el.empty()
|
||||
.attr( 'class', view.className ).addClass( 'state-' + view.model.get( 'state' ) )
|
||||
.append( $newRender.children() );
|
||||
if( this.selectable ){ this.showSelector( 0 ); }
|
||||
next();
|
||||
});
|
||||
// fade the new in
|
||||
if( fade ){
|
||||
$( view ).queue( function( next ){ this.$el.fadeIn( view.fxSpeed, next ); });
|
||||
}
|
||||
// trigger an event to know we're ready
|
||||
$( view ).queue( function( next ){
|
||||
this.trigger( 'rendered', view );
|
||||
if( this.model.inReadyState() ){
|
||||
this.trigger( 'rendered:ready', view );
|
||||
}
|
||||
if( this.draggable ){ this.draggableOn(); }
|
||||
next();
|
||||
});
|
||||
},
|
||||
|
||||
/** Show or hide the body/details of history content.
|
||||
* note: if the model does not have detailed data, fetch that data before showing the body
|
||||
* @param {Event} event the event that triggered this (@link HDABaseView#events)
|
||||
* @param {Boolean} expanded if true, expand; if false, collapse
|
||||
* @fires body-expanded when a body has been expanded
|
||||
* @fires body-collapsed when a body has been collapsed
|
||||
*/
|
||||
toggleBodyVisibility : function( event, expand ){
|
||||
// bail (with propagation) if keydown and not space or enter
|
||||
var KEYCODE_SPACE = 32, KEYCODE_RETURN = 13;
|
||||
if( event && ( event.type === 'keydown' )
|
||||
&& !( event.keyCode === KEYCODE_SPACE || event.keyCode === KEYCODE_RETURN ) ){
|
||||
return true;
|
||||
}
|
||||
|
||||
var $body = this.$el.find( '.dataset-body' );
|
||||
expand = ( expand === undefined )?( !$body.is( ':visible' ) ):( expand );
|
||||
if( expand ){
|
||||
this.expandBody();
|
||||
} else {
|
||||
this.collapseBody();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// ......................................................................... selection
|
||||
/** display a (fa-icon) checkbox on the left of the hda that fires events when checked
|
||||
* Note: this also hides the primary actions
|
||||
*/
|
||||
showSelector : function(){
|
||||
// make sure selected state is represented properly
|
||||
if( this.selected ){
|
||||
this.select( null, true );
|
||||
}
|
||||
|
||||
this.selectable = true;
|
||||
this.trigger( 'selectable', true, this );
|
||||
|
||||
this.$( '.dataset-primary-actions' ).hide();
|
||||
this.$( '.dataset-selector' ).show();
|
||||
},
|
||||
|
||||
/** remove the selection checkbox */
|
||||
hideSelector : function(){
|
||||
// reverse the process from showSelect
|
||||
this.selectable = false;
|
||||
this.trigger( 'selectable', false, this );
|
||||
|
||||
this.$( '.dataset-selector' ).hide();
|
||||
this.$( '.dataset-primary-actions' ).show();
|
||||
},
|
||||
|
||||
toggleSelector : function(){
|
||||
if( !this.$el.find( '.dataset-selector' ).is( ':visible' ) ){
|
||||
this.showSelector();
|
||||
} else {
|
||||
this.hideSelector();
|
||||
}
|
||||
},
|
||||
|
||||
/** event handler for selection (also programmatic selection)
|
||||
*/
|
||||
select : function( event ){
|
||||
// switch icon, set selected, and trigger event
|
||||
this.$el.find( '.dataset-selector span' )
|
||||
.removeClass( 'fa-square-o' ).addClass( 'fa-check-square-o' );
|
||||
if( !this.selected ){
|
||||
this.trigger( 'selected', this, event );
|
||||
this.selected = true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/** event handler for clearing selection (also programmatic deselection)
|
||||
*/
|
||||
deselect : function( event ){
|
||||
// switch icon, set selected, and trigger event
|
||||
this.$el.find( '.dataset-selector span' )
|
||||
.removeClass( 'fa-check-square-o' ).addClass( 'fa-square-o' );
|
||||
if( this.selected ){
|
||||
this.trigger( 'de-selected', this, event );
|
||||
this.selected = false;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
toggleSelect : function( event ){
|
||||
if( this.selected ){
|
||||
this.deselect( event );
|
||||
} else {
|
||||
this.select( event );
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @class Read only view for HistoryDatasetAssociation.
|
||||
* @name HDABaseView
|
||||
@@ -156,16 +14,19 @@ var HistoryContentBaseView = Backbone.View.extend( baseMVC.LoggableMixin ).exten
|
||||
* @borrows LoggableMixin#log as #log
|
||||
* @constructs
|
||||
*/
|
||||
var HDABaseView = HistoryContentBaseView.extend(
|
||||
var HDABaseView = historyContentBaseView.HistoryContentBaseView.extend(
|
||||
/** @lends HDABaseView.prototype */{
|
||||
|
||||
///** logger used to record this.log messages, commonly set to console */
|
||||
//// comment this out to suppress log output
|
||||
//logger : console,
|
||||
|
||||
tagName : "div",
|
||||
className : "dataset hda history-panel-hda",
|
||||
id : function(){ return 'hda-' + this.model.get( 'id' ); },
|
||||
|
||||
fxSpeed : 'fast',
|
||||
|
||||
// ......................................................................... set up
|
||||
/** Set up the view, cache url templates, bind listeners
|
||||
* @param {Object} attributes
|
||||
@@ -253,6 +114,37 @@ var HDABaseView = HistoryContentBaseView.extend(
|
||||
return $newRender;
|
||||
},
|
||||
|
||||
_queueNewRender : function( $newRender, fade ) {
|
||||
fade = ( fade === undefined )?( true ):( fade );
|
||||
var view = this;
|
||||
|
||||
// fade the old render out (if desired)
|
||||
if( fade ){
|
||||
$( view ).queue( function( next ){ this.$el.fadeOut( view.fxSpeed, next ); });
|
||||
}
|
||||
// empty the old render, update to any new HDA state, swap in the new render contents, handle multi-select
|
||||
$( view ).queue( function( next ){
|
||||
this.$el.empty()
|
||||
.attr( 'class', view.className ).addClass( 'state-' + view.model.get( 'state' ) )
|
||||
.append( $newRender.children() );
|
||||
if( this.selectable ){ this.showSelector( 0 ); }
|
||||
next();
|
||||
});
|
||||
// fade the new in
|
||||
if( fade ){
|
||||
$( view ).queue( function( next ){ this.$el.fadeIn( view.fxSpeed, next ); });
|
||||
}
|
||||
// trigger an event to know we're ready
|
||||
$( view ).queue( function( next ){
|
||||
this.trigger( 'rendered', view );
|
||||
if( this.model.inReadyState() ){
|
||||
this.trigger( 'rendered:ready', view );
|
||||
}
|
||||
if( this.draggable ){ this.draggableOn(); }
|
||||
next();
|
||||
});
|
||||
},
|
||||
|
||||
/** set up js behaviors, event handlers for elements within the given container
|
||||
* @param {jQuery} $container jq object that contains the elements to process (defaults to this.$el)
|
||||
*/
|
||||
@@ -316,11 +208,11 @@ var HDABaseView = HistoryContentBaseView.extend(
|
||||
if( Galaxy.frame && Galaxy.frame.active ){
|
||||
// Create frame with TabularChunkedView.
|
||||
Galaxy.frame.add({
|
||||
title : "Data Viewer: " + self.model.get('name'),
|
||||
title : "Data Viewer: " + self.model.get( 'name' ),
|
||||
type : "other",
|
||||
content : function(parent_elt) {
|
||||
var new_dataset = new dataset.TabularDataset({id: self.model.get('id')});
|
||||
$.when(new_dataset.fetch()).then(function() {
|
||||
content : function( parent_elt ){
|
||||
var new_dataset = new dataset.TabularDataset({ id: self.model.get( 'id' ) });
|
||||
$.when( new_dataset.fetch() ).then( function(){
|
||||
dataset.createTabularDatasetChunkedView({
|
||||
model: new_dataset,
|
||||
parent_elt: parent_elt,
|
||||
@@ -558,6 +450,31 @@ var HDABaseView = HistoryContentBaseView.extend(
|
||||
'click .dataset-selector' : 'toggleSelect'
|
||||
},
|
||||
|
||||
/** Show or hide the body/details of history content.
|
||||
* note: if the model does not have detailed data, fetch that data before showing the body
|
||||
* @param {Event} event the event that triggered this (@link HDABaseView#events)
|
||||
* @param {Boolean} expanded if true, expand; if false, collapse
|
||||
* @fires body-expanded when a body has been expanded
|
||||
* @fires body-collapsed when a body has been collapsed
|
||||
*/
|
||||
toggleBodyVisibility : function( event, expand ){
|
||||
// bail (with propagation) if keydown and not space or enter
|
||||
var KEYCODE_SPACE = 32, KEYCODE_RETURN = 13;
|
||||
if( event && ( event.type === 'keydown' )
|
||||
&& !( event.keyCode === KEYCODE_SPACE || event.keyCode === KEYCODE_RETURN ) ){
|
||||
return true;
|
||||
}
|
||||
|
||||
var $body = this.$el.find( '.dataset-body' );
|
||||
expand = ( expand === undefined )?( !$body.is( ':visible' ) ):( expand );
|
||||
if( expand ){
|
||||
this.expandBody();
|
||||
} else {
|
||||
this.collapseBody();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/** Render and show the full, detailed body of this view including extra data and controls.
|
||||
* @fires body-expanded when a body has been expanded
|
||||
*/
|
||||
@@ -599,6 +516,75 @@ var HDABaseView = HistoryContentBaseView.extend(
|
||||
});
|
||||
},
|
||||
|
||||
// ......................................................................... selection
|
||||
/** display a (fa-icon) checkbox on the left of the hda that fires events when checked
|
||||
* Note: this also hides the primary actions
|
||||
*/
|
||||
showSelector : function(){
|
||||
// make sure selected state is represented properly
|
||||
if( this.selected ){
|
||||
this.select( null, true );
|
||||
}
|
||||
|
||||
this.selectable = true;
|
||||
this.trigger( 'selectable', true, this );
|
||||
|
||||
this.$( '.dataset-primary-actions' ).hide();
|
||||
this.$( '.dataset-selector' ).show();
|
||||
},
|
||||
|
||||
/** remove the selection checkbox */
|
||||
hideSelector : function(){
|
||||
// reverse the process from showSelect
|
||||
this.selectable = false;
|
||||
this.trigger( 'selectable', false, this );
|
||||
|
||||
this.$( '.dataset-selector' ).hide();
|
||||
this.$( '.dataset-primary-actions' ).show();
|
||||
},
|
||||
|
||||
toggleSelector : function(){
|
||||
if( !this.$el.find( '.dataset-selector' ).is( ':visible' ) ){
|
||||
this.showSelector();
|
||||
} else {
|
||||
this.hideSelector();
|
||||
}
|
||||
},
|
||||
|
||||
/** event handler for selection (also programmatic selection)
|
||||
*/
|
||||
select : function( event ){
|
||||
// switch icon, set selected, and trigger event
|
||||
this.$el.find( '.dataset-selector span' )
|
||||
.removeClass( 'fa-square-o' ).addClass( 'fa-check-square-o' );
|
||||
if( !this.selected ){
|
||||
this.trigger( 'selected', this, event );
|
||||
this.selected = true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/** event handler for clearing selection (also programmatic deselection)
|
||||
*/
|
||||
deselect : function( event ){
|
||||
// switch icon, set selected, and trigger event
|
||||
this.$el.find( '.dataset-selector span' )
|
||||
.removeClass( 'fa-check-square-o' ).addClass( 'fa-square-o' );
|
||||
if( this.selected ){
|
||||
this.trigger( 'de-selected', this, event );
|
||||
this.selected = false;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
toggleSelect : function( event ){
|
||||
if( this.selected ){
|
||||
this.deselect( event );
|
||||
} else {
|
||||
this.select( event );
|
||||
}
|
||||
},
|
||||
|
||||
// ......................................................................... drag/drop
|
||||
draggableOn : function(){
|
||||
this.draggable = true;
|
||||
@@ -824,7 +810,7 @@ HDABaseView.templates = {
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
HistoryContentBaseView : HistoryContentBaseView,
|
||||
HDABaseView : HDABaseView
|
||||
};});
|
||||
return {
|
||||
HDABaseView : HDABaseView
|
||||
};
|
||||
});
|
||||
|
||||
@@ -423,7 +423,7 @@ var HDAEditView = hdaBase.HDABaseView.extend(
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'HDAView(' + modelString + ')';
|
||||
return 'HDAEditView(' + modelString + ')';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -510,6 +510,7 @@ var HDAEditView = hdaBase.HDABaseView.extend(
|
||||
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
HDAEditView : HDAEditView
|
||||
};});
|
||||
return {
|
||||
HDAEditView : HDAEditView
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,169 +1,7 @@
|
||||
define([
|
||||
"mvc/base-mvc",
|
||||
"mvc/history/history-content-base",
|
||||
"utils/localization"
|
||||
], function( baseMVC, _l ){
|
||||
//==============================================================================
|
||||
/** @class model for contents related to a history.
|
||||
* @name HistoryContent
|
||||
*
|
||||
* @augments Backbone.Model
|
||||
* @borrows LoggableMixin#logger as #logger
|
||||
* @borrows LoggableMixin#log as #log
|
||||
* @constructs
|
||||
*/
|
||||
var HistoryContent = Backbone.Model.extend( baseMVC.LoggableMixin ).extend( {
|
||||
idAttribute : 'type_id',
|
||||
|
||||
/** fetch location of this HDA's history in the api */
|
||||
urlRoot: galaxy_config.root + 'api/histories/',
|
||||
|
||||
constructor : function( attrs, options ){
|
||||
attrs.type_id = HistoryContent.typeIdStr( attrs.history_content_type, attrs.id );
|
||||
Backbone.Model.apply( this, arguments );
|
||||
},
|
||||
|
||||
initialize : function( attrs, options ){
|
||||
// assumes type won't change
|
||||
this.on( 'change:id', this._createTypeId );
|
||||
//TODO: not sure this covers all the bases...
|
||||
},
|
||||
|
||||
/** full url spec. for this HDA */
|
||||
url : function(){
|
||||
return this.urlRoot + this.get( 'history_id' ) + '/contents/' + this.get('history_content_type') + 's/' + this.get( 'id' );
|
||||
},
|
||||
|
||||
/** the more common alias of visible */
|
||||
hidden : function(){
|
||||
return !this.get( 'visible' );
|
||||
},
|
||||
|
||||
// ........................................................................ ajax
|
||||
|
||||
/** save this HDA, _Mark_ing it as deleted (just a flag) */
|
||||
'delete' : function _delete( options ){
|
||||
if( this.get( 'deleted' ) ){ return jQuery.when(); }
|
||||
return this.save( { deleted: true }, options );
|
||||
},
|
||||
/** save this HDA, _Mark_ing it as undeleted */
|
||||
undelete : function _undelete( options ){
|
||||
if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }
|
||||
return this.save( { deleted: false }, options );
|
||||
},
|
||||
|
||||
/** save this HDA as not visible */
|
||||
hide : function _hide( options ){
|
||||
if( !this.get( 'visible' ) ){ return jQuery.when(); }
|
||||
return this.save( { visible: false }, options );
|
||||
},
|
||||
/** save this HDA as visible */
|
||||
unhide : function _uhide( options ){
|
||||
if( this.get( 'visible' ) ){ return jQuery.when(); }
|
||||
return this.save( { visible: true }, options );
|
||||
},
|
||||
|
||||
/** based on show_deleted, show_hidden (gen. from the container control),
|
||||
* would this ds show in the list of ds's?
|
||||
* @param {Boolean} show_deleted are we showing deleted hdas?
|
||||
* @param {Boolean} show_hidden are we showing hidden hdas?
|
||||
*/
|
||||
isVisible : function( show_deleted, show_hidden ){
|
||||
var isVisible = true;
|
||||
if( ( !show_deleted )
|
||||
&& ( this.get( 'deleted' ) || this.get( 'purged' ) ) ){
|
||||
isVisible = false;
|
||||
}
|
||||
if( ( !show_hidden )
|
||||
&& ( !this.get( 'visible' ) ) ){
|
||||
isVisible = false;
|
||||
}
|
||||
return isVisible;
|
||||
},
|
||||
|
||||
/** search the attribute with key attrKey for the string searchFor; T/F if found */
|
||||
searchAttribute : function( attrKey, searchFor ){
|
||||
var attrVal = this.get( attrKey );
|
||||
//console.debug( 'searchAttribute', attrKey, attrVal, searchFor );
|
||||
// bail if empty searchFor or unsearchable values
|
||||
if( !searchFor
|
||||
|| ( attrVal === undefined || attrVal === null ) ){
|
||||
return false;
|
||||
}
|
||||
// pass to sep. fn for deep search of array attributes
|
||||
if( _.isArray( attrVal ) ){ return this._searchArrayAttribute( attrVal, searchFor ); }
|
||||
return ( attrVal.toString().toLowerCase().indexOf( searchFor.toLowerCase() ) !== -1 );
|
||||
},
|
||||
|
||||
/** deep(er) search for array attributes; T/F if found */
|
||||
_searchArrayAttribute : function( array, searchFor ){
|
||||
//console.debug( '_searchArrayAttribute', array, searchFor );
|
||||
searchFor = searchFor.toLowerCase();
|
||||
//precondition: searchFor has already been validated as non-empty string
|
||||
//precondition: assumes only 1 level array
|
||||
//TODO: could possibly break up searchFor more (CSV...)
|
||||
return _.any( array, function( elem ){
|
||||
return ( elem.toString().toLowerCase().indexOf( searchFor.toLowerCase() ) !== -1 );
|
||||
});
|
||||
},
|
||||
|
||||
/** search all searchAttributes for the string searchFor,
|
||||
* returning a list of keys of attributes that contain searchFor
|
||||
*/
|
||||
search : function( searchFor ){
|
||||
var model = this;
|
||||
return _.filter( this.searchAttributes, function( key ){
|
||||
return model.searchAttribute( key, searchFor );
|
||||
});
|
||||
},
|
||||
|
||||
/** alias of search, but returns a boolean; accepts attribute specifiers where
|
||||
* the attributes searched can be narrowed to a single attribute using
|
||||
* the form: matches( 'genome_build=hg19' )
|
||||
* (the attribute keys allowed can also be aliases to the true attribute key;
|
||||
* see searchAliases above)
|
||||
* @param {String} term plain text or ATTR_SPECIFIER sep. key=val pair
|
||||
* @returns {Boolean} was term found in (any) attribute(s)
|
||||
*/
|
||||
matches : function( term ){
|
||||
var ATTR_SPECIFIER = '=',
|
||||
split = term.split( ATTR_SPECIFIER );
|
||||
// attribute is specified - search only that
|
||||
if( split.length >= 2 ){
|
||||
var attrKey = split[0];
|
||||
attrKey = this.searchAliases[ attrKey ] || attrKey;
|
||||
return this.searchAttribute( attrKey, split[1] );
|
||||
}
|
||||
// no attribute is specified - search all attributes in searchAttributes
|
||||
return !!this.search( term ).length;
|
||||
},
|
||||
|
||||
/** an implicit AND search for all terms; IOW, an hda must match all terms given
|
||||
* where terms is a whitespace separated value string.
|
||||
* e.g. given terms of: 'blah bler database=hg19'
|
||||
* an HDA would have to have attributes containing blah AND bler AND a genome_build == hg19
|
||||
* To include whitespace in terms: wrap the term in double quotations.
|
||||
*/
|
||||
matchesAll : function( terms ){
|
||||
var model = this;
|
||||
// break the terms up by whitespace and filter out the empty strings
|
||||
terms = terms.match( /(".*"|\w*=".*"|\S*)/g ).filter( function( s ){ return !!s; });
|
||||
return _.all( terms, function( term ){
|
||||
term = term.replace( /"/g, '' );
|
||||
return model.matches( term );
|
||||
});
|
||||
},
|
||||
|
||||
_createTypeId : function(){
|
||||
this.set( 'type_id', TypeIdModel.typeIdStr( this.get( 'history_content_type' ), this.get( 'id' ) ) );
|
||||
},
|
||||
|
||||
} );
|
||||
|
||||
/** create a type + id string for use in mixed collections */
|
||||
HistoryContent.typeIdStr = function _typeId( type, id ){
|
||||
return [ type, id ].join( '-' );
|
||||
};
|
||||
|
||||
], function( historyContent, _l ){
|
||||
//==============================================================================
|
||||
/** @class (HDA) model for a Galaxy dataset
|
||||
* related to a history.
|
||||
@@ -174,7 +12,7 @@ HistoryContent.typeIdStr = function _typeId( type, id ){
|
||||
* @borrows LoggableMixin#log as #log
|
||||
* @constructs
|
||||
*/
|
||||
var HistoryDatasetAssociation = HistoryContent.extend(
|
||||
var HistoryDatasetAssociation = historyContent.HistoryContent.extend(
|
||||
/** @lends HistoryDatasetAssociation.prototype */{
|
||||
|
||||
///** logger used to record this.log messages, commonly set to console */
|
||||
@@ -456,326 +294,9 @@ HistoryDatasetAssociation.NOT_READY_STATES = [
|
||||
HistoryDatasetAssociation.STATES.NEW
|
||||
];
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone collection of (HDA) models
|
||||
* TODO: Rename HistoryContentCollection
|
||||
*
|
||||
* @borrows LoggableMixin#logger as #logger
|
||||
* @borrows LoggableMixin#log as #log
|
||||
* @constructs
|
||||
*/
|
||||
var HDACollection = Backbone.Collection.extend( baseMVC.LoggableMixin ).extend(
|
||||
/** @lends HDACollection.prototype */{
|
||||
model : function( attrs, options ) {
|
||||
if( attrs.history_content_type == "dataset" ) {
|
||||
return new HistoryDatasetAssociation( attrs, options );
|
||||
} else if( attrs.history_content_type == "dataset_collection" ) {
|
||||
return new HistoryDatasetCollectionAssociation( attrs, options );
|
||||
} else {
|
||||
// TODO: Handle unknown history_content_type...
|
||||
}
|
||||
},
|
||||
|
||||
///** logger used to record this.log messages, commonly set to console */
|
||||
//// comment this out to suppress log output
|
||||
//logger : console,
|
||||
|
||||
/** root api url */
|
||||
urlRoot : galaxy_config.root + 'api/histories',
|
||||
/** complete api url */
|
||||
url : function(){
|
||||
return this.urlRoot + '/' + this.historyId + '/contents';
|
||||
},
|
||||
|
||||
/** Set up.
|
||||
* @see Backbone.Collection#initialize
|
||||
*/
|
||||
initialize : function( models, options ){
|
||||
options = options || {};
|
||||
this.historyId = options.historyId;
|
||||
//this._setUpListeners();
|
||||
},
|
||||
|
||||
//_setUpListeners : function(){
|
||||
//},
|
||||
|
||||
// ........................................................................ common queries
|
||||
/** Get the ids of every hda in this collection
|
||||
* @returns array of encoded ids
|
||||
*/
|
||||
ids : function(){
|
||||
return this.map( function( hda ){ return hda.get('id'); });
|
||||
},
|
||||
|
||||
/** Get hdas that are not ready
|
||||
* @returns array of HDAs
|
||||
*/
|
||||
notReady : function(){
|
||||
return this.filter( function( hda ){
|
||||
return !hda.inReadyState();
|
||||
});
|
||||
},
|
||||
|
||||
/** Get the id of every hda in this collection not in a 'ready' state (running).
|
||||
* @returns an array of hda ids
|
||||
* @see HistoryDatasetAssociation#inReadyState
|
||||
*/
|
||||
running : function(){
|
||||
var idList = [];
|
||||
this.each( function( item ){
|
||||
if( !item.inReadyState() ){
|
||||
idList.push( item.get( 'id' ) );
|
||||
}
|
||||
});
|
||||
return idList;
|
||||
},
|
||||
|
||||
/** Get the hda with the given hid
|
||||
* @param {Int} hid the hid to search for
|
||||
* @returns {HistoryDatasetAssociation} the hda with the given hid or undefined if not found
|
||||
*/
|
||||
getByHid : function( hid ){
|
||||
return _.first( this.filter( function( hda ){ return hda.get( 'hid' ) === hid; }) );
|
||||
},
|
||||
|
||||
/** Get every 'shown' hda in this collection based on show_deleted/hidden
|
||||
* @param {Boolean} show_deleted are we showing deleted hdas?
|
||||
* @param {Boolean} show_hidden are we showing hidden hdas?
|
||||
* @returns array of hda models
|
||||
* @see HistoryDatasetAssociation#isVisible
|
||||
*/
|
||||
getVisible : function( show_deleted, show_hidden, filters ){
|
||||
filters = filters || [];
|
||||
//console.debug( 'filters:', filters );
|
||||
//TODO:?? why doesn't this return a collection?
|
||||
// always filter by show deleted/hidden first
|
||||
var filteredHdas = new HDACollection( this.filter( function( item ){
|
||||
return item.isVisible( show_deleted, show_hidden );
|
||||
}));
|
||||
|
||||
_.each( filters, function( filter_fn ){
|
||||
if( !_.isFunction( filter_fn ) ){ return; }
|
||||
filteredHdas = new HDACollection( filteredHdas.filter( filter_fn ) );
|
||||
});
|
||||
//if( filteredHdas.length ){
|
||||
// console.debug( 'filteredHdas:' );
|
||||
// filteredHdas.each( function( hda ){
|
||||
// console.debug( '\t', hda );
|
||||
// });
|
||||
//} else {
|
||||
// console.warn( 'no visible hdas' );
|
||||
//}
|
||||
return filteredHdas;
|
||||
},
|
||||
|
||||
/** return true if any hdas don't have details */
|
||||
haveDetails : function(){
|
||||
return this.all( function( hda ){ return hda.hasDetails(); });
|
||||
},
|
||||
|
||||
// ........................................................................ ajax
|
||||
/** fetch detailed model data for all HDAs in this collection */
|
||||
fetchAllDetails : function( options ){
|
||||
options = options || {};
|
||||
var detailsFlag = { details: 'all' };
|
||||
options.data = ( options.data )?( _.extend( options.data, detailsFlag ) ):( detailsFlag );
|
||||
return this.fetch( options );
|
||||
},
|
||||
|
||||
/** using a queue, perform hdaModelAjaxFn on each of the hdas in this collection */
|
||||
ajaxQueue : function( hdaAjaxFn, options ){
|
||||
var deferred = jQuery.Deferred(),
|
||||
startingLength = this.length,
|
||||
responses = [];
|
||||
|
||||
if( !startingLength ){
|
||||
deferred.resolve([]);
|
||||
return deferred;
|
||||
}
|
||||
|
||||
// use reverse order (stylistic choice)
|
||||
var ajaxFns = this.chain().reverse().map( function( hda, i ){
|
||||
return function(){
|
||||
var xhr = hdaAjaxFn.call( hda, options );
|
||||
// if successful, notify using the deferred to allow tracking progress
|
||||
xhr.done( function( response ){
|
||||
deferred.notify({ curr: i, total: startingLength, response: response, model: hda });
|
||||
});
|
||||
// (regardless of previous error or success) if not last ajax call, shift and call the next
|
||||
// if last fn, resolve deferred
|
||||
xhr.always( function( response ){
|
||||
responses.push( response );
|
||||
if( ajaxFns.length ){
|
||||
ajaxFns.shift()();
|
||||
} else {
|
||||
deferred.resolve( responses );
|
||||
}
|
||||
});
|
||||
};
|
||||
}).value();
|
||||
// start the queue
|
||||
ajaxFns.shift()();
|
||||
|
||||
return deferred;
|
||||
},
|
||||
|
||||
// ........................................................................ sorting/filtering
|
||||
/** return a new collection of HDAs whose attributes contain the substring matchesWhat */
|
||||
matches : function( matchesWhat ){
|
||||
return this.filter( function( hda ){
|
||||
return hda.matches( matchesWhat );
|
||||
});
|
||||
},
|
||||
|
||||
// ........................................................................ misc
|
||||
set : function( models, options ){
|
||||
// arrrrrrrrrrrrrrrrrg...
|
||||
// override to get a correct/smarter merge when incoming data is partial (e.g. stupid backbone)
|
||||
// w/o this partial models from the server will fill in missing data with model defaults
|
||||
// and overwrite existing data on the client
|
||||
// see Backbone.Collection.set and _prepareModel
|
||||
var collection = this;
|
||||
models = _.map( models, function( model ){
|
||||
var attrs = model.attributes || model; // Handle raw json or Backbone model.
|
||||
var typeId = HistoryContent.typeIdStr( attrs.history_content_type, attrs.id );
|
||||
var existing = collection.get( typeId );
|
||||
if( !existing ){ return model; }
|
||||
|
||||
// merge the models _BEFORE_ calling the superclass version
|
||||
var merged = existing.toJSON();
|
||||
_.extend( merged, model );
|
||||
return merged;
|
||||
});
|
||||
// now call superclass when the data is filled
|
||||
Backbone.Collection.prototype.set.call( this, models, options );
|
||||
},
|
||||
|
||||
/** Convert this ad-hoc collection of HDAs to a formal collection tracked
|
||||
by the server.
|
||||
**/
|
||||
promoteToHistoryDatasetCollection : function _promote( history, collection_type, options ){
|
||||
options = options || {};
|
||||
options.url = this.url();
|
||||
options.type = "POST";
|
||||
var full_collection_type = collection_type;
|
||||
var element_identifiers = [],
|
||||
name = null;
|
||||
|
||||
// This mechanism is rough - no error handling, allows invalid selections, no way
|
||||
// for user to pick/override element identifiers. This is only really meant
|
||||
if( collection_type == "list" ) {
|
||||
this.chain().each( function( hda ) {
|
||||
// TODO: Handle duplicate names.
|
||||
var name = hda.attributes.name;
|
||||
var id = hda.get('id');
|
||||
var content_type = hda.attributes.history_content_type;
|
||||
if( content_type == "dataset" ) {
|
||||
if( full_collection_type != "list" ) {
|
||||
console.log( "Invalid collection type" );
|
||||
}
|
||||
element_identifiers.push( { name: name, src: "hda", id: id } );
|
||||
} else {
|
||||
if( full_collection_type == "list" ) {
|
||||
full_collection_type = "list:" + hda.attributes.collection_type;
|
||||
} else {
|
||||
if( full_collection_type != "list:" + hda.attributes.collection_type ) {
|
||||
console.log( "Invalid collection type" );
|
||||
}
|
||||
}
|
||||
element_identifiers.push( { name: name, src: "hdca", id: id } );
|
||||
}
|
||||
});
|
||||
name = "New Dataset List";
|
||||
} else if( collection_type == "paired" ) {
|
||||
var ids = this.ids();
|
||||
if( ids.length != 2 ){
|
||||
// TODO: Do something...
|
||||
}
|
||||
element_identifiers.push( { name: "forward", src: "hda", id: ids[ 0 ] } );
|
||||
element_identifiers.push( { name: "reverse", src: "hda", id: ids[ 1 ] } );
|
||||
name = "New Dataset Pair";
|
||||
}
|
||||
options.data = {type: "dataset_collection",
|
||||
name: name,
|
||||
collection_type: full_collection_type,
|
||||
element_identifiers: JSON.stringify(element_identifiers),
|
||||
};
|
||||
|
||||
var xhr = jQuery.ajax( options );
|
||||
xhr.done( function( message, status, responseObj ){
|
||||
history.refresh( );
|
||||
});
|
||||
xhr.fail( function( xhr, status, message ){
|
||||
if( xhr.responseJSON && xhr.responseJSON.error ){
|
||||
error = xhr.responseJSON.error;
|
||||
} else {
|
||||
error = xhr.responseJSON;
|
||||
}
|
||||
xhr.responseText = error;
|
||||
// Do something?
|
||||
});
|
||||
return xhr;
|
||||
},
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return ([ 'HDACollection(', [ this.historyId, this.length ].join(), ')' ].join( '' ));
|
||||
}
|
||||
});
|
||||
|
||||
var HistoryDatasetCollectionAssociation = HistoryContent.extend(
|
||||
/** @lends HistoryDatasetCollectionAssociation.prototype */{
|
||||
/** default attributes for a model */
|
||||
defaults : {
|
||||
// parent (containing) history
|
||||
history_id : null,
|
||||
// often used with tagging
|
||||
model_class : 'HistoryDatasetCollectionAssociation',
|
||||
history_content_type : 'dataset_collection',
|
||||
hid : 0,
|
||||
|
||||
id : null,
|
||||
name : '(unnamed dataset collection)',
|
||||
// one of HistoryDatasetAssociation.STATES, calling them all 'ok' for now.
|
||||
state : 'ok',
|
||||
|
||||
accessible : true,
|
||||
deleted : false,
|
||||
visible : true,
|
||||
|
||||
purged : false, // Purged doesn't make sense for collections - at least right now.
|
||||
|
||||
tags : [],
|
||||
annotation : ''
|
||||
},
|
||||
urls : function(){
|
||||
},
|
||||
|
||||
inReadyState : function(){
|
||||
return true; // TODO
|
||||
},
|
||||
|
||||
// ........................................................................ search
|
||||
/** what attributes of an collection will be used in a text search */
|
||||
searchAttributes : [
|
||||
'name'
|
||||
],
|
||||
|
||||
/** our attr keys don't often match the labels we display to the user - so, when using
|
||||
* attribute specifiers ('name="bler"') in a term, allow passing in aliases for the
|
||||
* following attr keys.
|
||||
*/
|
||||
searchAliases : {
|
||||
title : 'name'
|
||||
// TODO: Add tag...
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
HistoryDatasetAssociation : HistoryDatasetAssociation,
|
||||
HDACollection : HDACollection
|
||||
};});
|
||||
return {
|
||||
HistoryDatasetAssociation : HistoryDatasetAssociation
|
||||
};
|
||||
});
|
||||
|
||||
@@ -560,25 +560,24 @@ return Backbone.View.extend({
|
||||
if (this.grid.can_async_op(operation)) {
|
||||
this.update_grid();
|
||||
} else {
|
||||
this.go_to(inbound, '');
|
||||
this.go_to(inbound, href);
|
||||
}
|
||||
|
||||
// done
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for href details
|
||||
if (href)
|
||||
{
|
||||
// refresh grid
|
||||
if (href) {
|
||||
this.go_to(inbound, href);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// refresh grid
|
||||
if (this.grid.get('async')) {
|
||||
this.update_grid();
|
||||
} else {
|
||||
this.go_to(inbound, '');
|
||||
this.go_to(inbound, href);
|
||||
}
|
||||
|
||||
// done
|
||||
@@ -596,9 +595,10 @@ return Backbone.View.extend({
|
||||
this.grid.set('advanced_search', advanced_search);
|
||||
|
||||
// get default url
|
||||
if(!href)
|
||||
if(!href) {
|
||||
href = this.grid.get('url_base') + '?' + $.param(this.grid.get_url_data());
|
||||
|
||||
}
|
||||
|
||||
// clear grid of transient request attributes.
|
||||
this.grid.set({
|
||||
operation: undefined,
|
||||
|
||||
@@ -53,7 +53,7 @@ var CurrentHistoryPanel = hpanel.HistoryPanel.extend(
|
||||
noneFoundMsg : _l( "No matching datasets found" ),
|
||||
|
||||
// ......................................................................... SET UP
|
||||
/** Set up the view, set up storage, bind listeners to HDACollection events
|
||||
/** Set up the view, set up storage, bind listeners to HistoryContents events
|
||||
* @param {Object} attributes
|
||||
*/
|
||||
initialize : function( attributes ){
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
define([
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( baseMVC, _l ){
|
||||
/* global Backbone */
|
||||
//==============================================================================
|
||||
/** @class Read only view for history content views to extend.
|
||||
* @name HistoryContentBaseView
|
||||
*
|
||||
* @augments Backbone.View
|
||||
* @borrows LoggableMixin#logger as #logger
|
||||
* @borrows LoggableMixin#log as #log
|
||||
* @constructs
|
||||
*/
|
||||
var HistoryContentBaseView = Backbone.View.extend( baseMVC.LoggableMixin ).extend(
|
||||
/** @lends HistoryContentBaseView.prototype */{
|
||||
});
|
||||
|
||||
//TODO: not sure base view class is warranted or even wise
|
||||
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
HistoryContentBaseView : HistoryContentBaseView
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,181 @@
|
||||
define([
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( baseMVC, _l ){
|
||||
//==============================================================================
|
||||
/** @class base model for content items contained in a history or hdca.
|
||||
* @name HistoryContent
|
||||
*
|
||||
* @augments Backbone.Model
|
||||
* @borrows LoggableMixin#logger as #logger
|
||||
* @borrows LoggableMixin#log as #log
|
||||
* @constructs
|
||||
*/
|
||||
var HistoryContent = Backbone.Model.extend( baseMVC.LoggableMixin ).extend( {
|
||||
idAttribute : 'type_id',
|
||||
|
||||
constructor : function( attrs, options ){
|
||||
attrs.type_id = HistoryContent.typeIdStr( attrs.history_content_type, attrs.id );
|
||||
Backbone.Model.apply( this, arguments );
|
||||
},
|
||||
|
||||
initialize : function( attrs, options ){
|
||||
// assumes type won't change
|
||||
this.on( 'change:id', this._createTypeId );
|
||||
//TODO: not sure this covers all the bases...
|
||||
},
|
||||
_createTypeId : function(){
|
||||
this.set( 'type_id', TypeIdModel.typeIdStr( this.get( 'history_content_type' ), this.get( 'id' ) ) );
|
||||
},
|
||||
|
||||
// ........................................................................ common queries
|
||||
/** the more common alias of visible */
|
||||
hidden : function(){
|
||||
return !this.get( 'visible' );
|
||||
},
|
||||
|
||||
/** based on show_deleted, show_hidden (gen. from the container control),
|
||||
* would this ds show in the list of ds's?
|
||||
* @param {Boolean} show_deleted are we showing deleted hdas?
|
||||
* @param {Boolean} show_hidden are we showing hidden hdas?
|
||||
*/
|
||||
isVisible : function( show_deleted, show_hidden ){
|
||||
var isVisible = true;
|
||||
if( ( !show_deleted )
|
||||
&& ( this.get( 'deleted' ) || this.get( 'purged' ) ) ){
|
||||
isVisible = false;
|
||||
}
|
||||
if( ( !show_hidden )
|
||||
&& ( !this.get( 'visible' ) ) ){
|
||||
isVisible = false;
|
||||
}
|
||||
return isVisible;
|
||||
},
|
||||
|
||||
// ........................................................................ ajax
|
||||
/** */
|
||||
urlRoot: galaxy_config.root + 'api/histories/',
|
||||
|
||||
/** full url spec. for this HDA */
|
||||
url : function(){
|
||||
return this.urlRoot + this.get( 'history_id' ) + '/contents/'
|
||||
+ this.get('history_content_type') + 's/' + this.get( 'id' );
|
||||
},
|
||||
|
||||
/** save this HDA, _Mark_ing it as deleted (just a flag) */
|
||||
'delete' : function _delete( options ){
|
||||
if( this.get( 'deleted' ) ){ return jQuery.when(); }
|
||||
return this.save( { deleted: true }, options );
|
||||
},
|
||||
/** save this HDA, _Mark_ing it as undeleted */
|
||||
undelete : function _undelete( options ){
|
||||
if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }
|
||||
return this.save( { deleted: false }, options );
|
||||
},
|
||||
|
||||
/** save this HDA as not visible */
|
||||
hide : function _hide( options ){
|
||||
if( !this.get( 'visible' ) ){ return jQuery.when(); }
|
||||
return this.save( { visible: false }, options );
|
||||
},
|
||||
/** save this HDA as visible */
|
||||
unhide : function _uhide( options ){
|
||||
if( this.get( 'visible' ) ){ return jQuery.when(); }
|
||||
return this.save( { visible: true }, options );
|
||||
},
|
||||
|
||||
// ........................................................................ searching
|
||||
/** search the attribute with key attrKey for the string searchFor; T/F if found */
|
||||
searchAttribute : function( attrKey, searchFor ){
|
||||
var attrVal = this.get( attrKey );
|
||||
//console.debug( 'searchAttribute', attrKey, attrVal, searchFor );
|
||||
// bail if empty searchFor or unsearchable values
|
||||
if( !searchFor
|
||||
|| ( attrVal === undefined || attrVal === null ) ){
|
||||
return false;
|
||||
}
|
||||
// pass to sep. fn for deep search of array attributes
|
||||
if( _.isArray( attrVal ) ){ return this._searchArrayAttribute( attrVal, searchFor ); }
|
||||
return ( attrVal.toString().toLowerCase().indexOf( searchFor.toLowerCase() ) !== -1 );
|
||||
},
|
||||
|
||||
/** deep(er) search for array attributes; T/F if found */
|
||||
_searchArrayAttribute : function( array, searchFor ){
|
||||
//console.debug( '_searchArrayAttribute', array, searchFor );
|
||||
searchFor = searchFor.toLowerCase();
|
||||
//precondition: searchFor has already been validated as non-empty string
|
||||
//precondition: assumes only 1 level array
|
||||
//TODO: could possibly break up searchFor more (CSV...)
|
||||
return _.any( array, function( elem ){
|
||||
return ( elem.toString().toLowerCase().indexOf( searchFor.toLowerCase() ) !== -1 );
|
||||
});
|
||||
},
|
||||
|
||||
/** search all searchAttributes for the string searchFor,
|
||||
* returning a list of keys of attributes that contain searchFor
|
||||
*/
|
||||
search : function( searchFor ){
|
||||
var model = this;
|
||||
return _.filter( this.searchAttributes, function( key ){
|
||||
return model.searchAttribute( key, searchFor );
|
||||
});
|
||||
},
|
||||
|
||||
/** alias of search, but returns a boolean; accepts attribute specifiers where
|
||||
* the attributes searched can be narrowed to a single attribute using
|
||||
* the form: matches( 'genome_build=hg19' )
|
||||
* (the attribute keys allowed can also be aliases to the true attribute key;
|
||||
* see searchAliases above)
|
||||
* @param {String} term plain text or ATTR_SPECIFIER sep. key=val pair
|
||||
* @returns {Boolean} was term found in (any) attribute(s)
|
||||
*/
|
||||
matches : function( term ){
|
||||
var ATTR_SPECIFIER = '=',
|
||||
split = term.split( ATTR_SPECIFIER );
|
||||
// attribute is specified - search only that
|
||||
if( split.length >= 2 ){
|
||||
var attrKey = split[0];
|
||||
attrKey = this.searchAliases[ attrKey ] || attrKey;
|
||||
return this.searchAttribute( attrKey, split[1] );
|
||||
}
|
||||
// no attribute is specified - search all attributes in searchAttributes
|
||||
return !!this.search( term ).length;
|
||||
},
|
||||
|
||||
/** an implicit AND search for all terms; IOW, an hda must match all terms given
|
||||
* where terms is a whitespace separated value string.
|
||||
* e.g. given terms of: 'blah bler database=hg19'
|
||||
* an HDA would have to have attributes containing blah AND bler AND a genome_build == hg19
|
||||
* To include whitespace in terms: wrap the term in double quotations.
|
||||
*/
|
||||
matchesAll : function( terms ){
|
||||
var model = this;
|
||||
// break the terms up by whitespace and filter out the empty strings
|
||||
terms = terms.match( /(".*"|\w*=".*"|\S*)/g ).filter( function( s ){ return !!s; });
|
||||
return _.all( terms, function( term ){
|
||||
term = term.replace( /"/g, '' );
|
||||
return model.matches( term );
|
||||
});
|
||||
},
|
||||
|
||||
// ........................................................................ misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var nameAndId = this.get( 'id' ) || '';
|
||||
if( this.get( 'name' ) ){
|
||||
nameAndId = this.get( 'hid' ) + ' :"' + this.get( 'name' ) + '",' + nameAndId;
|
||||
}
|
||||
return 'HistoryContent(' + nameAndId + ')';
|
||||
}
|
||||
});
|
||||
|
||||
/** create a type + id string for use in mixed collections */
|
||||
HistoryContent.typeIdStr = function _typeId( type, id ){
|
||||
return [ type, id ].join( '-' );
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
HistoryContent : HistoryContent
|
||||
};});
|
||||
@@ -0,0 +1,277 @@
|
||||
define([
|
||||
"mvc/history/history-content-base",
|
||||
"mvc/dataset/hda-model",
|
||||
"mvc/collection/hdca-model",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( historyContent, hdaModel, hdcaModel, baseMVC, _l ){
|
||||
//==============================================================================
|
||||
/** @class Backbone collection for history content.
|
||||
* NOTE: history content seems like a dataset collection, but differs in that it is mixed:
|
||||
* each element can be either an HDA (dataset) or a DatasetCollection and co-exist on
|
||||
* the same level.
|
||||
* Dataset collections on the other hand are not mixed and (so far) can only contain either
|
||||
* HDAs or child dataset collections on one level.
|
||||
* This is why this does not inherit from any of the DatasetCollections (currently).
|
||||
*
|
||||
* @borrows LoggableMixin#logger as #logger
|
||||
* @borrows LoggableMixin#log as #log
|
||||
* @constructs
|
||||
*/
|
||||
var HistoryContents = Backbone.Collection.extend( baseMVC.LoggableMixin ).extend(
|
||||
/** @lends HistoryContents.prototype */{
|
||||
|
||||
///** logger used to record this.log messages, commonly set to console */
|
||||
//// comment this out to suppress log output
|
||||
//logger : console,
|
||||
|
||||
/** since history content is a mix, override model fn into a factory, creating based on history_content_type */
|
||||
model : function( attrs, options ) {
|
||||
if( attrs.history_content_type === "dataset" ) {
|
||||
return new hdaModel.HistoryDatasetAssociation( attrs, options );
|
||||
} else if( attrs.history_content_type === "dataset_collection" ) {
|
||||
return new hdcaModel.HistoryDatasetCollectionAssociation( attrs, options );
|
||||
}
|
||||
// TODO: Handle unknown history_content_type...
|
||||
throw new TypeError( 'Unknown history_content_type:' + attrs.history_content_type );
|
||||
},
|
||||
|
||||
/** Set up.
|
||||
* @see Backbone.Collection#initialize
|
||||
*/
|
||||
initialize : function( models, options ){
|
||||
options = options || {};
|
||||
this.historyId = options.historyId;
|
||||
//this._setUpListeners();
|
||||
},
|
||||
|
||||
/** root api url */
|
||||
urlRoot : galaxy_config.root + 'api/histories',
|
||||
/** complete api url */
|
||||
url : function(){
|
||||
return this.urlRoot + '/' + this.historyId + '/contents';
|
||||
},
|
||||
|
||||
// ........................................................................ common queries
|
||||
/** Get the ids of every hda in this collection
|
||||
* @returns array of encoded ids
|
||||
*/
|
||||
ids : function(){
|
||||
return this.map( function( hda ){ return hda.get('id'); });
|
||||
},
|
||||
|
||||
/** Get hdas that are not ready
|
||||
* @returns array of HDAs
|
||||
*/
|
||||
notReady : function(){
|
||||
return this.filter( function( hda ){
|
||||
return !hda.inReadyState();
|
||||
});
|
||||
},
|
||||
|
||||
/** Get the id of every hda in this collection not in a 'ready' state (running).
|
||||
* @returns an array of hda ids
|
||||
* @see HistoryDatasetAssociation#inReadyState
|
||||
*/
|
||||
running : function(){
|
||||
var idList = [];
|
||||
this.each( function( item ){
|
||||
if( !item.inReadyState() ){
|
||||
idList.push( item.get( 'id' ) );
|
||||
}
|
||||
});
|
||||
return idList;
|
||||
},
|
||||
|
||||
/** Get the hda with the given hid
|
||||
* @param {Int} hid the hid to search for
|
||||
* @returns {HistoryDatasetAssociation} the hda with the given hid or undefined if not found
|
||||
*/
|
||||
getByHid : function( hid ){
|
||||
return _.first( this.filter( function( hda ){ return hda.get( 'hid' ) === hid; }) );
|
||||
},
|
||||
|
||||
/** Get every 'shown' hda in this collection based on show_deleted/hidden
|
||||
* @param {Boolean} show_deleted are we showing deleted hdas?
|
||||
* @param {Boolean} show_hidden are we showing hidden hdas?
|
||||
* @returns array of hda models
|
||||
* @see HistoryDatasetAssociation#isVisible
|
||||
*/
|
||||
getVisible : function( show_deleted, show_hidden, filters ){
|
||||
filters = filters || [];
|
||||
//console.debug( 'filters:', filters );
|
||||
//TODO:?? why doesn't this return a collection?
|
||||
// always filter by show deleted/hidden first
|
||||
var filteredHdas = new HistoryContents( this.filter( function( item ){
|
||||
return item.isVisible( show_deleted, show_hidden );
|
||||
}));
|
||||
|
||||
_.each( filters, function( filter_fn ){
|
||||
if( !_.isFunction( filter_fn ) ){ return; }
|
||||
filteredHdas = new HistoryContents( filteredHdas.filter( filter_fn ) );
|
||||
});
|
||||
return filteredHdas;
|
||||
},
|
||||
|
||||
/** return true if any hdas don't have details */
|
||||
haveDetails : function(){
|
||||
return this.all( function( hda ){ return hda.hasDetails(); });
|
||||
},
|
||||
|
||||
// ........................................................................ ajax
|
||||
/** fetch detailed model data for all HDAs in this collection */
|
||||
fetchAllDetails : function( options ){
|
||||
options = options || {};
|
||||
var detailsFlag = { details: 'all' };
|
||||
options.data = ( options.data )?( _.extend( options.data, detailsFlag ) ):( detailsFlag );
|
||||
return this.fetch( options );
|
||||
},
|
||||
|
||||
/** using a queue, perform hdaModelAjaxFn on each of the hdas in this collection */
|
||||
ajaxQueue : function( hdaAjaxFn, options ){
|
||||
var deferred = jQuery.Deferred(),
|
||||
startingLength = this.length,
|
||||
responses = [];
|
||||
|
||||
if( !startingLength ){
|
||||
deferred.resolve([]);
|
||||
return deferred;
|
||||
}
|
||||
|
||||
// use reverse order (stylistic choice)
|
||||
var ajaxFns = this.chain().reverse().map( function( hda, i ){
|
||||
return function(){
|
||||
var xhr = hdaAjaxFn.call( hda, options );
|
||||
// if successful, notify using the deferred to allow tracking progress
|
||||
xhr.done( function( response ){
|
||||
deferred.notify({ curr: i, total: startingLength, response: response, model: hda });
|
||||
});
|
||||
// (regardless of previous error or success) if not last ajax call, shift and call the next
|
||||
// if last fn, resolve deferred
|
||||
xhr.always( function( response ){
|
||||
responses.push( response );
|
||||
if( ajaxFns.length ){
|
||||
ajaxFns.shift()();
|
||||
} else {
|
||||
deferred.resolve( responses );
|
||||
}
|
||||
});
|
||||
};
|
||||
}).value();
|
||||
// start the queue
|
||||
ajaxFns.shift()();
|
||||
|
||||
return deferred;
|
||||
},
|
||||
|
||||
// ........................................................................ sorting/filtering
|
||||
/** return a new collection of HDAs whose attributes contain the substring matchesWhat */
|
||||
matches : function( matchesWhat ){
|
||||
return this.filter( function( hda ){
|
||||
return hda.matches( matchesWhat );
|
||||
});
|
||||
},
|
||||
|
||||
// ........................................................................ misc
|
||||
set : function( models, options ){
|
||||
// arrrrrrrrrrrrrrrrrg...
|
||||
// override to get a correct/smarter merge when incoming data is partial (e.g. stupid backbone)
|
||||
// w/o this partial models from the server will fill in missing data with model defaults
|
||||
// and overwrite existing data on the client
|
||||
// see Backbone.Collection.set and _prepareModel
|
||||
var collection = this;
|
||||
models = _.map( models, function( model ){
|
||||
var attrs = model.attributes || model; // Handle raw json or Backbone model.
|
||||
var typeId = historyContent.HistoryContent.typeIdStr( attrs.history_content_type, attrs.id );
|
||||
var existing = collection.get( typeId );
|
||||
if( !existing ){ return model; }
|
||||
|
||||
// merge the models _BEFORE_ calling the superclass version
|
||||
var merged = existing.toJSON();
|
||||
_.extend( merged, model );
|
||||
return merged;
|
||||
});
|
||||
// now call superclass when the data is filled
|
||||
Backbone.Collection.prototype.set.call( this, models, options );
|
||||
},
|
||||
|
||||
/** Convert this ad-hoc collection of HDAs to a formal collection tracked
|
||||
by the server.
|
||||
**/
|
||||
promoteToHistoryDatasetCollection : function _promote( history, collection_type, options ){
|
||||
options = options || {};
|
||||
options.url = this.url();
|
||||
options.type = "POST";
|
||||
var full_collection_type = collection_type;
|
||||
var element_identifiers = [],
|
||||
name = null;
|
||||
|
||||
// This mechanism is rough - no error handling, allows invalid selections, no way
|
||||
// for user to pick/override element identifiers. This is only really meant
|
||||
if( collection_type === "list" ) {
|
||||
this.chain().each( function( hda ) {
|
||||
// TODO: Handle duplicate names.
|
||||
var name = hda.attributes.name;
|
||||
var id = hda.get('id');
|
||||
var content_type = hda.attributes.history_content_type;
|
||||
if( content_type === "dataset" ) {
|
||||
if( full_collection_type !== "list" ) {
|
||||
console.log( "Invalid collection type" );
|
||||
}
|
||||
element_identifiers.push( { name: name, src: "hda", id: id } );
|
||||
} else {
|
||||
if( full_collection_type === "list" ) {
|
||||
full_collection_type = "list:" + hda.attributes.collection_type;
|
||||
} else {
|
||||
if( full_collection_type !== "list:" + hda.attributes.collection_type ) {
|
||||
console.log( "Invalid collection type" );
|
||||
}
|
||||
}
|
||||
element_identifiers.push( { name: name, src: "hdca", id: id } );
|
||||
}
|
||||
});
|
||||
name = "New Dataset List";
|
||||
} else if( collection_type === "paired" ) {
|
||||
var ids = this.ids();
|
||||
if( ids.length !== 2 ){
|
||||
// TODO: Do something...
|
||||
}
|
||||
element_identifiers.push( { name: "forward", src: "hda", id: ids[ 0 ] } );
|
||||
element_identifiers.push( { name: "reverse", src: "hda", id: ids[ 1 ] } );
|
||||
name = "New Dataset Pair";
|
||||
}
|
||||
options.data = {
|
||||
type: "dataset_collection",
|
||||
name: name,
|
||||
collection_type: full_collection_type,
|
||||
element_identifiers: JSON.stringify( element_identifiers )
|
||||
};
|
||||
|
||||
var xhr = jQuery.ajax( options );
|
||||
xhr.done( function( message, status, responseObj ){
|
||||
history.refresh( );
|
||||
});
|
||||
xhr.fail( function( xhr, status, message ){
|
||||
if( xhr.responseJSON && xhr.responseJSON.error ){
|
||||
error = xhr.responseJSON.error;
|
||||
} else {
|
||||
error = xhr.responseJSON;
|
||||
}
|
||||
xhr.responseText = error;
|
||||
// Do something?
|
||||
});
|
||||
return xhr;
|
||||
},
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return ([ 'HistoryContents(', [ this.parentId, this.length ].join(), ')' ].join( '' ));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
HistoryContents : HistoryContents
|
||||
};
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
define([
|
||||
"mvc/dataset/hda-model",
|
||||
"mvc/history/history-contents",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( hdaModel, baseMVC, _l ){
|
||||
], function( historyContents, baseMVC, _l ){
|
||||
//==============================================================================
|
||||
/** @class Model for a Galaxy history resource - both a record of user
|
||||
* tool use and a collection of the datasets those tools produced.
|
||||
@@ -45,8 +45,8 @@ var History = Backbone.Model.extend( baseMVC.LoggableMixin ).extend(
|
||||
this.logger = options.logger || null;
|
||||
this.log( this + ".initialize:", historyJSON, hdaJSON, options );
|
||||
|
||||
/** HDACollection of the HDAs contained in this history. */
|
||||
this.hdas = new hdaModel.HDACollection( hdaJSON || [], { historyId: this.get( 'id' )});
|
||||
/** HistoryContents collection of the HDAs contained in this history. */
|
||||
this.hdas = new historyContents.HistoryContents( hdaJSON || [], { historyId: this.get( 'id' )});
|
||||
// if we've got hdas passed in the constructor, load them
|
||||
if( hdaJSON && _.isArray( hdaJSON ) ){
|
||||
this.hdas.reset( hdaJSON );
|
||||
@@ -230,12 +230,12 @@ History.getHistoryData = function getHistoryData( historyId, options ){
|
||||
}
|
||||
var data = {};
|
||||
if( hdaDetailIds.length ) {
|
||||
data[ "dataset_details" ] = hdaDetailIds.join( ',' );
|
||||
data.dataset_details = hdaDetailIds.join( ',' );
|
||||
}
|
||||
if( hdcaDetailIds.length ) {
|
||||
// for symmetry, not actually used by backend of consumed
|
||||
// by frontend.
|
||||
data[ "dataset_collection_details" ] = hdcaDetailIds.join( ',' );
|
||||
data.dataset_collection_details = hdcaDetailIds.join( ',' );
|
||||
}
|
||||
return jQuery.ajax( galaxy_config.root + 'api/histories/' + historyData.id + '/contents', { data: data });
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ define([
|
||||
"mvc/dataset/hda-model",
|
||||
"mvc/dataset/hda-edit",
|
||||
"mvc/collection/dataset-collection-edit",
|
||||
"mvc/history/history-contents",
|
||||
"mvc/history/readonly-history-panel",
|
||||
"mvc/tags",
|
||||
"mvc/annotations",
|
||||
"utils/localization"
|
||||
], function( hdaModel, hdaEdit, datasetCollectionEdit, readonlyPanel, tagsMod, annotationsMod, _l ){
|
||||
], function( hdaModel, hdaEdit, datasetCollectionEdit, historyContents, readonlyPanel, tagsMod, annotationsMod, _l ){
|
||||
/* =============================================================================
|
||||
TODO:
|
||||
|
||||
@@ -36,7 +37,7 @@ var HistoryPanel = readonlyPanel.ReadOnlyHistoryPanel.extend(
|
||||
HDAViewClass : hdaEdit.HDAEditView,
|
||||
|
||||
// ......................................................................... SET UP
|
||||
/** Set up the view, set up storage, bind listeners to HDACollection events
|
||||
/** Set up the view, set up storage, bind listeners to HistoryContents events
|
||||
* @param {Object} attributes
|
||||
*/
|
||||
initialize : function( attributes ){
|
||||
@@ -493,7 +494,7 @@ var HistoryPanel = readonlyPanel.ReadOnlyHistoryPanel.extend(
|
||||
|
||||
/** return an HdaCollection of the models of all currenly selected hdas */
|
||||
getSelectedHdaCollection : function(){
|
||||
return new hdaModel.HDACollection( _.map( this.getSelectedHdaViews(), function( hdaView ){
|
||||
return new historyContents.HistoryContents( _.map( this.getSelectedHdaViews(), function( hdaView ){
|
||||
return hdaView.model;
|
||||
}), { historyId: this.model.id });
|
||||
},
|
||||
|
||||
@@ -97,7 +97,7 @@ var ReadOnlyHistoryPanel = Backbone.View.extend( baseMVC.LoggableMixin ).extend(
|
||||
noneFoundMsg : _l( 'No matching datasets found' ),
|
||||
|
||||
// ......................................................................... SET UP
|
||||
/** Set up the view, set up storage, bind listeners to HDACollection events
|
||||
/** Set up the view, set up storage, bind listeners to HistoryContents events
|
||||
* @param {Object} attributes optional settings for the panel
|
||||
*/
|
||||
initialize : function( attributes ){
|
||||
|
||||
@@ -196,7 +196,6 @@ var View = Backbone.View.extend(
|
||||
|
||||
// Load content into frame.
|
||||
var content_elt = $frame_el.find('.f-content');
|
||||
console.log(content_elt);
|
||||
if (_.isFunction(options.content)) {
|
||||
options.content(content_elt);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
define(["mvc/dataset/hda-model","mvc/dataset/hda-base"],function(b,a){var c=a.HistoryContentBaseView.extend({className:"dataset hda history-panel-hda",id:function(){return"hdca-"+this.model.get("id")},initialize:function(d){if(d.logger){this.logger=this.model.logger=d.logger}this.log(this+".initialize:",d);this.selectable=d.selectable||false;this.selected=d.selected||false;this.expanded=d.expanded||false},render:function(e){var d=this._buildNewRender();this._queueNewRender(d,e);return this},templateSkeleton:function(){return['<div class="dataset hda">','<div class="dataset-warnings">',"<% if ( deleted ) { %>",'<div class="dataset-deleted-msg warningmessagesmall"><strong>',_l("This dataset has been deleted."),"</div>","<% } %>","<% if ( ! visible ) { %>",'<div class="dataset-hidden-msg warningmessagesmall"><strong>',_l("This dataset has been hidden."),"</div>","<% } %>","</div>",'<div class="dataset-selector"><span class="fa fa-2x fa-square-o"></span></div>','<div class="dataset-primary-actions"></div>','<div class="dataset-title-bar clear" tabindex="0">','<span class="dataset-state-icon state-icon"></span>','<div class="dataset-title">','<span class="hda-hid"><%= hid %></span> ','<span class="dataset-name"><%= name %></span>',"</div>","</div>",'<div class="dataset-body"></div>',"</div>"].join("")},templateBody:function(){return['<div class="dataset-body">','<div class="dataset-summary">',"A dataset collection.","</div>"].join("")},_buildNewRender:function(){var d=$(_.template(this.templateSkeleton(),this.model.toJSON()));d.find(".dataset-primary-actions").append(this._render_titleButtons());d.children(".dataset-body").replaceWith(this._render_body());this._setUpBehaviors(d);return d},_render_titleButtons:function(){return[]},_render_body:function(){var e=$('<div>Error: unknown state "'+this.model.get("state")+'".</div>'),d=this["_render_body_"+this.model.get("state")];if(_.isFunction(d)){e=d.call(this)}this._setUpBehaviors(e);if(this.expanded){e.show()}return e},_setUpBehaviors:function(d){d=d||this.$el;make_popup_menus(d);d.find("[title]").tooltip({placement:"bottom"})},events:{"click .dataset-title-bar":"toggleBodyVisibility","keydown .dataset-title-bar":"toggleBodyVisibility","click .dataset-selector":"toggleSelect"},expandBody:function(){var d=this;function e(){d.$el.children(".dataset-body").replaceWith(d._render_body());d.$el.children(".dataset-body").slideDown(d.fxSpeed,function(){d.expanded=true;d.trigger("body-expanded",d.model)})}e()},collapseBody:function(){var d=this;this.$el.children(".dataset-body").slideUp(d.fxSpeed,function(){d.expanded=false;d.trigger("body-collapsed",d.model.id)})},_render_body_ok:function(){var d=$(_.template(this.templateBody(),this.model.toJSON()));if(this.model.get("deleted")){return d}return d}});return{DatasetCollectionBaseView:c}});
|
||||
define(["mvc/history/history-content-base-view","utils/localization"],function(e,b){var c=e.HistoryContentBaseView.extend({className:"dataset hda history-panel-hda",id:function(){return"hdca-"+this.model.get("id")},initialize:function(f){if(f.logger){this.logger=this.model.logger=f.logger}this.log(this+".initialize:",f);this.selectable=f.selectable||false;this.selected=f.selected||false;this.expanded=f.expanded||false},render:function(g){var f=this._buildNewRender();this._queueNewRender(f,g);return this},_buildNewRender:function(){var f=$(c.templates.skeleton(this.model.toJSON()));f.find(".dataset-primary-actions").append(this._render_titleButtons());f.children(".dataset-body").replaceWith(this._render_body());this._setUpBehaviors(f);return f},_queueNewRender:function(g,h){h=(h===undefined)?(true):(h);var f=this;if(h){$(f).queue(function(i){this.$el.fadeOut(f.fxSpeed,i)})}$(f).queue(function(i){this.$el.empty().attr("class",f.className).addClass("state-"+f.model.get("state")).append(g.children());if(this.selectable){this.showSelector(0)}i()});if(h){$(f).queue(function(i){this.$el.fadeIn(f.fxSpeed,i)})}$(f).queue(function(i){this.trigger("rendered",f);if(this.model.inReadyState()){this.trigger("rendered:ready",f)}if(this.draggable){this.draggableOn()}i()})},_render_titleButtons:function(){return[]},_render_body:function(){var g=$('<div>Error: unknown state "'+this.model.get("state")+'".</div>'),f=this["_render_body_"+this.model.get("state")];if(_.isFunction(f)){g=f.call(this)}this._setUpBehaviors(g);if(this.expanded){g.show()}return g},_setUpBehaviors:function(f){f=f||this.$el;make_popup_menus(f);f.find("[title]").tooltip({placement:"bottom"})},events:{"click .dataset-title-bar":"toggleBodyVisibility","keydown .dataset-title-bar":"toggleBodyVisibility","click .dataset-selector":"toggleSelect"},toggleBodyVisibility:function(i,g){var f=32,h=13;if(i&&(i.type==="keydown")&&!(i.keyCode===f||i.keyCode===h)){return true}var j=this.$el.find(".dataset-body");g=(g===undefined)?(!j.is(":visible")):(g);if(g){this.expandBody()}else{this.collapseBody()}return false},expandBody:function(){var f=this;function g(){f.$el.children(".dataset-body").replaceWith(f._render_body());f.$el.children(".dataset-body").slideDown(f.fxSpeed,function(){f.expanded=true;f.trigger("body-expanded",f.model)})}g()},collapseBody:function(){var f=this;this.$el.children(".dataset-body").slideUp(f.fxSpeed,function(){f.expanded=false;f.trigger("body-collapsed",f.model.id)})},_render_body_ok:function(){var f=$(c.templates.body(this.model.toJSON()));if(this.model.get("deleted")){return f}return f},showSelector:function(){if(this.selected){this.select(null,true)}this.selectable=true;this.trigger("selectable",true,this);this.$(".dataset-primary-actions").hide();this.$(".dataset-selector").show()},hideSelector:function(){this.selectable=false;this.trigger("selectable",false,this);this.$(".dataset-selector").hide();this.$(".dataset-primary-actions").show()},toggleSelector:function(){if(!this.$el.find(".dataset-selector").is(":visible")){this.showSelector()}else{this.hideSelector()}},select:function(f){this.$el.find(".dataset-selector span").removeClass("fa-square-o").addClass("fa-check-square-o");if(!this.selected){this.trigger("selected",this,f);this.selected=true}return false},deselect:function(f){this.$el.find(".dataset-selector span").removeClass("fa-check-square-o").addClass("fa-square-o");if(this.selected){this.trigger("de-selected",this,f);this.selected=false}return false},toggleSelect:function(f){if(this.selected){this.deselect(f)}else{this.select(f)}},toString:function(){var f=(this.model)?(this.model+""):("(no model)");return"HDCABaseView("+f+")"}});var a=_.template(['<div class="dataset hda">','<div class="dataset-warnings">',"<% if ( collection.deleted ) { %>",'<div class="dataset-deleted-msg warningmessagesmall"><strong>',b("This collection has been deleted."),"</div>","<% } %>","<% if ( !collection.visible ) { %>",'<div class="dataset-hidden-msg warningmessagesmall"><strong>',b("This collection has been hidden."),"</div>","<% } %>","</div>",'<div class="dataset-selector"><span class="fa fa-2x fa-square-o"></span></div>','<div class="dataset-primary-actions"></div>','<div class="dataset-title-bar clear" tabindex="0">','<span class="dataset-state-icon state-icon"></span>','<div class="dataset-title">','<span class="hda-hid"><%= collection.hid %></span> ','<span class="dataset-name"><%= collection.name %></span>',"</div>","</div>",'<div class="dataset-body"></div>',"</div>"].join(""));var d=_.template(['<div class="dataset-body">','<div class="dataset-summary">',b("A dataset collection."),"</div>"].join(""));c.templates={skeleton:function(f){return a({_l:b,collection:f})},body:function(f){return d({_l:b,collection:f})}};return{DatasetCollectionBaseView:c}});
|
||||
@@ -1 +1 @@
|
||||
define(["mvc/dataset/hda-model","mvc/collection/dataset-collection-base",],function(b,c){var a=c.DatasetCollectionBaseView.extend({initialize:function(d){c.DatasetCollectionBaseView.prototype.initialize.call(this,d)},_render_titleButtons:function(){return c.DatasetCollectionBaseView.prototype._render_titleButtons.call(this).concat([this._render_deleteButton()])},_render_deleteButton:function(){if((this.model.get("state")===b.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===b.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var d=this,e={title:_l("Delete"),classes:"dataset-delete",onclick:function(){d.$el.find(".icon-btn.dataset-delete").trigger("mouseout");d.model["delete"]()}};if(this.model.get("deleted")){e={title:_l("Dataset collection is already deleted"),disabled:true}}e.faIcon="fa-times";return faIconButton(e)}});return{DatasetCollectionEditView:a}});
|
||||
define(["mvc/dataset/hda-model","mvc/collection/dataset-collection-base","utils/localization"],function(c,d,b){var a=d.DatasetCollectionBaseView.extend({initialize:function(e){d.DatasetCollectionBaseView.prototype.initialize.call(this,e)},_render_titleButtons:function(){return d.DatasetCollectionBaseView.prototype._render_titleButtons.call(this).concat([this._render_deleteButton()])},_render_deleteButton:function(){if((this.model.get("state")===c.HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===c.HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var e=this,f={title:b("Delete"),classes:"dataset-delete",onclick:function(){e.$el.find(".icon-btn.dataset-delete").trigger("mouseout");e.model["delete"]()}};if(this.model.get("deleted")){f={title:b("Dataset collection is already deleted"),disabled:true}}f.faIcon="fa-times";return faIconButton(f)},toString:function(){var e=(this.model)?(this.model+""):("(no model)");return"HDCAEditView("+e+")"}});return{DatasetCollectionEditView:a}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["mvc/history/history-content-base","utils/localization"],function(b,c){var a=b.HistoryContent.extend({defaults:{history_id:null,model_class:"HistoryDatasetCollectionAssociation",history_content_type:"dataset_collection",hid:0,id:null,name:"(unnamed dataset collection)",state:"ok",accessible:true,deleted:false,visible:true,purged:false,tags:[],annotation:""},urls:function(){},inReadyState:function(){return true},searchAttributes:["name"],searchAliases:{title:"name"},toString:function(){var d=this.get("id")||"";if(this.get("name")){d=this.get("hid")+' :"'+this.get("name")+'",'+d}return"HDCA-"+this.get("collection_type")+"("+d+")"}});return{HistoryDatasetCollectionAssociation:a}});
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
define(["mvc/base-mvc","utils/localization"],function(a,b){var c=Backbone.View.extend(a.LoggableMixin).extend({});return{HistoryContentBaseView:c}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["mvc/base-mvc","utils/localization"],function(b,e){var f=Backbone.Model.extend(b.LoggableMixin).extend({idAttribute:"type_id",constructor:function(j,i){j.type_id=f.typeIdStr(j.history_content_type,j.id);Backbone.Model.apply(this,arguments)},initialize:function(j,i){this.on("change:id",this._createTypeId)},_createTypeId:function(){this.set("type_id",TypeIdModel.typeIdStr(this.get("history_content_type"),this.get("id")))},hidden:function(){return !this.get("visible")},isVisible:function(j,k){var i=true;if((!j)&&(this.get("deleted")||this.get("purged"))){i=false}if((!k)&&(!this.get("visible"))){i=false}return i},urlRoot:galaxy_config.root+"api/histories/",url:function(){return this.urlRoot+this.get("history_id")+"/contents/"+this.get("history_content_type")+"s/"+this.get("id")},"delete":function d(i){if(this.get("deleted")){return jQuery.when()}return this.save({deleted:true},i)},undelete:function a(i){if(!this.get("deleted")||this.get("purged")){return jQuery.when()}return this.save({deleted:false},i)},hide:function c(i){if(!this.get("visible")){return jQuery.when()}return this.save({visible:false},i)},unhide:function g(i){if(this.get("visible")){return jQuery.when()}return this.save({visible:true},i)},searchAttribute:function(k,i){var j=this.get(k);if(!i||(j===undefined||j===null)){return false}if(_.isArray(j)){return this._searchArrayAttribute(j,i)}return(j.toString().toLowerCase().indexOf(i.toLowerCase())!==-1)},_searchArrayAttribute:function(j,i){i=i.toLowerCase();return _.any(j,function(k){return(k.toString().toLowerCase().indexOf(i.toLowerCase())!==-1)})},search:function(i){var j=this;return _.filter(this.searchAttributes,function(k){return j.searchAttribute(k,i)})},matches:function(j){var l="=",i=j.split(l);if(i.length>=2){var k=i[0];k=this.searchAliases[k]||k;return this.searchAttribute(k,i[1])}return !!this.search(j).length},matchesAll:function(j){var i=this;j=j.match(/(".*"|\w*=".*"|\S*)/g).filter(function(k){return !!k});return _.all(j,function(k){k=k.replace(/"/g,"");return i.matches(k)})},toString:function(){var i=this.get("id")||"";if(this.get("name")){i=this.get("hid")+' :"'+this.get("name")+'",'+i}return"HistoryContent("+i+")"}});f.typeIdStr=function h(i,j){return[i,j].join("-")};return{HistoryContent:f}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["mvc/history/history-content-base","mvc/dataset/hda-model","mvc/collection/hdca-model","mvc/base-mvc","utils/localization"],function(b,d,f,a,c){var g=Backbone.Collection.extend(a.LoggableMixin).extend({model:function(i,h){if(i.history_content_type==="dataset"){return new d.HistoryDatasetAssociation(i,h)}else{if(i.history_content_type==="dataset_collection"){return new f.HistoryDatasetCollectionAssociation(i,h)}}throw new TypeError("Unknown history_content_type:"+i.history_content_type)},initialize:function(i,h){h=h||{};this.historyId=h.historyId},urlRoot:galaxy_config.root+"api/histories",url:function(){return this.urlRoot+"/"+this.historyId+"/contents"},ids:function(){return this.map(function(h){return h.get("id")})},notReady:function(){return this.filter(function(h){return !h.inReadyState()})},running:function(){var h=[];this.each(function(i){if(!i.inReadyState()){h.push(i.get("id"))}});return h},getByHid:function(h){return _.first(this.filter(function(i){return i.get("hid")===h}))},getVisible:function(h,k,j){j=j||[];var i=new g(this.filter(function(l){return l.isVisible(h,k)}));_.each(j,function(l){if(!_.isFunction(l)){return}i=new g(i.filter(l))});return i},haveDetails:function(){return this.all(function(h){return h.hasDetails()})},fetchAllDetails:function(i){i=i||{};var h={details:"all"};i.data=(i.data)?(_.extend(i.data,h)):(h);return this.fetch(i)},ajaxQueue:function(k,j){var i=jQuery.Deferred(),h=this.length,m=[];if(!h){i.resolve([]);return i}var l=this.chain().reverse().map(function(o,n){return function(){var p=k.call(o,j);p.done(function(q){i.notify({curr:n,total:h,response:q,model:o})});p.always(function(q){m.push(q);if(l.length){l.shift()()}else{i.resolve(m)}})}}).value();l.shift()();return i},matches:function(h){return this.filter(function(i){return i.matches(h)})},set:function(j,h){var i=this;j=_.map(j,function(m){var l=m.attributes||m;var n=b.HistoryContent.typeIdStr(l.history_content_type,l.id);var o=i.get(n);if(!o){return m}var k=o.toJSON();_.extend(k,m);return k});Backbone.Collection.prototype.set.call(this,j,h)},promoteToHistoryDatasetCollection:function e(m,k,i){i=i||{};i.url=this.url();i.type="POST";var o=k;var l=[],h=null;if(k==="list"){this.chain().each(function(r){var p=r.attributes.name;var s=r.get("id");var q=r.attributes.history_content_type;if(q==="dataset"){if(o!=="list"){console.log("Invalid collection type")}l.push({name:p,src:"hda",id:s})}else{if(o==="list"){o="list:"+r.attributes.collection_type}else{if(o!=="list:"+r.attributes.collection_type){console.log("Invalid collection type")}}l.push({name:p,src:"hdca",id:s})}});h="New Dataset List"}else{if(k==="paired"){var j=this.ids();if(j.length!==2){}l.push({name:"forward",src:"hda",id:j[0]});l.push({name:"reverse",src:"hda",id:j[1]});h="New Dataset Pair"}}i.data={type:"dataset_collection",name:h,collection_type:o,element_identifiers:JSON.stringify(l)};var n=jQuery.ajax(i);n.done(function(r,p,q){m.refresh()});n.fail(function(r,p,q){if(r.responseJSON&&r.responseJSON.error){error=r.responseJSON.error}else{error=r.responseJSON}r.responseText=error});return n},toString:function(){return(["HistoryContents(",[this.parentId,this.length].join(),")"].join(""))}});return{HistoryContents:g}});
|
||||
@@ -1 +1 @@
|
||||
define(["mvc/dataset/hda-model","mvc/base-mvc","utils/localization"],function(c,a,b){var e=Backbone.Model.extend(a.LoggableMixin).extend({defaults:{model_class:"History",id:null,name:"Unnamed History",state:"new",diskSize:0,deleted:false},urlRoot:galaxy_config.root+"api/histories",initialize:function(h,i,g){g=g||{};this.logger=g.logger||null;this.log(this+".initialize:",h,i,g);this.hdas=new c.HDACollection(i||[],{historyId:this.get("id")});if(i&&_.isArray(i)){this.hdas.reset(i)}this._setUpListeners();this.updateTimeoutId=null},_setUpListeners:function(){this.on("error",function(h,k,g,j,i){this.errorHandler(h,k,g,j,i)});if(this.hdas){this.listenTo(this.hdas,"error",function(){this.trigger.apply(this,["error:hdas"].concat(jQuery.makeArray(arguments)))})}this.on("change:id",function(h,g){if(this.hdas){this.hdas.historyId=g}},this)},errorHandler:function(h,k,g,j,i){this.clearUpdateTimeout()},ownedByCurrUser:function(){if(!Galaxy||!Galaxy.currUser){return false}if(Galaxy.currUser.isAnonymous()||Galaxy.currUser.id!==this.get("user_id")){return false}return true},hdaCount:function(){return _.reduce(_.values(this.get("state_details")),function(g,h){return g+h},0)},checkForUpdates:function(g){if(this.hdas.running().length){this.setUpdateTimeout()}else{this.trigger("ready");if(_.isFunction(g)){g.call(this)}}return this},setUpdateTimeout:function(g){g=g||e.UPDATE_DELAY;var h=this;this.clearUpdateTimeout();this.updateTimeoutId=setTimeout(function(){h.refresh()},g);return this.updateTimeoutId},clearUpdateTimeout:function(){if(this.updateTimeoutId){clearTimeout(this.updateTimeoutId);this.updateTimeoutId=null}},refresh:function(h,g){h=h||[];g=g||{};var i=this;g.data=g.data||{};if(h.length){g.data.details=h.join(",")}var j=this.hdas.fetch(g);j.done(function(k){i.checkForUpdates(function(){this.fetch()})});return j},toString:function(){return"History("+this.get("id")+","+this.get("name")+")"}});e.UPDATE_DELAY=4000;e.getHistoryData=function f(h,s){s=s||{};var m=s.hdaDetailIds||[];var j=s.hdcaDetailIds||[];var o=jQuery.Deferred(),n=null;function i(t){return jQuery.ajax(galaxy_config.root+"api/histories/"+h)}function g(t){if(!t||!t.state_ids){return 0}return _.reduce(t.state_ids,function(u,w,v){return u+w.length},0)}function r(u){if(!g(u)){return[]}if(_.isFunction(m)){m=m(u)}if(_.isFunction(j)){j=j(u)}var t={};if(m.length){t.dataset_details=m.join(",")}if(j.length){t.dataset_collection_details=j.join(",")}return jQuery.ajax(galaxy_config.root+"api/histories/"+u.id+"/contents",{data:t})}var q=s.historyFn||i,p=s.hdaFn||r;var l=q(h);l.done(function(t){n=t;o.notify({status:"history data retrieved",historyJSON:n})});l.fail(function(v,t,u){o.reject(v,"loading the history")});var k=l.then(p);k.then(function(t){o.notify({status:"dataset data retrieved",historyJSON:n,hdaJSON:t});o.resolve(n,t)});k.fail(function(v,t,u){o.reject(v,"loading the datasets",{history:n})});return o};var d=Backbone.Collection.extend(a.LoggableMixin).extend({model:e,urlRoot:galaxy_config.root+"api/histories"});return{History:e,HistoryCollection:d}});
|
||||
define(["mvc/history/history-contents","mvc/base-mvc","utils/localization"],function(e,a,b){var d=Backbone.Model.extend(a.LoggableMixin).extend({defaults:{model_class:"History",id:null,name:"Unnamed History",state:"new",diskSize:0,deleted:false},urlRoot:galaxy_config.root+"api/histories",initialize:function(h,i,g){g=g||{};this.logger=g.logger||null;this.log(this+".initialize:",h,i,g);this.hdas=new e.HistoryContents(i||[],{historyId:this.get("id")});if(i&&_.isArray(i)){this.hdas.reset(i)}this._setUpListeners();this.updateTimeoutId=null},_setUpListeners:function(){this.on("error",function(h,k,g,j,i){this.errorHandler(h,k,g,j,i)});if(this.hdas){this.listenTo(this.hdas,"error",function(){this.trigger.apply(this,["error:hdas"].concat(jQuery.makeArray(arguments)))})}this.on("change:id",function(h,g){if(this.hdas){this.hdas.historyId=g}},this)},errorHandler:function(h,k,g,j,i){this.clearUpdateTimeout()},ownedByCurrUser:function(){if(!Galaxy||!Galaxy.currUser){return false}if(Galaxy.currUser.isAnonymous()||Galaxy.currUser.id!==this.get("user_id")){return false}return true},hdaCount:function(){return _.reduce(_.values(this.get("state_details")),function(g,h){return g+h},0)},checkForUpdates:function(g){if(this.hdas.running().length){this.setUpdateTimeout()}else{this.trigger("ready");if(_.isFunction(g)){g.call(this)}}return this},setUpdateTimeout:function(g){g=g||d.UPDATE_DELAY;var h=this;this.clearUpdateTimeout();this.updateTimeoutId=setTimeout(function(){h.refresh()},g);return this.updateTimeoutId},clearUpdateTimeout:function(){if(this.updateTimeoutId){clearTimeout(this.updateTimeoutId);this.updateTimeoutId=null}},refresh:function(h,g){h=h||[];g=g||{};var i=this;g.data=g.data||{};if(h.length){g.data.details=h.join(",")}var j=this.hdas.fetch(g);j.done(function(k){i.checkForUpdates(function(){this.fetch()})});return j},toString:function(){return"History("+this.get("id")+","+this.get("name")+")"}});d.UPDATE_DELAY=4000;d.getHistoryData=function f(h,s){s=s||{};var m=s.hdaDetailIds||[];var j=s.hdcaDetailIds||[];var o=jQuery.Deferred(),n=null;function i(t){return jQuery.ajax(galaxy_config.root+"api/histories/"+h)}function g(t){if(!t||!t.state_ids){return 0}return _.reduce(t.state_ids,function(u,w,v){return u+w.length},0)}function r(u){if(!g(u)){return[]}if(_.isFunction(m)){m=m(u)}if(_.isFunction(j)){j=j(u)}var t={};if(m.length){t.dataset_details=m.join(",")}if(j.length){t.dataset_collection_details=j.join(",")}return jQuery.ajax(galaxy_config.root+"api/histories/"+u.id+"/contents",{data:t})}var q=s.historyFn||i,p=s.hdaFn||r;var l=q(h);l.done(function(t){n=t;o.notify({status:"history data retrieved",historyJSON:n})});l.fail(function(v,t,u){o.reject(v,"loading the history")});var k=l.then(p);k.then(function(t){o.notify({status:"dataset data retrieved",historyJSON:n,hdaJSON:t});o.resolve(n,t)});k.fail(function(v,t,u){o.reject(v,"loading the datasets",{history:n})});return o};var c=Backbone.Collection.extend(a.LoggableMixin).extend({model:d,urlRoot:galaxy_config.root+"api/histories"});return{History:d,HistoryCollection:c}});
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user