From 0e3c165c1255c385ff2502b8709ae29f7b1a30bf Mon Sep 17 00:00:00 2001 From: John Chilton Date: Sun, 8 Feb 2015 15:29:02 -0500 Subject: [PATCH 01/10] Add link to actual presentation slides to CONTRIBUTING.md. For people who want to follow along with Dannon at home. --- CONTRIBUTING.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b6d25a34fd..1a21a62fb2d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,13 @@ # Contributing This document briefly describes how to contribute to the -galaxy-central core project - also checkout the video of our [2013 -Galaxy Community Conference -presentation](http://vimeo.com/channels/581875/73486255) on the -topic. For information on contributing more broadly to the Galaxy -ecosystem and a deeper discussion of some of these points - please see -the [Develop](https://wiki.galaxyproject.org/Develop/) section of the +galaxy-central core project - also checkout our 2013 Galaxy Community +Conference presentation] on the topic +([video](http://vimeo.com/channels/581875/73486255), +[presentation](https://wiki.galaxyproject.org/Documents/Presentations/GCC2013?action=AttachFile&do=view&target=BakerContribute.pdf)). For +information on contributing more broadly to the Galaxy ecosystem and a +deeper discussion of some of these points - please see the +[Develop](https://wiki.galaxyproject.org/Develop/) section of the [Galaxy Wiki](https://wiki.galaxyproject.org/). ## Before you Begin From a69db7a7fb75e6336707e8601e05198b0112d03f Mon Sep 17 00:00:00 2001 From: John Chilton Date: Sun, 8 Feb 2015 18:17:10 -0500 Subject: [PATCH 02/10] Link to "easiest ways to start" card in CONTRIBUTING.md. --- CONTRIBUTING.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1a21a62fb2d..112df260fdb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,6 +81,13 @@ the [development mailing list](http://dev.list.galaxyproject.org/). * A description of how to test the change. +## Ideas + +Galaxy's [Trello board](http://bit.ly/gxytrello) is filled with bugs and ideas +for enhancements, but we maintain a [card](https://trello.com/c/eFdPIdIB) with +links to smaller issues we believe would make the best entry points for new +developers. + ## A Quick Note about Tools For the most part, Galaxy tools should be published to the From 3ece75b4fae19bf6e20855b886175433496b7fda Mon Sep 17 00:00:00 2001 From: Aysam Guerler Date: Sun, 8 Feb 2015 23:49:24 -0500 Subject: [PATCH 03/10] Parameters: Fix initial value for drill downs, Workflow/ToolForm: Move module building to api --- .../scripts/mvc/tools/tools-form-workflow.js | 16 ++-- client/galaxy/scripts/mvc/tools/tools-tree.js | 85 +++++++++---------- lib/galaxy/tools/__init__.py | 3 +- lib/galaxy/tools/parameters/basic.py | 2 + lib/galaxy/webapps/galaxy/api/tools.py | 10 +-- lib/galaxy/webapps/galaxy/api/workflows.py | 49 +++++++++++ lib/galaxy/webapps/galaxy/buildapp.py | 1 + .../webapps/galaxy/controllers/workflow.py | 21 +---- .../scripts/mvc/tools/tools-form-workflow.js | 16 ++-- static/scripts/mvc/tools/tools-tree.js | 85 +++++++++---------- .../packed/mvc/tools/tools-form-workflow.js | 2 +- static/scripts/packed/mvc/tools/tools-tree.js | 2 +- templates/webapps/galaxy/tool_form.mako | 2 +- .../galaxy/workflow/editor_tool_form.mako | 6 +- 14 files changed, 166 insertions(+), 134 deletions(-) diff --git a/client/galaxy/scripts/mvc/tools/tools-form-workflow.js b/client/galaxy/scripts/mvc/tools/tools-form-workflow.js index 0b19f6ef1e9..bbd904283d0 100644 --- a/client/galaxy/scripts/mvc/tools/tools-form-workflow.js +++ b/client/galaxy/scripts/mvc/tools/tools-form-workflow.js @@ -275,10 +275,16 @@ define(['utils/utils', 'mvc/tools/tools-form-base'], /** Request a new model for an already created tool form and updates the form inputs */ _updateModel: function() { - // create the request dictionary + // link self var self = this; - var current_state = this.tree.finalize(); - + + // create the request dictionary + var current_state = { + tool_id : this.options.id, + tool_version : this.options.version, + inputs : this.tree.finalize() + } + // log tool state console.debug('tools-form-workflow::_refreshForm() - Refreshing states.'); console.debug(current_state); @@ -287,11 +293,11 @@ define(['utils/utils', 'mvc/tools/tools-form-base'], var process_id = this.deferred.register(); // build model url for request - var model_url = galaxy_config.root + 'workflow/editor_form_post?tool_id=' + this.options.id + '&__is_dynamic__=False'; + var model_url = galaxy_config.root + 'api/workflows/build_module'; // post job Utils.request({ - type : 'GET', + type : 'POST', url : model_url, data : current_state, success : function(data) { diff --git a/client/galaxy/scripts/mvc/tools/tools-tree.js b/client/galaxy/scripts/mvc/tools/tools-tree.js index b036c68cbee..44918ece8b7 100644 --- a/client/galaxy/scripts/mvc/tools/tools-tree.js +++ b/client/galaxy/scripts/mvc/tools/tools-tree.js @@ -10,13 +10,13 @@ return Backbone.Model.extend({ initialize: function(app) { this.app = app; }, - + /** Convert dictionary representation into tool api specific flat dictionary format. */ finalize: function(patch) { // link this var self = this; - + // dictionary with api specific identifiers this.map_dict = {}; @@ -24,24 +24,24 @@ return Backbone.Model.extend({ if (!this.app.section) { return {}; } - + // ensure that dictionary with patching functions exists patch = patch || {}; - + // dictionary formatted for job submission or tool form update var result_dict = {}; // prepare full dictionary var dict = {}; - + // fill dictionary from dom this._iterate(this.app.section.$el, dict); - + // add identifier and value to job definition function add(job_input_id, input_id, input_value) { // add entry to result dictionary result_dict[job_input_id] = input_value; - + // backup id mapping self.map_dict[job_input_id] = input_id; }; @@ -53,21 +53,21 @@ return Backbone.Model.extend({ if (node.input) { // get node var input = node.input; - + // create identifier var job_input_id = identifier; if (identifier != '') { job_input_id += '|'; } job_input_id += input.name; - + // process input type switch (input.type) { // handle repeats case 'repeat': // section identifier var section_label = 'section-'; - + // collect repeat block identifiers var block_indices = []; var block_prefix = null; @@ -81,10 +81,10 @@ return Backbone.Model.extend({ } } } - + // sort repeat blocks block_indices.sort(function(a,b) { return a - b; }); - + // add to response dictionary in created order var index = 0; for (var i in block_indices) { @@ -98,10 +98,10 @@ return Backbone.Model.extend({ if (patch[input.test_param.type]) { value = patch[input.test_param.type](value); } - + // add conditional value add (job_input_id + '|' + input.test_param.name, input.id, value); - + // identify selected case var selectedCase = self.matchCase(input, value); if (selectedCase != -1) { @@ -116,22 +116,17 @@ return Backbone.Model.extend({ // get field var field = self.app.field_list[input.id]; if (field && field.value) { - // get and patch field value + // validate field value var value = field.value(); - if (patch[input.type]) { - value = patch[input.type](value); - } - - // validate field value - if (field.skip) { - continue; - } - - // validate field value if (field.validate && !field.validate()) { value = null; } + // get and patch field value + if (patch[input.type]) { + value = patch[input.type](value); + } + // ignore certain values if (input.ignore === undefined || (value !== null && input.ignore != value)) { // add value to submission @@ -149,20 +144,20 @@ return Backbone.Model.extend({ } } } - + // start conversion convert('', dict); - + // return result return result_dict; }, - + /** Match job definition identifier to input element identifier */ match: function (job_input_id) { return this.map_dict && this.map_dict[job_input_id]; }, - + /** Match conditional values to selected cases */ matchCase: function(input, value) { @@ -174,27 +169,27 @@ return Backbone.Model.extend({ value = input.test_param.falsevalue || 'false'; } } - + // find selected case for (var i in input.cases) { if (input.cases[i].value == value) { return i; } } - + // selected case not found return -1; }, - + /** Matches identifier from api model to input elements */ matchModel: function(model, callback) { // final result dictionary var result = {}; - + // link this var self = this; - + // search throughout response function search (id, head) { for (var i in head) { @@ -224,23 +219,23 @@ return Backbone.Model.extend({ } } } - + // match all ids and return messages search('', model.inputs); // return matched results return result; }, - + /** Matches identifier from api response to input elements */ matchResponse: function(response) { // final result dictionary var result = {}; - + // link this var self = this; - + // search throughout response function search (id, head) { if (typeof head === 'string') { @@ -262,14 +257,14 @@ return Backbone.Model.extend({ } } } - + // match all ids and return messages search('', response); - + // return matched results return result; }, - + /** Iterate through the tool form dom and map it to the dictionary. */ _iterate: function(parent, dict) { @@ -279,15 +274,15 @@ return Backbone.Model.extend({ children.each(function() { // get child element var child = this; - + // get id var id = $(child).attr('id'); - + // create new branch if ($(child).hasClass('section-row')) { // create sub dictionary dict[id] = {}; - + // add input element if it exists var input = self.app.input_list[id]; if (input) { @@ -295,7 +290,7 @@ return Backbone.Model.extend({ input : input } } - + // fill sub dictionary self._iterate(child, dict[id]); } else { diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 46ba59e477b..ec5f7086416 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -2250,13 +2250,12 @@ class Tool( object, Dictifiable ): return tool_dict - def to_json (self, trans, **kwd): + def to_json (self, trans, kwd={}, is_dynamic=True): """ Recursively creates a tool dictionary containing repeats, dynamic options and updated states. """ job_id = kwd.get('__job_id__', None) dataset_id = kwd.get('__dataset_id__', None) - is_dynamic = string_as_bool(kwd.get('__is_dynamic__', True)) # load job details if provided job = None diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py index 4a0930338eb..12522c27254 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -1618,6 +1618,8 @@ class DrillDownSelectToolParameter( SelectToolParameter ): return UnvalidatedValue( None ) initial_values = [] recurse_options( initial_values, self.get_options( trans=trans, other_values=context ) ) + if len( initial_values ) == 0: + initial_values = None return initial_values def value_to_display_text( self, value, app ): diff --git a/lib/galaxy/webapps/galaxy/api/tools.py b/lib/galaxy/webapps/galaxy/api/tools.py index 7af238f86d6..2940d1ecf47 100644 --- a/lib/galaxy/webapps/galaxy/api/tools.py +++ b/lib/galaxy/webapps/galaxy/api/tools.py @@ -76,14 +76,10 @@ class ToolsController( BaseAPIController, UsesVisualizationMixin ): GET /api/tools/{tool_id}/build Returns a tool model including dynamic parameters and updated values, repeats block etc. """ - tool_id = urllib.unquote_plus( id ) tool_version = kwd.get( 'tool_version', None ) - tool = self.app.toolbox.get_tool( tool_id, tool_version ) - if not tool: - trans.response.status = 500 - return { 'error': 'Could not find tool with id \'%s\'' % tool_id } - return tool.to_json(trans, **kwd) - + tool = self._get_tool( id, tool_version=tool_version, user=trans.user ) + return tool.to_json(trans, kwd) + @_future_expose_api @web.require_admin def reload( self, trans, tool_id, **kwd ): diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 74befbead2b..69086100b0a 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -6,6 +6,8 @@ from __future__ import absolute_import import uuid import logging +import copy +import urllib from sqlalchemy import desc, or_, and_ from galaxy import exceptions, util from galaxy.model.item_attrs import UsesAnnotations @@ -17,6 +19,7 @@ from galaxy.web.base.controller import SharableMixin from galaxy.workflow.extract import extract_workflow from galaxy.workflow.run import invoke, queue_invoke from galaxy.workflow.run_request import build_workflow_run_config +from galaxy.workflow.modules import module_factory log = logging.getLogger(__name__) @@ -305,6 +308,52 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn raise exceptions.RequestParameterInvalidException( message ) return self.workflow_contents_manager.workflow_to_dict( trans, stored_workflow, style="instance" ) + @expose_api + def build_module( self, trans, payload={}): + """ + POST /api/workflows/build_module + Builds module details including a tool model for the workflow editor. + """ + tool_id = payload.get( 'tool_id', None ) + tool_version = payload.get( 'tool_version', None ) + tool_inputs = payload.get( 'inputs', None ) + annotation = payload.get( 'annotation', '' ) + + # load tool + tool = self._get_tool( tool_id, tool_version=tool_version, user=trans.user ) + + # initialize module + trans.workflow_building_mode = True + module = module_factory.from_dict( trans, { + 'type' : 'tool', + 'tool_id' : tool.id, + 'tool_state' : None + } ) + + # create tool model and default tool state (if missing) + tool_model = module.tool.to_json(trans, tool_inputs, is_dynamic=False) + module.state.inputs = copy.deepcopy(tool_model['state_inputs']) + return { + 'tool_model' : tool_model, + 'tool_state' : module.get_state(), + 'data_inputs' : module.get_data_inputs(), + 'data_outputs' : module.get_data_outputs(), + 'tool_errors' : module.get_errors(), + 'form_html' : module.get_config_form(), + 'annotation' : annotation, + 'post_job_actions' : module.get_post_job_actions(tool_inputs) + } + + # + # -- Helper methods -- + # + def _get_tool( self, id, tool_version=None, user=None ): + id = urllib.unquote_plus( id ) + tool = self.app.toolbox.get_tool( id, tool_version ) + if not tool or not tool.allow_user_access( user ): + raise exceptions.ObjectNotFound("Could not find tool with id '%s'" % id) + return tool + def __api_import_new_workflow( self, trans, payload, **kwd ): data = payload['workflow'] diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 80a3739c269..d367c4fcd51 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -217,6 +217,7 @@ def populate_api_routes( webapp, app ): webapp.mapper.resource_with_deleted( 'user', 'users', path_prefix='/api' ) webapp.mapper.resource( 'genome', 'genomes', path_prefix='/api' ) webapp.mapper.resource( 'visualization', 'visualizations', path_prefix='/api' ) + webapp.mapper.connect( '/api/workflows/build_module', action='build_module', controller="workflows" ) webapp.mapper.resource( 'workflow', 'workflows', path_prefix='/api' ) webapp.mapper.resource_with_deleted( 'history', 'histories', path_prefix='/api' ) webapp.mapper.connect( '/api/histories/{history_id}/citations', action='citations', controller="histories" ) diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index 24fcdd99e5f..4ec37df9152 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -609,7 +609,6 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix This is used for the form shown in the right pane when a node is selected. """ - tool_state = incoming.pop('tool_state', None) trans.workflow_building_mode = True module = module_factory.from_dict( trans, { @@ -617,22 +616,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix 'tool_id': tool_id, 'tool_state': tool_state } ) - - # create tool model and default tool state (if missing) - if type == 'tool' and not tool_state: - tool_model = module.tool.to_json(trans, **incoming) - module.state.inputs = copy.deepcopy(tool_model['state_inputs']) - return { - 'tool_model': tool_model, - 'tool_state': module.get_state(), - 'data_inputs': module.get_data_inputs(), - 'data_outputs': module.get_data_outputs(), - 'tool_errors': module.get_errors(), - 'form_html': module.get_config_form(), - 'annotation': annotation, - 'post_job_actions': module.get_post_job_actions(incoming) - } - + # update module state module.update_state( incoming ) @@ -669,7 +653,8 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix module = module_factory.new( trans, type, **kwargs ) tool_model = None if type == 'tool': - tool_model = module.tool.to_json(trans) + tool_model = module.tool.to_json(trans, is_dynamic=False) + module.state.inputs = copy.deepcopy(tool_model['state_inputs']) return { 'type': module.type, 'name': module.get_name(), diff --git a/static/scripts/mvc/tools/tools-form-workflow.js b/static/scripts/mvc/tools/tools-form-workflow.js index 0b19f6ef1e9..bbd904283d0 100644 --- a/static/scripts/mvc/tools/tools-form-workflow.js +++ b/static/scripts/mvc/tools/tools-form-workflow.js @@ -275,10 +275,16 @@ define(['utils/utils', 'mvc/tools/tools-form-base'], /** Request a new model for an already created tool form and updates the form inputs */ _updateModel: function() { - // create the request dictionary + // link self var self = this; - var current_state = this.tree.finalize(); - + + // create the request dictionary + var current_state = { + tool_id : this.options.id, + tool_version : this.options.version, + inputs : this.tree.finalize() + } + // log tool state console.debug('tools-form-workflow::_refreshForm() - Refreshing states.'); console.debug(current_state); @@ -287,11 +293,11 @@ define(['utils/utils', 'mvc/tools/tools-form-base'], var process_id = this.deferred.register(); // build model url for request - var model_url = galaxy_config.root + 'workflow/editor_form_post?tool_id=' + this.options.id + '&__is_dynamic__=False'; + var model_url = galaxy_config.root + 'api/workflows/build_module'; // post job Utils.request({ - type : 'GET', + type : 'POST', url : model_url, data : current_state, success : function(data) { diff --git a/static/scripts/mvc/tools/tools-tree.js b/static/scripts/mvc/tools/tools-tree.js index b036c68cbee..44918ece8b7 100644 --- a/static/scripts/mvc/tools/tools-tree.js +++ b/static/scripts/mvc/tools/tools-tree.js @@ -10,13 +10,13 @@ return Backbone.Model.extend({ initialize: function(app) { this.app = app; }, - + /** Convert dictionary representation into tool api specific flat dictionary format. */ finalize: function(patch) { // link this var self = this; - + // dictionary with api specific identifiers this.map_dict = {}; @@ -24,24 +24,24 @@ return Backbone.Model.extend({ if (!this.app.section) { return {}; } - + // ensure that dictionary with patching functions exists patch = patch || {}; - + // dictionary formatted for job submission or tool form update var result_dict = {}; // prepare full dictionary var dict = {}; - + // fill dictionary from dom this._iterate(this.app.section.$el, dict); - + // add identifier and value to job definition function add(job_input_id, input_id, input_value) { // add entry to result dictionary result_dict[job_input_id] = input_value; - + // backup id mapping self.map_dict[job_input_id] = input_id; }; @@ -53,21 +53,21 @@ return Backbone.Model.extend({ if (node.input) { // get node var input = node.input; - + // create identifier var job_input_id = identifier; if (identifier != '') { job_input_id += '|'; } job_input_id += input.name; - + // process input type switch (input.type) { // handle repeats case 'repeat': // section identifier var section_label = 'section-'; - + // collect repeat block identifiers var block_indices = []; var block_prefix = null; @@ -81,10 +81,10 @@ return Backbone.Model.extend({ } } } - + // sort repeat blocks block_indices.sort(function(a,b) { return a - b; }); - + // add to response dictionary in created order var index = 0; for (var i in block_indices) { @@ -98,10 +98,10 @@ return Backbone.Model.extend({ if (patch[input.test_param.type]) { value = patch[input.test_param.type](value); } - + // add conditional value add (job_input_id + '|' + input.test_param.name, input.id, value); - + // identify selected case var selectedCase = self.matchCase(input, value); if (selectedCase != -1) { @@ -116,22 +116,17 @@ return Backbone.Model.extend({ // get field var field = self.app.field_list[input.id]; if (field && field.value) { - // get and patch field value + // validate field value var value = field.value(); - if (patch[input.type]) { - value = patch[input.type](value); - } - - // validate field value - if (field.skip) { - continue; - } - - // validate field value if (field.validate && !field.validate()) { value = null; } + // get and patch field value + if (patch[input.type]) { + value = patch[input.type](value); + } + // ignore certain values if (input.ignore === undefined || (value !== null && input.ignore != value)) { // add value to submission @@ -149,20 +144,20 @@ return Backbone.Model.extend({ } } } - + // start conversion convert('', dict); - + // return result return result_dict; }, - + /** Match job definition identifier to input element identifier */ match: function (job_input_id) { return this.map_dict && this.map_dict[job_input_id]; }, - + /** Match conditional values to selected cases */ matchCase: function(input, value) { @@ -174,27 +169,27 @@ return Backbone.Model.extend({ value = input.test_param.falsevalue || 'false'; } } - + // find selected case for (var i in input.cases) { if (input.cases[i].value == value) { return i; } } - + // selected case not found return -1; }, - + /** Matches identifier from api model to input elements */ matchModel: function(model, callback) { // final result dictionary var result = {}; - + // link this var self = this; - + // search throughout response function search (id, head) { for (var i in head) { @@ -224,23 +219,23 @@ return Backbone.Model.extend({ } } } - + // match all ids and return messages search('', model.inputs); // return matched results return result; }, - + /** Matches identifier from api response to input elements */ matchResponse: function(response) { // final result dictionary var result = {}; - + // link this var self = this; - + // search throughout response function search (id, head) { if (typeof head === 'string') { @@ -262,14 +257,14 @@ return Backbone.Model.extend({ } } } - + // match all ids and return messages search('', response); - + // return matched results return result; }, - + /** Iterate through the tool form dom and map it to the dictionary. */ _iterate: function(parent, dict) { @@ -279,15 +274,15 @@ return Backbone.Model.extend({ children.each(function() { // get child element var child = this; - + // get id var id = $(child).attr('id'); - + // create new branch if ($(child).hasClass('section-row')) { // create sub dictionary dict[id] = {}; - + // add input element if it exists var input = self.app.input_list[id]; if (input) { @@ -295,7 +290,7 @@ return Backbone.Model.extend({ input : input } } - + // fill sub dictionary self._iterate(child, dict[id]); } else { diff --git a/static/scripts/packed/mvc/tools/tools-form-workflow.js b/static/scripts/packed/mvc/tools/tools-form-workflow.js index 744d005de36..37916b81f3a 100644 --- a/static/scripts/packed/mvc/tools/tools-form-workflow.js +++ b/static/scripts/packed/mvc/tools/tools-form-workflow.js @@ -1 +1 @@ -define(["utils/utils","mvc/tools/tools-form-base"],function(b,a){var c=a.extend({initialize:function(e){this.node=workflow.active_node;if(!this.node){console.debug("FAILED - tools-form-workflow:initialize() - Node not found in workflow.");return}this.post_job_actions=this.node.post_job_actions||{};this.options=e;this.options.text_enable="In Advance";this.options.text_disable="At Runtime";this.options.is_dynamic=false;this.options.narrow=true;this.options.initial_errors=true;this.options.cls_portlet="ui-portlet-narrow";b.deepeach(e.inputs,function(f){if(f.type){f.optional=(["data","data_hidden","hidden","drill_down","repeat","conditional"]).indexOf(f.type)==-1}});b.deepeach(e.inputs,function(f){if(f.type){if(f.type=="conditional"){f.test_param.optional=false}}});var d=this;b.get({url:galaxy_config.root+"api/datatypes",cache:true,success:function(f){d.datatypes=f;d._makeSections(e.inputs);a.prototype.initialize.call(d,e)}})},_makeSections:function(d){d[b.uuid()]={label:"Annotation / Notes",name:"annotation",type:"text",area:true,help:"Add an annotation or note for this step. It will be shown with the workflow.",value:this.node.annotation};var f=this.node.output_terminals&&Object.keys(this.node.output_terminals)[0];if(f){d[b.uuid()]={name:"pja__"+f+"__EmailAction",label:"Email notification",type:"boolean",value:String(Boolean(this.post_job_actions["EmailAction"+f])),ignore:"false",help:"An email notification will be send when the job has completed.",payload:{host:window.location.host}};d[b.uuid()]={name:"pja__"+f+"__DeleteIntermediatesAction",label:"Output cleanup",type:"boolean",value:String(Boolean(this.post_job_actions["DeleteIntermediatesAction"+f])),ignore:"false",help:"Delete intermediate outputs if they are not used as input for another job."};for(var e in this.node.output_terminals){d[b.uuid()]=this._makeSection(e)}}},_makeSection:function(h){var g=[];for(key in this.datatypes){g.push({0:this.datatypes[key],1:this.datatypes[key]})}g.sort(function(j,i){return j.label>i.label?1:j.labelhere for more information.'},{action:"ChangeDatatypeAction",argument:"newtype",label:"Change datatype",type:"select",ignore:"",options:g,help:"This action will change the datatype of the output to the indicated value."},{action:"TagDatasetAction",argument:"tags",label:"Tags",type:"text",value:"",ignore:"",help:"This action will set tags for the dataset."},{label:"Assign columns",type:"section",inputs:[{action:"ColumnSetAction",argument:"chromCol",label:"Chrom column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"startCol",label:"Start column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"endCol",label:"End column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"strandCol",label:"Strand column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"nameCol",label:"Name column",type:"integer",value:"",ignore:""}],help:"This action will set column assignments in the output dataset. Blank fields are ignored."}]};var d=this;function e(n,o){o=o||[];o.push(n);for(var m in n.inputs){var k=n.inputs[m];if(k.action){k.name="pja__"+h+"__"+k.action;if(k.argument){k.name+="__"+k.argument}if(k.payload){for(var s in k.payload){var q=k.payload[s];k.payload[k.name+"__"+s]=q;delete q}}var r=d.post_job_actions[k.action+h];if(r){for(var l in o){o[l].expand=true}if(k.argument){k.value=r.action_arguments&&r.action_arguments[k.argument]||k.value}else{k.value="true"}}}if(k.inputs){e(k,o.slice(0))}}}e(f);return f},_buildModel:function(){Galaxy.modal.show({title:"Coming soon...",body:"This feature has not been implemented yet.",buttons:{Close:function(){Galaxy.modal.hide()}}})},_updateModel:function(){var d=this;var e=this.tree.finalize();console.debug("tools-form-workflow::_refreshForm() - Refreshing states.");console.debug(e);var g=this.deferred.register();var f=galaxy_config.root+"workflow/editor_form_post?tool_id="+this.options.id+"&__is_dynamic__=False";b.request({type:"GET",url:f,data:e,success:function(h){d.node.update_field_data(h);d._errors(h&&h.tool_model);d.deferred.done(g);console.debug("tools-form::_refreshForm() - States refreshed.");console.debug(h)},error:function(h){d.deferred.done(g);console.debug("tools-form::_refreshForm() - Refresh request failed.");console.debug(h)}})}});return{View:c}}); \ No newline at end of file +define(["utils/utils","mvc/tools/tools-form-base"],function(b,a){var c=a.extend({initialize:function(e){this.node=workflow.active_node;if(!this.node){console.debug("FAILED - tools-form-workflow:initialize() - Node not found in workflow.");return}this.post_job_actions=this.node.post_job_actions||{};this.options=e;this.options.text_enable="In Advance";this.options.text_disable="At Runtime";this.options.is_dynamic=false;this.options.narrow=true;this.options.initial_errors=true;this.options.cls_portlet="ui-portlet-narrow";b.deepeach(e.inputs,function(f){if(f.type){f.optional=(["data","data_hidden","hidden","drill_down","repeat","conditional"]).indexOf(f.type)==-1}});b.deepeach(e.inputs,function(f){if(f.type){if(f.type=="conditional"){f.test_param.optional=false}}});var d=this;b.get({url:galaxy_config.root+"api/datatypes",cache:true,success:function(f){d.datatypes=f;d._makeSections(e.inputs);a.prototype.initialize.call(d,e)}})},_makeSections:function(d){d[b.uuid()]={label:"Annotation / Notes",name:"annotation",type:"text",area:true,help:"Add an annotation or note for this step. It will be shown with the workflow.",value:this.node.annotation};var f=this.node.output_terminals&&Object.keys(this.node.output_terminals)[0];if(f){d[b.uuid()]={name:"pja__"+f+"__EmailAction",label:"Email notification",type:"boolean",value:String(Boolean(this.post_job_actions["EmailAction"+f])),ignore:"false",help:"An email notification will be send when the job has completed.",payload:{host:window.location.host}};d[b.uuid()]={name:"pja__"+f+"__DeleteIntermediatesAction",label:"Output cleanup",type:"boolean",value:String(Boolean(this.post_job_actions["DeleteIntermediatesAction"+f])),ignore:"false",help:"Delete intermediate outputs if they are not used as input for another job."};for(var e in this.node.output_terminals){d[b.uuid()]=this._makeSection(e)}}},_makeSection:function(h){var g=[];for(key in this.datatypes){g.push({0:this.datatypes[key],1:this.datatypes[key]})}g.sort(function(j,i){return j.label>i.label?1:j.labelhere for more information.'},{action:"ChangeDatatypeAction",argument:"newtype",label:"Change datatype",type:"select",ignore:"",options:g,help:"This action will change the datatype of the output to the indicated value."},{action:"TagDatasetAction",argument:"tags",label:"Tags",type:"text",value:"",ignore:"",help:"This action will set tags for the dataset."},{label:"Assign columns",type:"section",inputs:[{action:"ColumnSetAction",argument:"chromCol",label:"Chrom column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"startCol",label:"Start column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"endCol",label:"End column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"strandCol",label:"Strand column",type:"integer",value:"",ignore:""},{action:"ColumnSetAction",argument:"nameCol",label:"Name column",type:"integer",value:"",ignore:""}],help:"This action will set column assignments in the output dataset. Blank fields are ignored."}]};var d=this;function e(n,o){o=o||[];o.push(n);for(var m in n.inputs){var k=n.inputs[m];if(k.action){k.name="pja__"+h+"__"+k.action;if(k.argument){k.name+="__"+k.argument}if(k.payload){for(var s in k.payload){var q=k.payload[s];k.payload[k.name+"__"+s]=q;delete q}}var r=d.post_job_actions[k.action+h];if(r){for(var l in o){o[l].expand=true}if(k.argument){k.value=r.action_arguments&&r.action_arguments[k.argument]||k.value}else{k.value="true"}}}if(k.inputs){e(k,o.slice(0))}}}e(f);return f},_buildModel:function(){Galaxy.modal.show({title:"Coming soon...",body:"This feature has not been implemented yet.",buttons:{Close:function(){Galaxy.modal.hide()}}})},_updateModel:function(){var d=this;var e={tool_id:this.options.id,tool_version:this.options.version,inputs:this.tree.finalize()};console.debug("tools-form-workflow::_refreshForm() - Refreshing states.");console.debug(e);var g=this.deferred.register();var f=galaxy_config.root+"api/workflows/build_module";b.request({type:"POST",url:f,data:e,success:function(h){d.node.update_field_data(h);d._errors(h&&h.tool_model);d.deferred.done(g);console.debug("tools-form::_refreshForm() - States refreshed.");console.debug(h)},error:function(h){d.deferred.done(g);console.debug("tools-form::_refreshForm() - Refresh request failed.");console.debug(h)}})}});return{View:c}}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/tools/tools-tree.js b/static/scripts/packed/mvc/tools/tools-tree.js index 0ad40284bf1..893a8cdfac7 100644 --- a/static/scripts/packed/mvc/tools/tools-tree.js +++ b/static/scripts/packed/mvc/tools/tools-tree.js @@ -1 +1 @@ -define(["utils/utils"],function(a){return Backbone.Model.extend({initialize:function(b){this.app=b},finalize:function(g){var b=this;this.map_dict={};if(!this.app.section){return{}}g=g||{};var f={};var e={};this._iterate(this.app.section.$el,e);function d(j,i,h){f[j]=h;b.map_dict[j]=i}function c(p,s){for(var n in s){var k=s[n];if(k.input){var u=k.input;var o=p;if(p!=""){o+="|"}o+=u.name;switch(u.type){case"repeat":var j="section-";var x=[];var r=null;for(var w in k){var q=w.indexOf(j);if(q!=-1){q+=j.length;x.push(parseInt(w.substr(q)));if(!r){r=w.substr(0,q)}}}x.sort(function(y,i){return y-i});var n=0;for(var l in x){c(o+"_"+n++,k[r+x[l]])}break;case"conditional":var v=b.app.field_list[u.id].value();if(g[u.test_param.type]){v=g[u.test_param.type](v)}d(o+"|"+u.test_param.name,u.id,v);var h=b.matchCase(u,v);if(h!=-1){c(o,s[u.id+"-section-"+h])}break;case"section":c("",k);break;default:var t=b.app.field_list[u.id];if(t&&t.value){var v=t.value();if(g[u.type]){v=g[u.type](v)}if(t.skip){continue}if(t.validate&&!t.validate()){v=null}if(u.ignore===undefined||(v!==null&&u.ignore!=v)){d(o,u.id,v);if(u.payload){for(var m in u.payload){d(m,u.id,u.payload[m])}}}}}}}}c("",e);return f},match:function(b){return this.map_dict&&this.map_dict[b]},matchCase:function(b,d){if(b.test_param.type=="boolean"){if(d=="true"){d=b.test_param.truevalue||"true"}else{d=b.test_param.falsevalue||"false"}}for(var c in b.cases){if(b.cases[c].value==d){return c}}return -1},matchModel:function(d,f){var b={};var c=this;function e(g,p){for(var m in p){var k=p[m];var n=k.name;if(g!=""){n=g+"|"+n}switch(k.type){case"repeat":for(var l in k.cache){e(n+"_"+l,k.cache[l])}break;case"conditional":var q=k.test_param&&k.test_param.value;var h=c.matchCase(k,q);if(h!=-1){e(n,k.cases[h].inputs)}break;default:var o=c.map_dict[n];if(o){f(o,k)}}}}e("",d.inputs);return b},matchResponse:function(d){var b={};var c=this;function e(l,j){if(typeof j==="string"){var g=c.map_dict[l];if(g){b[g]=j}}else{for(var h in j){var f=h;if(l!==""){var k="|";if(j instanceof Array){k="_"}f=l+k+f}e(f,j[h])}}}e("",d);return b},_iterate:function(d,e){var b=this;var c=$(d).children();c.each(function(){var h=this;var g=$(h).attr("id");if($(h).hasClass("section-row")){e[g]={};var f=b.app.input_list[g];if(f){e[g]={input:f}}b._iterate(h,e[g])}else{b._iterate(h,e)}})}})}); \ No newline at end of file +define(["utils/utils"],function(a){return Backbone.Model.extend({initialize:function(b){this.app=b},finalize:function(g){var b=this;this.map_dict={};if(!this.app.section){return{}}g=g||{};var f={};var e={};this._iterate(this.app.section.$el,e);function d(j,i,h){f[j]=h;b.map_dict[j]=i}function c(p,s){for(var n in s){var k=s[n];if(k.input){var u=k.input;var o=p;if(p!=""){o+="|"}o+=u.name;switch(u.type){case"repeat":var j="section-";var x=[];var r=null;for(var w in k){var q=w.indexOf(j);if(q!=-1){q+=j.length;x.push(parseInt(w.substr(q)));if(!r){r=w.substr(0,q)}}}x.sort(function(y,i){return y-i});var n=0;for(var l in x){c(o+"_"+n++,k[r+x[l]])}break;case"conditional":var v=b.app.field_list[u.id].value();if(g[u.test_param.type]){v=g[u.test_param.type](v)}d(o+"|"+u.test_param.name,u.id,v);var h=b.matchCase(u,v);if(h!=-1){c(o,s[u.id+"-section-"+h])}break;case"section":c("",k);break;default:var t=b.app.field_list[u.id];if(t&&t.value){var v=t.value();if(t.validate&&!t.validate()){v=null}if(g[u.type]){v=g[u.type](v)}if(u.ignore===undefined||(v!==null&&u.ignore!=v)){d(o,u.id,v);if(u.payload){for(var m in u.payload){d(m,u.id,u.payload[m])}}}}}}}}c("",e);return f},match:function(b){return this.map_dict&&this.map_dict[b]},matchCase:function(b,d){if(b.test_param.type=="boolean"){if(d=="true"){d=b.test_param.truevalue||"true"}else{d=b.test_param.falsevalue||"false"}}for(var c in b.cases){if(b.cases[c].value==d){return c}}return -1},matchModel:function(d,f){var b={};var c=this;function e(g,p){for(var m in p){var k=p[m];var n=k.name;if(g!=""){n=g+"|"+n}switch(k.type){case"repeat":for(var l in k.cache){e(n+"_"+l,k.cache[l])}break;case"conditional":var q=k.test_param&&k.test_param.value;var h=c.matchCase(k,q);if(h!=-1){e(n,k.cases[h].inputs)}break;default:var o=c.map_dict[n];if(o){f(o,k)}}}}e("",d.inputs);return b},matchResponse:function(d){var b={};var c=this;function e(l,j){if(typeof j==="string"){var g=c.map_dict[l];if(g){b[g]=j}}else{for(var h in j){var f=h;if(l!==""){var k="|";if(j instanceof Array){k="_"}f=l+k+f}e(f,j[h])}}}e("",d);return b},_iterate:function(d,e){var b=this;var c=$(d).children();c.each(function(){var h=this;var g=$(h).attr("id");if($(h).hasClass("section-row")){e[g]={};var f=b.app.input_list[g];if(f){e[g]={input:f}}b._iterate(h,e[g])}else{b._iterate(h,e)}})}})}); \ No newline at end of file diff --git a/templates/webapps/galaxy/tool_form.mako b/templates/webapps/galaxy/tool_form.mako index 96c3f6bd4a1..9f7d986b62e 100644 --- a/templates/webapps/galaxy/tool_form.mako +++ b/templates/webapps/galaxy/tool_form.mako @@ -10,7 +10,7 @@ ${h.css('base', 'jquery-ui/smoothness/jquery-ui')} ## This avoids making two separate requests since the classic form requires the mako anyway. params = dict(trans.request.params) params['__dataset_id__'] = params.get('id', None) - self.form_config = tool.to_json(trans, **params) + self.form_config = tool.to_json(trans, params) self.form_config.update({ 'id' : tool.id, 'job_id' : trans.security.encode_id( job.id ) if job else None, diff --git a/templates/webapps/galaxy/workflow/editor_tool_form.mako b/templates/webapps/galaxy/workflow/editor_tool_form.mako index 35abe4ac0af..ce039bd145c 100644 --- a/templates/webapps/galaxy/workflow/editor_tool_form.mako +++ b/templates/webapps/galaxy/workflow/editor_tool_form.mako @@ -6,11 +6,9 @@ ## TEMPORARY: create tool dictionary in mako while both tool forms are in use. ## This avoids making two separate requests since the classic form requires the mako anyway. from galaxy.tools.parameters import params_to_incoming - incoming = { - '__is_dynamic__' : False - } + incoming = {} params_to_incoming( incoming, tool.inputs, module.state.inputs, trans.app, to_html=False) - self.form_config = tool.to_json(trans, **incoming) + self.form_config = tool.to_json(trans, incoming, is_dynamic=False) self.form_config.update({ 'id' : tool.id, 'job_id' : trans.security.encode_id( job.id ) if job else None, From 637b43f67b0846914d2ac9fc884bd1bf820fe799 Mon Sep 17 00:00:00 2001 From: Aysam Guerler Date: Mon, 9 Feb 2015 09:15:25 -0500 Subject: [PATCH 04/10] ToolForm: Set missing values to null --- client/galaxy/scripts/mvc/tools/tools-input.js | 3 +-- client/galaxy/scripts/mvc/tools/tools-section.js | 13 ++++++++----- static/scripts/mvc/tools/tools-input.js | 3 +-- static/scripts/mvc/tools/tools-section.js | 13 ++++++++----- static/scripts/packed/mvc/tools/tools-input.js | 2 +- static/scripts/packed/mvc/tools/tools-section.js | 2 +- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/client/galaxy/scripts/mvc/tools/tools-input.js b/client/galaxy/scripts/mvc/tools/tools-input.js index be4975d1b79..f6b47d35481 100644 --- a/client/galaxy/scripts/mvc/tools/tools-input.js +++ b/client/galaxy/scripts/mvc/tools/tools-input.js @@ -34,8 +34,7 @@ define([], function() { this.field.skip = false; var v = this.field.value && this.field.value(); this.field.skip = Boolean(options.optional && - ((this.default_value === undefined) || - ((this.field.validate && !this.field.validate()) || !v || + (((this.field.validate && !this.field.validate()) || !v || (v == this.default_value) || (Number(v) == Number(this.default_value)) || (JSON.stringify(v) == JSON.stringify(this.default_value))))); diff --git a/client/galaxy/scripts/mvc/tools/tools-section.js b/client/galaxy/scripts/mvc/tools/tools-section.js index e96d6d37e23..ac7caf13c75 100644 --- a/client/galaxy/scripts/mvc/tools/tools-section.js +++ b/client/galaxy/scripts/mvc/tools/tools-section.js @@ -294,17 +294,20 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/ui/ui-portlet', // get id var id = input_def.id; + // add regular/default value if missing + if (input_def.value === undefined) { + input_def.value = null; + } + if (input_def.default_value === undefined) { + input_def.default_value = input_def.value; + } + // create input field var field = this._createField(input_def); // add to field list this.app.field_list[id] = field; - // fix default value if missing - if (input_def.default_value === undefined) { - input_def.default_value = input_def.value; - } - // create input field wrapper var input_element = new InputElement(this.app, { label : input_def.label, diff --git a/static/scripts/mvc/tools/tools-input.js b/static/scripts/mvc/tools/tools-input.js index be4975d1b79..f6b47d35481 100644 --- a/static/scripts/mvc/tools/tools-input.js +++ b/static/scripts/mvc/tools/tools-input.js @@ -34,8 +34,7 @@ define([], function() { this.field.skip = false; var v = this.field.value && this.field.value(); this.field.skip = Boolean(options.optional && - ((this.default_value === undefined) || - ((this.field.validate && !this.field.validate()) || !v || + (((this.field.validate && !this.field.validate()) || !v || (v == this.default_value) || (Number(v) == Number(this.default_value)) || (JSON.stringify(v) == JSON.stringify(this.default_value))))); diff --git a/static/scripts/mvc/tools/tools-section.js b/static/scripts/mvc/tools/tools-section.js index e96d6d37e23..ac7caf13c75 100644 --- a/static/scripts/mvc/tools/tools-section.js +++ b/static/scripts/mvc/tools/tools-section.js @@ -294,17 +294,20 @@ define(['utils/utils', 'mvc/ui/ui-table', 'mvc/ui/ui-misc', 'mvc/ui/ui-portlet', // get id var id = input_def.id; + // add regular/default value if missing + if (input_def.value === undefined) { + input_def.value = null; + } + if (input_def.default_value === undefined) { + input_def.default_value = input_def.value; + } + // create input field var field = this._createField(input_def); // add to field list this.app.field_list[id] = field; - // fix default value if missing - if (input_def.default_value === undefined) { - input_def.default_value = input_def.value; - } - // create input field wrapper var input_element = new InputElement(this.app, { label : input_def.label, diff --git a/static/scripts/packed/mvc/tools/tools-input.js b/static/scripts/packed/mvc/tools/tools-input.js index 8404eee79d0..c927b4bc4a3 100644 --- a/static/scripts/packed/mvc/tools/tools-input.js +++ b/static/scripts/packed/mvc/tools/tools-input.js @@ -1 +1 @@ -define([],function(){return Backbone.View.extend({initialize:function(d,c){this.app=d;this.text_enable=d.options.text_enable||"Enable";this.text_disable=d.options.text_disable||"Disable";this.field=c.field;this.default_value=c.default_value;this.setElement(this._template(c));this.$field=this.$el.find(".ui-table-form-field");this.$title_optional=this.$el.find(".ui-table-form-title-optional");this.$error_text=this.$el.find(".ui-table-form-error-text");this.$error=this.$el.find(".ui-table-form-error");this.$field.prepend(this.field.$el);this.field.skip=false;var b=this.field.value&&this.field.value();this.field.skip=Boolean(c.optional&&((this.default_value===undefined)||((this.field.validate&&!this.field.validate())||!b||(b==this.default_value)||(Number(b)==Number(this.default_value))||(JSON.stringify(b)==JSON.stringify(this.default_value)))));this._refresh();var a=this;this.$title_optional.on("click",function(){a.field.skip=!a.field.skip;a._refresh()})},error:function(a){this.$error_text.html(a);this.$error.show();this.$el.addClass("ui-error")},reset:function(){this.$error.hide();this.$el.removeClass("ui-error")},_refresh:function(){if(!this.field.skip){this.$field.fadeIn("fast");this.$title_optional.html(this.text_disable)}else{this.reset();this.$field.hide();this.$title_optional.html(this.text_enable);this.field.value&&this.field.value(this.default_value)}this.app.trigger("refresh")},_template:function(a){var b='
';if(a.optional){b+=a.label+' []'}else{b+=a.label}b+='
';if(a.help){b+='
'+a.help+"
"}b+="
";return b}})}); \ No newline at end of file +define([],function(){return Backbone.View.extend({initialize:function(d,c){this.app=d;this.text_enable=d.options.text_enable||"Enable";this.text_disable=d.options.text_disable||"Disable";this.field=c.field;this.default_value=c.default_value;this.setElement(this._template(c));this.$field=this.$el.find(".ui-table-form-field");this.$title_optional=this.$el.find(".ui-table-form-title-optional");this.$error_text=this.$el.find(".ui-table-form-error-text");this.$error=this.$el.find(".ui-table-form-error");this.$field.prepend(this.field.$el);this.field.skip=false;var b=this.field.value&&this.field.value();this.field.skip=Boolean(c.optional&&(((this.field.validate&&!this.field.validate())||!b||(b==this.default_value)||(Number(b)==Number(this.default_value))||(JSON.stringify(b)==JSON.stringify(this.default_value)))));this._refresh();var a=this;this.$title_optional.on("click",function(){a.field.skip=!a.field.skip;a._refresh()})},error:function(a){this.$error_text.html(a);this.$error.show();this.$el.addClass("ui-error")},reset:function(){this.$error.hide();this.$el.removeClass("ui-error")},_refresh:function(){if(!this.field.skip){this.$field.fadeIn("fast");this.$title_optional.html(this.text_disable)}else{this.reset();this.$field.hide();this.$title_optional.html(this.text_enable);this.field.value&&this.field.value(this.default_value)}this.app.trigger("refresh")},_template:function(a){var b='
';if(a.optional){b+=a.label+' []'}else{b+=a.label}b+='
';if(a.help){b+='
'+a.help+"
"}b+="
";return b}})}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/tools/tools-section.js b/static/scripts/packed/mvc/tools/tools-section.js index de2f6fd8a18..887f841ca69 100644 --- a/static/scripts/packed/mvc/tools/tools-section.js +++ b/static/scripts/packed/mvc/tools/tools-section.js @@ -1 +1 @@ -define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-misc","mvc/ui/ui-portlet","mvc/tools/tools-repeat","mvc/tools/tools-select-content","mvc/tools/tools-input"],function(e,b,h,d,c,a,f){var g=Backbone.View.extend({initialize:function(j,i){this.app=j;this.inputs=i.inputs;i.cls="ui-table-plain";i.cls_tr="section-row";this.table=new b.View(i);this.setElement(this.table.$el);this.render()},render:function(){this.table.delAll();for(var j in this.inputs){this.add(this.inputs[j])}},add:function(k){var j=this;var i=jQuery.extend(true,{},k);i.id=k.id=e.uuid();this.app.input_list[i.id]=i;var l=i.type;switch(l){case"conditional":this._addConditional(i);break;case"repeat":this._addRepeat(i);break;case"section":this._addSection(i);break;default:this._addRow(i)}},_addConditional:function(j){var k=this;j.test_param.id=j.id;var n=this._addRow(j.test_param);n.options.onchange=function(w){var v=k.app.tree.matchCase(j,w);for(var u in j.cases){var q=j.cases[u];var t=j.id+"-section-"+u;var p=k.table.get(t);var s=false;for(var r in q.inputs){if(!q.inputs[r].hidden){s=true;break}}if(u==v&&s){p.fadeIn("fast")}else{p.hide()}}k.app.trigger("refresh")};for(var m in j.cases){var l=j.id+"-section-"+m;var o=new g(this.app,{inputs:j.cases[m].inputs});o.$el.addClass("ui-table-section");this.table.add(o.$el);this.table.append(l)}n.trigger("change")},_addRepeat:function(p){var s=this;var q=0;var l=new c.View({title:p.title,title_new:p.title,min:p.min,max:p.max,onnew:function(){n(p.inputs);s.app.trigger("refresh")}});function n(i){var t=p.id+"-section-"+(q++);var u=new g(s.app,{inputs:i});l.add({id:t,$el:u.$el,ondel:function(){l.del(t);s.app.trigger("refresh")}})}var j=p.min;var r=_.size(p.cache);for(var m=0;m").addClass("ui-table-form-info").html(i.help));var k=false;l.$content.hide();l.$header.css("cursor","pointer");l.$header.on("click",function(){if(k){k=false;l.$content.hide();m.setIcon("fa-eye-slash")}else{k=true;l.$content.fadeIn("fast");m.setIcon("fa-eye")}});if(i.expand){l.$header.trigger("click")}this.table.add(l.$el);this.table.append(i.id)},_addRow:function(i){var l=i.id;var j=this._createField(i);this.app.field_list[l]=j;if(i.default_value===undefined){i.default_value=i.value}var k=new f(this.app,{label:i.label,default_value:i.default_value,optional:i.optional,help:i.help,field:j});this.app.element_list[l]=k;this.table.add(k.$el);this.table.append(l);if(i.hidden){this.table.get(l).hide()}return j},_createField:function(i){var j=null;switch(i.type){case"text":j=this._fieldText(i);break;case"select":j=this._fieldSelect(i);break;case"data":j=this._fieldData(i);break;case"data_collection":j=this._fieldData(i);break;case"data_column":i.error_text="Missing columns in referenced dataset.";j=this._fieldSelect(i);break;case"hidden":j=this._fieldHidden(i);break;case"hidden_data":j=this._fieldHidden(i);break;case"integer":j=this._fieldSlider(i);break;case"float":j=this._fieldSlider(i);break;case"boolean":j=this._fieldBoolean(i);break;case"genomebuild":i.searchable=true;j=this._fieldSelect(i);break;case"drill_down":j=this._fieldDrilldown(i);break;case"baseurl":j=this._fieldHidden(i);break;default:this.app.incompatible=true;if(i.options){j=this._fieldSelect(i)}else{j=this._fieldText(i)}console.debug("tools-form::_addRow() : Auto matched field type ("+i.type+").")}if(i.value!==undefined){j.value(i.value)}return j},_fieldData:function(i){if(!this.app.options.is_dynamic){i.info="Data input '"+i.name+"' ("+e.textify(i.extensions.toString())+")";i.value=null;return this._fieldHidden(i)}var j=this;return new a.View(this.app,{id:"field-"+i.id,extensions:i.extensions,optional:i.optional,multiple:i.multiple,type:i.type,data:i.options,onchange:function(){j.app.trigger("refresh")}})},_fieldSelect:function(j){if(!this.app.options.is_dynamic&&j.is_dynamic){return this._fieldText(j)}var l=[];for(var m in j.options){var n=j.options[m];l.push({label:n[0],value:n[1]})}var o=h.Select;switch(j.display){case"checkboxes":o=h.Checkbox;break;case"radio":o=h.Radio;break}var k=this;return new o.View({id:"field-"+j.id,data:l,error_text:j.error_text||"No options available",multiple:j.multiple,searchable:j.searchable,onchange:function(){k.app.trigger("refresh")}})},_fieldDrilldown:function(i){if(!this.app.options.is_dynamic&&i.is_dynamic){return this._fieldText(i)}var j=this;return new h.Drilldown.View({id:"field-"+i.id,data:i.options,display:i.display,onchange:function(){j.app.trigger("refresh")}})},_fieldText:function(i){if(i.options){i.area=i.multiple;if(!e.validate(i.value)){i.value=""}else{if(i.value instanceof Array){i.value=value.toString()}else{i.value=String(i.value).replace(/[\[\]'"\s]/g,"");if(i.multiple){i.value=i.value.replace(/,/g,"\n")}}}}var j=this;return new h.Input({id:"field-"+i.id,area:i.area,onchange:function(){j.app.trigger("refresh")}})},_fieldSlider:function(i){var j=this;return new h.Slider.View({id:"field-"+i.id,precise:i.type=="float",min:i.min,max:i.max,onchange:function(){j.app.trigger("refresh")}})},_fieldHidden:function(i){return new h.Hidden({id:"field-"+i.id,info:i.info})},_fieldBoolean:function(i){var j=this;return new h.RadioButton.View({id:"field-"+i.id,data:[{label:"Yes",value:"true"},{label:"No",value:"false"}],onchange:function(){j.app.trigger("refresh")}})}});return{View:g}}); \ No newline at end of file +define(["utils/utils","mvc/ui/ui-table","mvc/ui/ui-misc","mvc/ui/ui-portlet","mvc/tools/tools-repeat","mvc/tools/tools-select-content","mvc/tools/tools-input"],function(e,b,h,d,c,a,f){var g=Backbone.View.extend({initialize:function(j,i){this.app=j;this.inputs=i.inputs;i.cls="ui-table-plain";i.cls_tr="section-row";this.table=new b.View(i);this.setElement(this.table.$el);this.render()},render:function(){this.table.delAll();for(var j in this.inputs){this.add(this.inputs[j])}},add:function(k){var j=this;var i=jQuery.extend(true,{},k);i.id=k.id=e.uuid();this.app.input_list[i.id]=i;var l=i.type;switch(l){case"conditional":this._addConditional(i);break;case"repeat":this._addRepeat(i);break;case"section":this._addSection(i);break;default:this._addRow(i)}},_addConditional:function(j){var k=this;j.test_param.id=j.id;var n=this._addRow(j.test_param);n.options.onchange=function(w){var v=k.app.tree.matchCase(j,w);for(var u in j.cases){var q=j.cases[u];var t=j.id+"-section-"+u;var p=k.table.get(t);var s=false;for(var r in q.inputs){if(!q.inputs[r].hidden){s=true;break}}if(u==v&&s){p.fadeIn("fast")}else{p.hide()}}k.app.trigger("refresh")};for(var m in j.cases){var l=j.id+"-section-"+m;var o=new g(this.app,{inputs:j.cases[m].inputs});o.$el.addClass("ui-table-section");this.table.add(o.$el);this.table.append(l)}n.trigger("change")},_addRepeat:function(p){var s=this;var q=0;var l=new c.View({title:p.title,title_new:p.title,min:p.min,max:p.max,onnew:function(){n(p.inputs);s.app.trigger("refresh")}});function n(i){var t=p.id+"-section-"+(q++);var u=new g(s.app,{inputs:i});l.add({id:t,$el:u.$el,ondel:function(){l.del(t);s.app.trigger("refresh")}})}var j=p.min;var r=_.size(p.cache);for(var m=0;m").addClass("ui-table-form-info").html(i.help));var k=false;l.$content.hide();l.$header.css("cursor","pointer");l.$header.on("click",function(){if(k){k=false;l.$content.hide();m.setIcon("fa-eye-slash")}else{k=true;l.$content.fadeIn("fast");m.setIcon("fa-eye")}});if(i.expand){l.$header.trigger("click")}this.table.add(l.$el);this.table.append(i.id)},_addRow:function(i){var l=i.id;if(i.value===undefined){i.value=null}if(i.default_value===undefined){i.default_value=i.value}var j=this._createField(i);this.app.field_list[l]=j;var k=new f(this.app,{label:i.label,default_value:i.default_value,optional:i.optional,help:i.help,field:j});this.app.element_list[l]=k;this.table.add(k.$el);this.table.append(l);if(i.hidden){this.table.get(l).hide()}return j},_createField:function(i){var j=null;switch(i.type){case"text":j=this._fieldText(i);break;case"select":j=this._fieldSelect(i);break;case"data":j=this._fieldData(i);break;case"data_collection":j=this._fieldData(i);break;case"data_column":i.error_text="Missing columns in referenced dataset.";j=this._fieldSelect(i);break;case"hidden":j=this._fieldHidden(i);break;case"hidden_data":j=this._fieldHidden(i);break;case"integer":j=this._fieldSlider(i);break;case"float":j=this._fieldSlider(i);break;case"boolean":j=this._fieldBoolean(i);break;case"genomebuild":i.searchable=true;j=this._fieldSelect(i);break;case"drill_down":j=this._fieldDrilldown(i);break;case"baseurl":j=this._fieldHidden(i);break;default:this.app.incompatible=true;if(i.options){j=this._fieldSelect(i)}else{j=this._fieldText(i)}console.debug("tools-form::_addRow() : Auto matched field type ("+i.type+").")}if(i.value!==undefined){j.value(i.value)}return j},_fieldData:function(i){if(!this.app.options.is_dynamic){i.info="Data input '"+i.name+"' ("+e.textify(i.extensions.toString())+")";i.value=null;return this._fieldHidden(i)}var j=this;return new a.View(this.app,{id:"field-"+i.id,extensions:i.extensions,optional:i.optional,multiple:i.multiple,type:i.type,data:i.options,onchange:function(){j.app.trigger("refresh")}})},_fieldSelect:function(j){if(!this.app.options.is_dynamic&&j.is_dynamic){return this._fieldText(j)}var l=[];for(var m in j.options){var n=j.options[m];l.push({label:n[0],value:n[1]})}var o=h.Select;switch(j.display){case"checkboxes":o=h.Checkbox;break;case"radio":o=h.Radio;break}var k=this;return new o.View({id:"field-"+j.id,data:l,error_text:j.error_text||"No options available",multiple:j.multiple,searchable:j.searchable,onchange:function(){k.app.trigger("refresh")}})},_fieldDrilldown:function(i){if(!this.app.options.is_dynamic&&i.is_dynamic){return this._fieldText(i)}var j=this;return new h.Drilldown.View({id:"field-"+i.id,data:i.options,display:i.display,onchange:function(){j.app.trigger("refresh")}})},_fieldText:function(i){if(i.options){i.area=i.multiple;if(!e.validate(i.value)){i.value=""}else{if(i.value instanceof Array){i.value=value.toString()}else{i.value=String(i.value).replace(/[\[\]'"\s]/g,"");if(i.multiple){i.value=i.value.replace(/,/g,"\n")}}}}var j=this;return new h.Input({id:"field-"+i.id,area:i.area,onchange:function(){j.app.trigger("refresh")}})},_fieldSlider:function(i){var j=this;return new h.Slider.View({id:"field-"+i.id,precise:i.type=="float",min:i.min,max:i.max,onchange:function(){j.app.trigger("refresh")}})},_fieldHidden:function(i){return new h.Hidden({id:"field-"+i.id,info:i.info})},_fieldBoolean:function(i){var j=this;return new h.RadioButton.View({id:"field-"+i.id,data:[{label:"Yes",value:"true"},{label:"No",value:"false"}],onchange:function(){j.app.trigger("refresh")}})}});return{View:g}}); \ No newline at end of file From 440d623ffd6462592a3f85f05a0f7b759f4a74da Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Mon, 9 Feb 2015 09:17:53 -0600 Subject: [PATCH 05/10] Add a _future_expose_api_raw_anonymous_and_sessionless to restore functionality that was previously available with _future_expose_api_raw_anonymous. --- lib/galaxy/web/__init__.py | 1 + lib/galaxy/web/framework/decorators.py | 3 +++ lib/galaxy/webapps/galaxy/api/job_files.py | 8 ++++---- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/web/__init__.py b/lib/galaxy/web/__init__.py index e7f4b9e2656..0160b6049d2 100644 --- a/lib/galaxy/web/__init__.py +++ b/lib/galaxy/web/__init__.py @@ -19,6 +19,7 @@ from framework.decorators import _future_expose_api_anonymous from framework.decorators import _future_expose_api_raw from framework.decorators import _future_expose_api_raw_anonymous from framework.decorators import _future_expose_api_anonymous_and_sessionless +from framework.decorators import _future_expose_api_raw_anonymous_and_sessionless from framework.formbuilder import form from framework.formbuilder import FormBuilder diff --git a/lib/galaxy/web/framework/decorators.py b/lib/galaxy/web/framework/decorators.py index 5ca37b60902..00f1154c55d 100644 --- a/lib/galaxy/web/framework/decorators.py +++ b/lib/galaxy/web/framework/decorators.py @@ -344,3 +344,6 @@ def _future_expose_api_raw( func ): def _future_expose_api_raw_anonymous( func ): return _future_expose_api( func, to_json=False, user_required=False ) + +def _future_expose_api_raw_anonymous_and_sessionless( func ): + return _future_expose_api( func, to_json=False, user_required=False, user_or_session_required=False ) diff --git a/lib/galaxy/webapps/galaxy/api/job_files.py b/lib/galaxy/webapps/galaxy/api/job_files.py index 2dde8de2dc6..27445beac38 100644 --- a/lib/galaxy/webapps/galaxy/api/job_files.py +++ b/lib/galaxy/webapps/galaxy/api/job_files.py @@ -8,8 +8,8 @@ from galaxy import exceptions from galaxy import util from galaxy import model from galaxy.web.base.controller import BaseAPIController -from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous -from galaxy.web import _future_expose_api_raw_anonymous as expose_api_raw_anonymous +from galaxy.web import _future_expose_api_anonymous_and_sessionless as expose_api_anonymous_and_sessionless +from galaxy.web import _future_expose_api_raw_anonymous_and_sessionless as expose_api_raw_anonymous_and_sessionless import logging @@ -28,7 +28,7 @@ class JobFilesAPIController( BaseAPIController ): security model for tool execution. """ - @expose_api_raw_anonymous + @expose_api_raw_anonymous_and_sessionless def index( self, trans, job_id, **kwargs ): """ index( self, trans, job_id, **kwargs ) @@ -54,7 +54,7 @@ class JobFilesAPIController( BaseAPIController ): path = kwargs.get("path", None) return open(path, 'rb') - @expose_api_anonymous + @expose_api_anonymous_and_sessionless def create( self, trans, job_id, payload, **kwargs ): """ create( self, trans, job_id, payload, **kwargs ) From b1ecf26c5a1968b9d25dec16df356d8aa8a47961 Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Mon, 9 Feb 2015 10:36:43 -0500 Subject: [PATCH 06/10] Dataproviders, column: allow filtering data by parsed column values (numeric, list, and string) --- lib/galaxy/datatypes/dataproviders/column.py | 189 ++++++++++++++----- 1 file changed, 142 insertions(+), 47 deletions(-) diff --git a/lib/galaxy/datatypes/dataproviders/column.py b/lib/galaxy/datatypes/dataproviders/column.py index af444382fc4..6669f96819d 100644 --- a/lib/galaxy/datatypes/dataproviders/column.py +++ b/lib/galaxy/datatypes/dataproviders/column.py @@ -3,6 +3,9 @@ Providers that provide lists of lists generally where each line of a source is further subdivided into multiple data (e.g. columns from a line). """ +import urllib +import re + import line _TODO = """ @@ -34,12 +37,13 @@ class ColumnarDataProvider( line.RegexLineDataProvider ): 'column_count' : 'int', 'column_types' : 'list:str', 'parse_columns' : 'bool', - 'deliminator' : 'str' + 'deliminator' : 'str', + 'filters' : 'list:str' } def __init__( self, source, indeces=None, column_count=None, column_types=None, parsers=None, parse_columns=True, - deliminator='\t', **kwargs ): + deliminator='\t', filters=None, **kwargs ): """ :param indeces: a list of indeces of columns to gather from each row Optional: will default to `None`. @@ -103,6 +107,104 @@ class ColumnarDataProvider( line.RegexLineDataProvider ): # overwrite with user desired parsers self.parsers.update( parsers or {} ) + filters = filters or [] + self.column_filters = [] + for filter_ in filters: + parsed = self.parse_filter( filter_ ) + #TODO: might be better to error on bad filter/None here + if callable( parsed ): + self.column_filters.append( parsed ) + + def parse_filter( self, filter_param_str ): + split = filter_param_str.split( '-', 3 ) + if not len( split ) == 3: + return None + column, op, val = split + + # better checking v. len and indeces + column = int( column ) + if column > len( self.column_types ): + return None + if self.column_types[ column ] in ( 'float', 'int' ): + return self.create_numeric_filter( column, op, val ) + if self.column_types[ column ] in ( 'str' ): + return self.create_string_filter( column, op, val ) + if self.column_types[ column ] in ( 'list' ): + return self.create_list_filter( column, op, val ) + return None + + def create_numeric_filter( self, column, op, val ): + """ + Return an anonymous filter function that will be passed the array + of parsed columns. Return None if no filter function can be + created for the given params. + + The function will compare the column at index `column` against `val` + using the given op where op is one of: + lt: less than, le: less than or equal to, + eq: equal to, ne: not equal to, + ge: greather than or equal to, gt: greater than + + `val` is cast as float here and will return None if there's a parsing error. + """ + try: + val = float( val ) + except ValueError: + return None + if 'lt' == op: + return lambda d: d[column] < val + elif 'le' == op: + return lambda d: d[column] <= val + elif 'eq' == op: + return lambda d: d[column] == val + elif 'ne' == op: + return lambda d: d[column] != val + elif 'ge' == op: + return lambda d: d[column] >= val + elif 'gt' == op: + return lambda d: d[column] > val + return None + + def create_string_filter( self, column, op, val ): + """ + Return an anonymous filter function that will be passed the array + of parsed columns. Return None if no filter function can be + created for the given params. + + The function will compare the column at index `column` against `val` + using the given op where op is one of: + eq: exactly matches, + has: the column contains the substring `val`, + re: the column matches the regular expression in `val` + """ + if 'eq' == op: + return lambda d: d[column] == val + elif 'has' == op: + return lambda d: val in d[column] + elif 're' == op: + val = urllib.unquote_plus( val ) + val = re.compile( val ) + return lambda d: val.match( d[column] ) is not None + return None + + def create_list_filter( self, column, op, val ): + """ + Return an anonymous filter function that will be passed the array + of parsed columns. Return None if no filter function can be + created for the given params. + + The function will compare the column at index `column` against `val` + using the given op where op is one of: + eq: the list `val` exactly matches the list in the column, + has: the list in the column contains the sublist `val`, + """ + if 'eq' == op: + val = self.parse_value( val, 'list' ) + return lambda d: d[column] == val + elif 'has' == op: + return lambda d: val in d[column] + return None + def get_default_parsers( self ): """ Return parser dictionary keyed for each columnar type @@ -140,6 +242,39 @@ class ColumnarDataProvider( line.RegexLineDataProvider ): #'gffstrand': # -, +, ?, or '.' for None, etc. } + def filter( self, line ): + line = super( ColumnarDataProvider, self ).filter( line ) + if line == None: + return line + columns = self.parse_columns_from_line( line ) + return self.filter_by_columns( columns ) + + def parse_columns_from_line( self, line ): + """ + Returns a list of the desired, parsed columns. + :param line: the line to parse + :type line: str + """ + #TODO: too much going on in this loop - the above should all be precomputed AMAP... + all_columns = line.split( self.deliminator ) + # if no indeces were passed to init, return all columns + selected_indeces = self.selected_column_indeces or list( xrange( len( all_columns ) ) ) + parsed_columns = [] + for parser_index, column_index in enumerate( selected_indeces ): + parsed_columns.append( self.parse_column_at_index( all_columns, parser_index, column_index ) ) + return parsed_columns + + def parse_column_at_index( self, columns, parser_index, index ): + """ + Get the column type for the parser from `self.column_types` or `None` + if the type is unavailable. + """ + try: + return self.parse_value( columns[ index ], self.get_column_type( parser_index ) ) + # if a selected index is not within columns, return None + except IndexError, index_err: + return None + def parse_value( self, val, type ): """ Attempt to parse and return the given value based on the given type. @@ -173,51 +308,11 @@ class ColumnarDataProvider( line.RegexLineDataProvider ): except IndexError, ind_err: return None - def parse_column_at_index( self, columns, parser_index, index ): - """ - Get the column type for the parser from `self.column_types` or `None` - if the type is unavailable. - """ - try: - return self.parse_value( columns[ index ], self.get_column_type( parser_index ) ) - # if a selected index is not within columns, return None - except IndexError, index_err: - return None - - def parse_columns_from_line( self, line ): - """ - Returns a list of the desired, parsed columns. - :param line: the line to parse - :type line: str - """ - #TODO: too much going on in this loop - the above should all be precomputed AMAP... - all_columns = line.split( self.deliminator ) - # if no indeces were passed to init, return all columns - selected_indeces = self.selected_column_indeces or list( xrange( len( all_columns ) ) ) - parsed_columns = [] - for parser_index, column_index in enumerate( selected_indeces ): - parsed_columns.append( self.parse_column_at_index( all_columns, parser_index, column_index ) ) - return parsed_columns - - def __iter__( self ): - parent_gen = super( ColumnarDataProvider, self ).__iter__() - for line in parent_gen: - columns = self.parse_columns_from_line( line ) - yield columns - - #TODO: implement column filters here and not below - flatten hierarchy - -class FilteredByColumnDataProvider( ColumnarDataProvider ): - """ - Data provider that provide a list of columns from the lines of its source - _only_ if they pass a given filter function. - - e.g. column #3 is type int and > N - """ - # TODO: how to do this and still have limit and offset work? - def __init__( self, source, **kwargs ): - raise NotImplementedError() - super( FilteredByColumnDataProvider, self ).__init__( source, **kwargs ) + def filter_by_columns( self, columns ): + for filter_fn in self.column_filters: + if not filter_fn( columns ): + return None + return columns class DictDataProvider( ColumnarDataProvider ): From 2e9a80b5e224a0302e0b3091d63d3783f375fd11 Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Mon, 9 Feb 2015 09:59:36 -0600 Subject: [PATCH 07/10] Remove escaping of quotes in the slurm cli status checks, I don't think they should be there (the format args should in fact be quoted). --- lib/galaxy/jobs/runners/util/cli/job/slurm.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/jobs/runners/util/cli/job/slurm.py b/lib/galaxy/jobs/runners/util/cli/job/slurm.py index c3fe43793ef..2d899f0d17d 100644 --- a/lib/galaxy/jobs/runners/util/cli/job/slurm.py +++ b/lib/galaxy/jobs/runners/util/cli/job/slurm.py @@ -7,7 +7,7 @@ try: except ImportError: # Not in Galaxy, map Galaxy job states to Pulsar ones. from galaxy.util import enum - job_states = enum(RUNNING='running', OK='complete', QUEUED='queued') + job_states = enum(RUNNING='running', OK='complete', QUEUED='queued', ERROR="failed") from ..job import BaseJobExec @@ -59,10 +59,10 @@ class Slurm(BaseJobExec): return 'scancel %s' % job_id def get_status(self, job_ids=None): - return 'squeue -a -o \\"%A %t\\"' + return "squeue -a -o '%A %t'" def get_single_status(self, job_id): - return 'squeue -a -o \\"%A %t\\" -j ' + job_id + return "squeue -a -o '%A %t' -j " + job_id def parse_status(self, status, job_ids): # Get status for each job, skipping header. @@ -80,6 +80,7 @@ class Slurm(BaseJobExec): # Job still on cluster and has state. id, state = status[1].split() return self._get_job_state(state) + # else line like "slurm_load_jobs error: Invalid job id specified" return job_states.OK def _get_job_state(self, state): From 2b8fca4a03ea9edacb2409dbbfdb2f4b38de4ce7 Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Mon, 9 Feb 2015 11:02:31 -0500 Subject: [PATCH 08/10] Visualizations, scatterplot: update handlebars since server runtime was updated --- config/plugins/visualizations/scatterplot/package.json | 2 +- .../visualizations/scatterplot/static/scatterplot-edit.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/plugins/visualizations/scatterplot/package.json b/config/plugins/visualizations/scatterplot/package.json index 84909ef83bf..c70e2d502a3 100644 --- a/config/plugins/visualizations/scatterplot/package.json +++ b/config/plugins/visualizations/scatterplot/package.json @@ -16,8 +16,8 @@ "devDependencies": { "grunt": "~0.4.1", "grunt-cli": "~0.1.9", - "grunt-contrib-handlebars": "~0.5.10", "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-handlebars": "^0.9.3", "grunt-contrib-uglify": "~0.2.2", "grunt-contrib-watch": "~0.5.3" } diff --git a/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js b/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js index 66da2e44f48..fc44b025410 100644 --- a/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js +++ b/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js @@ -1 +1 @@ -function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){$(".chart-info-box").remove(),q.redraw(),e(),s=d(),$(o.node()).trigger("zoom.scatterplot",{scale:n.scale(),translate:n.translate()})}function g(a,c,d){return c+=8,$(['
',void 0!==b.idColumn?"
"+d[b.idColumn]+"
":"","
",j(d),"
","
",k(d),"
","
"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,30]).scale(b.scale||1).translate(b.translate||[0,0]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.xTicks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.yTicks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=6;q.x.label=o.append("text").attr("id","x-axis-label").attr("class","axis-label").text(b.xLabel).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("id","y-axis-label").attr("class","axis-label").text(b.yLabel).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",0);t.transition().duration(b.animDuration).attr("r",b.datapointSize),e(),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.classed("highlight",!0).style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function",i=this.escapeExpression;return g+='

