OXYGENHUB
# 3eb491e9489b oxygenhub / templates / flow_run.html
11.1 KiB · HTML · last changed Flow priorities: site, organisation and repository levels, and expediting a run 19 days ago source blame history raw
42fa9554 Andy McCall ↶ 1
{% extends "base.html" %}
2
{% import "repo_tabs.html" as m %}
a0d4bbfb Andy McCall ↶ 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 %}
42fa9554 Andy McCall ↶ 5
{% block tabs %}{% call m::tabs(repo, "flow") %}{% endblock %}
6
{% block mainclass %} class="wide"{% endblock %}
7
{% block content %}
8
<div class="head">
a0d4bbfb Andy McCall ↶ 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>
4f692533 Andy McCall ↶ 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>
1edac4b6 Andy McCall ↶ 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 %}
107eaba5 Andy McCall ↶ 12
  {% if !run.priority.is_empty() %}<p class="small"><span class="tag tag-urgent" title="Runners take it before any other">{{ run.priority }}</span> {{ run.priority_note }}</p>{% endif %}
13
  {% if can_expedite %}<form class="inline" method="post" action="{{ repo.url }}/flow/{{ run.number }}/expedite" title="Runners take an urgent run's jobs before any other's"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="text" name="reason" size="22" maxlength="200" placeholder="why it is urgent" required> <button type="submit" class="small">EXPEDITE</button></form>{% endif %}
745d225d Andy McCall ↶ 14
  {% 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 %}
42fa9554 Andy McCall ↶ 15
</div>
bf3d0754 Andy McCall ↶ 16
{% if let Some(q) = queue %}
17
<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>
18
{% endif %}
42fa9554 Andy McCall ↶ 19
{% if run.status == "error" %}
20
<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>
21
{% else %}
417184c7 Andy McCall ↶ 22
{% if !approvals.is_empty() %}<section class="panel approvals" aria-label="Approvals">
23
  <h2>Approvals</h2>
24
  <ul class="plain">{% for a in approvals %}<li class="approval st-{{ a.state }}"><strong>{{ a.job }}</strong>
25
    {% if a.state == "waiting" %}waits for approval by someone with the <em>{{ a.by }}</em> role{% if a.others_only %}, other than {{ run.actor }}{% endif %}.
47e37402 Andy McCall ↶ 26
      {% if a.sealed %}<p class="small">It takes an approval signed with your own key. In a checkout of {{ repo.owner }}/{{ repo.name }}:</p>
27
      <div class="clone"><input type="text" readonly value="{{ a.command }}" onfocus="this.select()" aria-label="The command that approves it"><button type="button" class="small" data-copy="{{ a.command }}">copy</button></div>{% endif %}
28
      {% if a.may_decide() %}<form class="inline" method="post" action="{{ repo.url }}/flow/{{ run.number }}/jobs/{{ a.job }}/decide"><input type="hidden" name="_csrf" value="{{ shell.csrf }}">{% if !a.sealed %}<button type="submit" name="action" value="approve" class="small">APPROVE</button> {% endif %}<button type="submit" name="action" value="reject" class="small danger-btn">REJECT</button></form>
417184c7 Andy McCall ↶ 29
      {% else %}<span class="small mute">{{ a.why_not() }}</span>{% endif %}
47e37402 Andy McCall ↶ 30
    {% else if a.state == "approved" %}approved by {{ a.who }} <time title="{{ a.when_ms|when }}">{{ a.when_ms|ago }}</time>{% if !a.seal.is_empty() %}, <span class="sig sig-valid">◈</span> sealed with their key (approval <code title="{{ a.seal }}">{{ a.seal|short }}</code>){% else if a.sealed %}{% endif %}.
417184c7 Andy McCall ↶ 31
    {% else if a.state == "rejected" %}rejected by {{ a.who }} <time title="{{ a.when_ms|when }}">{{ a.when_ms|ago }}</time>.
32
    {% else %}not approved in time.{% endif %}</li>{% endfor %}</ul>
33
</section>{% endif %}
42fa9554 Andy McCall ↶ 34
<section class="panel flow-graph-box">
a0d4bbfb Andy McCall ↶ 35
  <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 }}">
42fa9554 Andy McCall ↶ 36
    {% for e in graph.edges %}<path class="edge st-{{ e.status }}" d="{{ e.d }}"/>{% endfor %}
37
    {% for n in graph.nodes %}
38
    <a href="#job-{{ n.j.id }}" data-job-node="{{ n.j.id }}" class="node st-{{ n.j.status }}">
39
      <rect x="{{ n.x }}" y="{{ n.y }}" width="{{ crate::web::flow::NODE_W }}" height="{{ crate::web::flow::NODE_H }}" rx="6"/>
40
      <text class="g" x="{{ n.x + 18 }}" y="{{ n.y + 30 }}" text-anchor="middle" data-glyph>{{ crate::web::flow::glyph(n.j.status) }}</text>
41
      <text class="name" x="{{ n.x + 34 }}" y="{{ n.y + 21 }}">{{ n.j.name }}</text>
42
      <text class="meta" x="{{ n.x + 34 }}" y="{{ n.y + 37 }}" data-meta>{{ n.j.status }} · {{ n.duration }}</text>
43
    </a>
44
    {% endfor %}
45
  </svg>
46
</section>
47
{% for l in logs %}
67040b15 Andy McCall ↶ 48
<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 %}>
501d8b26 Andy McCall ↶ 49
  <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>
e8fa5369 Andy McCall ↶ 50
  {% 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 %}
42fa9554 Andy McCall ↶ 51
  {% if !l.j.error.is_empty() %}<div class="strip bad">{{ l.j.error }}</div>{% endif %}
6f52a041 Andy McCall ↶ 52
  {% 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 &quot;always&quot;: 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 %}
0b203bbf Andy McCall ↶ 53
  {% if let Some(t) = l.tests %}<div class="flow-tests" id="tests-{{ l.j.id }}">
54
    <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>
55
    {% for f in l.failures %}<details class="tests-failure">
56
      <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>
57
      {% if !f.output.is_empty() %}<pre class="flow-log">{{ f.output }}</pre>{% endif %}
58
    </details>{% endfor %}
59
    {% 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 %}
60
  </div>{% endif %}
42fa9554 Andy McCall ↶ 61
  <pre class="flow-log" data-log data-seq="{{ l.last_seq }}">{% for line in l.lines %}<span class="ln s-{{ line.stream }}"><span class="t">{{ line.t / 1000 }}.{{ "{:01}"|format(line.t % 1000 / 100) }}s</span>{{ line.text }}</span>
62
{% endfor %}</pre>
501d8b26 Andy McCall ↶ 63
  {% if !l.j.placed.is_empty() %}<p class="small mute pad">Ran on {{ l.j.runner }}: {{ l.j.placed }}.</p>{% endif %}
4d2e44d7 Andy McCall ↶ 64
  {% 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 %}
42fa9554 Andy McCall ↶ 65
  {% 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 %}
66
</details>
67
{% endfor %}
68
{% endif %}
69
{% endblock %}