From 038eb44d2d9be9f46ded515c1e3081df15f27388 Mon Sep 17 00:00:00 2001 From: hujambo-dunia Date: Tue, 2 Apr 2024 09:36:51 -0400 Subject: [PATCH] Added/modified backend API endpoints when there is a Job ID (Dataset Page) and when there is no Job ID (Tool Form Page): passes newly included Error-Transcript JSON and User object to the email report methods (Work-in-Progress). --- .../components/Common/SelfReportingError.vue | 80 ++++++++++++------- .../DatasetInformation/DatasetError.vue | 2 + client/src/components/Tool/ToolForm.vue | 42 +++++++--- lib/galaxy/schema/jobs.py | 5 ++ lib/galaxy/tools/error_reports/__init__.py | 2 +- .../tools/error_reports/plugins/email.py | 5 +- lib/galaxy/tools/errors.py | 12 +-- lib/galaxy/webapps/galaxy/api/jobs.py | 36 ++++++++- 8 files changed, 132 insertions(+), 52 deletions(-) diff --git a/client/src/components/Common/SelfReportingError.vue b/client/src/components/Common/SelfReportingError.vue index 55212935b883..e1d0a75512fc 100644 --- a/client/src/components/Common/SelfReportingError.vue +++ b/client/src/components/Common/SelfReportingError.vue @@ -4,12 +4,9 @@

Dataset Error Report

-

- - - - - + + +

Details

@@ -21,19 +18,6 @@
- -
-

Detected Common Potential Problems

-

- The tool was started with one or more empty input datasets. This frequently results in tool errors - due to problematic input choices. -

-

- The tool was started with one or more duplicate input datasets. This frequently results in tool - errors due to problematic input choices. -

-
-

Troubleshooting

