remove dead code from dashboard

This commit is contained in:
Beverly Nguyen
2025-03-04 21:14:53 -08:00
parent 0df04cfd89
commit e59516562f
8 changed files with 52 additions and 659 deletions

View File

@@ -4,32 +4,22 @@ from functools import partial
from itertools import groupby
from zoneinfo import ZoneInfo
from flask import Response, abort, jsonify, render_template, request, session, url_for
from flask import abort, jsonify, render_template, request, session, url_for
from flask_login import current_user
from werkzeug.utils import redirect
from app import (
billing_api_client,
current_service,
job_api_client,
service_api_client,
template_statistics_client,
)
from app.formatters import format_date_numeric, format_datetime_numeric
from app.main import main
from app.main.views.user_profile import set_timezone
from app.statistics_utils import get_formatted_percentage
from app.utils import (
DELIVERED_STATUSES,
FAILURE_STATUSES,
REQUESTED_STATUSES,
service_has_permission,
)
from app.utils.csv import Spreadsheet
from app.utils.pagination import generate_next_dict, generate_previous_dict
from app.utils import FAILURE_STATUSES, REQUESTED_STATUSES
from app.utils.time import get_current_financial_year
from app.utils.user import user_has_permissions
from notifications_utils.recipients import format_phone_number_human_readable
@main.route("/services/<uuid:service_id>/dashboard")
@@ -62,14 +52,20 @@ def service_dashboard(service_id):
total_messages = service_api_client.get_service_message_ratio(service_id)
messages_remaining = total_messages.get("messages_remaining", 0)
messages_sent = total_messages.get("messages_sent", 0)
all_statistics = template_statistics_client.get_template_statistics_for_service(
service_id, limit_days=7
)
template_statistics = aggregate_template_usage(all_statistics)
return render_template(
"views/dashboard/dashboard.html",
updates_url=url_for(".service_dashboard_updates", service_id=service_id),
partials=get_dashboard_partials(service_id),
jobs=job_lists,
service_data_retention_days=service_data_retention_days,
messages_remaining=messages_remaining,
messages_sent=messages_sent,
template_statistics=template_statistics,
most_used_template_count=max(
[row["count"] for row in template_statistics] or [0]
),
)
@@ -120,7 +116,7 @@ def get_local_daily_stats_for_last_x_days(stats_utc, user_timezone, days):
]
aggregator = {
d: {
"sms": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
"sms": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
}
for d in days_list
@@ -128,7 +124,9 @@ def get_local_daily_stats_for_last_x_days(stats_utc, user_timezone, days):
# Convert each UTC timestamp to local date and iterate
for utc_ts, data in stats_utc.items():
utc_dt = datetime.strptime(utc_ts, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=ZoneInfo("UTC"))
utc_dt = datetime.strptime(utc_ts, "%Y-%m-%dT%H:%M:%SZ").replace(
tzinfo=ZoneInfo("UTC")
)
local_day = utc_dt.astimezone(tz).strftime("%Y-%m-%d")
if local_day in aggregator:
@@ -152,18 +150,6 @@ def get_daily_stats_by_user(service_id):
return jsonify(stats)
@main.route("/services/<uuid:service_id>/dashboard.json")
@user_has_permissions("view_activity")
def service_dashboard_updates(service_id):
return jsonify(**get_dashboard_partials(service_id))
@main.route("/services/<uuid:service_id>/template-activity")
@user_has_permissions("view_activity")
def template_history(service_id):
return redirect(url_for("main.template_usage", service_id=service_id), code=301)
@main.route("/services/<uuid:service_id>/template-usage")
@user_has_permissions("view_activity")
def template_usage(service_id):
@@ -249,103 +235,6 @@ def usage(service_id):
)
@main.route("/services/<uuid:service_id>/monthly")
@user_has_permissions("view_activity")
def monthly(service_id):
year, current_financial_year = requested_and_current_financial_year(request)
return render_template(
"views/dashboard/monthly.html",
months=format_monthly_stats_to_list(
service_api_client.get_monthly_notification_stats(service_id, year)["data"]
),
years=get_tuples_of_financial_years(
partial_url=partial(url_for, ".monthly", service_id=service_id),
start=current_financial_year - 2,
end=current_financial_year,
),
selected_year=year,
)
@main.route("/services/<uuid:service_id>/inbox")
@user_has_permissions("view_activity")
@service_has_permission("inbound_sms")
def inbox(service_id):
return render_template(
"views/dashboard/inbox.html",
partials=get_inbox_partials(service_id),
updates_url=url_for(
".inbox_updates", service_id=service_id, page=request.args.get("page")
),
)
@main.route("/services/<uuid:service_id>/inbox.json")
@user_has_permissions("view_activity")
@service_has_permission("inbound_sms")
def inbox_updates(service_id):
return jsonify(get_inbox_partials(service_id))
@main.route("/services/<uuid:service_id>/inbox.csv")
@user_has_permissions("view_activity")
def inbox_download(service_id):
return Response(
Spreadsheet.from_rows(
[
[
"Phone number",
"Message",
"Received",
]
]
+ [
[
format_phone_number_human_readable(message["user_number"]),
message["content"].lstrip(("=+-@")),
format_datetime_numeric(message["created_at"]),
]
for message in service_api_client.get_inbound_sms(service_id)["data"]
]
).as_csv_data,
mimetype="text/csv",
headers={
"Content-Disposition": 'inline; filename="Received text messages {}.csv"'.format(
format_date_numeric(datetime.utcnow().isoformat())
)
},
)
def get_inbox_partials(service_id):
page = int(request.args.get("page", 1))
inbound_messages_data = service_api_client.get_most_recent_inbound_sms(
service_id, page=page
)
inbound_messages = inbound_messages_data["data"]
if not inbound_messages:
inbound_number = current_service.inbound_number
else:
inbound_number = None
prev_page = None
if page > 1:
prev_page = generate_previous_dict("main.inbox", service_id, page)
next_page = None
if inbound_messages_data["has_next"]:
next_page = generate_next_dict("main.inbox", service_id, page)
return {
"messages": render_template(
"views/dashboard/_inbox_messages.html",
messages=inbound_messages,
inbound_number=inbound_number,
prev_page=prev_page,
next_page=next_page,
)
}
def filter_out_cancelled_stats(template_statistics):
return [s for s in template_statistics if s["status"] != "cancelled"]
@@ -377,71 +266,6 @@ def aggregate_template_usage(template_statistics, sort_key="count"):
return sorted(templates, key=lambda x: x[sort_key], reverse=True)
def aggregate_notifications_stats(template_statistics):
template_statistics = filter_out_cancelled_stats(template_statistics)
notifications = {
template_type: {status: 0 for status in ("requested", "delivered", "failed")}
for template_type in ["sms", "email"]
}
for stat in template_statistics:
notifications[stat["template_type"]]["requested"] += stat["count"]
if stat["status"] in DELIVERED_STATUSES:
notifications[stat["template_type"]]["delivered"] += stat["count"]
elif stat["status"] in FAILURE_STATUSES:
notifications[stat["template_type"]]["failed"] += stat["count"]
return notifications
def get_dashboard_partials(service_id):
all_statistics = template_statistics_client.get_template_statistics_for_service(
service_id, limit_days=7
)
template_statistics = aggregate_template_usage(all_statistics)
stats = aggregate_notifications_stats(all_statistics)
dashboard_totals = (get_dashboard_totals(stats),)
free_sms_allowance = billing_api_client.get_free_sms_fragment_limit_for_year(
current_service.id,
)
# These 2 calls will update the dashboard sms allowance count while in trial mode.
billing_api_client.get_monthly_usage_for_service(
service_id, get_current_financial_year()
)
billing_api_client.create_or_update_free_sms_fragment_limit(
service_id, free_sms_fragment_limit=free_sms_allowance
)
yearly_usage = billing_api_client.get_annual_usage_for_service(
service_id,
get_current_financial_year(),
)
return {
"upcoming": render_template(
"views/dashboard/_upcoming.html",
),
"inbox": render_template(
"views/dashboard/_inbox.html",
),
"totals": render_template(
"views/dashboard/_totals.html",
service_id=service_id,
statistics=dashboard_totals[0],
),
"template-statistics": render_template(
"views/dashboard/template-statistics.html",
template_statistics=template_statistics,
most_used_template_count=max(
[row["count"] for row in template_statistics] or [0]
),
),
"usage": render_template(
"views/dashboard/_usage.html",
**get_annual_usage_breakdown(yearly_usage, free_sms_allowance),
),
}
def get_dashboard_totals(statistics):
for msg_type in statistics.values():

