diff --git a/lib/galaxy/util/__init__.py b/lib/galaxy/util/__init__.py index b8a35995f2f0..b688d633c9b8 100644 --- a/lib/galaxy/util/__init__.py +++ b/lib/galaxy/util/__init__.py @@ -644,12 +644,6 @@ def pretty_print_time_interval(time=False, precise=False, utc=False): return "a few years ago" -def pretty_print_json(json_data, is_json_string=False): - if is_json_string: - json_data = json.loads(json_data) - return json.dumps(json_data, sort_keys=True, indent=4) - - # characters that are valid valid_chars = set(string.ascii_letters + string.digits + " -=_.()/+*^,:?!") @@ -1621,6 +1615,19 @@ def send_mail(frm, to, subject, body, config, html=None, reply_to=None): :type reply_to: str :param reply_to: Reply-to address (Default None) """ + if config.smtp_server.startswith("mock_emails_to_path://"): + path = config.smtp_server[len("mock_emails_to_path://") :] + email_dict = { + "from": frm, + "to": to, + "subject": subject, + "body": body, + "html": html, + "reply_to": reply_to, + } + email_json = json.to_json_string(email_dict) + with open(path, "w") as f: + f.write(email_json) to = listify(to) if html: diff --git a/lib/galaxy_test/base/populators.py b/lib/galaxy_test/base/populators.py index 69b720369af3..8aa4020bdf84 100644 --- a/lib/galaxy_test/base/populators.py +++ b/lib/galaxy_test/base/populators.py @@ -1048,6 +1048,36 @@ def new_error_dataset(self, history_id: str) -> str: assert output_details["state"] == "error", output_details return output_details["id"] + def report_job_error_raw( + self, job_id: str, dataset_id: str, message: str = "", email: Optional[str] = None + ) -> Response: + url = f"jobs/{job_id}/error" + payload = dict( + dataset_id=dataset_id, + message=message, + ) + if email is not None: + payload["email"] = email + report_response = self._post(url, data=payload, json=True) + return report_response + + def report_job_error( + self, job_id: str, dataset_id: str, message: str = "", email: Optional[str] = None + ) -> Response: + report_response = self.report_job_error_raw(job_id, dataset_id, message=message, email=email) + api_asserts.assert_status_code_is_ok(report_response) + return report_response.json() + + def run_detect_errors(self, history_id: str, exit_code: int, stdout: str = "", stderr: str = "") -> dict: + inputs = { + "stdoutmsg": stdout, + "stderrmsg": stderr, + "exit_code": exit_code, + } + response = self.run_tool("detect_errors", inputs, history_id) + self.wait_for_history(history_id, assert_ok=False) + return response + def run_exit_code_from_file(self, history_id: str, hdca_id: str) -> dict: exit_code_inputs = { "input": {"batch": True, "values": [{"src": "hdca", "id": hdca_id}]}, diff --git a/test/integration/test_error_report.py b/test/integration/test_error_report.py new file mode 100644 index 000000000000..1dac0a9c0104 --- /dev/null +++ b/test/integration/test_error_report.py @@ -0,0 +1,112 @@ +"""Integration tests for user error reporting.""" + +import json +import os +import string + +from galaxy_test.base.populators import DatasetPopulator +from galaxy_test.driver import integration_util + +JSON_ERROR_REPORTS = """ +- type: json + verbose: true + user_submission: true + directory: ${reports_directory} +""" + +MOCK_EMAIL_ERROR_REPORTS = """ +- type: email + verbose: true + user_submission: true +""" + + +class TestErrorReportIntegration(integration_util.IntegrationTestCase): + dataset_populator: DatasetPopulator + framework_tool_and_types = True + + def setUp(self): + super().setUp() + self.dataset_populator = DatasetPopulator(self.galaxy_interactor) + + @classmethod + def handle_galaxy_config_kwds(cls, config): + reports_directory = cls._test_driver.mkdtemp() + cls.reports_directory = reports_directory + template = string.Template(JSON_ERROR_REPORTS) + reports_yaml = template.safe_substitute({"reports_directory": reports_directory}) + reports_conf = os.path.join(reports_directory, "error_report.yml") + with open(reports_conf, "w") as f: + f.write(reports_yaml) + config["error_report_file"] = reports_conf + + def test_basic_tool_error(self): + with self.dataset_populator.test_history() as history_id: + response = self.dataset_populator.run_detect_errors(history_id, 6, "my stdout", "my stderr") + job_id = response["jobs"][0]["id"] + dataset_result = response["outputs"][0] + self.dataset_populator.report_job_error(job_id, dataset_result["id"]) + assert len(os.listdir(self.reports_directory)) == 2 + error_json = self.read_error_report(job_id) + error_dict = json.loads(error_json) + assert error_dict["exit_code"] == 6 + + def test_tool_error_custom_message_and_email(self): + with self.dataset_populator.test_history() as history_id: + response = self.dataset_populator.run_detect_errors(history_id, 6, "my stdout", "my stderr") + job_id = response["jobs"][0]["id"] + dataset_result = response["outputs"][0] + self.dataset_populator.report_job_error( + job_id, dataset_result["id"], "some new details", "notreal@galaxyproject.org" + ) + error_json = self.read_error_report(job_id) + error_dict = json.loads(error_json) + assert error_dict["exit_code"] == 6 + assert error_dict["message"] == "some new details" + assert error_dict["email"] == "notreal@galaxyproject.org" + + def read_error_report(self, job_id: str): + app = self._app + job_id_decoded = app.security.decode_id(job_id) + with open(os.path.join(self.reports_directory, str(job_id_decoded))) as f: + return f.read() + + +class TestErrorEmailReportIntegration(integration_util.IntegrationTestCase): + dataset_populator: DatasetPopulator + framework_tool_and_types = True + + def setUp(self): + super().setUp() + self.dataset_populator = DatasetPopulator(self.galaxy_interactor) + + @classmethod + def handle_galaxy_config_kwds(cls, config): + reports_directory = cls._test_driver.mkdtemp() + cls.reports_directory = reports_directory + template = string.Template(MOCK_EMAIL_ERROR_REPORTS) + reports_yaml = template.safe_substitute({"reports_directory": reports_directory}) + reports_conf = os.path.join(reports_directory, "error_report.yml") + with open(reports_conf, "w") as f: + f.write(reports_yaml) + config["error_report_file"] = reports_conf + config["smtp_server"] = f"mock_emails_to_path://{reports_directory}/email.json" + config["error_email_to"] = "jsonfiles@thefilesystem.org" + + def test_tool_error_custom_message_and_email(self): + with self.dataset_populator.test_history() as history_id: + response = self.dataset_populator.run_detect_errors(history_id, 6, "my stdout", "my stderr") + job_id = response["jobs"][0]["id"] + dataset_result = response["outputs"][0] + self.dataset_populator.report_job_error( + job_id, dataset_result["id"], "some new details", "notreal@galaxyproject.org" + ) + error_json = self.read_most_recent_error_report() + error_dict = json.loads(error_json) + assert error_dict["to"] == "jsonfiles@thefilesystem.org, notreal@galaxyproject.org" + assert error_dict["subject"] == "Galaxy tool error report from notreal@galaxyproject.org (detect_errors)" + assert "