\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n

\n\n
\n \n
',(f=c.datapointSize)?f=f.call(b,{hash:{},data:e}):(f=b.datapointSize,f=typeof f===h?f.apply(b):f),g+=i(f)+'
\n
\n

\n Size of the graphic representation of each data point\n

\n
\n\n
\n \n
',(f=c.width)?f=f.call(b,{hash:{},data:e}):(f=b.width,f=typeof f===h?f.apply(b):f),g+=i(f)+'
\n
\n

\n (not including chart margins and axes)\n

\n
\n\n
\n \n
',(f=c.height)?f=f.call(b,{hash:{},data:e}):(f=b.height,f=typeof f===h?f.apply(b):f),g+=i(f)+'
\n
\n

\n (not including chart margins and axes)\n

\n
\n\n
\n \n \n

\n
\n\n
\n \n \n

\n
\n\n\n'}),this.scatterplot.datacontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function";return g+='

\n Use the following control to change which columns are used by the chart. Click any cell\n from the last three rows of the table to select the column for the appropriate data.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n

\n\n
    \n
  • X Column: which column values will be used for the x axis of the chart.
  • \n
  • Y Column: which column values will be used for the y axis of the chart.
  • \n
  • ID Column: an additional column value displayed when the user hovers over a data point.\n It may be useful to select unique or categorical identifiers here (such as gene ids).\n
  • \n
