Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/add daily usage widget #1658

Merged
merged 23 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ff5fb92
chore: add missing tailwind colors
andrewleith Sep 7, 2023
3e3a32e
style(daily limits widget): add new css rules for component
andrewleith Sep 7, 2023
3c66b85
feat(daily limits widget): add new component
andrewleith Sep 7, 2023
796a3d4
feat(daily limits widget): add new components to dashboard
andrewleith Sep 7, 2023
a4e2c7e
chore: formatting
andrewleith Sep 7, 2023
8ed11f0
tests(daily limits widget): add tests for under 80%, at 80% and at 10…
andrewleith Sep 7, 2023
b64e9b5
chore(tests): fix a test whose purpose is unclear
andrewleith Sep 7, 2023
fb85240
fix(daily limits widget): re-work a11y message a bit to make it read …
andrewleith Sep 11, 2023
8844802
fix(daily limits widget): remove testing content; update translations
andrewleith Sep 11, 2023
fb13cb7
test(daily limits widget): fix up a11y part of the test due to re-factor
andrewleith Sep 11, 2023
266a757
Merge branch 'main' into feat/add-daily-usage-widget
andrewleith Sep 11, 2023
ca61e92
fix(daily limits widget): update heading content
andrewleith Sep 12, 2023
7f58628
fix: comment out widget for now
andrewleith Sep 12, 2023
c378ba4
feat(daily limits): add french translations
andrewleith Sep 13, 2023
576f1e8
feat(daily limits/sms): update SMS over character limit error
andrewleith Sep 13, 2023
96e1cc9
fat(daily limit/sms): update sms template content textarea hint
andrewleith Sep 13, 2023
145f0c6
Merge branch 'main' into feat/add-daily-usage-widget
andrewleith Sep 13, 2023
ea0cb7d
feat(daily limits): unhide new widgets
andrewleith Sep 13, 2023
51003d5
test(daily limits): fix tests since FF was removed in main
andrewleith Sep 13, 2023
018ab39
chore: formatting / refactor
andrewleith Sep 14, 2023
a4fe676
test(sms error message): update test with new message
andrewleith Sep 14, 2023
9ba8c98
fix(a11y): fix unrelated html validation issue where uuids are used a…
andrewleith Sep 14, 2023
cb24831
a11y(templates): span cannot contain a div
andrewleith Sep 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/assets/stylesheets/index.css

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions app/assets/stylesheets/tailwind/components/remaining-messages.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@layer components {
/*! purgecss start ignore */

.remaining-messages {
@apply bg-blue text-white p-gutterHalf;
}

.rm-header {
@apply text-smaller inline-block;
}

.rm-used {
@apply .text-titlelarge pb-4 font-bold leading-none;
}

.rm-total {
float: right;
}

.rm-bar {
box-shadow: inset 0 -10px #cfd5dd;
display: flex;
align-items: flex-end;
}

.rm-bar-usage {
width: max(var(--usage), 4px);
height: 30px;
background: currentColor;
}

/*! purgecss end ignore */
}
1 change: 1 addition & 0 deletions app/assets/stylesheets/tailwind/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
@import "./components/task-list.css";
@import "./components/back-link.css";
@import "./components/preview-fullpage-border.css";
@import "./components/remaining-messages.css";

/* views */
@import "./views/dashboard.css";
Expand Down
17 changes: 6 additions & 11 deletions app/main/views/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@
from flask_login import current_user
from markupsafe import Markup
from notifications_python_client.errors import HTTPError
from notifications_utils import (
EMAIL_CHAR_COUNT_LIMIT,
SMS_CHAR_COUNT_LIMIT,
TEMPLATE_NAME_CHAR_COUNT_LIMIT,
)
from notifications_utils import TEMPLATE_NAME_CHAR_COUNT_LIMIT
from notifications_utils.formatters import nl2br
from notifications_utils.recipients import first_column_headings

Expand Down Expand Up @@ -109,9 +105,8 @@ def delete_preview_data(service_id, template_id=None):
redis_client.delete(key)


