mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Switch the simplified and legacy workflow run forms from a JSON-encoded
``options_pagination`` round-trip on ``/api/workflows/{id}/download?style=run``
to a direct ``GET /api/histories/{history_id}/contents`` call per scroll /
search. Workflow ``data`` / ``data_collection`` inputs cannot use the
Python-only filter knobs that tool ``data`` parameters can (``options_filter_attribute``,
``data_destination``), so SQL-level filtering on extension + state + name/hid
is sufficient — no need for the workflow API to mediate every page fetch.
The expanded "acceptable extensions" set (formats ∪ implicit-conversion
sources, already computed by ``BaseDataToolParameter._acceptable_extensions``)
is shipped once in the initial run spec; the client passes it to the contents
API as ``q=extension-in&qv=ext1,ext2,...``.
Add SQL extension filtering to ``History.paginated_active_dataset_collections``
and to ``HistoryContentsFilters`` for HDCAs: a correlated subquery walks up to
three levels of nesting (``list``, ``paired``, ``list:paired``, ``list:list``,
``list:list:paired``) and rejects collections that contain any leaf HDA whose
extension is outside the acceptable set — matching the canonical
``SummaryDatasetCollectionMatcher`` semantics without a schema migration.
350 lines
14 KiB
Vue
350 lines
14 KiB
Vue
<template>
|
|
<div v-if="currentUser && currentHistoryId" class="workflow-expanded-form">
|
|
<BAlert v-if="!canRunOnHistory" variant="warning" show>
|
|
<span v-localize>
|
|
The workflow cannot run because the current history is immutable. Please select a different history or
|
|
send the results to a new one.
|
|
</span>
|
|
</BAlert>
|
|
<div class="h4 clearfix mb-3">
|
|
<b>Workflow: {{ model.name }}</b> <i>(version: {{ model.runData.version + 1 }})</i>
|
|
<div class="float-right d-flex flex-gapx-1">
|
|
<b-button
|
|
v-if="!disableSimpleForm"
|
|
v-g-tooltip.hover
|
|
variant="link"
|
|
class="text-decoration-none"
|
|
title="Use simplified run form instead"
|
|
@click="$emit('showSimple')">
|
|
<span class="fas fa-arrow-left" /> Simple Form
|
|
</b-button>
|
|
<ButtonSpinner
|
|
id="run-workflow"
|
|
title="Run Workflow"
|
|
:tooltip="runButtonTooltip"
|
|
:disabled="!canRunOnHistory || hasCredentialErrors"
|
|
:wait="showExecuting"
|
|
@onClick="onExecute" />
|
|
</div>
|
|
</div>
|
|
|
|
<BAlert v-if="disableSimpleFormReason" show variant="warning">
|
|
This is the legacy workflow run form.
|
|
<span v-if="disableSimpleFormReason === 'hasReplacementParameters'">
|
|
This workflow contains parameters in tool steps that require advanced handling. The simplified form does
|
|
not support these parameters.
|
|
</span>
|
|
<span v-else-if="disableSimpleFormReason === 'hasDisconnectedInputs'">
|
|
One or more tools in this workflow have required inputs that are not connected to other steps. The
|
|
simplified form cannot handle disconnected runtime inputs.
|
|
</span>
|
|
<span v-else-if="disableSimpleFormReason === 'hasWorkflowResourceParameters'">
|
|
This workflow is configured with resource request parameters. The simplified form does not support
|
|
workflows with resource options.
|
|
</span>
|
|
</BAlert>
|
|
|
|
<WorkflowCredentials v-if="credentialTools.length" :tool-identifiers="credentialTools" />
|
|
|
|
<FormCard v-if="wpInputsAvailable" title="Workflow Parameters">
|
|
<template v-slot:body>
|
|
<FormDisplay :inputs="wpInputs" @onChange="onWpInputs" />
|
|
</template>
|
|
</FormCard>
|
|
<FormCard title="History Options">
|
|
<template v-slot:body>
|
|
<FormDisplay :inputs="historyInputs" @onChange="onHistoryInputs" />
|
|
</template>
|
|
</FormCard>
|
|
<FormCard title="Job re-use Options">
|
|
<template v-slot:body>
|
|
<FormElement
|
|
v-model="useCachedJobs"
|
|
title="Attempt to re-use jobs with identical parameters?"
|
|
help="This may skip executing jobs that you have already run."
|
|
type="boolean" />
|
|
</template>
|
|
</FormCard>
|
|
<OnCompleteActions v-model="onCompleteActions" />
|
|
<FormCard v-if="resourceInputsAvailable" title="Workflow Resource Options">
|
|
<template v-slot:body>
|
|
<FormDisplay :inputs="resourceInputs" @onChange="onResourceInputs" />
|
|
</template>
|
|
</FormCard>
|
|
<div v-for="step in model.steps" :key="step.index">
|
|
<WorkflowRunDefaultStep
|
|
v-if="step.step_type == 'tool' || step.step_type == 'subworkflow'"
|
|
:model="step"
|
|
:replace-params="getReplaceParams(step.inputs)"
|
|
:validation-scroll-to="getValidationScrollTo(step.index)"
|
|
:history-id="currentHistoryId"
|
|
@onChange="onToolStepInputs"
|
|
@onValidation="onValidation" />
|
|
<WorkflowRunInputStep
|
|
v-else
|
|
:model="step"
|
|
:validation-scroll-to="getValidationScrollTo(step.index)"
|
|
:history-id="currentHistoryId"
|
|
@onChange="onDefaultStepInputs"
|
|
@onValidation="onValidation" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { BAlert } from "bootstrap-vue";
|
|
import { mapState } from "pinia";
|
|
|
|
import { useUserMultiToolCredentials } from "@/composables/userMultiToolCredentials";
|
|
import { useHistoryStore } from "@/stores/historyStore";
|
|
import { useToolsServiceCredentialsDefinitionsStore } from "@/stores/toolsServiceCredentialsDefinitionsStore";
|
|
import { useUserStore } from "@/stores/userStore";
|
|
|
|
import { getReplacements } from "./model";
|
|
import { invokeWorkflow } from "./services";
|
|
|
|
import WorkflowRunDefaultStep from "./WorkflowRunDefaultStep.vue";
|
|
import WorkflowRunInputStep from "./WorkflowRunInputStep.vue";
|
|
import ButtonSpinner from "@/components/Common/ButtonSpinner.vue";
|
|
import FormCard from "@/components/Form/FormCard.vue";
|
|
import FormDisplay from "@/components/Form/FormDisplay.vue";
|
|
import FormElement from "@/components/Form/FormElement.vue";
|
|
import OnCompleteActions from "@/components/Workflow/Run/OnCompleteActions.vue";
|
|
import WorkflowCredentials from "@/components/Workflow/Run/WorkflowCredentials.vue";
|
|
|
|
export default {
|
|
components: {
|
|
BAlert,
|
|
ButtonSpinner,
|
|
FormDisplay,
|
|
FormCard,
|
|
FormElement,
|
|
OnCompleteActions,
|
|
WorkflowCredentials,
|
|
WorkflowRunDefaultStep,
|
|
WorkflowRunInputStep,
|
|
},
|
|
props: {
|
|
model: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
canMutateCurrentHistory: {
|
|
type: Boolean,
|
|
required: true,
|
|
},
|
|
disableSimpleForm: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
disableSimpleFormReason: {
|
|
type: String,
|
|
default: undefined,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
showExecuting: false,
|
|
stepData: {},
|
|
stepValidations: {},
|
|
stepScrollTo: {},
|
|
wpData: {},
|
|
inputs: {},
|
|
historyData: {},
|
|
useCachedJobs: false,
|
|
onCompleteActions: [],
|
|
historyInputs: [
|
|
{
|
|
type: "conditional",
|
|
name: "new_history",
|
|
test_param: {
|
|
name: "check",
|
|
label: "Send results to a new history",
|
|
type: "boolean",
|
|
value: "false",
|
|
help: "",
|
|
},
|
|
cases: [
|
|
{
|
|
value: "true",
|
|
inputs: [
|
|
{
|
|
name: "name",
|
|
label: "History name",
|
|
type: "text",
|
|
value: this.model.name,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
value: "false",
|
|
inputs: [],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
},
|
|
computed: {
|
|
...mapState(useUserStore, ["currentUser"]),
|
|
...mapState(useHistoryStore, ["currentHistoryId"]),
|
|
credentialTools() {
|
|
return this.model.steps
|
|
.filter((step) => step.step_type === "tool" && step.credentials?.length)
|
|
.map((step) => {
|
|
const { setToolServiceCredentialsDefinitionFor } = useToolsServiceCredentialsDefinitionsStore();
|
|
setToolServiceCredentialsDefinitionFor(step.id, step.version, step.credentials);
|
|
|
|
return {
|
|
toolId: step.id,
|
|
toolVersion: step.version,
|
|
};
|
|
});
|
|
},
|
|
resourceInputsAvailable() {
|
|
return this.resourceInputs.length > 0;
|
|
},
|
|
resourceInputs() {
|
|
return this.toArray(this.model.workflowResourceParameters);
|
|
},
|
|
wpInputsAvailable() {
|
|
return this.wpInputs.length > 0;
|
|
},
|
|
wpInputs() {
|
|
return this.toArray(this.model.wpInputs);
|
|
},
|
|
shouldRunOnNewHistory() {
|
|
return Boolean(this.historyData["new_history|name"]);
|
|
},
|
|
canRunOnHistory() {
|
|
return this.shouldRunOnNewHistory || this.canMutateCurrentHistory;
|
|
},
|
|
hasCredentialErrors() {
|
|
if (this.credentialTools.length) {
|
|
const { hasUserProvidedAllRequiredToolsServiceCredentials } = useUserMultiToolCredentials(
|
|
this.credentialTools,
|
|
);
|
|
return !hasUserProvidedAllRequiredToolsServiceCredentials.value;
|
|
}
|
|
return false;
|
|
},
|
|
runButtonTooltip() {
|
|
if (this.hasCredentialErrors) {
|
|
return "Please provide all required credentials before running the workflow.";
|
|
}
|
|
return "Run workflow";
|
|
},
|
|
},
|
|
methods: {
|
|
getReplaceParams(inputs) {
|
|
return getReplacements(inputs, this.stepData, this.wpData);
|
|
},
|
|
getValidationScrollTo(stepId) {
|
|
if (this.stepScrollTo.stepId == stepId) {
|
|
return this.stepScrollTo.stepError;
|
|
}
|
|
return [];
|
|
},
|
|
onDefaultStepInputs(stepId, data) {
|
|
this.inputs[stepId] = data.input;
|
|
},
|
|
onToolStepInputs(stepId, data) {
|
|
this.stepData[stepId] = data;
|
|
},
|
|
onHistoryInputs(data) {
|
|
this.historyData = data;
|
|
},
|
|
onResourceInputs(data) {
|
|
this.resourceData = data;
|
|
},
|
|
onWpInputs(data) {
|
|
this.wpData = data;
|
|
},
|
|
onValidation(stepId, validation) {
|
|
this.stepValidations[stepId] = validation;
|
|
},
|
|
onExecute() {
|
|
for (const [stepId, stepValidation] of Object.entries(this.stepValidations)) {
|
|
if (stepValidation) {
|
|
this.stepScrollTo = {
|
|
stepId: stepId,
|
|
stepError: stepValidation.slice(),
|
|
};
|
|
return;
|
|
}
|
|
}
|
|
|
|
const parameters = {};
|
|
Object.entries(this.stepData).forEach(([stepId, stepData]) => {
|
|
const stepDataFiltered = {};
|
|
Object.entries(stepData).forEach(([inputName, inputValue]) => {
|
|
if (!this.model.isConnected(stepId, inputName)) {
|
|
stepDataFiltered[inputName] = inputValue;
|
|
}
|
|
});
|
|
parameters[stepId] = stepDataFiltered;
|
|
});
|
|
|
|
const jobDef = {
|
|
new_history_name: this.historyData["new_history|name"] ? this.historyData["new_history|name"] : null,
|
|
history_id: !this.historyData["new_history|name"] ? this.model.historyId : null,
|
|
resource_params: this.resourceData,
|
|
replacement_params: this.wpData,
|
|
use_cached_job: this.useCachedJobs,
|
|
inputs: this.inputs,
|
|
parameters: parameters,
|
|
// Tool form will submit flat maps for each parameter
|
|
// (e.g. "repeat_0|cond|param": "foo" instead of nested
|
|
// data structures).
|
|
parameters_normalized: true,
|
|
// Tool form always wants a list of invocations back
|
|
// so that inputs can be batched.
|
|
batch: true,
|
|
// the user is already warned if tool versions are wrong,
|
|
// they can still choose to invoke the workflow anyway.
|
|
require_exact_tool_versions: false,
|
|
version: this.model.runData.version,
|
|
// Completion actions to run when workflow finishes
|
|
on_complete: this.onCompleteActions.length > 0 ? this.onCompleteActions : null,
|
|
};
|
|
|
|
console.debug("WorkflowRunForm::onExecute()", "Ready for submission.", jobDef);
|
|
this.showExecuting = true;
|
|
invokeWorkflow(this.model.workflowId, jobDef)
|
|
.then((invocations) => {
|
|
console.debug("WorkflowRunForm::onExecute()", "Submission successful.", invocations);
|
|
this.showExecuting = false;
|
|
this.$emit("submissionSuccess", invocations);
|
|
})
|
|
.catch((e) => {
|
|
console.debug("WorkflowRunForm::onExecute()", "Submission failed.", e);
|
|
this.showExecuting = false;
|
|
const errorData = e && e.response && e.response.data && e.response.data.err_data;
|
|
if (errorData) {
|
|
try {
|
|
const errorEntries = Object.entries(errorData);
|
|
this.stepScrollTo = {
|
|
stepId: errorEntries[0][0],
|
|
stepError: Object.entries(errorEntries[0][1])[0],
|
|
};
|
|
} catch (errorFormatting) {
|
|
console.debug(
|
|
errorFormatting,
|
|
"WorkflowRunForm::onExecute()",
|
|
"Invalid server error response format.",
|
|
errorData,
|
|
);
|
|
this.$emit("submissionError", e);
|
|
}
|
|
} else {
|
|
this.$emit("submissionError", e);
|
|
}
|
|
});
|
|
},
|
|
toArray(obj) {
|
|
return obj ? Object.keys(obj).map((k) => obj[k]) : [];
|
|
},
|
|
},
|
|
};
|
|
</script>
|