mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-05-16 23:09:14 -04:00
remove dead code from dashboard
This commit is contained in:
@@ -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():
|
||||
|
||||
@@ -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>
|
||||
@@ -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 service’s phone number ({}) you’ll 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>
|
||||
@@ -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>
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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>
|
||||
@@ -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 service’s phone number (2028675301) you’ll 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()
|
||||
|
||||
Reference in New Issue
Block a user