estimage/webapp/templates/utils.j2
{% from 'bootstrap5/utils.html' import render_icon %}
{% from 'bootstrap5/form.html' import render_form %}
{% macro render_whatever_retro(card, model, today, recursive=true) %}
{% if card.children %}
{{ render_epic_retro(card, model, today, recursive) }}
{% else %}
{{ render_task(card, "retrospective", model) }}
{% endif %}
{% endmacro %}
{% macro render_epic_retro(epic, model, today, recursive=true) %}
<li>
<div id="{{ epic.name }}">
{{ epic_link(epic, "retrospective") }} — {{ epic.title }}
<div class="container-md">
<div class="row">
<div class="col">
<p>
{{ state_to_string(epic.status) }}, {{ "%.2g" % model.remaining_point_estimate_of(epic.name).expected }} points remaining.
</p>
<p>
Owner:
{%- if epic.assignee -%}
{{ " " ~ epic.assignee }}
{%- else -%}
{{ " nobody" }}
{%- endif %}
</p>
{% if epic.status_summary -%}
<p>
Summary
{%- if epic.status_summary_time -%}
{{ " " ~ (today - epic.status_summary_time).days }} days old
{%- endif -%}
: {{ epic.status_summary | safe }}</p>
{%- endif %}
{% if recursive -%}
{% for card in (epic.children | sort(attribute="name")) %}
<ul>
{{- render_whatever_retro(card, model, today, recursive=recursive) -}}
</ul>
{% endfor %}
{%- endif %}
</div>
{% if model.remaining_point_estimate_of(epic.name).expected > 0 %}
<div class="col">
<img src="{{ head_url_for('vis.visualize_epic_burndown', epic_name=epic.name, size="small") }}" alt="Epic Burndown"/>
</div>
{% endif %}
</div>
</div>
</div>
</li>
{% endmacro %}
{% macro render_whatever(card, model, recursive=true) %}
{% if card.children %}
{{ render_epic(card, model, recursive=recursive) }}
{% else %}
{{ render_task(card, "projective", model) }}
{% endif %}
{% endmacro %}
{% macro refresh_whatever(card, mode, next) -%}
<a href="{{ head_url_for("main.refresh_single", name=card.name, mode=mode, next=next) }}">{{ render_icon("arrow-clockwise") }}</a>
{%- endmacro %}
{% set task_type_to_function = {
"projective": "main.view_projective_task",
"retrospective": "main.view_retro_task",
} %}
{% set epic_type_to_function = {
"projective": "main.view_epic_proj",
"retrospective": "main.view_epic_retro",
} %}
{% macro task_or_epic_link(card, type) -%}
{% if card.children %}
{{ epic_link(card, type) }}
{% else %}
{{ task_link(card, type) }}
{% endif %}
{%- endmacro %}
{% macro task_link(task, type) -%}
<a href="{{ head_url_for(task_type_to_function[type], task_name=task.name) }}">{{ task.name }}</a> <a href="{{ task.uri }}" rel="external">{{ render_icon("box-arrow-up-right") }}</a>
{%- endmacro %}
{% macro epic_external_link(epic) -%}
<a href="{{ epic.uri }}" rel="external">{{ render_icon("box-arrow-up-right") }}</a>
{%- endmacro %}
{% macro epic_link(epic, type="projective") -%}
<a href="{{ head_url_for(epic_type_to_function[type], epic_name=epic.name) }}">{{ epic.name }}</a> {{ epic_external_link(epic) }}
{%- endmacro %}
{% macro render_state_short(state) -%}
<span class="state.style_class">state.shortcut</span>
{%- endmacro %}
{% macro render_state(state) -%}
<span class="state.style_class">state.name</span>
{%- endmacro %}
{% macro render_task_basic(task, task_type, model) -%}
{{ task_link(task, task_type) }} — <span class="task-state">{{ state_to_string(task.status) }}</span><span class="task-points {{- " uncounted_points" if model and model.get_element(task.name).masked }}">{{ "%.2g" % task.point_cost }}</span>
{{ truncate_text_to(task.title, "350pt") }}
{%- endmacro %}
{% macro truncate_text_to(text, width) -%}
<span class="d-inline-block text-truncate align-top" style="max-width: {{ width }};">{{ text }}</span>
{%- endmacro %}
{% macro render_task(task, task_type, model) %}
<li>
<div id={{ task.name }}">
{{- render_task_basic(task, task_type, model) -}}
</div>
</li>
{% endmacro %}
{% macro render_epic_basic(epic) %}
{{ epic_link(epic) }} — {{ epic.title }}
{% endmacro %}
{% macro render_epic(epic, model, recursive=true) %}
<li>
<div id={{ epic.name }}">
{{ render_epic_basic(epic) }}
{% if model.remaining_point_estimate.expected > 0 %}
<p>
Left to do: {{ "%.3g" % model.remaining_point_estimate_of(epic.name).expected }}, i.e. {{ "%i %%" % (model.remaining_point_estimate_of(epic.name).expected / model.remaining_point_estimate.expected * 100) }} of the whole
</p>
{% endif %}
{% if recursive -%}
<ul>
{% for card in (epic.children | sort(attribute="name")) %}
{{- render_whatever(card, model, recursive=recursive) -}}
{% endfor %}
</ul>
{%- endif %}
</div>
</li>
{% endmacro %}
{% macro render_estimate(estimate, unit="") -%}
{{ "{expected:.3g}{unit_with_space}, 𝜎 = {sigma:.2g}{unit_with_space}".format(
expected=estimate.expected, sigma=estimate.sigma, unit_with_space=" " ~ unit) }}
{%- endmacro %}
{% macro render_precise_estimate(number, unit="") -%}
{{ "{expected:.3g}{unit_with_space}".format(
expected=number, unit_with_space=" " ~ unit) }}
{%- endmacro %}
{% macro accordion_with_stuff(stem, collapsed, header_text, body_text, heading_level=4) -%}
<div class="accordion" id="accordion{{ stem }}">
<div class="accordion-item">
<h{{ heading_level }} class="accordion-header" id="accordionHeading">
<button class="accordion-button {{- " collapsed" if collapsed }}" type="button" data-bs-toggle="collapse" data-bs-target="#collapse{{ stem }}" aria-expanded="{{ "false" if collapsed else "true" }}" aria-controls="collapse{{ stem }}">
{{ header_text }}
</button>
</h{{ heading_level }}>
<div id="collapse{{ stem }}" class="accordion-collapse collapse {{- " show" if not collapsed }}" aria-labelledby="accordionHeading" data-bs-parent="#accordion{{ stem }}">
<div class="accordion-body">
{{ body_text }}
</div>
</div>
</div>
</div>
{%- endmacro %}
{% macro task_metadata(task) -%}
<h2>{{ task.title }}</h2>
{% if task.uri -%}
<a href="{{ task.uri }}">{{ task.uri }}</a>
{% endif -%}
<div>State: {{ state_to_string(task.status) }}</div>
{% if (task.description | length) < 1200 %}
<h3>Description</h3>
<p>
{{ (task.description | safe) or "No description" }}
</p>
{%- else %}
{{ accordion_with_stuff("Description", true, "Description", (task.description | safe) or "No description", 3) }}
{%- endif %}
{%- endmacro %}
{% macro render_similar_sized_tasks(similar_sized_cards) -%}
<div class="row">
<h4>Tasks of similar sizes</h4>
<p>
<ul>
{%- for card in similar_sized_cards["proj"] %}
<li>{{ render_task_basic(card, "projective", None) }}: {{ render_estimate(card.point_estimate) }}</li>
{%- endfor %}
{%- for card in similar_sized_cards["retro"] %}
<li>{{ render_task_basic(card, "retrospective", None) }}: {{ render_estimate(card.point_estimate) }}</li>
{%- endfor %}
</ul>
</p>
</div>
{%- endmacro %}
{% set states_table = {
"todo": "To Do",
"in_progress": "In Progress",
"review": "Needs Peer Review",
"done": "Done",
"abandoned": "Abandoned",
} %}
{% macro state_to_string(state) -%}
{{ states_table.get(state, state) }}
{%- endmacro %}