Skip to content

邮件通知模板

image-20230417005027225

使用方法:将以下内容保存到$JENKINS_HOME/email-templates/detailed_email.jelly,然后直接在default content中调用${JELLY_SCRIPT,template="detailed_email"}即可。

<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define">
<html>
<head>
<title>${project.name}</title>
<style>
body table, td, th, p, h1, h2 {
margin:0;
font:normal normal
100% 'Avenir Next';
background-color: #ffffff;
}
h1, h2 {
border-bottom:none 1px #999999;
padding:5px;
margin-top:10px;
margin-bottom:10px;
color: #000000;
font: normal bold 130%
'Avenir Next';
background-color:#f0f0f0;
}
tr.gray {
background-color:#f0f0f0;
}
h2 {
padding:5px;
margin-top:5px;
margin-bottom:5px;
font: italic bold 110% 'Avenir Next';
}
.bg2 {
color:black;
background-color:#E0E0E0;
font-size:110%
}
th {
font-weight: bold;
}
tr, td, th {
padding:2px;
}
td.test_passed {
color:green;
}
td.test_failed {
color:red;
}
td.center {
text-align: center;
}
td.test_skipped {
color:grey;
}
.console {
font: normal normal 90% Courier New,
monotype;
padding:0px;
margin:0px;
}
div.content, div.header {
background: #ffffff;
border: none
1px #666;
margin: 2px;
content:
2px;
padding: 2px;
}
table.border, th.border, td.border {
border:
1px solid grey;
border-collapse:collapse;
}
</style>
</head>
<body>
<div class="header">
<j:set var="spc" value="&amp;nbsp;&amp;nbsp;" />
<!-- GENERAL INFO -->
<table>
<tr class="gray">
<td align="right">
<j:choose>
<j:when test="${build.result=='SUCCESS'}">
<img src="${rooturl}static/e59dfe28/images/32x32/green.gif" />
</j:when>
<j:when test="${build.result=='FAILURE'}">
<img src="${rooturl}static/e59dfe28/images/32x32/red.gif" />
</j:when>
<j:when test="${build.result=='UNSTABLE'}">
<img src="${rooturl}static/e59dfe28/images/32x32/yellow.gif" />
</j:when>
<j:otherwise>
<img src="${rooturl}static/e59dfe28/images/32x32/green.gif" />
</j:otherwise>
</j:choose>
</td>
<td align="left">
<j:choose>
<j:when test="${empty(build.result)}">
<b style="font-size: 170%;">BUILD SUCCESS</b>
</j:when>
<j:otherwise>
<b style="font-size: 170%;">BUILD ${build.result}</b>
</j:otherwise>
</j:choose>
</td>
</tr>
<tr>
<td><b>Build URL</b></td>
<td>
<a href="${rooturl}${build.url}">${rooturl}${build.url}</a>
</td>
</tr>
<tr>
<td><b>Project:</b></td>
<td>${project.name}</td>
</tr>
<tr>
<td><b>Date of build:</b></td>
<td>${it.timestampString}</td>
</tr>
<tr>
<td><b>Build duration:</b></td>
<td>${build.durationString}</td>
</tr>
<tr>
<td><b>Build cause:</b></td>
<td>
<j:forEach var="cause" items="${build.causes}">${cause.shortDescription}
</j:forEach>
</td>
</tr>
</table>
</div>
<!-- CHANGE SET -->
<div class="content">
<j:forEach var="changeSet" items="${build.changeSets}" varStatus="loop" begin="0" end="1"/>
<!--j:set var="changeSet" value="${build.changeSet}[0]" / -->
<j:if test="${changeSet!=null}">
<j:set var="hadChanges" value="false" />
<TABLE width="100%">
<TR><TD class="bg1" colspan="2"><a href="${rooturl}${build.url}/changes">
<h1>Changes</h1>
</a></TD></TR>
<j:forEach var="cs" items="${changeSet}" varStatus="loop">
<j:set var="hadChanges" value="true" />
<j:set var="aUser" value="${cs.hudsonUser}"/>
<TR>
<TD colspan="2" class="bg2">${spc}Revision <B>${cs.commitId?:cs.revision?:cs.changeNumber}</B> by
<B>${aUser!=null?aUser.displayName:cs.author.displayName}: </B>
<B>(${cs.msgAnnotated})</B>
</TD>
</TR>
<j:forEach var="p" items="${cs.affectedFiles}">
<TR>
<TD width="10%">${spc}${p.editType.name}</TD>
<TD>${p.path}</TD>
</TR>
</j:forEach>
</j:forEach>
<j:if test="${!hadChanges}">
<TR><TD colspan="2">No Changes</TD></TR>
</j:if>
</TABLE>
<BR/>
</j:if>
</div>
<!-- HEALTH TEMPLATE -->
<div class="content">
<j:set var="healthIconSize" value="16x16" />
<j:set var="healthReports" value="${project.buildHealthReports}" />
<j:if test="${healthReports!=null}">
<h1>Health Report</h1>
<table>
<tr>
<th>W</th>
<th>Description</th>
<th>Score</th>
</tr>
<j:forEach var="healthReport" items="${healthReports}">
<tr>
<td>
<img
src="${rooturl}${healthReport.getIconUrl(healthIconSize)}" />
</td>
<td>${healthReport.description}</td>
<td>${healthReport.score}</td>
</tr>
</j:forEach>
</table>
<br />
</j:if>
</div>
<!-- JUnit TEMPLATE -->
<j:set var="junitResultList" value="${it.JUnitTestResult}" />
<j:if test="${junitResultList.isEmpty()!=true}">
<j:forEach var="testResult" items="${junitResultList}">
<j:set var="failCount" value="${failCount + testResult.getFailCount()}"/>
<j:set var="skipCount" value="${skipCount + testResult.getSkipCount()}"/>
<j:set var="passCount" value="${passCount + testResult.getPassCount()}"/>
</j:forEach>
<div class="content">
<a href="${rooturl}${build.url}/testReport">
<h1>Tests Reports:
<span style="color: green">Passed: ${passCount}</span>,
<span style="color: #ff0000">Failed: ${failCount}</span>,
<span style="color: grey">Ignored: ${skipCount}</span>
</h1>
</a>
<!--
<h2> Test Trend </h2>
<img alt="[Test result trend chart]" src="${rooturl}${project.url}/test/trend?failureOnly=false" usemap="#map0" />
<h2> JUnit Tests </h2>
-->
<table class="border">
<tr>
<th class="border">Package</th>
<th class="border">Failed</th>
<th class="border">Passed</th>
<th class="border">Skipped</th>
<th class="border">Total</th>
</tr>
<j:forEach var="junitResult" items="${it.JUnitTestResult}">
<j:forEach var="packageResult" items="${junitResult.getChildren()}">
<tr>
<td class="border">
<tt>${packageResult.getName()}</tt>
</td>
<td class="border test_failed">${packageResult.getFailCount()}</td>
<td class="border test_passed">${packageResult.getPassCount()}</td>
<td class="border test_skipped">${packageResult.getSkipCount()}</td>
<td class="border">
<b>${packageResult.getPassCount()+packageResult.getFailCount()+packageResult.getSkipCount()}
</b>
</td>
</tr>
<j:forEach var="failed_test"
items="${packageResult.getFailedTests()}">
<tr>
<td class="test_failed" colspan="5">
<tt>${failed_test.getFullName()}</tt>
</td>
</tr>
</j:forEach>
</j:forEach>
</j:forEach>
</table>
<br />
</div>
</j:if>
<!-- COBERTURA TEMPLATE -->
<j:set var="coberturaAction" value="${it.coberturaAction}" />
<j:if test="${coberturaAction!=null}">
<div class="content">
<j:set var="coberturaResult" value="${coberturaAction.result}" />
<j:if test="${coberturaResult!=null}">
<a href="${rooturl}${build.url}/cobertura">
<h1>Cobertura Report</h1>
</a>
<h2>Project Coverage Summary</h2>
<h3>Trend</h3>
<img src="${rooturl}${build.url}cobertura/graph" />
<h3>Project Coverage summary</h3>
<table class="border">
<tr>
<th class="border">Name</th>
<j:forEach var="metric" items="${coberturaResult.metrics}">
<th class="border">${metric.name}</th>
</j:forEach>
</tr>
<tr>
<td class="border">${coberturaResult.name}</td>
<j:forEach var="metric" items="${coberturaResult.metrics}">
<td class="border"
data="${coberturaResult.getCoverage(metric).percentageFloat}">${coberturaResult.getCoverage(metric).percentage}%
(${coberturaResult.getCoverage(metric)})
</td>
</j:forEach>
</tr>
</table>
<j:if test="${coberturaResult.sourceCodeLevel}">
<h2>Source</h2>
<j:choose>
<j:when test="${coberturaResult.sourceFileAvailable}">
<div style="overflow-x:scroll;">
<table class="source">
<thead>
<tr>
<th colspan="3">${coberturaResult.relativeSourcePath}
</th>
</tr>
</thead>
${coberturaResult.sourceFileContent}
</table>
</div>
</j:when>
<j:otherwise>
<p>
<i>Source code is unavailable</i>
</p>
</j:otherwise>
</j:choose>
</j:if>
<j:forEach var="element" items="${coberturaResult.childElements}">
<j:set var="childMetrics"
value="${coberturaResult.getChildMetrics(element)}" />
<h2>Coverage Breakdown by ${element.displayName}</h2>
<table class="border">
<tr>
<th class="border">Name</th>
<j:forEach var="metric" items="${childMetrics}">
<th class="border">${metric.name}</th>
</j:forEach>
</tr>
<j:forEach var="c" items="${coberturaResult.children}">
<j:set var="child" value="${coberturaResult.getChild(c)}" />
<tr>
<td class="border">
${child.xmlTransform(child.name)}
</td>
<j:forEach var="metric" items="${childMetrics}">
<j:set var="childResult" value="${child.getCoverage(metric)}" />
<j:choose>
<j:when test="${childResult!=null}">
<td class="border" data="${childResult.percentageFloat}">${childResult.percentage}%
(${childResult})
</td>
</j:when>
<j:otherwise>
<td class="border" data="101">N/A</td>
</j:otherwise>
</j:choose>
</j:forEach>
</tr>
</j:forEach>
</table>
</j:forEach>
</j:if>
<br />
</div>
</j:if>
<!-- Static Analysis -->
<j:set var="actions" value="${it.staticAnalysisActions}" />
<j:if test="${!actions.isEmpty()}">
<div class="content">
<h1>Static Analysis Results</h1>
<table>
<tr>
<th></th>
<th>Name</th>
<th>Result</th>
<th>Total</th>
<th>High</th>
<th>Normal</th>
<th>Low</th>
</tr>
<j:forEach var="action" items="${actions}">
<tr>
<td>
<img src="${rooturl}${action.smallImageName}" />
</td>
<td>
<a href="${rooturl}${build.url}/${action.urlName}">${action.displayName}</a>
</td>
<td class="center">
<j:choose>
<j:when test="${action.result.pluginResult=='SUCCESS'}">
<img src="${rooturl}static/e59dfe28/images/16x16/blue.gif" />
</j:when>
<j:when test="${action.result.pluginResult=='FAILURE'}">
<img src="${rooturl}static/e59dfe28/images/16x16/red.gif" />
</j:when>
<j:otherwise>
<img src="${rooturl}static/e59dfe28/images/16x16/yellow.gif" />
</j:otherwise>
</j:choose>
</td>
<td class="center">${action.result.numberOfAnnotations} </td>
<td class="center">${action.result.getNumberOfAnnotations('HIGH')} </td>
<td class="center">${action.result.getNumberOfAnnotations('NORMAL')} </td>
<td class="center">${action.result.getNumberOfAnnotations('LOW')} </td>
</tr>
</j:forEach>
</table>
</div>
</j:if>
<!-- clangScan Analysis -->
<j:set var="clangScanAction" value='${it.getAction("jenkins.plugins.clangscanbuild.actions.ClangScanBuildAction")}' />
<j:if test="${clangScanAction!=null}">
<div class="content">
<a href="${rooturl}${build.url}clangScanBuildBugs">
<h1>Clang Scan Static Analysis Results</h1>
</a>
<h2> Trend </h2>
<img lazymap="${rooturl}${project.url}clangScanBuildTrend/graph" alt="[Clang Scan Static Analysis Results trend chart]" src="${rooturl}${project.url}clangScanBuildTrend/graph" usemap="#map0" />
<h2> Result </h2>
<j:set var="summary" value="${clangScanAction.loadBugSummary()}" />
<p> There are ${summary.getBugCount()} clang Scan bugs </p>
<j:if test="${summary.getBugCount() !=0 }">
<table border="1px" class="pane">
<tr>
<th> </th>
<th>File</th>
<th>Bug Type</th>
<th>Category</th>
<th>Description</th>
<th>Details </th>
</tr>
<j:forEach var="clangBug" items="${summary.getBugs()}">
<j:choose>
<j:when test="${clangBug.isNewBug()}">
<j:set var="isNew" value="new" />
</j:when>
<j:otherwise>
<j:set var="isNew" value="" />
</j:otherwise>
</j:choose>
<tr>
<td>${isNew}</td>
<td>${clangBug.getSourceFile()}</td>
<td>${clangBug.getBugType()}</td>
<td>${clangBug.getBugCategory()}</td>
<td>${clangBug.getBugDescription()}</td>
<td>
<a href="${rooturl}${build.url}/clangScanBuildBugs/browse/${clangBug.getReportFile()}#EndPath"> detail </a>
</td>
</tr>
</j:forEach>
</table>
</j:if>
</div>
</j:if>
<!-- ARTIFACTS -->
<j:set var="artifacts" value="${build.artifacts}" />
<j:if test="${artifacts!=null and artifacts.size()&gt;0}">
<div class="content">
<h1>Build Artifacts</h1>
<ul>
<j:forEach var="f" items="${artifacts}">
<li>
<a href="${rooturl}${build.url}artifact/${f}">${f}</a>
</li>
</j:forEach>
</ul>
</div>
</j:if>
<!-- MAVEN ARTIFACTS -->
<j:set var="mbuilds" value="${build.moduleBuilds}" />
<j:if test="${mbuilds!=null}">
<div class="content">
<h1>Build Artifacts</h1>
<j:forEach var="m" items="${mbuilds}">
<h2>${m.key.displayName}</h2>
<j:forEach var="mvnbld" items="${m.value}">
<j:set var="artifacts" value="${mvnbld.artifacts}" />
<j:if test="${artifacts!=null and artifacts.size()&gt;0}">
<ul>
<j:forEach var="f" items="${artifacts}">
<li>
<a href="${rooturl}${mvnbld.url}artifact/${f}">${f}</a>
</li>
</j:forEach>
</ul>
</j:if>
</j:forEach>
</j:forEach>
<br />
</div>
</j:if>
</body>
</html>
</j:jelly>

