Skip to content

Instantly share code, notes, and snippets.

@johninweb
Forked from rahulsom/jenkins.jelly
Created March 24, 2016 17:27
Show Gist options
  • Select an option

  • Save johninweb/1b9545a10f939cb5e8fb to your computer and use it in GitHub Desktop.

Select an option

Save johninweb/1b9545a10f939cb5e8fb to your computer and use it in GitHub Desktop.

Revisions

  1. @rahulsom rahulsom revised this gist Mar 21, 2013. 1 changed file with 0 additions and 107 deletions.
    107 changes: 0 additions & 107 deletions InlineStyles.groovy
    Original file line number Diff line number Diff line change
    @@ -1,107 +0,0 @@
    groovy.grape.Grape.grab([group:'org.jsoup', module:'jsoup', version:'1.7.2'])

    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;

    import groovy.xml.QName
    import groovy.xml.XmlUtil

    import javax.mail.internet.MimeMessage

    class Inliner {
    boolean debug = true

    private static String concatenateProperties(String oldProp, String newProp) {
    if (!oldProp.endsWith(";"))
    oldProp += ";";
    return oldProp.trim() + newProp;
    }

    String fetchStyles(groovy.util.Node xhtml) {
    xhtml.depthFirst().findAll {
    it instanceof groovy.util.Node && it.name().namespaceURI == 'http://www.w3.org/1999/xhtml'
    }.findAll { groovy.util.Node it ->
    def tag = (it.name() as QName).localPart
    (tag == 'link' && it.@rel == 'stylesheet' && it.attribute('data-inline')) ||
    (tag == 'style' && it.attribute('data-inline'))
    }.collect { groovy.util.Node it ->
    def tag = (it.name() as QName).localPart
    if (tag == 'link') {
    new File(it.@href).text
    } else if (tag == 'style') {
    it.text()
    } else {
    ''
    }
    }.join('\n')
    }

    private void removeStylesheets(groovy.util.Node xhtml) {
    xhtml.depthFirst().findAll {
    it instanceof groovy.util.Node && it.name().namespaceURI == 'http://www.w3.org/1999/xhtml'
    }?.findAll { groovy.util.Node it ->
    def tag = (it.name() as QName).localPart
    (tag == 'link' && it.@rel == 'stylesheet' && it.attribute('data-inline')) ||
    (tag == 'style' && it.attribute('data-inline'))
    }?.each { groovy.util.Node it ->
    it.replaceNode { '' }
    }
    }

    def process(String input) {
    def s = new XmlParser()
    s.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
    def xhtml = s.parseText(input)
    println "Parsed xhtml"

    def stylesheet = fetchStyles(xhtml)
    println "Parsed stylesheet"

    removeStylesheets(xhtml)
    println "Removed css form xhtml"

    Document doc = Jsoup.parse(XmlUtil.serialize(xhtml));

    def trimmedStylesheet = stylesheet.replaceAll('\n', '').replaceAll('/\\*.*?\\*/', '').replaceAll(/ +/, ' ')
    println trimmedStylesheet
    String styleRules = trimmedStylesheet.trim(), delims = "{}";
    StringTokenizer st = new StringTokenizer(styleRules, delims);
    while (st.countTokens() > 1) {
    String selector = st.nextToken(), properties = st.nextToken();
    println "Applying ${selector}"
    Elements selectedElements = doc.select(selector);
    for (Element selElem : selectedElements) {
    String oldProperties = selElem.attr('cssstyle');
    selElem.attr('cssstyle',
    oldProperties.length() > 0 ? concatenateProperties(
    oldProperties, properties) : properties);

    String oldRules = selElem.attr('rules');
    selElem.attr('rules',
    oldRules.length() > 0 ? concatenateProperties(
    oldRules, selector) : selector);
    }
    }

    def output = doc.toString().replaceAll(/"/, "'")

    xhtml = s.parseText(output )
    xhtml.depthFirst().findAll{ it instanceof groovy.util.Node}.each { groovy.util.Node it ->
    if (it.attributes().containsKey('cssstyle')) {
    if (it.attributes().containsKey('style')) {
    it.@style = it.@cssstyle + it.@style
    } else {
    it.@style = it.@cssstyle
    }
    it.attributes().remove('cssstyle')
    }
    }
    XmlUtil.serialize(xhtml)
    }
    }

    def i = new Inliner()
    msg.content.getBodyPart(0).content = i.process(msg.content.getBodyPart(0).content)

  2. @rahulsom rahulsom revised this gist Mar 19, 2013. 2 changed files with 208 additions and 105 deletions.
    107 changes: 107 additions & 0 deletions InlineStyles.groovy
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,107 @@
    groovy.grape.Grape.grab([group:'org.jsoup', module:'jsoup', version:'1.7.2'])

    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;

    import groovy.xml.QName
    import groovy.xml.XmlUtil

    import javax.mail.internet.MimeMessage

    class Inliner {
    boolean debug = true

    private static String concatenateProperties(String oldProp, String newProp) {
    if (!oldProp.endsWith(";"))
    oldProp += ";";
    return oldProp.trim() + newProp;
    }

    String fetchStyles(groovy.util.Node xhtml) {
    xhtml.depthFirst().findAll {
    it instanceof groovy.util.Node && it.name().namespaceURI == 'http://www.w3.org/1999/xhtml'
    }.findAll { groovy.util.Node it ->
    def tag = (it.name() as QName).localPart
    (tag == 'link' && it.@rel == 'stylesheet' && it.attribute('data-inline')) ||
    (tag == 'style' && it.attribute('data-inline'))
    }.collect { groovy.util.Node it ->
    def tag = (it.name() as QName).localPart
    if (tag == 'link') {
    new File(it.@href).text
    } else if (tag == 'style') {
    it.text()
    } else {
    ''
    }
    }.join('\n')
    }

    private void removeStylesheets(groovy.util.Node xhtml) {
    xhtml.depthFirst().findAll {
    it instanceof groovy.util.Node && it.name().namespaceURI == 'http://www.w3.org/1999/xhtml'
    }?.findAll { groovy.util.Node it ->
    def tag = (it.name() as QName).localPart
    (tag == 'link' && it.@rel == 'stylesheet' && it.attribute('data-inline')) ||
    (tag == 'style' && it.attribute('data-inline'))
    }?.each { groovy.util.Node it ->
    it.replaceNode { '' }
    }
    }

    def process(String input) {
    def s = new XmlParser()
    s.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
    def xhtml = s.parseText(input)
    println "Parsed xhtml"

    def stylesheet = fetchStyles(xhtml)
    println "Parsed stylesheet"

    removeStylesheets(xhtml)
    println "Removed css form xhtml"

    Document doc = Jsoup.parse(XmlUtil.serialize(xhtml));

    def trimmedStylesheet = stylesheet.replaceAll('\n', '').replaceAll('/\\*.*?\\*/', '').replaceAll(/ +/, ' ')
    println trimmedStylesheet
    String styleRules = trimmedStylesheet.trim(), delims = "{}";
    StringTokenizer st = new StringTokenizer(styleRules, delims);
    while (st.countTokens() > 1) {
    String selector = st.nextToken(), properties = st.nextToken();
    println "Applying ${selector}"
    Elements selectedElements = doc.select(selector);
    for (Element selElem : selectedElements) {
    String oldProperties = selElem.attr('cssstyle');
    selElem.attr('cssstyle',
    oldProperties.length() > 0 ? concatenateProperties(
    oldProperties, properties) : properties);

    String oldRules = selElem.attr('rules');
    selElem.attr('rules',
    oldRules.length() > 0 ? concatenateProperties(
    oldRules, selector) : selector);
    }
    }

    def output = doc.toString().replaceAll(/"/, "'")

    xhtml = s.parseText(output )
    xhtml.depthFirst().findAll{ it instanceof groovy.util.Node}.each { groovy.util.Node it ->
    if (it.attributes().containsKey('cssstyle')) {
    if (it.attributes().containsKey('style')) {
    it.@style = it.@cssstyle + it.@style
    } else {
    it.@style = it.@cssstyle
    }
    it.attributes().remove('cssstyle')
    }
    }
    XmlUtil.serialize(xhtml)
    }
    }

    def i = new Inliner()
    msg.content.getBodyPart(0).content = i.process(msg.content.getBodyPart(0).content)

    206 changes: 101 additions & 105 deletions jenkins.jelly
    Original file line number Diff line number Diff line change
    @@ -7,15 +7,12 @@
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>ZURBemails</title>

    <style>
    /* -------------------------------------
    GLOBAL
    ------------------------------------- */
    <style data-inline="true">
    * {
    margin:0;
    padding:0;
    margin:0;
    padding:0;
    font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
    }
    * { font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; }

    img {
    max-width: 100%;
    @@ -30,11 +27,6 @@
    width: 100%!important;
    height: 100%;
    }


    /* -------------------------------------
    ELEMENTS
    ------------------------------------- */
    a { color: #2BA6CB;}

    .btn {
    @@ -58,24 +50,6 @@
    background-color: #d9edf7;
    }

    .callout.SUCCESS {
    background-color: #dff0d8;
    border: #468847;
    color: #468847;
    }

    .callout.FAILURE {
    background-color: #f2dede;
    border: #b94a48;
    color: #b94a48;
    }

    .callout.UNSTABLE {
    background-color: #fcf8e3;
    border: #c09853;
    color: #c09853;
    }

    .callout a {
    font-weight:bold;
    color: #2BA6CB;
    @@ -105,25 +79,15 @@
    width:100%;
    }

    /* -------------------------------------
    HEADER
    ------------------------------------- */
    table.head-wrap { width: 100%;}

    .header.container table td.logo { padding: 15px; }
    .header.container table td.label { padding: 15px; padding-left:0px;}


    /* -------------------------------------
    BODY
    ------------------------------------- */
    table.body-wrap { width: 100%;}


    /* -------------------------------------
    FOOTER
    ------------------------------------- */
    table.footer-wrap { width: 100%; clear:both!important;
    table.footer-wrap {
    width: 100%; clear:both!important;
    }
    .footer-wrap .container td.content p { border-top: 1px solid rgb(215,215,215); padding-top:15px;}
    .footer-wrap .container td.content p {
    @@ -132,10 +96,6 @@

    }


    /* -------------------------------------
    TYPOGRAPHY
    ------------------------------------- */
    h1,h2,h3,h4,h5,h6 {
    font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
    line-height: 1.1; margin-bottom:15px; color:#000;
    @@ -167,9 +127,6 @@
    list-style-position: inside;
    }

    /* -------------------------------------
    SIDEBAR
    ------------------------------------- */
    ul.sidebar {
    background:#ebebeb;
    display:block;
    @@ -212,15 +169,13 @@
    .content {
    padding:15px;
    max-width:600px;
    width:600px;
    margin:0 auto;
    display:block;
    }

    /* Let's make sure tables in the content area are 100% wide */
    .content table { width: 100%; }


    /* Odds and ends */
    .column {
    width: 300px;
    @@ -242,27 +197,6 @@
    /* Be sure to place a .clear element after each set of columns, just to be safe */
    .clear { display: block; clear: both; }


    /* -------------------------------------------
    PHONE
    For clients that support media queries.
    Nothing fancy.
    -------------------------------------------- */
    @media only screen and (max-width: 600px) {

    a[class="btn"] {
    display:block!important; margin-bottom:10px!important; background-image:none!important;
    margin-right:0!important;
    }

    div[class="column"] { width: auto!important; float:none!important;}

    table.social div[class="column"] {
    width:auto!important;
    }

    }

    tr.test_failed {
    background-color: #f2dede;
    color: #b94a48;
    @@ -279,9 +213,44 @@
    font-size: 12px;
    }

    .content>table{
    table-layout:fixed;
    }
    .content>table {
    table-layout:fixed;
    }

    </style>
    <style>
    .callout.SUCCESS {
    background-color: #dff0d8;
    border: #468847;
    color: #468847;
    }

    .callout.FAILURE {
    background-color: #f2dede;
    border: #b94a48;
    color: #b94a48;
    }

    .callout.UNSTABLE {
    background-color: #fcf8e3;
    border: #c09853;
    color: #c09853;
    }

    @media only screen and (max-width: 600px) {

    a[class="btn"] {
    display:block!important; margin-bottom:10px!important; background-image:none!important;
    margin-right:0!important;
    }

    div[class="column"] { width: auto!important; float:none!important;}

    table.social div[class="column"] {
    width:auto!important;
    }

    }

    </style>

    @@ -350,14 +319,23 @@
    <td>Build description</td><td>${build.description}</td>
    </tr>
    <tr>
    <td>Built server</td>
    <td>Build server</td>
    <td>
    <j:choose>
    <j:when test="${build.builtOnStr!=''}">${build.builtOnStr}</j:when>
    <j:otherwise>build-vm</j:otherwise>
    </j:choose>
    </td>
    </tr>
    <tr>
    <td>Build branch</td>
    <td>
    ${ENV}
    <j:if test="${build.changeSet.branch!=null}">
    <j:whitespace trim="false"> in ${build.changeSet.branch}</j:whitespace>
    </j:if>
    </td>
    </tr>
    </table>
    </div><!-- /Callout Panel -->

    @@ -369,7 +347,7 @@
    <h3>Health Report</h3>
    <table class="bordered" cellpadding="0" cellspacing="0" border="0" align="center">
    <tr>
    <th>W</th>
    <th width="10%">W</th>
    <th>Description</th>
    <th>Score</th>
    </tr>
    @@ -399,15 +377,17 @@
    </a>
    <j:forEach var="cs" items="${changeSet.logs}" varStatus="loop">
    <j:set var="hadChanges" value="true" />
    <h4>${cs.msgAnnotated}</h4>
    <p>by <em>${cs.author}</em></p>
    <h4>${cs.msgAnnotated}
    <small>by <em>${cs.author}</em></small>
    </h4>
    <table cellpadding="0" cellspacing="0" border="0" align="center">
    <j:forEach var="p" items="${cs.affectedFiles}">
    <tr>
    <td width="10%">${spc}${p.editType.name}</td>
    <td>
    <tt>${p.path}</tt>
    <td width="10%">
    <j:set var="type" value="${spc}${p.editType.name}"/>
    ${type}
    </td>
    <td>${p.path}</td>
    </tr>
    </j:forEach>
    </table>
    @@ -419,11 +399,11 @@
    </j:if>
    </div>

    <div class="callout">

    <!-- JUnit TEMPLATE -->
    <j:set var="junitResultList" value="${it.JUnitTestResult}" />
    <j:if test="${junitResultList.isEmpty()!=true}">
    <div class="callout">
    <a href="${rooturl}${build.url}/testReport">
    <h3>JUnit Tests</h3>
    </a>
    @@ -436,37 +416,53 @@
    <th class="border">Total</th>
    </tr>
    <j:forEach var="junitResult" items="${it.JUnitTestResult}">
    <j:forEach var="packageResult" items="${junitResult.getChildren()}">
    <j:set var="testTrClass" value=""/>
    <j:if test="${packageResult.getFailCount() > 0}">
    <j:set var="testTrClass" value="test_failed"/>
    </j:if>

    <j:forEach var="pr" items="${junitResult.getChildren()}">
    <j:set var="testTrClass" value=""/>
    <j:if test="${pr.getFailCount() > 0}">
    <j:set var="testTrClass" value="test_failed"/>
    </j:if>

    <j:set var="failed" value="${pr.getFailCount()}"/>
    <j:set var="passed" value="${pr.getPassCount()}"/>
    <j:set var="skipped" value="${pr.getSkipCount()}"/>
    <j:set var="total" value="${pr.getPassCount()+pr.getFailCount()+pr.getSkipCount()}"/>
    <tr class="testPackage ${testTrClass}">
    <td class="border">
    <tt>${packageResult.getName()}</tt>
    </td>
    <td class="border test_failed" align="right">${packageResult.getFailCount()}</td>
    <td class="border test_passed" align="right">${packageResult.getPassCount()}</td>
    <td class="border test_skipped" align="right">${packageResult.getSkipCount()}</td>
    <td class="border" align="right">
    <b>${packageResult.getPassCount()+packageResult.getFailCount()+packageResult.getSkipCount()}</b>
    </td>
    </tr>
    <j:forEach var="failed_test" items="${packageResult.getFailedTests()}">
    <td class="border">
    <tt>${pr.getName()}</tt>
    </td>
    <td class="border test_failed" align="right">${failed}</td>
    <td class="border test_passed" align="right">${passed}</td>
    <td class="border test_skipped" align="right">${skipped}</td>
    <td class="border" align="right">
    <b>${total}</b>
    </td>
    </tr>
    <j:forEach var="failed_test" items="${pr.getFailedTests()}">
    <tr>
    <td class="test_failed name" colspan="5">
    <tt>${failed_test.getSimpleName()}.${failed_test.getName()}</tt>
    <tt>
    <j:set var="testname" value="${failed_test.getSimpleName()}.${failed_test.getName()}"/>
    <j:if test="${failed_test.age > 1}">
    ${testname}
    <em>(age: ${failed_test.age})</em>
    </j:if>
    <j:if test="${failed_test.age == 1}">
    <strong>
    ${testname}
    <em>(age: ${failed_test.age})</em>
    </strong>
    </j:if>
    </tt>
    </td>
    </tr>
    </j:forEach>
    </j:forEach>
    </j:forEach>
    </table>
    <br />
    </div>
    </j:if>

    </div>

    <!-- COBERTURA TEMPLATE -->
    <j:set var="coberturaAction" value="${it.coberturaAction}" />
    @@ -477,7 +473,7 @@
    <a href="${rooturl}${build.url}/cobertura">
    <h3>Cobertura Report</h3>
    </a>
    <h2>Project Coverage Summary</h2>
    <h4>Project Coverage Summary</h4>
    <table class="border" cellpadding="0" cellspacing="0" border="0" align="center">
    <tr>
    <th class="border">Name</th>
    @@ -496,7 +492,7 @@
    </tr>
    </table>
    <j:if test="${coberturaResult.sourceCodeLevel}">
    <h2>Source</h2>
    <h4>Source</h4>
    <j:choose>
    <j:when test="${coberturaResult.sourceFileAvailable}">
    <div style="overflow-x:scroll;">
    @@ -519,7 +515,7 @@
    </j:if>
    <j:forEach var="element" items="${coberturaResult.childElements}">
    <j:set var="childMetrics" value="${coberturaResult.getChildMetrics(element)}" />
    <h2>Coverage Breakdown by ${element.displayName}</h2>
    <h4>Coverage Breakdown by ${element.displayName}</h4>
    <table class="bordered" cellpadding="0" cellspacing="0" border="0" align="center">
    <tr>
    <th class="border">Name</th>
  3. @rahulsom rahulsom created this gist Mar 9, 2013.
    691 changes: 691 additions & 0 deletions jenkins.jelly
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,691 @@
    <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <!-- If you delete this tag, the sky will fall on your head -->
    <meta name="viewport" content="width=device-width" />

    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>ZURBemails</title>

    <style>
    /* -------------------------------------
    GLOBAL
    ------------------------------------- */
    * {
    margin:0;
    padding:0;
    }
    * { font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; }

    img {
    max-width: 100%;
    }
    .collapse {
    margin:0;
    padding:0;
    }
    body {
    -webkit-font-smoothing:antialiased;
    -webkit-text-size-adjust:none;
    width: 100%!important;
    height: 100%;
    }


    /* -------------------------------------
    ELEMENTS
    ------------------------------------- */
    a { color: #2BA6CB;}

    .btn {
    text-decoration:none;
    color: #FFF;
    background-color: #666;
    padding:10px 16px;
    font-weight:bold;
    margin-right:10px;
    text-align:center;
    cursor:pointer;
    display: inline-block;
    }

    .callout {
    padding:15px;
    margin-bottom: 15px;
    }

    .callout.ALT {
    background-color: #d9edf7;
    }

    .callout.SUCCESS {
    background-color: #dff0d8;
    border: #468847;
    color: #468847;
    }

    .callout.FAILURE {
    background-color: #f2dede;
    border: #b94a48;
    color: #b94a48;
    }

    .callout.UNSTABLE {
    background-color: #fcf8e3;
    border: #c09853;
    color: #c09853;
    }

    .callout a {
    font-weight:bold;
    color: #2BA6CB;
    }

    table.social {
    /* padding:15px; */
    background-color: #ebebeb;

    }
    .social .soc-btn {
    padding: 3px 7px;
    font-size:12px;
    margin-bottom:10px;
    text-decoration:none;
    color: #FFF;font-weight:bold;
    display:block;
    text-align:center;
    }
    a.fb { background-color: #3B5998!important; }
    a.tw { background-color: #1daced!important; }
    a.gp { background-color: #DB4A39!important; }
    a.ms { background-color: #000!important; }

    .sidebar .soc-btn {
    display:block;
    width:100%;
    }

    /* -------------------------------------
    HEADER
    ------------------------------------- */
    table.head-wrap { width: 100%;}

    .header.container table td.logo { padding: 15px; }
    .header.container table td.label { padding: 15px; padding-left:0px;}


    /* -------------------------------------
    BODY
    ------------------------------------- */
    table.body-wrap { width: 100%;}


    /* -------------------------------------
    FOOTER
    ------------------------------------- */
    table.footer-wrap { width: 100%; clear:both!important;
    }
    .footer-wrap .container td.content p { border-top: 1px solid rgb(215,215,215); padding-top:15px;}
    .footer-wrap .container td.content p {
    font-size:10px;
    font-weight: bold;

    }


    /* -------------------------------------
    TYPOGRAPHY
    ------------------------------------- */
    h1,h2,h3,h4,h5,h6 {
    font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
    line-height: 1.1; margin-bottom:15px; color:#000;
    }
    h1 small, h2 small, h3 small, h4 small, h5 small, h6 small {
    font-size: 60%; color: #6f6f6f; line-height: 0; text-transform: none;
    }

    h1 { font-weight:200; font-size: 44px;}
    h2 { font-weight:200; font-size: 37px;}
    h3 { font-weight:500; font-size: 27px;}
    h4 { font-weight:500; font-size: 23px;}
    h5 { font-weight:900; font-size: 17px;}
    h6 { font-weight:900; font-size: 14px; text-transform: uppercase; color:#444;}

    .collapse { margin:0!important;}

    p, ul {
    margin-bottom: 10px;
    font-weight: normal;
    font-size:14px;
    line-height:1.6;
    }
    p.lead { font-size:17px; }
    p.last { margin-bottom:0px;}

    ul li {
    margin-left:5px;
    list-style-position: inside;
    }

    /* -------------------------------------
    SIDEBAR
    ------------------------------------- */
    ul.sidebar {
    background:#ebebeb;
    display:block;
    list-style-type: none;
    }
    ul.sidebar li { display: block; margin:0;}
    ul.sidebar li a {
    text-decoration:none;
    color: #666;
    padding:10px 16px;
    /* font-weight:bold; */
    margin-right:10px;
    /* text-align:center; */
    cursor:pointer;
    border-bottom: 1px solid #777777;
    border-top: 1px solid #FFFFFF;
    display:block;
    margin:0;
    }
    ul.sidebar li a.last { border-bottom-width:0px;}
    ul.sidebar li a h1,ul.sidebar li a h2,ul.sidebar li a h3,ul.sidebar li a h4,ul.sidebar li a h5,ul.sidebar li a h6,ul.sidebar li a p { margin-bottom:0!important;}



    /* ---------------------------------------------------
    RESPONSIVENESS
    Nuke it from orbit. It's the only way to be sure.
    ------------------------------------------------------ */

    /* Set a max-width, and make it display as block so it will automatically stretch to that width, but
    will also shrink down on a phone or something */
    .container {
    display:block!important;
    max-width:600px!important;
    margin:0 auto!important; /* makes it centered */
    clear:both!important;
    }

    /* This should also be a block element, so that it will fill 100% of the .container */
    .content {
    padding:15px;
    max-width:600px;
    width:600px;
    margin:0 auto;
    display:block;
    }

    /* Let's make sure tables in the content area are 100% wide */
    .content table { width: 100%; }


    /* Odds and ends */
    .column {
    width: 300px;
    float:left;
    }
    .column tr td { padding: 15px; }
    .column-wrap {
    padding:0!important;
    margin:0 auto;
    max-width:600px!important;
    }
    .column table { width:100%;}
    .social .column {
    width: 280px;
    min-width: 279px;
    float:left;
    }

    /* Be sure to place a .clear element after each set of columns, just to be safe */
    .clear { display: block; clear: both; }


    /* -------------------------------------------
    PHONE
    For clients that support media queries.
    Nothing fancy.
    -------------------------------------------- */
    @media only screen and (max-width: 600px) {

    a[class="btn"] {
    display:block!important; margin-bottom:10px!important; background-image:none!important;
    margin-right:0!important;
    }

    div[class="column"] { width: auto!important; float:none!important;}

    table.social div[class="column"] {
    width:auto!important;
    }

    }

    tr.test_failed {
    background-color: #f2dede;
    color: #b94a48;
    }

    td.test_failed tt {
    padding-left: 20px;
    color: #b94a48;
    }

    code.console {
    font-family: Menlo, Monaco, "Courier New", monospace;
    word-wrap: break-word;
    font-size: 12px;
    }

    .content>table{
    table-layout:fixed;
    }

    </style>

    </head>

    <body bgcolor="#FFFFFF">

    <!-- HEADER -->
    <table class="head-wrap" bgcolor="#999999">
    <tr>
    <td></td>
    <td class="header container">

    <div class="content">
    <table bgcolor="#999999">
    <tr>
    <td>
    <img src="http://jenkins-ci.org/sites/default/files/jenkins_logo.png" width="200" height="50"/>
    </td>
    <td align="right"><h6 class="collapse">${project.name}</h6></td>
    </tr>
    </table>
    </div>

    </td>
    <td></td>
    </tr>
    </table><!-- /HEADER -->


    <!-- BODY -->
    <table class="body-wrap">
    <tr>
    <td></td>
    <td class="container" bgcolor="#FFFFFF">

    <div class="content">
    <table>
    <tr>
    <td>
    <j:set var="spc" value="&amp;nbsp;&amp;nbsp;" />
    <div class="callout ${build.result}">
    <table class="bordered status ${build.result}" cellpadding="0" cellspacing="0" border="0"
    align="center">
    <tr>
    <td valign="center" colspan="2"><h3>BUILD ${build.result}</h3></td>
    </tr>
    <tr>
    <td>Build URL</td><td><a href="${rooturl}${build.url}">${build.url}</a></td>
    </tr>
    <tr>
    <td>Project</td><td>${project.name}</td></tr>
    <tr>
    <td>Date of build</td><td>${it.timestampString}</td>
    </tr>
    <tr>
    <td>Build duration</td><td>${build.durationString}</td>
    </tr>
    <tr>
    <td>Build cause</td>
    <td>
    <j:forEach var="cause" items="${build.causes}">${cause.shortDescription} </j:forEach>
    </td>
    </tr>
    <tr>
    <td>Build description</td><td>${build.description}</td>
    </tr>
    <tr>
    <td>Built server</td>
    <td>
    <j:choose>
    <j:when test="${build.builtOnStr!=''}">${build.builtOnStr}</j:when>
    <j:otherwise>build-vm</j:otherwise>
    </j:choose>
    </td>
    </tr>
    </table>
    </div><!-- /Callout Panel -->

    <div class="callout">
    <!-- HEALTH TEMPLATE -->
    <j:set var="healthIconSize" value="16x16" />
    <j:set var="healthReports" value="${project.buildHealthReports}" />
    <j:if test="${healthReports!=null}">
    <h3>Health Report</h3>
    <table class="bordered" cellpadding="0" cellspacing="0" border="0" align="center">
    <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 align="right">${healthReport.score}</td>
    </tr>
    </j:forEach>
    </table>
    <br />
    </j:if>
    </div>


    <div class="callout ALT">

    <!-- CHANGE SET -->
    <j:set var="changeSet" value="${build.changeSet}" />
    <j:if test="${changeSet!=null}">
    <j:set var="hadChanges" value="false" />
    <a href="${rooturl}${build.url}/changes">
    <h3>Changes</h3>
    </a>
    <j:forEach var="cs" items="${changeSet.logs}" varStatus="loop">
    <j:set var="hadChanges" value="true" />
    <h4>${cs.msgAnnotated}</h4>
    <p>by <em>${cs.author}</em></p>
    <table cellpadding="0" cellspacing="0" border="0" align="center">
    <j:forEach var="p" items="${cs.affectedFiles}">
    <tr>
    <td width="10%">${spc}${p.editType.name}</td>
    <td>
    <tt>${p.path}</tt>
    </td>
    </tr>
    </j:forEach>
    </table>
    </j:forEach>
    <j:if test="${!hadChanges}">
    <p>No Changes</p>
    </j:if>
    <br />
    </j:if>
    </div>

    <div class="callout">

    <!-- JUnit TEMPLATE -->
    <j:set var="junitResultList" value="${it.JUnitTestResult}" />
    <j:if test="${junitResultList.isEmpty()!=true}">
    <a href="${rooturl}${build.url}/testReport">
    <h3>JUnit Tests</h3>
    </a>
    <table class="bordered" cellpadding="0" cellspacing="0" border="0" align="center">
    <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()}">
    <j:set var="testTrClass" value=""/>
    <j:if test="${packageResult.getFailCount() > 0}">
    <j:set var="testTrClass" value="test_failed"/>
    </j:if>

    <tr class="testPackage ${testTrClass}">
    <td class="border">
    <tt>${packageResult.getName()}</tt>
    </td>
    <td class="border test_failed" align="right">${packageResult.getFailCount()}</td>
    <td class="border test_passed" align="right">${packageResult.getPassCount()}</td>
    <td class="border test_skipped" align="right">${packageResult.getSkipCount()}</td>
    <td class="border" align="right">
    <b>${packageResult.getPassCount()+packageResult.getFailCount()+packageResult.getSkipCount()}</b>
    </td>
    </tr>
    <j:forEach var="failed_test" items="${packageResult.getFailedTests()}">
    <tr>
    <td class="test_failed name" colspan="5">
    <tt>${failed_test.getSimpleName()}.${failed_test.getName()}</tt>
    </td>
    </tr>
    </j:forEach>
    </j:forEach>
    </j:forEach>
    </table>
    <br />
    </j:if>

    </div>

    <!-- COBERTURA TEMPLATE -->
    <j:set var="coberturaAction" value="${it.coberturaAction}" />
    <j:if test="${coberturaAction!=null}">
    <j:set var="coberturaResult" value="${coberturaAction.result}" />
    <j:if test="${coberturaResult!=null}">
    <div class="callout ALT">
    <a href="${rooturl}${build.url}/cobertura">
    <h3>Cobertura Report</h3>
    </a>
    <h2>Project Coverage Summary</h2>
    <table class="border" cellpadding="0" cellspacing="0" border="0" align="center">
    <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" cellpadding="0" cellspacing="0" border="0" align="center">
    <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="bordered" cellpadding="0" cellspacing="0" border="0" align="center">
    <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>
    </div>
    </j:if>
    <br />
    </j:if>

    <!-- JACOCO TEMPLATE -->
    <j:set var="jacocoAction" value="${it.getAction('hudson.plugins.jacoco.JacocoBuildAction')}" />
    <j:if test="${jacocoAction!=null}">
    <j:set var="jacocoResult" value="${jacocoAction.result}" />
    <j:if test="${jacocoResult!=null}">
    <div class="callout ALT">
    <a href="${rooturl}${build.url}jacoco/">
    <h3>Jacoco Results</h3>
    </a>
    <table class="bordered" cellpadding="0" cellspacing="0" border="0" align="center">
    <tr>
    <th class="border">Package</th>
    <th class="border">Branches</th>
    <th class="border">Complexity</th>
    <th class="border">Instructions</th>
    <th class="border">Methods</th>
    <th class="border">Lines</th>
    <th class="border">Classes</th>
    </tr>
    <j:forEach var="child" items="${jacocoResult.getChildren().entrySet()}">
    <j:set var="cResult" value="${child.value}"/>
    <tr>
    <td class="border">
    <a href="${rooturl}${build.url}jacoco/${child.key}/"><tt>${child.key}</tt></a>
    </td>
    <td class="border right">${cResult.branchCoverage.percentage}%</td>
    <td class="border right">${cResult.complexityScore.percentage}%</td>
    <td class="border right">${cResult.instructionCoverage.percentage}%</td>
    <td class="border right">${cResult.methodCoverage.percentage}%</td>
    <td class="border right">${cResult.lineCoverage.percentage}%</td>
    <td class="border right">${cResult.classCoverage.percentage}%</td>
    </tr>
    </j:forEach>
    <tr>
    <td class="border">
    <a href="${rooturl}${build.url}jacoco/"><tt>OVERALL</tt></a>
    </td>
    <td class="border right">${jacocoResult.branchCoverage.percentage}%</td>
    <td class="border right">${jacocoResult.complexityScore.percentage}%</td>
    <td class="border right">${jacocoResult.instructionCoverage.percentage}%</td>
    <td class="border right">${jacocoResult.methodCoverage.percentage}%</td>
    <td class="border right">${jacocoResult.lineCoverage.percentage}%</td>
    <td class="border right">${jacocoResult.classCoverage.percentage}%</td>
    </tr>
    </table>
    </div>
    </j:if>
    </j:if>


    <!-- ARTIFACTS -->
    <j:set var="artifacts" value="${build.artifacts}" />
    <j:if test="${artifacts!=null and artifacts.size()&gt;0}">
    <div class="callout">
    <h3>Build Artifacts</h3>
    <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="callout">
    <h3>Build Artifacts</h3>
    <j:forEach var="m" items="${mbuilds}">
    <h4>${m.key.displayName}</h4>
    <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>

    <div class="callout ALT">
    <!-- CONSOLE OUTPUT -->
    <a href="${rooturl}${build.url}/console">
    <h3>Console Output</h3>
    </a>
    <code class="console">
    <j:forEach var="line" items="${build.getLog(50)}">
    ${line}<br/>
    </j:forEach>
    </code>
    <br />
    </div>

    </td>
    </tr>
    </table>
    </div>

    </td>
    <td></td>
    </tr>
    </table><!-- /BODY -->

    <!-- FOOTER -->
    <table class="footer-wrap">
    <tr>
    <td></td>
    <td class="container">

    <!-- content -->
    <div class="content">
    <p>
    You are receiving this email from Jenkins because you're either on a notification list or you
    commited some code to a project being built by Jenkins.
    </p>
    </div><!-- /content -->

    </td>
    <td></td>
    </tr>
    </table><!-- /FOOTER -->

    </body>
    </html>
    </j:jelly>