def get_char_limit_error_msg(template_type):
CHAR_LIMIT = SMS_CHAR_COUNT_LIMIT if template_type == "sms" else EMAIL_CHAR_COUNT_LIMIT
return (_("Message must be less than {char_limit} characters")).format(char_limit=CHAR_LIMIT + 1)
def get_char_limit_error_msg():
return _("Too many characters")


@main.route("/services/<service_id>/templates/<uuid:template_id>")
Expand Down Expand Up @@ -192,7 +187,7 @@ def preview_template(service_id, template_id=None):
except HTTPError as e:
if e.status_code == 400:
if "content" in e.message and any(["character count greater than" in x for x in e.message["content"]]):
error_message = get_char_limit_error_msg(template["template_type"])
error_message = get_char_limit_error_msg()
flash(error_message)
elif "name" in e.message and any(["Template name must be less than" in x for x in e.message["name"]]):
error_message = (_("Template name must be less than {char_limit} characters")).format(
Expand Down Expand Up @@ -758,7 +753,7 @@ def add_service_template(service_id, template_type, template_folder_id=None):
and "content" in e.message
and any(["character count greater than" in x for x in e.message["content"]])
):
error_message = get_char_limit_error_msg(template_type)
error_message = get_char_limit_error_msg()
form.template_content.errors.extend([error_message])
elif "name" in e.message and any(["Template name must be less than" in x for x in e.message["name"]]):
error_message = (_("Template name must be less than {char_limit} characters")).format(
Expand Down Expand Up @@ -886,7 +881,7 @@ def edit_service_template(service_id, template_id):
except HTTPError as e:
if e.status_code == 400:
if "content" in e.message and any(["character count greater than" in x for x in e.message["content"]]):
error_message = get_char_limit_error_msg(template["template_type"])
error_message = get_char_limit_error_msg()
form.template_content.errors.extend([error_message])
elif "name" in e.message and any(["Template name must be less than" in x for x in e.message["name"]]):
error_message = (_("Template name must be less than {char_limit} characters")).format(
Expand Down
20 changes: 20 additions & 0 deletions app/templates/components/remaining-messages.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{% macro remaining_messages(header, total, used) %}
{% set near_limit = ((used/total) >= 0.8) %}
<div class="remaining-messages">
<div class="rm-header mb-2">{{ header }}</div>
andrewleith marked this conversation as resolved.
Show resolved Hide resolved
<div class="rm-message mb-2">
<span class="rm-used">{{ "{:,}".format(used) if session['userlang'] == 'en' else "{:,}".format(used).replace(',', ' ') }}</span>
<span class="rm-total">{{ _('of') }} {{ "{:,}".format(total) if session['userlang'] == 'en' else "{:,}".format(total).replace(',', ' ') }}</span>
<span class="visually-hidden">{{ header }}</span>
{% if near_limit %}
<span class="visually-hidden"> - {{ _('You are nearing the daily {} limit').format(header.lower()) }}</span>
{% endif %}
</div>

<div class="rm-bar">
<div class="rm-bar-usage {{ 'text-red-300' if near_limit else 'text-green-300' }}" style="--usage: {{ (used/total)*100 }}%;">

</div>
</div>
</div>
{% endmacro %}
39 changes: 15 additions & 24 deletions app/templates/views/dashboard/_totals_daily.html
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
{% from "components/big-number.html" import big_number %}
{% from "components/message-count-label.html" import message_count_label %}

{% set suffix = _("left today") %}
{% if current_lang == "fr" %}
{% set suffix_plural = _("many left today") %}
{% else %}
{% set suffix_plural = _("left today") %}
{% endif %}
{% set email_remaining = current_service.message_limit - statistics['email']['requested'] %}
{% set sms_parts_remaining = current_service.sms_daily_limit - statistics['sms']['requested'] %}
{% from 'components/remaining-messages.html' import remaining_messages %}

<div class="ajax-block-container">
<div class="grid-row contain-floats">
<div id="total-email" class="{{column_width}}">
{{ big_number(
email_remaining,
message_count_label(email_remaining, 'email', suffix=suffix, suffix_plural=suffix_plural),
status=True,
) }}
</div>
<div id="total-sms" class="{{column_width}}">
{{ big_number(
sms_parts_remaining,
message_count_label(sms_parts_remaining, 'sms', suffix=suffix, suffix_plural=suffix_plural),
status=True,
) }}
<h2 class="heading-medium mt-8">
{{ _('Daily usage') }}
<br />
<small class="text-gray-600 text-small font-normal" style="color: #5E6975">
{{ _('Message limits reset each night at 7pm Eastern Time') }}
</small>
</h2>
<div class="grid-row contain-floats">
<div class="{{column_width}}">
{{ remaining_messages(header=_('emails'), total=current_service.message_limit, used=statistics['email']['requested']) }}
</div>
<div class="{{column_width}}">
{{ remaining_messages(header=_('text messages'), total=current_service.sms_daily_limit, used=statistics['sms']['requested']) }}
</div>
</div>
</div>
</div>
3 changes: 3 additions & 0 deletions app/templates/views/dashboard/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ <h2 class="heading-medium mt-8">{{ _("Scheduled sends") }}</h2>
{% endif %}

{{ ajax_block(partials, updates_url, 'weekly_totals', interval=5) }}
{{ ajax_block(partials, updates_url, 'daily_totals', interval=5) }}
andrewleith marked this conversation as resolved.
Show resolved Hide resolved

<hr />

{% if partials['has_template_statistics'] %}
<h2 class="heading-medium mt-8">{{ _("Templates used") }}</h2>
Expand Down
4 changes: 2 additions & 2 deletions app/templates/views/dashboard/template-statistics.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
{{ spark_bar_field(item.count, most_used_template_count, id=item.template_id) }}
{% else %}
{% call field() %}
<span id='{{item.template_id}}' class="heading-small">
<div id='a{{item.template_id}}' class="heading-small">
{{ big_number(
item.count,
smallest=True
) }}
</span>
</div>
{% endcall %}
{% endif %}
{% endcall %}
Expand Down
2 changes: 1 addition & 1 deletion app/templates/views/edit-sms-template.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
{{ textbox(form.name, width='w-full', hint=_('This will not show in the message. Choose a name that helps you find the template when you need to reuse it.')) }}
</div>
<div class="md:w-2/3 px-gutterHalf">
{{ textbox(form.template_content, highlight_tags=True, width='w-full', rows=5) }}
{{ textbox(form.template_content, highlight_tags=True, width='w-full', rows=5, hint=_('Maximum 612 characters. Some messages may be too long due to custom content.')) }}
{% if current_user.platform_admin %}
{{ radios(form.process_type, hint=_('This is only manageable by platform admins')) }}
{% endif %}
Expand Down
7 changes: 7 additions & 0 deletions app/translations/csv/fr.csv
Original file line number Diff line number Diff line change
Expand Up @@ -1753,3 +1753,10 @@
"You cannot send this text message today","Vous ne pouvez pas envoyer ce message texte aujourd’hui."
"You cannot send all these email messages today","Vous ne pouvez pas envoyer tous ces courriels aujourd’hui."
"You cannot send all these text messages today","Vous ne pouvez pas envoyer tous ces messages texte aujourd’hui."
"of","de"
"Sent since 7 pm Eastern Time","Envoyé depuis 19 h, heure de l'Est"
"You are nearing the daily {} limit","Vous approchez de la limite quotidienne de {}"
"Daily usage","Utilisation quotidienne"
"Message limits reset each night at 7pm Eastern Time","Les limites d’envoi sont réinitialisées chaque soir à 19 h, heure de l’Est"
"Maximum 612 characters. Some messages may be too long due to custom content.","612 caractères au maximum. Certains messages peuvent être trop longs en raison de leur contenu personnalisé."
"Too many characters","Limite de caractère atteinte"
2 changes: 2 additions & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ module.exports = {
hover: "#990c1a",
border: "#6a0812",
mellow: "#df3034",
300: "#D74D42",
},
white: {
default: "#FFF",
Expand Down Expand Up @@ -100,6 +101,7 @@ module.exports = {
hover: "#00692f",
border: "#003618",
green: "#006435",
300: "#29A35A",
},
black: {
default: "#000",
Expand Down
122 changes: 120 additions & 2 deletions tests/app/main/views/test_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,8 @@ def test_should_show_upcoming_jobs_on_dashboard(
@pytest.mark.parametrize(
"permissions, column_name, expected_column_count",
[
(["email", "sms"], ".w-1\\/2", 4),
(["email", "sms"], ".w-1\\/2", 4),
(["email", "sms"], ".w-1\\/2", 6),
(["email", "sms"], ".w-1\\/2", 6),
],
)
def test_correct_columns_display_on_dashboard_v15(
Expand Down Expand Up @@ -1294,3 +1294,121 @@ def test_a11y_template_usage_should_not_contain_duplicate_ids(
list.append(element["id"])

assert len(list) == len(set(list)) # check for duplicates


@pytest.mark.parametrize(
"totals, notification_type, expected_color, expected_sent, expected_limit, expect_accessible_message",
[
# With a service email limit of 1000, and 100 emails sent in the past 24 hours
(
{
"email": {"requested": 100, "delivered": 0, "failed": 0},
"sms": {"requested": 0, "delivered": 0, "failed": 0},
},
"email",
"text-green-300",
"100",
"1,000",
False,
),
# With a service SMS limit of 1000, and 10 sms sent in the past 24 hours
(
{
"email": {"requested": 0, "delivered": 0, "failed": 0},
"sms": {"requested": 10, "delivered": 0, "failed": 0},
},
"sms",
"text-green-300",
"10",
"1,000",
False,
),
# With a service email limit of 1000, and 800 emails sent in the past 24 hours
(
{
"email": {"requested": 800, "delivered": 0, "failed": 0},
"sms": {"requested": 0, "delivered": 0, "failed": 0},
},
"email",
"text-red-300",
"800",
"1,000",
True,
),
# With a service SMS limit of 1000, and 900 sms sent in the past 24 hours
(
{
"email": {"requested": 0, "delivered": 0, "failed": 0},
"sms": {"requested": 900, "delivered": 0, "failed": 0},
},
"sms",
"text-red-300",
"900",
"1,000",
True,
),
# With a service email limit of 1000, and 1000 emails sent in the past 24 hours
(
{
"email": {"requested": 1000, "delivered": 0, "failed": 0},
"sms": {"requested": 0, "delivered": 0, "failed": 0},
},
"email",
"text-red-300",
"1,000",
"1,000",
True,
),
# With a service SMS limit of 1000, and 1000 sms sent in the past 24 hours
(
{
"email": {"requested": 0, "delivered": 0, "failed": 0},
"sms": {"requested": 1000, "delivered": 0, "failed": 0},
},
"sms",
"text-red-300",
"1,000",
"1,000",
True,
),
],
)
def test_dashboard_daily_limits(
client_request,
mocker,
mock_get_service_templates,
mock_get_template_statistics,
mock_get_service_statistics,
mock_get_jobs,
service_one,
totals,
notification_type,
expected_color,
expected_sent,
expected_limit,
expect_accessible_message,
app_,
):
service_one["permissions"] = (["email", "sms"],)

mocker.patch("app.main.views.dashboard.get_dashboard_totals", return_value=totals)

page = client_request.get("main.service_dashboard", service_id=service_one["id"])

component_index = 0 if notification_type == "email" else 1

assert page.find_all(class_="remaining-messages")[component_index].find(class_="rm-used").text == expected_sent
assert page.find_all(class_="remaining-messages")[component_index].find(class_="rm-total").text[3:] == expected_limit
assert (
expected_color in page.find_all(class_="remaining-messages")[component_index].find(class_="rm-bar-usage").attrs["class"]
)

if expect_accessible_message:
assert (
len(
page.find_all(class_="remaining-messages")[component_index]
.find(class_="rm-message")
.find_all(class_="visually-hidden")
)
== 2
)
4 changes: 2 additions & 2 deletions tests/app/main/views/test_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -1589,7 +1589,7 @@ def test_should_not_create_too_big_template(
},
_expected_status=200,
)
assert "Message must be less than 613 characters" in page.text
assert "Too many characters" in page.text


def test_should_not_update_too_big_template(
Expand All @@ -1612,7 +1612,7 @@ def test_should_not_update_too_big_template(
},
_expected_status=200,
)
assert "Message must be less than 613 characters" in page.text
assert "Too many characters" in page.text


def test_should_redirect_when_saving_a_template_email(
Expand Down