Skip to content

Commit 97930b1

Browse files
Merge pull request #213 from NHSDigital/update-vaccination-dashboard-content
Update vaccination dashboard content following review
2 parents cb30bd3 + 856430c commit 97930b1

File tree

7 files changed

+228
-74
lines changed

7 files changed

+228
-74
lines changed

mavis/reporting/api_client/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,6 @@ def get_genders(self) -> list[dict]:
109109
return [
110110
{"value": "female", "text": "Female"},
111111
{"value": "male", "text": "Male"},
112-
{"value": "not specified", "text": "Not specified"},
113112
{"value": "not known", "text": "Not known"},
113+
{"value": "not specified", "text": "Not specified"},
114114
]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{% from "card/macro.jinja" import card %}
2+
3+
{% macro app_dashboard_card(params) %}
4+
5+
<div class="{{ params.classes }} nhsuk-card-group__item app-card-group__item">
6+
{{ card({
7+
"classes": "app-card " ~ params.card_classes,
8+
"heading": params.heading | escape,
9+
"headingClasses": "nhsuk-heading-xs",
10+
"headingLevel": 3,
11+
"descriptionHtml": "
12+
<p class='nhsuk-card__description'>" ~
13+
params.description | escape ~
14+
"<span class='nhsuk-card__caption'>" ~
15+
params.caption | escape ~
16+
"</span>" ~
17+
"</p>",
18+
}) }}
19+
</div>
20+
21+
{% endmacro %}

mavis/reporting/templates/layouts/dashboard.jinja

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"text": "Reports",
1616
"level": 1,
1717
"size": "l",
18-
"summary": "For " ~ academic_year ~ " so far"
18+
"summary": "For the " ~ academic_year ~ " school year so far"
1919
}) }}
2020
{% endblock %}
2121

mavis/reporting/templates/vaccinations.jinja

Lines changed: 69 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
{% from "tables/macro.jinja" import table %}
21
{% from "components/filters.jinja" import filters %}
2+
{% from "components/heading.jinja" import heading %}
33
{% from "card/macro.jinja" import card %}
4-
4+
{% from "tables/macro.jinja" import table %}
5+
{% from "components/app_dashboard_card.jinja" import app_dashboard_card %}
56

67
{% extends 'layouts/dashboard.jinja' %}
78

@@ -21,81 +22,77 @@
2122
</div>
2223

2324
<div id="dashboard" class="app-grid-column-content">
24-
2525
<div class="nhsuk-card-group nhsuk-grid-row app-card-group">
26-
<div class="nhsuk-grid-column-one-third nhsuk-card-group__item app-card-group__item">
27-
{% set child_or_children = "child" if data.cohort == 1 else "children" %}
28-
{{ card({
29-
"classes": "app-card app-card--blue",
30-
"heading": "Cohort",
31-
"headingClasses": "nhsuk-heading-xs",
32-
"headingLevel": 3,
33-
"descriptionHtml": "<p class='nhsuk-card__description'>" ~ data.cohort | thousands ~ "<span class='nhsuk-card__caption'>" ~ child_or_children ~ " </span></p>",
34-
}) }}
35-
</div>
36-
37-
<div class="nhsuk-grid-column-one-third nhsuk-card-group__item app-card-group__item">
38-
{{ card({
39-
"classes": "app-card app-card--red",
40-
"heading": "Not vaccinated",
41-
"headingClasses": "nhsuk-heading-xs",
42-
"headingLevel": 3,
43-
"descriptionHtml": "<p class='nhsuk-card__description'>" ~ data["not_vaccinated_percentage"] | percentage ~ "<span class='nhsuk-card__caption'>" ~ data["not_vaccinated"] | format_child_count ~ "</span></p>",
44-
}) }}
45-
</div>
46-
47-
<div class="nhsuk-grid-column-one-third nhsuk-card-group__item app-card-group__item">
48-
{{ card({
49-
"classes": "app-card app-card--green",
50-
"heading": "Vaccinated",
51-
"headingClasses": "nhsuk-heading-xs",
52-
"headingLevel": 3,
53-
"descriptionHtml": "<p class='nhsuk-card__description'>" ~ data["vaccinated_percentage"] | percentage ~ "<span class='nhsuk-card__caption'>" ~ data["vaccinated"] | format_child_count ~ "</span></p>",
54-
}) }}
55-
</div>
56-
<div class="nhsuk-grid-column-one-half nhsuk-card-group__item app-card-group__item">
57-
{{ card({
58-
"classes": "app-card",
59-
"heading": "Vaccinated by SAIS in " ~ academic_year,
60-
"headingClasses": "nhsuk-heading-xs",
61-
"headingLevel": 3,
62-
"descriptionHtml": "<p class='nhsuk-card__description'>" ~ data["vaccinated_by_sais_percentage"] | percentage ~ "<span class='nhsuk-card__caption'>" ~ data["vaccinated_by_sais"] | format_child_count ~ "</span></p>",
63-
}) }}
64-
</div>
26+
{{ app_dashboard_card({
27+
"classes": "nhsuk-grid-column-one-third",
28+
"card_classes": "app-card--blue",
29+
"heading": "Cohort",
30+
"description": data.cohort | thousands,
31+
"caption": "child" if data.cohort == 1 else "children",
32+
}) }}
33+
34+
{{ app_dashboard_card({
35+
"classes": "nhsuk-grid-column-one-third",
36+
"card_classes": "app-card--red",
37+
"heading": "Not vaccinated",
38+
"description": data["not_vaccinated_percentage"] | percentage,
39+
"caption": data["not_vaccinated"] | format_child_count,
40+
}) }}
41+
42+
{{ app_dashboard_card({
43+
"classes": "nhsuk-grid-column-one-third",
44+
"card_classes": "app-card--green",
45+
"heading": "Vaccinated",
46+
"description": data["vaccinated_percentage"] | percentage,
47+
"caption": data["vaccinated"] | format_child_count,
48+
}) }}
49+
</div>
6550

