Skip to content

Commit

Permalink
[WIP] Use tool request API from the tool form.
Browse files Browse the repository at this point in the history
  • Loading branch information
jmchilton committed Sep 27, 2024
1 parent 8642393 commit e87bb5d
Show file tree
Hide file tree
Showing 10 changed files with 1,606 additions and 84 deletions.
235 changes: 160 additions & 75 deletions client/src/components/Tool/ToolForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
<template v-slot:header-buttons>
<ButtonSpinner
id="execute"
title="Run Tool"
:title="runButtonTitle"
:disabled="!canMutateHistory"
class="btn-sm"
:wait="showExecuting"
Expand All @@ -98,7 +98,7 @@
</template>
<template v-slot:buttons>
<ButtonSpinner
title="Run Tool"
:title="runButtonTitle"
class="mt-3 mb-3"
:disabled="!canMutateHistory"
:wait="showExecuting"
Expand All @@ -111,12 +111,14 @@

<script>
import { getGalaxyInstance } from "app";
import axios from "axios";
import ButtonSpinner from "components/Common/ButtonSpinner";
import Heading from "components/Common/Heading";
import FormDisplay from "components/Form/FormDisplay";
import FormElement from "components/Form/FormElement";
import LoadingSpan from "components/LoadingSpan";
import ToolEntryPoints from "components/ToolEntryPoints/ToolEntryPoints";
import { getAppRoot } from "onload/loadConfig";
import { mapActions, mapState, storeToRefs } from "pinia";
import { useHistoryItemsStore } from "stores/historyItemsStore";
import { useJobStore } from "stores/jobStore";
Expand All @@ -128,7 +130,8 @@ import { useHistoryStore } from "@/stores/historyStore";
import { useUserStore } from "@/stores/userStore";
import ToolRecommendation from "../ToolRecommendation";
import { getToolFormData, submitJob, updateToolFormData } from "./services";
import { getToolFormData, getToolInputs, submitJob, submitToolRequest, updateToolFormData } from "./services";
import { structuredInputs } from "./structured";
import ToolCard from "./ToolCard";
import { allowCachedJobs } from "./utilities";
Expand Down Expand Up @@ -204,6 +207,8 @@ export default {
],
immutableHistoryMessage:
"This history is immutable and you cannot run tools in it. Please switch to a different history.",
toolInputs: null,
submissionStateMessage: null,
};
},
computed: {
Expand Down Expand Up @@ -249,7 +254,15 @@ export default {
return this.currentHistory && canMutateHistory(this.currentHistory);
},
runButtonTitle() {
return "Run Tool";
if (this.showExecuting) {
if (this.submissionStateMessage) {
return this.submissionStateMessage;
} else {
return "Run Tool";
}
} else {
return "Run Tool";
}
},
},
watch: {
Expand Down Expand Up @@ -301,11 +314,38 @@ export default {
onChangeVersion(newVersion) {
this.requestTool(newVersion);
},
waitOnRequest(response, requestContent, config, prevRoute) {
const toolRequestId = response.tool_request_id;
const handleRequestState = (toolRequestStateResponse) => {
const state = toolRequestStateResponse.data;
console.log(`state is ${state}`);
if (["new"].indexOf(state) !== -1) {
setTimeout(doRequestCheck, 1000);
} else if (state == "failed") {
this.handleError(null, requestContent);
} else {
refreshContentsWrapper();
this.showForm = false;
this.showSuccess = true;
this.handleSubmissionComplete(config, prevRoute);
}
};
const doRequestCheck = () => {
axios
.get(`${getAppRoot()}api/tool_requests/${toolRequestId}/state`)
.then(handleRequestState)
.catch((e) => this.handleError(e, requestContent));
};
setTimeout(doRequestCheck, 1000);
},
requestTool(newVersion) {
this.currentVersion = newVersion || this.currentVersion;
this.disabled = true;
this.loading = true;
console.debug("ToolForm - Requesting tool.", this.id);
getToolInputs(this.id, this.currentVersion).then((data) => {
this.toolInputs = data;
});
return getToolFormData(this.id, this.currentVersion, this.job_id, this.history_id)
.then((data) => {
this.formConfig = data;
Expand All @@ -331,90 +371,135 @@ export default {
onUpdatePreferredObjectStoreId(preferredObjectStoreId) {
this.preferredObjectStoreId = preferredObjectStoreId;
},
handleSubmissionComplete(config, prevRoute) {
const changeRoute = prevRoute === this.$route.fullPath;
if (changeRoute) {
this.$router.push(`/jobs/submission/success`);
} else {
if ([true, "true"].includes(config.enable_tool_recommendations)) {
this.showRecommendation = true;
}
document.querySelector(".center-panel").scrollTop = 0;
}
},
handleError(e, errorContent) {
this.errorMessage = e?.response?.data?.err_msg;
this.showExecuting = false;
this.submissionStateMessage = null;
let genericError = true;
const errorData = e && e.response && e.response.data && e.response.data.err_data;
if (errorData) {
const errorEntries = Object.entries(errorData);
if (errorEntries.length > 0) {
this.validationScrollTo = errorEntries[0];
genericError = false;
}
}
if (genericError) {
this.showError = true;
this.errorTitle = "Job submission failed.";
this.errorContent = errorContent;
}
},
onExecute(config, historyId) {
if (this.validationInternal) {
this.validationScrollTo = this.validationInternal.slice();
return;
}
this.showExecuting = true;
const jobDef = {
history_id: historyId,
tool_id: this.formConfig.id,
tool_version: this.formConfig.version,
inputs: {
...this.formData,
},
this.submissionStateMessage = "Preparing Request";
const inputs = {
...this.formData,
};
if (this.useEmail) {
jobDef.inputs["send_email_notification"] = true;
}
if (this.useJobRemapping) {
jobDef.inputs["rerun_remap_job_id"] = this.job_id;
}
if (this.useCachedJobs) {
jobDef.inputs["use_cached_job"] = true;
const toolId = this.formConfig.id;
const toolVersion = this.formConfig.version;
let validatedInputs = null;
try {
validatedInputs = structuredInputs(inputs, this.toolInputs);
} catch {
// failed validation, just use legacy API
}
if (this.preferredObjectStoreId) {
jobDef.preferred_object_store_id = this.preferredObjectStoreId;
}
if (this.dataManagerMode === "bundle") {
jobDef.data_manager_mode = this.dataManagerMode;
}
console.debug("toolForm::onExecute()", jobDef);
const prevRoute = this.$route.fullPath;
submitJob(jobDef).then(
(jobResponse) => {
this.showExecuting = false;
let changeRoute = false;
refreshContentsWrapper();
if (jobResponse.produces_entry_points) {
this.showEntryPoints = true;
this.entryPoints = jobResponse.jobs;
}
const nJobs = jobResponse && jobResponse.jobs ? jobResponse.jobs.length : 0;
if (nJobs > 0) {
this.showForm = false;
const toolName = this.toolName;
this.saveLatestResponse({
jobDef,
jobResponse,
toolName,
});
changeRoute = prevRoute === this.$route.fullPath;
} else {
this.showError = true;
this.showForm = true;
this.errorTitle = "Job submission rejected.";
this.errorContent = jobResponse;
if (validatedInputs) {
const toolRequest = {
history_id: historyId,
tool_id: toolId,
tool_version: toolVersion,
inputs: validatedInputs,
};
if (this.useCachedJobs) {
toolRequest.use_cached_jobs = true;
}
if (this.preferredObjectStoreId) {
toolRequest.preferred_object_store_id = this.preferredObjectStoreId;
}
if (this.dataManagerMode === "bundle") {
toolRequest.data_manager_mode = this.dataManagerMode;
}
this.submissionStateMessage = "Sending Request";
submitToolRequest(toolRequest).then(
(jobResponse) => {
this.submissionStateMessage = "Processing Request";
console.log(jobResponse);
this.waitOnRequest(jobResponse, toolRequest, config, prevRoute);
},
(e) => {
this.handleError(e, toolRequest);
}
if (changeRoute) {
this.$router.push(`/jobs/submission/success`);
} else {
if ([true, "true"].includes(config.enable_tool_recommendations)) {
this.showRecommendation = true;
);
} else {
const jobDef = {
history_id: historyId,
tool_id: toolId,
tool_version: toolVersion,
inputs: inputs,
};
if (this.useEmail) {
jobDef.inputs["send_email_notification"] = true;
}
if (this.useJobRemapping) {
jobDef.inputs["rerun_remap_job_id"] = this.job_id;
}
if (this.useCachedJobs) {
jobDef.inputs["use_cached_job"] = true;
}
if (this.preferredObjectStoreId) {
jobDef.preferred_object_store_id = this.preferredObjectStoreId;
}
if (this.dataManagerMode === "bundle") {
jobDef.data_manager_mode = this.dataManagerMode;
}
console.debug("toolForm::onExecute()", jobDef);
submitJob(jobDef).then(
(jobResponse) => {
this.showExecuting = false;
refreshContentsWrapper();
if (jobResponse.produces_entry_points) {
this.showEntryPoints = true;
this.entryPoints = jobResponse.jobs;
}
document.querySelector("#center").scrollTop = 0;
}
},
(e) => {
this.errorMessage = e?.response?.data?.err_msg;
this.showExecuting = false;
let genericError = true;
const errorData = e && e.response && e.response.data && e.response.data.err_data;
if (errorData) {
const errorEntries = Object.entries(errorData);
if (errorEntries.length > 0) {
this.validationScrollTo = errorEntries[0];
genericError = false;
const nJobs = jobResponse && jobResponse.jobs ? jobResponse.jobs.length : 0;
if (nJobs > 0) {
this.showForm = false;
const toolName = this.toolName;
this.saveLatestResponse({
jobDef,
jobResponse,
toolName,
});
} else {
this.showError = true;
this.showForm = true;
this.errorTitle = "Job submission rejected.";
this.errorContent = jobResponse;
}
this.handleSubmissionComplete(config, prevRoute);
},
(e) => {
this.handleError(e, jobDef);
}
if (genericError) {
this.showError = true;
this.errorTitle = "Job submission failed.";
this.errorContent = jobDef;
}
}
);
);
}
},
},
};
Expand Down
5 changes: 5 additions & 0 deletions client/src/components/Tool/ToolSuccess.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const jobStore = useJobStore();
const router = useRouter();
const jobDef = computed(() => responseVal.value.jobDef);
const usedToolRequest = computed(() => responseVal.value.usedToolRequest);
const jobResponse = computed(() => responseVal.value.jobResponse);
const responseVal = computed(() => jobStore.getLatestResponse);
const showRecommendation = computed(() => config.value.enable_tool_recommendations);
Expand All @@ -37,6 +38,10 @@ if (Object.keys(responseVal.value).length === 0) {
<div v-if="jobResponse?.produces_entry_points">
<ToolEntryPoints v-for="job in jobResponse.jobs" :key="job.id" :job-id="job.id" />
</div>
<ToolSuccessMessage
:job-response="jobResponse"
:tool-name="toolName"
:used-tool-request="usedToolRequest" />
<ToolSuccessMessage :job-response="jobResponse" :tool-name="toolName" />
<Webhook type="tool" :tool-id="jobDef.tool_id" />
<ToolRecommendation v-if="showRecommendation" :tool-id="jobDef.tool_id" />
Expand Down
36 changes: 27 additions & 9 deletions client/src/components/Tool/ToolSuccessMessage.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
<template>
<div class="donemessagelarge">
<p>
Started tool <b>{{ toolName }}</b> and successfully added {{ nJobsText }} to the queue.
</p>
<p>It produces {{ nOutputsText }}:</p>
<ul>
<li v-for="item of jobResponse.outputs" :key="item.hid">
<b>{{ item.hid }}: {{ item.name }}</b>
</li>
</ul>
<div v-if="usedToolRequest">
You used the fancy new API... something new will be here.
<img
src="https://www.animatedimages.org/data/media/695/animated-under-construction-image-0055.gif"
alt="90s style under construction" />
</div>
<div v-else>
<p>
Started tool <b>{{ toolName }}</b> and successfully added {{ nJobsText }} to the queue.
</p>
<p>The tool uses {{ nInputsText }}:</p>
<ul>
<li v-for="item of inputs" :key="item.hid">
<b>{{ item.hid }}: {{ item.name }}</b>
</li>
</ul>
<p>It produces {{ nOutputsText }}:</p>
<ul>
<li v-for="item of jobResponse.outputs" :key="item.hid">
<b>{{ item.hid }}: {{ item.name }}</b>
</li>
</ul>
</div>
<p>
You can check the status of queued jobs and view the resulting data by refreshing the History panel. When
the job has been run the status will change from 'running' to 'finished' if completed successfully or
Expand All @@ -28,6 +42,10 @@ export default {
type: String,
required: true,
},
usedToolRequest: {
type: Boolean,
required: true,
},
},
computed: {
nOutputs() {
Expand Down
Loading

0 comments on commit e87bb5d

Please sign in to comment.