View File

@@ -1,17 +0,0 @@
<div class="ajax-block">
{% if current_service.inbound_sms_summary != None %}
<a id="total-received" class="usa-link banner-dashboard" class="banner-dashboard" href="{{ url_for('.inbox', service_id=current_service.id) }}">
<span class="banner-dashboard-count">
{{ current_service.inbound_sms_summary.count|format_thousands }}
</span>
<span class="banner-dashboard-count-label">
{{ current_service.inbound_sms_summary.count|message_count_label('sms', suffix='received') }}
</span>
{% if current_service.inbound_sms_summary.most_recent %}
<span class="banner-dashboard-meta">
latest message {{ current_service.inbound_sms_summary.most_recent | format_delta }}
</span>
{% endif %}
</a>
{% endif %}
</div>

View File

@@ -1,38 +0,0 @@
{% from "components/table.html" import list_table, field, hidden_field_heading, right_aligned_field_heading, row_heading %}
{% from "components/previous-next-navigation.html" import previous_next_navigation %}
<div class="ajax-block-container">
{% if messages %}
<p class="bottom-gutter-2-3 top-gutter-1-2">
<a href="{{ url_for('.inbox_download', service_id=current_service.id) }}" download class="usa-link bold">Download these messages</a>
</p>
{% endif %}
{% call(item, row_number) list_table(
messages,
caption="Inbox",
caption_visible=False,
empty_message='When users text your services phone number ({}) youll see the messages here'.format(inbound_number),
field_headings=[
'From',
'First two lines of message'
],
field_headings_visible=False
) %}
{% call field() %}
<a
class="usa-link file-list-filename"
href="{{ url_for('.conversation', service_id=current_service.id, notification_id=item.id) }}#n{{ item.id }}"
>
{{ item.user_number | format_phone_number_human_readable }}
</a>
<span class="file-list-hint">{{ item.content }}</span>
{% endcall %}
{% call field(align='right') %}
<span class="align-with-message-body">
{{ item.created_at | format_delta }}
</span>
{% endcall %}
{% endcall %}
{{ previous_next_navigation(prev_page, next_page) }}
</div>