66-
<div class="nhsuk-grid-column-one-half nhsuk-card-group__item app-card-group__item">
67-
{% set isFlu = current_filters.programme == "flu" %}
68-
{% set percentage = "N/A" if isFlu else data["vaccinated_previously_percentage"] | percentage %}
69-
{% set child_count = "N/A" if isFlu else data["vaccinated_previously"] | format_child_count %}
70-
{{ card({
71-
"classes": "app-card",
72-
"heading": "Vaccinated in a previous academic year",
73-
"headingClasses": "nhsuk-heading-xs",
74-
"headingLevel": 3,
75-
"descriptionHtml": "<p class='nhsuk-card__description'>" ~ percentage ~ "<span class='nhsuk-card__caption'>" ~ child_count ~ "</span></p>",
76-
}) }}
77-
</div>
51+
{{ heading({
52+
"text": "Vaccinated in the current school year",
53+
"level": 2,
54+
"size": "s"
55+
}) }}
7856

79-
<div class="nhsuk-grid-column-one-half nhsuk-card-group__item app-card-group__item">
80-
{{ card({
81-
"classes": "app-card",
82-
"heading": "Vaccinated elsewhere (self-reported)",
83-
"headingClasses": "nhsuk-heading-xs",
84-
"headingLevel": 3,
85-
"descriptionHtml": "<p class='nhsuk-card__description'>" ~ data["vaccinated_elsewhere_declared_percentage"] | percentage ~ "<span class='nhsuk-card__caption'>" ~ data["vaccinated_elsewhere_declared"] | format_child_count ~ "</span></p>",
86-
}) }}
87-
</div>
57+
<div class="nhsuk-grid-row app-card-group nhsuk-card-group">
58+
{{ app_dashboard_card({
59+
"classes": "nhsuk-grid-column-one-third",
60+
"heading": "Vaccinated by " ~ team.name,
61+
"description": data["vaccinated_by_sais_percentage"] | percentage,
62+
"caption": data["vaccinated_by_sais"] | format_child_count,
63+
}) }}
64+
65+
{{ app_dashboard_card({
66+
"classes": "nhsuk-grid-column-one-third",
67+
"heading": "Vaccinated elsewhere (self-reported)",
68+
"description": data["vaccinated_elsewhere_declared_percentage"] | percentage,
69+
"caption": data["vaccinated_elsewhere_declared"] | format_child_count,
70+
}) }}
71+
72+
{{ app_dashboard_card({
73+
"classes": "nhsuk-grid-column-one-third",
74+
"heading": "Vaccinated elsewhere (confirmed)",
75+
"description": data["vaccinated_elsewhere_recorded_percentage"] | percentage,
76+
"caption": data["vaccinated_elsewhere_recorded"] | format_child_count,
77+
}) }}
78+
</div>
8879

