mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #4476 from mvdbeek/workflow_view_tags
Show tags, import by drag-and-drop and avoid refreshed in workflow page
This commit is contained in:
@@ -11,29 +11,40 @@ var TagsEditor = Backbone.View
|
||||
.extend( baseMVC.LoggableMixin )
|
||||
.extend( baseMVC.HiddenUntilActivatedViewMixin ).extend({
|
||||
|
||||
tagName : 'div',
|
||||
className : 'tags-display',
|
||||
tagName : 'div',
|
||||
className : 'tags-display',
|
||||
select_width : '100%',
|
||||
events: {},
|
||||
|
||||
/** Set up listeners, parse options */
|
||||
initialize : function( options ){
|
||||
//console.debug( this, options );
|
||||
// only listen to the model only for changes to tags - re-render
|
||||
this.show_editor = false;
|
||||
if (options.usePrompt === false) {
|
||||
this.label = '';
|
||||
} else {
|
||||
this.label = '<label class="prompt">' + _l( 'Tags' ) + '</label>';
|
||||
}
|
||||
this.workflow_mode = options.workflow_mode || false;
|
||||
if (this.workflow_mode) {
|
||||
this.events.click = 'showEditor';
|
||||
this.events.keydown = 'keydownHandler';
|
||||
}
|
||||
this.hiddenUntilActivated( options.$activator, options );
|
||||
},
|
||||
|
||||
/** Build the DOM elements, call select to on the created input, and set up behaviors */
|
||||
render : function(){
|
||||
var self = this;
|
||||
this.$el.html( this._template() );
|
||||
|
||||
if (this.workflow_mode) {
|
||||
this.$el.html(this._workflowTemplate());
|
||||
} else {
|
||||
this.$el.html(this._defaultTemplate());
|
||||
}
|
||||
this.$input().select2({
|
||||
placeholder : 'Add tags',
|
||||
width : '100%',
|
||||
width : this.workflow_mode ? this.width : this.select_width,
|
||||
tags : function(){
|
||||
// initialize possible tags in the dropdown based on all the tags the user has used so far
|
||||
return self._getTagsUsed();
|
||||
@@ -59,14 +70,61 @@ var TagsEditor = Backbone.View
|
||||
},
|
||||
|
||||
/** @returns {String} the html text used to build the view's DOM */
|
||||
_template : function(){
|
||||
_defaultTemplate : function(){
|
||||
return [
|
||||
this.label,
|
||||
// set up initial tags by adding as CSV to input vals (necc. to init select2)
|
||||
'<input class="tags-input" value="', this.tagsToCSV(), '" />'
|
||||
this._renderEditor()
|
||||
].join( '' );
|
||||
},
|
||||
|
||||
_workflowTemplate : function(){
|
||||
// Shows labels by default, event handler controls whether we show tags or editor
|
||||
return [
|
||||
this.show_editor ? this._renderEditor() : this._renderTags(),
|
||||
].join( ' ' );
|
||||
},
|
||||
|
||||
keydownHandler : function (e) {
|
||||
switch (e.which) {
|
||||
// esc
|
||||
case 27 :
|
||||
// hide the tag editor when pressing escape
|
||||
this.hideEditor();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
showEditor: function() {
|
||||
this.show_editor = true;
|
||||
this.render();
|
||||
},
|
||||
|
||||
hideEditor: function() {
|
||||
this.show_editor = false;
|
||||
this.render();
|
||||
},
|
||||
|
||||
_renderEditor: function(){
|
||||
// set up initial tags by adding as CSV to input vals (necc. to init select2)
|
||||
return '<input class="tags-input" value="' + this.tagsToCSV() + '"/>'
|
||||
},
|
||||
|
||||
_renderTags : function(){
|
||||
var tags = this.model.get('tags');
|
||||
var addButton = 'static/images/fugue/tag--plus.png';
|
||||
var renderedArray = [];
|
||||
_.each(tags, function(tag) {
|
||||
tag = tag.indexOf("name:") == 0 ? tag.slice(5) : tag ;
|
||||
var renderString = '<span class="label label-info">' + tag + '</span>';
|
||||
renderedArray.push( renderString );
|
||||
});
|
||||
if (renderedArray.length === 0) {
|
||||
// If there are no tags to render we just show the add-tag-button
|
||||
renderedArray.push('<img src=' + addButton + ' class="add-tag-button" title="Add tags"/>');
|
||||
}
|
||||
return renderedArray.join(" ");
|
||||
},
|
||||
|
||||
/** @returns {String} the sorted, comma-separated tags from the model */
|
||||
tagsToCSV : function(){
|
||||
var self = this;
|
||||
@@ -132,8 +190,7 @@ var TagsEditor = Backbone.View
|
||||
toString : function(){ return [ 'TagsEditor(', this.model + '', ')' ].join(''); }
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
return {
|
||||
TagsEditor : TagsEditor
|
||||
TagsEditor : TagsEditor,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
define([
|
||||
"mvc/base-mvc",
|
||||
], function( baseMVC ){
|
||||
/* global Backbone */
|
||||
// workflow model
|
||||
|
||||
var logNamespace = 'workflow';
|
||||
//==============================================================================
|
||||
/** @class model for a single workflow.
|
||||
* @name WorkflowItem
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
var WorkflowItem = Backbone.Model.extend( baseMVC.LoggableMixin ).extend({
|
||||
_logNamespace : logNamespace,
|
||||
|
||||
urlRoot: '/api/workflows',
|
||||
|
||||
toJSON: function(){
|
||||
// need to overwrite this as endpoint expects the 'workflow' key in payload
|
||||
return {workflow : this.attributes};
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
/** @class collection for workflows.
|
||||
* @name WorkflowCollection
|
||||
* @augments Backbone.Collection
|
||||
*/
|
||||
var WorkflowCollection = Backbone.Collection.extend({
|
||||
model: WorkflowItem,
|
||||
url: '/api/workflows',
|
||||
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
|
||||
return {
|
||||
WorkflowItem: WorkflowItem,
|
||||
WorkflowCollection: WorkflowCollection,
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
@@ -1,94 +1,241 @@
|
||||
/** Workflow view */
|
||||
define( [ 'utils/utils', 'mvc/ui/ui-misc' ], function( Utils, Ui ) {
|
||||
define( [ "libs/toastr", "mvc/tag", "mvc/workflow/workflow-model" ], function( mod_toastr, TAGS, WORKFLOWS ) {
|
||||
|
||||
/** View of the individual workflows */
|
||||
var WorkflowItemView = Backbone.View.extend({
|
||||
tagName: 'tr', // name of (orphan) root tag in this.el
|
||||
initialize: function(){
|
||||
_.bindAll(this, 'render', '_rowTemplate', 'renderTagEditor', '_templateActions', 'removeWorkflow', 'copyWorkflow'); // every function that uses 'this' as the current object should be in here
|
||||
mod_toastr.options.timeOut = 1500;
|
||||
},
|
||||
|
||||
events: {
|
||||
'click #show-in-tool-panel': 'showInToolPanel',
|
||||
'click #delete-workflow' : 'removeWorkflow',
|
||||
'click #rename-workflow' : 'renameWorkflow',
|
||||
'click #copy-workflow' : 'copyWorkflow',
|
||||
},
|
||||
|
||||
render: function(){
|
||||
$(this.el).html(this._rowTemplate());
|
||||
return this;
|
||||
},
|
||||
|
||||
showInToolPanel: function(){
|
||||
this.model.set('show_in_tool_panel', !this.model.get('show_in_tool_panel'));
|
||||
this.model.save();
|
||||
// This reloads the whole page, so that the workflow appears in the tool panel.
|
||||
// Ideally we would notify only the tool panel of a change
|
||||
window.location = Galaxy.root + 'workflow';
|
||||
},
|
||||
|
||||
removeWorkflow: function(){
|
||||
var wfName = this.model.get('name');
|
||||
if (confirm( "Are you sure you want to delete workflow '" + wfName + "'?" )) {
|
||||
this.model.destroy({
|
||||
success: function() {
|
||||
mod_toastr.success("Successfully deleted workflow '" + wfName + "'");
|
||||
}
|
||||
});
|
||||
this.remove();
|
||||
};
|
||||
},
|
||||
|
||||
renameWorkflow: function(){
|
||||
var oldName = this.model.get('name');
|
||||
var newName = prompt("Enter a new Name for workflow '" + oldName + "'", oldName );
|
||||
if (newName) {
|
||||
this.model.save(
|
||||
{ 'name': newName },
|
||||
{ success: function() {
|
||||
mod_toastr.success("Successfully renamed workflow '" + oldName + "' to '" + newName + "'")
|
||||
}
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
copyWorkflow: function(){
|
||||
self = this;
|
||||
var oldName = this.model.get('name');
|
||||
$.getJSON(this.model.urlRoot + '/' + this.model.id + '/download', function(wfJson) {
|
||||
var newName = 'Copy of ' + oldName;
|
||||
var currentOwner = self.model.get('owner');
|
||||
if (currentOwner != Galaxy.user.attributes.username) {
|
||||
newName += ' shared by user ' + currentOwner;
|
||||
}
|
||||
wfJson.name = newName;
|
||||
self.collection.create(wfJson, { at: 0,
|
||||
wait: true,
|
||||
success: function() {
|
||||
mod_toastr.success("Successfully copied workflow '" + oldName + "' to '" + newName + "'")
|
||||
},
|
||||
error : function(model, resp, options) {
|
||||
// signature seems to have changed over the course of backbone dev
|
||||
// see https://github.com/jashkenas/backbone/issues/2606#issuecomment-19289483
|
||||
mod_toastr.error(options.errorThrown);
|
||||
}
|
||||
});
|
||||
}).error(function(jqXHR, textStatus, errorThrown) {
|
||||
mod_toastr.error(jqXHR.responseJSON.err_msg);
|
||||
})
|
||||
},
|
||||
|
||||
_rowTemplate: function() {
|
||||
var show = this.model.get("show_in_tool_panel");
|
||||
var wfId = this.model.id;
|
||||
var checkboxHtml = '<input id="show-in-tool-panel" type="checkbox" class="show-in-tool-panel" '+ ( show ? 'checked="' + show + '"' : "" ) +' value="' + wfId + '">';
|
||||
var trHtml = '<td>' +
|
||||
'<div class="dropdown">' +
|
||||
'<button class="menubutton" type="button" data-toggle="dropdown">' +
|
||||
_.escape( this.model.get("name") ) + '<span class="caret"></span>' +
|
||||
'</button>' +
|
||||
this._templateActions( ) +
|
||||
'</div>' +
|
||||
'</td>' +
|
||||
'<td><span>' + '<div class="' + wfId + ' tags-display"></div>' + '</td>' +
|
||||
'<td>' + ( this.model.get('owner') === Galaxy.user.attributes.username ? "You" : this.model.get('owner') ) +'</span></td>' +
|
||||
'<td>' + this.model.get("number_of_steps") + '</td>' +
|
||||
'<td>' + ( this.model.get("published") ? "Yes" : "No" ) + '</td>' +
|
||||
'<td>'+ checkboxHtml + '</td>';
|
||||
return trHtml;
|
||||
},
|
||||
|
||||
renderTagEditor: function(){
|
||||
var TagEditor = new TAGS.TagsEditor({
|
||||
model : this.model,
|
||||
el : $.find( '.' + this.model.id + '.tags-display' ),
|
||||
workflow_mode : true });
|
||||
TagEditor.toggle( true );
|
||||
TagEditor.render();
|
||||
},
|
||||
|
||||
/** Template for user actions for workflows */
|
||||
_templateActions: function( ) {
|
||||
if( this.model.get("owner") === Galaxy.user.attributes.username ) {
|
||||
return '<ul class="dropdown-menu action-dpd">' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/editor?id='+ this.model.id +'">Edit</a></li>' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/run?id='+ this.model.id +'">Run</a></li>' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/sharing?id='+ this.model.id +'">Share</a></li>' +
|
||||
'<li><a href="'+ Galaxy.root +'api/workflows/'+ this.model.id +'/download?format=json-download">Download</a></li>' +
|
||||
'<li><a id="copy-workflow" style="cursor: pointer;">Copy</a></li>' +
|
||||
'<li><a id="rename-workflow" style="cursor: pointer;">Rename</a></li>' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/display_by_id?id='+ this.model.id +'">View</a></li>' +
|
||||
'<li><a id="delete-workflow" style="cursor: pointer;">Delete</a></li>' +
|
||||
'</ul>';
|
||||
}
|
||||
else {
|
||||
return '<ul class="dropdown-menu action-dpd">' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/display_by_username_and_slug?username='+ workflow.owner +'&slug='+ workflow.slug +'">View</a></li>' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/run?id='+ this.model.id +'">Run</a></li>' +
|
||||
'<li><a id="copy-workflow" style="cursor: pointer;">Copy</a></li>' +
|
||||
'<li><a class="link-confirm-shared-'+ this.model.id +'" href="'+ Galaxy.root +'workflow/sharing?unshare_me=True&id='+ this.model.id +'">Remove</a></li>' +
|
||||
'</ul>';
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/** Build messages after user action */
|
||||
function build_messages() {
|
||||
var $el_message = this.$( '.response-message' ),
|
||||
response = {};
|
||||
response = {
|
||||
'status': Utils.getQueryString( 'status' ),
|
||||
'message': _.escape( Utils.getQueryString( 'message' ) ),
|
||||
'persistent': true,
|
||||
'cls': Utils.getQueryString( 'status' ) + 'message'
|
||||
};
|
||||
$el_message.empty().html( new Ui.Message( response ).$el );
|
||||
}
|
||||
|
||||
/** View of the main workflow list page */
|
||||
var View = Backbone.View.extend({
|
||||
var WorkflowListView = Backbone.View.extend({
|
||||
|
||||
initialize: function() {
|
||||
this.setElement( '<div/>' );
|
||||
this.render();
|
||||
_.bindAll(this, 'adjustActiondropdown')
|
||||
this.collection = new WORKFLOWS.WorkflowCollection();
|
||||
this.collection.fetch().done(this.render());
|
||||
this.collection.bind('add', this.appendItem);
|
||||
this.collection.on('sync', this.render, this);
|
||||
},
|
||||
|
||||
events: {
|
||||
'dragleave' : 'unhighlightDropZone',
|
||||
'drop' : 'drop',
|
||||
'dragover': function(ev) {
|
||||
$( '.hidden_description_layer' ).addClass( 'dragover' );
|
||||
$('.menubutton').addClass('background-none');
|
||||
ev.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
unhighlightDropZone: function() {
|
||||
$( '.hidden_description_layer' ).removeClass( 'dragover' );
|
||||
$('.menubutton').removeClass('background-none');
|
||||
},
|
||||
|
||||
drop: function(e) {
|
||||
// TODO: check that file is valid galaxy workflow
|
||||
this.unhighlightDropZone();
|
||||
e.preventDefault();
|
||||
var files = e.dataTransfer.files;
|
||||
var self = this;
|
||||
for (var i = 0, f; f = files[i]; i++) {
|
||||
self.readWorkflowFiles(f);
|
||||
}
|
||||
},
|
||||
|
||||
readWorkflowFiles: function(f) {
|
||||
var self = this;
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(theFile) {
|
||||
try {
|
||||
var wf_json = JSON.parse(reader.result);
|
||||
} catch(e) {
|
||||
mod_toastr.error("Could not read file '" + f.name + "'. Verify it is a valid Galaxy workflow");
|
||||
wf_json = null;
|
||||
}
|
||||
if (wf_json) {
|
||||
self.collection.create(wf_json, {
|
||||
at: 0,
|
||||
wait: true,
|
||||
success: function() {
|
||||
mod_toastr.success("Successfully imported workflow '" + wf_json.name + "'")
|
||||
},
|
||||
error : function(model, resp, options) {
|
||||
mod_toastr.error(options.errorThrown);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
reader.readAsText(f, 'utf-8');
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var self = this,
|
||||
min_query_length = 3;
|
||||
$.getJSON( Galaxy.root + 'api/workflows/', function( workflows ) {
|
||||
var $el_workflow = null;
|
||||
// Add workflow header
|
||||
self.$el.empty().append( self._templateHeader() );
|
||||
// Add user actions message if any
|
||||
build_messages();
|
||||
$el_workflow = self.$( '.user-workflows' );
|
||||
// Add the actions buttons
|
||||
$el_workflow.append( self._templateActionButtons() );
|
||||
if( workflows.length > 0) {
|
||||
$el_workflow.append( self._templateWorkflowTable( self, workflows) );
|
||||
self.adjust_actiondropdown( $el_workflow );
|
||||
// Register delete and run workflow events
|
||||
_.each( workflows, function( wf ) {
|
||||
self.confirm_delete( wf );
|
||||
});
|
||||
self.register_show_tool_menu();
|
||||
// Register search workflow event
|
||||
self.search_workflow( self.$( '.search-wf' ), self.$( '.workflow-search tr' ), min_query_length );
|
||||
}
|
||||
else {
|
||||
$el_workflow.append( self._templateNoWorkflow() );
|
||||
}
|
||||
});
|
||||
// Add workflow header
|
||||
var header = this._templateHeader();
|
||||
// Add the actions buttons
|
||||
var templateActions = this._templateActionButtons();
|
||||
var tableTemplate = this._templateWorkflowTable();
|
||||
this.$el.html( header + templateActions + tableTemplate);
|
||||
var self = this;
|
||||
_(this.collection.models).each(function(item){ // in case collection is not empty
|
||||
self.appendItem(item);
|
||||
self.confirmDelete(item);
|
||||
}, this);
|
||||
var minQueryLength = 3;
|
||||
this.searchWorkflow( this.$( '.search-wf' ), this.$( '.workflow-search tr' ), minQueryLength );
|
||||
this.adjustActiondropdown();
|
||||
return this;
|
||||
},
|
||||
|
||||
// Save the workflow as an item in Tool panel
|
||||
register_show_tool_menu: function() {
|
||||
var $el_checkboxes = this.$( '.show-in-tool-panel' );
|
||||
$el_checkboxes.on( 'click', function( e ) {
|
||||
var ids = [];
|
||||
// Look for all the checked checkboxes
|
||||
for( var item = 0; item < $el_checkboxes.length; item++ ) {
|
||||
var checkbox = $el_checkboxes[ item ];
|
||||
if( checkbox.checked ) {
|
||||
ids.push( checkbox.value );
|
||||
}
|
||||
}
|
||||
// Save all the checked workflows
|
||||
$.ajax({
|
||||
type: 'PUT',
|
||||
url: Galaxy.root + 'api/workflows/menu/',
|
||||
data: JSON.stringify( { 'workflow_ids': ids } ),
|
||||
contentType : 'application/json'
|
||||
}).done( function( response ) {
|
||||
window.location = Galaxy.root + 'workflow';
|
||||
});
|
||||
appendItem: function(item){
|
||||
var workflowItemView = new WorkflowItemView({
|
||||
model: item,
|
||||
collection: this.collection,
|
||||
});
|
||||
$( '.workflow-search' ).append(workflowItemView.render().el);
|
||||
workflowItemView.renderTagEditor();
|
||||
},
|
||||
|
||||
/** Add confirm box before removing/unsharing workflow */
|
||||
confirm_delete: function( workflow ) {
|
||||
var $el_wf_link = this.$( '.link-confirm-' + workflow.id ),
|
||||
$el_shared_wf_link = this.$( '.link-confirm-shared-' + workflow.id );
|
||||
$el_wf_link.click( function() {
|
||||
return confirm( "Are you sure you want to delete workflow '" + workflow.name + "'?" );
|
||||
});
|
||||
confirmDelete: function( workflow ) {
|
||||
var $el_shared_wf_link = this.$( '.link-confirm-shared-' + workflow.id );
|
||||
$el_shared_wf_link.click( function() {
|
||||
return confirm( "Are you sure you want to remove the shared workflow '" + workflow.name + "'?" );
|
||||
return confirm( "Are you sure you want to remove the shared workflow '" + workflow.attributes.name + "'?" );
|
||||
});
|
||||
},
|
||||
|
||||
/** Implement client side workflow search/filtering */
|
||||
search_workflow: function( $el_searchinput, $el_tabletr, min_querylen ) {
|
||||
searchWorkflow: function( $el_searchinput, $el_tabletr, min_querylen ) {
|
||||
$el_searchinput.on( 'keyup', function () {
|
||||
var query = $( this ).val();
|
||||
// Filter when query is at least 3 characters
|
||||
@@ -110,13 +257,12 @@ define( [ 'utils/utils', 'mvc/ui/ui-misc' ], function( Utils, Ui ) {
|
||||
},
|
||||
|
||||
/** Ajust the position of dropdown with respect to table */
|
||||
adjust_actiondropdown: function( $el ) {
|
||||
$el.on( 'show.bs.dropdown', function () {
|
||||
$el.css( "overflow", "inherit" );
|
||||
adjustActiondropdown: function( ) {
|
||||
$(this.el).on( 'show.bs.dropdown', function () {
|
||||
$(this.el).css( "overflow", "inherit" );
|
||||
});
|
||||
|
||||
$el.on( 'hide.bs.dropdown', function () {
|
||||
$el.css( "overflow", "auto" );
|
||||
$(this.el).on( 'hide.bs.dropdown', function () {
|
||||
$(this.el).css( "overflow", "auto" );
|
||||
});
|
||||
},
|
||||
|
||||
@@ -143,57 +289,17 @@ define( [ 'utils/utils', 'mvc/ui/ui-misc' ], function( Utils, Ui ) {
|
||||
},
|
||||
|
||||
/** Template for workflow table */
|
||||
_templateWorkflowTable: function( self, workflows ) {
|
||||
var tableHtml = "", trHtml = "";
|
||||
tableHtml = tableHtml + '<table class="table colored"><thead>' +
|
||||
_templateWorkflowTable: function( ) {
|
||||
var tableHtml = '<table class="table colored"><thead>' +
|
||||
'<tr class="header">' +
|
||||
'<th>Name</th>' +
|
||||
'<th>Tags</th>' +
|
||||
'<th>Owner</th>' +
|
||||
'<th># of Steps</th>' +
|
||||
'<th>Published</th>' +
|
||||
'<th>Show in tools panel</th>' +
|
||||
'</tr></thead>';
|
||||
_.each( workflows, function( wf ) {
|
||||
var checkbox_html = '<input type="checkbox" class="show-in-tool-panel" '+ ( wf.show_in_tool_panel ? 'checked="' + wf.show_in_tool_panel + '"' : "" ) +' value="' + wf.id + '">';
|
||||
trHtml = trHtml + '<tr>' +
|
||||
'<td>' +
|
||||
'<div class="dropdown">' +
|
||||
'<button class="menubutton" type="button" data-toggle="dropdown">' +
|
||||
_.escape( wf.name ) + '<span class="caret"></span>' +
|
||||
'</button>' +
|
||||
self._templateActions( wf ) +
|
||||
'</div>' +
|
||||
'</td>' +
|
||||
'<td>' + ( wf.owner === Galaxy.user.attributes.username ? "You" : wf.owner ) +'</td>' +
|
||||
'<td>' + wf.number_of_steps + '</td>' +
|
||||
'<td>' + ( wf.published ? "Yes" : "No" ) + '</td>' +
|
||||
'<td>'+ checkbox_html +'</td>' +
|
||||
'</tr>';
|
||||
});
|
||||
return tableHtml + '<tbody class="workflow-search">' + trHtml + '</tbody></table>';
|
||||
},
|
||||
|
||||
/** Template for user actions for workflows */
|
||||
_templateActions: function( workflow ) {
|
||||
if( workflow.owner === Galaxy.user.attributes.username ) {
|
||||
return '<ul class="dropdown-menu action-dpd">' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/editor?id='+ workflow.id +'">Edit</a></li>' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/run?id='+ workflow.id +'">Run</a></li>' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/sharing?id='+ workflow.id +'">Share or Download</a></li>' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/copy?id='+ workflow.id +'">Copy</a></li>' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/rename?id='+ workflow.id +'">Rename</a></li>' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/display_by_id?id='+ workflow.id +'">View</a></li>' +
|
||||
'<li><a class="link-confirm-'+ workflow.id +'" href="'+ Galaxy.root +'workflow/delete?id='+ workflow.id +'">Delete</a></li>' +
|
||||
'</ul>';
|
||||
}
|
||||
else {
|
||||
return '<ul class="dropdown-menu action-dpd">' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/display_by_username_and_slug?username='+ workflow.owner +'&slug='+ workflow.slug +'">View</a></li>' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/run?id='+ workflow.id +'">Run</a></li>' +
|
||||
'<li><a href="'+ Galaxy.root +'workflow/copy?id='+ workflow.id +'">Copy</a></li>' +
|
||||
'<li><a class="link-confirm-shared-'+ workflow.id +'" href="'+ Galaxy.root +'workflow/sharing?unshare_me=True&id='+ workflow.id +'">Remove</a></li>' +
|
||||
'</ul>';
|
||||
}
|
||||
return tableHtml + '<tbody class="workflow-search "><div class="hidden_description_layer"><p>Drop workflow files here to import</p>' + '</tbody></table></div>';
|
||||
},
|
||||
|
||||
/** Main template */
|
||||
@@ -264,11 +370,10 @@ define( [ 'utils/utils', 'mvc/ui/ui-misc' ], function( Utils, Ui ) {
|
||||
"</div>" +
|
||||
"</div>";
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
return {
|
||||
View : View,
|
||||
View : WorkflowListView,
|
||||
ImportWorkflowView : ImportWorkflowView
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1271,6 +1271,10 @@ a.action-button {
|
||||
}
|
||||
}
|
||||
|
||||
.menubutton.background-none {
|
||||
background: none;
|
||||
}
|
||||
|
||||
// A split menu button, the main button has an action, the arrow causes the
|
||||
// popup menu to appear
|
||||
|
||||
@@ -1824,3 +1828,26 @@ div.toolTitleNoSection
|
||||
.other-options {
|
||||
margin-bottom: 2%;
|
||||
}
|
||||
|
||||
.hidden_description_layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(200, 200, 200, 0.6);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
font-size: 2.0em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
/* transition effect. not necessary */
|
||||
transition: opacity .2s, visibility .2s;
|
||||
}
|
||||
|
||||
.hidden_description_layer.dragover {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -231,6 +231,8 @@ class WorkflowContentsManager(UsesAnnotations):
|
||||
if data['annotation']:
|
||||
annotation = sanitize_html(data['annotation'], 'utf-8', 'text/html')
|
||||
self.add_item_annotation(trans.sa_session, stored.user, stored, annotation)
|
||||
workflow_tags = data.get('tags', [])
|
||||
trans.app.tag_handler.set_tags_from_list(user=trans.user, item=stored, new_tags_list=workflow_tags)
|
||||
|
||||
# Persist
|
||||
trans.sa_session.add(stored)
|
||||
@@ -546,14 +548,17 @@ class WorkflowContentsManager(UsesAnnotations):
|
||||
workflow = stored.latest_workflow
|
||||
|
||||
annotation_str = ""
|
||||
tag_str = ""
|
||||
if stored is not None:
|
||||
annotation_str = self.get_item_annotation_str(trans.sa_session, trans.user, stored) or ''
|
||||
tag_str = stored.make_tag_string_list()
|
||||
# Pack workflow data into a dictionary and return
|
||||
data = {}
|
||||
data['a_galaxy_workflow'] = 'true' # Placeholder for identifying galaxy workflow
|
||||
data['format-version'] = "0.1"
|
||||
data['name'] = workflow.name
|
||||
data['annotation'] = annotation_str
|
||||
data['tags'] = tag_str
|
||||
if workflow.uuid is not None:
|
||||
data['uuid'] = str(workflow.uuid)
|
||||
data['steps'] = {}
|
||||
|
||||
@@ -358,11 +358,13 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn
|
||||
stored_workflow = self.__get_stored_accessible_workflow(trans, workflow_id)
|
||||
|
||||
style = kwd.get("style", "export")
|
||||
download_format = kwd.get('format')
|
||||
ret_dict = self.workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style=style)
|
||||
if not ret_dict:
|
||||
# This workflow has a tool that's missing from the distribution
|
||||
message = "Workflow cannot be exported due to missing tools."
|
||||
raise exceptions.MessageException(message)
|
||||
if download_format == 'json-download':
|
||||
sname = stored_workflow.name
|
||||
sname = ''.join(c in util.FILENAME_VALID_CHARS and c or '_' for c in sname)[0:150]
|
||||
trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy-Workflow-%s.ga"' % (sname)
|
||||
trans.response.set_content_type('application/galaxy-archive')
|
||||
return ret_dict
|
||||
|
||||
@expose_api
|
||||
@@ -436,15 +438,18 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn
|
||||
:returns: serialized version of the workflow
|
||||
"""
|
||||
stored_workflow = self.__get_stored_workflow(trans, id)
|
||||
if 'workflow' in payload:
|
||||
stored_workflow.name = sanitize_html(payload['name']) if ('name' in payload) else stored_workflow.name
|
||||
workflow_dict = payload.get('workflow')
|
||||
if workflow_dict:
|
||||
new_workflow_name = payload.get('name') or workflow_dict.get('name')
|
||||
if new_workflow_name:
|
||||
stored_workflow.name = sanitize_html(new_workflow_name)
|
||||
|
||||
if 'annotation' in payload:
|
||||
newAnnotation = sanitize_html(payload['annotation'])
|
||||
self.add_item_annotation(trans.sa_session, trans.get_user(), stored_workflow, newAnnotation)
|
||||
|
||||
if 'menu_entry' in payload:
|
||||
if payload['menu_entry']:
|
||||
if 'menu_entry' in payload or 'show_in_tool_panel' in workflow_dict:
|
||||
if payload.get('menu_entry') or workflow_dict.get('show_in_tool_panel'):
|
||||
menuEntry = model.StoredWorkflowMenuEntry()
|
||||
menuEntry.stored_workflow = stored_workflow
|
||||
trans.get_user().stored_workflow_menu_entries.append(menuEntry)
|
||||
@@ -453,15 +458,21 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn
|
||||
entries = {x.stored_workflow_id: x for x in trans.get_user().stored_workflow_menu_entries}
|
||||
if (trans.security.decode_id(id) in entries):
|
||||
trans.get_user().stored_workflow_menu_entries.remove(entries[trans.security.decode_id(id)])
|
||||
# set tags
|
||||
trans.app.tag_handler.set_tags_from_list(user=trans.user, item=stored_workflow, new_tags_list=workflow_dict.get('tags', []))
|
||||
|
||||
try:
|
||||
workflow, errors = self.workflow_contents_manager.update_workflow_from_dict(
|
||||
trans,
|
||||
stored_workflow,
|
||||
payload['workflow'],
|
||||
)
|
||||
except workflows.MissingToolsException:
|
||||
raise exceptions.MessageException("This workflow contains missing tools. It cannot be saved until they have been removed from the workflow or installed.")
|
||||
if 'steps' in workflow_dict:
|
||||
try:
|
||||
workflow, errors = self.workflow_contents_manager.update_workflow_from_dict(
|
||||
trans,
|
||||
stored_workflow,
|
||||
workflow_dict,
|
||||
)
|
||||
except workflows.MissingToolsException:
|
||||
raise exceptions.MessageException("This workflow contains missing tools. It cannot be saved until they have been removed from the workflow or installed.")
|
||||
else:
|
||||
# We only adjusted tags and menu entry
|
||||
return payload
|
||||
else:
|
||||
message = "Updating workflow requires dictionary containing 'workflow' attribute with new JSON description."
|
||||
raise exceptions.RequestParameterInvalidException(message)
|
||||
@@ -537,7 +548,8 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn
|
||||
|
||||
item = workflow.to_dict(value_mapper={'id': trans.security.encode_id})
|
||||
item['url'] = url_for('workflow', id=encoded_id)
|
||||
|
||||
item['owner'] = workflow.user.username
|
||||
item['number_of_steps'] = len(workflow.latest_workflow.steps)
|
||||
rval.append(item)
|
||||
|
||||
#
|
||||
|
||||
@@ -20,7 +20,7 @@ from galaxy.managers import workflows
|
||||
from galaxy.model.item_attrs import UsesItemRatings
|
||||
from galaxy.model.mapping import desc
|
||||
from galaxy.tools.parameters.basic import workflow_building_modes
|
||||
from galaxy.util import unicodify, FILENAME_VALID_CHARS
|
||||
from galaxy.util import unicodify
|
||||
from galaxy.util.sanitize_html import sanitize_html
|
||||
from galaxy.web import error, url_for
|
||||
from galaxy.web.base.controller import BaseUIController, SharableMixin, UsesStoredWorkflowMixin
|
||||
@@ -713,32 +713,6 @@ class WorkflowController(BaseUIController, SharableMixin, UsesStoredWorkflowMixi
|
||||
stored = self.get_stored_workflow(trans, id, check_ownership=False, check_accessible=True)
|
||||
return self._workflow_to_dict(trans, stored)
|
||||
|
||||
@web.json_pretty
|
||||
def export_to_file(self, trans, id):
|
||||
"""
|
||||
Get the latest Workflow for the StoredWorkflow identified by `id` and
|
||||
encode it as a json string that can be imported back into Galaxy
|
||||
|
||||
This has slightly different information than the above. In particular,
|
||||
it does not attempt to decode forms and build UIs, it just stores
|
||||
the raw state.
|
||||
"""
|
||||
|
||||
# Get workflow.
|
||||
stored = self.get_stored_workflow(trans, id, check_ownership=False, check_accessible=True)
|
||||
|
||||
# Stream workflow to file.
|
||||
stored_dict = self._workflow_to_dict(trans, stored)
|
||||
if not stored_dict:
|
||||
# This workflow has a tool that's missing from the distribution
|
||||
trans.response.status = 400
|
||||
return "Workflow cannot be exported due to missing tools."
|
||||
sname = stored.name
|
||||
sname = ''.join(c in FILENAME_VALID_CHARS and c or '_' for c in sname)[0:150]
|
||||
trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy-Workflow-%s.ga"' % (sname)
|
||||
trans.response.set_content_type('application/galaxy-archive')
|
||||
return stored_dict
|
||||
|
||||
@web.expose
|
||||
@web.json
|
||||
def upload_import_workflow(self, trans, cntrller='workflow', **kwd):
|
||||
|
||||
@@ -617,6 +617,9 @@ class ToolModule(WorkflowModule):
|
||||
if message:
|
||||
log.debug(message)
|
||||
module.version_changes.append(message)
|
||||
else:
|
||||
# The tool is not installed
|
||||
raise ToolMissingException("Tool %s missing. Cannot build workflow module." % tool_id)
|
||||
return module
|
||||
|
||||
# ---- Saving in various forms ------------------------------------------
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"tag.js","sources":["../../src/mvc/tag.js"],"names":["define","baseMVC","_l","TagsEditor","Backbone","View","extend","LoggableMixin","HiddenUntilActivatedViewMixin","tagName","className","initialize","options","this","label","usePrompt","hiddenUntilActivated","$activator","render","self","$el","html","_template","$input","select2","placeholder","width","tags","_getTagsUsed","_setUpBehaviors","_hashToName","tag","startsWith","slice","_nameToHash","tagsToCSV","join","tagsArray","model","get","_","isArray","isEmpty","map","escape","sort","find","Galaxy","user","on","event","val","save","added","_addNewTagToTagsUsed","text","newTag","tagsUsed","contains","push","set","remove","off","stopListening","prototype","call","toString"],"mappings":"AAAAA,QACI,eACA,sBACD,SAAUC,EAASC,GAMtB,GAAIC,GAAaC,SAASC,KACjBC,OAAQL,EAAQM,eAChBD,OAAQL,EAAQO,+BAAgCF,QAErDG,QAAc,MACdC,UAAc,eAGdC,WAAa,SAAUC,GAIfC,KAAKC,MADLF,EAAQG,aAAc,EACT,GAEA,yBAA2Bb,EAAI,QAAW,WAE3DW,KAAKG,qBAAsBJ,EAAQK,WAAYL,IAInDM,OAAS,WACL,GAAIC,GAAON,IAaX,OAZAA,MAAKO,IAAIC,KAAMR,KAAKS,aAEpBT,KAAKU,SAASC,SACVC,YAAc,WACdC,MAAc,OACdC,KAAO,WAEH,MAAOR,GAAKS,kBAIpBf,KAAKgB,kBACEhB,MAGXiB,YAAa,SAASC,GAClB,MAAIA,GAAIC,WAAW,KACR,QAAUD,EAAIE,MAAM,GAExBF,GAGXG,YAAa,SAASH,GAIlB,MAHIA,GAAIC,WAAW,WACfD,EAAM,IAAMA,EAAIE,MAAM,IAEnBF,GAIXT,UAAY,WACR,OACIT,KAAKC,MAEL,oCAAqCD,KAAKsB,YAAa,QACzDC,KAAM,KAIZD,UAAY,WACR,GAAIhB,GAAON,KACPwB,EAAYxB,KAAKyB,MAAMC,IAAK,OAChC,QAAKC,EAAEC,QAASJ,IAAeG,EAAEE,QAASL,GAC/B,GAEJA,EAAUM,IAAK,SAAUZ,GAC5B,MAAOS,GAAEI,OAAQzB,EAAKe,YAAaH,MACpCc,OAAOT,KAAM,MAIpBb,OAAS,WACL,MAAOV,MAAKO,IAAI0B,KAAM,qBAI1BlB,aAAe,WAEX,GAAIT,GAAON,IACX,OAAO2B,GAAEG,IAAII,OAAOC,KAAKT,IAAK,aAAepB,EAAKe,cAItDL,gBAAkB,WACd,GAAIV,GAAON,IACXA,MAAKU,SAAS0B,GAAI,SAAU,SAAUC,GAElCA,EAAMC,IAAMX,EAAEG,IAAIO,EAAMC,IAAKhC,EAAKW,aAElCX,EAAKmB,MAAMc,MAAOzB,KAAMuB,EAAMC,MAE1BD,EAAMG,OAGNlC,EAAKmC,qBAAsBJ,EAAMG,MAAME,KAAO,OAQ1DD,qBAAuB,SAAUE,GAE7B,GAAIC,GAAWV,OAAOC,KAAKT,IAAK,YAC3BC,GAAEkB,SAAUD,EAAUD,KACvBC,EAASE,KAAMH,GACfC,EAASZ,OACTE,OAAOC,KAAKY,IAAK,YAAaH,KAKtCI,OAAS,WACLhD,KAAKU,OAAOuC,MACZjD,KAAKkD,cAAelD,KAAKyB,OACzBlC,SAASC,KAAK2D,UAAUH,OAAOI,KAAMpD,OAIzCqD,SAAW,WAAY,OAAS,cAAerD,KAAKyB,MAAQ,GAAI,KAAMF,KAAK,MAI/E,QACIjC,WAAaA"}
|
||||
{"version":3,"file":"tag.js","sources":["../../src/mvc/tag.js"],"names":["define","baseMVC","_l","TagsEditor","Backbone","View","extend","LoggableMixin","HiddenUntilActivatedViewMixin","tagName","className","select_width","events","initialize","options","this","show_editor","label","usePrompt","workflow_mode","click","keydown","hiddenUntilActivated","$activator","render","self","$el","html","_workflowTemplate","_defaultTemplate","$input","select2","placeholder","width","tags","_getTagsUsed","_setUpBehaviors","_hashToName","tag","startsWith","slice","_nameToHash","_renderEditor","join","_renderTags","keydownHandler","e","which","hideEditor","showEditor","tagsToCSV","model","get","addButton","renderedArray","_","each","indexOf","renderString","push","length","tagsArray","isArray","isEmpty","map","escape","sort","find","Galaxy","user","on","event","val","save","added","_addNewTagToTagsUsed","text","newTag","tagsUsed","contains","set","remove","off","stopListening","prototype","call","toString"],"mappings":"AAAAA,QACI,eACA,sBACD,SAAUC,EAASC,GAMtB,GAAIC,GAAaC,SAASC,KACjBC,OAAQL,EAAQM,eAChBD,OAAQL,EAAQO,+BAAgCF,QAErDG,QAAe,MACfC,UAAe,eACfC,aAAe,OACfC,UAGAC,WAAa,SAAUC,GAGnBC,KAAKC,aAAc,EAEfD,KAAKE,MADLH,EAAQI,aAAc,EACT,GAEA,yBAA2BhB,EAAI,QAAW,WAE3Da,KAAKI,cAAgBL,EAAQK,gBAAiB,EAC1CJ,KAAKI,gBACLJ,KAAKH,OAAOQ,MAAQ,aACpBL,KAAKH,OAAOS,QAAU,kBAE1BN,KAAKO,qBAAsBR,EAAQS,WAAYT,IAInDU,OAAS,WACL,GAAIC,GAAOV,IAgBX,OAdGA,MAAKW,IAAIC,KADRZ,KAAKI,cACQJ,KAAKa,oBAEJb,KAAKc,oBAEvBd,KAAKe,SAASC,SACVC,YAAc,WACdC,MAAclB,KAAKI,cAAgBJ,KAAKkB,MAAQlB,KAAKJ,aACrDuB,KAAO,WAEH,MAAOT,GAAKU,kBAIpBpB,KAAKqB,kBACErB,MAGXsB,YAAa,SAASC,GAClB,MAAIA,GAAIC,WAAW,KACR,QAAUD,EAAIE,MAAM,GAExBF,GAGXG,YAAa,SAASH,GAIlB,MAHIA,GAAIC,WAAW,WACfD,EAAM,IAAMA,EAAIE,MAAM,IAEnBF,GAIXT,iBAAmB,WACf,OACId,KAAKE,MACLF,KAAK2B,iBACPC,KAAM,KAGZf,kBAAoB,WAEhB,OACIb,KAAKC,YAAcD,KAAK2B,gBAAkB3B,KAAK6B,eACjDD,KAAM,MAGZE,eAAiB,SAAUC,GACvB,OAAQA,EAAEC,OAEN,IAAK,IAEDhC,KAAKiC,eAKjBC,WAAY,WACRlC,KAAKC,aAAc,EACnBD,KAAKS,UAGTwB,WAAY,WACRjC,KAAKC,aAAc,EACnBD,KAAKS,UAGTkB,cAAe,WAEX,MAAO,oCAAsC3B,KAAKmC,YAAc,OAGpEN,YAAc,WACV,GAAIV,GAAOnB,KAAKoC,MAAMC,IAAI,QACtBC,EAAY,oCACZC,IAUJ,OATAC,GAAEC,KAAKtB,EAAM,SAASI,GAClBA,EAA8B,GAAxBA,EAAImB,QAAQ,SAAgBnB,EAAIE,MAAM,GAAKF,CACjD,IAAIoB,GAAe,kCAAoCpB,EAAM,SAC7DgB,GAAcK,KAAMD,KAEK,IAAzBJ,EAAcM,QAEdN,EAAcK,KAAK,YAAcN,EAAY,8CAE1CC,EAAcX,KAAK,MAI9BO,UAAY,WACR,GAAIzB,GAAOV,KACP8C,EAAY9C,KAAKoC,MAAMC,IAAK,OAChC,QAAKG,EAAEO,QAASD,IAAeN,EAAEQ,QAASF,GAC/B,GAEJA,EAAUG,IAAK,SAAU1B,GAC5B,MAAOiB,GAAEU,OAAQxC,EAAKgB,YAAaH,MACpC4B,OAAOvB,KAAM,MAIpBb,OAAS,WACL,MAAOf,MAAKW,IAAIyC,KAAM,qBAI1BhC,aAAe,WAEX,GAAIV,GAAOV,IACX,OAAOwC,GAAES,IAAII,OAAOC,KAAKjB,IAAK,aAAe3B,EAAKgB,cAItDL,gBAAkB,WACd,GAAIX,GAAOV,IACXA,MAAKe,SAASwC,GAAI,SAAU,SAAUC,GAElCA,EAAMC,IAAMjB,EAAES,IAAIO,EAAMC,IAAK/C,EAAKY,aAElCZ,EAAK0B,MAAMsB,MAAOvC,KAAMqC,EAAMC,MAE1BD,EAAMG,OAGNjD,EAAKkD,qBAAsBJ,EAAMG,MAAME,KAAO,OAQ1DD,qBAAuB,SAAUE,GAE7B,GAAIC,GAAWV,OAAOC,KAAKjB,IAAK,YAC3BG,GAAEwB,SAAUD,EAAUD,KACvBC,EAASnB,KAAMkB,GACfC,EAASZ,OACTE,OAAOC,KAAKW,IAAK,YAAaF,KAKtCG,OAAS,WACLlE,KAAKe,OAAOoD,MACZnE,KAAKoE,cAAepE,KAAKoC,OACzB/C,SAASC,KAAK+E,UAAUH,OAAOI,KAAMtE,OAIzCuE,SAAW,WAAY,OAAS,cAAevE,KAAKoC,MAAQ,GAAI,KAAMR,KAAK,MAG/E,QACIxC,WAAaA"}
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"workflow-model.js","sources":["../../../src/mvc/workflow/workflow-model.js"],"names":["define","baseMVC","logNamespace","WorkflowItem","Backbone","Model","extend","LoggableMixin","_logNamespace","urlRoot","toJSON","workflow","this","attributes","WorkflowCollection","Collection","model","url"],"mappings":"AAAAA,QACI,gBACD,SAAUC,GAIb,GAAIC,GAAe,WAMfC,EAAeC,SAASC,MAAMC,OAAQL,EAAQM,eAAgBD,QAC9DE,cAAgBN,EAEhBO,QAAS,iBAETC,OAAQ,WAER,OAAQC,SAAWC,KAAKC,eAUxBC,EAAqBV,SAASW,WAAWT,QACzCU,MAAOb,EACPc,IAAK,kBAMT,QACId,aAAcA,EACdW,mBAAoBA"}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
webpackJsonp([5],[function(e,i,n){(function(e,i){var a=n(1),t=a,r=n(51).GalaxyApp,o=n(6),l=n(52);window.app=function(n,c){window.Galaxy=new r(n,c),Galaxy.debug("login app");var p=encodeURI(n.redirect);if(!n.show_welcome_with_login){var d=a.param({use_panels:"True",redirect:p});return void(window.location.href=Galaxy.root+"user/login?"+d)}var w=e.View.extend({initialize:function(i){this.page=i,this.model=new e.Model({title:o("Login required")}),this.setElement(this._template())},render:function(){this.page.$("#galaxy_main").prop("src",n.welcome_url)},_template:function(){var e=n.root+"user/login?"+t.param({redirect:p});return'<iframe src="'+e+'" frameborder="0" style="width: 100%; height: 100%;"/>'}});t(function(){Galaxy.page=new l.View(i.extend(n,{Right:w}))})}}).call(i,n(2),n(3))}]);
|
||||
webpackJsonp([5],[function(e,i,n){(function(e,i){var a=n(1),t=a,r=n(52).GalaxyApp,o=n(6),l=n(53);window.app=function(n,c){window.Galaxy=new r(n,c),Galaxy.debug("login app");var p=encodeURI(n.redirect);if(!n.show_welcome_with_login){var d=a.param({use_panels:"True",redirect:p});return void(window.location.href=Galaxy.root+"user/login?"+d)}var w=e.View.extend({initialize:function(i){this.page=i,this.model=new e.Model({title:o("Login required")}),this.setElement(this._template())},render:function(){this.page.$("#galaxy_main").prop("src",n.welcome_url)},_template:function(){var e=n.root+"user/login?"+t.param({redirect:p});return'<iframe src="'+e+'" frameborder="0" style="width: 100%; height: 100%;"/>'}});t(function(){Galaxy.page=new l.View(i.extend(n,{Right:w}))})}}).call(i,n(2),n(3))}]);
|
||||
//# sourceMappingURL=login.bundled.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":["webpack:///login.bundled.js","webpack:///./galaxy/scripts/apps/login.js"],"names":["webpackJsonp","module","exports","__webpack_require__","Backbone","_","jQuery","$","GalaxyApp","_l","Page","window","app","options","bootstrapped","Galaxy","debug","redirect","encodeURI","show_welcome_with_login","params","param","use_panels","location","href","root","LoginPage","View","extend","initialize","page","this","model","Model","title","setElement","_template","render","prop","welcome_url","login_url","Right","call"],"mappings":"AAAAA,cAAc,IAER,SAAUC,EAAQC,EAASC,ICFjC,SAAAC,EAAAC,GAAA,GAAIC,GAASH,EAAS,GAClBI,EAAID,EACJE,EAAYL,EAAS,IAAWK,UAChCC,EAAKN,EAAS,GACdO,EAAOP,EAAS,GAEpBQ,QAAOC,IAAM,SAAcC,EAASC,GAChCH,OAAOI,OAAS,GAAIP,GAAWK,EAASC,GACxCC,OAAOC,MAAO,YACd,IAAIC,GAAWC,UAAWL,EAAQI,SAIlC,KAAKJ,EAAQM,wBAAyB,CAClC,GAAIC,GAASd,EAAOe,OAAQC,WAAa,OAAQL,SAAWA,GAE5D,aADAN,OAAOY,SAASC,KAAOT,OAAOU,KAAO,cAAgBL,GAIzD,GAAIM,GAAYtB,EAASuB,KAAKC,QAC1BC,WAAY,SAAUC,GAClBC,KAAKD,KAAOA,EACZC,KAAKC,MAAQ,GAAI5B,GAAS6B,OAAQC,MAAQzB,EAAI,oBAC9CsB,KAAKI,WAAYJ,KAAKK,cAE1BC,OAAQ,WACJN,KAAKD,KAAKvB,EAAG,gBAAiB+B,KAAM,MAAOzB,EAAQ0B,cAEvDH,UAAY,WACR,GAAII,GAAY3B,EAAQY,KAAO,cAAgBlB,EAAEc,OAASJ,SAAWA,GACrE,OAAO,gBAAkBuB,EAAY,2DAI7CjC,GAAE,WACEQ,OAAOe,KAAO,GAAIpB,GAAKiB,KAAMtB,EAAEuB,OAAQf,GACnC4B,MAAQf,UDQUgB,KAAKxC,EAASC,EAAoB,GAAIA,EAAoB","file":"login.bundled.js","sourcesContent":["webpackJsonp([5],[\n/* 0 */\n/***/ (function(module, exports, __webpack_require__) {\n\n\t/* WEBPACK VAR INJECTION */(function(Backbone, _) {var jQuery = __webpack_require__(1),\n\t $ = jQuery,\n\t GalaxyApp = __webpack_require__(51).GalaxyApp,\n\t _l = __webpack_require__(6),\n\t Page = __webpack_require__(52);\n\t\n\twindow.app = function app(options, bootstrapped) {\n\t window.Galaxy = new GalaxyApp(options, bootstrapped);\n\t Galaxy.debug('login app');\n\t var redirect = encodeURI(options.redirect);\n\t\n\t // TODO: remove iframe for user login (at least) and render login page from here\n\t // then remove this redirect\n\t if (!options.show_welcome_with_login) {\n\t var params = jQuery.param({ use_panels: 'True', redirect: redirect });\n\t window.location.href = Galaxy.root + 'user/login?' + params;\n\t return;\n\t }\n\t\n\t var LoginPage = Backbone.View.extend({\n\t initialize: function (page) {\n\t this.page = page;\n\t this.model = new Backbone.Model({ title: _l('Login required') });\n\t this.setElement(this._template());\n\t },\n\t render: function () {\n\t this.page.$('#galaxy_main').prop('src', options.welcome_url);\n\t },\n\t _template: function () {\n\t var login_url = options.root + 'user/login?' + $.param({ redirect: redirect });\n\t return '<iframe src=\"' + login_url + '\" frameborder=\"0\" style=\"width: 100%; height: 100%;\"/>';\n\t }\n\t });\n\t\n\t $(function () {\n\t Galaxy.page = new Page.View(_.extend(options, {\n\t Right: LoginPage\n\t }));\n\t });\n\t};\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(3)))\n\n/***/ })\n]);\n\n\n// WEBPACK FOOTER //\n// login.bundled.js","var jQuery = require( 'jquery' ),\n $ = jQuery,\n GalaxyApp = require( 'galaxy' ).GalaxyApp,\n _l = require( 'utils/localization' ),\n Page = require( 'layout/page' );\n\nwindow.app = function app( options, bootstrapped ){\n window.Galaxy = new GalaxyApp( options, bootstrapped );\n Galaxy.debug( 'login app' );\n var redirect = encodeURI( options.redirect );\n\n // TODO: remove iframe for user login (at least) and render login page from here\n // then remove this redirect\n if( !options.show_welcome_with_login ){\n var params = jQuery.param({ use_panels : 'True', redirect : redirect });\n window.location.href = Galaxy.root + 'user/login?' + params;\n return;\n }\n\n var LoginPage = Backbone.View.extend({\n initialize: function( page ) {\n this.page = page;\n this.model = new Backbone.Model({ title : _l( 'Login required' ) } );\n this.setElement( this._template() );\n },\n render: function() {\n this.page.$( '#galaxy_main' ).prop( 'src', options.welcome_url );\n },\n _template : function() {\n var login_url = options.root + 'user/login?' + $.param( { redirect : redirect } );\n return '<iframe src=\"' + login_url + '\" frameborder=\"0\" style=\"width: 100%; height: 100%;\"/>';\n }\n });\n\n $(function(){\n Galaxy.page = new Page.View( _.extend( options, {\n Right : LoginPage\n } ) );\n });\n};\n\n\n\n// WEBPACK FOOTER //\n// ./galaxy/scripts/apps/login.js"],"sourceRoot":""}
|
||||
{"version":3,"sources":["webpack:///login.bundled.js","webpack:///./galaxy/scripts/apps/login.js"],"names":["webpackJsonp","module","exports","__webpack_require__","Backbone","_","jQuery","$","GalaxyApp","_l","Page","window","app","options","bootstrapped","Galaxy","debug","redirect","encodeURI","show_welcome_with_login","params","param","use_panels","location","href","root","LoginPage","View","extend","initialize","page","this","model","Model","title","setElement","_template","render","prop","welcome_url","login_url","Right","call"],"mappings":"AAAAA,cAAc,IAER,SAAUC,EAAQC,EAASC,ICFjC,SAAAC,EAAAC,GAAA,GAAIC,GAASH,EAAS,GAClBI,EAAID,EACJE,EAAYL,EAAS,IAAWK,UAChCC,EAAKN,EAAS,GACdO,EAAOP,EAAS,GAEpBQ,QAAOC,IAAM,SAAcC,EAASC,GAChCH,OAAOI,OAAS,GAAIP,GAAWK,EAASC,GACxCC,OAAOC,MAAO,YACd,IAAIC,GAAWC,UAAWL,EAAQI,SAIlC,KAAKJ,EAAQM,wBAAyB,CAClC,GAAIC,GAASd,EAAOe,OAAQC,WAAa,OAAQL,SAAWA,GAE5D,aADAN,OAAOY,SAASC,KAAOT,OAAOU,KAAO,cAAgBL,GAIzD,GAAIM,GAAYtB,EAASuB,KAAKC,QAC1BC,WAAY,SAAUC,GAClBC,KAAKD,KAAOA,EACZC,KAAKC,MAAQ,GAAI5B,GAAS6B,OAAQC,MAAQzB,EAAI,oBAC9CsB,KAAKI,WAAYJ,KAAKK,cAE1BC,OAAQ,WACJN,KAAKD,KAAKvB,EAAG,gBAAiB+B,KAAM,MAAOzB,EAAQ0B,cAEvDH,UAAY,WACR,GAAII,GAAY3B,EAAQY,KAAO,cAAgBlB,EAAEc,OAASJ,SAAWA,GACrE,OAAO,gBAAkBuB,EAAY,2DAI7CjC,GAAE,WACEQ,OAAOe,KAAO,GAAIpB,GAAKiB,KAAMtB,EAAEuB,OAAQf,GACnC4B,MAAQf,UDQUgB,KAAKxC,EAASC,EAAoB,GAAIA,EAAoB","file":"login.bundled.js","sourcesContent":["webpackJsonp([5],[\n/* 0 */\n/***/ (function(module, exports, __webpack_require__) {\n\n\t/* WEBPACK VAR INJECTION */(function(Backbone, _) {var jQuery = __webpack_require__(1),\n\t $ = jQuery,\n\t GalaxyApp = __webpack_require__(52).GalaxyApp,\n\t _l = __webpack_require__(6),\n\t Page = __webpack_require__(53);\n\t\n\twindow.app = function app(options, bootstrapped) {\n\t window.Galaxy = new GalaxyApp(options, bootstrapped);\n\t Galaxy.debug('login app');\n\t var redirect = encodeURI(options.redirect);\n\t\n\t // TODO: remove iframe for user login (at least) and render login page from here\n\t // then remove this redirect\n\t if (!options.show_welcome_with_login) {\n\t var params = jQuery.param({ use_panels: 'True', redirect: redirect });\n\t window.location.href = Galaxy.root + 'user/login?' + params;\n\t return;\n\t }\n\t\n\t var LoginPage = Backbone.View.extend({\n\t initialize: function (page) {\n\t this.page = page;\n\t this.model = new Backbone.Model({ title: _l('Login required') });\n\t this.setElement(this._template());\n\t },\n\t render: function () {\n\t this.page.$('#galaxy_main').prop('src', options.welcome_url);\n\t },\n\t _template: function () {\n\t var login_url = options.root + 'user/login?' + $.param({ redirect: redirect });\n\t return '<iframe src=\"' + login_url + '\" frameborder=\"0\" style=\"width: 100%; height: 100%;\"/>';\n\t }\n\t });\n\t\n\t $(function () {\n\t Galaxy.page = new Page.View(_.extend(options, {\n\t Right: LoginPage\n\t }));\n\t });\n\t};\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(3)))\n\n/***/ })\n]);\n\n\n// WEBPACK FOOTER //\n// login.bundled.js","var jQuery = require( 'jquery' ),\n $ = jQuery,\n GalaxyApp = require( 'galaxy' ).GalaxyApp,\n _l = require( 'utils/localization' ),\n Page = require( 'layout/page' );\n\nwindow.app = function app( options, bootstrapped ){\n window.Galaxy = new GalaxyApp( options, bootstrapped );\n Galaxy.debug( 'login app' );\n var redirect = encodeURI( options.redirect );\n\n // TODO: remove iframe for user login (at least) and render login page from here\n // then remove this redirect\n if( !options.show_welcome_with_login ){\n var params = jQuery.param({ use_panels : 'True', redirect : redirect });\n window.location.href = Galaxy.root + 'user/login?' + params;\n return;\n }\n\n var LoginPage = Backbone.View.extend({\n initialize: function( page ) {\n this.page = page;\n this.model = new Backbone.Model({ title : _l( 'Login required' ) } );\n this.setElement( this._template() );\n },\n render: function() {\n this.page.$( '#galaxy_main' ).prop( 'src', options.welcome_url );\n },\n _template : function() {\n var login_url = options.root + 'user/login?' + $.param( { redirect : redirect } );\n return '<iframe src=\"' + login_url + '\" frameborder=\"0\" style=\"width: 100%; height: 100%;\"/>';\n }\n });\n\n $(function(){\n Galaxy.page = new Page.View( _.extend( options, {\n Right : LoginPage\n } ) );\n });\n};\n\n\n\n// WEBPACK FOOTER //\n// ./galaxy/scripts/apps/login.js"],"sourceRoot":""}
|
||||
@@ -1,2 +1,2 @@
|
||||
define(["mvc/base-mvc","utils/localization"],function(a,b){var c=Backbone.View.extend(a.LoggableMixin).extend(a.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"tags-display",initialize:function(a){this.label=a.usePrompt===!1?"":'<label class="prompt">'+b("Tags")+"</label>",this.hiddenUntilActivated(a.$activator,a)},render:function(){var a=this;return this.$el.html(this._template()),this.$input().select2({placeholder:"Add tags",width:"100%",tags:function(){return a._getTagsUsed()}}),this._setUpBehaviors(),this},_hashToName:function(a){return a.startsWith("#")?"name:"+a.slice(1):a},_nameToHash:function(a){return a.startsWith("name:")&&(a="#"+a.slice(5)),a},_template:function(){return[this.label,'<input class="tags-input" value="',this.tagsToCSV(),'" />'].join("")},tagsToCSV:function(){var a=this,b=this.model.get("tags");return!_.isArray(b)||_.isEmpty(b)?"":b.map(function(b){return _.escape(a._nameToHash(b))}).sort().join(",")},$input:function(){return this.$el.find("input.tags-input")},_getTagsUsed:function(){var a=this;return _.map(Galaxy.user.get("tags_used"),a._nameToHash)},_setUpBehaviors:function(){var a=this;this.$input().on("change",function(b){b.val=_.map(b.val,a._hashToName),a.model.save({tags:b.val}),b.added&&a._addNewTagToTagsUsed(b.added.text+"")})},_addNewTagToTagsUsed:function(a){var b=Galaxy.user.get("tags_used");_.contains(b,a)||(b.push(a),b.sort(),Galaxy.user.set("tags_used",b))},remove:function(){this.$input.off(),this.stopListening(this.model),Backbone.View.prototype.remove.call(this)},toString:function(){return["TagsEditor(",this.model+"",")"].join("")}});return{TagsEditor:c}});
|
||||
define(["mvc/base-mvc","utils/localization"],function(a,b){var c=Backbone.View.extend(a.LoggableMixin).extend(a.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"tags-display",select_width:"100%",events:{},initialize:function(a){this.show_editor=!1,this.label=a.usePrompt===!1?"":'<label class="prompt">'+b("Tags")+"</label>",this.workflow_mode=a.workflow_mode||!1,this.workflow_mode&&(this.events.click="showEditor",this.events.keydown="keydownHandler"),this.hiddenUntilActivated(a.$activator,a)},render:function(){var a=this;return this.$el.html(this.workflow_mode?this._workflowTemplate():this._defaultTemplate()),this.$input().select2({placeholder:"Add tags",width:this.workflow_mode?this.width:this.select_width,tags:function(){return a._getTagsUsed()}}),this._setUpBehaviors(),this},_hashToName:function(a){return a.startsWith("#")?"name:"+a.slice(1):a},_nameToHash:function(a){return a.startsWith("name:")&&(a="#"+a.slice(5)),a},_defaultTemplate:function(){return[this.label,this._renderEditor()].join("")},_workflowTemplate:function(){return[this.show_editor?this._renderEditor():this._renderTags()].join(" ")},keydownHandler:function(a){switch(a.which){case 27:this.hideEditor()}},showEditor:function(){this.show_editor=!0,this.render()},hideEditor:function(){this.show_editor=!1,this.render()},_renderEditor:function(){return'<input class="tags-input" value="'+this.tagsToCSV()+'"/>'},_renderTags:function(){var a=this.model.get("tags"),b="static/images/fugue/tag--plus.png",c=[];return _.each(a,function(a){a=0==a.indexOf("name:")?a.slice(5):a;var b='<span class="label label-info">'+a+"</span>";c.push(b)}),0===c.length&&c.push("<img src="+b+' class="add-tag-button" title="Add tags"/>'),c.join(" ")},tagsToCSV:function(){var a=this,b=this.model.get("tags");return!_.isArray(b)||_.isEmpty(b)?"":b.map(function(b){return _.escape(a._nameToHash(b))}).sort().join(",")},$input:function(){return this.$el.find("input.tags-input")},_getTagsUsed:function(){var a=this;return _.map(Galaxy.user.get("tags_used"),a._nameToHash)},_setUpBehaviors:function(){var a=this;this.$input().on("change",function(b){b.val=_.map(b.val,a._hashToName),a.model.save({tags:b.val}),b.added&&a._addNewTagToTagsUsed(b.added.text+"")})},_addNewTagToTagsUsed:function(a){var b=Galaxy.user.get("tags_used");_.contains(b,a)||(b.push(a),b.sort(),Galaxy.user.set("tags_used",b))},remove:function(){this.$input.off(),this.stopListening(this.model),Backbone.View.prototype.remove.call(this)},toString:function(){return["TagsEditor(",this.model+"",")"].join("")}});return{TagsEditor:c}});
|
||||
//# sourceMappingURL=../../maps/mvc/tag.js.map
|
||||
@@ -0,0 +1,2 @@
|
||||
define(["mvc/base-mvc"],function(a){var b="workflow",c=Backbone.Model.extend(a.LoggableMixin).extend({_logNamespace:b,urlRoot:"/api/workflows",toJSON:function(){return{workflow:this.attributes}}}),d=Backbone.Collection.extend({model:c,url:"/api/workflows"});return{WorkflowItem:c,WorkflowCollection:d}});
|
||||
//# sourceMappingURL=../../../maps/mvc/workflow/workflow-model.js.map
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -28,7 +28,7 @@ class NullTourCallback(object):
|
||||
pass
|
||||
|
||||
|
||||
def retry_call_during_transitions(f, attempts=5, sleep=.1):
|
||||
def retry_call_during_transitions(f, attempts=5, sleep=.1, exception_check=exception_indicates_stale_element):
|
||||
previous_attempts = 0
|
||||
while True:
|
||||
try:
|
||||
@@ -37,18 +37,18 @@ def retry_call_during_transitions(f, attempts=5, sleep=.1):
|
||||
if previous_attempts > attempts:
|
||||
raise
|
||||
|
||||
if not exception_indicates_stale_element(e):
|
||||
if not exception_check(e):
|
||||
raise
|
||||
|
||||
time.sleep(sleep)
|
||||
previous_attempts += 1
|
||||
|
||||
|
||||
def retry_during_transitions(f, attempts=5, sleep=.1):
|
||||
def retry_during_transitions(f, attempts=5, sleep=.1, exception_check=exception_indicates_stale_element):
|
||||
|
||||
@wraps(f)
|
||||
def _retry(*args, **kwds):
|
||||
retry_call_during_transitions(partial(f, *args, **kwds), attempts=attempts, sleep=sleep)
|
||||
return retry_call_during_transitions(partial(f, *args, **kwds), attempts=attempts, sleep=sleep, exception_check=exception_check)
|
||||
|
||||
return _retry
|
||||
|
||||
@@ -395,12 +395,26 @@ class NavigatesGalaxy(HasDriver):
|
||||
|
||||
def workflow_index_table_elements(self):
|
||||
self.wait_for_selector_visible("tbody.workflow-search")
|
||||
table_elements = self.driver.find_elements_by_css_selector("tbody.workflow-search > tr")
|
||||
table_elements = self.driver.find_elements_by_css_selector("tbody.workflow-search > tr:not([style*='display: none'])")
|
||||
return table_elements
|
||||
|
||||
def workflow_index_table_row(self, workflow_index=0):
|
||||
return self.workflow_index_table_elements()[workflow_index]
|
||||
|
||||
@retry_during_transitions
|
||||
def workflow_index_click_search(self):
|
||||
search_element = self.wait_for_selector_clickable("input.search-wf")
|
||||
search_element.click()
|
||||
return search_element
|
||||
|
||||
def workflow_index_rename(self, new_name, workflow_index=0):
|
||||
self.workflow_index_click_option("Rename", workflow_index=workflow_index)
|
||||
alert = self.driver.switch_to.alert
|
||||
alert.send_keys(new_name)
|
||||
alert.accept()
|
||||
|
||||
def workflow_index_click_option(self, option_title, workflow_index=0):
|
||||
table_elements = self.workflow_index_table_elements()
|
||||
workflow_row = table_elements[workflow_index]
|
||||
workflow_row = self.workflow_index_table_row(workflow_index=workflow_index)
|
||||
workflow_button = workflow_row.find_element_by_css_selector(".menubutton")
|
||||
workflow_button.click()
|
||||
menu_element = self.wait_for_selector_visible("ul.action-dpd")
|
||||
@@ -415,6 +429,35 @@ class NavigatesGalaxy(HasDriver):
|
||||
if not found_option:
|
||||
raise AssertionError("Failed to find workflow action option with title [%s]" % option_title)
|
||||
|
||||
def workflow_index_click_tag_display(self, workflow_index=0):
|
||||
workflow_row_element = self.workflow_index_table_row(workflow_index)
|
||||
tag_display = workflow_row_element.find_element_by_css_selector(".tags-display")
|
||||
tag_display.click()
|
||||
|
||||
def workflow_index_tags(self, workflow_index=0):
|
||||
workflow_row_element = self.workflow_index_table_row(workflow_index)
|
||||
tag_display = workflow_row_element.find_element_by_css_selector(".tags-display")
|
||||
tag_spans = tag_display.find_elements_by_css_selector("span.label")
|
||||
tags = []
|
||||
for tag_span in tag_spans:
|
||||
tags.append(tag_span.text)
|
||||
return tags
|
||||
|
||||
def workflow_sharing_click_publish(self):
|
||||
button = self.wait_for_selector_clickable("input[name='make_accessible_and_publish']")
|
||||
button.click()
|
||||
|
||||
def tagging_add(self, tags, auto_closes=True, parent_selector=""):
|
||||
|
||||
for i, tag in enumerate(tags):
|
||||
if auto_closes or i == 0:
|
||||
tag_area = parent_selector + ".tags-input input[type='text']"
|
||||
tag_area = self.wait_for_selector_clickable(tag_area)
|
||||
tag_area.click()
|
||||
|
||||
tag_area.send_keys(tag)
|
||||
self.send_enter(tag_area)
|
||||
|
||||
def workflow_run_submit(self):
|
||||
button = self.wait_for_selector(".ui-form-header button")
|
||||
button.click()
|
||||
|
||||
@@ -6,16 +6,17 @@ import datetime
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
import traceback
|
||||
|
||||
from functools import wraps
|
||||
from functools import partial, wraps
|
||||
|
||||
import requests
|
||||
|
||||
from galaxy_selenium import (
|
||||
driver_factory,
|
||||
)
|
||||
from galaxy_selenium.navigates_galaxy import NavigatesGalaxy
|
||||
from galaxy_selenium.navigates_galaxy import NavigatesGalaxy, retry_during_transitions
|
||||
|
||||
try:
|
||||
from pyvirtualdisplay import Display
|
||||
@@ -112,6 +113,9 @@ def selenium_test(f):
|
||||
return func_wrapper
|
||||
|
||||
|
||||
retry_assertion_during_transitions = partial(retry_during_transitions, exception_check=lambda e: isinstance(e, AssertionError))
|
||||
|
||||
|
||||
class SeleniumTestCase(FunctionalTestCase, NavigatesGalaxy):
|
||||
|
||||
framework_tool_and_types = True
|
||||
@@ -149,6 +153,9 @@ class SeleniumTestCase(FunctionalTestCase, NavigatesGalaxy):
|
||||
def setup_driver_and_session(self):
|
||||
self.display = driver_factory.virtual_display_if_enabled(headless_selenium())
|
||||
self.driver = get_driver()
|
||||
# New workflow index page does not degrade well to smaller sizes, needed
|
||||
# to increase this.
|
||||
self.driver.set_window_size(1280, 900)
|
||||
|
||||
if self.ensure_registered:
|
||||
self.register()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from .framework import (
|
||||
retry_assertion_during_transitions,
|
||||
selenium_test,
|
||||
SeleniumTestCase
|
||||
SeleniumTestCase,
|
||||
)
|
||||
|
||||
|
||||
@@ -33,16 +34,82 @@ class WorkflowManagementTestCase(SeleniumTestCase):
|
||||
def test_rename(self):
|
||||
self.workflow_index_open()
|
||||
self._workflow_import_from_url()
|
||||
self.workflow_index_click_option("Rename")
|
||||
rename_form_element = self.wait_for_selector("form")
|
||||
self.fill(rename_form_element, {
|
||||
"new_name": "CoolNewName"
|
||||
})
|
||||
self.click_submit(rename_form_element)
|
||||
self.workflow_index_rename("CoolNewName")
|
||||
|
||||
table_elements = self.workflow_index_table_elements()
|
||||
renamed_workflow_button = table_elements[0].find_element_by_css_selector(".menubutton")
|
||||
assert 'CoolNewName' in renamed_workflow_button.text, renamed_workflow_button.text
|
||||
@retry_assertion_during_transitions
|
||||
def check_name():
|
||||
row_element = self.workflow_index_table_row()
|
||||
renamed_workflow_button = row_element.find_element_by_css_selector(".menubutton")
|
||||
assert 'CoolNewName' in renamed_workflow_button.text, renamed_workflow_button.text
|
||||
|
||||
check_name()
|
||||
|
||||
@selenium_test
|
||||
def test_download(self):
|
||||
self.workflow_index_open()
|
||||
self._workflow_import_from_url()
|
||||
# TODO: fill this test out - getting downloaded files in general through Selenium is a bit tough,
|
||||
# going through the motions though should catch a couple potential problems.
|
||||
self.workflow_index_click_option("Download")
|
||||
|
||||
@selenium_test
|
||||
def test_tagging(self):
|
||||
self.workflow_index_open()
|
||||
self._workflow_import_from_url()
|
||||
|
||||
self.workflow_index_click_tag_display()
|
||||
self.tagging_add(["cooltag"])
|
||||
|
||||
@retry_assertion_during_transitions
|
||||
def check_tags():
|
||||
self.assertEqual(self.workflow_index_tags(), ["cooltag"])
|
||||
|
||||
check_tags()
|
||||
|
||||
@selenium_test
|
||||
def test_index_search(self):
|
||||
self.workflow_index_open()
|
||||
self._workflow_import_from_url()
|
||||
self.workflow_index_rename("searchforthis")
|
||||
self._assert_showing_n_workflows(1)
|
||||
|
||||
search_box = self.workflow_index_click_search()
|
||||
search_box.send_keys("doesnotmatch")
|
||||
self._assert_showing_n_workflows(0)
|
||||
|
||||
# Prevent stale element textbox by re-fetching, seems to be
|
||||
# needed but I don't understand why exactly. -John
|
||||
search_box = self.workflow_index_click_search()
|
||||
search_box.clear()
|
||||
self.send_enter(search_box)
|
||||
self._assert_showing_n_workflows(1)
|
||||
|
||||
search_box = self.workflow_index_click_search()
|
||||
search_box.send_keys("searchforthis")
|
||||
self.send_enter(search_box)
|
||||
self._assert_showing_n_workflows(1)
|
||||
|
||||
@selenium_test
|
||||
def test_publishing_display(self):
|
||||
self.workflow_index_open()
|
||||
self._workflow_import_from_url()
|
||||
self.workflow_index_rename("managementesttopublish")
|
||||
|
||||
row_element = self.workflow_index_table_row()
|
||||
columns = row_element.find_elements_by_css_selector("td")
|
||||
assert columns[4].text == "No"
|
||||
|
||||
self.workflow_index_click_option("Share")
|
||||
self.workflow_sharing_click_publish()
|
||||
|
||||
self.workflow_index_open()
|
||||
row_element = self.workflow_index_table_row()
|
||||
columns = row_element.find_elements_by_css_selector("td")
|
||||
assert columns[4].text == "Yes"
|
||||
|
||||
@retry_assertion_during_transitions
|
||||
def _assert_showing_n_workflows(self, n):
|
||||
self.assertEqual(len(self.workflow_index_table_elements()), n)
|
||||
|
||||
def _workflow_import_from_url(self):
|
||||
element = self.wait_for_selector_clickable(self.test_data["selectors"]["workflows"]["import_button"])
|
||||
|
||||
Reference in New Issue
Block a user