mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #6925 from jmchilton/workflow_step_parameters_3
Add workflow step parameters to workflow editor (try 3)
This commit is contained in:
@@ -84,7 +84,9 @@ export var Manager = Backbone.Model.extend({
|
||||
if (field && field.value) {
|
||||
value = field.value();
|
||||
if (input.ignore === undefined || input.ignore != value) {
|
||||
if (field.collapsed && input.collapsible_value) {
|
||||
if (field.collapsed && field.connected) {
|
||||
value = { __class__: "ConnectedValue" };
|
||||
} else if (field.collapsed && input.collapsible_value) {
|
||||
value = input.collapsible_value;
|
||||
}
|
||||
add(flat_id, input.id, value);
|
||||
|
||||
@@ -25,6 +25,7 @@ export default Backbone.View.extend({
|
||||
this.$collapsible = this.$(".ui-form-collapsible");
|
||||
this.$collapsible_text = this.$(".ui-form-collapsible-text");
|
||||
this.$collapsible_icon = this.$(".ui-form-collapsible-icon");
|
||||
this.$connected_icon = this.$(".ui-form-connected-icon");
|
||||
this.$title = this.$(".ui-form-title");
|
||||
this.$title_text = this.$(".ui-form-title-text");
|
||||
this.$error_text = this.$(".ui-form-error-text");
|
||||
@@ -36,16 +37,35 @@ export default Backbone.View.extend({
|
||||
|
||||
// decide wether to expand or collapse fields
|
||||
var collapsible_value = this.model.get("collapsible_value");
|
||||
var value = JSON.stringify(this.model.get("value"));
|
||||
var connected = value == JSON.stringify({ __class__: "ConnectedValue" });
|
||||
this.field.connected = connected;
|
||||
this.field.collapsed =
|
||||
collapsible_value !== undefined &&
|
||||
JSON.stringify(this.model.get("value")) == JSON.stringify(collapsible_value);
|
||||
this.field.connected ||
|
||||
(collapsible_value !== undefined &&
|
||||
JSON.stringify(this.model.get("value")) == JSON.stringify(collapsible_value));
|
||||
this.listenTo(this.model, "change", this.render, this);
|
||||
this.render();
|
||||
|
||||
// add click handler
|
||||
var self = this;
|
||||
this.$collapsible.on("click", () => {
|
||||
this.$collapsible_icon.on("click", () => {
|
||||
if (self.field.connected) {
|
||||
return;
|
||||
}
|
||||
self.field.collapsed = !self.field.collapsed;
|
||||
if (self.field.collapsed) {
|
||||
self.field.connected = false;
|
||||
}
|
||||
app.trigger && app.trigger("change");
|
||||
self.render();
|
||||
});
|
||||
this.$connected_icon.on("click", () => {
|
||||
self.field.connected = !self.field.connected;
|
||||
self.field.collapsed = self.field.connected;
|
||||
if (!self.field.connected) {
|
||||
this.model.set("value", null);
|
||||
}
|
||||
app.trigger && app.trigger("change");
|
||||
self.render();
|
||||
});
|
||||
@@ -108,21 +128,41 @@ export default Backbone.View.extend({
|
||||
style: this.model.get("style")
|
||||
});
|
||||
// render collapsible options
|
||||
if (
|
||||
const connected = this.field.connected;
|
||||
const collapsible =
|
||||
!connected &&
|
||||
!this.field.collapsible_disabled &&
|
||||
!this.model.get("disabled") &&
|
||||
this.model.get("collapsible_value") !== undefined
|
||||
) {
|
||||
this.model.get("collapsible_value") !== undefined;
|
||||
const connectable = this.model.get("connectable");
|
||||
if (collapsible || connectable) {
|
||||
var collapsible_state = this.field.collapsed ? "enable" : "disable";
|
||||
this.$title_text.hide();
|
||||
this.$collapsible.show();
|
||||
this.$collapsible_text.text(this.model.get("label"));
|
||||
this.$collapsible_icon
|
||||
.removeClass()
|
||||
.addClass("icon")
|
||||
.addClass(this.model.get(`cls_${collapsible_state}`))
|
||||
.attr("data-original-title", this.model.get(`text_${collapsible_state}`))
|
||||
.tooltip({ placement: "bottom" });
|
||||
if (collapsible && !connected) {
|
||||
this.$collapsible_icon
|
||||
.removeClass()
|
||||
.addClass("icon ui-form-collapsible-icon")
|
||||
.addClass(this.model.get(`cls_${collapsible_state}`))
|
||||
.attr("data-original-title", this.model.get(`text_${collapsible_state}`))
|
||||
.tooltip({ placement: "bottom" })
|
||||
.show();
|
||||
} else {
|
||||
this.$collapsible_icon.hide();
|
||||
}
|
||||
if (connectable) {
|
||||
const connectedIconStyle = this.field.connected ? "fa fa-times" : "fa fa-arrows-h";
|
||||
this.$connected_icon
|
||||
.removeClass()
|
||||
.addClass("icon ui-form-connected-icon")
|
||||
.addClass(connectedIconStyle)
|
||||
.attr("data-original-title", this.model.get(`text_${collapsible_state}`))
|
||||
.tooltip({ placement: "bottom" })
|
||||
.show();
|
||||
} else {
|
||||
this.$connected_icon.hide();
|
||||
}
|
||||
} else {
|
||||
this.$title_text.show().text(this.model.get("label"));
|
||||
this.$collapsible.hide();
|
||||
@@ -145,6 +185,7 @@ export default Backbone.View.extend({
|
||||
$("<div/>")
|
||||
.addClass("ui-form-collapsible")
|
||||
.append($("<i/>").addClass("ui-form-collapsible-icon"))
|
||||
.append($("<i/>").addClass("ui-form-connected-icon"))
|
||||
.append($("<span/>").addClass("ui-form-collapsible-text"))
|
||||
)
|
||||
.append($("<span/>").addClass("ui-form-title-text"))
|
||||
|
||||
@@ -175,6 +175,7 @@ var View = Backbone.View.extend({
|
||||
text_value: input_def.text_value,
|
||||
collapsible_value: input_def.collapsible_value,
|
||||
collapsible_preview: input_def.collapsible_preview,
|
||||
connectable: input_def.connectable,
|
||||
help: input_def.help,
|
||||
argument: input_def.argument,
|
||||
disabled: input_def.disabled,
|
||||
|
||||
@@ -90,6 +90,7 @@ var Tool = Backbone.View.extend({
|
||||
var options = form.model.attributes;
|
||||
Utils.deepeach(options.inputs, input => {
|
||||
if (input.type) {
|
||||
input.connectable = true;
|
||||
if (["data", "data_collection"].indexOf(input.type) != -1) {
|
||||
input.type = "hidden";
|
||||
input.info = `Data input '${input.name}' (${Utils.textify(input.extensions)})`;
|
||||
@@ -105,6 +106,7 @@ var Tool = Backbone.View.extend({
|
||||
});
|
||||
Utils.deepeach(options.inputs, input => {
|
||||
if (input.type === "conditional") {
|
||||
input.connectable = false;
|
||||
input.test_param.collapsible_value = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,5 +3,6 @@ export default {
|
||||
data_input: "fa-file-o",
|
||||
data_collection_input: "fa-folder-o",
|
||||
subworkflow: "fa-sitemap fa-rotate-270",
|
||||
parameter_input: "fa-pencil",
|
||||
pause: "fa-pause"
|
||||
};
|
||||
|
||||
@@ -6,6 +6,14 @@ import NodeView from "mvc/workflow/workflow-view-node";
|
||||
/* global $ */
|
||||
/* global Galaxy */
|
||||
|
||||
var StepParameterTypes = [
|
||||
'text',
|
||||
'integer',
|
||||
'float',
|
||||
'boolean',
|
||||
'color',
|
||||
]
|
||||
|
||||
var Node = Backbone.Model.extend({
|
||||
initialize: function(app, attr) {
|
||||
this.app = app;
|
||||
@@ -192,6 +200,7 @@ var Node = Backbone.Model.extend({
|
||||
this.content_id = this.config_form.id;
|
||||
}
|
||||
},
|
||||
|
||||
init_field_data: function(data) {
|
||||
//console.debug("init_field_data: ", data);
|
||||
if (data.type) {
|
||||
@@ -214,13 +223,14 @@ var Node = Backbone.Model.extend({
|
||||
node: node
|
||||
});
|
||||
node.nodeView = nodeView;
|
||||
$.each(data.data_inputs, (i, input) => {
|
||||
$.each(data.inputs, (i, input) => {
|
||||
nodeView.addDataInput(input);
|
||||
});
|
||||
if (data.data_inputs.length > 0 && data.data_outputs.length > 0) {
|
||||
|
||||
if (data.inputs.length > 0 && data.outputs.length > 0) {
|
||||
nodeView.addRule();
|
||||
}
|
||||
$.each(data.data_outputs, (i, output) => {
|
||||
$.each(data.outputs, (i, output) => {
|
||||
nodeView.addDataOutput(output);
|
||||
});
|
||||
nodeView.render();
|
||||
@@ -229,7 +239,7 @@ var Node = Backbone.Model.extend({
|
||||
update_field_data: function(data) {
|
||||
var node = this;
|
||||
var nodeView = node.nodeView;
|
||||
// remove unused output views and remove pre-existing output views from data.data_outputs,
|
||||
// remove unused output views and remove pre-existing output views from data.outputs,
|
||||
// so that these are not added twice.
|
||||
var unused_outputs = [];
|
||||
// nodeView.outputViews contains pre-existing outputs,
|
||||
@@ -237,7 +247,7 @@ var Node = Backbone.Model.extend({
|
||||
// Now we gather the unused outputs
|
||||
$.each(nodeView.outputViews, (i, output_view) => {
|
||||
var cur_name = output_view.output.name;
|
||||
var data_names = data.data_outputs;
|
||||
var data_names = data.outputs;
|
||||
var cur_name_in_data_outputs = false;
|
||||
_.each(data_names, data_name => {
|
||||
if (data_name.name == cur_name) {
|
||||
@@ -265,7 +275,7 @@ var Node = Backbone.Model.extend({
|
||||
node.workflow_outputs.splice(i, 1); // removes output from list of workflow outputs
|
||||
}
|
||||
});
|
||||
$.each(data.data_outputs, (i, output) => {
|
||||
$.each(data.outputs, (i, output) => {
|
||||
if (!nodeView.outputViews[output.name]) {
|
||||
nodeView.addDataOutput(output); // add data output if it does not yet exist
|
||||
} else {
|
||||
@@ -291,7 +301,7 @@ var Node = Backbone.Model.extend({
|
||||
var old_body = nodeView.$("div.inputs");
|
||||
var new_body = nodeView.newInputsDiv();
|
||||
var newTerminalViews = {};
|
||||
_.each(data.data_inputs, input => {
|
||||
_.each(data.inputs, input => {
|
||||
var terminalView = node.nodeView.addDataInput(input, new_body);
|
||||
newTerminalViews[input.name] = terminalView;
|
||||
});
|
||||
@@ -305,8 +315,9 @@ var Node = Backbone.Model.extend({
|
||||
// type (not really valid right?) but adding special logic here for
|
||||
// data collection input parameters that can have their collection
|
||||
// change.
|
||||
if (data.data_outputs.length == 1 && "collection_type" in data.data_outputs[0]) {
|
||||
nodeView.updateDataOutput(data.data_outputs[0]);
|
||||
var data_outputs = data.outputs;
|
||||
if (data_outputs.length == 1 && "collection_type" in data_outputs[0]) {
|
||||
nodeView.updateDataOutput(data_outputs[0]);
|
||||
}
|
||||
old_body.replaceWith(new_body);
|
||||
if ("workflow_outputs" in data) {
|
||||
|
||||
@@ -431,6 +431,18 @@ var InputTerminal = BaseInputTerminal.extend({
|
||||
}
|
||||
});
|
||||
|
||||
var InputParameterTerminal = BaseInputTerminal.extend({
|
||||
update: function(input) {
|
||||
this.type = input.type;
|
||||
},
|
||||
connect: function(connector) {
|
||||
BaseInputTerminal.prototype.connect.call(this, connector);
|
||||
},
|
||||
attachable: function(other) {
|
||||
return this.type == other.attributes.type;
|
||||
},
|
||||
});
|
||||
|
||||
var InputCollectionTerminal = BaseInputTerminal.extend({
|
||||
update: function(input) {
|
||||
this.multiple = false;
|
||||
@@ -566,9 +578,14 @@ var OutputCollectionTerminal = Terminal.extend({
|
||||
}
|
||||
});
|
||||
|
||||
var OutputParameterTerminal = Terminal.extend({
|
||||
});
|
||||
|
||||
export default {
|
||||
InputTerminal: InputTerminal,
|
||||
InputParameterTerminal: InputParameterTerminal,
|
||||
OutputTerminal: OutputTerminal,
|
||||
OutputParameterTerminal: OutputParameterTerminal,
|
||||
InputCollectionTerminal: InputCollectionTerminal,
|
||||
OutputCollectionTerminal: OutputCollectionTerminal,
|
||||
TerminalMapping: TerminalMapping,
|
||||
|
||||
@@ -9,7 +9,7 @@ var DataInputView = Backbone.View.extend({
|
||||
this.nodeView = options.nodeView;
|
||||
this.terminalElement = options.terminalElement;
|
||||
|
||||
this.$el.attr("name", this.input.name).html(this.input.label);
|
||||
this.$el.attr("name", this.input.name).html(this.input.label || this.input.name);
|
||||
|
||||
if (!options.skipResize) {
|
||||
this.$el.css({
|
||||
@@ -82,6 +82,61 @@ var DataOutputView = Backbone.View.extend({
|
||||
}
|
||||
});
|
||||
|
||||
var ParameterOutputView = Backbone.View.extend({
|
||||
className: "form-row dataRow",
|
||||
|
||||
initialize: function(options) {
|
||||
this.output = options.output;
|
||||
this.terminalElement = options.terminalElement;
|
||||
this.nodeView = options.nodeView;
|
||||
|
||||
var output = this.output;
|
||||
var label = output.label || output.name;
|
||||
var node = this.nodeView.node;
|
||||
|
||||
this.$el.html(label);
|
||||
this.calloutView = null;
|
||||
if (["tool", "subworkflow"].indexOf(node.type) >= 0) {
|
||||
var calloutView = new OutputCalloutView({
|
||||
label: label,
|
||||
output: output,
|
||||
node: node
|
||||
});
|
||||
this.calloutView = calloutView;
|
||||
this.$el.append(calloutView.el);
|
||||
this.$el.hover(
|
||||
() => {
|
||||
calloutView.hoverImage();
|
||||
},
|
||||
() => {
|
||||
calloutView.resetImage();
|
||||
}
|
||||
);
|
||||
}
|
||||
this.$el.css({
|
||||
position: "absolute",
|
||||
left: -1000,
|
||||
top: -1000,
|
||||
display: "none"
|
||||
});
|
||||
$("body").append(this.el);
|
||||
this.nodeView.updateMaxWidth(this.$el.outerWidth() + 17);
|
||||
this.$el
|
||||
.css({
|
||||
position: "",
|
||||
left: "",
|
||||
top: "",
|
||||
display: ""
|
||||
})
|
||||
.detach();
|
||||
},
|
||||
redrawWorkflowOutput: function() {
|
||||
if (this.calloutView) {
|
||||
this.calloutView.resetImage();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var OutputCalloutView = Backbone.View.extend({
|
||||
tagName: "div",
|
||||
|
||||
@@ -133,5 +188,6 @@ var OutputCalloutView = Backbone.View.extend({
|
||||
|
||||
export default {
|
||||
DataInputView: DataInputView,
|
||||
DataOutputView: DataOutputView
|
||||
DataOutputView: DataOutputView,
|
||||
ParameterOutputView: ParameterOutputView,
|
||||
};
|
||||
|
||||
@@ -48,10 +48,12 @@ export default Backbone.View.extend({
|
||||
skipResize = false;
|
||||
}
|
||||
var terminalView = this.terminalViews[input.name];
|
||||
var terminalViewClass =
|
||||
input.input_type == "dataset_collection"
|
||||
? TerminalViews.InputCollectionTerminalView
|
||||
: TerminalViews.InputTerminalView;
|
||||
var terminalViewClass = TerminalViews.InputTerminalView;
|
||||
if (input.input_type == "dataset_collection") {
|
||||
terminalViewClass = TerminalViews.InputCollectionTerminalView;
|
||||
} else if (input.input_type == "parameter") {
|
||||
terminalViewClass = TerminalViews.InputParameterTerminalView;
|
||||
}
|
||||
if (terminalView && !(terminalView instanceof terminalViewClass)) {
|
||||
terminalView.el.terminal.destroy();
|
||||
terminalView = null;
|
||||
@@ -80,14 +82,19 @@ export default Backbone.View.extend({
|
||||
},
|
||||
|
||||
addDataOutput: function(output) {
|
||||
var terminalViewClass = output.collection
|
||||
? TerminalViews.OutputCollectionTerminalView
|
||||
: TerminalViews.OutputTerminalView;
|
||||
var terminalViewClass = TerminalViews.OutputTerminalView;
|
||||
var outputViewClass = DataViews.DataOutputView;
|
||||
if (output.collection) {
|
||||
terminalViewClass = TerminalViews.OutputCollectionTerminalView;
|
||||
} else if (output.parameter) {
|
||||
terminalViewClass = TerminalViews.OutputParameterTerminalView;
|
||||
outputViewClass = DataViews.ParameterOutputView;
|
||||
}
|
||||
var terminalView = new terminalViewClass({
|
||||
node: this.node,
|
||||
output: output
|
||||
});
|
||||
var outputView = new DataViews.DataOutputView({
|
||||
var outputView = new outputViewClass({
|
||||
output: output,
|
||||
terminalElement: terminalView.el,
|
||||
nodeView: this
|
||||
|
||||
@@ -151,6 +151,17 @@ var InputTerminalView = BaseInputTerminalView.extend({
|
||||
}
|
||||
});
|
||||
|
||||
var InputParameterTerminalView = BaseInputTerminalView.extend({
|
||||
terminalMappingClass: Terminals.TerminalMapping,
|
||||
terminalMappingViewClass: InputTerminalMappingView,
|
||||
terminalForInput: function(input) {
|
||||
return new Terminals.InputParameterTerminal({
|
||||
element: this.el,
|
||||
input: input
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var InputCollectionTerminalView = BaseInputTerminalView.extend({
|
||||
terminalMappingClass: Terminals.TerminalMapping,
|
||||
terminalMappingViewClass: InputTerminalMappingView,
|
||||
@@ -269,9 +280,25 @@ var OutputCollectionTerminalView = BaseOutputTerminalView.extend({
|
||||
}
|
||||
});
|
||||
|
||||
var OutputParameterTerminalView = BaseOutputTerminalView.extend({
|
||||
terminalMappingClass: Terminals.TerminalMapping,
|
||||
terminalMappingViewClass: TerminalMappingView,
|
||||
terminalForOutput: function(output) {
|
||||
var collection_type = output.collection_type;
|
||||
var collection_type_source = output.collection_type_source;
|
||||
var terminal = new Terminals.OutputCollectionTerminal({
|
||||
element: this.el,
|
||||
type: output.type,
|
||||
});
|
||||
return terminal;
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
InputTerminalView: InputTerminalView,
|
||||
InputParameterTerminalView: InputParameterTerminalView,
|
||||
OutputTerminalView: OutputTerminalView,
|
||||
OutputParameterTerminalView: OutputParameterTerminalView,
|
||||
InputCollectionTerminalView: InputCollectionTerminalView,
|
||||
OutputCollectionTerminalView: OutputCollectionTerminalView
|
||||
};
|
||||
|
||||
@@ -357,16 +357,16 @@ QUnit.module("Node unit test", {
|
||||
},
|
||||
init_field_data_simple: function(option_overrides) {
|
||||
var data = Utils.merge(option_overrides, {
|
||||
data_inputs: [{ name: "input1", extensions: ["data"] }],
|
||||
data_outputs: [{ name: "output1", extensions: ["data"] }],
|
||||
inputs: [{ name: "input1", extensions: ["data"] }],
|
||||
outputs: [{ name: "output1", extensions: ["data"] }],
|
||||
label: null
|
||||
});
|
||||
this.node.init_field_data(data);
|
||||
},
|
||||
update_field_data_with_new_input: function(option_overrides) {
|
||||
var new_data = Utils.merge(option_overrides, {
|
||||
data_inputs: [{ name: "input1", extensions: ["data"] }, { name: "extra_0|input1", extensions: ["data"] }],
|
||||
data_outputs: [{ name: "output1", extensions: ["data"] }],
|
||||
inputs: [{ name: "input1", extensions: ["data"] }, { name: "extra_0|input1", extensions: ["data"] }],
|
||||
outputs: [{ name: "output1", extensions: ["data"] }],
|
||||
post_job_actions: "{}",
|
||||
label: "New Label"
|
||||
});
|
||||
@@ -401,8 +401,8 @@ QUnit.test("init_field_data properties", function(assert) {
|
||||
var node = this.node;
|
||||
this.expect_workflow_node_changed(assert, function() {
|
||||
var data = {
|
||||
data_inputs: [],
|
||||
data_outputs: [],
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
type: "tool",
|
||||
name: "cat1",
|
||||
config_form: "{}",
|
||||
@@ -507,8 +507,8 @@ QUnit.test("update_field_data destroys old terminals", function(assert) {
|
||||
var node = this.node;
|
||||
this.expect_workflow_node_changed(assert, function() {
|
||||
var data = {
|
||||
data_inputs: [{ name: "input1", extensions: ["data"] }, { name: "willDisappear", extensions: ["data"] }],
|
||||
data_outputs: [{ name: "output1", extensions: ["data"] }]
|
||||
inputs: [{ name: "input1", extensions: ["data"] }, { name: "willDisappear", extensions: ["data"] }],
|
||||
outputs: [{ name: "output1", extensions: ["data"] }]
|
||||
};
|
||||
node.init_field_data(data);
|
||||
var old_input_terminal = node.input_terminals.willDisappear;
|
||||
|
||||
@@ -561,7 +561,7 @@ class WorkflowContentsManager(UsesAnnotations):
|
||||
data['upgrade_messages'][step.order_index] = {module.tool.name: "\n".join(module.version_changes)}
|
||||
# Get user annotation.
|
||||
annotation_str = self.get_item_annotation_str(trans.sa_session, trans.user, step) or ''
|
||||
config_form = module.get_config_form()
|
||||
config_form = module.get_config_form(step=step)
|
||||
# Pack attributes into plain dictionary
|
||||
step_dict = {
|
||||
'id': step.order_index,
|
||||
@@ -571,8 +571,8 @@ class WorkflowContentsManager(UsesAnnotations):
|
||||
'name': module.get_name(),
|
||||
'tool_state': module.get_state(),
|
||||
'errors': module.get_errors(),
|
||||
'data_inputs': module.get_data_inputs(),
|
||||
'data_outputs': module.get_data_outputs(),
|
||||
'inputs': module.get_all_inputs(connectable_only=True),
|
||||
'outputs': module.get_all_outputs(),
|
||||
'config_form': config_form,
|
||||
'annotation': annotation_str,
|
||||
'post_job_actions': {},
|
||||
@@ -598,9 +598,6 @@ class WorkflowContentsManager(UsesAnnotations):
|
||||
if isinstance(input, DataCollectionToolParameter):
|
||||
input_connections_type[input.name] = "dataset_collection"
|
||||
visit_input_values(module.tool.inputs, module.state.inputs, callback)
|
||||
# Filter
|
||||
# FIXME: this removes connection without displaying a message currently!
|
||||
input_connections = [conn for conn in input_connections if conn.input_name in data_input_names]
|
||||
# post_job_actions
|
||||
pja_dict = {}
|
||||
for pja in step.post_job_actions:
|
||||
@@ -653,12 +650,12 @@ class WorkflowContentsManager(UsesAnnotations):
|
||||
"""
|
||||
for order_index in sorted(steps):
|
||||
step = steps[order_index]
|
||||
for i, step_data_output in enumerate(step['data_outputs']):
|
||||
for i, step_data_output in enumerate(step['outputs']):
|
||||
if step_data_output.get('collection_type_source') and step_data_output['collection_type'] is None:
|
||||
collection_type_source = step_data_output['collection_type_source']
|
||||
for input_connection in step['input_connections'].get(collection_type_source, []):
|
||||
input_step = steps[input_connection['id']]
|
||||
for input_step_data_output in input_step['data_outputs']:
|
||||
for input_step_data_output in input_step['outputs']:
|
||||
if input_step_data_output['name'] == input_connection['output_name']:
|
||||
step_data_output['collection_type'] = input_step_data_output.get('collection_type')
|
||||
return steps
|
||||
@@ -789,8 +786,6 @@ class WorkflowContentsManager(UsesAnnotations):
|
||||
# If the tool is installed we attempt to verify input values
|
||||
# and connections, otherwise the last known state will be dumped without modifications.
|
||||
visit_input_values(module.tool.inputs, module.state.inputs, callback)
|
||||
# FIXME: this removes connection without displaying a message currently!
|
||||
input_connections = [conn for conn in input_connections if (conn.input_name in data_input_names or conn.non_data_connection)]
|
||||
|
||||
# Encode input connections as dictionary
|
||||
input_conn_dict = {}
|
||||
@@ -846,6 +841,8 @@ class WorkflowContentsManager(UsesAnnotations):
|
||||
label = "Input Dataset"
|
||||
elif step_type == "data_collection_input":
|
||||
label = "Input Dataset Collection"
|
||||
elif step_type == 'parameter_input':
|
||||
label = "Input Parameter"
|
||||
else:
|
||||
raise ValueError("Invalid step_type %s" % step_type)
|
||||
if legacy:
|
||||
|
||||
@@ -4192,14 +4192,9 @@ class WorkflowStepConnection(RepresentById):
|
||||
self.input_step_id = None
|
||||
self.input_name = None
|
||||
|
||||
def set_non_data_connection(self):
|
||||
self.output_name = WorkflowStepConnection.NON_DATA_CONNECTION
|
||||
self.input_name = WorkflowStepConnection.NON_DATA_CONNECTION
|
||||
|
||||
@property
|
||||
def non_data_connection(self):
|
||||
return (self.output_name == WorkflowStepConnection.NON_DATA_CONNECTION and
|
||||
self.input_name == WorkflowStepConnection.NON_DATA_CONNECTION)
|
||||
return (self.output_name == self.input_name == WorkflowStepConnection.NON_DATA_CONNECTION)
|
||||
|
||||
def copy(self):
|
||||
# TODO: handle subworkflow ids...
|
||||
|
||||
@@ -9,7 +9,7 @@ from boltons.iterutils import remap
|
||||
|
||||
from galaxy.util.expressions import ExpressionContext
|
||||
from galaxy.util.json import safe_loads
|
||||
from .basic import DataCollectionToolParameter, DataToolParameter, RuntimeValue, SelectToolParameter
|
||||
from .basic import DataCollectionToolParameter, DataToolParameter, is_runtime_value, runtime_to_json, SelectToolParameter
|
||||
from .grouping import Conditional, Repeat, Section, UploadDataset
|
||||
|
||||
REPLACE_ON_TRUTHY = object()
|
||||
@@ -179,11 +179,8 @@ def check_param(trans, param, incoming_value, param_values):
|
||||
error = None
|
||||
try:
|
||||
if trans.workflow_building_mode:
|
||||
if isinstance(value, RuntimeValue):
|
||||
return [{'__class__' : 'RuntimeValue'}, None]
|
||||
if isinstance(value, dict):
|
||||
if value.get('__class__') == 'RuntimeValue':
|
||||
return [value, None]
|
||||
if is_runtime_value(value):
|
||||
return [runtime_to_json(value), None]
|
||||
value = param.from_json(value, trans, param_values)
|
||||
param.validate(value, trans)
|
||||
except ValueError as e:
|
||||
|
||||
@@ -56,7 +56,7 @@ def contains_workflow_parameter(value, search=False):
|
||||
|
||||
def is_runtime_value(value):
|
||||
return isinstance(value, RuntimeValue) or (isinstance(value, dict)
|
||||
and value.get("__class__") == "RuntimeValue")
|
||||
and value.get("__class__") in ["RuntimeValue", "ConnectedValue"])
|
||||
|
||||
|
||||
def is_runtime_context(trans, other_values):
|
||||
@@ -155,13 +155,13 @@ class ToolParameter(Dictifiable):
|
||||
|
||||
def value_to_basic(self, value, app, use_security=False):
|
||||
if is_runtime_value(value):
|
||||
return {'__class__': 'RuntimeValue'}
|
||||
return runtime_to_json(value)
|
||||
return self.to_json(value, app, use_security)
|
||||
|
||||
def value_from_basic(self, value, app, ignore_errors=False):
|
||||
# Handle Runtime and Unvalidated values
|
||||
if is_runtime_value(value):
|
||||
return RuntimeValue()
|
||||
return runtime_to_object(value)
|
||||
elif isinstance(value, dict) and value.get('__class__') == 'UnvalidatedValue':
|
||||
return value['value']
|
||||
# Delegate to the 'to_python' method
|
||||
@@ -2246,7 +2246,7 @@ class BaseJsonToolParameter(ToolParameter):
|
||||
|
||||
def value_to_basic(self, value, app, use_security=False):
|
||||
if is_runtime_value(value):
|
||||
return {'__class__': 'RuntimeValue'}
|
||||
return runtime_to_json(value)
|
||||
return value
|
||||
|
||||
def to_json(self, value, app, use_security):
|
||||
@@ -2323,8 +2323,29 @@ parameter_types = dict(
|
||||
)
|
||||
|
||||
|
||||
def runtime_to_json(runtime_value):
|
||||
if isinstance(runtime_value, ConnectedValue) or (isinstance(runtime_value, dict) and runtime_value["__class__"] == "ConnectedValue"):
|
||||
return {"__class__": "ConnectedValue"}
|
||||
else:
|
||||
return {"__class__": "RuntimeValue"}
|
||||
|
||||
|
||||
def runtime_to_object(runtime_value):
|
||||
if isinstance(runtime_value, ConnectedValue) or (isinstance(runtime_value, dict) and runtime_value["__class__"] == "ConnectedValue"):
|
||||
return ConnectedValue()
|
||||
else:
|
||||
return RuntimeValue()
|
||||
|
||||
|
||||
class RuntimeValue(object):
|
||||
"""
|
||||
Wrapper to note a value that is not yet set, but will be required at runtime.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ConnectedValue(RuntimeValue):
|
||||
"""
|
||||
Wrapper to note a value that is not yet set, but will be inferred from a connection.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -557,8 +557,8 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn
|
||||
'annotation' : inputs.get('__annotation', ''),
|
||||
'name' : module.get_name(),
|
||||
'tool_state' : module.get_state(),
|
||||
'data_inputs' : module.get_data_inputs(),
|
||||
'data_outputs' : module.get_data_outputs(),
|
||||
'inputs' : module.get_all_inputs(connectable_only=True),
|
||||
'outputs' : module.get_all_outputs(),
|
||||
'config_form' : module.get_config_form(),
|
||||
'post_job_actions' : module.get_post_job_actions(inputs)
|
||||
}
|
||||
|
||||
@@ -30,11 +30,12 @@ from galaxy.tools.parameters import (
|
||||
)
|
||||
from galaxy.tools.parameters.basic import (
|
||||
BooleanToolParameter,
|
||||
ConnectedValue,
|
||||
DataCollectionToolParameter,
|
||||
DataToolParameter,
|
||||
is_runtime_value,
|
||||
parameter_types,
|
||||
RuntimeValue,
|
||||
runtime_to_json,
|
||||
SelectToolParameter,
|
||||
TextToolParameter,
|
||||
workflow_building_modes
|
||||
@@ -151,12 +152,18 @@ class WorkflowModule(object):
|
||||
""" This returns inputs displayed in the workflow editor """
|
||||
return {}
|
||||
|
||||
def get_all_inputs(self, data_only=False, connectable_only=False):
|
||||
return []
|
||||
|
||||
def get_data_inputs(self):
|
||||
""" Get configure time data input descriptions. """
|
||||
return self.get_all_inputs(data_only=True)
|
||||
|
||||
def get_all_outputs(self, data_only=False):
|
||||
return []
|
||||
|
||||
def get_data_outputs(self):
|
||||
return []
|
||||
return self.get_all_outputs(data_only=True)
|
||||
|
||||
def get_post_job_actions(self, incoming):
|
||||
return []
|
||||
@@ -172,7 +179,7 @@ class WorkflowModule(object):
|
||||
""" Replace connected inputs with placeholder/dummy values. """
|
||||
pass
|
||||
|
||||
def get_config_form(self):
|
||||
def get_config_form(self, step=None):
|
||||
""" Serializes input parameters of a module into input dictionaries. """
|
||||
return {
|
||||
'title' : self.name,
|
||||
@@ -361,7 +368,7 @@ class SubWorkflowModule(WorkflowModule):
|
||||
return self.subworkflow.name
|
||||
return self.name
|
||||
|
||||
def get_all_inputs(self, data_only=False):
|
||||
def get_all_inputs(self, data_only=False, connectable_only=False):
|
||||
""" Get configure time data input descriptions. """
|
||||
# Filter subworkflow steps and get inputs
|
||||
step_to_input_type = {
|
||||
@@ -389,10 +396,7 @@ class SubWorkflowModule(WorkflowModule):
|
||||
inputs.append(input)
|
||||
return inputs
|
||||
|
||||
def get_data_inputs(self):
|
||||
return self.get_all_inputs(data_only=True)
|
||||
|
||||
def get_data_outputs(self):
|
||||
def get_all_outputs(self, data_only=False):
|
||||
outputs = []
|
||||
if hasattr(self.subworkflow, 'workflow_outputs'):
|
||||
from galaxy.managers.workflows import WorkflowContentsManager
|
||||
@@ -403,7 +407,7 @@ class SubWorkflowModule(WorkflowModule):
|
||||
tooltip=False)
|
||||
for order_index in sorted(subworkflow_dict['steps']):
|
||||
step = subworkflow_dict['steps'][order_index]
|
||||
data_outputs = subworkflow_dict['steps'][order_index]['data_outputs']
|
||||
data_outputs = subworkflow_dict['steps'][order_index]['outputs']
|
||||
for workflow_output in step['workflow_outputs']:
|
||||
label = workflow_output['label']
|
||||
if not label:
|
||||
@@ -498,7 +502,7 @@ class InputModule(WorkflowModule):
|
||||
state.inputs = dict(input=None)
|
||||
return state
|
||||
|
||||
def get_data_inputs(self):
|
||||
def get_all_inputs(self, data_only=False, connectable_only=False):
|
||||
return []
|
||||
|
||||
def execute(self, trans, progress, invocation_step, use_cached_job=False):
|
||||
@@ -538,7 +542,7 @@ class InputDataModule(InputModule):
|
||||
type = "data_input"
|
||||
name = "Input dataset"
|
||||
|
||||
def get_data_outputs(self):
|
||||
def get_all_outputs(self, data_only=False):
|
||||
return [dict(name='output', extensions=['input'])]
|
||||
|
||||
def get_filter_set(self, connections=None):
|
||||
@@ -579,7 +583,7 @@ class InputDataCollectionModule(InputModule):
|
||||
input_element = Element("param", name="input", label=self.label, type="data_collection", collection_type=collection_type)
|
||||
return dict(input=DataCollectionToolParameter(None, input_element, self.trans))
|
||||
|
||||
def get_data_outputs(self):
|
||||
def get_all_outputs(self, data_only=False):
|
||||
return [
|
||||
dict(
|
||||
name='output',
|
||||
@@ -604,14 +608,20 @@ class InputParameterModule(WorkflowModule):
|
||||
optional = self.state.inputs.get("optional", self.default_optional)
|
||||
input_parameter_type = SelectToolParameter(None, XML(
|
||||
'''
|
||||
<param name="parameter_type" label="Parameter type" type="select" value="%s">
|
||||
<param name="parameter_type" label="Parameter type" type="select">
|
||||
<option value="text">Text</option>
|
||||
<option value="integer">Integer</option>
|
||||
<option value="float">Float</option>
|
||||
<option value="boolean">Boolean (True or False)</option>
|
||||
<option value="color">Color</option>
|
||||
</param>
|
||||
''' % parameter_type))
|
||||
'''))
|
||||
for i, option in enumerate(input_parameter_type.static_options):
|
||||
option = list(option)
|
||||
if option[1] == parameter_type:
|
||||
# item 0 is option description, item 1 is value, item 2 is "selected"
|
||||
option[2] = True
|
||||
input_parameter_type.static_options[i] = tuple(option)
|
||||
return odict([("parameter_type", input_parameter_type),
|
||||
("optional", BooleanToolParameter(None, Element("param", name="optional", label="Optional", type="boolean", value=optional)))])
|
||||
|
||||
@@ -635,8 +645,16 @@ class InputParameterModule(WorkflowModule):
|
||||
state.inputs = dict(input=None)
|
||||
return state
|
||||
|
||||
def get_data_inputs(self):
|
||||
return []
|
||||
def get_all_outputs(self, data_only=False):
|
||||
if data_only:
|
||||
return []
|
||||
|
||||
return [dict(
|
||||
name='output',
|
||||
label=self.label,
|
||||
type=self.state.inputs.get('parameter_type', self.parameter_type),
|
||||
parameter=True,
|
||||
)]
|
||||
|
||||
def execute(self, trans, progress, invocation_step, use_cached_job=False):
|
||||
step = invocation_step.workflow_step
|
||||
@@ -651,7 +669,7 @@ class PauseModule(WorkflowModule):
|
||||
type = "pause"
|
||||
name = "Pause for dataset review"
|
||||
|
||||
def get_data_inputs(self):
|
||||
def get_all_inputs(self, data_only=False, connectable_only=False):
|
||||
input = dict(
|
||||
name="input",
|
||||
label="Dataset for Review",
|
||||
@@ -659,9 +677,9 @@ class PauseModule(WorkflowModule):
|
||||
extensions='input',
|
||||
input_type="dataset",
|
||||
)
|
||||
return [input]
|
||||
return [input] if not data_only else []
|
||||
|
||||
def get_data_outputs(self):
|
||||
def get_all_outputs(self, data_only=False):
|
||||
return [dict(name="output", label="Reviewed Dataset", extensions=['input'])]
|
||||
|
||||
def get_runtime_state(self):
|
||||
@@ -803,14 +821,24 @@ class ToolModule(WorkflowModule):
|
||||
def get_inputs(self):
|
||||
return self.tool.inputs if self.tool else {}
|
||||
|
||||
def get_all_inputs(self, data_only=False):
|
||||
def get_all_inputs(self, data_only=False, connectable_only=False):
|
||||
if data_only and connectable_only:
|
||||
raise Exception("Must specify at most one of data_only and connectable_only as True.")
|
||||
|
||||
inputs = []
|
||||
if self.tool:
|
||||
|
||||
def callback(input, prefixed_name, prefixed_label, **kwargs):
|
||||
def callback(input, prefixed_name, prefixed_label, value=None, **kwargs):
|
||||
visible = not hasattr(input, 'hidden') or not input.hidden
|
||||
input_type = input.type
|
||||
is_data = isinstance(input, DataToolParameter) or isinstance(input, DataCollectionToolParameter)
|
||||
skip = data_only and (not visible or not is_data)
|
||||
is_connectable = is_runtime_value(value) and runtime_to_json(value)["__class__"] == "ConnectedValue"
|
||||
if data_only:
|
||||
skip = not visible or not is_data
|
||||
elif connectable_only:
|
||||
skip = not visible or not (is_data or is_connectable)
|
||||
else:
|
||||
skip = not visible
|
||||
if not skip:
|
||||
if isinstance(input, DataToolParameter):
|
||||
inputs.append(dict(
|
||||
@@ -835,16 +863,14 @@ class ToolModule(WorkflowModule):
|
||||
label=prefixed_label,
|
||||
multiple=False,
|
||||
input_type="parameter",
|
||||
type=input_type,
|
||||
)
|
||||
)
|
||||
|
||||
visit_input_values(self.tool.inputs, self.state.inputs, callback)
|
||||
return inputs
|
||||
|
||||
def get_data_inputs(self):
|
||||
return self.get_all_inputs(data_only=True)
|
||||
|
||||
def get_data_outputs(self):
|
||||
def get_all_outputs(self, data_only=False):
|
||||
data_outputs = []
|
||||
if self.tool:
|
||||
for name, tool_output in self.tool.outputs.items():
|
||||
@@ -890,9 +916,9 @@ class ToolModule(WorkflowModule):
|
||||
)
|
||||
return data_outputs
|
||||
|
||||
def get_config_form(self):
|
||||
def get_config_form(self, step=None):
|
||||
if self.tool:
|
||||
self.add_dummy_datasets()
|
||||
self.add_dummy_datasets(connections=step and step.input_connections)
|
||||
incoming = {}
|
||||
params_to_incoming(incoming, self.tool.inputs, self.state.inputs, self.trans.app)
|
||||
return self.tool.to_json(self.trans, incoming, workflow_building_mode=True)
|
||||
@@ -911,22 +937,23 @@ class ToolModule(WorkflowModule):
|
||||
|
||||
# Any input needs to have value RuntimeValue or obtain the value from connected steps
|
||||
def callback(input, prefixed_name, context, **kwargs):
|
||||
if isinstance(input, DataToolParameter) or isinstance(input, DataCollectionToolParameter):
|
||||
if connections is not None and steps is not None and self.trans.workflow_building_mode is workflow_building_modes.USE_HISTORY:
|
||||
if prefixed_name in input_connections_by_name:
|
||||
connection = input_connections_by_name[prefixed_name]
|
||||
output_step = next(output_step for output_step in steps if connection.output_step_id == output_step.id)
|
||||
if output_step.type.startswith('data'):
|
||||
output_inputs = output_step.module.get_runtime_inputs(connections=connections)
|
||||
output_value = output_inputs['input'].get_initial_value(self.trans, context)
|
||||
if isinstance(input, DataToolParameter) and isinstance(output_value, self.trans.app.model.HistoryDatasetCollectionAssociation):
|
||||
return output_value.to_hda_representative()
|
||||
return output_value
|
||||
return RuntimeValue()
|
||||
else:
|
||||
return input.get_initial_value(self.trans, context)
|
||||
elif connections is None or prefixed_name in input_connections_by_name:
|
||||
return RuntimeValue()
|
||||
input_type = input.type
|
||||
is_data = input_type in ['data', 'data_collection']
|
||||
if is_data and connections is not None and steps is not None and self.trans.workflow_building_mode is workflow_building_modes.USE_HISTORY:
|
||||
if prefixed_name in input_connections_by_name:
|
||||
connection = input_connections_by_name[prefixed_name]
|
||||
output_step = next(output_step for output_step in steps if connection.output_step_id == output_step.id)
|
||||
if output_step.type.startswith('data'):
|
||||
output_inputs = output_step.module.get_runtime_inputs(connections=connections)
|
||||
output_value = output_inputs['input'].get_initial_value(self.trans, context)
|
||||
if input_type == "data" and isinstance(output_value, self.trans.app.model.HistoryDatasetCollectionAssociation):
|
||||
return output_value.to_hda_representative()
|
||||
return output_value
|
||||
return ConnectedValue()
|
||||
else:
|
||||
return input.get_initial_value(self.trans, context)
|
||||
elif (is_data and connections is None) or prefixed_name in input_connections_by_name:
|
||||
return ConnectedValue()
|
||||
visit_input_values(self.tool.inputs, self.state.inputs, callback)
|
||||
else:
|
||||
raise ToolMissingException("Tool %s missing. Cannot add dummy datasets." % self.tool_id)
|
||||
@@ -1300,6 +1327,11 @@ def load_module_sections(trans):
|
||||
"title": "Input Dataset Collection",
|
||||
"description": "Input dataset collection"
|
||||
},
|
||||
{
|
||||
"name": "parameter_input",
|
||||
"title": "Parameter Input",
|
||||
"description": "Simple inputs used for workflow logic"
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1312,12 +1344,7 @@ def load_module_sections(trans):
|
||||
"name": "pause",
|
||||
"title": "Pause Workflow for Dataset Review",
|
||||
"description": "Pause for Review"
|
||||
},
|
||||
{
|
||||
"name": "parameter_input",
|
||||
"title": "Parameter Input",
|
||||
"description": "Simple inputs used for workflow logic"
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -352,13 +352,10 @@ class WorkflowProgress(object):
|
||||
try:
|
||||
replacement = step_outputs[output_name]
|
||||
except KeyError:
|
||||
if is_data:
|
||||
# Must resolve.
|
||||
template = "Workflow evaluation problem - failed to find output_name %s in step_outputs %s"
|
||||
message = template % (output_name, step_outputs)
|
||||
raise Exception(message)
|
||||
else:
|
||||
replacement = modules.NO_REPLACEMENT
|
||||
# Must resolve.
|
||||
template = "Workflow evaluation problem - failed to find output_name %s in step_outputs %s"
|
||||
message = template % (output_name, step_outputs)
|
||||
raise Exception(message)
|
||||
if isinstance(replacement, model.HistoryDatasetCollectionAssociation):
|
||||
if not replacement.collection.populated:
|
||||
if not replacement.collection.waiting_for_elements:
|
||||
@@ -390,11 +387,12 @@ class WorkflowProgress(object):
|
||||
|
||||
if self.inputs_by_step_id:
|
||||
step_id = step.id
|
||||
if step_id not in self.inputs_by_step_id:
|
||||
if step_id not in self.inputs_by_step_id and 'output' not in outputs:
|
||||
template = "Step with id %s not found in inputs_step_id (%s)"
|
||||
message = template % (step_id, self.inputs_by_step_id)
|
||||
raise ValueError(message)
|
||||
outputs['output'] = self.inputs_by_step_id[step_id]
|
||||
elif step_id in self.inputs_by_step_id:
|
||||
outputs['output'] = self.inputs_by_step_id[step_id]
|
||||
|
||||
self.set_step_outputs(invocation_step, outputs)
|
||||
|
||||
|
||||
@@ -265,6 +265,17 @@ def build_workflow_run_configs(trans, workflow, payload):
|
||||
# inputs with referential integrity if parameters are already normalized (coming from tool form).
|
||||
normalized_inputs = {}
|
||||
|
||||
if param_map:
|
||||
# disentangle raw parameter dictionaries into formal request structures if we can
|
||||
# to setup proper WorkflowRequestToInputDatasetAssociation, WorkflowRequestToInputDatasetCollectionAssociation
|
||||
# and WorkflowRequestInputStepParameter objects.
|
||||
for step in workflow.steps:
|
||||
normalized_key = step.id
|
||||
if step.type == "parameter_input":
|
||||
if normalized_key in param_map:
|
||||
value = param_map.pop(normalized_key)
|
||||
normalized_inputs[normalized_key] = value["input"]
|
||||
|
||||
steps_by_id = workflow.steps_by_id
|
||||
# Set workflow inputs.
|
||||
for key, input_dict in normalized_inputs.items():
|
||||
|
||||
@@ -468,8 +468,8 @@ class WorkflowsApiTestCase(BaseWorkflowsApiTestCase):
|
||||
'name',
|
||||
'tool_state',
|
||||
'tooltip',
|
||||
'data_inputs',
|
||||
'data_outputs',
|
||||
'inputs',
|
||||
'outputs',
|
||||
'config_form',
|
||||
'annotation',
|
||||
'post_job_actions',
|
||||
@@ -494,7 +494,7 @@ steps:
|
||||
downloaded_workflow = self._download_workflow(workflow_id, style="editor")
|
||||
steps = downloaded_workflow['steps']
|
||||
assert len(steps) == 2
|
||||
assert steps['1']['data_outputs'][0]['collection_type'] == 'list:paired'
|
||||
assert steps['1']['outputs'][0]['collection_type'] == 'list:paired'
|
||||
|
||||
@skip_without_tool('collection_type_source')
|
||||
def test_export_editor_subworkflow_collection_type_source(self):
|
||||
@@ -525,7 +525,7 @@ steps:
|
||||
steps = downloaded_workflow['steps']
|
||||
assert len(steps) == 2
|
||||
assert steps['1']['type'] == 'subworkflow'
|
||||
assert steps['1']['data_outputs'][0]['collection_type'] == 'list:paired'
|
||||
assert steps['1']['outputs'][0]['collection_type'] == 'list:paired'
|
||||
|
||||
def test_import_missing_tool(self):
|
||||
workflow = self.workflow_populator.load_workflow_from_resource(name="test_workflow_missing_tool")
|
||||
|
||||
@@ -236,7 +236,8 @@ workflows:
|
||||
workflow_run:
|
||||
|
||||
selectors:
|
||||
input_div: "[step-label='${label}'] .select2-container"
|
||||
input_div: "[step-label='${label}']"
|
||||
input_data_div: "[step-label='${label}'] .select2-container"
|
||||
# TODO: put step labels in the DOM ideally
|
||||
subworkflow_step_icon: ".portlet-title-icon.fa-sitemap + span"
|
||||
|
||||
@@ -264,6 +265,9 @@ workflow_editor:
|
||||
tool_menu_section_link: '#title___workflow__${section_name}__ a span'
|
||||
tool_menu_item_link: 'a#tool-menu-${section_name}-${item_name}'
|
||||
|
||||
connect_icon: 'div.ui-form-element[tour_id="${name}"] .ui-form-connected-icon'
|
||||
collapse_icon: 'div.ui-form-element[tour_id="${name}"] .ui-form-collapsible-icon'
|
||||
|
||||
label_input: "[tour_id='__label'] input"
|
||||
annotation_input: "[tour_id='__annotation'] textarea"
|
||||
|
||||
|
||||
@@ -54,10 +54,7 @@ class WorkflowEditorTestCase(SeleniumTestCase):
|
||||
editor = self.components.workflow_editor
|
||||
|
||||
name = self.workflow_create_new()
|
||||
editor.canvas_body.wait_for_visible()
|
||||
editor.tool_menu.wait_for_visible()
|
||||
editor.tool_menu_section_link(section_name="inputs").wait_for_and_click()
|
||||
editor.tool_menu_item_link(section_name="inputs", item_name="data_input").wait_for_and_click()
|
||||
self.workflow_editor_add_input(item_name="data_input")
|
||||
self.screenshot("workflow_editor_data_input_new")
|
||||
editor.label_input.wait_for_and_send_keys("input1")
|
||||
editor.annotation_input.wait_for_and_send_keys("my cool annotation")
|
||||
@@ -85,16 +82,13 @@ class WorkflowEditorTestCase(SeleniumTestCase):
|
||||
editor = self.components.workflow_editor
|
||||
|
||||
name = self.workflow_create_new()
|
||||
editor.canvas_body.wait_for_visible()
|
||||
editor.tool_menu.wait_for_visible()
|
||||
editor.tool_menu_section_link(section_name="inputs").wait_for_and_click()
|
||||
editor.tool_menu_item_link(section_name="inputs", item_name="data_collection_input").wait_for_and_click()
|
||||
self.screenshot("workflow_editor_data_input_collection_new")
|
||||
self.workflow_editor_add_input(item_name="data_collection_input")
|
||||
self.screenshot("workflow_editor_data_collection_input_new")
|
||||
editor.label_input.wait_for_and_send_keys("input1")
|
||||
editor.annotation_input.wait_for_and_send_keys("my cool annotation")
|
||||
editor.label_input.wait_for_and_click() # Seems to help force the save of whole annotation.
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.screenshot("workflow_editor_data_input_collection_filled_in")
|
||||
self.screenshot("workflow_editor_data_collection_input_filled_in")
|
||||
self.workflow_editor_save_and_close()
|
||||
self.workflow_index_open_with_name(name)
|
||||
data_input_node = editor.node._(label="input1")
|
||||
@@ -107,7 +101,82 @@ class WorkflowEditorTestCase(SeleniumTestCase):
|
||||
|
||||
data_input_node.destroy.wait_for_and_click()
|
||||
data_input_node.wait_for_absent()
|
||||
self.screenshot("workflow_editor_data_input_deleted")
|
||||
self.screenshot("workflow_editor_data_collection_input_deleted")
|
||||
|
||||
@selenium_test
|
||||
def test_integer_input(self):
|
||||
editor = self.components.workflow_editor
|
||||
|
||||
name = self.workflow_create_new()
|
||||
self.workflow_editor_add_input(item_name="parameter_input")
|
||||
self.screenshot("workflow_editor_parameter_input_new")
|
||||
|
||||
editor.label_input.wait_for_and_send_keys("input1")
|
||||
editor.annotation_input.wait_for_and_send_keys("my cool annotation")
|
||||
editor.label_input.wait_for_and_click() # Seems to help force the save of whole annotation.
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
self.screenshot("workflow_editor_parameter_input_filled_in")
|
||||
self.workflow_editor_save_and_close()
|
||||
self.workflow_index_open_with_name(name)
|
||||
data_input_node = editor.node._(label="input1")
|
||||
data_input_node.title.wait_for_and_click()
|
||||
|
||||
label = editor.label_input.wait_for_value()
|
||||
assert label == "input1", label
|
||||
# should work but Galaxy is broken.
|
||||
# assert editor.annotation_input.wait_for_value() == "my cool annotation"
|
||||
|
||||
data_input_node.destroy.wait_for_and_click()
|
||||
data_input_node.wait_for_absent()
|
||||
self.screenshot("workflow_editor_parameter_input_deleted")
|
||||
|
||||
@selenium_test
|
||||
def test_non_data_connections(self):
|
||||
self.open_in_workflow_editor("""
|
||||
class: GalaxyWorkflow
|
||||
inputs:
|
||||
input_int: integer
|
||||
steps:
|
||||
simple_constructs:
|
||||
tool_id: simple_constructs
|
||||
label: tool_exec
|
||||
in:
|
||||
inttest: input_int
|
||||
""")
|
||||
self.screenshot("workflow_editor_parameter_connection_simple")
|
||||
self.assert_connected("input_int#output", "simple_constructs#inttest")
|
||||
|
||||
editor = self.components.workflow_editor
|
||||
|
||||
tool_node = editor.node._(label="simple_constructs")
|
||||
tool_input = tool_node.input_terminal(name="inttest")
|
||||
tool_input.wait_for_and_click()
|
||||
|
||||
editor.connector_destroy_callout.wait_for_and_click()
|
||||
self.assert_not_connected("input_int#output", "simple_constructs#inttest")
|
||||
self.screenshot("workflow_editor_parameter_connection_destroyed")
|
||||
|
||||
# When connected, cannot turn it into a RuntimeValue..
|
||||
collapse_input = editor.collapse_icon(name="inttest")
|
||||
collapse_input.wait_for_absent_or_hidden()
|
||||
|
||||
# If it is disconnected, then can specify as RuntimeValue
|
||||
connect_icon = editor.connect_icon(name="inttest")
|
||||
connect_icon.wait_for_visible()
|
||||
connect_icon.wait_for_and_click()
|
||||
collapse_input.wait_for_visible()
|
||||
|
||||
# Also the connector should disappear
|
||||
tool_input.wait_for_absent_or_hidden()
|
||||
|
||||
# Now make it connected again and watch the requestss
|
||||
connect_icon.wait_for_and_click()
|
||||
|
||||
tool_input.wait_for_visible()
|
||||
collapse_input.wait_for_absent_or_hidden()
|
||||
|
||||
self.workflow_editor_connect("input_int#output", "simple_constructs#inttest", screenshot_partial="workflow_editor_parameter_connection_dragging")
|
||||
self.assert_connected("input_int#output", "simple_constructs#inttest")
|
||||
|
||||
@selenium_test
|
||||
def test_existing_connections(self):
|
||||
@@ -329,6 +398,16 @@ steps:
|
||||
|
||||
return source_id, sink_id
|
||||
|
||||
def workflow_editor_add_input(self, item_name="data_input"):
|
||||
editor = self.components.workflow_editor
|
||||
|
||||
# Make sure we're on the the workflow editor and not clicking the main tool panel.
|
||||
editor.canvas_body.wait_for_visible()
|
||||
|
||||
editor.tool_menu.wait_for_visible()
|
||||
editor.tool_menu_section_link(section_name="inputs").wait_for_and_click()
|
||||
editor.tool_menu_item_link(section_name="inputs", item_name=item_name).wait_for_and_click()
|
||||
|
||||
def workflow_editor_destroy_connection(self, sink):
|
||||
editor = self.components.workflow_editor
|
||||
|
||||
|
||||
@@ -96,6 +96,39 @@ class WorkflowRunTestCase(SeleniumTestCase, UsesHistoryItemAssertions):
|
||||
details = self.dataset_populator.get_history_dataset_details(history_id, hid=output_hid)
|
||||
assert details["name"] == "moocow suffix", details
|
||||
|
||||
@selenium_test
|
||||
@managed_history
|
||||
def test_step_parameter_inputs(self):
|
||||
self.perform_upload(self.get_filename("1.txt"))
|
||||
self.wait_for_history()
|
||||
self.open_in_workflow_run("""
|
||||
class: GalaxyWorkflow
|
||||
inputs:
|
||||
input_int: integer
|
||||
input_data: data
|
||||
steps:
|
||||
simple_constructs:
|
||||
tool_id: simple_constructs
|
||||
label: tool_exec
|
||||
in:
|
||||
inttest: input_int
|
||||
files_0|file: input_data
|
||||
""")
|
||||
workflow_run = self.components.workflow_run
|
||||
input_div_element = workflow_run.input_div(label="input_int").wait_for_visible()
|
||||
input_element = input_div_element.find_element_by_css_selector("input")
|
||||
input_element.clear()
|
||||
input_element.send_keys("12345")
|
||||
|
||||
self.screenshot("workflow_run_step_parameter_input")
|
||||
self.workflow_run_submit()
|
||||
output_hid = 2
|
||||
self.history_panel_wait_for_hid_ok(output_hid, allowed_force_refreshes=1)
|
||||
history_id = self.current_history_id()
|
||||
content = self.dataset_populator.get_history_dataset_content(history_id, hid=output_hid)
|
||||
assert "12345" in content, content
|
||||
assert "chr6_hla_hap2" in content
|
||||
|
||||
@selenium_test
|
||||
@managed_history
|
||||
def test_replacement_parameters_on_subworkflows(self):
|
||||
@@ -160,7 +193,7 @@ class WorkflowRunTestCase(SeleniumTestCase, UsesHistoryItemAssertions):
|
||||
def workflow_run_specify_inputs(self, inputs):
|
||||
workflow_run = self.components.workflow_run
|
||||
for label, value in inputs.items():
|
||||
input_div_element = workflow_run.input_div(label=label).wait_for_visible()
|
||||
input_div_element = workflow_run.input_data_div(label=label).wait_for_visible()
|
||||
self.select2_set_value(input_div_element, "%d: " % value["hid"])
|
||||
|
||||
def workflow_run_with_name(self, name):
|
||||
|
||||
Reference in New Issue
Block a user