\n\n
\n
',(f=c.peek)?f=f.call(b,{hash:{},data:e}):(f=b.peek,f=typeof f===h?f.apply(b):f),(f||0===f)&&(g+=f),g+='
\n
\n\n

\n Note: If it can be determined from the dataset\'s filetype that a column is not numeric,\n that column choice may be disabled for either the x or y axis.\n

\n\n\n'}),this.scatterplot.editor=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f="";return f+='
\n \n \n\n \n
\n \n
\n \n
\n \n \n
\n \n
\n\n \n
\n\n
\n
\n'});var ScatterplotConfigEditor=Backbone.View.extend({className:"scatterplot-control-form",initialize:function(a){if(this.model||(this.model=new Visualization({type:"scatterplot"})),!a||!a.dataset)throw new Error("ScatterplotConfigEditor requires a dataset");this.dataset=a.dataset,this.display=new ScatterplotDisplay({dataset:a.dataset,model:this.model})},render:function(){this.$el.empty().append(ScatterplotConfigEditor.templates.mainLayout({})),this.model.id&&(this.$el.find(".copy-btn").show(),this.$el.find(".save-btn").text("Update saved")),this.$el.find("[title]").tooltip(),this._render_dataControl(),this._render_chartControls(),this._render_chartDisplay();var a=this.model.get("config");return this.model.id&&_.isFinite(a.xColumn)&&_.isFinite(a.yColumn)&&this.renderChart(),this},_getColumnIndecesByType:function(){var a={numeric:[],text:[],all:[]};return _.each(this.dataset.metadata_column_types||[],function(b,c){"int"===b||"float"===b?a.numeric.push(c):("str"===b||"list"===b)&&a.text.push(c),a.all.push(c)}),a.numeric.length<2&&(a.numeric=[]),a},_render_dataControl:function(a){a=a||this.$el;var b=this,c=this.model.get("config"),d=this._getColumnIndecesByType(),e=a.find(".tab-pane#data-control");return e.html(ScatterplotConfigEditor.templates.dataControl({peek:this.dataset.peek})),e.find(".peek").peekColumnSelector({controls:[{label:"X Column",id:"xColumn",selected:c.xColumn,disabled:d.text},{label:"Y Column",id:"yColumn",selected:c.yColumn,disabled:d.text},{label:"ID Column",id:"idColumn",selected:c.idColumn}]}).on("peek-column-selector.change",function(a,c){b.model.set("config",c)}).on("peek-column-selector.rename",function(){}),e.find("[title]").tooltip(),e},_render_chartControls:function(a){function b(){var a=$(this),b=a.slider("value");c.model.set("config",_.object([[a.parent().data("config-key"),b]])),a.siblings(".slider-output").text(b)}a=a||this.$el;var c=this,d=this.model.get("config"),e=a.find("#chart-control");e.html(ScatterplotConfigEditor.templates.chartControl(d));var f={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};e.find(".numeric-slider-input").each(function(){var a=$(this),c=a.attr("data-config-key"),e=_.extend(f[c],{value:d[c],change:b,slide:b});a.find(".slider").slider(e),a.children(".slider-output").text(d[c])});var g=this.dataset.metadata_column_names||[],h=d.xLabel||g[d.xColumn]||"X",i=d.yLabel||g[d.yColumn]||"Y";return e.find('input[name="X-axis-label"]').val(h).on("change",function(){c.model.set("config",{xLabel:$(this).val()})}),e.find('input[name="Y-axis-label"]').val(i).on("change",function(){c.model.set("config",{yLabel:$(this).val()})}),e.find("[title]").tooltip(),e},_render_chartDisplay:function(a){a=a||this.$el;var b=a.find(".tab-pane#chart-display");return this.display.setElement(b),this.display.render(),b.find("[title]").tooltip(),b},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart","click .save-btn":"saveVisualization"},saveVisualization:function(){var a=this;this.model.save().fail(function(b,c,d){console.error(b,c,d),a.trigger("save:error",view),alert("Error loading data:\n"+b.responseText)}).then(function(){a.display.render()})},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.display.fetchData()},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:scatterplot.editor,dataControl:scatterplot.datacontrol,chartControl:scatterplot.chartcontrol};var ScatterplotDisplay=Backbone.View.extend({initialize:function(a){this.data=null,this.dataset=a.dataset,this.lineCount=this.dataset.metadata_data_lines||null},fetchData:function(){this.showLoadingIndicator();var a=this,b=this.model.get("config"),c=jQuery.getJSON("/api/datasets/"+this.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:b.pagination.perPage,offset:b.pagination.currPage*b.pagination.perPage});return c.done(function(b){a.data=b.data,a.trigger("data:fetched",a),a.renderData()}),c.fail(function(b,c,d){console.error(b,c,d),a.trigger("data:error",a),alert("Error loading data:\n"+b.responseText)}),c},showLoadingIndicator:function(){this.$el.find(".scatterplot-data-info").html(['
','','loading...',"
"].join(""))},template:function(){var a=['
','
','