View File

@@ -1,25 +0,0 @@
<div class="ajax-block-container">
<div class="grid-row grid-gap">
<div id="total-sms" class="grid-col-12 margin-top-2">
<span class="big-number-with-status display-block margin-bottom-2">
<p>
<span class="big-number-smaller">
<span class="big-number-number">
{% if statistics['sms']['requested'] is number %}
{{ "{:,}".format(statistics['sms']['requested']) }}
{% else %}
{{ statistics['sms']['requested'] }}
{% endif %}
</span>
<span class="big-number-label">{{ statistics['sms']['requested']|message_count_label('sms', suffix='sent') }} in the last seven days</span>
</span>
</p>
<a class="usa-button usa-button--outline" href="{{ url_for('.view_notifications', service_id=service_id, message_type='sms', status='sending,delivered,failed') }}">
Details
</a>
{# Removing the failures area for now, as the user can click on the above link to see all the details.
In the future state of the dashboard, the all statuses will be more apparent with data visualizations #}
</span>
</div>
</div>
</div>

View File

@@ -19,13 +19,14 @@
{% if current_user.has_permissions('manage_templates') and not current_service.all_templates %}
{% include 'views/dashboard/write-first-messages.html' %}
{% endif %}
<!-- this could use some kind of update once the messages are sent and no more scheduled jobs but not every second call or anything -->
{{ ajax_block(partials, updates_url, 'upcoming') }}
<!-- {{ ajax_block(partials, updates_url, 'upcoming') }} -->
{% include 'views/dashboard/_upcoming.html' %}
<h2 class="font-body-2xl line-height-sans-2 margin-top-0">{{ current_service.name }} Dashboard</h2>
{{ ajax_block(partials, updates_url, 'inbox') }}
<div id="totalMessageChartContainer" data-messages-sent="{{ messages_sent }}" data-messages-remaining="{{ messages_remaining }}">
<h2 id="chartTitle">Total messages</h2>
<svg id="totalMessageChart"></svg>
@@ -37,7 +38,10 @@
{% if current_user.has_permissions('manage_service') %}{% endif %}
{{ ajax_block(partials, updates_url, 'template-statistics') }}
<!-- do we actually need this pulling every minute? -->
<!-- {{ ajax_block(partials, updates_url, 'template-statistics') }} -->
{% include 'views/dashboard/most-used-templates.html' %}
</div>
{% endblock %}

View File

@@ -1,25 +0,0 @@
{% extends "withnav_template.html" %}
{% from "components/ajax-block.html" import ajax_block %}
{% from "components/page-header.html" import page_header %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% block service_page_title %}
Received text messages
{% endblock %}
{% block backLink %}
{{ usaBackLink({ "href": url_for('main.service_dashboard', service_id=current_service.id) }) }}
{% endblock %}
{% block maincolumn_content %}
{{ page_header('Received text messages') }}
{{ ajax_block(
partials,
updates_url,
'messages',
) }}
{% endblock %}

View File

@@ -26,6 +26,7 @@
</tr>
</thead>
<tbody>
{{template_statistics}}
{% for item in template_statistics[:8] %}
<tr class="table-row">
<td>
@@ -34,12 +35,13 @@
<td><p>{{ item.template_folder }}</p></td>
<td><p>{{ item.last_used|format_datetime_table}}</p></td>
<td><p>{{ item.created_by }}</p></td>
<td><p>{{ item.count }}</p></td>
<td><p>{{ '{:,.0f}'.format(item.count) }}</p></td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="/services/78409625-0c0a-485e-b82c-b19c8f4b1bdb/template-usage" class="usa-link show-more-no-border"><span>See templates by month</span></a>
<a
href="{{ url_for('.template_usage', service_id=current_service.id) }}" class="usa-link show-more-no-border"><span>See templates by month</span></a>
</div>
{% endif %}
</div>

View File

@@ -9,7 +9,6 @@ from flask import url_for
from freezegun import freeze_time
from app.main.views.dashboard import (
aggregate_notifications_stats,
aggregate_status_types,
aggregate_template_usage,
format_monthly_stats_to_list,
@@ -330,287 +329,6 @@ def test_inbound_messages_not_visible_to_service_without_permissions(
assert mock_get_inbound_sms_summary.called is False
def test_inbound_messages_shows_count_of_messages_when_there_are_messages(
client_request,
mocker,
service_one,
mock_get_service_templates_when_no_templates_exist,
mock_get_jobs,
mock_get_scheduled_job_stats,
mock_get_service_statistics,
mock_get_template_statistics,
mock_get_annual_usage_for_service,
mock_get_free_sms_fragment_limit,
mock_get_inbound_sms_summary,
):
service_one["permissions"] = ["inbound_sms"]
mocker.patch(
"app.service_api_client.get_service_message_ratio",
return_value=mock_service_message_ratio,
)
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
)
mock_get_jobs.assert_called_with(SERVICE_ONE_ID)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
banner = page.select("a.banner-dashboard")[1]
assert (
normalize_spaces(banner.text)
== "9,999 text messages received latest message just now"
)
assert banner["href"] == url_for("main.inbox", service_id=SERVICE_ONE_ID)
def test_inbound_messages_shows_count_of_messages_when_there_are_no_messages(
client_request,
mocker,
service_one,
mock_get_service_templates_when_no_templates_exist,
mock_get_jobs,
mock_get_scheduled_job_stats,
mock_get_service_statistics,
mock_get_template_statistics,
mock_get_annual_usage_for_service,
mock_get_free_sms_fragment_limit,
mock_get_inbound_sms_summary_with_no_messages,
):
service_one["permissions"] = ["inbound_sms"]
mocker.patch(
"app.service_api_client.get_service_message_ratio",
return_value=mock_service_message_ratio,
)
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
)
mock_get_jobs.assert_called_with(SERVICE_ONE_ID)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
banner = page.select("a.banner-dashboard")[1]
assert normalize_spaces(banner.text) == "0 text messages received"
assert banner["href"] == url_for("main.inbox", service_id=SERVICE_ONE_ID)
@pytest.mark.parametrize(
("index", "expected_row"),
enumerate(
[
"(202) 867-5300 message-1 1 hour ago",
"(202) 867-5300 message-2 1 hour ago",
"(202) 867-5300 message-3 1 hour ago",
"(202) 867-5302 message-4 3 hours ago",
"+33(0)1 12345678 message-5 5 hours ago",
"(202) 555-0104 message-6 7 hours ago",
"(202) 555-0104 message-7 9 hours ago",
]
),
)
def test_inbox_showing_inbound_messages(
client_request,
service_one,
mock_get_service_templates_when_no_templates_exist,
mock_get_service_statistics,
mock_get_template_statistics,
mock_get_annual_usage_for_service,
mock_get_most_recent_inbound_sms,
index,
expected_row,
):
service_one["permissions"] = ["inbound_sms"]
page = client_request.get(
"main.inbox",
service_id=SERVICE_ONE_ID,
)
rows = page.select("tbody tr")
assert len(rows) == 8
assert normalize_spaces(rows[index].text) == expected_row
assert page.select_one("a[download]")["href"] == url_for(
"main.inbox_download",
service_id=SERVICE_ONE_ID,
)
def test_get_inbound_sms_shows_page_links(
client_request,
service_one,
mock_get_service_templates_when_no_templates_exist,
mock_get_service_statistics,
mock_get_template_statistics,
mock_get_annual_usage_for_service,
mock_get_most_recent_inbound_sms,
mock_get_inbound_number_for_service,
):
service_one["permissions"] = ["inbound_sms"]
page = client_request.get(
"main.inbox",
service_id=SERVICE_ONE_ID,
page=2,
)
assert "Next page" in page.find("li", {"class": "next-page"}).text
assert "Previous page" in page.find("li", {"class": "previous-page"}).text
def test_empty_inbox(
client_request,
service_one,
mock_get_service_templates_when_no_templates_exist,
mock_get_service_statistics,
mock_get_template_statistics,
mock_get_annual_usage_for_service,
mock_get_most_recent_inbound_sms_with_no_messages,
mock_get_inbound_number_for_service,
):
service_one["permissions"] = ["inbound_sms"]
page = client_request.get(
"main.inbox",
service_id=SERVICE_ONE_ID,
)
assert normalize_spaces(page.select("tbody tr")) == (
"When users text your services phone number (2028675301) youll see the messages here"
)
assert not page.select("a[download]")
assert not page.select("li.next-page")
assert not page.select("li.previous-page")
@pytest.mark.parametrize(
"endpoint",
[
"main.inbox",
"main.inbox_updates",
],
)
def test_inbox_not_accessible_to_service_without_permissions(
client_request,
service_one,
endpoint,
):
service_one["permissions"] = []
client_request.get(
endpoint,
service_id=SERVICE_ONE_ID,
_expected_status=403,
)
def test_anyone_can_see_inbox(
client_request,
api_user_active,
service_one,
mocker,
mock_get_most_recent_inbound_sms_with_no_messages,
mock_get_inbound_number_for_service,
):
service_one["permissions"] = ["inbound_sms"]
validate_route_permission_with_client(
mocker,
client_request,
"GET",
200,
url_for("main.inbox", service_id=service_one["id"]),
["view_activity"],
api_user_active,
service_one,
)
def test_view_inbox_updates(
client_request,
service_one,
mocker,
mock_get_most_recent_inbound_sms_with_no_messages,
):
service_one["permissions"] += ["inbound_sms"]
mock_get_partials = mocker.patch(
"app.main.views.dashboard.get_inbox_partials",
return_value={"messages": "foo"},
)
response = client_request.get_response(
"main.inbox_updates",
service_id=SERVICE_ONE_ID,
)
assert json.loads(response.get_data(as_text=True)) == {"messages": "foo"}
mock_get_partials.assert_called_once_with(SERVICE_ONE_ID)
@freeze_time("2016-07-01 16:00")
def test_download_inbox(
client_request,
mock_get_inbound_sms,
):
response = client_request.get_response(
"main.inbox_download",
service_id=SERVICE_ONE_ID,
)
assert response.headers["Content-Type"] == ("text/csv; " "charset=utf-8")
assert response.headers["Content-Disposition"] == (
"inline; " 'filename="Received text messages 07-01-2016.csv"'
)
assert response.get_data(as_text=True) == (
"Phone number,Message,Received\r\n"
"(202) 867-5300,message-1,07-01-2016 11:00 America/New_York\r\n"
"(202) 867-5300,message-2,07-01-2016 10:59 America/New_York\r\n"
"(202) 867-5300,message-3,07-01-2016 10:59 America/New_York\r\n"
"(202) 867-5302,message-4,07-01-2016 08:59 America/New_York\r\n"
"+33(0)1 12345678,message-5,07-01-2016 06:59 America/New_York\r\n"
"(202) 555-0104,message-6,07-01-2016 04:59 America/New_York\r\n"
"(202) 555-0104,message-7,07-01-2016 02:59 America/New_York\r\n"
"+68212345,message-8,07-01-2016 02:59 America/New_York\r\n"
)
@freeze_time("2016-07-01 13:00")
@pytest.mark.parametrize(
("message_content", "expected_cell"),
[
("=2+5", "2+5"),
("==2+5", "2+5"),
("-2+5", "2+5"),
("+2+5", "2+5"),
("@2+5", "2+5"),
("looks safe,=2+5", '"looks safe,=2+5"'),
],
)
def test_download_inbox_strips_formulae(
mocker,
client_request,
fake_uuid,
message_content,
expected_cell,
):
mocker.patch(
"app.service_api_client.get_inbound_sms",
return_value={
"has_next": False,
"data": [
{
"user_number": "elevenchars",
"notify_number": "foo",
"content": message_content,
"created_at": datetime.utcnow().isoformat(),
"id": fake_uuid,
}
],
},
)
response = client_request.get_response(
"main.inbox_download",
service_id=SERVICE_ONE_ID,
)
assert expected_cell in response.get_data(as_text=True).split("\r\n")[1]
def test_should_show_recent_templates_on_dashboard(
client_request,
mocker,
@@ -697,26 +415,6 @@ def test_should_not_show_recent_templates_on_dashboard_if_only_one_template_used
assert expected_count == 50, f"Expected count to be 50, but got {expected_count}"
@freeze_time("2016-07-01 12:00") # 4 months into 2016 financial year
@pytest.mark.parametrize(
"extra_args",
[
{},
{"year": "2016"},
],
)
def test_should_show_redirect_from_template_history(
client_request,
extra_args,
):
client_request.get(
"main.template_history",
service_id=SERVICE_ONE_ID,
_expected_status=301,
**extra_args,
)
@freeze_time("2017-01-01 12:00")
@pytest.mark.parametrize(
"extra_args",
@@ -761,29 +459,9 @@ def test_should_show_monthly_breakdown_of_template_usage(
)
def test_anyone_can_see_monthly_breakdown(
client_request,
api_user_active,
service_one,
mocker,
mock_get_monthly_notification_stats,
):
validate_route_permission_with_client(
mocker,
client_request,
"GET",
200,
url_for("main.monthly", service_id=service_one["id"]),
["view_activity"],
api_user_active,
service_one,
)
@pytest.mark.parametrize(
"endpoint",
[
"main.monthly",
"main.template_usage",
],
)
@@ -806,16 +484,6 @@ def test_stats_pages_show_last_3_years(
)
def test_monthly_has_equal_length_tables(
client_request,
service_one,
mock_get_monthly_notification_stats,
):
page = client_request.get("main.monthly", service_id=service_one["id"])
assert page.select_one(".table-field-headings th").get("width") == "33%"
@freeze_time("2016-01-01 11:09:00.061258")
def test_should_show_upcoming_jobs_on_dashboard(
mocker,
@@ -1463,14 +1131,6 @@ def test_aggregate_template_stats():
assert expected[1]["template_type"] == "sms"
def test_aggregate_notifications_stats():
expected = aggregate_notifications_stats(copy.deepcopy(stub_template_stats))
assert expected == {
"sms": {"requested": 100, "delivered": 50, "failed": 0},
"email": {"requested": 200, "delivered": 0, "failed": 100},
}
def test_get_dashboard_totals_adds_percentages():
stats = {
"sms": {"requested": 3, "delivered": 0, "failed": 2},
@@ -1859,22 +1519,22 @@ def test_service_dashboard_shows_batched_jobs(
def test_simple_local_conversion(mock_dt):
stats_utc = {
"2025-02-24T15:00:00Z": {
"sms": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
"sms": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
},
"2025-02-25T07:00:00Z": {
"sms": {"delivered": 2, "failure": 0, "pending": 0, "requested": 2},
"sms": {"delivered": 2, "failure": 0, "pending": 0, "requested": 2},
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
},
}
# Mock today's date in local time: 2025-02-25 at 10:00
mock_dt.now.return_value = datetime(2025, 2, 25, 10, 0, 0, tzinfo=ZoneInfo("America/New_York"))
mock_dt.now.return_value = datetime(
2025, 2, 25, 10, 0, 0, tzinfo=ZoneInfo("America/New_York")
)
local_stats = get_local_daily_stats_for_last_x_days(
stats_utc,
"America/New_York",
days=2
stats_utc, "America/New_York", days=2
)
assert len(local_stats) == 2
@@ -1909,7 +1569,7 @@ def test_no_timestamps_returns_zeroed_days():
def test_timestamp_in_future_time_zone():
stats_utc = {
"2025-02-25T01:00:00Z": {
"sms": {"delivered": 5, "failure": 0, "pending": 0, "requested": 5},
"sms": {"delivered": 5, "failure": 0, "pending": 0, "requested": 5},
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
}
}
@@ -1931,15 +1591,15 @@ def test_timestamp_in_future_time_zone():
def test_many_timestamps_one_local_day():
stats_utc = {
"2025-02-24T05:00:00Z": {
"sms": {"delivered": 2, "failure": 1, "pending": 0, "requested": 3},
"sms": {"delivered": 2, "failure": 1, "pending": 0, "requested": 3},
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
},
"2025-02-24T09:30:00Z": {
"sms": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
"sms": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
"email": {"delivered": 2, "failure": 0, "pending": 0, "requested": 2},
},
"2025-02-24T16:59:59Z": {
"sms": {"delivered": 4, "failure": 0, "pending": 0, "requested": 4},
"sms": {"delivered": 4, "failure": 0, "pending": 0, "requested": 4},
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
},
}
@@ -1965,18 +1625,22 @@ def test_local_conversion_phoenix():
"""Test aggregator logic in Mountain Time, no DST (America/Phoenix)."""
stats_utc = {
"2025-02-25T01:00:00Z": {
"sms": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
"sms": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
},
"2025-02-25T12:00:00Z": {
"sms": {"delivered": 2, "failure": 0, "pending": 0, "requested": 2},
"sms": {"delivered": 2, "failure": 0, "pending": 0, "requested": 2},
"email": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
},
}
with patch("app.main.views.dashboard.datetime", wraps=datetime) as mock_dt:
mock_dt.now.return_value = datetime(2025, 2, 25, 12, 0, 0, tzinfo=ZoneInfo("America/Phoenix"))
local_stats = get_local_daily_stats_for_last_x_days(stats_utc, "America/Phoenix", days=1)
mock_dt.now.return_value = datetime(
2025, 2, 25, 12, 0, 0, tzinfo=ZoneInfo("America/Phoenix")
)
local_stats = get_local_daily_stats_for_last_x_days(
stats_utc, "America/Phoenix", days=1
)
assert len(local_stats) == 1
(day_key,) = local_stats.keys()
@@ -1988,18 +1652,22 @@ def test_local_conversion_honolulu():
"""Test aggregator logic in Hawaii (Pacific/Honolulu)."""
stats_utc = {
"2025-02-25T03:00:00Z": {
"sms": {"delivered": 2, "failure": 0, "pending": 0, "requested": 2},
"sms": {"delivered": 2, "failure": 0, "pending": 0, "requested": 2},
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
},
"2025-02-25T21:00:00Z": {
"sms": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
"sms": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
"email": {"delivered": 2, "failure": 0, "pending": 0, "requested": 2},
},
}
with patch("app.main.views.dashboard.datetime", wraps=datetime) as mock_dt:
mock_dt.now.return_value = datetime(2025, 2, 25, 12, 0, 0, tzinfo=ZoneInfo("Pacific/Honolulu"))
local_stats = get_local_daily_stats_for_last_x_days(stats_utc, "Pacific/Honolulu", days=1)
mock_dt.now.return_value = datetime(
2025, 2, 25, 12, 0, 0, tzinfo=ZoneInfo("Pacific/Honolulu")
)
local_stats = get_local_daily_stats_for_last_x_days(
stats_utc, "Pacific/Honolulu", days=1
)
assert len(local_stats) == 1
(day_key,) = local_stats.keys()