| 1 |
{% extends "base.html" %} |
| 2 |
{% block title %}Charts · {{ project.title }} · Projects · {{ project.owner }}{% endblock %} |
| 3 |
{% block mainclass %} class="wide"{% endblock %} |
| 4 |
{% block content %} |
| 5 |
<div class="head"><h1>{{ project.title }} <small><a href="/projects/{{ project.owner }}">{{ project.owner }}'s projects</a> · #{{ project.number }}</small></h1></div> |
| 6 |
<nav class="board-tabs" aria-label="Views of this board"> |
| 7 |
<a class="board-tab" href="/projects/{{ project.owner }}/{{ project.number }}">Board</a> |
| 8 |
{% for v in views %}<a class="board-tab" href="/projects/{{ project.owner }}/{{ project.number }}?view={{ v.name|urlencode }}">{{ v.name }} <span class="small mute">{{ v.layout }}</span></a>{% endfor %} |
| 9 |
<a class="board-tab on" href="/projects/{{ project.owner }}/{{ project.number }}/charts" aria-current="page">Charts</a> |
| 10 |
</nav> |
| 11 |
<p class="small mute">From the board's own history: when each card joined each column. No estimates; the forecasts sample the weeks the board has had.</p> |
| 12 |
|
| 13 |
<section aria-labelledby="fc-h"> |
| 14 |
<h2 id="fc-h">Forecasts</h2> |
| 15 |
<form class="board-filter" method="get" action="/projects/{{ project.owner }}/{{ project.number }}/charts" role="search"> |
| 16 |
<label class="visually-hidden" for="fc-q">Forecast the cards matching</label><input type="search" id="fc-q" name="q" value="{{ q }}" placeholder="forecast cards matching: label:bug, milestone:…, repo:…"> |
| 17 |
<button type="submit" class="small">FORECAST</button> |
| 18 |
</form> |
| 19 |
<div class="table-wrap"><table class="board-table"> |
| 20 |
<thead><tr><th scope="col">Cards</th><th scope="col">Left</th><th scope="col">50% done by</th><th scope="col">85% done by</th></tr></thead> |
| 21 |
<tbody>{% for f in c.forecasts %}<tr><td>{{ f.scope }}</td><td>{{ f.remaining }}</td> |
| 22 |
{% match f.p50 %}{% when Some(d) %}<td><strong>{{ d }}</strong></td>{% when None %}<td class="mute" colspan="2">no forecast: no card has reached Done in the weeks it draws from</td>{% endmatch %} |
| 23 |
{% if let Some(d) = f.p85 %}<td>{{ d }}</td>{% else if f.p50.is_some() %}<td class="mute">beyond ten years</td>{% endif %}</tr>{% endfor %}</tbody> |
| 24 |
</table></div> |
| 25 |
{% if let Some(f) = c.forecasts.first() %}<p class="small mute">From {{ f.weeks_of_history }} week{% if f.weeks_of_history != 1 %}s{% endif %} of throughput (cards reaching Done each week), sampled {{ crate::web::board_charts::TRIALS_SHOWN }} times; the same history gives the same dates all day.</p>{% endif %} |
| 26 |
</section> |
| 27 |
|
| 28 |
<div class="chart-grid"> |
| 29 |
<figure class="chart-card wide-card" id="timeline"><figcaption>Timeline</figcaption> |
| 30 |
{% if timeline_svg.is_empty() %}<p class="mute">No iterations yet, and no open card with a milestone. Start an iteration on the board, or give cards milestones, and they appear here.</p> |
| 31 |
{% else %}{{ timeline_svg|safe }} |
| 32 |
<div class="legend"><span class="key s-main">open iteration</span><span class="key s-mute">past iteration</span><span class="key s-warn">forecast: 50% to 85%</span><span class="key s-bad">milestone due</span></div>{% endif %} |
| 33 |
</figure> |
| 34 |
{% if let Some((name, _, days)) = c.burn %} |
| 35 |
<figure class="chart-card"><figcaption>{{ name }}: burn-down</figcaption>{{ burn_svg|safe }} |
| 36 |
<div class="legend"><span class="key s-main">cards left</span><span class="key s-mute">scope</span></div> |
| 37 |
<p class="small mute">{% if let Some((l, s)) = days.last() %}{{ l }} left of {{ s }} after {{ days.len() }} day{% if days.len() != 1 %}s{% endif %}{% endif %}.</p> |
| 38 |
</figure> |
| 39 |
{% endif %} |
| 40 |
<figure class="chart-card"><figcaption>Cycle time</figcaption> |
| 41 |
{% match c.cycle_median_days %}{% when Some(m) %}<p class="kpi"><span class="kpi-v">{{ m }} d</span> <span class="kpi-l">median</span> · <span class="kpi-v">{% if let Some(p) = c.cycle_p85_days %}{{ p }}{% endif %} d</span> <span class="kpi-l">85th percentile</span></p> |
| 42 |
<p class="small mute">From leaving the first column to reaching Done, over {{ c.cycle_finished }} finished card{% if c.cycle_finished != 1 %}s{% endif %}.</p> |
| 43 |
{% when None %}<p class="mute">No card has gone from started to Done yet.</p>{% endmatch %} |
| 44 |
</figure> |
| 45 |
<figure class="chart-card"><figcaption>Throughput: cards done each week</figcaption>{{ throughput_svg|safe }} |
| 46 |
<details><summary class="small">the numbers</summary><table class="board-table"><thead><tr><th scope="col">Week from</th><th scope="col">Done</th></tr></thead><tbody>{% for (w, n) in week_table %}<tr><td>{{ w }}</td><td>{{ n }}</td></tr>{% endfor %}</tbody></table></details> |
| 47 |
</figure> |
| 48 |
<figure class="chart-card wide-card"><figcaption>Cumulative flow</figcaption>{{ flow_svg|safe }} |
| 49 |
<div class="legend">{% for name in c.columns %}<span class="key cf-key cf-{% if loop.last %}5{% else %}{{ loop.index0 % 5 }}{% endif %}">{{ name }}</span>{% endfor %}</div> |
| 50 |
<p class="small mute">Cards in each column at the end of each day. A band that keeps widening is where work waits.</p> |
| 51 |
<details><summary class="small">the last fortnight's numbers</summary><div class="table-wrap"><table class="board-table"><thead><tr><th scope="col">Day</th>{% for name in c.columns %}<th scope="col">{{ name }}</th>{% endfor %}</tr></thead><tbody>{% for (d, row) in flow_table %}<tr><td>{{ d }}</td>{% for n in row %}<td>{{ n }}</td>{% endfor %}</tr>{% endfor %}</tbody></table></div></details> |
| 52 |
</figure> |
| 53 |
</div> |
| 54 |
{% endblock %} |