Loading client/src/components/History/Content/Dataset/DatasetActions.vue +27 −0 Original line number Diff line number Diff line Loading @@ -61,6 +61,15 @@ @click.stop="onHighlight"> <span class="fa fa-sitemap" /> </b-button> <b-button v-if="showStop" class="stop-btn px-1" title="Finish Job Early" size="sm" variant="link" @click.stop="onStop"> <span class="fa fa-solid fa-stop" /> </b-button> <b-button v-if="showRerun" class="px-1" title="Help" size="sm" variant="link" @click.stop="onRerun"> <span class="fa fa-question" /> </b-button> Loading @@ -74,6 +83,7 @@ import { copy as sendToClipboard } from "utils/clipboard"; import { absPath, prependPath } from "@/utils/redirect"; import { downloadUrlMixin } from "./mixins.js"; import DatasetDownload from "./DatasetDownload"; import { stopJob } from "components/History/model/queries"; export default { components: { Loading Loading @@ -120,6 +130,9 @@ export default { visualizeUrl() { return prependPath(this.itemUrls.visualize); }, showStop() { return this.item.state == "running"; }, }, methods: { onCopyLink() { Loading @@ -145,6 +158,20 @@ export default { onHighlight() { this.$emit("toggleHighlights"); }, onStop() { stopJob(this.item.creating_job); document.querySelector(".stop-btn").classList.add("stopping-job"); }, }, }; </script> <style scoped> .stopping-job { animation: blink-animation .5s steps(5, start) infinite; } @keyframes blink-animation { to { visibility: hidden; } } </style> client/src/components/History/model/queries.js +9 −0 Original line number Diff line number Diff line Loading @@ -53,6 +53,15 @@ export async function deleteContent(content, deleteParams = {}) { return doResponse(response); } /** * Stops job and marks it as complete. */ export async function stopJob(job_id) { const url = `api/jobs/${job_id}/finish`; const response = await axios.put(prependPath(url)); return doResponse(response); } /** * Update specific fields on datasets or collections. * @param {Object} content content object Loading lib/galaxy/jobs/runners/pulsar.py +5 −2 Original line number Diff line number Diff line Loading @@ -739,7 +739,7 @@ class PulsarJobRunner(AsynchronousJobRunner): ) return False def stop_job(self, job_wrapper): def stop_job(self, job_wrapper, soft_kill=True): job = job_wrapper.get_job() if not job.job_runner_external_id: return Loading Loading @@ -778,6 +778,9 @@ class PulsarJobRunner(AsynchronousJobRunner): job_id = job.job_runner_external_id log.debug(f"Attempt remote Pulsar kill of job with url {pulsar_url} and id {job_id}") client = self.get_client(job.destination_params, job_id) if soft_kill: client.kill(soft_kill=soft_kill) else: client.kill() def recover(self, job, job_wrapper): Loading lib/galaxy/managers/jobs.py +12 −0 Original line number Diff line number Diff line Loading @@ -270,6 +270,18 @@ class JobManager: else: return False def finish_early(self, job): if not job.finished: try: job.mark_stopped(self.app.config.track_jobs_in_database) session = self.app.model.session with transaction(session): session.commit() self.app.job_manager.stop(job, message="") return True except Exception as e: log.error("Job Runner does not support stopping job early.") return False class JobSearch: """Search for jobs using tool inputs or other jobs""" Loading lib/galaxy/webapps/galaxy/api/jobs.py +21 −0 Original line number Diff line number Diff line Loading @@ -339,6 +339,27 @@ class JobController(BaseGalaxyAPIController, UsesVisualizationMixin): exceptions.RequestParameterInvalidException(f"Job with id '{job.tool_id}' is not paused") return self.__dictify_associations(trans, job.output_datasets, job.output_library_datasets) @expose_api def finish(self, trans: ProvidesUserContext, id, **kwd) -> List[dict]: """ * PUT /api/jobs/{id}/finish Finished a job regardless of execution status (ie early job finish) :type id: string :param id: Encoded job id :rtype: list of dicts :returns: list of dictionaries containing output dataset associations """ job = self.__get_job(trans, id) if not job: raise exceptions.ObjectNotFound("Could not access job with the given id") if job.state == job.states.RUNNING: self.job_manager.finish_early(job) else: exceptions.RequestParameterInvalidException(f"Job with id '{job.tool_id}' is not running.") return self.__dictify_associations(trans, job.output_datasets, job.output_library_datasets) @expose_api_anonymous def metrics(self, trans: ProvidesUserContext, **kwd): """ Loading Loading
client/src/components/History/Content/Dataset/DatasetActions.vue +27 −0 Original line number Diff line number Diff line Loading @@ -61,6 +61,15 @@ @click.stop="onHighlight"> <span class="fa fa-sitemap" /> </b-button> <b-button v-if="showStop" class="stop-btn px-1" title="Finish Job Early" size="sm" variant="link" @click.stop="onStop"> <span class="fa fa-solid fa-stop" /> </b-button> <b-button v-if="showRerun" class="px-1" title="Help" size="sm" variant="link" @click.stop="onRerun"> <span class="fa fa-question" /> </b-button> Loading @@ -74,6 +83,7 @@ import { copy as sendToClipboard } from "utils/clipboard"; import { absPath, prependPath } from "@/utils/redirect"; import { downloadUrlMixin } from "./mixins.js"; import DatasetDownload from "./DatasetDownload"; import { stopJob } from "components/History/model/queries"; export default { components: { Loading Loading @@ -120,6 +130,9 @@ export default { visualizeUrl() { return prependPath(this.itemUrls.visualize); }, showStop() { return this.item.state == "running"; }, }, methods: { onCopyLink() { Loading @@ -145,6 +158,20 @@ export default { onHighlight() { this.$emit("toggleHighlights"); }, onStop() { stopJob(this.item.creating_job); document.querySelector(".stop-btn").classList.add("stopping-job"); }, }, }; </script> <style scoped> .stopping-job { animation: blink-animation .5s steps(5, start) infinite; } @keyframes blink-animation { to { visibility: hidden; } } </style>
client/src/components/History/model/queries.js +9 −0 Original line number Diff line number Diff line Loading @@ -53,6 +53,15 @@ export async function deleteContent(content, deleteParams = {}) { return doResponse(response); } /** * Stops job and marks it as complete. */ export async function stopJob(job_id) { const url = `api/jobs/${job_id}/finish`; const response = await axios.put(prependPath(url)); return doResponse(response); } /** * Update specific fields on datasets or collections. * @param {Object} content content object Loading
lib/galaxy/jobs/runners/pulsar.py +5 −2 Original line number Diff line number Diff line Loading @@ -739,7 +739,7 @@ class PulsarJobRunner(AsynchronousJobRunner): ) return False def stop_job(self, job_wrapper): def stop_job(self, job_wrapper, soft_kill=True): job = job_wrapper.get_job() if not job.job_runner_external_id: return Loading Loading @@ -778,6 +778,9 @@ class PulsarJobRunner(AsynchronousJobRunner): job_id = job.job_runner_external_id log.debug(f"Attempt remote Pulsar kill of job with url {pulsar_url} and id {job_id}") client = self.get_client(job.destination_params, job_id) if soft_kill: client.kill(soft_kill=soft_kill) else: client.kill() def recover(self, job, job_wrapper): Loading
lib/galaxy/managers/jobs.py +12 −0 Original line number Diff line number Diff line Loading @@ -270,6 +270,18 @@ class JobManager: else: return False def finish_early(self, job): if not job.finished: try: job.mark_stopped(self.app.config.track_jobs_in_database) session = self.app.model.session with transaction(session): session.commit() self.app.job_manager.stop(job, message="") return True except Exception as e: log.error("Job Runner does not support stopping job early.") return False class JobSearch: """Search for jobs using tool inputs or other jobs""" Loading
lib/galaxy/webapps/galaxy/api/jobs.py +21 −0 Original line number Diff line number Diff line Loading @@ -339,6 +339,27 @@ class JobController(BaseGalaxyAPIController, UsesVisualizationMixin): exceptions.RequestParameterInvalidException(f"Job with id '{job.tool_id}' is not paused") return self.__dictify_associations(trans, job.output_datasets, job.output_library_datasets) @expose_api def finish(self, trans: ProvidesUserContext, id, **kwd) -> List[dict]: """ * PUT /api/jobs/{id}/finish Finished a job regardless of execution status (ie early job finish) :type id: string :param id: Encoded job id :rtype: list of dicts :returns: list of dictionaries containing output dataset associations """ job = self.__get_job(trans, id) if not job: raise exceptions.ObjectNotFound("Could not access job with the given id") if job.state == job.states.RUNNING: self.job_manager.finish_early(job) else: exceptions.RequestParameterInvalidException(f"Job with id '{job.tool_id}' is not running.") return self.__dictify_associations(trans, job.output_datasets, job.output_library_datasets) @expose_api_anonymous def metrics(self, trans: ProvidesUserContext, **kwd): """ Loading