image-20230417230207237

<STYLE>
BODY, TABLE, TD, TH, P {
font-family:Verdana,Helvetica,sans serif;
font-size:11px;
color:black;
}
h1 { color:black; }
h2 { color:black; }
h3 { color:black; }
TD.bg1 { color:white; background-color:#0000C0; font-size:120% }
TD.bg2 { color:white; background-color:#4040FF; font-size:110% }
TD.bg3 { color:white; background-color:#8080FF; }
TD.test_passed { color:blue; }
TD.test_failed { color:red; }
TD.console { font-family:Courier New; }
</STYLE>
<BODY>
<TABLE>
<TR><TD align="right"><IMG SRC="${rooturl}static/e59dfe28/images/32x32/<%= (build.result == null || build.result.toString() == 'SUCCESS') ? "blue.gif" : build.result.toString() == 'FAILURE' ? 'red.gif' : 'yellow.gif' %>" />
</TD><TD valign="center"><B style="font-size: 200%;">BUILD ${build.result ?: 'SUCCESSFUL'}</B></TD></TR>
<TR><TD>URL</TD><TD><A href="${rooturl}${build.url}">${rooturl}${build.url}</A></TD></TR>
<TR><TD>Project:</TD><TD>${project.name}</TD></TR>
<TR><TD>Date:</TD><TD>${it.timestampString}</TD></TR>
<TR><TD>Duration:</TD><TD>${build.durationString}</TD></TR>
<TR><TD>Cause:</TD><TD><% build.causes.each() { cause -> %> ${cause.shortDescription} <% } %></TD></TR>
</TABLE>
<BR/>
<!-- CHANGE SET -->
<% def changeSets = build.changeSets
if(changeSets != null) {
def hadChanges = false %>
<TABLE width="100%">
<TR><TD class="bg1" colspan="2"><B>CHANGES</B></TD></TR>
<% changeSets.each() { cs_list ->
cs_list.each() { cs ->
hadChanges = true %>
<TR>
<TD colspan="2" class="bg2">&nbsp;&nbsp;Revision <B><%= cs.metaClass.hasProperty('commitId') ? cs.commitId : cs.metaClass.hasProperty('revision') ? cs.revision :
cs.metaClass.hasProperty('changeNumber') ? cs.changeNumber : "" %></B> by
<B><%= cs.author %>: </B>
<B>(${cs.msgAnnotated})</B>
</TD>
</TR>
<% cs.affectedFiles.each() { p -> %>
<TR>
<TD width="10%">&nbsp;&nbsp;${p.editType.name}</TD>
<TD>${p.path}</TD>
</TR>
<% }
}
}
if(!hadChanges) { %>
<TR><TD colspan="2">No Changes</TD></TR>
<% } %>
</TABLE>
<BR/>
<% } %>
<!-- ARTIFACTS -->
<% def artifacts = build.artifacts
if(artifacts != null && artifacts.size() > 0) { %>
<TABLE width="100%">
<TR><TD class="bg1"><B>BUILD ARTIFACTS</B></TD></TR>
<TR>
<TD>
<% artifacts.each() { f -> %>
<li>
<a href="${rooturl}${build.url}artifact/${f}">${f}</a>
</li>
<% } %>
</TD>
</TR>
</TABLE>
<BR/>
<% } %>
<!-- MAVEN ARTIFACTS -->
<%
try {
def mbuilds = build.moduleBuilds
if(mbuilds != null) { %>
<TABLE width="100%">
<TR><TD class="bg1"><B>BUILD ARTIFACTS</B></TD></TR>
<%
try {
mbuilds.each() { m -> %>
<TR><TD class="bg2"><B>${m.key.displayName}</B></TD></TR>
<% m.value.each() { mvnbld ->
def artifactz = mvnbld.artifacts
if(artifactz != null && artifactz.size() > 0) { %>
<TR>
<TD>
<% artifactz.each() { f -> %>
<li>
<a href="${rooturl}${mvnbld.url}artifact/${f}">${f}</a>
</li>
<% } %>
</TD>
</TR>
<% }
}
}
} catch(e) {
// we don't do anything
} %>
</TABLE>
<BR/>
<% }
}catch(e) {
// we don't do anything
}
%>
<!-- JUnit TEMPLATE -->
<% def junitResultList = it.JUnitTestResult
try {
def cucumberTestResultAction = it.getAction("org.jenkinsci.plugins.cucumber.jsontestsupport.CucumberTestResultAction")
junitResultList.add(cucumberTestResultAction.getResult())
} catch(e) {
//cucumberTestResultAction not exist in this build
}
if (junitResultList.size() > 0) { %>
<TABLE width="100%">
<TR><TD class="bg1" colspan="2"><B>${junitResultList.first().displayName}</B></TD></TR>
<% junitResultList.each{
junitResult -> %>
<% junitResult.getChildren().each { packageResult -> %>
<TR><TD class="bg2" colspan="2"> Name: ${packageResult.getName()} Failed: ${packageResult.getFailCount()} test(s), Passed: ${packageResult.getPassCount()} test(s), Skipped: ${packageResult.getSkipCount()} test(s), Total: ${packageResult.getPassCount()+packageResult.getFailCount()+packageResult.getSkipCount()} test(s)</TD></TR>
<% packageResult.getFailedTests().each{ failed_test -> %>
<TR bgcolor="white"><TD class="test_failed" colspan="2"><B><li>Failed: ${failed_test.getFullName()} </li></B></TD></TR>
<% }
}
} %>
</TABLE>
<BR/>
<%
} %>
<!-- CONSOLE OUTPUT -->
<% if(build.result==hudson.model.Result.FAILURE) { %>
<TABLE width="100%" cellpadding="0" cellspacing="0">
<TR><TD class="bg1"><B>CONSOLE OUTPUT</B></TD></TR>
<% build.getLog(100).each() { line -> %>
<TR><TD class="console">${org.apache.commons.lang.StringEscapeUtils.escapeHtml(line)}</TD></TR>
<% } %>
</TABLE>
<BR/>
<% } %>
</BODY>

image-20230417005050936

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
</head>
<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4"
offset="0">
<table width="95%" cellpadding="0" cellspacing="0" style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">
<tr>
<td>各位同事,大家好,以下为${PROJECT_NAME }项目构建信息</td>
</tr>
<tr>
<td><br />
<b><font color="#0B610B">构建信息</font></b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td>
<ul>
<li>项目名称 : ${PROJECT_NAME}</li>
<li>构建编号 : 第${BUILD_NUMBER}次构建</li>
<li>触发原因: ${CAUSE}</li>
<li>构建状态: ${BUILD_STATUS}</li>
<li>构建日志: <a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
<li>构建 Url : <a href="${BUILD_URL}">${BUILD_URL}</a></li>
<li>工作目录 : <a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></li>
<li>项目 Url : <a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
</ul>
</td>
</tr>
<tr>
<td><b><font color="#0B610B">历史变更记录:</font></b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td>
${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br />%c<br />",showPaths=true,changesFormat="<pre>[%a]<br />%m</pre>",pathFormat="&nbsp;&nbsp;&nbsp;&nbsp;%p"}
</td>
</tr>
</table>
</body>
</html>

部分内容来源于网络。