Loading client/src/components/JobInformation/JobInformation.test.js +1 −1 Original line number Diff line number Diff line Loading @@ -24,7 +24,7 @@ describe("JobInformation/JobInformation.vue", () => { beforeEach(() => { axiosMock = new MockAdapter(axios); axiosMock.onGet(new RegExp(`api/configuration/decode/*`)).reply(200, { decoded_id: 123 }); axiosMock.onGet("/api/jobs/test_id?full=True&stdout_position=0&stdout_length=50000&stderr_position=0&stderr_length=50000").reply(200, jobResponse); axiosMock.onGet("/api/jobs/test_id?full=True").reply(200, jobResponse); }); afterEach(() => { Loading client/src/components/JobInformation/JobInformation.vue +34 −10 Original line number Diff line number Diff line <template> <div> <job-details-provider auto-refresh :jobId="job_id" :stdout_position=stdout_position :stdout_length=stdout_length :stderr_position=stderr_position :stderr_length=stderr_length @update:result="updateJob"/> <job-details-provider auto-refresh :job-id="job_id" @update:result="updateJob"/> <JobConsoleOutputProvider auto-refresh="True" :job-id="job_id" :stdout_position="stdout_position" :stdout_length="stdout_length" :stderr_position="stderr_position" :stderr_length="stderr_length" @update:result="updateConsoleOutputs"/> <h2 class="h-md">Job Information</h2> <table id="job-information" class="tabletip info_data_table"> <tbody> Loading Loading @@ -80,7 +88,7 @@ import { getAppRoot } from "onload/loadConfig"; import DecodedId from "../DecodedId.vue"; import CodeRow from "./CodeRow.vue"; import { JobDetailsProvider } from "components/providers/JobProvider"; import { JobDetailsProvider, JobConsoleOutputProvider } from "components/providers/JobProvider"; import UtcDate from "components/UtcDate"; import CopyToClipboard from "components/CopyToClipboard"; import JOB_STATES_MODEL from "utils/job-states-model"; Loading @@ -91,6 +99,7 @@ export default { CodeRow, DecodedId, JobDetailsProvider, JobConsoleOutputProvider, UtcDate, CopyToClipboard, }, Loading Loading @@ -131,16 +140,31 @@ export default { }, updateJob(job) { this.job = job; // Keep stdout in memory and only fetch new text via JobProvider if (job.tool_stdout != null) { // Load stored stdout and stderr if (this.jobIsTerminal) { if (job.tool_stdout) { this.stdout_text += job.tool_stdout; this.stdout_position += job.tool_stdout.length; } if (job.tool_stderr != null) { if (job.tool_stderr) { this.stderr_text += job.tool_stderr; this.stderr_position += job.tool_stderr.length; } } }, updateConsoleOutputs(output) { // Keep stdout in memory and only fetch new text via JobProvider if (output && !this.jobIsTerminal) { if (output.stdout != null) { this.stdout_text += output.stdout; this.stdout_position += output.stdout.length; } if (output.stderr != null) { this.stderr_text += output.stderr; this.stderr_position += output.stderr.length; } } } }, }; </script> client/src/components/providers/JobProvider.js +13 −2 Original line number Diff line number Diff line Loading @@ -5,8 +5,18 @@ import { rethrowSimple } from "utils/simple-error"; import { stateIsTerminal } from "./utils"; import { cleanPaginationParameters } from "./utils"; async function jobDetails({ jobId, stdout_position = 0, stdout_length = 0, stderr_position = 0, stderr_length = 0 }) { const url = `${getAppRoot()}api/jobs/${jobId}?full=True&stdout_position=${stdout_position}&stdout_length=${stdout_length}&stderr_position=${stderr_position}&stderr_length=${stderr_length}`; async function jobDetails({ jobId }) { const url = `${getAppRoot()}api/jobs/${jobId}?full=True`; try { const { data } = await axios.get(url); return data; } catch (e) { rethrowSimple(e); } } async function jobConsoleOutput({ jobId, stdout_position = 0, stdout_length = 0, stderr_position = 0, stderr_length = 0 }) { const url = `${getAppRoot()}api/jobs/${jobId}/console_output?stdout_position=${stdout_position}&stdout_length=${stdout_length}&stderr_position=${stderr_position}&stderr_length=${stderr_length}`; try { const { data } = await axios.get(url); return data; Loading @@ -26,6 +36,7 @@ async function jobProblems({ jobId }) { } export const JobDetailsProvider = SingleQueryProvider(jobDetails, stateIsTerminal); export const JobConsoleOutputProvider = SingleQueryProvider(jobConsoleOutput, stateIsTerminal); export const JobProblemProvider = SingleQueryProvider(jobProblems, stateIsTerminal); export function jobsProvider(ctx, callback, extraParams = {}) { Loading lib/galaxy/managers/jobs.py +16 −8 Original line number Diff line number Diff line Loading @@ -228,7 +228,7 @@ class JobManager: ) return self.job_lock() def get_accessible_job(self, trans, decoded_job_id, stdout_position=-1, stdout_length=0, stderr_position=-1, stderr_length=0): def get_accessible_job(self, trans, decoded_job_id): job = trans.sa_session.query(trans.app.model.Job).filter(trans.app.model.Job.id == decoded_job_id).first() if job is None: raise ObjectNotFound() Loading @@ -245,8 +245,15 @@ class JobManager: if not self.dataset_manager.is_accessible(data_assoc.dataset.dataset, trans.user): raise ItemAccessibilityException("You are not allowed to rerun this job.") trans.sa_session.refresh(job) return job def get_job_console_output(self, trans, job, stdout_position=-1, stdout_length=0, stderr_position=-1, stderr_length=0): if job is None: raise ObjectNotFound() # If stdout_length and stdout_position are good values, then load standard out and add it to status console_output = dict() console_output["state"] = job.state if job.state == job.states.RUNNING: working_directory = trans.app.object_store.get_filename( job, base_dir="job_work", dir_only=True, obj_dir=True Loading @@ -256,8 +263,7 @@ class JobManager: stdout_path = Path(working_directory) / STDOUT_LOCATION stdout_file = open(stdout_path, "r") stdout_file.seek(stdout_position) job.job_stdout = stdout_file.read(stdout_length) job.tool_stdout = job.job_stdout console_output["stdout"] = stdout_file.read(stdout_length) except Exception as e: log.error("Could not read STDOUT: %s", e) if stderr_length > 0 and stderr_position > -1: Loading @@ -265,11 +271,13 @@ class JobManager: stderr_path = Path(working_directory) / STDERR_LOCATION stderr_file = open(stderr_path, "r") stderr_file.seek(stderr_position) job.job_stderr = stderr_file.read(stderr_length) job.tool_stderr = job.job_stderr console_output["stderr"] = stderr_file.read(stderr_length) except Exception as e: log.error("Could not read STDERR: %s", e) return job else: console_output["stdout"] = job.tool_stdout console_output["stderr"] = job.tool_stderr return console_output def stop(self, job, message=None): if not job.finished: Loading lib/galaxy/webapps/galaxy/api/jobs.py +43 −10 Original line number Diff line number Diff line Loading @@ -178,10 +178,6 @@ class FastAPIJobs: id: DecodedDatabaseIdField, trans: ProvidesUserContext = DependsOnTrans, full: Optional[bool] = False, stdout_position: Optional[int] = None, stdout_length: Optional[int] = None, stderr_position: Optional[int] = None, stderr_length: Optional[int] = None, ) -> Dict[str, Any]: """ Return dictionary containing description of job data Loading @@ -189,17 +185,11 @@ class FastAPIJobs: Parameters - id: ID of job to return - full: Return extra information ? - stdout_position: The index of the character to begine reading stdout from - stdout_length: How many characters of stdout to read """ return self.service.show( trans, id, bool(full), int(stdout_position) if stdout_position else 0, int(stdout_length) if stdout_length else 0, int(stderr_position) if stderr_position else 0, int(stderr_length) if stderr_length else 0, ) @router.get("/api/jobs") Loading Loading @@ -364,6 +354,49 @@ class JobController(BaseGalaxyAPIController, UsesVisualizationMixin): 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 def console_output( self, trans: ProvidesUserContext, id, stdout_position, stdout_length, stderr_position, stderr_length, **kwd ): """ * GET /api/jobs/{id}/console_output Get the stdout and/or stderr of a job. :type id: string :param id: Encoded job id :type stdout_position: int :param stdout_position: The index of the character to begin reading stdout from :type stdout_length: int :param stdout_length: How many characters of stdout to read :type stderr_position: int :param stderr_position: The index of the character to begin reading stderr from :type stderr_length: int :param stderr_length: How many characters of stderr to read :rtype: dict :returns: dict containing stdout and stderr fields """ job = self.__get_job(trans, id) return self.job_manager.get_job_console_output( trans, job, int(stdout_position), int(stdout_length), int(stderr_position), int(stderr_length) ) @expose_api_anonymous def metrics(self, trans: ProvidesUserContext, **kwd): """ Loading Loading
client/src/components/JobInformation/JobInformation.test.js +1 −1 Original line number Diff line number Diff line Loading @@ -24,7 +24,7 @@ describe("JobInformation/JobInformation.vue", () => { beforeEach(() => { axiosMock = new MockAdapter(axios); axiosMock.onGet(new RegExp(`api/configuration/decode/*`)).reply(200, { decoded_id: 123 }); axiosMock.onGet("/api/jobs/test_id?full=True&stdout_position=0&stdout_length=50000&stderr_position=0&stderr_length=50000").reply(200, jobResponse); axiosMock.onGet("/api/jobs/test_id?full=True").reply(200, jobResponse); }); afterEach(() => { Loading
client/src/components/JobInformation/JobInformation.vue +34 −10 Original line number Diff line number Diff line <template> <div> <job-details-provider auto-refresh :jobId="job_id" :stdout_position=stdout_position :stdout_length=stdout_length :stderr_position=stderr_position :stderr_length=stderr_length @update:result="updateJob"/> <job-details-provider auto-refresh :job-id="job_id" @update:result="updateJob"/> <JobConsoleOutputProvider auto-refresh="True" :job-id="job_id" :stdout_position="stdout_position" :stdout_length="stdout_length" :stderr_position="stderr_position" :stderr_length="stderr_length" @update:result="updateConsoleOutputs"/> <h2 class="h-md">Job Information</h2> <table id="job-information" class="tabletip info_data_table"> <tbody> Loading Loading @@ -80,7 +88,7 @@ import { getAppRoot } from "onload/loadConfig"; import DecodedId from "../DecodedId.vue"; import CodeRow from "./CodeRow.vue"; import { JobDetailsProvider } from "components/providers/JobProvider"; import { JobDetailsProvider, JobConsoleOutputProvider } from "components/providers/JobProvider"; import UtcDate from "components/UtcDate"; import CopyToClipboard from "components/CopyToClipboard"; import JOB_STATES_MODEL from "utils/job-states-model"; Loading @@ -91,6 +99,7 @@ export default { CodeRow, DecodedId, JobDetailsProvider, JobConsoleOutputProvider, UtcDate, CopyToClipboard, }, Loading Loading @@ -131,16 +140,31 @@ export default { }, updateJob(job) { this.job = job; // Keep stdout in memory and only fetch new text via JobProvider if (job.tool_stdout != null) { // Load stored stdout and stderr if (this.jobIsTerminal) { if (job.tool_stdout) { this.stdout_text += job.tool_stdout; this.stdout_position += job.tool_stdout.length; } if (job.tool_stderr != null) { if (job.tool_stderr) { this.stderr_text += job.tool_stderr; this.stderr_position += job.tool_stderr.length; } } }, updateConsoleOutputs(output) { // Keep stdout in memory and only fetch new text via JobProvider if (output && !this.jobIsTerminal) { if (output.stdout != null) { this.stdout_text += output.stdout; this.stdout_position += output.stdout.length; } if (output.stderr != null) { this.stderr_text += output.stderr; this.stderr_position += output.stderr.length; } } } }, }; </script>
client/src/components/providers/JobProvider.js +13 −2 Original line number Diff line number Diff line Loading @@ -5,8 +5,18 @@ import { rethrowSimple } from "utils/simple-error"; import { stateIsTerminal } from "./utils"; import { cleanPaginationParameters } from "./utils"; async function jobDetails({ jobId, stdout_position = 0, stdout_length = 0, stderr_position = 0, stderr_length = 0 }) { const url = `${getAppRoot()}api/jobs/${jobId}?full=True&stdout_position=${stdout_position}&stdout_length=${stdout_length}&stderr_position=${stderr_position}&stderr_length=${stderr_length}`; async function jobDetails({ jobId }) { const url = `${getAppRoot()}api/jobs/${jobId}?full=True`; try { const { data } = await axios.get(url); return data; } catch (e) { rethrowSimple(e); } } async function jobConsoleOutput({ jobId, stdout_position = 0, stdout_length = 0, stderr_position = 0, stderr_length = 0 }) { const url = `${getAppRoot()}api/jobs/${jobId}/console_output?stdout_position=${stdout_position}&stdout_length=${stdout_length}&stderr_position=${stderr_position}&stderr_length=${stderr_length}`; try { const { data } = await axios.get(url); return data; Loading @@ -26,6 +36,7 @@ async function jobProblems({ jobId }) { } export const JobDetailsProvider = SingleQueryProvider(jobDetails, stateIsTerminal); export const JobConsoleOutputProvider = SingleQueryProvider(jobConsoleOutput, stateIsTerminal); export const JobProblemProvider = SingleQueryProvider(jobProblems, stateIsTerminal); export function jobsProvider(ctx, callback, extraParams = {}) { Loading
lib/galaxy/managers/jobs.py +16 −8 Original line number Diff line number Diff line Loading @@ -228,7 +228,7 @@ class JobManager: ) return self.job_lock() def get_accessible_job(self, trans, decoded_job_id, stdout_position=-1, stdout_length=0, stderr_position=-1, stderr_length=0): def get_accessible_job(self, trans, decoded_job_id): job = trans.sa_session.query(trans.app.model.Job).filter(trans.app.model.Job.id == decoded_job_id).first() if job is None: raise ObjectNotFound() Loading @@ -245,8 +245,15 @@ class JobManager: if not self.dataset_manager.is_accessible(data_assoc.dataset.dataset, trans.user): raise ItemAccessibilityException("You are not allowed to rerun this job.") trans.sa_session.refresh(job) return job def get_job_console_output(self, trans, job, stdout_position=-1, stdout_length=0, stderr_position=-1, stderr_length=0): if job is None: raise ObjectNotFound() # If stdout_length and stdout_position are good values, then load standard out and add it to status console_output = dict() console_output["state"] = job.state if job.state == job.states.RUNNING: working_directory = trans.app.object_store.get_filename( job, base_dir="job_work", dir_only=True, obj_dir=True Loading @@ -256,8 +263,7 @@ class JobManager: stdout_path = Path(working_directory) / STDOUT_LOCATION stdout_file = open(stdout_path, "r") stdout_file.seek(stdout_position) job.job_stdout = stdout_file.read(stdout_length) job.tool_stdout = job.job_stdout console_output["stdout"] = stdout_file.read(stdout_length) except Exception as e: log.error("Could not read STDOUT: %s", e) if stderr_length > 0 and stderr_position > -1: Loading @@ -265,11 +271,13 @@ class JobManager: stderr_path = Path(working_directory) / STDERR_LOCATION stderr_file = open(stderr_path, "r") stderr_file.seek(stderr_position) job.job_stderr = stderr_file.read(stderr_length) job.tool_stderr = job.job_stderr console_output["stderr"] = stderr_file.read(stderr_length) except Exception as e: log.error("Could not read STDERR: %s", e) return job else: console_output["stdout"] = job.tool_stdout console_output["stderr"] = job.tool_stderr return console_output def stop(self, job, message=None): if not job.finished: Loading
lib/galaxy/webapps/galaxy/api/jobs.py +43 −10 Original line number Diff line number Diff line Loading @@ -178,10 +178,6 @@ class FastAPIJobs: id: DecodedDatabaseIdField, trans: ProvidesUserContext = DependsOnTrans, full: Optional[bool] = False, stdout_position: Optional[int] = None, stdout_length: Optional[int] = None, stderr_position: Optional[int] = None, stderr_length: Optional[int] = None, ) -> Dict[str, Any]: """ Return dictionary containing description of job data Loading @@ -189,17 +185,11 @@ class FastAPIJobs: Parameters - id: ID of job to return - full: Return extra information ? - stdout_position: The index of the character to begine reading stdout from - stdout_length: How many characters of stdout to read """ return self.service.show( trans, id, bool(full), int(stdout_position) if stdout_position else 0, int(stdout_length) if stdout_length else 0, int(stderr_position) if stderr_position else 0, int(stderr_length) if stderr_length else 0, ) @router.get("/api/jobs") Loading Loading @@ -364,6 +354,49 @@ class JobController(BaseGalaxyAPIController, UsesVisualizationMixin): 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 def console_output( self, trans: ProvidesUserContext, id, stdout_position, stdout_length, stderr_position, stderr_length, **kwd ): """ * GET /api/jobs/{id}/console_output Get the stdout and/or stderr of a job. :type id: string :param id: Encoded job id :type stdout_position: int :param stdout_position: The index of the character to begin reading stdout from :type stdout_length: int :param stdout_length: How many characters of stdout to read :type stderr_position: int :param stderr_position: The index of the character to begin reading stderr from :type stderr_length: int :param stderr_length: How many characters of stderr to read :rtype: dict :returns: dict containing stdout and stderr fields """ job = self.__get_job(trans, id) return self.job_manager.get_job_console_output( trans, job, int(stdout_position), int(stdout_length), int(stderr_position), int(stderr_length) ) @expose_api_anonymous def metrics(self, trans: ProvidesUserContext, **kwd): """ Loading