mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-24 12:13:33 +08:00
Merge pull request #9151 from jmchilton/simplified_run
Simplified Workflow Run Form
This commit is contained in:
@@ -44,9 +44,22 @@
|
||||
<workflow-run-form
|
||||
ref="runform"
|
||||
:model="model"
|
||||
v-if="!simpleForm"
|
||||
:set-run-button-status="setRunButtonStatus"
|
||||
@submissionSuccess="handleInvocations"
|
||||
/>
|
||||
<div v-else>
|
||||
<workflow-run-form-simple
|
||||
ref="runform"
|
||||
:model="model"
|
||||
:set-run-button-status="setRunButtonStatus"
|
||||
:target-history="simpleFormTargetHistory"
|
||||
:use-job-cache="simpleFormUseJobCache"
|
||||
@submissionSuccess="handleInvocations"
|
||||
/>
|
||||
<!-- Options to default one way or the other, disable if admins want, etc.. -->
|
||||
<a href="#" @click="showAdvanced">Expand to full workflow form.</a>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
@@ -58,6 +71,7 @@ import WaitButton from "components/WaitButton";
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
import WorkflowRunSuccess from "./WorkflowRunSuccess";
|
||||
import WorkflowRunForm from "./WorkflowRunForm";
|
||||
import WorkflowRunFormSimple from "./WorkflowRunFormSimple";
|
||||
import { WorkflowRunModel } from "./model.js";
|
||||
import { errorMessageAsString } from "utils/simple-error";
|
||||
|
||||
@@ -67,9 +81,22 @@ export default {
|
||||
WaitButton,
|
||||
WorkflowRunSuccess,
|
||||
WorkflowRunForm,
|
||||
WorkflowRunFormSimple,
|
||||
},
|
||||
props: {
|
||||
workflowId: { type: String },
|
||||
preferSimpleForm: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
simpleFormTargetHistory: {
|
||||
type: String,
|
||||
default: "current",
|
||||
},
|
||||
simpleFormUseJobCache: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -83,6 +110,7 @@ export default {
|
||||
runButtonWaitText: "",
|
||||
runButtonPercentage: -1,
|
||||
invocations: null,
|
||||
simpleForm: null,
|
||||
model: null,
|
||||
};
|
||||
},
|
||||
@@ -90,6 +118,34 @@ export default {
|
||||
getRunData(this.workflowId)
|
||||
.then((runData) => {
|
||||
const model = new WorkflowRunModel(runData);
|
||||
let simpleForm = this.preferSimpleForm;
|
||||
if (simpleForm) {
|
||||
// These only work with PJA - the API doesn't evaluate them at
|
||||
// all outside that context currently. The main workflow form renders
|
||||
// these dynamically and takes care of all the validation and setup details
|
||||
// on the frontend. If these are implemented on the backend at some
|
||||
// point this restriction can be lifted.
|
||||
if (model.hasReplacementParametersInToolForm) {
|
||||
console.log("cannot render simple workflow form - has ${} values in tool steps");
|
||||
simpleForm = false;
|
||||
}
|
||||
// If there are required parameters in a tool form (a disconnected runtime
|
||||
// input), we have to render the tool form steps and cannot use the
|
||||
// simplified tool form.
|
||||
if (model.hasOpenToolSteps) {
|
||||
console.log(
|
||||
"cannot render simple workflow form - one or more tools have disconnected runtime inputs"
|
||||
);
|
||||
simpleForm = false;
|
||||
}
|
||||
// Just render the whole form for resource request parameters (kind of
|
||||
// niche - I'm not sure anyone is using these currently anyway).
|
||||
if (model.hasWorkflowResourceParameters) {
|
||||
console.log(`Cannot render simple workflow form - workflow resource parameters are configured`);
|
||||
simpleForm = false;
|
||||
}
|
||||
}
|
||||
this.simpleForm = simpleForm;
|
||||
this.model = model;
|
||||
this.hasUpgradeMessages = model.hasUpgradeMessages;
|
||||
this.hasStepVersionChanges = model.hasStepVersionChanges;
|
||||
@@ -97,6 +153,7 @@ export default {
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((response) => {
|
||||
console.log(response);
|
||||
this.error = errorMessageAsString(response);
|
||||
});
|
||||
},
|
||||
@@ -112,6 +169,9 @@ export default {
|
||||
handleInvocations(invocations) {
|
||||
this.invocations = invocations;
|
||||
},
|
||||
showAdvanced() {
|
||||
this.simpleForm = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div ref="form"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from "mvc/form/form-view";
|
||||
import { invokeWorkflow } from "./services";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
setRunButtonStatus: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
targetHistory: {
|
||||
type: String,
|
||||
default: "current",
|
||||
},
|
||||
useJobCache: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: null,
|
||||
inputTypes: {},
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
created() {
|
||||
this.form = new Form(this.formDefinition());
|
||||
this.$nextTick(() => {
|
||||
const el = this.$refs["form"];
|
||||
el.appendChild(this.form.$el[0]);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
formDefinition() {
|
||||
const inputs = [];
|
||||
|
||||
// Add workflow parameters.
|
||||
Object.values(this.model.wpInputs).forEach((input) => {
|
||||
const inputCopy = Object.assign({}, input);
|
||||
// do we want to keep the color if we're not showing steps?
|
||||
inputCopy.color = undefined;
|
||||
inputs.push(inputCopy);
|
||||
this.inputTypes[inputCopy.name] = "replacement_parameter";
|
||||
});
|
||||
|
||||
// Add actual input modules.
|
||||
this.model.steps.forEach((step, i) => {
|
||||
const is_input =
|
||||
["data_input", "data_collection_input", "parameter_input"].indexOf(step.step_type) != -1;
|
||||
if (!is_input) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stepName = new String(step.step_index);
|
||||
const stepLabel = step.step_label || new String(step.step_index + 1);
|
||||
const help = step.annotation;
|
||||
const longFormInput = step.inputs[0];
|
||||
const stepAsInput = Object.assign({}, longFormInput, { name: stepName, help: help, label: stepLabel });
|
||||
// disable collection mapping...
|
||||
stepAsInput.flavor = "module";
|
||||
inputs.push(stepAsInput);
|
||||
this.inputTypes[stepName] = step.step_type;
|
||||
});
|
||||
|
||||
const def = {
|
||||
inputs: inputs,
|
||||
};
|
||||
return def;
|
||||
},
|
||||
execute() {
|
||||
const replacementParams = {};
|
||||
const inputs = {};
|
||||
const formData = this.form.data.create();
|
||||
for (const inputName in formData) {
|
||||
const value = formData[inputName];
|
||||
const inputType = this.inputTypes[inputName];
|
||||
if (inputType == "replacement_parameter") {
|
||||
replacementParams[inputName] = value;
|
||||
} else if (inputType == "data_input" || inputType == "data_collection_input") {
|
||||
inputs[inputName] = value;
|
||||
}
|
||||
}
|
||||
const data = {
|
||||
replacement_dict: replacementParams,
|
||||
inputs: inputs,
|
||||
inputs_by: "step_index",
|
||||
batch: true,
|
||||
use_cached_job: this.useJobCache,
|
||||
};
|
||||
if (this.targetHistory == "current") {
|
||||
data.history_id = this.model.historyId;
|
||||
} else {
|
||||
data.new_history_name = this.model.name;
|
||||
}
|
||||
invokeWorkflow(this.model.workflowId, data)
|
||||
.then((invocations) => {
|
||||
this.$emit("submissionSuccess", invocations);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -10,16 +10,19 @@ export class WorkflowRunModel {
|
||||
this.runData = runData;
|
||||
this.name = runData.name;
|
||||
this.workflowResourceParameters = runData.workflow_resource_parameters;
|
||||
this.hasWorkflowResourceParameters = !_.isEmpty(this.workflowResourceParameters);
|
||||
this.historyId = runData.history_id;
|
||||
this.workflowId = runData.id;
|
||||
|
||||
this.hasUpgradeMessages = runData.has_upgrade_messages;
|
||||
this.hasStepVersionChanges = runData.step_version_changes && runData.step_version_changes.length > 0;
|
||||
this.hasStepVersionChanges = !_.isEmpty(runData.step_version_changes);
|
||||
|
||||
this.steps = [];
|
||||
this.links = [];
|
||||
this.parms = [];
|
||||
this.wpInputs = {};
|
||||
let hasOpenToolSteps = false;
|
||||
let hasReplacementParametersInToolForm = false;
|
||||
|
||||
_.each(runData.steps, (step, i) => {
|
||||
var icon = WorkflowIcons[step.step_type];
|
||||
@@ -130,6 +133,7 @@ export class WorkflowRunModel {
|
||||
_.each(this.steps, (step, i) => {
|
||||
_.each(this.parms[i], (input, name) => {
|
||||
_handleWorkflowParameter(input.value, (wp_input) => {
|
||||
hasReplacementParametersInToolForm = true;
|
||||
wp_input.links.push(step);
|
||||
input.wp_linked = true;
|
||||
input.type = "text";
|
||||
@@ -166,6 +170,7 @@ export class WorkflowRunModel {
|
||||
(input.value && input.value.__class__ == "RuntimeValue" && !input.step_linked)
|
||||
) {
|
||||
step.collapsed = false;
|
||||
hasOpenToolSteps = true;
|
||||
}
|
||||
if (is_runtime_value) {
|
||||
input.value = null;
|
||||
@@ -180,6 +185,8 @@ export class WorkflowRunModel {
|
||||
});
|
||||
}
|
||||
});
|
||||
this.hasOpenToolSteps = hasOpenToolSteps;
|
||||
this.hasReplacementParametersInToolForm = hasReplacementParametersInToolForm;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -386,6 +386,20 @@ export const getAnalysisRouter = (Galaxy) =>
|
||||
/** load workflow by its url in run mode */
|
||||
_loadWorkflow: function () {
|
||||
const workflowId = QueryStringParsing.get("id");
|
||||
this._display_vue_helper(WorkflowRun, { workflowId: workflowId }, "workflow");
|
||||
const Galaxy = getGalaxyInstance();
|
||||
let preferSimpleForm = Galaxy.config.simplified_workflow_run_ui == "prefer";
|
||||
const preferSimpleFormOverride = QueryStringParsing.get("simplified_workflow_run_ui");
|
||||
if (preferSimpleFormOverride == "prefer") {
|
||||
preferSimpleForm = true;
|
||||
}
|
||||
const simpleFormTargetHistory = Galaxy.config.simplified_workflow_run_ui_target_history;
|
||||
const simpleFormUseJobCache = Galaxy.config.simplified_workflow_run_ui_job_cache == "on";
|
||||
const props = {
|
||||
workflowId,
|
||||
preferSimpleForm,
|
||||
simpleFormTargetHistory,
|
||||
simpleFormUseJobCache,
|
||||
};
|
||||
this._display_vue_helper(WorkflowRun, props, "workflow");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -79,6 +79,9 @@ class ConfigSerializer(base.ModelSerializer):
|
||||
'ga_code' : _use_config,
|
||||
'enable_unique_workflow_defaults' : _use_config,
|
||||
'enable_beta_markdown_export' : _use_config,
|
||||
'simplified_workflow_run_ui' : _use_config,
|
||||
'simplified_workflow_run_ui_target_history': _use_config,
|
||||
'simplified_workflow_run_ui_job_cache': _use_config,
|
||||
'has_user_tool_filters' : _defaults_to(False),
|
||||
# TODO: is there no 'correct' way to get an api url? controller='api', action='tools' is a hack
|
||||
# at any rate: the following works with path_prefix but is still brittle
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import copy
|
||||
import itertools
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from collections import namedtuple, OrderedDict
|
||||
|
||||
from galaxy import (
|
||||
exceptions,
|
||||
@@ -14,61 +14,123 @@ from . import visit_input_values
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
WorkflowParameterExpansion = namedtuple('WorkflowParameterExpansion', ['param_combinations', 'param_keys', 'input_combinations'])
|
||||
|
||||
def expand_workflow_inputs(inputs):
|
||||
|
||||
class ParamKey(object):
|
||||
|
||||
def __init__(self, step_id, key):
|
||||
self.step_id = step_id
|
||||
self.key = key
|
||||
|
||||
|
||||
class InputKey(object):
|
||||
|
||||
def __init__(self, input_id):
|
||||
self.input_id = input_id
|
||||
|
||||
|
||||
def expand_workflow_inputs(param_inputs, inputs=None):
|
||||
"""
|
||||
Expands incoming encoded multiple payloads, into the set of all individual payload combinations
|
||||
>>> params, param_keys = expand_workflow_inputs({'1': {'input': {'batch': True, 'product': True, 'values': [{'hid': '1'}, {'hid': '2'}] }}})
|
||||
>>> print(["%s" % (p['1']['input']['hid']) for p in params])
|
||||
>>> expansion = expand_workflow_inputs({'1': {'input': {'batch': True, 'product': True, 'values': [{'hid': '1'}, {'hid': '2'}] }}})
|
||||
>>> print(["%s" % (p['1']['input']['hid']) for p in expansion.param_combinations])
|
||||
['1', '2']
|
||||
>>> params, param_keys = expand_workflow_inputs({'1': {'input': {'batch': True, 'values': [{'hid': '1'}, {'hid': '2'}] }}})
|
||||
>>> print(["%s" % (p['1']['input']['hid']) for p in params])
|
||||
>>> expansion = expand_workflow_inputs({'1': {'input': {'batch': True, 'values': [{'hid': '1'}, {'hid': '2'}] }}})
|
||||
>>> print(["%s" % (p['1']['input']['hid']) for p in expansion.param_combinations])
|
||||
['1', '2']
|
||||
>>> params, param_keys = expand_workflow_inputs({'1': {'input': {'batch': True, 'values': [{'hid': '1'}, {'hid': '2'}] }}, '2': {'input': {'batch': True, 'values': [{'hid': '3'}, {'hid': '4'}] }}})
|
||||
>>> print(["%s%s" % (p['1']['input']['hid'], p['2']['input']['hid']) for p in params])
|
||||
>>> expansion = expand_workflow_inputs({'1': {'input': {'batch': True, 'values': [{'hid': '1'}, {'hid': '2'}] }}, '2': {'input': {'batch': True, 'values': [{'hid': '3'}, {'hid': '4'}] }}})
|
||||
>>> print(["%s%s" % (p['1']['input']['hid'], p['2']['input']['hid']) for p in expansion.param_combinations])
|
||||
['13', '24']
|
||||
>>> params, param_keys = expand_workflow_inputs({'1': {'input': {'batch': True, 'product': True, 'values': [{'hid': '1'}, {'hid': '2'}] }}, '2': {'input': {'batch': True, 'values': [{'hid': '3'}, {'hid': '4'}, {'hid': '5'}] }}})
|
||||
>>> print(["%s%s" % (p['1']['input']['hid'], p['2']['input']['hid']) for p in params])
|
||||
>>> expansion = expand_workflow_inputs({'1': {'input': {'batch': True, 'product': True, 'values': [{'hid': '1'}, {'hid': '2'}] }}, '2': {'input': {'batch': True, 'values': [{'hid': '3'}, {'hid': '4'}, {'hid': '5'}] }}})
|
||||
>>> print(["%s%s" % (p['1']['input']['hid'], p['2']['input']['hid']) for p in expansion.param_combinations])
|
||||
['13', '23', '14', '24', '15', '25']
|
||||
>>> params, param_keys = expand_workflow_inputs({'1': {'input': {'batch': True, 'product': True, 'values': [{'hid': '1'}, {'hid': '2'}] }}, '2': {'input': {'batch': True, 'product': True, 'values': [{'hid': '3'}, {'hid': '4'}, {'hid': '5'}] }}, '3': {'input': {'batch': True, 'product': True, 'values': [{'hid': '6'}, {'hid': '7'}, {'hid': '8'}] }}})
|
||||
>>> print(["%s%s%s" % (p['1']['input']['hid'], p['2']['input']['hid'], p['3']['input']['hid']) for p in params])
|
||||
>>> expansion = expand_workflow_inputs({'1': {'input': {'batch': True, 'product': True, 'values': [{'hid': '1'}, {'hid': '2'}] }}, '2': {'input': {'batch': True, 'product': True, 'values': [{'hid': '3'}, {'hid': '4'}, {'hid': '5'}] }}, '3': {'input': {'batch': True, 'product': True, 'values': [{'hid': '6'}, {'hid': '7'}, {'hid': '8'}] }}})
|
||||
>>> print(["%s%s%s" % (p['1']['input']['hid'], p['2']['input']['hid'], p['3']['input']['hid']) for p in expansion.param_combinations])
|
||||
['136', '137', '138', '146', '147', '148', '156', '157', '158', '236', '237', '238', '246', '247', '248', '256', '257', '258']
|
||||
>>> expansion = expand_workflow_inputs(None, inputs={'myinput': {'batch': True, 'product': True, 'values': [{'hid': '1'}, {'hid': '2'}] }})
|
||||
>>> print(["%s" % (p['myinput']['hid']) for p in expansion.input_combinations])
|
||||
['1', '2']
|
||||
"""
|
||||
param_inputs = param_inputs or {}
|
||||
inputs = inputs or {}
|
||||
|
||||
linked_n = None
|
||||
linked = []
|
||||
product = []
|
||||
linked_keys = []
|
||||
product_keys = []
|
||||
for step_id, step in sorted(inputs.items()):
|
||||
|
||||
def is_batch(value):
|
||||
return isinstance(value, dict) and 'batch' in value and value['batch'] is True and 'values' in value and isinstance(value['values'], list)
|
||||
|
||||
for step_id, step in sorted(param_inputs.items()):
|
||||
for key, value in sorted(step.items()):
|
||||
if isinstance(value, dict) and 'batch' in value and value['batch'] is True and 'values' in value and isinstance(value['values'], list):
|
||||
if is_batch(value):
|
||||
nval = len(value['values'])
|
||||
if 'product' in value and value['product'] is True:
|
||||
product.append(value['values'])
|
||||
product_keys.append((step_id, key))
|
||||
product_keys.append(ParamKey(step_id, key))
|
||||
else:
|
||||
if linked_n is None:
|
||||
linked_n = nval
|
||||
elif linked_n != nval or nval == 0:
|
||||
raise exceptions.RequestParameterInvalidException('Failed to match linked batch selections. Please select equal number of data files.')
|
||||
linked.append(value['values'])
|
||||
linked_keys.append((step_id, key))
|
||||
params = []
|
||||
linked_keys.append(ParamKey(step_id, key))
|
||||
|
||||
# Force it to a list to allow modification...
|
||||
input_items = list(inputs.items())
|
||||
for input_id, value in input_items:
|
||||
if is_batch(value):
|
||||
nval = len(value['values'])
|
||||
if 'product' in value and value['product'] is True:
|
||||
product.append(value['values'])
|
||||
product_keys.append(InputKey(input_id))
|
||||
else:
|
||||
if linked_n is None:
|
||||
linked_n = nval
|
||||
elif linked_n != nval or nval == 0:
|
||||
raise exceptions.RequestParameterInvalidException('Failed to match linked batch selections. Please select equal number of data files.')
|
||||
linked.append(value['values'])
|
||||
linked_keys.append(InputKey(input_id))
|
||||
elif isinstance(value, dict) and 'batch' in value:
|
||||
# remove batch wrapper and render simplified input form rest of workflow
|
||||
# code expects
|
||||
inputs[input_id] = value['values'][0]
|
||||
|
||||
param_combinations = []
|
||||
input_combinations = []
|
||||
params_keys = []
|
||||
linked = linked or [[None]]
|
||||
product = product or [[None]]
|
||||
linked_keys = linked_keys or [(None, None)]
|
||||
product_keys = product_keys or [(None, None)]
|
||||
linked_keys = linked_keys or [None]
|
||||
product_keys = product_keys or [None]
|
||||
for linked_values, product_values in itertools.product(zip(*linked), itertools.product(*product)):
|
||||
new_params = copy.deepcopy(inputs)
|
||||
new_params = copy.deepcopy(param_inputs)
|
||||
new_inputs = copy.deepcopy(inputs)
|
||||
new_keys = []
|
||||
for (step_id, key), value in list(zip(linked_keys, linked_values)) + list(zip(product_keys, product_values)):
|
||||
if step_id is not None:
|
||||
new_params[step_id][key] = value
|
||||
new_keys.append(str(value['hid']))
|
||||
for input_key, value in list(zip(linked_keys, linked_values)) + list(zip(product_keys, product_values)):
|
||||
if input_key:
|
||||
if isinstance(input_key, ParamKey):
|
||||
step_id = input_key.step_id
|
||||
key = input_key.key
|
||||
assert step_id is not None
|
||||
new_params[step_id][key] = value
|
||||
if 'hid' in value:
|
||||
new_keys.append(str(value['hid']))
|
||||
else:
|
||||
input_id = input_key.input_id
|
||||
assert input_id is not None
|
||||
new_inputs[input_id] = value
|
||||
if 'hid' in value:
|
||||
new_keys.append(str(value['hid']))
|
||||
|
||||
params_keys.append(new_keys)
|
||||
params.append(new_params)
|
||||
return params, params_keys
|
||||
param_combinations.append(new_params)
|
||||
input_combinations.append(new_inputs)
|
||||
|
||||
return WorkflowParameterExpansion(param_combinations, params_keys, input_combinations)
|
||||
|
||||
|
||||
def process_key(incoming_key, incoming_value, d):
|
||||
|
||||
@@ -2436,6 +2436,38 @@ mapping:
|
||||
When false, the most recently added compatible item in the history will
|
||||
be used for each "Set at Runtime" input, independent of others in the workflow.
|
||||
|
||||
simplified_workflow_run_ui:
|
||||
type: str
|
||||
default: 'prefer'
|
||||
enum: ['off', 'prefer']
|
||||
required: false
|
||||
desc: |
|
||||
If set to 'off' by default, always use the traditional workflow form that renders
|
||||
all steps in the GUI and serializes the tool state of all steps during
|
||||
invocation. Set to 'prefer' to default to a simplified workflow UI that
|
||||
only renders the inputs if possible (the workflow must have no disconnected
|
||||
runtime inputs and not replacement parameters within tool steps). In the
|
||||
future 'force' may be added an option for Galaskio-style servers that should
|
||||
only render simplified workflows.
|
||||
|
||||
simplified_workflow_run_ui_target_history:
|
||||
type: str
|
||||
default: 'current'
|
||||
enum: ['current', 'new']
|
||||
required: false
|
||||
desc: |
|
||||
When the simplified workflow run form is rendered, should the invocation outputs
|
||||
be sent to the 'current' history or a 'new' history.
|
||||
|
||||
simplified_workflow_run_ui_job_cache:
|
||||
type: str
|
||||
default: 'off'
|
||||
enum: ['on', 'off']
|
||||
required: false
|
||||
desc: |
|
||||
When the simplified workflow run form is rendered, should the invocation use job
|
||||
caching. This isn't a boolean so an option for 'show-selection' can be added later.
|
||||
|
||||
myexperiment_target_url:
|
||||
type: str
|
||||
default: www.myexperiment.org:80
|
||||
|
||||
@@ -246,25 +246,27 @@ def build_workflow_run_configs(trans, workflow, payload):
|
||||
|
||||
run_configs = []
|
||||
unexpanded_param_map = _normalize_step_parameters(workflow.steps, raw_parameters, legacy=legacy, already_normalized=already_normalized)
|
||||
expanded_params, expanded_param_keys = expand_workflow_inputs(unexpanded_param_map)
|
||||
for index, param_map in enumerate(expanded_params):
|
||||
history = _get_target_history(trans, workflow, payload, expanded_param_keys, index)
|
||||
inputs = payload.get('inputs', None)
|
||||
inputs_by = payload.get('inputs_by', None)
|
||||
# New default is to reference steps by index of workflow step
|
||||
# which is intrinsic to the workflow and independent of the state
|
||||
# of Galaxy at the time of workflow import.
|
||||
default_inputs_by = 'step_index|step_uuid'
|
||||
if inputs is None:
|
||||
# Default to legacy behavior - read ds_map and reference steps
|
||||
# by unencoded step id (a raw database id).
|
||||
inputs = payload.get('ds_map', {})
|
||||
if legacy:
|
||||
default_inputs_by = 'step_id|step_uuid'
|
||||
inputs_by = inputs_by or default_inputs_by
|
||||
else:
|
||||
inputs = inputs or {}
|
||||
unexpanded_inputs = payload.get('inputs', None)
|
||||
inputs_by = payload.get('inputs_by', None)
|
||||
# New default is to reference steps by index of workflow step
|
||||
# which is intrinsic to the workflow and independent of the state
|
||||
# of Galaxy at the time of workflow import.
|
||||
default_inputs_by = 'step_index|step_uuid'
|
||||
inputs_by = inputs_by or default_inputs_by
|
||||
|
||||
if unexpanded_inputs is None:
|
||||
# Default to legacy behavior - read ds_map and reference steps
|
||||
# by unencoded step id (a raw database id).
|
||||
unexpanded_inputs = payload.get('ds_map', {})
|
||||
if legacy:
|
||||
default_inputs_by = 'step_id|step_uuid'
|
||||
inputs_by = inputs_by or default_inputs_by
|
||||
else:
|
||||
unexpanded_inputs = unexpanded_inputs or {}
|
||||
|
||||
expanded_params, expanded_param_keys, expanded_inputs = expand_workflow_inputs(unexpanded_param_map, unexpanded_inputs)
|
||||
for index, (param_map, inputs) in enumerate(zip(expanded_params, expanded_inputs)):
|
||||
history = _get_target_history(trans, workflow, payload, expanded_param_keys, index)
|
||||
if inputs or not already_normalized:
|
||||
normalized_inputs = _normalize_inputs(workflow.steps, inputs, inputs_by)
|
||||
else:
|
||||
|
||||
@@ -3518,6 +3518,50 @@ steps:
|
||||
self.assertEqual(r3, t3)
|
||||
self.assertEqual(r4, t4)
|
||||
|
||||
@skip_without_tool("cat1")
|
||||
@skip_without_tool("addValue")
|
||||
def test_run_batch_inputs(self):
|
||||
workflow = self.workflow_populator.load_workflow_from_resource("test_workflow_batch")
|
||||
workflow_id = self.workflow_populator.create_workflow(workflow)
|
||||
with self.dataset_populator.test_history() as history_id:
|
||||
hda1 = self.dataset_populator.new_dataset(history_id, content="1 2 3")
|
||||
hda2 = self.dataset_populator.new_dataset(history_id, content="4 5 6")
|
||||
hda3 = self.dataset_populator.new_dataset(history_id, content="7 8 9")
|
||||
hda4 = self.dataset_populator.new_dataset(history_id, content="10 11 12")
|
||||
inputs = {
|
||||
"coolinput": {"batch": True, "values": [{"id" : hda1.get("id"), "hid": hda1.get("hid"), "src": "hda"},
|
||||
{"id" : hda2.get("id"), "hid": hda2.get("hid"), "src": "hda"},
|
||||
{"id" : hda3.get("id"), "hid": hda2.get("hid"), "src": "hda"},
|
||||
{"id" : hda4.get("id"), "hid": hda2.get("hid"), "src": "hda"}]}
|
||||
}
|
||||
parameters = {
|
||||
"1": {"input": {"batch": False, "values": [{"id" : hda1.get("id"), "hid": hda1.get("hid"), "src": "hda"}]}, "exp": "2"}
|
||||
}
|
||||
workflow_request = {
|
||||
"history_id" : history_id,
|
||||
"batch" : True,
|
||||
"inputs" : dumps(inputs),
|
||||
"inputs_by" : "name",
|
||||
"parameters_normalized": True,
|
||||
"parameters" : dumps(parameters),
|
||||
}
|
||||
invocation_response = self._post("workflows/%s/usage" % workflow_id, data=workflow_request)
|
||||
self._assert_status_code_is(invocation_response, 200)
|
||||
time.sleep(5)
|
||||
self.dataset_populator.wait_for_history(history_id, assert_ok=True)
|
||||
r1 = "1 2 3\t1\n1 2 3\t2\n"
|
||||
r2 = "4 5 6\t1\n1 2 3\t2\n"
|
||||
r3 = "7 8 9\t1\n1 2 3\t2\n"
|
||||
r4 = "10 11 12\t1\n1 2 3\t2\n"
|
||||
t1 = self.dataset_populator.get_history_dataset_content(history_id, hid=7)
|
||||
t2 = self.dataset_populator.get_history_dataset_content(history_id, hid=10)
|
||||
t3 = self.dataset_populator.get_history_dataset_content(history_id, hid=13)
|
||||
t4 = self.dataset_populator.get_history_dataset_content(history_id, hid=16)
|
||||
self.assertEqual(r1, t1)
|
||||
self.assertEqual(r2, t2)
|
||||
self.assertEqual(r3, t3)
|
||||
self.assertEqual(r4, t4)
|
||||
|
||||
@skip_without_tool("validation_default")
|
||||
def test_parameter_substitution_sanitization(self):
|
||||
substitions = dict(input1="\" ; echo \"moo")
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"name": "Input Dataset"
|
||||
}
|
||||
],
|
||||
"label": null,
|
||||
"label": "coolinput",
|
||||
"name": "Input dataset",
|
||||
"outputs": [],
|
||||
"position": {
|
||||
|
||||
@@ -254,6 +254,7 @@ def setup_galaxy_config(
|
||||
logging=LOGGING_CONFIG_DEFAULT,
|
||||
monitor_thread_join_timeout=5,
|
||||
object_store_store_by="uuid",
|
||||
simplified_workflow_run_ui="off",
|
||||
)
|
||||
if not use_shared_connection_for_amqp:
|
||||
config["amqp_internal_connection"] = "sqlalchemy+sqlite:///%s?isolation_level=IMMEDIATE" % os.path.join(tmpdir, "control.sqlite")
|
||||
|
||||
@@ -198,6 +198,7 @@
|
||||
<tool file="for_workflows/count_list.xml" />
|
||||
<tool file="for_workflows/count_multi_file.xml" />
|
||||
<tool file="for_workflows/create_input_collection.xml" />
|
||||
<tool file="for_workflows/randomlines.xml" />
|
||||
|
||||
<section id="filter" name="For Tours">
|
||||
<tool file="for_tours/filtering.xml" />
|
||||
|
||||
@@ -195,6 +195,7 @@ DO_NOT_TEST = [
|
||||
'object_store_store_by', # broken: default overridden
|
||||
'pretty_datetime_format', # untestable; refactor config/__init__ to test
|
||||
'retry_metadata_internally', # broken: default overridden
|
||||
'simplified_workflow_run_ui', # set to off in testing
|
||||
'statsd_host', # broken: default overridden with empty string
|
||||
'template_cache_path', # may or may not be able to test; may be broken
|
||||
'tool_config_file', # default not used; may or may not be testable
|
||||
|
||||
Reference in New Issue
Block a user