<% def dateFormat = new java.text.SimpleDateFormat("yyyy-M-d H:m:s") %>

${pipeline.documentation.title}

Created by Bpipe, ${new Date()}


Result


<%if(pipeline.failed) {%> Failed in stage <%=stages.flatten().reverse().find { list -> list.find {!it.succeeded} }?.context?.stageName%> <%} else {%> Succeeded <%}%>

Timeline


<% log = { System.out.println(it) } def nodeSize = { Node n -> n.children().count { it.attributes().type == "stage" } + n.children().grep { it.attributes().type == "pipeline" }.sum { nodeSize(it) } } def depthf = nodes.depthFirst() def breadf = nodes.breadthFirst() def nstages = depthf.grep { it.attributes().type == 'stage' && !it.attributes().stage.synthetic } %> <% // To let us render the whole pipeline proportionally, we do it in two phases. // The first phase constructs a list of rows, which end up as vertically displayed rows. // Each row contains a list of 'blocks', that are represent the actual activity of the // branch of the pipeline being rendered in that row. The block is actually a // Map describing the type of block to be rendered and other attributes. So the data structure // here is List> levels = [] layoutNode = { parent, depth -> if(levels.size()<=depth) levels.add([]) float usedWidth = 0.0f def childWidths = [] def childDepth = depth+1 for(Node n in parent.children()) { Map attr = n.attributes() if(attr.type == 'branchpoint') { def branchWidths = [] for(Node cn in n.children()) { // each child is a pipeline node while(levels.size()<=childDepth) levels.add([]) levels[childDepth].add([label:'', width:usedWidth, type:'empty']) def w = layoutNode(cn, childDepth) branchWidths.add(w) // println "
  • child pipeline returned width $w now widths are $childWidths" if(levels[childDepth]) childDepth+=1 } def bwMax = branchWidths.max() if(bwMax != null) { childWidths.add(branchWidths.max()) usedWidth+=branchWidths.max() } } else if(attr.type == 'stage' && !attr.stage.synthetic) { if(childDepth > depth+1) { levels.add([[type:'empty', label:'',width: usedWidth]]) depth = levels.size()-1 childDepth = depth+1 } def stage = attr.stage List outputs = attr.outputs def maxStopTime = outputs*.stopTimeMs.max() def minStartTime = outputs*.startTimeMs.min() def timeMs = 0 if(maxStopTime && minStartTime) { timeMs = maxStopTime - minStartTime } else { timeMs = stage?.context?.documentation?.elapsedMs?:0 maxStopTime = timeMs; minStartTime = 0; } float width = timeMs def duration = groovy.time.TimeCategory.minus(new Date(maxStopTime), new Date(minStartTime)).toString().replaceAll("seconds","s") levels[depth].add( [label: stage.stageName, width: width, type: 'block', anno: duration, stage: stage ]) usedWidth += (width) } } return usedWidth } layoutNode(nodes,0) def rowWidths = levels.collect { it.sum { it.width } } def maxRowWidth = rowWidths.max() // Downscale to ensure total width = 95% levels.each { level -> level.each { it.width = Math.max(0.05,it.width * 0.95/maxRowWidth) } } // Because of the max in the previous downscaling, we might still have overflowed! rowWidths = levels.collect { it.sum { it.width } } maxRowWidth = rowWidths.max() if(maxRowWidth > 0.97) levels.each { level -> level.each { it.width = it.width * 0.95/maxRowWidth } } %> <% int branchCount = 1; levels.eachWithIndex { level, index -> %>
    <% def prevBlock = [ type: 'none' ] def arrows = [ downright : '⤵', right: '→' ] level.eachWithIndex { block, blockIndex -> %> <% def nextBlock = (blockIndex < level.size()-1) ? level[blockIndex+1] : [type:'none']; if(prevBlock.type == 'block') { if(nextBlock.type=='block') { %>
    ${arrows.right}
    <%} else if(nextBlock.type != 'none') { %> ${arrows.downright} <% } %> <% } %> <% prevBlock = block; if(block.type=="block") ++branchCount; %> <%} if(prevBlock.type != 'none' && index'+arrows.downright+''); %>
    <% level.each { block -> %>
    <%=block.anno ?: '' %>
    <% } %>
    <% //log( index + ": " + level.collect { it.label+"($it.type) " +String.valueOf(it.width) }.join("|")) %> <% } %>

    Pipeline Stages


    Total Runtime = <%=groovy.time.TimeCategory.minus(pipeline.documentation.finishedAt,pipeline.documentation.startedAt)%>

      <% branchCount = 0; levels.each { level -> level.each { block -> if(!block.stage) return; def stage = block.stage; def ctx = stage.context; ++branchCount; %>
    1. <% if(ctx.documentation?.title) { %> ${ctx.stageName} : ${ctx.documentation.title} <%} else {%> ${ctx.stageName} <% } %> <%if(stage.succeeded) {%> ✓ <%} else {%> X <%}%>

      <%if(ctx.documentation) {%> <% def doc = ctx.documentation; %> <%if(doc.desc) {%>

      ${doc.desc}

      <%}%> <%if(doc.author) {%> <%}%> <%if(doc.inputs || ctx.inputs) {%> <%if(ctx.inputs) {%> <%} else {%> <% if(doc.inputs instanceof List) { %> <% } %> <% } %> <%}%> <%if(doc.outputs || ctx.outputs) {%> <% if(ctx.outputs) { %> <% } else { %> <% if(doc.outputs instanceof List) { %> <% } else { %> <% } %> <% } %> <%}%> <%if(doc.tools) {%> <%}%> <%if(doc?.constraints) {%> <%}%> <%if(doc.startedAt) {%> <%}%>
      Author ${doc.author}
      Inputs
        <% ctx.inputs.each { inp -> %>
      • ${inp.replaceAll("^\\./","")}
      • <% } %>
        <% doc.inputs.each { inp -> %>
      • ${inp}
      • <% } %>
      <% } else { %>
      ${doc.inputs}
      Commands${ctx.trackedOutputs*.value*.command.join("
      ")}
      Outputs
        <%ctx.outputs.each { output ->%>
      • ${output.replaceAll("^\\./","")}
      • <%}%>
        <% doc.outputs.each { inp -> %>
      • ${inp}
      • <% } %>
      ${doc.outputs}
      Tools
        <% doc.tools.each { name, detail -> %>
      • <% if(detail.meta.link) { %> ${name} <% } else { %> ${name} <% } %> : ${detail.version} <% if(detail.meta.desc) { %> - ${detail.meta.desc} <% } %>
      • <% } %>
      Constraints ${doc.constraints}
      Execution Time <%if(doc.elapsedMs<1000 || !doc.finishedAt) { %> ${dateFormat.format(doc.startedAt)} <% } else {%> ${dateFormat.format(doc.startedAt)} - ${dateFormat.format(doc.finishedAt)} <% } %> <%if(doc.finishedAt) use(groovy.time.TimeCategory) { %> (${doc.finishedAt - doc.startedAt}) <% } %>
      <%}%>
    2. <%}%>
    <%}%>