There are a number of helpful resources to self diagnose and correct problems. @@ -60,6 +44,19 @@ v-model="message" :area="true" title="Please provide detailed information on the activities leading to this issue:" /> + + ({{ title }}) Error transcript: + + + +
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; -import { BAlert } from "bootstrap-vue"; +import { BAlert, BButton, BCollapse, BLink } from "bootstrap-vue"; import FormElement from "components/Form/FormElement"; import { JobProblemProvider } from "components/providers/JobProvider"; import { mapState } from "pinia"; @@ -84,6 +81,7 @@ import { useMarkdown } from "@/composables/markdown"; import { useUserStore } from "@/stores/userStore"; import { sendErrorReport } from "../DatasetInformation/services"; +import { sendErrorReportTool } from "../ToolInformation/services"; export default { components: { @@ -91,6 +89,9 @@ export default { FormElement, JobProblemProvider, BAlert, + BButton, + BCollapse, + BLink, }, props: { dataset: { @@ -105,6 +106,10 @@ export default { type: Array, default: () => [], }, + transcript: { + type: String, + default: "", + }, }, setup() { const { renderMarkdown } = useMarkdown({ openLinksInNewPage: true }); @@ -115,6 +120,7 @@ export default { message: null, errorMessage: null, resultMessages: [], + isExpanded: false, }; }, computed: { @@ -128,20 +134,36 @@ export default { const isEmailActive = !getGalaxyInstance().config.show_inactivity_warning; return !this.currentUser?.email || !isEmailActive; }, + title() { + return this.isExpanded ? `-` : `+`; + }, }, methods: { onError(err) { this.errorMessage = err; }, - submit(dataset, email) { - sendErrorReport(dataset, this.message, email).then( - (resultMessages) => { - this.resultMessages = resultMessages; - }, - (errorMessage) => { - this.errorMessage = errorMessage; - } - ); + submit(dataset, userEmailJob) { + const email = userEmailJob || this.currentUserEmail; + const message = this.message; + if (this.transcript) { + sendErrorReportTool(dataset, message, email, this.transcript).then( + (resultMessages) => { + this.resultMessages = resultMessages; + }, + (errorMessage) => { + this.errorMessage = errorMessage; + } + ); + } else { + sendErrorReport(dataset, message, email, this.transcript).then( + (resultMessages) => { + this.resultMessages = resultMessages; + }, + (errorMessage) => { + this.errorMessage = errorMessage; + } + ); + } }, hasDetails(outputs) { return ( diff --git a/client/src/components/DatasetInformation/DatasetError.vue b/client/src/components/DatasetInformation/DatasetError.vue index 41186a407873..957b24aa7276 100644 --- a/client/src/components/DatasetInformation/DatasetError.vue +++ b/client/src/components/DatasetInformation/DatasetError.vue @@ -80,6 +80,7 @@ export default { return [ { text: `An error occurred while running the tool ${toolId}.`, + variant: "danger" }, ]; }, @@ -102,6 +103,7 @@ export default { submit(dataset, userEmailJob) { const email = userEmailJob || this.currentUserEmail; const message = this.message; + sendErrorReport(dataset, message, email).then( (resultMessages) => { this.resultMessages = resultMessages; diff --git a/client/src/components/Tool/ToolForm.vue b/client/src/components/Tool/ToolForm.vue index 9c524b2c0511..528c290d4af4 100644 --- a/client/src/components/Tool/ToolForm.vue +++ b/client/src/components/Tool/ToolForm.vue @@ -8,16 +8,16 @@ - - {{ errorMessage }} - - - The server could not complete this request. Please verify your parameter settings, retry submission and - contact the Galaxy Team if this error persists. A transcript of the submitted data is shown below. - - -

{{ errorContentPretty }}
- + ${toolId}.`, + variant: "danger" + }, + { + text: "The server could not complete this request. Please verify your parameter settings, retry submission and contact the Galaxy Team if this error persists. A transcript of the submitted data is shown below.", + variant: "warning" + }, + ]; + }, + buildCommandOutputs(detail) { + return [ + { + text: "Tool Message (?)", + detail: [detail], + } + ]; + }, }, }; diff --git a/lib/galaxy/schema/jobs.py b/lib/galaxy/schema/jobs.py index fe6316262983..fea16f214ef4 100644 --- a/lib/galaxy/schema/jobs.py +++ b/lib/galaxy/schema/jobs.py @@ -98,6 +98,11 @@ class ReportJobErrorPayload(Model): title="Message", description="The optional message sent with the error report.", ) + toolTranscript: Optional[str] = Field( + default=None, + title="Transcript", + description="The optional Tool Transcript error created by the user and sent with the error report.", + ) class SearchJobsPayload(Model): diff --git a/lib/galaxy/tools/error_reports/__init__.py b/lib/galaxy/tools/error_reports/__init__.py index 3ac83fbb42a3..8b5c42f682e2 100644 --- a/lib/galaxy/tools/error_reports/__init__.py +++ b/lib/galaxy/tools/error_reports/__init__.py @@ -65,7 +65,7 @@ def submit_report(self, dataset, job, tool, user=None, user_submission=False, ** for plugin in self.plugins: if user_submission == plugin.user_submission: try: - response = plugin.submit_report(dataset, job, tool, **kwargs) + response = plugin.submit_report(dataset, job, tool, user, **kwargs) log.debug("Bug report plugin %s generated response %s", plugin, response) if plugin.verbose and response: responses.append(response) diff --git a/lib/galaxy/tools/error_reports/plugins/email.py b/lib/galaxy/tools/error_reports/plugins/email.py index 42446c2ecb3e..b0aa0ae84183 100644 --- a/lib/galaxy/tools/error_reports/plugins/email.py +++ b/lib/galaxy/tools/error_reports/plugins/email.py @@ -23,12 +23,13 @@ def __init__(self, **kwargs): self.verbose = string_as_bool(kwargs.get("verbose", True)) self.user_submission = string_as_bool(kwargs.get("user_submission", True)) - def submit_report(self, dataset, job, tool, **kwargs): + def submit_report(self, dataset, job, tool, user, **kwargs): """Send report as an email""" try: error_reporter = EmailErrorReporter(dataset.id, self.app) error_reporter.send_report( - user=job.get_user(), + user=user, + tool=tool, email=kwargs.get("email", None), message=kwargs.get("message", None), redact_user_details_in_bugreport=self.redact_user_details_in_bugreport, diff --git a/lib/galaxy/tools/errors.py b/lib/galaxy/tools/errors.py index e1dc2052bb5f..3d59126c781a 100644 --- a/lib/galaxy/tools/errors.py +++ b/lib/galaxy/tools/errors.py @@ -156,7 +156,7 @@ def _can_access_dataset(self, user): roles = [] return self.app.security_agent.can_access_dataset(roles, self.hda.dataset) - def create_report(self, user, email="", message="", redact_user_details_in_bugreport=False, **kwd): + def create_report(self, user, tool, email="", message="", redact_user_details_in_bugreport=False, **kwd): hda = self.hda job = self.job host = self.app.url_for("/", qualified=True) @@ -205,8 +205,8 @@ def create_report(self, user, email="", message="", redact_user_details_in_bugre hda_show_params_link=hda_show_params_link, job_id_encoded=self.app.security.encode_id(job.id), job_id=job.id, - tool_version=job.tool_version, - job_tool_id=job.tool_id, + tool_version=tool.tool_version, + job_tool_id=tool.id, job_tool_version=hda.tool_version, job_runner_external_id=job.job_runner_external_id, job_command_line=job.command_line, @@ -227,12 +227,12 @@ def create_report(self, user, email="", message="", redact_user_details_in_bugre self.html_report = string.Template(error_report_template_html).safe_substitute(report_variables) - def _send_report(self, user, email=None, message=None, **kwd): + def _send_report(self, user, tool, email=None, message=None, **kwd): return self.report - def send_report(self, user, email=None, message=None, **kwd): + def send_report(self, user, tool, email=None, message=None, **kwd): if self.report is None: - self.create_report(user, email=email, message=message, **kwd) + self.create_report(user, tool, email=email, message=message, **kwd) return self._send_report(user, email=email, message=message, **kwd) diff --git a/lib/galaxy/webapps/galaxy/api/jobs.py b/lib/galaxy/webapps/galaxy/api/jobs.py index 7d0d2757b333..2b269561d322 100644 --- a/lib/galaxy/webapps/galaxy/api/jobs.py +++ b/lib/galaxy/webapps/galaxy/api/jobs.py @@ -73,6 +73,8 @@ ) from galaxy.work.context import WorkRequestContext +import json + log = logging.getLogger(__name__) router = Router(tags=["jobs"]) @@ -311,15 +313,41 @@ def error( payload: Annotated[ReportJobErrorPayload, ReportErrorBody], job_id: JobIdPathParam, trans: ProvidesUserContext = DependsOnTrans, + ) -> JobErrorSummary: + myMessages = self.error_all(payload, job_id, trans) + return JobErrorSummary(messages=myMessages) + + @router.post( + "/api/jobs/no-job-id-error", + name="report_error_no_job_id", + summary="Submits a bug report via the API.", + ) + def error_no_job_id( + self, + payload: Annotated[ReportJobErrorPayload, ReportErrorBody], + trans: ProvidesUserContext = DependsOnTrans, + ) -> JobErrorSummary: + myMessages = self.error_all(payload=payload, job_id=None, trans=trans) + return JobErrorSummary(messages=myMessages) + + def error_all( + self, + payload: Annotated[ReportJobErrorPayload, ReportErrorBody], + job_id, + trans: ProvidesUserContext = DependsOnTrans, ) -> JobErrorSummary: # Get dataset on which this error was triggered dataset_id = payload.dataset_id dataset = self.service.hda_manager.get_accessible(id=dataset_id, user=trans.user) - # Get job - job = self.service.get_job(trans, job_id) if not dataset.creating_job or dataset.creating_job.id != job.id: raise exceptions.RequestParameterInvalidException("dataset_id was not created by job_id") - tool = trans.app.toolbox.get_tool(job.tool_id, tool_version=job.tool_version) or None + if payload.toolTranscript: + job = {} + transcript = json.loads(payload.toolTranscript) + tool = trans.app.toolbox.get_tool(transcript["tool_id"], tool_version=transcript["tool_version"]) + else: + job = self.service.get_job(trans, job_id) + tool = trans.app.toolbox.get_tool(job.tool_id, tool_version=job.tool_version) or None email = payload.email if not email and not trans.anonymous: email = trans.user.email @@ -332,7 +360,7 @@ def error( email=email, message=payload.message, ) - return JobErrorSummary(messages=messages) + return messages @router.get( "/api/jobs/{job_id}/inputs",