diff --git a/client/galaxy/scripts/mvc/dataset/dataset-error.js b/client/galaxy/scripts/mvc/dataset/dataset-error.js index 15ed502ce27..94499260574 100644 --- a/client/galaxy/scripts/mvc/dataset/dataset-error.js +++ b/client/galaxy/scripts/mvc/dataset/dataset-error.js @@ -32,6 +32,7 @@ var View = Backbone.View.extend({ url: job_url, success: job => { this.render_error_page(dataset, job); + this.find_common_problems(job); }, error: response => { var error_response = { @@ -56,15 +57,53 @@ var View = Backbone.View.extend({ }); }, + find_common_problems: function(job) { + var job_url = `${getAppRoot()}api/jobs/${job.id}/common_problems`; + Utils.get({ + url: job_url, + success: common_problems => { + this.render_common_problems(job, common_problems); + }, + error: response => { + console.log("error"); + console.log(response); + } + }); + return; + }, + + render_common_problems: function(job, common_problems) { + const has_duplicate_inputs = common_problems.has_duplicate_inputs; + const has_empty_inputs = common_problems.has_empty_inputs; + if (has_duplicate_inputs || has_empty_inputs) { + const reportEl = this.$el.find(".common_problems"); + reportEl.text("Detected Common Potential Problems"); + if (has_empty_inputs) { + reportEl.after(` +
+ The tool was executed with one or more empty input datasets, this isn't + always a problem and may not have been but frequently this is a source + of error. +
`); + } + if (has_duplicate_inputs) { + reportEl.after(` +
+ The tool was executed with one or more duplicate input datasets, this isn't + always a problem and may not have been but frequently this is a source + of error. +
`); + } + } + }, + /** Render the view */ render_error_page: function(dataset, job) { this.$el.empty().append(` ${this._templateHeader()}
An error occurred while running the tool ${job.tool_id}.
-Tool execution generated the following messages:
-${_.escape(job.stderr)}
-
+ ${this.job_summary(job)}
There are a number of help resources to self diagnose and @@ -85,6 +124,32 @@ var View = Backbone.View.extend({ this.$el.append(this._getBugFormTemplate(dataset, job)); }, + job_summary: function(job) { + const tool_stderr = job.tool_stderr; + const job_stderr = job.job_stderr; + const job_messages = job.job_messages; + if (!tool_stderr && !job_stderr && !job_messages) { + return '
'; + } + var message = "Execution resulted in the following messages:
"; + for (let job_message of job_messages) { + message += `${_.escape(job_message["desc"])}`;
+ }
+ }
+ if (tool_stderr) {
+ message += "Tool generated the following standard error:
"; + message += `${_.escape(tool_stderr)}`;
+ }
+ if (job_stderr) {
+ message += "Galaxy job runner generated the following standard error:
"; + message += `${_.escape(job_stderr)}`;
+ }
+ message += ``;
+ return message;
+ },
+
/** Display actions messages */
display_message: function(response, $el, doNotClear, safe) {
if (!safe) {
diff --git a/lib/galaxy/webapps/galaxy/api/jobs.py b/lib/galaxy/webapps/galaxy/api/jobs.py
index 144b703cf90..f42c83a998b 100644
--- a/lib/galaxy/webapps/galaxy/api/jobs.py
+++ b/lib/galaxy/webapps/galaxy/api/jobs.py
@@ -169,6 +169,30 @@ class JobController(BaseAPIController, UsesLibraryMixinItems):
job_dict['job_metrics'] = [metric_to_dict(metric) for metric in job.metrics]
return job_dict
+ @expose_api
+ def common_problems(self, trans, id, **kwd):
+ """
+ * GET /api/jobs/{id}/common_problems
+ check inputs and job for common potential problems to aid in error reporting
+ """
+ job = self.__get_job(trans, id)
+ seen_ids = set()
+ has_empty_inputs = False
+ has_duplicate_inputs = False
+ for job_input_assoc in job.input_datasets:
+ input_dataset_instance = job_input_assoc.dataset
+ if input_dataset_instance.get_total_size() == 0:
+ has_empty_inputs = True
+ input_instance_id = input_dataset_instance.id
+ if input_instance_id in seen_ids:
+ has_duplicate_inputs = True
+ else:
+ seen_ids.add(input_instance_id)
+ # TODO: check percent of failing jobs around a window on job.update_time for handler - report if high.
+ # TODO: check percent of failing jobs around a window on job.update_time for destination_id - report if high.
+ # TODO: sniff inputs (add flag to allow checking files?)
+ return {"has_empty_inputs": has_empty_inputs, "has_duplicate_inputs": has_duplicate_inputs}
+
@expose_api
def inputs(self, trans, id, **kwd):
"""
diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py
index b10c23002f4..6eb8fa7a76c 100644
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -888,6 +888,7 @@ def populate_api_routes(webapp, app):
webapp.mapper.connect('build_for_rerun', '/api/jobs/{id}/build_for_rerun', controller='jobs', action='build_for_rerun', conditions=dict(method=['GET']))
webapp.mapper.connect('resume', '/api/jobs/{id}/resume', controller='jobs', action='resume', conditions=dict(method=['PUT']))
webapp.mapper.connect('job_error', '/api/jobs/{id}/error', controller='jobs', action='error', conditions=dict(method=['POST']))
+ webapp.mapper.connect('common_problems', '/api/jobs/{id}/common_problems', controller='jobs', action='common_problems', conditions=dict(method=['GET']))
# Job files controllers. Only for consumption by remote job runners.
webapp.mapper.resource('file',
diff --git a/test/api/test_jobs.py b/test/api/test_jobs.py
index 22dc29d1d51..e3a5908dacd 100644
--- a/test/api/test_jobs.py
+++ b/test/api/test_jobs.py
@@ -190,6 +190,34 @@ class JobsApiTestCase(api.ApiTestCase):
assert_ok=False)
assert not dataset['visible']
+ @skip_without_tool('empty_output')
+ def test_common_problems(self):
+ with self.dataset_populator.test_history() as history_id:
+ empty_run_response = self.dataset_populator.run_tool(
+ tool_id='empty_output',
+ inputs={},
+ history_id=history_id,
+ )
+ empty_hda = empty_run_response["outputs"][0]
+ cat_empty_twice_run_response = self.dataset_populator.run_tool(
+ tool_id='cat1',
+ inputs={
+ 'input1': {'src': 'hda', 'id': empty_hda['id']},
+ 'queries_0|input2': {'src': 'hda', 'id': empty_hda['id']}
+ },
+ history_id=history_id,
+ )
+ empty_output_job = empty_run_response["jobs"][0]
+ cat_empty_job = cat_empty_twice_run_response["jobs"][0]
+ empty_output_common_problems_response = self._get('jobs/%s/common_problems' % empty_output_job["id"]).json()
+ cat_empty_common_problems_response = self._get('jobs/%s/common_problems' % cat_empty_job["id"]).json()
+ self._assert_has_keys(empty_output_common_problems_response, "has_empty_inputs", "has_duplicate_inputs")
+ self._assert_has_keys(cat_empty_common_problems_response, "has_empty_inputs", "has_duplicate_inputs")
+ assert not empty_output_common_problems_response["has_empty_inputs"]
+ assert cat_empty_common_problems_response["has_empty_inputs"]
+ assert not empty_output_common_problems_response["has_duplicate_inputs"]
+ assert cat_empty_common_problems_response["has_duplicate_inputs"]
+
@skip_without_tool('detect_errors_aggressive')
def test_report_error(self):
with self.dataset_populator.test_history() as history_id: