diff --git a/lib/galaxy/web/base/controller.py b/lib/galaxy/web/base/controller.py index c527d2c7b3d..03c95e44d97 100644 --- a/lib/galaxy/web/base/controller.py +++ b/lib/galaxy/web/base/controller.py @@ -353,8 +353,6 @@ class UsesVisualizationMixin( SharableItemSecurityMixin, UsesLibraryMixinItems ) viz_types = [ "trackster", "circster" ] - len_files = None - def create_visualization( self, trans, title, slug, type, dbkey, annotation=None, config={} ): """ Create visualiation and first revision. """ visualization = self._create_visualization( trans, title, type, dbkey, slug, annotation ) @@ -434,9 +432,9 @@ class UsesVisualizationMixin( SharableItemSecurityMixin, UsesLibraryMixinItems ) end = config['viewport']['end'] overview = config['viewport']['overview'] vis_rev.config[ "viewport" ] = { 'chrom': chrom, 'start': start, 'end': end, 'overview': overview } - elif type == 'circster': - # TODO. - pass + else: + # Default action is to save the config as is with no validation. + vis_rev.config = config vis.latest_revision = vis_rev session.add( vis_rev ) @@ -543,6 +541,10 @@ class UsesVisualizationMixin( SharableItemSecurityMixin, UsesLibraryMixinItems ) if 'viewport' in latest_revision.config: config['viewport'] = latest_revision.config['viewport'] + else: + # Default action is to return config unaltered. + latest_revision = visualization.latest_revision + config = latest_revision.config return config diff --git a/lib/galaxy/web/controllers/tracks.py b/lib/galaxy/web/controllers/tracks.py index 909a4854441..b4a22d07a84 100644 --- a/lib/galaxy/web/controllers/tracks.py +++ b/lib/galaxy/web/controllers/tracks.py @@ -16,7 +16,7 @@ from galaxy.datatypes.interval import Gff, Bed from galaxy.model import NoConverterException, ConverterDependencyException from galaxy.visualization.tracks.data_providers import * from galaxy.visualization.genomes import decode_dbkey, Genomes -from galaxy.visualization.tracks.visual_analytics import get_tool_def, get_dataset_job +from galaxy.visualization.tracks.visual_analytics import get_dataset_job class NameColumn( grids.TextColumn ): @@ -471,12 +471,42 @@ class TracksController( BaseUIController, UsesVisualizationMixin, UsesHistoryDat @web.expose @web.require_login( "use Galaxy visualizations", use_panels=True ) - def paramamonster( self, trans, hda_ldda, dataset_id ): - # Get dataset. - dataset = self.get_hda_or_ldda( trans, hda_ldda, dataset_id ) + def paramamonster( self, trans, id=None, hda_ldda=None, dataset_id=None, regions=None ): + if id: + # Loading a shared visualization. + viz = self.get_visualization( trans, id ) + viz_config = self.get_visualization_config( trans, viz ) + dataset = self.get_dataset( trans, viz_config[ 'dataset_id' ] ) + else: + # Loading new visualization. + dataset = self.get_hda_or_ldda( trans, hda_ldda, dataset_id ) + job = get_dataset_job( dataset ) + viz_config = { + 'dataset_id': dataset_id, + 'tool_id': job.tool_id, + 'regions': regions + } + + viz_config[ 'regions' ] = [ + { + 'chrom': 'chr19', + 'start': '10000', + 'end': '26000' + }, + { + 'chrom': 'chr19', + 'start': '150000', + 'end': '175000' + } + + ] - return trans.fill_template_mako( "visualization/paramamonster.mako", dataset=dataset, - tool=self.app.toolbox.tools_by_id[ 'cufflinks' ].to_dict( trans, for_display=True ) ) + # Add tool, dataset attributes to config based on id. + tool = trans.app.toolbox.get_tool( viz_config[ 'tool_id' ] ) + viz_config[ 'tool' ] = tool.to_dict( trans, for_display=True ) + viz_config[ 'dataset' ] = dataset.get_api_value() + + return trans.fill_template_mako( "visualization/paramamonster.mako", config=viz_config ) @web.expose @web.require_login( "use Galaxy visualizations", use_panels=True ) diff --git a/lib/galaxy/web/controllers/visualization.py b/lib/galaxy/web/controllers/visualization.py index b18ab364dff..51e45a41f92 100644 --- a/lib/galaxy/web/controllers/visualization.py +++ b/lib/galaxy/web/controllers/visualization.py @@ -5,14 +5,26 @@ from galaxy.web.framework.helpers import time_ago, grids, iff from galaxy.util.sanitize_html import sanitize_html class VisualizationListGrid( grids.Grid ): + def get_link( item ): + """ + Returns dictionary used to create item link. + """ + controller = "tracks" + if item.type == "trackster": + action = "browser" + elif item.type == "paramamonster": + action = "paramamonster" + elif item.type == "circster": + action = "circster" + return dict( controller=controller, action=action, id=item.id ) + # Grid definition title = "Saved Visualizations" model_class = model.Visualization default_sort_key = "-update_time" default_filter = dict( title="All", deleted="False", tags="All", sharing="All" ) columns = [ - grids.TextColumn( "Title", key="title", attach_popup=True, - link=( lambda item: dict( controller="tracks", action="browser", id=item.id ) ) ), + grids.TextColumn( "Title", key="title", attach_popup=True, link=get_link ), grids.TextColumn( "Type", key="type" ), grids.TextColumn( "Dbkey", key="dbkey" ), grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.VisualizationTagAssociation, filterable="advanced", grid_name="VisualizationListGrid" ), @@ -383,12 +395,20 @@ class VisualizationController( BaseUIController, SharableMixin, UsesAnnotations, template="visualization/create.mako" ) @web.json - def save( self, trans, config, type, id=None, title=None, dbkey=None, annotation=None ): + def save( self, trans, vis_json=None, type=None, id=None, title=None, dbkey=None, annotation=None ): """ Save a visualization; if visualization does not have an ID, a new visualization is created. Returns JSON of visualization. """ - return self.save_visualization( trans, from_json_string( config ), type, id, title, dbkey, annotation ) + + # Get visualization attributes from kwargs or from config. + vis_config = from_json_string( vis_json ) + vis_type = type or vis_config[ 'type' ] + vis_id = id or vis_config.get( 'id', None ) + vis_title = title or vis_config.get( 'title', None ) + vis_dbkey = dbkey or vis_config.get( 'dbkey', None ) + vis_annotation = annotation or vis_config.get( 'annotation', None ) + return self.save_visualization( trans, vis_config, vis_type, vis_id, vis_title, vis_dbkey, vis_annotation ) @web.expose @web.require_login( "edit visualizations" ) diff --git a/static/scripts/mvc/data.js b/static/scripts/mvc/data.js index 5133f0b7551..8cd5d6f8ecf 100644 --- a/static/scripts/mvc/data.js +++ b/static/scripts/mvc/data.js @@ -2,7 +2,7 @@ * A dataset. In Galaxy, datasets are associated with a history, so * this object is also known as a HistoryDatasetAssociation. */ -var Dataset = Backbone.Model.extend({ +var Dataset = Backbone.RelationalModel.extend({ defaults: { id: "", type: "", diff --git a/static/scripts/mvc/tools.js b/static/scripts/mvc/tools.js index efce487c651..cd597c7123a 100644 --- a/static/scripts/mvc/tools.js +++ b/static/scripts/mvc/tools.js @@ -46,7 +46,8 @@ var Tool = BaseModel.extend({ key: 'inputs', relatedModel: 'ToolInput', reverseRelation: { - key: 'tool' + key: 'tool', + includeInJSON: false } } ], diff --git a/static/scripts/viz/paramamonster.js b/static/scripts/viz/paramamonster.js index c10f692f49c..15fc9d85cd4 100644 --- a/static/scripts/viz/paramamonster.js +++ b/static/scripts/viz/paramamonster.js @@ -6,7 +6,7 @@ /** * Tree for a tool's parameters. */ -var ToolParameterTree = Backbone.Model.extend({ +var ToolParameterTree = Backbone.RelationalModel.extend({ defaults: { tool: null, tree_data: null @@ -18,9 +18,6 @@ var ToolParameterTree = Backbone.Model.extend({ this.get('tool').get('inputs').each(function(input) { if (!input.get_samples()) { return; } - // All inputs are in tree to start. - self.add_param(input); - // Listen for changes to input's attributes. input.on('change:min change:max change:num_samples', function(input) { if (input.get('in_ptree')) { @@ -38,7 +35,16 @@ var ToolParameterTree = Backbone.Model.extend({ }, self); }); - self.set_tree_data(); + // If there is a config, use it. + if (options.config) { + _.each(options.config, function(input_config) { + var input = self.get('tool').get('inputs').find(function(input) { + return input.get('name') === input_config.name; + }); + self.add_param(input); + input.set(input_config); + }); + } }, add_param: function(param) { @@ -203,16 +209,51 @@ var ToolParameterTree = Backbone.Model.extend({ cur_node = find_child(cur_node.children); } return cur_node; + }, + + /** + * Returns a list of parameters used in tree. + */ + toJSON: function() { + // FIXME: returning and jsonifying complete param causes trouble on the server side, + // so just use essential attributes for now. + return this.get_tree_params().map(function(param) { + return { + name: param.get('name'), + min: param.get('min'), + max: param.get('max'), + num_samples: param.get('num_samples') + }; + }); } }); -var ParamaMonsterTrack = Backbone.Model.extend({ +var ParamaMonsterTrack = Backbone.RelationalModel.extend({ defaults: { track: null, settings: null, regions: null }, + relations: [ + { + type: Backbone.HasMany, + key: 'regions', + relatedModel: 'GenomeRegion' + } + ], + + initialize: function(options) { + // FIXME: find a better way to deal with needed URLs: + var track_config = _.extend({ + data_url: galaxy_paths.get('raw_data_url'), + converted_datasets_state_url: galaxy_paths.get('dataset_state_url') + }, options.track); + // HACK: remove prefs b/c they cause a redraw, which is not supported now. + delete track_config.mode; + this.set('track', object_from_template(track_config, {}, null)); + }, + same_settings: function(a_track) { var this_settings = this.get('settings'), other_settings = a_track.get('settings'); @@ -223,6 +264,14 @@ var ParamaMonsterTrack = Backbone.Model.extend({ } } return true; + }, + + toJSON: function() { + return { + track: this.get('track').to_dict(), + settings: this.get('settings'), + regions: this.get('regions') + }; } }); @@ -235,18 +284,45 @@ var TrackCollection = Backbone.Collection.extend({ */ var ParamaMonsterVisualization = Visualization.extend({ defaults: _.extend({}, Visualization.prototype.defaults, { + dataset: null, tool: null, parameter_tree: null, regions: null, tracks: null }), + + relations: [ + { + type: Backbone.HasOne, + key: 'dataset', + relatedModel: 'Dataset' + }, + { + type: Backbone.HasOne, + key: 'tool', + relatedModel: 'Tool' + }, + { + type: Backbone.HasMany, + key: 'regions', + relatedModel: 'GenomeRegion' + }, + { + type: Backbone.HasMany, + key: 'tracks', + relatedModel: 'ParamaMonsterTrack' + } + // NOTE: cannot use relationship for parameter tree because creating tree is complex. + ], initialize: function(options) { var tool_with_samplable_inputs = this.get('tool').copy(true); this.set('tool_with_samplable_inputs', tool_with_samplable_inputs); - this.set('parameter_tree', new ToolParameterTree({ tool: tool_with_samplable_inputs })); - this.set('tracks', new TrackCollection()); + this.set('parameter_tree', new ToolParameterTree({ + tool: tool_with_samplable_inputs, + config: options.tree_config + })); }, add_placeholder: function(settings) { @@ -255,6 +331,20 @@ var ParamaMonsterVisualization = Visualization.extend({ add_track: function(track) { this.get('tracks').add(track); + }, + + toJSON: function() { + // TODO: could this be easier by using relational models? + return { + id: this.get('id'), + title: 'Parameter exploration for dataset \'' + this.get('dataset').get('name') + '\'', + type: 'paramamonster', + dataset_id: this.get('dataset').id, + tool_id: this.get('tool').id, + regions: this.get('regions').toJSON(), + tree_config: this.get('parameter_tree').toJSON(), + tracks: this.get('tracks').toJSON() + }; } }); @@ -301,7 +391,7 @@ var ParamaMonsterTrackView = Backbone.View.extend({ settings_td.prepend(icon_menu.$el); // Render tile placeholders. - _.each(this.model.get('regions'), function() { + this.model.get('regions').each(function() { self.$el.append($('