{% block content %} diff --git a/app/templates/views/platform-admin/complaints.html b/app/templates/views/platform-admin/complaints.html index 378d3b876..9b4e0b10f 100644 --- a/app/templates/views/platform-admin/complaints.html +++ b/app/templates/views/platform-admin/complaints.html @@ -13,26 +13,26 @@ Email complaints +
+ {% call(item, row_number) list_table( + complaints, + caption="Complaints", + caption_visible=False, + empty_message='No complaints', + field_headings=['Notification Id', 'Service', 'Complaint type', 'Complaint Date'], + field_headings_visible=True + ) %} - {% call(item, row_number) list_table( - complaints, - caption="Complaints", - caption_visible=False, - empty_message='No complaints', - field_headings=['Notification Id', 'Service', 'Complaint type', 'Complaint Date'], - field_headings_visible=True - ) %} + {{ link_field(item.notification_id, url_for('main.view_notification', service_id=item.service_id, notification_id=item.notification_id)) }} - {{ link_field(item.notification_id, url_for('main.view_notification', service_id=item.service_id, notification_id=item.notification_id)) }} + {{ link_field(item.service_name, url_for('main.service_dashboard', service_id=item.service_id)) }} - {{ link_field(item.service_name, url_for('main.service_dashboard', service_id=item.service_id)) }} + {{ text_field(item.complaint_type) }} - {{ text_field(item.complaint_type) }} - - {{ text_field(item.complaint_date|format_datetime_table if item.complaint_date else None) }} - - {% endcall %} + {{ text_field(item.complaint_date|format_datetime_table if item.complaint_date else None) }} + {% endcall %} +
{{ previous_next_navigation(prev_page, next_page) }} {% endblock %} diff --git a/app/templates/views/platform-admin/services.html b/app/templates/views/platform-admin/services.html index d56e3390e..10acbce31 100644 --- a/app/templates/views/platform-admin/services.html +++ b/app/templates/views/platform-admin/services.html @@ -7,6 +7,7 @@ {% from "components/components/details/macro.njk" import usaDetails %} {% macro services_table(services, caption) %} +
{% call(item, row_number) mapping_table( caption=caption, caption_visible=False, @@ -48,6 +49,7 @@ {% endfor %} {% endcall %} +
{% endmacro %} diff --git a/app/templates/views/pricing/index.html b/app/templates/views/pricing/index.html index 46a7c2666..a65f363de 100644 --- a/app/templates/views/pricing/index.html +++ b/app/templates/views/pricing/index.html @@ -35,7 +35,7 @@ part.

Calculation of message parts without special characters

-
+
{% call mapping_table( caption='Text message pricing', field_headings=['Message length', 'Charge'], @@ -86,7 +86,7 @@ recipient’s preferred language, but we are aware that, rarely, a phone carrier to be calculated as detailed below.

Calculation of message parts with special characters or non-romanized scripts

{% set accentedChars %} -
+
{% call mapping_table( caption='Accented characters that affect text message charges', field_headings=['Character', 'Description'], @@ -151,7 +151,7 @@ to be calculated as detailed below.

"html": accentedChars }) }} -

+
{% call mapping_table( caption='Pricing for text messages containing accented characters that affect the charges', field_headings=['Message length', 'Charge'], diff --git a/app/templates/views/service-settings.html b/app/templates/views/service-settings.html index 1745bd528..70b40b382 100644 --- a/app/templates/views/service-settings.html +++ b/app/templates/views/service-settings.html @@ -16,7 +16,7 @@

Settings

-
+
{% call mapping_table( caption='General', diff --git a/app/templates/views/signedout.html b/app/templates/views/signedout.html index fab9fbb30..9a458a10c 100644 --- a/app/templates/views/signedout.html +++ b/app/templates/views/signedout.html @@ -20,7 +20,7 @@ Notify.gov

Reach people where they are with government-powered text messages

Notify.gov is a text message service that helps federal, state, local, tribal and territorial governments more effectively communicate with the people they serve.

-
+
if you are an existing pilot partner diff --git a/app/templates/views/usage.html b/app/templates/views/usage.html index f964982ee..03fb0a5ce 100644 --- a/app/templates/views/usage.html +++ b/app/templates/views/usage.html @@ -70,7 +70,7 @@
#} {% if months %} -
+
{% call(item, row_index) list_table( months, diff --git a/app/templates/withnav_template.html b/app/templates/withnav_template.html index ce4b5d9b3..93b7d973e 100644 --- a/app/templates/withnav_template.html +++ b/app/templates/withnav_template.html @@ -30,7 +30,7 @@ {% block orgNavBreadcrumb %}{% include "/new/components/org_nav_breadcrumb.html" %}{% endblock %} {% endif %}
-
+
{% block sideNavigation %} {% if current_org.name %} {% include "/new/components/org_nav.html" %} @@ -44,7 +44,7 @@ #} {% endblock %}
-
+
{% block beforeContent %} {% block backLink %}{% endblock %} {% endblock %} diff --git a/poetry.lock b/poetry.lock index adcd85c60..6c80bb40f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1344,9 +1344,13 @@ files = [ {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, @@ -1681,7 +1685,6 @@ files = [ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] @@ -2528,6 +2531,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, From 8461c6156b0b7d8a52e2d2323b493cca6ede8e46 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Thu, 25 Jul 2024 17:09:17 -0700 Subject: [PATCH 59/88] created testing for activity page --- .../views/activity/all-activity.html | 7 +- app/utils/pagination.py | 15 +- tests/app/main/views/test_jobs_activity.py | 179 ++++++++++++++++++ tests/app/utils/test_pagination.py | 25 ++- 4 files changed, 210 insertions(+), 16 deletions(-) create mode 100644 tests/app/main/views/test_jobs_activity.py diff --git a/app/templates/views/activity/all-activity.html b/app/templates/views/activity/all-activity.html index 0d4258f2d..7eb7cb3c5 100644 --- a/app/templates/views/activity/all-activity.html +++ b/app/templates/views/activity/all-activity.html @@ -59,13 +59,9 @@ {% endif %} {% endset %} - {% block maincolumn_content %}

All activity

- {% if current_user.has_permissions('manage_templates') and not current_service.all_templates %} - {% include 'views/dashboard/write-first-messages.html' %} - {% endif %}

All activity

Sent jobs

@@ -115,6 +111,9 @@ {% endfor %} {% else %} + + No batched job messages found (messages are kept for {{ service_data_retention_days }} days). + {% endif %} diff --git a/app/utils/pagination.py b/app/utils/pagination.py index 50e6371be..7d3f89918 100644 --- a/app/utils/pagination.py +++ b/app/utils/pagination.py @@ -34,17 +34,10 @@ def generate_previous_next_dict(view, service_id, page, title, url_args): def generate_pagination_pages(total_items, page_size, current_page): total_pages = (total_items + page_size - 1) // page_size pagination = {"current": current_page, "pages": [], "last": total_pages} - if total_pages <= 4: + if total_pages <= 9: pagination["pages"] = list(range(1, total_pages + 1)) else: - if current_page <= 3: - pagination["pages"] = [1, 2, 3, total_pages] - else: - pagination["pages"] = [ - 1, - current_page - 1, - current_page, - current_page + 1, - total_pages, - ] + start_page = max(1, min(current_page - 4, total_pages - 8)) + end_page = min(start_page + 8, total_pages) + pagination["pages"] = list(range(start_page, end_page + 1)) return pagination diff --git a/tests/app/main/views/test_jobs_activity.py b/tests/app/main/views/test_jobs_activity.py new file mode 100644 index 000000000..8cb171d94 --- /dev/null +++ b/tests/app/main/views/test_jobs_activity.py @@ -0,0 +1,179 @@ +from bs4 import BeautifulSoup + +from app.utils.pagination import get_page_from_request +from tests.conftest import SERVICE_ONE_ID + +MOCK_JOBS = { + "data": [ + { + "archived": False, + "created_at": "2024-01-04T20:43:52+00:00", + "created_by": { + "id": "mocked_user_id", + "name": "mocked_user", + }, + "id": "55b242b5-9f62-4271-aff7-039e9c320578", + "job_status": "finished", + "notification_count": 1, + "original_file_name": "mocked_file.csv", + "processing_finished": "2024-01-25T23:02:25+00:00", + "processing_started": "2024-01-25T23:02:24+00:00", + "scheduled_for": None, + "service": "21b3ee3d-1cb0-4666-bfa0-9c5ac26d3fe3", + "service_name": {"name": "Mock Texting Service"}, + "statistics": [{"count": 1, "status": "sending"}], + "template": "6a456418-498c-4c86-b0cd-9403c14a216c", + "template_name": "Mock Template Name", + "template_type": "sms", + "template_version": 3, + "updated_at": "2024-01-25T23:02:25+00:00", + } + ], + 'links': { + 'last': '/service/21b3ee3d-1cb0-4666-bfa0-9c5ac26d3fe3/job?page=3', + 'next': '/service/21b3ee3d-1cb0-4666-bfa0-9c5ac26d3fe3/job?page=3', + 'prev': '/service/21b3ee3d-1cb0-4666-bfa0-9c5ac26d3fe3/job?page=1' + }, + 'page_size': 50, + 'total': 115 +} + + +def test_all_activity( + client_request, + mocker, +): + current_page = get_page_from_request() + mock_get_page_of_jobs = mocker.patch( + "app.job_api_client.get_page_of_jobs", return_value=MOCK_JOBS + ) + + response = client_request.get_response( + "main.all_jobs_activity", + service_id=SERVICE_ONE_ID, + page=current_page, + ) + assert response.status_code == 200, "Request failed" + assert response.data is not None, "Response data is None" + + assert "All activity" in response.text + mock_get_page_of_jobs.assert_called_with(SERVICE_ONE_ID, page=current_page) + page = BeautifulSoup(response.data, 'html.parser') + table = page.find('table') + assert table is not None, "Table not found in the response" + + headers = [th.get_text(strip=True) for th in table.find_all('th')] + expected_headers = ["Job ID#", "Template", "Time sent", "Sender", "Report"] + + assert headers == expected_headers, f"Expected headers {expected_headers}, but got {headers}" + + rows = table.find('tbody').find_all('tr', class_='table-row') + assert len(rows) == 1, "Expected one job row in the table" + + job_row = rows[0] + cells = job_row.find_all('td') + assert len(cells) == 5, "Expected five columns in the job row" + + job_id_cell = cells[0].find('a').get_text(strip=True) + + assert job_id_cell == "55b242b5", f"Expected job ID '55b242b5', but got '{job_id_cell}'" + template_cell = cells[1].get_text(strip=True) + assert template_cell == "Mock Template Name", ( + f"Expected template 'Mock Template Name', but got '{template_cell}'" + ) + time_sent_cell = cells[2].get_text(strip=True) + assert time_sent_cell == "01-25-2024 at 06:02 PM", ( + f"Expected time sent '01-25-2024 at 06:02 PM', but got '{time_sent_cell}'" + ) + sender_cell = cells[3].get_text(strip=True) + assert sender_cell == "mocked_user", f"Expected sender 'mocked_user', but got '{sender_cell}'" + + report_cell = cells[4].find('span').get_text(strip=True) + assert report_cell == "N/A", f"Expected report 'N/A', but got '{report_cell}'" + + mock_get_page_of_jobs.assert_called_with(SERVICE_ONE_ID, page=current_page) + + +def test_all_activity_no_jobs( + client_request, + mocker +): + current_page = get_page_from_request() + mock_get_page_of_jobs = mocker.patch( + "app.job_api_client.get_page_of_jobs", + return_value={ + "data": [], + 'links': { + 'last': '/service/21b3ee3d-1cb0-4666-bfa0-9c5ac26d3fe3/job?page=1', + 'next': None, + 'prev': None + }, + 'page_size': 50, + 'total': 0 + } + ) + response = client_request.get_response( + "main.all_jobs_activity", + service_id=SERVICE_ONE_ID, + page=current_page, + ) + + assert response.status_code == 200, "Request failed" + + page = BeautifulSoup(response.data, 'html.parser') + + no_jobs_message_td = page.find('td', class_='table-empty-message') + assert no_jobs_message_td is not None, "No jobs message not found in the response" + + expected_message = "No batched job messages found (messages are kept for 7 days)." + actual_message = no_jobs_message_td.get_text(strip=True) + + assert expected_message == actual_message, ( + f"Expected message '{expected_message}', but got '{actual_message}'" + ) + mock_get_page_of_jobs.assert_called_with(SERVICE_ONE_ID, page=current_page) + + +def test_all_activity_pagination(client_request, mocker): + current_page = get_page_from_request() + mock_get_page_of_jobs = mocker.patch( + "app.job_api_client.get_page_of_jobs", + return_value={ + "data": [ + { + "id": f"job-{i}", + "created_at": "2024-01-25T23:02:25+00:00", + "created_by": {"name": "mocked_user"}, + "processing_finished": "2024-01-25T23:02:25+00:00", + "processing_started": "2024-01-25T23:02:24+00:00", + "template_name": "Mock Template Name", + "original_file_name": "mocked_file.csv", + "notification_count": 1 + } for i in range(1, 101) + ], + 'links': { + 'last': '/service/21b3ee3d-1cb0-4666-bfa0-9c5ac26d3fe3/job?page=2', + 'next': '/service/21b3ee3d-1cb0-4666-bfa0-9c5ac26d3fe3/job?page=2', + 'prev': None + }, + 'page_size': 50, + 'total': 100 + } + ) + + response = client_request.get_response( + "main.all_jobs_activity", + service_id=SERVICE_ONE_ID, + page=current_page, + ) + mock_get_page_of_jobs.assert_called_with(SERVICE_ONE_ID, page=current_page) + + page = BeautifulSoup(response.data, 'html.parser') + pagination_controls = page.find_all('li', class_='usa-pagination__item') + assert pagination_controls, "Pagination controls not found in the response" + + pagination_texts = [item.get_text(strip=True) for item in pagination_controls] + expected_pagination_texts = ['1', '2', 'Next'] + assert pagination_texts == expected_pagination_texts, ( + f"Expected pagination controls {expected_pagination_texts}, but got {pagination_texts}" + ) diff --git a/tests/app/utils/test_pagination.py b/tests/app/utils/test_pagination.py index 4a98a1913..a1264d8f5 100644 --- a/tests/app/utils/test_pagination.py +++ b/tests/app/utils/test_pagination.py @@ -1,4 +1,10 @@ -from app.utils.pagination import generate_next_dict, generate_previous_dict +import pytest + +from app.utils.pagination import ( + generate_next_dict, + generate_pagination_pages, + generate_previous_dict, +) def test_generate_previous_dict(client_request): @@ -20,3 +26,20 @@ def test_generate_previous_next_dict_adds_other_url_args(client_request): "main.view_notifications", "foo", 2, {"message_type": "blah"} ) assert "notifications/blah" in result["url"] + + +@pytest.mark.parametrize( + ("total_items", "page_size", "current_page", "expected"), + [ + (100, 50, 1, {"current": 1, "pages": [1, 2], "last": 2}), + (450, 50, 1, {"current": 1, "pages": [1, 2, 3, 4, 5, 6, 7, 8, 9], "last": 9}), + (500, 50, 1, {"current": 1, "pages": [1, 2, 3, 4, 5, 6, 7, 8, 9], "last": 10}), + (500, 50, 5, {"current": 5, "pages": [1, 2, 3, 4, 5, 6, 7, 8, 9], "last": 10}), + (500, 50, 6, {"current": 6, "pages": [2, 3, 4, 5, 6, 7, 8, 9, 10], "last": 10}), + (500, 50, 10, {"current": 10, "pages": [2, 3, 4, 5, 6, 7, 8, 9, 10], "last": 10}), + (950, 50, 15, {"current": 15, "pages": [11, 12, 13, 14, 15, 16, 17, 18, 19], "last": 19}), + ], +) +def test_generate_pagination_pages(total_items, page_size, current_page, expected): + result = generate_pagination_pages(total_items, page_size, current_page) + assert result == expected From 8fb019b93dadd8331de1f7e6375d96de7567a2e1 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Thu, 25 Jul 2024 17:36:13 -0700 Subject: [PATCH 60/88] added margin --- app/templates/views/invite-user.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/views/invite-user.html b/app/templates/views/invite-user.html index b4f6789b2..bfeef5db5 100644 --- a/app/templates/views/invite-user.html +++ b/app/templates/views/invite-user.html @@ -38,9 +38,9 @@ {% endcall %}

Set up user permissions

Setting the right digital control is one of many aspects to keep content and information on a need to know basis. Permissions allow you to do this. Give team members access to important views related to their responsibilities.

-

Permission

+

Permissions

-
+