89-
<div class="nhsuk-grid-column-one-half nhsuk-card-group__item app-card-group__item">
90-
{{ card({
91-
"classes": "app-card",
92-
"heading": "Vaccinated elsewhere (recorded)",
93-
"headingClasses": "nhsuk-heading-xs",
94-
"headingLevel": 3,
95-
"descriptionHtml": "<p class='nhsuk-card__description'>" ~ data["vaccinated_elsewhere_recorded_percentage"] | percentage ~ "<span class='nhsuk-card__caption'>" ~ data["vaccinated_elsewhere_recorded"] | format_child_count ~ "</span></p>",
80+
{% if current_filters.programme != "flu" %}
81+
{{ heading({
82+
"text": "Vaccinated before this school year",
83+
"level": 2,
84+
"size": "s"
85+
}) }}
86+
87+
<div class="nhsuk-grid-row app-card-group nhsuk-card-group">
88+
{{ app_dashboard_card({
89+
"classes": "nhsuk-grid-column-full",
90+
"heading": "Vaccinated in all settings",
91+
"description": data["vaccinated_previously_percentage"] | percentage,
92+
"caption": data["vaccinated_previously"] | format_child_count,
9693
}) }}
9794
</div>
98-
</div>
95+
{% endif %}
9996

10097
{% set rows = [] %}
10198
{% for row in data.monthly_vaccinations_given %}
@@ -110,7 +107,7 @@
110107
]) %}
111108

112109
{{ table({
113-
"heading": "Monthly vaccinations",
110+
"heading": "Monthly vaccinations by " ~ team.name,
114111
"tableClasses": "nhsuk-table--data nhsuk-table--with-total",
115112
"panel": true,
116113
"head": [

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ dev = [
2424
"boto3==1.40.64",
2525
"pyyaml>=6.0.2",
2626
"pyright>=1.1.405",
27+
"beautifulsoup4>=4.14.2",
2728
]
2829

2930
[tool.ruff]
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import pytest
2+
from bs4 import BeautifulSoup
3+
4+
from mavis.reporting import create_app
5+
6+
7+
def card_params():
8+
return {
9+
"classes": "nhsuk-grid-column-one-third",
10+
"card_classes": "app-card--blue",
11+
"heading": "Test Heading",
12+
"description": "123",
13+
"caption": "children",
14+
}
15+
16+
17+
def card_params_with_html():
18+
return {
19+
"classes": "nhsuk-grid-column-one-third",
20+
"card_classes": "app-card--blue",
21+
"heading": "<strong>Test Heading</strong>",
22+
"description": "<p>123</p>",
23+
"caption": "<strong>children</strong>",
24+
}
25+
26+
27+
def card_soup(render_macro, params):
28+
"""Get the BeautifulSoup object for a card."""
29+
html = render_macro(params)
30+
return BeautifulSoup(html, "html.parser")
31+
32+
33+
@pytest.fixture()
34+
def app():
35+
app = create_app()
36+
app.config.update(
37+
{
38+
"MAVIS_ROOT_URL": "http://mavis.test/",
39+
"TESTING": True,
40+
"CLIENT_ID": "test_client_id",
41+
"CLIENT_SECRET": "test_client_secret",
42+
}
43+
)
44+
return app
45+
46+
47+
@pytest.fixture()
48+
def render_macro(app):
49+
"""Helper fixture to render the app_dashboard_card macro."""
50+
51+
def _render(params):
52+
with app.app_context():
53+
template = app.jinja_env.get_template("components/app_dashboard_card.jinja")
54+
macro = template.module.app_dashboard_card
55+
return macro(params)
56+
57+
return _render
58+
59+
60+
class TestAppDashboardCard:
61+
def test_card_classes_are_correct(self, render_macro):
62+
"""Test that the card classes are correct."""
63+
64+
soup = card_soup(render_macro, card_params())
65+
66+
outer_div = soup.find("div", class_="nhsuk-card-group__item")
67+
assert outer_div is not None
68+
outer_classes = outer_div.get("class") or []
69+
assert "nhsuk-grid-column-one-third" in outer_classes
70+
assert "app-card-group__item" in outer_classes
71+
72+
card = outer_div.find("div", class_="app-card")
73+
assert card is not None
74+
card_classes = card.get("class") or []
75+
assert "app-card--blue" in card_classes
76+
77+
def test_heading_is_correct(self, render_macro):
78+
"""Test that the heading is rendered with correct classes and level."""
79+
80+
soup = card_soup(render_macro, card_params())
81+
heading = soup.find("h3", class_="nhsuk-heading-xs")
82+
assert heading is not None
83+
assert heading.text.strip() == "Test Heading"
84+
85+
def test_description_and_caption_are_correct(self, render_macro):
86+
"""Test that description and caption are rendered correctly."""
87+
88+
soup = card_soup(render_macro, card_params())
89+
description_p = soup.find("p", class_="nhsuk-card__description")
90+
assert description_p is not None
91+
assert description_p.contents[0].text == "123"
92+
93+
caption_span = description_p.find("span", class_="nhsuk-card__caption")
94+
assert caption_span is not None
95+
assert caption_span.text.strip() == "children"
96+
97+
def test_html_is_escaped(self, render_macro):
98+
"""Test that HTML is escaped."""
99+
100+
soup = card_soup(render_macro, card_params_with_html())
101+
heading = soup.find("h3", class_="nhsuk-heading-xs")
102+
assert heading is not None
103+
assert heading.text.strip() == "<strong>Test Heading</strong>"
104+
105+
description_p = soup.find("p", class_="nhsuk-card__description")
106+
assert description_p is not None
107+
assert description_p.contents[0].text == "<p>123</p>"
108+
109+
caption_span = description_p.find("span", class_="nhsuk-card__caption")
110+
assert caption_span is not None
111+
assert caption_span.text.strip() == "<strong>children</strong>"

uv.lock

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)