diff --git a/lib/galaxy/tools/errors.py b/lib/galaxy/tools/errors.py
index 251e1e894db..dc4fce2eaff 100644
--- a/lib/galaxy/tools/errors.py
+++ b/lib/galaxy/tools/errors.py
@@ -3,6 +3,7 @@ Functionality for dealing with tool errors.
"""
import string
from galaxy import model, util, web
+import cgi
error_report_template = """
GALAXY TOOL ERROR REPORT
@@ -51,6 +52,91 @@ ${job_traceback}
(This is an automated message).
"""
+error_report_template_html = """
+
+
+
+
+Galaxy Tool Error Report
+from ${host}
+
+Error Localization
+
+
+User Provided Information
+
+The user ${email_str} provided the following information:
+
+
+${message}
+
+
+
+Detailed Job Information
+
+Job environment and execution information is available at the job Info Page.
+
+
+
+ | Job ID | ${job_id} (${job_id_encoded}) |
+ | Tool ID | ${job_tool_id} |
+ | Tool Version | ${tool_version} |
+ | Job PID or DRM id | ${job_runner_external_id} |
+ | Job Tool Version | ${job_tool_version} |
+
+
+
+Job Execution and Failure Information
+
+Command Line
+
+${job_command_line}
+
+
+stderr
+
+${job_stderr}
+
+
+stdout
+
+${job_stdout}
+
+
+Job Information
+
+${job_info}
+
+
+Job Traceback
+
+${job_traceback}
+
+
+This is an automated message. Do not reply to this address.
+
+"""
+
class ErrorReporter( object ):
def __init__( self, hda, app ):
@@ -91,30 +177,40 @@ class ErrorReporter( object ):
email_str = "'%s' (providing preferred contact email '%s')" % (user.email, email)
else:
email_str = "'%s'" % (email or 'anonymously')
- self.report = string.Template( error_report_template ) \
- .safe_substitute( host=host,
- dataset_id_encoded=self.app.security.encode_id( hda.dataset_id ),
- dataset_id=hda.dataset_id,
- history_id_encoded=history_id_encoded,
- history_id=hda.history_id,
- hda_id_encoded=hda_id_encoded,
- hid=hda.hid,
- history_item_name=hda.get_display_name(),
- history_view_link=history_view_link,
- 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,
- job_tool_version=hda.tool_version,
- job_runner_external_id=job.job_runner_external_id,
- job_command_line=job.command_line,
- job_stderr=util.unicodify( job.stderr ),
- job_stdout=util.unicodify( job.stdout ),
- job_info=util.unicodify( job.info ),
- job_traceback=util.unicodify( job.traceback ),
- email_str=email_str,
- message=util.unicodify( message ) )
+
+ report_variables = dict(
+ host=host,
+ dataset_id_encoded=self.app.security.encode_id( hda.dataset_id ),
+ dataset_id=hda.dataset_id,
+ history_id_encoded=history_id_encoded,
+ history_id=hda.history_id,
+ hda_id_encoded=hda_id_encoded,
+ hid=hda.hid,
+ history_item_name=hda.get_display_name(),
+ history_view_link=history_view_link,
+ 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,
+ job_tool_version=hda.tool_version,
+ job_runner_external_id=job.job_runner_external_id,
+ job_command_line=job.command_line,
+ job_stderr=util.unicodify( job.stderr ),
+ job_stdout=util.unicodify( job.stdout ),
+ job_info=util.unicodify( job.info ),
+ job_traceback=util.unicodify( job.traceback ),
+ email_str=email_str,
+ message=util.unicodify( message )
+ )
+
+ self.report = string.Template( error_report_template ).safe_substitute( report_variables )
+
+ # Escape all of the content for use in the HTML report
+ for parameter in report_variables.keys():
+ report_variables[parameter] = cgi.escape(report_variables[parameter])
+
+ self.html_report = string.Template( error_report_template_html ).safe_substitute( report_variables )
def _send_report( self, user, email=None, message=None, **kwd ):
return self.report
@@ -146,5 +242,6 @@ class EmailErrorReporter( ErrorReporter ):
subject = "%s (%s)" % ( subject, self.app.toolbox.get_tool( self.job.tool_id, self.job.tool_version ).old_id )
except Exception:
pass
+
# Send it
- return util.send_mail( frm, to, subject, self.report, self.app.config )
+ return util.send_mail( frm, to, subject, self.report, self.app.config, html=self.html_report )
diff --git a/lib/galaxy/util/__init__.py b/lib/galaxy/util/__init__.py
index 3478801d726..98c47941b60 100644
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -28,7 +28,7 @@ from os.path import normpath, relpath
from xml.etree import ElementInclude, ElementTree
from six import binary_type, iteritems, PY3, string_types, text_type
-from six.moves import email_mime_text, xrange, zip
+from six.moves import email_mime_text, email_mime_multipart, xrange, zip
from six.moves.urllib import parse as urlparse
from six.moves.urllib import request as urlrequest
@@ -1205,19 +1205,51 @@ def size_to_bytes( size ):
return int( size )
-def send_mail( frm, to, subject, body, config ):
+def send_mail( frm, to, subject, body, config, html=None ):
"""
Sends an email.
+
+ :type frm: str
+ :param frm: from address
+
+ :type to: str
+ :param to: to address
+
+ :type subject: str
+ :param subject: Subject line
+
+ :type body: str
+ :param body: Body text (should be plain text)
+
+ :type config: object
+ :param config: Galaxy configuration object
+
+ :type html: str
+ :param html: Alternative HTML representation of the body content. If
+ provided will convert the message to a MIMEMultipart. (Default 'None')
"""
+
to = listify( to )
- msg = email_mime_text.MIMEText( body.encode( 'ascii', 'replace' ) )
+ if html:
+ msg = email_mime_multipart.MIMEMultipart('alternative')
+ else:
+ msg = email_mime_text.MIMEText( body.encode( 'ascii', 'replace' ) )
+
msg[ 'To' ] = ', '.join( to )
msg[ 'From' ] = frm
msg[ 'Subject' ] = subject
+
if config.smtp_server is None:
log.error( "Mail is not configured for this Galaxy instance." )
log.info( msg )
return
+
+ if html:
+ mp_text = email_mime_text.MIMEText( body.encode( 'ascii', 'replace' ), 'plain' )
+ mp_html = email_mime_text.MIMEText( html.encode( 'ascii', 'replace' ), 'html' )
+ msg.attach(mp_text)
+ msg.attach(mp_html)
+
smtp_ssl = asbool( getattr(config, 'smtp_ssl', False ) )
if smtp_ssl:
s = smtplib.SMTP_SSL()