','','',"
",'
','
',"
","
","",'
'].join("");return a},render:function(){return this.$el.addClass("scatterplot-display").html(this.template()),this.data&&this.renderData(),this},renderData:function(){this.renderLeftControls(),this.renderRightControls(),this.renderPlot(this.data),this.getStats()},renderLeftControls:function(){var a=this,b=this.model.get("config");return this.$el.find(".controls .left .page-control").pagination({startingPage:b.pagination.currPage,perPage:b.pagination.perPage,totalDataSize:this.lineCount,currDataSize:this.data.length}).off().on("pagination.page-change",function(c,d){b.pagination.currPage=d,a.model.set("config",{pagination:b.pagination}),a.resetZoom(),a.fetchData()}),this},renderRightControls:function(){var a=this;this.setLineInfo(this.data),this.$el.find(".stats-toggle-btn").off().click(function(){a.toggleStats()}),this.$el.find(".rerender-btn").off().click(function(){a.resetZoom(),a.renderPlot(this.data)})},renderPlot:function(){var a=this,b=this.$el.find("svg");this.toggleStats(!1),b.off().empty().show().on("zoom.scatterplot",function(b,c){a.model.set("config",c)}),scatterplot(b.get(0),this.model.get("config"),this.data)},setLineInfo:function(a,b){if(a){var c=this.model.get("config"),d=this.lineCount||"an unknown total",e=c.pagination.currPage*c.pagination.perPage,f=e+a.length;this.$el.find(".controls p.scatterplot-data-info").text([e+1,"to",f,"of",d].join(" "))}else this.$el.find(".controls p.scatterplot-data-info").html(b||"");return this},resetZoom:function(a,b){return a=void 0!==a?a:1,b=void 0!==b?b:[0,0],this.model.set("config",{scale:a,translate:b}),this},getStats:function(){if(this.data){var a=this,b=this.model.get("config"),c=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js");c.postMessage({data:this.data,keys:[b.xColumn,b.yColumn]}),c.onerror=function(){c.terminate()},c.onmessage=function(b){a.renderStats(b.data)}}},renderStats:function(a){var b=this.model.get("config"),c=this.$el.find(".stats-display"),d=b.xLabel,e=b.yLabel,f=$("").addClass("table").append([""].join("")).append(_.map(a,function(a,b){return $([""].join(""))}));c.empty().append(f)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}}),ScatterplotModel=Visualization.extend({defaults:{type:"scatterplot",config:{pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},xTicks:10,xLabel:"X",yTicks:10,yLabel:"Y",datapointSize:4,animDuration:500,scale:1,translate:[0,0]}}}); \ No newline at end of file +function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){$(".chart-info-box").remove(),q.redraw(),e(),s=d(),$(o.node()).trigger("zoom.scatterplot",{scale:n.scale(),translate:n.translate()})}function g(a,c,d){return c+=8,$(['
',void 0!==b.idColumn?"
"+d[b.idColumn]+"
":"","
",j(d),"
","
",k(d),"
","
"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,30]).scale(b.scale||1).translate(b.translate||[0,0]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.xTicks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.yTicks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=6;q.x.label=o.append("text").attr("id","x-axis-label").attr("class","axis-label").text(b.xLabel).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("id","y-axis-label").attr("class","axis-label").text(b.yLabel).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",0);t.transition().duration(b.animDuration).attr("r",b.datapointSize),e(),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.classed("highlight",!0).style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")= 2.0.0-beta.1"],main:function(a,b,c,d){var e,f="function",g=b.helperMissing,h=this.escapeExpression;return'

\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n

\n\n
\n \n
'+h((e=null!=(e=b.datapointSize||(null!=a?a.datapointSize:a))?e:g,typeof e===f?e.call(a,{name:"datapointSize",hash:{},data:d}):e))+'
\n
\n

\n Size of the graphic representation of each data point\n

\n
\n\n
\n \n
'+h((e=null!=(e=b.width||(null!=a?a.width:a))?e:g,typeof e===f?e.call(a,{name:"width",hash:{},data:d}):e))+'
\n
\n

\n (not including chart margins and axes)\n

\n
\n\n
\n \n
'+h((e=null!=(e=b.height||(null!=a?a.height:a))?e:g,typeof e===f?e.call(a,{name:"height",hash:{},data:d}):e))+'
\n
\n

\n (not including chart margins and axes)\n

\n
\n\n
\n \n \n

\n
\n\n
\n \n \n

\n
\n\n\n'},useData:!0}),this.scatterplot.datacontrol=Handlebars.template({compiler:[6,">= 2.0.0-beta.1"],main:function(a,b,c,d){var e,f,g="function",h=b.helperMissing,i='

\n Use the following control to change which columns are used by the chart. Click any cell\n from the last three rows of the table to select the column for the appropriate data.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n

\n\n
    \n
  • X Column: which column values will be used for the x axis of the chart.
  • \n
  • Y Column: which column values will be used for the y axis of the chart.
  • \n
  • ID Column: an additional column value displayed when the user hovers over a data point.\n It may be useful to select unique or categorical identifiers here (such as gene ids).\n
  • \n
\n\n
\n
';return f=null!=(f=b.peek||(null!=a?a.peek:a))?f:h,e=typeof f===g?f.call(a,{name:"peek",hash:{},data:d}):f,null!=e&&(i+=e),i+'
\n
\n\n

\n Note: If it can be determined from the dataset\'s filetype that a column is not numeric,\n that column choice may be disabled for either the x or y axis.\n

\n\n\n'},useData:!0}),this.scatterplot.editor=Handlebars.template({compiler:[6,">= 2.0.0-beta.1"],main:function(){return'
\n \n\n
\n
\n
\n \n
\n
\n\n
\n\n
\n
\n'},useData:!0});var ScatterplotConfigEditor=Backbone.View.extend({className:"scatterplot-control-form",initialize:function(a){if(this.model||(this.model=new Visualization({type:"scatterplot"})),!a||!a.dataset)throw new Error("ScatterplotConfigEditor requires a dataset");this.dataset=a.dataset,this.display=new ScatterplotDisplay({dataset:a.dataset,model:this.model})},render:function(){this.$el.empty().append(ScatterplotConfigEditor.templates.mainLayout({})),this.model.id&&(this.$el.find(".copy-btn").show(),this.$el.find(".save-btn").text("Update saved")),this.$el.find("[title]").tooltip(),this._render_dataControl(),this._render_chartControls(),this._render_chartDisplay();var a=this.model.get("config");return this.model.id&&_.isFinite(a.xColumn)&&_.isFinite(a.yColumn)&&this.renderChart(),this},_getColumnIndecesByType:function(){var a={numeric:[],text:[],all:[]};return _.each(this.dataset.metadata_column_types||[],function(b,c){"int"===b||"float"===b?a.numeric.push(c):("str"===b||"list"===b)&&a.text.push(c),a.all.push(c)}),a.numeric.length<2&&(a.numeric=[]),a},_render_dataControl:function(a){a=a||this.$el;var b=this,c=this.model.get("config"),d=this._getColumnIndecesByType(),e=a.find(".tab-pane#data-control");return e.html(ScatterplotConfigEditor.templates.dataControl({peek:this.dataset.peek})),e.find(".peek").peekColumnSelector({controls:[{label:"X Column",id:"xColumn",selected:c.xColumn,disabled:d.text},{label:"Y Column",id:"yColumn",selected:c.yColumn,disabled:d.text},{label:"ID Column",id:"idColumn",selected:c.idColumn}]}).on("peek-column-selector.change",function(a,c){b.model.set("config",c)}).on("peek-column-selector.rename",function(){}),e.find("[title]").tooltip(),e},_render_chartControls:function(a){function b(){var a=$(this),b=a.slider("value");c.model.set("config",_.object([[a.parent().data("config-key"),b]])),a.siblings(".slider-output").text(b)}a=a||this.$el;var c=this,d=this.model.get("config"),e=a.find("#chart-control");e.html(ScatterplotConfigEditor.templates.chartControl(d));var f={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};e.find(".numeric-slider-input").each(function(){var a=$(this),c=a.attr("data-config-key"),e=_.extend(f[c],{value:d[c],change:b,slide:b});a.find(".slider").slider(e),a.children(".slider-output").text(d[c])});var g=this.dataset.metadata_column_names||[],h=d.xLabel||g[d.xColumn]||"X",i=d.yLabel||g[d.yColumn]||"Y";return e.find('input[name="X-axis-label"]').val(h).on("change",function(){c.model.set("config",{xLabel:$(this).val()})}),e.find('input[name="Y-axis-label"]').val(i).on("change",function(){c.model.set("config",{yLabel:$(this).val()})}),e.find("[title]").tooltip(),e},_render_chartDisplay:function(a){a=a||this.$el;var b=a.find(".tab-pane#chart-display");return this.display.setElement(b),this.display.render(),b.find("[title]").tooltip(),b},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart","click .save-btn":"saveVisualization"},saveVisualization:function(){var a=this;this.model.save().fail(function(b,c,d){console.error(b,c,d),a.trigger("save:error",view),alert("Error loading data:\n"+b.responseText)}).then(function(){a.display.render()})},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.display.fetchData()},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:scatterplot.editor,dataControl:scatterplot.datacontrol,chartControl:scatterplot.chartcontrol};var ScatterplotDisplay=Backbone.View.extend({initialize:function(a){this.data=null,this.dataset=a.dataset,this.lineCount=this.dataset.metadata_data_lines||null},fetchData:function(){this.showLoadingIndicator();var a=this,b=this.model.get("config"),c=jQuery.getJSON("/api/datasets/"+this.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:b.pagination.perPage,offset:b.pagination.currPage*b.pagination.perPage});return c.done(function(b){a.data=b.data,a.trigger("data:fetched",a),a.renderData()}),c.fail(function(b,c,d){console.error(b,c,d),a.trigger("data:error",a),alert("Error loading data:\n"+b.responseText)}),c},showLoadingIndicator:function(){this.$el.find(".scatterplot-data-info").html(['
','','loading...',"
"].join(""))},template:function(){var a=['
','
','

','','',"
",'
','
',"
","
","",'
'].join("");return a},render:function(){return this.$el.addClass("scatterplot-display").html(this.template()),this.data&&this.renderData(),this},renderData:function(){this.renderLeftControls(),this.renderRightControls(),this.renderPlot(this.data),this.getStats()},renderLeftControls:function(){var a=this,b=this.model.get("config");return this.$el.find(".controls .left .page-control").pagination({startingPage:b.pagination.currPage,perPage:b.pagination.perPage,totalDataSize:this.lineCount,currDataSize:this.data.length}).off().on("pagination.page-change",function(c,d){b.pagination.currPage=d,a.model.set("config",{pagination:b.pagination}),a.resetZoom(),a.fetchData()}),this},renderRightControls:function(){var a=this;this.setLineInfo(this.data),this.$el.find(".stats-toggle-btn").off().click(function(){a.toggleStats()}),this.$el.find(".rerender-btn").off().click(function(){a.resetZoom(),a.renderPlot(this.data)})},renderPlot:function(){var a=this,b=this.$el.find("svg");this.toggleStats(!1),b.off().empty().show().on("zoom.scatterplot",function(b,c){a.model.set("config",c)}),scatterplot(b.get(0),this.model.get("config"),this.data)},setLineInfo:function(a,b){if(a){var c=this.model.get("config"),d=this.lineCount||"an unknown total",e=c.pagination.currPage*c.pagination.perPage,f=e+a.length;this.$el.find(".controls p.scatterplot-data-info").text([e+1,"to",f,"of",d].join(" "))}else this.$el.find(".controls p.scatterplot-data-info").html(b||"");return this},resetZoom:function(a,b){return a=void 0!==a?a:1,b=void 0!==b?b:[0,0],this.model.set("config",{scale:a,translate:b}),this},getStats:function(){if(this.data){var a=this,b=this.model.get("config"),c=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js");c.postMessage({data:this.data,keys:[b.xColumn,b.yColumn]}),c.onerror=function(){c.terminate()},c.onmessage=function(b){a.renderStats(b.data)}}},renderStats:function(a){var b=this.model.get("config"),c=this.$el.find(".stats-display"),d=b.xLabel,e=b.yLabel,f=$("
",d,"",e,"
",b,"",a[0],"",a[1],"
").addClass("table").append([""].join("")).append(_.map(a,function(a,b){return $([""].join(""))}));c.empty().append(f)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}}),ScatterplotModel=Visualization.extend({defaults:{type:"scatterplot",config:{pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},xTicks:10,xLabel:"X",yTicks:10,yLabel:"Y",datapointSize:4,animDuration:500,scale:1,translate:[0,0]}}}); \ No newline at end of file From cb0428e94a0376a46c48eb94b080e625d7483e68 Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Mon, 9 Feb 2015 11:18:11 -0500 Subject: [PATCH 09/10] UI: better defesive prog in metrics logger --- client/galaxy/scripts/utils/metrics-logger.js | 2 +- static/scripts/packed/utils/metrics-logger.js | 2 +- static/scripts/utils/metrics-logger.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/galaxy/scripts/utils/metrics-logger.js b/client/galaxy/scripts/utils/metrics-logger.js index 1592e8e3ec9..25eb7b0dd5e 100644 --- a/client/galaxy/scripts/utils/metrics-logger.js +++ b/client/galaxy/scripts/utils/metrics-logger.js @@ -34,7 +34,7 @@ function MetricsLogger( options ){ var self = this; ///** get the current user's id from bootstrapped data or options */ - self.userId = window.bootstrapped? window.bootstrapped.user.id: null; + self.userId = ( window.bootstrapped && window.bootstrapped.user )? window.bootstrapped.user.id: null; self.userId = self.userId || options.userId || null; /** the (optional) console to emit logs to */ diff --git a/static/scripts/packed/utils/metrics-logger.js b/static/scripts/packed/utils/metrics-logger.js index 51dfea9142a..ebc15e29507 100644 --- a/static/scripts/packed/utils/metrics-logger.js +++ b/static/scripts/packed/utils/metrics-logger.js @@ -1 +1 @@ -define([],function(){function i(D){D=D||{};var C=this;C.userId=window.bootstrapped?window.bootstrapped.user.id:null;C.userId=C.userId||D.userId||null;C.consoleLogger=D.consoleLogger||null;C._init(D);return C}i.ALL=0;i.DEBUG=10;i.INFO=20;i.WARN=30;i.ERROR=40;i.METRIC=50;i.NONE=100;i.defaultOptions={logLevel:i.NONE,consoleLevel:i.NONE,defaultNamespace:"Galaxy",clientPrefix:"client.",maxCacheSize:3000,postSize:1000,addTime:true,cacheKeyPrefix:"logs-",postUrl:"/api/metrics",delayPostInMs:1000*60*10,getPingData:undefined,onServerResponse:undefined};i.prototype._init=function j(E){var D=this;D.options={};for(var C in i.defaultOptions){if(i.defaultOptions.hasOwnProperty(C)){D.options[C]=(E.hasOwnProperty(C))?(E[C]):(i.defaultOptions[C])}}D.options.logLevel=D._parseLevel(D.options.logLevel);D.options.consoleLevel=D._parseLevel(D.options.consoleLevel);D._sending=false;D._waiting=null;D._postSize=D.options.postSize;D._initCache();return D};i.prototype._initCache=function a(){try{this.cache=new z({maxSize:this.options.maxCacheSize,key:this.options.cacheKeyPrefix+this.userId})}catch(C){this._emitToConsole("warn","MetricsLogger",["Could not intitialize logging cache:",C]);this.options.logLevel=i.NONE}};i.prototype._parseLevel=function n(E){var D=typeof E;if(D==="number"){return E}if(D==="string"){var C=E.toUpperCase();if(i.hasOwnProperty(C)){return i[C]}}throw new Error("Unknown log level: "+E)};i.prototype.emit=function q(F,E,D){var C=this;E=E||C.options.defaultNamespace;if(!F||!D){return C}F=C._parseLevel(F);if(F>=C.options.logLevel){C._addToCache(F,E,D)}if(C.consoleLogger&&F>=C.options.consoleLevel){C._emitToConsole(F,E,D)}return C};i.prototype._addToCache=function b(H,E,D){this._emitToConsole("debug","MetricsLogger",["_addToCache:",arguments,this.options.addTime,this.cache.length()]);var C=this;try{var G=C.cache.add(C._buildEntry(H,E,D));if(G>=C._postSize){C._postCache()}}catch(F){C._emitToConsole("warn","MetricsLogger",["Metrics logger could not stringify logArguments:",E,D]);C._emitToConsole("error","MetricsLogger",[F])}return C};i.prototype._buildEntry=function u(F,D,C){this._emitToConsole("debug","MetricsLogger",["_buildEntry:",arguments]);var E={level:F,namespace:this.options.clientPrefix+D,args:C};if(this.options.addTime){E.time=new Date().toISOString()}return E};i.prototype._postCache=function v(F){F=F||{};this._emitToConsole("info","MetricsLogger",["_postCache",F,this._postSize]);if(!this.options.postUrl||this._sending){return jQuery.when({})}var E=this,H=F.count||E._postSize,C=E.cache.get(H),G=C.length,D=(typeof E.options.getPingData==="function")?(E.options.getPingData()):({});D.metrics=JSON.stringify(C);E._sending=true;return jQuery.post(E.options.postUrl,D).always(function(){E._sending=false}).fail(function(K,I,J){E._postSize=E.options.maxCacheSize;this.emit("error","MetricsLogger",["_postCache error:",K.readyState,K.status,K.responseJSON||K.responseText])}).done(function(I){if(typeof E.options.onServerResponse==="function"){E.options.onServerResponse(I)}E.cache.remove(G);E._postSize=E.options.postSize})};i.prototype._delayPost=function k(){var C=this;C._waiting=setTimeout(function(){C._waiting=null},C.options.delayPostInMs)};i.prototype._emitToConsole=function c(G,F,E){var C=this;if(!C.consoleLogger){return C}var D=Array.prototype.slice.call(E,0);D.unshift(F);if(G>=i.METRIC&&typeof(C.consoleLogger.info)==="function"){return C.consoleLogger.info.apply(C.consoleLogger,D)}else{if(G>=i.ERROR&&typeof(C.consoleLogger.error)==="function"){return C.consoleLogger.error.apply(C.consoleLogger,D)}else{if(G>=i.WARN&&typeof(C.consoleLogger.warn)==="function"){C.consoleLogger.warn.apply(C.consoleLogger,D)}else{if(G>=i.INFO&&typeof(C.consoleLogger.info)==="function"){C.consoleLogger.info.apply(C.consoleLogger,D)}else{if(G>=i.DEBUG&&typeof(C.consoleLogger.debug)==="function"){C.consoleLogger.debug.apply(C.consoleLogger,D)}else{if(typeof(C.consoleLogger.log)==="function"){C.consoleLogger.log.apply(C.consoleLogger,D)}}}}}}return C};i.prototype.log=function h(){this.emit(1,this.options.defaultNamespace,Array.prototype.slice.call(arguments,0))};i.prototype.debug=function p(){this.emit(i.DEBUG,this.options.defaultNamespace,Array.prototype.slice.call(arguments,0))};i.prototype.info=function x(){this.emit(i.INFO,this.options.defaultNamespace,Array.prototype.slice.call(arguments,0))};i.prototype.warn=function w(){this.emit(i.WARN,this.options.defaultNamespace,Array.prototype.slice.call(arguments,0))};i.prototype.error=function t(){this.emit(i.ERROR,this.options.defaultNamespace,Array.prototype.slice.call(arguments,0))};i.prototype.metric=function r(){this.emit(i.METRIC,this.options.defaultNamespace,Array.prototype.slice.call(arguments,0))};function z(D){var C=this;return C._init(D||{})}z.defaultOptions={maxSize:5000};z.prototype._init=function j(C){if(!this._hasStorage()){throw new Error("LoggingCache needs localStorage")}if(!C.key){throw new Error("LoggingCache needs key for localStorage")}this.key=C.key;this._initStorage();this.maxSize=C.maxSize||z.defaultOptions.maxSize;return this};z.prototype._hasStorage=function A(){var D="test";try{localStorage.setItem(D,D);localStorage.removeItem(D);return true}catch(C){return false}};z.prototype._initStorage=function m(){if(localStorage.getItem(this.key)===null){return this.empty()}return this};z.prototype.add=function o(E){var D=this,F=D._fetchAndParse(),C=(F.length+1)-D.maxSize;if(C>0){F.splice(0,C)}F.push(E);D._unparseAndStore(F);return F.length};z.prototype._fetchAndParse=function g(){var C=this;return JSON.parse(localStorage.getItem(C.key))};z.prototype._unparseAndStore=function f(C){var D=this;return localStorage.setItem(D.key,JSON.stringify(C))};z.prototype.length=function e(){return this._fetchAndParse().length};z.prototype.get=function y(C){return this._fetchAndParse().slice(0,C)};z.prototype.remove=function B(C){var E=this._fetchAndParse(),D=E.splice(0,C);this._unparseAndStore(E);return D};z.prototype.empty=function l(){localStorage.setItem(this.key,"[]");return this};z.prototype.stringify=function s(C){return JSON.stringify(this.get(C))};z.prototype.print=function d(){console.log(JSON.stringify(this._fetchAndParse(),null," "))};return{MetricsLogger:i,LoggingCache:z}}); \ No newline at end of file +define([],function(){function i(D){D=D||{};var C=this;C.userId=(window.bootstrapped&&window.bootstrapped.user)?window.bootstrapped.user.id:null;C.userId=C.userId||D.userId||null;C.consoleLogger=D.consoleLogger||null;C._init(D);return C}i.ALL=0;i.DEBUG=10;i.INFO=20;i.WARN=30;i.ERROR=40;i.METRIC=50;i.NONE=100;i.defaultOptions={logLevel:i.NONE,consoleLevel:i.NONE,defaultNamespace:"Galaxy",clientPrefix:"client.",maxCacheSize:3000,postSize:1000,addTime:true,cacheKeyPrefix:"logs-",postUrl:"/api/metrics",delayPostInMs:1000*60*10,getPingData:undefined,onServerResponse:undefined};i.prototype._init=function j(E){var D=this;D.options={};for(var C in i.defaultOptions){if(i.defaultOptions.hasOwnProperty(C)){D.options[C]=(E.hasOwnProperty(C))?(E[C]):(i.defaultOptions[C])}}D.options.logLevel=D._parseLevel(D.options.logLevel);D.options.consoleLevel=D._parseLevel(D.options.consoleLevel);D._sending=false;D._waiting=null;D._postSize=D.options.postSize;D._initCache();return D};i.prototype._initCache=function a(){try{this.cache=new z({maxSize:this.options.maxCacheSize,key:this.options.cacheKeyPrefix+this.userId})}catch(C){this._emitToConsole("warn","MetricsLogger",["Could not intitialize logging cache:",C]);this.options.logLevel=i.NONE}};i.prototype._parseLevel=function n(E){var D=typeof E;if(D==="number"){return E}if(D==="string"){var C=E.toUpperCase();if(i.hasOwnProperty(C)){return i[C]}}throw new Error("Unknown log level: "+E)};i.prototype.emit=function q(F,E,D){var C=this;E=E||C.options.defaultNamespace;if(!F||!D){return C}F=C._parseLevel(F);if(F>=C.options.logLevel){C._addToCache(F,E,D)}if(C.consoleLogger&&F>=C.options.consoleLevel){C._emitToConsole(F,E,D)}return C};i.prototype._addToCache=function b(H,E,D){this._emitToConsole("debug","MetricsLogger",["_addToCache:",arguments,this.options.addTime,this.cache.length()]);var C=this;try{var G=C.cache.add(C._buildEntry(H,E,D));if(G>=C._postSize){C._postCache()}}catch(F){C._emitToConsole("warn","MetricsLogger",["Metrics logger could not stringify logArguments:",E,D]);C._emitToConsole("error","MetricsLogger",[F])}return C};i.prototype._buildEntry=function u(F,D,C){this._emitToConsole("debug","MetricsLogger",["_buildEntry:",arguments]);var E={level:F,namespace:this.options.clientPrefix+D,args:C};if(this.options.addTime){E.time=new Date().toISOString()}return E};i.prototype._postCache=function v(F){F=F||{};this._emitToConsole("info","MetricsLogger",["_postCache",F,this._postSize]);if(!this.options.postUrl||this._sending){return jQuery.when({})}var E=this,H=F.count||E._postSize,C=E.cache.get(H),G=C.length,D=(typeof E.options.getPingData==="function")?(E.options.getPingData()):({});D.metrics=JSON.stringify(C);E._sending=true;return jQuery.post(E.options.postUrl,D).always(function(){E._sending=false}).fail(function(K,I,J){E._postSize=E.options.maxCacheSize;this.emit("error","MetricsLogger",["_postCache error:",K.readyState,K.status,K.responseJSON||K.responseText])}).done(function(I){if(typeof E.options.onServerResponse==="function"){E.options.onServerResponse(I)}E.cache.remove(G);E._postSize=E.options.postSize})};i.prototype._delayPost=function k(){var C=this;C._waiting=setTimeout(function(){C._waiting=null},C.options.delayPostInMs)};i.prototype._emitToConsole=function c(G,F,E){var C=this;if(!C.consoleLogger){return C}var D=Array.prototype.slice.call(E,0);D.unshift(F);if(G>=i.METRIC&&typeof(C.consoleLogger.info)==="function"){return C.consoleLogger.info.apply(C.consoleLogger,D)}else{if(G>=i.ERROR&&typeof(C.consoleLogger.error)==="function"){return C.consoleLogger.error.apply(C.consoleLogger,D)}else{if(G>=i.WARN&&typeof(C.consoleLogger.warn)==="function"){C.consoleLogger.warn.apply(C.consoleLogger,D)}else{if(G>=i.INFO&&typeof(C.consoleLogger.info)==="function"){C.consoleLogger.info.apply(C.consoleLogger,D)}else{if(G>=i.DEBUG&&typeof(C.consoleLogger.debug)==="function"){C.consoleLogger.debug.apply(C.consoleLogger,D)}else{if(typeof(C.consoleLogger.log)==="function"){C.consoleLogger.log.apply(C.consoleLogger,D)}}}}}}return C};i.prototype.log=function h(){this.emit(1,this.options.defaultNamespace,Array.prototype.slice.call(arguments,0))};i.prototype.debug=function p(){this.emit(i.DEBUG,this.options.defaultNamespace,Array.prototype.slice.call(arguments,0))};i.prototype.info=function x(){this.emit(i.INFO,this.options.defaultNamespace,Array.prototype.slice.call(arguments,0))};i.prototype.warn=function w(){this.emit(i.WARN,this.options.defaultNamespace,Array.prototype.slice.call(arguments,0))};i.prototype.error=function t(){this.emit(i.ERROR,this.options.defaultNamespace,Array.prototype.slice.call(arguments,0))};i.prototype.metric=function r(){this.emit(i.METRIC,this.options.defaultNamespace,Array.prototype.slice.call(arguments,0))};function z(D){var C=this;return C._init(D||{})}z.defaultOptions={maxSize:5000};z.prototype._init=function j(C){if(!this._hasStorage()){throw new Error("LoggingCache needs localStorage")}if(!C.key){throw new Error("LoggingCache needs key for localStorage")}this.key=C.key;this._initStorage();this.maxSize=C.maxSize||z.defaultOptions.maxSize;return this};z.prototype._hasStorage=function A(){var D="test";try{localStorage.setItem(D,D);localStorage.removeItem(D);return true}catch(C){return false}};z.prototype._initStorage=function m(){if(localStorage.getItem(this.key)===null){return this.empty()}return this};z.prototype.add=function o(E){var D=this,F=D._fetchAndParse(),C=(F.length+1)-D.maxSize;if(C>0){F.splice(0,C)}F.push(E);D._unparseAndStore(F);return F.length};z.prototype._fetchAndParse=function g(){var C=this;return JSON.parse(localStorage.getItem(C.key))};z.prototype._unparseAndStore=function f(C){var D=this;return localStorage.setItem(D.key,JSON.stringify(C))};z.prototype.length=function e(){return this._fetchAndParse().length};z.prototype.get=function y(C){return this._fetchAndParse().slice(0,C)};z.prototype.remove=function B(C){var E=this._fetchAndParse(),D=E.splice(0,C);this._unparseAndStore(E);return D};z.prototype.empty=function l(){localStorage.setItem(this.key,"[]");return this};z.prototype.stringify=function s(C){return JSON.stringify(this.get(C))};z.prototype.print=function d(){console.log(JSON.stringify(this._fetchAndParse(),null," "))};return{MetricsLogger:i,LoggingCache:z}}); \ No newline at end of file diff --git a/static/scripts/utils/metrics-logger.js b/static/scripts/utils/metrics-logger.js index 1592e8e3ec9..25eb7b0dd5e 100644 --- a/static/scripts/utils/metrics-logger.js +++ b/static/scripts/utils/metrics-logger.js @@ -34,7 +34,7 @@ function MetricsLogger( options ){ var self = this; ///** get the current user's id from bootstrapped data or options */ - self.userId = window.bootstrapped? window.bootstrapped.user.id: null; + self.userId = ( window.bootstrapped && window.bootstrapped.user )? window.bootstrapped.user.id: null; self.userId = self.userId || options.userId || null; /** the (optional) console to emit logs to */ From 7abdea1eaa5cfde4aeae5610aed42c027434a32e Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Mon, 9 Feb 2015 11:34:47 -0500 Subject: [PATCH 10/10] Managers, users: add tags_used and requests to user serializer to match info bootstrapped in galaxy.masthead.mako and galaxy_client_app.mako --- lib/galaxy/managers/users.py | 67 +++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/lib/galaxy/managers/users.py b/lib/galaxy/managers/users.py index 9580122af53..74899b505be 100644 --- a/lib/galaxy/managers/users.py +++ b/lib/galaxy/managers/users.py @@ -199,6 +199,42 @@ class UserManager( base.ModelManager, deletable.PurgableManagerMixin ): #TODO: use quota manager return self.app.quota_agent.get_percent( user=user ) + def tags_used( self, trans, user, tag_models=None ): + """ + Return a list of distinct 'user_tname:user_value' strings that the + given user has used. + """ + #TODO: simplify and unify with tag manager + if self.is_anonymous( user ): + return [] + + # get all the taggable model TagAssociations + if not tag_models: + tag_models = [ v.tag_assoc_class for v in self.app.tag_handler.item_tag_assoc_info.values() ] + # create a union of subqueries for each for this user - getting only the tname and user_value + all_tags_query = None + for tag_model in tag_models: + subq = ( self.app.model.context.query( tag_model.user_tname, tag_model.user_value ) + .filter( tag_model.user == trans.user ) ) + all_tags_query = subq if all_tags_query is None else all_tags_query.union( subq ) + + # if nothing init'd the query, bail + if all_tags_query is None: + return [] + + # boil the tag tuples down into a sorted list of DISTINCT name:val strings + tags = all_tags_query.distinct().all() + tags = [( ( name + ':' + val ) if val else name ) for name, val in tags ] + return sorted( tags ) + + def has_requests( self, trans, user ): + """ + """ + if self.is_anonymous( user ): + return False + request_types = self.app.security_agent.get_accessible_request_types( trans, user ) + return ( user.requests or request_types ) + class UserSerializer( base.ModelSerializer, deletable.PurgableSerializerMixin ): @@ -214,21 +250,22 @@ class UserSerializer( base.ModelSerializer, deletable.PurgableSerializerMixin ): 'id', 'email', 'username' ]) self.add_view( 'detailed', [ - 'update_time', - 'create_time', - - 'deleted', - 'purged', - 'active', + #'update_time', + #'create_time', 'total_disk_usage', 'nice_total_disk_usage', 'quota_percent' - # 'preferences', - # # all tags - # 'tags', - # # all annotations - # 'annotations' + + #'deleted', + #'purged', + #'active', + + #'preferences', + # all tags + 'tags_used', + ## all annotations + #'annotations' ], include_keys_from='summary' ) def add_serializers( self ): @@ -240,7 +277,13 @@ class UserSerializer( base.ModelSerializer, deletable.PurgableSerializerMixin ): 'create_time' : self.serialize_date, 'update_time' : self.serialize_date, 'is_admin' : lambda t, i, k: self.user_manager.is_admin( t, i ), - 'quota_percent' : lambda t, i, k: self.user_manager.quota( t, i ) + + 'total_disk_usage' : lambda t, i, k: float( i.total_disk_usage ), + 'quota_percent' : lambda t, i, k: self.user_manager.quota( t, i ), + + 'tags_used' : lambda t, i, k: self.user_manager.tags_used( t, i ), + #TODO: 'has_requests' is more apt + 'requests' : lambda t, i, k: self.user_manager.has_requests( t, i ) }) def serialize_current_anonymous_user( self, trans, user, keys ):
",d,"",e,"
",b,"",a[0],"",a[1],"