This commit is contained in:
Dannon Baker
2014-07-10 14:31:51 -04:00
142 changed files with 3104 additions and 3590 deletions
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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
+11
View File
@@ -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> -->
-3
View File
@@ -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:
+17 -4
View File
@@ -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 ):
+9 -8
View File
@@ -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 = []
-43
View File
@@ -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
-250
View File
@@ -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 ) )
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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 )
+32 -3
View File
@@ -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.
"""
+7
View File
@@ -0,0 +1,7 @@
class ModelManager( object ):
pass
class ModelSerializer( object ):
pass
+179 -5
View File
@@ -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
+138 -2
View File
@@ -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 ):
+35 -14
View File
@@ -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
-67
View File
@@ -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()
+19 -7
View File
@@ -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()
+38 -9
View File
@@ -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
-243
View File
@@ -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)
+28 -3
View File
@@ -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()
+4 -3
View File
@@ -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 + " -=_.()/+*^,:?!")
-75
View File
@@ -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.' )
+5 -1
View File
@@ -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
+353 -359
View File
@@ -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 )
+16 -43
View File
@@ -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 ) )
+2
View File
@@ -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 )
+20
View File
@@ -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",
+27
View File
@@ -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
+14
View File
@@ -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
View File
@@ -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
};
});
+139 -153
View File
@@ -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
};
});
+5 -4
View File
@@ -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
};
});
+7 -486
View File
@@ -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
};
});
+8 -8
View File
@@ -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
};
});
+6 -6
View File
@@ -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 });
}
+4 -3
View File
@@ -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 ){
-1
View File
@@ -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