diff --git a/pom.xml b/pom.xml index 63db319..9f6d8c9 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ <properties> <powermock.version>1.4.5</powermock.version> </properties> - + <developers> <developer> <id>ashlux</id> @@ -39,7 +39,7 @@ <groupId>org.jvnet.hudson.main</groupId> <artifactId>maven-plugin</artifactId> </dependency> - + <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> @@ -71,6 +71,12 @@ <version>1.8.5</version> <scope>test</scope> </dependency> + + <dependency> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy-all</artifactId> + <version>1.5.6</version> + </dependency> <dependency> <groupId>org.powermock.modules</groupId> <artifactId>powermock-module-junit4</artifactId> diff --git a/src/main/java/hudson/plugins/emailext/EmailType.java b/src/main/java/hudson/plugins/emailext/EmailType.java index 20f5f94..e5687df 100644 --- a/src/main/java/hudson/plugins/emailext/EmailType.java +++ b/src/main/java/hudson/plugins/emailext/EmailType.java @@ -40,6 +40,11 @@ public class EmailType { */ private boolean sendToRecipientList; + /** + * Specifies whether the mail's content should be interpreted as Groovy script + */ + private boolean script; + public EmailType(){ subject = ""; body = ""; @@ -47,6 +52,7 @@ public class EmailType { sendToDevelopers = false; includeCulprits = false; sendToRecipientList = false; + script = false; } public String getSubject() { @@ -104,4 +110,11 @@ public class EmailType { this.recipientList = recipientList; } + public boolean isScript() { + return script; + } + + public void setScript(boolean script) { + this.script = script; + } } diff --git a/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisher.java b/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisher.java index 6800dae..afb15d8 100644 --- a/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisher.java +++ b/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisher.java @@ -3,10 +3,7 @@ package hudson.plugins.emailext; import hudson.EnvVars; import hudson.Extension; import hudson.Launcher; -import hudson.model.AbstractBuild; -import hudson.model.BuildListener; -import hudson.model.Result; -import hudson.model.User; +import hudson.model.*; import hudson.plugins.emailext.plugins.ContentBuilder; import hudson.plugins.emailext.plugins.EmailTrigger; import hudson.plugins.emailext.plugins.EmailTriggerDescriptor; @@ -17,6 +14,9 @@ import hudson.tasks.MailMessageIdAction; import hudson.tasks.Mailer; import hudson.tasks.Notifier; import hudson.tasks.Publisher; +import hudson.util.FormValidation; +import net.sf.json.JSONObject; +import org.kohsuke.stapler.*; import javax.mail.Address; import javax.mail.Message; @@ -37,6 +37,10 @@ import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import java.nio.charset.Charset; + +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerRequest; /** * {@link Publisher} that sends notification e-mail. @@ -56,6 +60,8 @@ public class ExtendedEmailPublisher extends Notifier { public static final String CHARSET = "utf-8"; + public static final String DEFAULT_CHARSET_SENTINAL = "default"; + public static void addEmailTriggerType(EmailTriggerDescriptor triggerType) throws EmailExtException { if(EMAIL_TRIGGER_TYPE_MAP.containsKey(triggerType.getMailerId())) throw new EmailExtException("An email trigger type with name " + @@ -102,6 +108,11 @@ public class ExtendedEmailPublisher extends Notifier { public String contentType; /** + * The charset of the emails for this project. + */ + public String charset; + + /** * The default subject of the emails for this project. ($PROJECT_DEFAULT_SUBJECT) */ public String defaultSubject; @@ -111,6 +122,10 @@ public class ExtendedEmailPublisher extends Notifier { */ public String defaultContent; + public boolean defaultContentIsScript; + + public String buildForTesting; + /** * Get the list of configured email triggers for this project. */ @@ -248,8 +263,7 @@ public class ExtendedEmailPublisher extends Notifier { msg.setFrom(new InternetAddress(ExtendedEmailPublisher.DESCRIPTOR.getAdminAddress())); } - // Set the contents of the email - + //Set the contents of the email msg.setSentDate(new Date()); setSubject( type, build, msg ); @@ -304,18 +318,37 @@ public class ExtendedEmailPublisher extends Notifier { return msg; } + private static boolean isNullOrBlank(String s) { // or, add dependency on commons-lang StringUtils instead? + return s == null || s.trim().length() == 0; + } + + private String getCharset() { + String cs = charset; + if (isNullOrBlank(cs) || DEFAULT_CHARSET_SENTINAL.equalsIgnoreCase(cs)) { + cs = DESCRIPTOR.getDefaultCharset(); + } + if (isNullOrBlank(cs) || DEFAULT_CHARSET_SENTINAL.equalsIgnoreCase(cs)) { + return CHARSET; + } else { + return cs; + } + } + private void setSubject( final EmailType type, final AbstractBuild<?, ?> build, MimeMessage msg ) throws MessagingException { String subject = new ContentBuilder().transformText(type.getSubject(), this, type, build); - msg.setSubject(subject, CHARSET); + msg.setSubject(subject, getCharset()); } private void setContent( final EmailType type, final AbstractBuild<?, ?> build, MimeMessage msg ) throws MessagingException { final String text = new ContentBuilder().transformText(type.getBody(), this, type, build); + msg.setContent(text, getContentType()); + } + public String getContentType() { String messageContentType = contentType; // contentType is null if the project was not reconfigured after upgrading. if (messageContentType == null || "default".equals(messageContentType)) { @@ -326,9 +359,8 @@ public class ExtendedEmailPublisher extends Notifier { messageContentType = "text/plain"; } } - messageContentType += "; charset=" + CHARSET; - - msg.setContent(text, messageContentType); + messageContentType += "; charset=" + getCharset(); + return messageContentType; } private static void addAddressesFromRecipientList(Set<InternetAddress> addresses, String recipientList, @@ -356,6 +388,7 @@ public class ExtendedEmailPublisher extends Notifier { return DESCRIPTOR; } + @Extension public static final ExtendedEmailPublisherDescriptor DESCRIPTOR = new ExtendedEmailPublisherDescriptor(); diff --git a/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisherDescriptor.java b/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisherDescriptor.java index c175efe..f9f8af4 100644 --- a/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisherDescriptor.java +++ b/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisherDescriptor.java @@ -1,7 +1,10 @@ package hudson.plugins.emailext; +import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Hudson; +import hudson.model.Job; +import hudson.plugins.emailext.plugins.ContentBuilder; import hudson.plugins.emailext.plugins.EmailTrigger; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Publisher; @@ -10,6 +13,7 @@ import hudson.util.Secret; import net.sf.json.JSONObject; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; import javax.mail.Authenticator; import javax.mail.PasswordAuthentication; @@ -18,6 +22,7 @@ import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.servlet.ServletException; import java.io.IOException; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Properties; @@ -73,6 +78,11 @@ public class ExtendedEmailPublisherDescriptor */ private String defaultContentType; + /** + * This is a global default charset (mime type) for emails. + */ + private String defaultCharset; + /** * This is a global default subject line for sending emails. */ @@ -83,6 +93,16 @@ public class ExtendedEmailPublisherDescriptor */ private String defaultBody; + /** + * This indicates that the global default body or subject line should be evaluated as a script. + */ + private boolean defaultIsScript; + + /** + * This just remembers the last build for testing that was saved, for the user's convenience. + */ + public String defaultBuildForTesting; + private boolean overrideGlobalSettings; @Override @@ -106,6 +126,11 @@ public class ExtendedEmailPublisherDescriptor return defaultSuffix; } + public String getDefaultCharset() + { + return defaultCharset; + } + /** * JavaMail session. */ @@ -166,7 +191,7 @@ public class ExtendedEmailPublisherDescriptor public String getHudsonUrl() { - return hudsonUrl; + return hudsonUrl != null ? hudsonUrl : Hudson.getInstance().getRootUrl(); } public String getSmtpServer() @@ -209,6 +234,14 @@ public class ExtendedEmailPublisherDescriptor return defaultBody; } + public boolean getDefaultIsScript() { + return defaultIsScript; + } + + public String getDefaultBuildForTesting() { + return defaultBuildForTesting; + } + public boolean getOverrideGlobalSettings() { return overrideGlobalSettings; @@ -230,8 +263,11 @@ public class ExtendedEmailPublisherDescriptor ExtendedEmailPublisher m = new ExtendedEmailPublisher(); m.recipientList = listRecipients; m.contentType = formData.getString( "project_content_type" ); + m.charset = formData.getString("project_charset"); m.defaultSubject = formData.getString( "project_default_subject" ); m.defaultContent = formData.getString( "project_default_content" ); + m.defaultContentIsScript = formData.optBoolean("project_default_content_is_script"); + m.buildForTesting = formData.getString("project_build_for_testing"); m.configuredTriggers = new ArrayList<EmailTrigger>(); // Create a new email trigger for each one that is configured @@ -314,10 +350,13 @@ public class ExtendedEmailPublisherDescriptor smtpPort = nullify( req.getParameter( "ext_mailer_smtp_port" ) ); defaultContentType = nullify( req.getParameter( "ext_mailer_default_content_type" ) ); + defaultCharset = nullify(req.getParameter("ext_mailer_default_charset")); // Allow global defaults to be set for the subject and body of the email defaultSubject = nullify( req.getParameter( "ext_mailer_default_subject" ) ); defaultBody = nullify( req.getParameter( "ext_mailer_default_body" ) ); + defaultIsScript = req.getParameter("ext_mailer_default_is_script") != null; + defaultBuildForTesting = req.getParameter("ext_mailer_default_build_for_testing"); overrideGlobalSettings = req.getParameter( "ext_mailer_override_global_settings" ) != null; @@ -358,6 +397,144 @@ public class ExtendedEmailPublisherDescriptor throws IOException, ServletException { return new EmailRecepientUtils().validateFormRecipientList( value ); - } + } + + public FormValidation doCharsetCheck(StaplerRequest req, StaplerResponse rsp, @QueryParameter final String value) throws IOException, ServletException { + String charset = nullify(value); + if (charset == null || ExtendedEmailPublisher.DEFAULT_CHARSET_SENTINAL.equalsIgnoreCase(charset) || Charset.isSupported(charset)) { + return FormValidation.ok(); + } else { + return FormValidation.error("unsupported charset"); + } + } + + public FormValidation doBuildForTestingCheck(StaplerRequest req, StaplerResponse rsp, @QueryParameter final String value) throws IOException, ServletException { + String buildForTesting = nullify(value); + if (buildForTesting == null) { + return FormValidation.ok(); + } + try { + getBuildForTesting(buildForTesting); + return FormValidation.ok(); + } + catch (FormValidation e) { + return e; + } + } + + // validateButton in config.jelly + public FormValidation doTestAgainstBuild(StaplerRequest req) throws IOException, ServletException { + ExtendedEmailPublisher publisher = new ExtendedEmailPublisher(); + publisher.contentType = req.getParameter("project_content_type"); + publisher.charset = req.getParameter("project_charset"); + publisher.defaultSubject = req.getParameter("project_default_subject"); + publisher.defaultContent = req.getParameter("project_default_content"); + publisher.defaultContentIsScript = Boolean.valueOf(req.getParameter("project_default_content_is_script")); + publisher.buildForTesting = req.getParameter("project_build_for_testing"); + return doTestAgainstBuild(publisher, false, req); + } + + // validateButton in global.jelly + public FormValidation doGlobalTestAgainstBuild(StaplerRequest req) throws IOException, ServletException { + ExtendedEmailPublisher publisher = new ExtendedEmailPublisher(); + // testing at project level because the corresponding globals are static + publisher.contentType = req.getParameter("ext_mailer_default_content_type"); + publisher.charset = req.getParameter("ext_mailer_default_charset"); + publisher.defaultSubject = req.getParameter("ext_mailer_default_subject"); + publisher.defaultContent = req.getParameter("ext_mailer_default_body"); + publisher.defaultContentIsScript = Boolean.valueOf(req.getParameter("ext_mailer_default_is_script")); + publisher.buildForTesting = req.getParameter("ext_mailer_default_build_for_testing"); + return doTestAgainstBuild(publisher, true, req); + } + + // for iframe callback + private String testedEmailText; + private String testedEmailContentType; + private FormValidation doTestAgainstBuild(ExtendedEmailPublisher publisher, + boolean globallyResolved, StaplerRequest req) throws FormValidation { + if (nullify(publisher.buildForTesting) == null) { + return FormValidation.error("need to specify a build for testing"); + } + testedEmailContentType = publisher.getContentType(); + AbstractBuild build = getBuildForTesting(publisher.buildForTesting); + String subject; + if (globallyResolved) { + // Work around ContentBuilder.transformText()'s static access of the + // global subject and body, + // which has not been updated before testing. + subject = transformResolvedText(publisher.defaultSubject, + publisher, build); + testedEmailText = transformResolvedText(publisher.defaultContent, + publisher, build); + } else { + // use default tokens to induce resolution for project-level script + // flag + subject = transformText( + ExtendedEmailPublisher.PROJECT_DEFAULT_SUBJECT_TEXT, + publisher, build); + testedEmailText = transformText( + ExtendedEmailPublisher.PROJECT_DEFAULT_BODY_TEXT, + publisher, build); + } + String resultUrl = req.getRequestURI() + .replace("testAgainstBuild", "testedEmailText") + .replace("globalTestAgainstBuild", "testedEmailText"); // todo: + // something + // less + // hacky? + return FormValidation + .okWithMarkup("resulting subject: " + + subject // todo: subject charset? + + "<br/>resulting body:<br/> <iframe width='100%' height='400px' src='" + + resultUrl + "'/>"); } + + private static String transformResolvedText(String text, ExtendedEmailPublisher publisher, AbstractBuild build) { + return new ContentBuilder().transformResolvedText(publisher.defaultContentIsScript, text, publisher, new EmailType(), build); + } + + private static String transformText(String text, ExtendedEmailPublisher publisher, AbstractBuild build) { + return new ContentBuilder().transformText(text, publisher, new EmailType(), build); + } + + // callback from iframe + public void doTestedEmailText(StaplerRequest req, StaplerResponse rsp) throws IOException { + rsp.setContentType(testedEmailContentType); + rsp.getWriter().write(testedEmailText); + } + + private AbstractBuild getBuildForTesting(String buildForTesting) throws FormValidation { + int slashIndex = buildForTesting.indexOf('/'); + if (slashIndex == -1) { + throw FormValidation.error("must format as '<jobName>/<buildNumber>'"); + } + String jobName = buildForTesting.substring(0, slashIndex); + String buildNumber = buildForTesting.substring(slashIndex + 1); + Job job; + try { + job = (Job) Hudson.getInstance().getItem(jobName); + } + catch (ClassCastException e) { + throw FormValidation.error(jobName + " is not a job"); + } + if (job == null) { + throw FormValidation.error(jobName + " job not found"); + } + AbstractBuild build; + try { + build = (AbstractBuild) job.getBuildByNumber(Integer.valueOf(buildNumber)); + } + catch (NumberFormatException e) { + throw FormValidation.error("cannot parse build number: " + e.getMessage()); + } + catch (ClassCastException e) { + throw FormValidation.error("not a build: " + e.getMessage()); + } + if (build == null) { + throw FormValidation.error("build " + buildNumber + " not found"); + } + return build; + } + +} diff --git a/src/main/java/hudson/plugins/emailext/plugins/ContentBuilder.java b/src/main/java/hudson/plugins/emailext/plugins/ContentBuilder.java index 1da8925..f79a3aa 100644 --- a/src/main/java/hudson/plugins/emailext/plugins/ContentBuilder.java +++ b/src/main/java/hudson/plugins/emailext/plugins/ContentBuilder.java @@ -1,5 +1,7 @@ package hudson.plugins.emailext.plugins; +import groovy.text.SimpleTemplateEngine; +import groovy.text.Template; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.plugins.emailext.EmailExtException; @@ -60,15 +62,24 @@ public class ContentBuilder { } public String transformText(String origText, ExtendedEmailPublisher publisher, EmailType type, AbstractBuild<?,?> build) { - String newText = origText.replaceAll(PROJECT_DEFAULT_BODY, Matcher.quoteReplacement(publisher.defaultContent)) - .replaceAll(PROJECT_DEFAULT_SUBJECT, Matcher.quoteReplacement(publisher.defaultSubject)) - .replaceAll(DEFAULT_BODY, Matcher.quoteReplacement(ExtendedEmailPublisher.DESCRIPTOR.getDefaultBody())) - .replaceAll(DEFAULT_SUBJECT, Matcher.quoteReplacement(ExtendedEmailPublisher.DESCRIPTOR.getDefaultSubject())); - - newText = replaceTokensWithContent(newText, publisher, type, build); - return newText; + boolean isScript = type.isScript(); + String projectlyResolvedText = origText.replaceAll(PROJECT_DEFAULT_BODY, Matcher.quoteReplacement(publisher.defaultContent)) + .replaceAll(PROJECT_DEFAULT_SUBJECT, Matcher.quoteReplacement(publisher.defaultSubject)); + boolean usingSomeProjectDefaultContent = !projectlyResolvedText.equals(origText); + isScript |= publisher.defaultContentIsScript && usingSomeProjectDefaultContent; + String globallyResolvedText = projectlyResolvedText.replaceAll(DEFAULT_BODY, Matcher.quoteReplacement(ExtendedEmailPublisher.DESCRIPTOR.getDefaultBody())) + .replaceAll(DEFAULT_SUBJECT, Matcher.quoteReplacement(ExtendedEmailPublisher.DESCRIPTOR.getDefaultSubject())); + boolean usingSomeGlobalDefaultContent = !globallyResolvedText.equals(projectlyResolvedText); + isScript |= ExtendedEmailPublisher.DESCRIPTOR.getDefaultIsScript() && usingSomeGlobalDefaultContent; + return transformResolvedText(isScript, globallyResolvedText, publisher, type, build); + } + + // exposed for testing + public String transformResolvedText(boolean isScript, String text, ExtendedEmailPublisher publisher, EmailType type, AbstractBuild<?,?> build) { + return isScript ? transformUsingScript(text, publisher, type, build) + : replaceTokensWithContent(text, publisher, type, build); } - + private static <P extends AbstractProject<P, B>, B extends AbstractBuild<P, B>> String replaceTokensWithContent(String origText, ExtendedEmailPublisher publisher, EmailType type, AbstractBuild<P, B> build) { StringBuffer sb = new StringBuffer(); @@ -192,4 +203,37 @@ public class ContentBuilder { } + private static <P extends AbstractProject<P, B>, B extends AbstractBuild<P, B>> + String transformUsingScript(String origText, ExtendedEmailPublisher publisher, EmailType type, AbstractBuild<P,B> build) { + SimpleTemplateEngine engine = new SimpleTemplateEngine(); + Template template = null; + try { + template = engine.createTemplate(origText); + Map binding = new HashMap(); + Map<String,Object> args = new HashMap<String,Object>(); + //Setting the AbstractBuild as a bind variable + binding.put("build", build); + + //Set all the EmailContent as bind variables to be directly used in the script + for(Map.Entry<String, EmailContent> contentEntry : EMAIL_CONTENT_TYPE_MAP.entrySet()){ + //do the replacement only if really needed to avoid exception like missing argumet etc. + if(origText.contains(contentEntry.getKey())){ + EmailContent content = contentEntry.getValue(); + String contentText = content.getContent(build, publisher, type, args); + if(content.hasNestedContent()){ + contentText = replaceTokensWithContent(contentText, publisher, type, build); + } + binding.put(contentEntry.getKey(), contentText); + } + } + + return template.make(binding).toString(); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Error creating GroovyTemplate from text ["+origText+"]",e); + //Throwing the exception as Runtime as any exception here would mostly (?) be due to incorrect script + //and not an expected reason + throw new RuntimeException("Error using the template",e); + } + } + } diff --git a/src/main/resources/hudson/plugins/emailext/ExtendedEmailPublisher/config.jelly b/src/main/resources/hudson/plugins/emailext/ExtendedEmailPublisher/config.jelly index e6a02ea..eca3cb6 100644 --- a/src/main/resources/hudson/plugins/emailext/ExtendedEmailPublisher/config.jelly +++ b/src/main/resources/hudson/plugins/emailext/ExtendedEmailPublisher/config.jelly @@ -1,6 +1,6 @@ -<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" - xmlns:d="jelly:define" xmlns:l="/lib/layout" - xmlns:t="/lib/hudson" xmlns:f="/lib/form" +<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" + xmlns:d="jelly:define" xmlns:l="/lib/layout" + xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:m="/hudson/plugins/emailext/tags"> <!-- This script defines the view for the extended email plugin. @@ -19,9 +19,9 @@ help="/plugin/email-ext/help/projectConfig/globalRecipientList.html"> <input class="setting-input validated" name="recipientlist_recipients" type="text" value="${instance.recipientList}" - checkUrl="'${rootURL}/publisher/ExtendedEmailPublisher/recipientListRecipientsCheck?value='+encodeURIComponent(this.value)"/> + checkUrl="'${rootURL}/publisher/ExtendedEmailPublisher/recipientListRecipientsCheck?value='+toValue(this)"/> </f:entry> - + <!-- This is the content type for the emails. --> <f:entry title="Content Type" help="/plugin/email-ext/help/projectConfig/contentType.html"> @@ -39,6 +39,25 @@ </select> </f:entry> + <f:entry title="Charset" + help="/plugin/email-ext/help/projectConfig/charset.html"> + <!--input class="setting-input validated" name="project_charset" + type="text" value="${instance.charset}" + checkUrl="'${rootURL}/publisher/ExtendedEmailPublisher/charsetCheck?value='+toValue(this)"/--> + <j:choose> + <j:when test="${instance.configured}"> + <input class="setting-input validated" name="project_charset" + type="text" value="${instance.charset}" + checkUrl="'${rootURL}/publisher/ExtendedEmailPublisher/charsetCheck?value='+toValue(this)"/> + </j:when> + <j:otherwise> + <input class="setting-input validated" name="project_charset" + type="text" value="default" + checkUrl="'${rootURL}/publisher/ExtendedEmailPublisher/charsetCheck?value='+toValue(this)"/> + </j:otherwise> + </j:choose> + </f:entry> + <!-- This is the default subject line for the project. --> <f:entry title="Default Subject" help="/plugin/email-ext/help/projectConfig/defaultSubject.html"> @@ -54,6 +73,19 @@ </j:choose> </f:entry> + <f:entry title="Default Content is Script" + help="/plugin/email-ext/help/projectConfig/mailType/script.html"> + <j:choose> + <j:when test="${instance.configured}"> + <f:checkbox name="project_default_content_is_script" + checked="${instance.defaultContentIsScript}" /> + </j:when> + <j:otherwise> + <f:checkbox name="project_default_content_is_script" checked="false" /> + </j:otherwise> + </j:choose> + </f:entry> + <!-- This is the default content for the project. --> <f:entry title="Default Content" help="/plugin/email-ext/help/projectConfig/defaultBody.html"> @@ -72,7 +104,7 @@ </j:otherwise> </j:choose> </f:entry> - + <!-- This is the help section. It displays a bunch of dynamic help for all content tokens. --> <tr> <td></td> @@ -87,12 +119,21 @@ <td colspan="2"><div id="${secId}contentTokenHelpConf" class="help" style="display:none">${contentTokenText}</div></td> <td></td> </tr> - - <!-- Configure advanced properties like per-build-result status email contents, + + <f:entry title="Build for Testing" + help="/plugin/email-ext/help/projectConfig/buildForTesting.html"> + <input class="setting-input validated" name="project_build_for_testing" + type="text" value="${instance.buildForTesting}" + checkUrl="'${rootURL}/publisher/ExtendedEmailPublisher/buildForTestingCheck?value='+toValue(this)"/> + </f:entry> + + <f:validateButton title="${%Test Against Build}" method="testAgainstBuild" with="project_build_for_testing,project_default_content_is_script,project_default_content,project_default_subject,project_charset,project_content_type,recipientlist_recipients" /> + + <!-- Configure advanced properties like per-build-result status email contents, whether or not to send email to developers who made changes, and whether or not to send email to the global list of devs--> - <f:advanced> - + <f:advanced> + <tr> <td></td> <td colspan="3" style="margin-left:10px"> @@ -132,23 +173,23 @@ </td> <td/> </tr> - + <j:forEach var="trigger" items="${instance.configuredTriggers}"> - <m:mailtype title="${trigger.descriptor.triggerName}" + <m:mailtype title="${trigger.descriptor.triggerName}" mailType="${trigger.descriptor.mailerId}" - mailTypeObj="${trigger.email}" - sendToList="${trigger.defaultSendToList}" - sendToDevs="${trigger.defaultSendToDevs}" + mailTypeObj="${trigger.email}" + sendToList="${trigger.defaultSendToList}" + sendToDevs="${trigger.defaultSendToDevs}" includeCulps="${trigger.defaultIncludeCulps}" trigger="${trigger}" configured="${true}" secId="${secId}"> </m:mailtype> </j:forEach> - + <j:choose> <j:when test="${instance==null}"> - <j:invokeStatic var="nonConfigTriggers" method="getTriggersForNonConfiguredInstance" className="hudson.plugins.emailext.ExtendedEmailPublisher"/> + <j:invokeStatic var="nonConfigTriggers" method="getTriggersForNonConfiguredInstance" className="hudson.plugins.emailext.ExtendedEmailPublisher"/> </j:when> <j:otherwise> <j:set var="nonConfigTriggers" value="${instance.nonConfiguredTriggers}" /> @@ -185,19 +226,19 @@ </tr> </tbody> </table> - + <!-- This table is for non-configured types. We'll do some dom manipulation to make sure that when a trigger is selected from the dropdown that it - is added to the configured table. Also, if a configured trigger is + is added to the configured table. Also, if a configured trigger is deleted from the configured table, we'll add it here. --> <table style="display:none"> <tbody id="${secId}non-configured-email-triggers" > <j:forEach var="trigger" items="${nonConfigTriggers}"> - <m:mailtype title="${trigger.descriptor.triggerName}" + <m:mailtype title="${trigger.descriptor.triggerName}" mailType="${trigger.descriptor.mailerId}" - mailTypeObj="${trigger.email}" - sendToList="${trigger.defaultSendToList}" - sendToDevs="${trigger.defaultSendToDevs}" + mailTypeObj="${trigger.email}" + sendToList="${trigger.defaultSendToList}" + sendToDevs="${trigger.defaultSendToDevs}" includeCulps="${trigger.defaultIncludeCulps}" trigger="${trigger}" configured="${false}" @@ -206,8 +247,8 @@ </j:forEach> </tbody> </table> - - <!-- This select element is used to hold option elements for the configured mailers. + + <!-- This select element is used to hold option elements for the configured mailers. We'll do some dom manipulation to make sure that configured triggers' select option elements are placed here so that they are not shown.--> <select style="display:none" id="${secId}configured-options"> diff --git a/src/main/resources/hudson/plugins/emailext/ExtendedEmailPublisher/global.jelly b/src/main/resources/hudson/plugins/emailext/ExtendedEmailPublisher/global.jelly index 28c11f1..bda7c58 100644 --- a/src/main/resources/hudson/plugins/emailext/ExtendedEmailPublisher/global.jelly +++ b/src/main/resources/hudson/plugins/emailext/ExtendedEmailPublisher/global.jelly @@ -15,7 +15,7 @@ <f:entry title="System Admin E-mail Address" help="/descriptor/hudson.tasks.Mailer/help/adminAddress"> <f:textbox name="ext_mailer_admin_address" value="${descriptor.adminAddress}" - checkUrl="'${rootURL}/publisher/Mailer/addressCheck?value='+encode(this.value)"/> + checkUrl="'${rootURL}/publisher/Mailer/addressCheck?value='+toValue(this)"/> </f:entry> <f:entry title="Hudson URL" help="/descriptor/hudson.tasks.Mailer/help/url"> @@ -57,6 +57,12 @@ >HTML (text/html)</f:option> </select> </f:entry> + <f:entry title="Default Charset" + help="/plugin/email-ext/help/globalConfig/defaultCharset.html"> + <input class="setting-input validated" name="ext_mailer_default_charset" + type="text" value="${descriptor.defaultCharset}" + checkUrl="'${rootURL}/publisher/ExtendedEmailPublisher/charsetCheck?value='+toValue(this)"/> + </f:entry> <f:entry title="Default Subject" help="/plugin/email-ext/help/globalConfig/defaultSubject.html"> <input class="setting-input" name="ext_mailer_default_subject" @@ -68,6 +74,21 @@ name="ext_mailer_default_body" value="${descriptor.defaultBody}"/> </f:entry> + <f:entry title="Default is Script" + help="/plugin/email-ext/help/globalConfig/defaultIsScript.html"> + <f:checkbox name="ext_mailer_default_is_script" checked="${descriptor.defaultIsScript}" /> + </f:entry> + + <f:entry title="Build for Testing" + help="/plugin/email-ext/help/globalConfig/buildForTesting.html"> + <input class="setting-input validated" name="ext_mailer_default_build_for_testing" + type="text" value="${descriptor.defaultBuildForTesting}" + checkUrl="'${rootURL}/publisher/ExtendedEmailPublisher/buildForTestingCheck?value='+toValue(this)"/> + </f:entry> + + <f:validateButton title="${%Test Against Build}" method="globalTestAgainstBuild" + with="ext_mailer_default_build_for_testing,ext_mailer_default_is_script,ext_mailer_default_subject,ext_mailer_default_body,ext_mailer_default_charset,ext_mailer_default_content_type" /> + <!-- This is the help section. It displays a bunch of dynamic help for all content tokens. --> <tr> <td></td> diff --git a/src/main/resources/hudson/plugins/emailext/tags/mailtype.jelly b/src/main/resources/hudson/plugins/emailext/tags/mailtype.jelly index a0e0e85..aa4d03d 100644 --- a/src/main/resources/hudson/plugins/emailext/tags/mailtype.jelly +++ b/src/main/resources/hudson/plugins/emailext/tags/mailtype.jelly @@ -123,6 +123,11 @@ name="mailer_${mailType}_body" value="${mailTypeObj.body}"/> </f:entry> + <f:entry title="Content is Script" + help="${rootURL}/plugin/email-ext/help/projectConfig/mailType/script.html"> + <f:checkbox name="mailer_${mailType}_script" + checked="${mailTypeObj.script}" /> + </f:entry> </table> </td> </tr> diff --git a/src/main/webapp/help/globalConfig/buildForTesting.html b/src/main/webapp/help/globalConfig/buildForTesting.html new file mode 100644 index 0000000..017db19 --- /dev/null +++ b/src/main/webapp/help/globalConfig/buildForTesting.html @@ -0,0 +1,3 @@ +<div> + Specifies the build used by the Test Against Build button. Example: common/12 +</div> diff --git a/src/main/webapp/help/globalConfig/defaultCharset.html b/src/main/webapp/help/globalConfig/defaultCharset.html new file mode 100644 index 0000000..9f73c3c --- /dev/null +++ b/src/main/webapp/help/globalConfig/defaultCharset.html @@ -0,0 +1,4 @@ +<div> + The default charset of the emails sent after a build. + If this is set to "default", then it uses UTF-8. +</div> diff --git a/src/main/webapp/help/globalConfig/defaultIsScript.html b/src/main/webapp/help/globalConfig/defaultIsScript.html new file mode 100644 index 0000000..45ba123 --- /dev/null +++ b/src/main/webapp/help/globalConfig/defaultIsScript.html @@ -0,0 +1,4 @@ +<div> + Indicates that the global default subject or content includes a script. + If they are used within an email, the whole email will be evaluated as a script. +</div> diff --git a/src/main/webapp/help/projectConfig/buildForTesting.html b/src/main/webapp/help/projectConfig/buildForTesting.html new file mode 100644 index 0000000..017db19 --- /dev/null +++ b/src/main/webapp/help/projectConfig/buildForTesting.html @@ -0,0 +1,3 @@ +<div> + Specifies the build used by the Test Against Build button. Example: common/12 +</div> diff --git a/src/main/webapp/help/projectConfig/charset.html b/src/main/webapp/help/projectConfig/charset.html new file mode 100644 index 0000000..a30d9bf --- /dev/null +++ b/src/main/webapp/help/projectConfig/charset.html @@ -0,0 +1,4 @@ +<div> + The charset of the emails sent after a build. If this is set + to "default", then it uses the value set on the main configuration page. +</div> diff --git a/src/main/webapp/help/projectConfig/mailType/script.html b/src/main/webapp/help/projectConfig/mailType/script.html new file mode 100644 index 0000000..c97b9fd --- /dev/null +++ b/src/main/webapp/help/projectConfig/mailType/script.html @@ -0,0 +1,6 @@ +<div> + Specifies whether the mail's content should be interpreted as a Groovy + <a href="http://groovy.codehaus.org/Groovy+Templates">SimpleTemplate</a> script. + If so, the script can access the <code>AbstractBuild</code> object using the + <code>build</code> bind variable. +</div> diff --git a/src/test/java/hudson/plugins/emailext/ExtendedEmailPublisherTest.java b/src/test/java/hudson/plugins/emailext/ExtendedEmailPublisherTest.java index 56560d3..1bcb785 100644 --- a/src/test/java/hudson/plugins/emailext/ExtendedEmailPublisherTest.java +++ b/src/test/java/hudson/plugins/emailext/ExtendedEmailPublisherTest.java @@ -245,9 +245,11 @@ public class ExtendedEmailPublisherTest { JSONObject form = new JSONObject(); form.put( "project_content_type", "default" ); + form.put( "project_charset", "default" ); form.put( "recipientlist_recipients", "ashlux@gmail.com" ); form.put( "project_default_subject", "Make millions in Nigeria" ); form.put( "project_default_content", "Give me a $1000 check and I'll mail you back $5000!!!" ); + form.put( "project_build_for_testing", "" ); publisher = (ExtendedEmailPublisher) ExtendedEmailPublisher.DESCRIPTOR.newInstance( null, form ); diff --git a/src/test/java/hudson/plugins/emailext/plugins/ContentBuilderTest.java b/src/test/java/hudson/plugins/emailext/plugins/ContentBuilderTest.java index 11eb30b..7b5a584 100644 --- a/src/test/java/hudson/plugins/emailext/plugins/ContentBuilderTest.java +++ b/src/test/java/hudson/plugins/emailext/plugins/ContentBuilderTest.java @@ -1,6 +1,7 @@ package hudson.plugins.emailext.plugins; import hudson.model.AbstractBuild; +import hudson.plugins.emailext.EmailType; import hudson.plugins.emailext.ExtendedEmailPublisher; import hudson.plugins.emailext.ExtendedEmailPublisherDescriptor; import org.jvnet.hudson.test.HudsonTestCase; @@ -147,18 +148,18 @@ public class ContentBuilderTest public void testTransformText_shouldExpand_$PROJECT_DEFAULT_CONTENT() throws IOException, InterruptedException { - assertEquals(publisher.defaultContent, new ContentBuilder().transformText( "$PROJECT_DEFAULT_CONTENT", publisher, null, + assertEquals(publisher.defaultContent, new ContentBuilder().transformText( "$PROJECT_DEFAULT_CONTENT", publisher, new EmailType(), mock( AbstractBuild.class ) )); - assertEquals(publisher.defaultContent, new ContentBuilder().transformText( "${PROJECT_DEFAULT_CONTENT}", publisher, null, + assertEquals(publisher.defaultContent, new ContentBuilder().transformText( "${PROJECT_DEFAULT_CONTENT}", publisher, new EmailType(), mock( AbstractBuild.class ) )); } public void testTransformText_shouldExpand_$PROJECT_DEFAULT_SUBJECT() throws IOException, InterruptedException { - assertEquals(publisher.defaultSubject, new ContentBuilder().transformText( "$PROJECT_DEFAULT_SUBJECT", publisher, null, + assertEquals(publisher.defaultSubject, new ContentBuilder().transformText( "$PROJECT_DEFAULT_SUBJECT", publisher, new EmailType(), mock( AbstractBuild.class ) )); - assertEquals(publisher.defaultSubject, new ContentBuilder().transformText( "${PROJECT_DEFAULT_SUBJECT}", publisher, null, + assertEquals(publisher.defaultSubject, new ContentBuilder().transformText( "${PROJECT_DEFAULT_SUBJECT}", publisher, new EmailType(), mock( AbstractBuild.class ) )); } @@ -166,10 +167,10 @@ public class ContentBuilderTest throws IOException, InterruptedException { assertEquals( ExtendedEmailPublisher.DESCRIPTOR.getDefaultBody(), - new ContentBuilder().transformText( "$DEFAULT_CONTENT", publisher, null, + new ContentBuilder().transformText( "$DEFAULT_CONTENT", publisher, new EmailType(), mock( AbstractBuild.class ) ) ); assertEquals( ExtendedEmailPublisher.DESCRIPTOR.getDefaultBody(), - new ContentBuilder().transformText( "${DEFAULT_CONTENT}", publisher, null, + new ContentBuilder().transformText( "${DEFAULT_CONTENT}", publisher, new EmailType(), mock( AbstractBuild.class ) ) ); } @@ -177,10 +178,10 @@ public class ContentBuilderTest throws IOException, InterruptedException { assertEquals( ExtendedEmailPublisher.DESCRIPTOR.getDefaultSubject(), - new ContentBuilder().transformText( "$DEFAULT_SUBJECT", publisher, null, + new ContentBuilder().transformText( "$DEFAULT_SUBJECT", publisher, new EmailType(), mock( AbstractBuild.class ) ) ); assertEquals( ExtendedEmailPublisher.DESCRIPTOR.getDefaultSubject(), - new ContentBuilder().transformText( "${DEFAULT_SUBJECT}", publisher, null, + new ContentBuilder().transformText( "${DEFAULT_SUBJECT}", publisher, new EmailType(), mock( AbstractBuild.class ) ) ); } } -- 1.7.2.3