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()}

Dataset Error

An error occurred while running the tool ${job.tool_id}.

-

Tool execution generated the following messages:

-
${_.escape(job.stderr)}
- + ${this.job_summary(job)}

Troubleshoot This Error

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 = "

Error Details

"; + if (job_messages) { + 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: