| 1 |
{% extends "base.html" %} |
| 2 |
{% import "repo_tabs.html" as m %} |
| 3 |
{% block title %}Run #{{ run.number }} · Flow · {{ repo.owner }}/{{ repo.name }}{% endblock %} |
| 4 |
{% block bodyattrs %} data-repo="{{ repo.url }}"{% if run.live() %} data-flow-live="{{ repo.url }}/flow/{{ run.number }}/live"{% endif %}{% endblock %} |
| 5 |
{% block tabs %}{% call m::tabs(repo, "flow") %}{% endblock %} |
| 6 |
{% block mainclass %} class="wide"{% endblock %} |
| 7 |
{% block content %} |
| 8 |
<div class="head"> |
| 9 |
<h1><span class="flow-glyph st-{{ run.status }}" data-run-glyph>{{ crate::web::flow::glyph(run.status) }}</span> Run #{{ run.number }} <small data-run-status>{{ run.status }}</small></h1> |
| 10 |
<p class="small mute">{% if run.bookmark.is_empty() %}<span class="tag" title="Run on one change, not a bookmark">change</span>{% else %}<span class="lbl lbl-bm">{{ run.bookmark }}</span>{% endif %} at <a href="{{ repo.url }}/tree/{{ run.heads }}" title="state {{ run.state }}"><code>{{ run.state|short }}</code></a> · {{ run.started_by() }} <time title="{{ run.created_ms|when_i }}">{{ run.created_ms|ago_i }}</time> · {{ duration }}{% if !run.pipeline_hash.is_empty() %} · pipeline <code title="{{ run.pipeline_hash }}">{{ run.pipeline_hash|short }}</code>{% endif %} · <a href="{{ repo.url }}/file/{{ run.heads }}/.flow/pipeline.kdl">.flow/pipeline.kdl</a> · <a href="{{ repo.url }}/flow">all runs</a></p> |
| 11 |
{% if can_rerun %}<form class="inline" method="post" action="{{ repo.url }}/flow/{{ run.number }}/rerun"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><button type="submit" class="small">RE-RUN</button></form>{% endif %} |
| 12 |
{% if can_cancel %}<form class="inline" method="post" action="{{ repo.url }}/flow/{{ run.number }}/cancel" onsubmit="return confirm('Cancel run #{{ run.number }}? Jobs still running are stopped.')"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><button type="submit" class="small danger-btn">CANCEL</button></form>{% endif %} |
| 13 |
</div> |
| 14 |
{% if let Some(q) = queue %} |
| 15 |
<section class="panel"><div class="strip">Waiting for a runner: {{ q.place().unwrap_or_default() }}.{% if !q.running.is_empty() %} Running now: {{ q.running.join(", ") }}.{% endif %}{% if !q.ahead.is_empty() %} Queued ahead: {{ q.ahead.join(", ") }}.{% endif %}{% if q.hidden > 0 %} Also ahead or running: {{ q.hidden }} job{% if q.hidden != 1 %}s{% endif %} in private repositories.{% endif %}</div></section> |
| 16 |
{% endif %} |
| 17 |
{% if run.status == "error" %} |
| 18 |
<section class="panel"><div class="strip bad">The pipeline is not valid, so nothing ran.</div><pre class="snippet">{{ run.error }}</pre><p class="small mute">Run <code>flow check</code> locally to see the same problems.</p></section> |
| 19 |
{% else %} |
| 20 |
<section class="panel flow-graph-box"> |
| 21 |
<svg class="flow-graph" viewBox="0 0 {{ graph.width }} {{ graph.height }}" width="{{ graph.width }}" height="{{ graph.height }}" role="img" aria-label="Job graph of run {{ run.number }}"> |
| 22 |
{% for e in graph.edges %}<path class="edge st-{{ e.status }}" d="{{ e.d }}"/>{% endfor %} |
| 23 |
{% for n in graph.nodes %} |
| 24 |
<a href="#job-{{ n.j.id }}" data-job-node="{{ n.j.id }}" class="node st-{{ n.j.status }}"> |
| 25 |
<rect x="{{ n.x }}" y="{{ n.y }}" width="{{ crate::web::flow::NODE_W }}" height="{{ crate::web::flow::NODE_H }}" rx="6"/> |
| 26 |
<text class="g" x="{{ n.x + 18 }}" y="{{ n.y + 30 }}" text-anchor="middle" data-glyph>{{ crate::web::flow::glyph(n.j.status) }}</text> |
| 27 |
<text class="name" x="{{ n.x + 34 }}" y="{{ n.y + 21 }}">{{ n.j.name }}</text> |
| 28 |
<text class="meta" x="{{ n.x + 34 }}" y="{{ n.y + 37 }}" data-meta>{{ n.j.status }} · {{ n.duration }}</text> |
| 29 |
</a> |
| 30 |
{% endfor %} |
| 31 |
</svg> |
| 32 |
</section> |
| 33 |
{% for l in logs %} |
| 34 |
<details class="panel flow-job st-{{ l.j.status }}" id="job-{{ l.j.id }}" data-job="{{ l.j.id }}"{% if l.j.status == "failed" || l.j.status == "error" || l.j.status == "running" || (l.j.status == "queued" && !l.j.waiting.is_empty()) %} open{% endif %}> |
| 35 |
<summary><span class="flow-glyph st-{{ l.j.status }}" data-glyph>{{ crate::web::flow::glyph(l.j.status) }}</span> <strong>{{ l.j.name }}</strong> <span class="small mute" data-meta>{{ l.j.status }}{% if let Some(c) = l.j.exit_code %} · exit {{ c }}{% endif %} · {{ l.duration }}{% if !l.j.runner.is_empty() %} · on <span{% if !l.j.placed.is_empty() %} title="Placed there: {{ l.j.placed }}"{% endif %}>{{ l.j.runner }}</span>{% endif %}</span>{% if !l.j.receipt_attest.is_empty() %} <span class="small mute">· <a href="{{ repo.url }}/receipts/{{ l.j.receipt_attest }}" title="Receipt {{ l.j.receipt_hash }}, kept in the repository; check it with oxy receipt verify">signed receipt</a> by {{ l.j.receipt_signer }}</span>{% else if !l.j.receipt_note.is_empty() %} <span class="small warn" title="{{ l.j.receipt_note }}">· receipt not kept: {{ l.j.receipt_note }}</span>{% else if !l.j.receipt_hash.is_empty() %} <span class="small mute">· unsigned receipt <code title="{{ l.j.receipt_hash }}">{{ l.j.receipt_hash|short }}</code></span>{% endif %}{% if !l.artifacts.is_empty() %} <span class="small mute">· {{ l.artifacts.len()|plural("artifact", "artifacts") }}</span>{% endif %}{% if let Some(t) = l.tests %} <span class="small{% if t.broken() > 0 %} warn{% endif %}">· <a href="#tests-{{ l.j.id }}">{{ t.summary() }}</a></span>{% endif %}{% if let Some((cid, pct)) = l.coverage %} <span class="small">· <a href="{{ repo.url }}/coverage?report={{ cid }}">coverage {{ pct }}</a></span>{% endif %}</summary> |
| 36 |
{% if can_rerun && l.j.done() %}<form class="tiny inline job-rerun" method="post" action="{{ repo.url }}/flow/{{ run.number }}/jobs/{{ l.j.name }}/rerun"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><button type="submit" class="small" title="Run {{ l.j.name }} again in this run, with the jobs that depend on it; what it depends on keeps its results">RE-RUN THIS JOB</button></form>{% endif %} |
| 37 |
{% if !l.j.error.is_empty() %}<div class="strip bad">{{ l.j.error }}</div>{% endif %} |
| 38 |
{% if !l.artifacts.is_empty() %}<ul class="flow-artifacts small">{% for a in l.artifacts %}<li><a href="{{ repo.url }}/flow/{{ run.number }}/artifacts/{{ l.j.name }}/{{ a.name }}">{{ a.name }}</a> <span class="mute">{{ a.path }} · {{ a.size|bytes }} · blob <code title="{{ a.blob }}">{{ a.blob|short }}</code></span>{% if l.j.status != "passed" %} <span class="warn" title="Kept with when "always": the job did not pass, so its receipt does not name this file">· from a job that did not pass, not in its receipt</span>{% endif %}{% if !releases.is_empty() && l.j.status == "passed" %} <form class="tiny inline" method="post" action="{{ repo.url }}/flow/{{ run.number }}/artifacts/{{ l.j.name }}/{{ a.name }}/attach"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><select name="release" aria-label="Release to attach {{ a.name }} to">{% for r in releases %}<option value="{{ r.id }}">{{ r.tag }}</option>{% endfor %}</select> <button>Attach to release</button></form>{% endif %}</li>{% endfor %}</ul>{% endif %} |
| 39 |
{% if let Some(t) = l.tests %}<div class="flow-tests" id="tests-{{ l.j.id }}"> |
| 40 |
<p class="small"><span class="tests-count ok">{{ t.passed }} passed</span>{% if t.failed > 0 %} · <span class="tests-count bad">{{ t.failed }} failed</span>{% endif %}{% if t.errors > 0 %} · <span class="tests-count bad">{{ t.errors }} errored</span>{% endif %}{% if t.skipped > 0 %} · <span class="tests-count mute">{{ t.skipped }} skipped</span>{% endif %} <span class="mute">· {{ crate::flow::human_ms(*t.time_ms) }} of test time</span></p> |
| 41 |
{% for f in l.failures %}<details class="tests-failure"> |
| 42 |
<summary><span class="bad">{% if f.status == "error" %}!{% else %}✕{% endif %}</span> <code>{{ f.title() }}</code>{% if !f.message.is_empty() %} <span class="small mute">{{ f.message }}</span>{% endif %}</summary> |
| 43 |
{% if !f.output.is_empty() %}<pre class="flow-log">{{ f.output }}</pre>{% endif %} |
| 44 |
</details>{% endfor %} |
| 45 |
{% if t.broken() > l.failures.len() as i64 %}<p class="small mute">and {{ t.broken() - l.failures.len() as i64 }} more failures; the junit artifact has them all.</p>{% endif %} |
| 46 |
</div>{% endif %} |
| 47 |
<pre class="flow-log" data-log data-seq="{{ l.last_seq }}">{% for seg in l.segments %}{% match seg %}{% when crate::web::flow::Segment::Line(line) %}<span class="ln s-{{ line.stream }}"><span class="t">{{ crate::web::flow::secs(line.t) }}</span>{{ line.text }}</span> |
| 48 |
{% when crate::web::flow::Segment::Group(g) %}<details class="log-group" data-from="{{ g.from_t }}"{% if g.to_t.is_none() %} open data-open-group{% endif %}><summary class="ln"><span class="t">{{ crate::web::flow::secs(g.from_t) }}</span>{{ g.title }}{% if let Some(e) = g.to_t %} <span class="took">{{ crate::web::flow::secs(e - g.from_t) }}</span>{% endif %}</summary>{% for line in g.lines %}<span class="ln s-{{ line.stream }}"><span class="t">{{ crate::web::flow::secs(line.t) }}</span>{{ line.text }}</span> |
| 49 |
{% endfor %}</details>{% endmatch %}{% endfor %}</pre> |
| 50 |
{% if !l.j.placed.is_empty() %}<p class="small mute pad">Ran on {{ l.j.runner }}: {{ l.j.placed }}.</p>{% endif %} |
| 51 |
{% if l.j.status == "queued" && !l.j.waiting.is_empty() %}<p class="small warn pad" data-waiting>Waiting: {{ l.j.waiting }}.</p>{% else if l.lines.is_empty() && l.j.status == "queued" %}<p class="small mute pad">Waiting for a runner{% if !l.j.depends_on().is_empty() %} and for {{ l.j.depends_on().join(", ") }}{% endif %}.</p>{% endif %} |
| 52 |
{% if l.lines.is_empty() && l.j.status == "skipped" %}<p class="small mute pad">Skipped: a job it depends on did not pass.</p>{% endif %} |
| 53 |
</details> |
| 54 |
{% endfor %} |
| 55 |
{% endif %} |
| 56 |
{% endblock %} |