diff --git a/app/assets/javascripts/activityChart.js b/app/assets/javascripts/activityChart.js index 4fa8922ec..be15a3aad 100644 --- a/app/assets/javascripts/activityChart.js +++ b/app/assets/javascripts/activityChart.js @@ -295,24 +295,28 @@ liveRegion.textContent = `Data updated for ${selectedText} - last 7 days`; const tableHeading = document.querySelector('#tableActivity h2'); - const senderColumns = document.querySelectorAll('.sender-column'); + const senderElements = [ + document.querySelector('[data-column="sender"]'), + ...document.querySelectorAll('[data-sender]') + ]; const allRows = document.querySelectorAll('#activity-table tbody tr'); - const caption = document.querySelector('#activity-table caption'); + const table = document.getElementById('activity-table'); + const caption = table.querySelector('caption'); if (selectedValue === 'individual') { tableHeading.textContent = 'My activity'; caption.textContent = `Table showing the sent jobs for ${currentUserName}`; - senderColumns.forEach(col => { - col.style.display = 'none'; + senderElements.forEach(el => { + if (el) el.style.display = 'none'; }); allRows.forEach(row => row.style.display = 'none'); const userRows = Array.from(allRows).filter(row => { - const senderCell = row.querySelector('.sender-column'); - const rowSender = senderCell ? senderCell.textContent.trim() : ''; + const senderCell = row.querySelector('[data-sender]'); + const rowSender = senderCell ? senderCell.dataset.sender : ''; return rowSender === currentUserName; }); @@ -333,8 +337,8 @@ tableHeading.textContent = 'Service activity'; caption.textContent = `Table showing the sent jobs for service`; - senderColumns.forEach(col => { - col.style.display = ''; + senderElements.forEach(el => { + if (el) el.style.display = ''; }); allRows.forEach((row, index) => { diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss index 029aaa22f..9aebabd35 100644 --- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss +++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss @@ -492,44 +492,12 @@ td.table-empty-message { .job-table { width: 100%; border-collapse: collapse; - td.file-name { - width: 25%; - overflow-wrap: anywhere; - } - td.jobid { - width: 5%; - } - td.template { - width: 25%; - } - td.time-sent { - width: 15%; - } - td.sender { - width: 20%; - overflow-wrap: break-word; - } - td.count-of-recipients { - width: 5%; - } - td.report { - width: 2%; - text-align: center; - } - td.delivered { - width: 2%; - text-align: center; - } - td.failed { - width: 2%; - text-align: center; - } - td.report img { - padding-top: 5px; - } th { padding: 0.5rem 1rem; } + td { + padding: 0.5rem 1rem; + } } @media (max-width: 768px) { diff --git a/app/main/views/activity.py b/app/main/views/activity.py index 1d6d12d98..6481a4832 100644 --- a/app/main/views/activity.py +++ b/app/main/views/activity.py @@ -56,6 +56,7 @@ def all_jobs_activity(service_id): next_page=next_page, prev_page=prev_page, pagination=pagination, + total_jobs=jobs.get("total", 0), **download_availability, download_link_one_day=url_for( ".download_notifications_csv", diff --git a/app/templates/views/activity/all-activity.html b/app/templates/views/activity/all-activity.html index 94209a38c..1db831afc 100644 --- a/app/templates/views/activity/all-activity.html +++ b/app/templates/views/activity/all-activity.html @@ -51,6 +51,11 @@ {% endif %} + {% if pagination and total_jobs %} +

+ Page {{ pagination.current }} of {{ pagination.last }} ({{ total_jobs }} total jobs) +

+ {% endif %} {% endif %} {% endset %} {% block maincolumn_content %} @@ -58,7 +63,7 @@

All activity

All activity

Sent jobs

-
+
@@ -66,23 +71,20 @@ - - - - - - @@ -90,28 +92,34 @@ {% if all_jobs_dict %} {% for job in all_jobs_dict %} - - - + - - + - - + {% endfor %} {% else %} @@ -122,7 +130,7 @@
Table showing all sent jobs for this service
Job ID# + Template + Started + Sender + Report - Delivered - - Failed + + Status
+ {{ job.job_id[:8] if job.job_id else 'Manually entered number' }} {{ job.template_name }} + {{ job.template_name }} {{ job.activity_time|format_datetime_table }} {{ job.created_by.name }} + {{ job.created_by.name }} {% if job.can_download %} - + Download report link {% else %} N/A {% endif %} {{ job.delivered_count if job.delivered_count is not none else '0' }}{{ job.failed_count if job.failed_count is not none else '0' }} + + {{ job.delivered_count if job.delivered_count is not none else '0' }} delivered + + + {{ job.failed_count if job.failed_count is not none else '0' }} failed + +
-

Note: Report data is only available for 7 days after your message has been sent

+

Note: Report data is only available for 7 days after your message has been sent

{{show_pagination}} {% if current_user.has_permissions(ServicePermission.VIEW_ACTIVITY) %} diff --git a/app/templates/views/dashboard/activity-table.html b/app/templates/views/dashboard/activity-table.html index 1dbb2bc7d..9205c5228 100644 --- a/app/templates/views/dashboard/activity-table.html +++ b/app/templates/views/dashboard/activity-table.html @@ -22,15 +22,15 @@

Service activity

- +
- - + @@ -39,13 +39,13 @@ {% if jobs %} {% for job in jobs %} - - - + - - + + {% endfor %} {% else %} diff --git a/backstop_data/bitmaps_reference/backstop_test_All_Activity_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_All_Activity_0_document_0_desktop.png new file mode 100644 index 000000000..7dc758fa1 Binary files /dev/null and b/backstop_data/bitmaps_reference/backstop_test_All_Activity_0_document_0_desktop.png differ diff --git a/gunicorn_config.py b/gunicorn_config.py index 65494fd14..345abc4f3 100644 --- a/gunicorn_config.py +++ b/gunicorn_config.py @@ -2,6 +2,7 @@ import multiprocessing import os import sys import traceback + import gunicorn # Let gunicorn figure out the right number of workers diff --git a/notifications_python_client/base.py b/notifications_python_client/base.py index 9c010118a..5b333d97e 100644 --- a/notifications_python_client/base.py +++ b/notifications_python_client/base.py @@ -1,7 +1,6 @@ import json import logging import urllib.parse - from os import getenv import requests diff --git a/tests/app/main/views/test_jobs_activity.py b/tests/app/main/views/test_jobs_activity.py index c2a6bcd3e..24e055ee0 100644 --- a/tests/app/main/views/test_jobs_activity.py +++ b/tests/app/main/views/test_jobs_activity.py @@ -77,8 +77,7 @@ def test_all_activity( "Started", "Sender", "Report", - "Delivered", - "Failed", + "Status", ] assert ( @@ -90,7 +89,7 @@ def test_all_activity( job_row = rows[0] cells = job_row.find_all("td") - assert len(cells) == 7, "Expected five columns in the job row" + assert len(cells) == 6, "Expected six columns in the job row" job_id_cell = cells[0].find("a").get_text(strip=True) @@ -113,13 +112,9 @@ def test_all_activity( report_cell = cells[4].find("span").get_text(strip=True) assert report_cell == "N/A", f"Expected report 'N/A', but got '{report_cell}'" - delivered_cell = cells[5].get_text(strip=True) - assert ( - delivered_cell == "1" - ), f"Expected delivered count '1', but got '{delivered_cell}'" - - failed_cell = cells[6].get_text(strip=True) - assert failed_cell == "5", f"Expected failed count '5', but got '{failed_cell}'" + status_cell = cells[5].get_text(strip=True) + assert "1 delivered" in status_cell, f"Expected status to contain '1 delivered', but got '{status_cell}'" + assert "5 failed" in status_cell, f"Expected status to contain '5 failed', but got '{status_cell}'" def test_all_activity_no_jobs(client_request, mocker): diff --git a/tests/javascripts/activityChart.test.js b/tests/javascripts/activityChart.test.js index 89e924dc1..ff90d3342 100644 --- a/tests/javascripts/activityChart.test.js +++ b/tests/javascripts/activityChart.test.js @@ -239,14 +239,19 @@ test('handleDropdownChange updates DOM for individual selection', () => {

Table showing the sent jobs for {{current_service.name}}
Job ID# TemplateJob statusSender + Job statusSender # of Recipients
+ {{ job.id[:8] if job.id else 'Manually entered number' }} {{ job.template_name }} + {{ job.template_name }} {% if not job.finished_processing %} {% if job.scheduled_for%} Scheduled for {{ job.scheduled_for|format_datetime_table }} @@ -58,8 +58,8 @@ Sent on {{ job.processing_started|format_datetime_table }} {% endif %} {{ job.created_by.name }}{{ job.notification_count }}{{ job.created_by.name }}{{ job.notification_count }}
+ + + + + - - - - - - - + + + + + + +
Sender
Test User
Other User
Test User
Test User
Other User
Test User
Test User
Test User
Other User
Test User
Test User
Other User
Test User
Test User
@@ -269,15 +274,18 @@ test('handleDropdownChange updates DOM for individual selection', () => { expect(document.getElementById('table-heading').textContent).toBe('My activity'); expect(document.getElementById('caption').textContent).toContain('Test User'); - document.querySelectorAll('.sender-column').forEach(col => { - expect(col.style.display).toBe('none'); + const senderHeader = document.querySelector('[data-column="sender"]'); + expect(senderHeader.style.display).toBe('none'); + + document.querySelectorAll('[data-sender]').forEach(cell => { + expect(cell.style.display).toBe('none'); }); const rows = Array.from(document.querySelectorAll('#activity-table tbody tr')); const visibleRows = rows.filter(row => row.style.display !== 'none'); expect(visibleRows.length).toBeLessThanOrEqual(5); visibleRows.forEach(row => { - const sender = row.querySelector('.sender-column').textContent.trim(); + const sender = row.querySelector('[data-sender]').dataset.sender; expect(sender).toBe('Test User'); }); @@ -295,10 +303,15 @@ test('handleDropdownChange shows empty message when user has no jobs', () => {

+ + + + + - - - + + + @@ -324,8 +337,11 @@ test('handleDropdownChange shows empty message when user has no jobs', () => { expect(document.getElementById('table-heading').textContent).toBe('My activity'); expect(document.getElementById('caption').textContent).toContain('Test User'); - document.querySelectorAll('.sender-column').forEach(col => { - expect(col.style.display).toBe('none'); + const senderHeader = document.querySelector('[data-column="sender"]'); + expect(senderHeader.style.display).toBe('none'); + + document.querySelectorAll('[data-sender]').forEach(cell => { + expect(cell.style.display).toBe('none'); }); const emptyMessageRow = document.querySelector('.table-empty-message').closest('tr'); diff --git a/urls.js b/urls.js index c53398bb9..f9ad9bd8a 100644 --- a/urls.js +++ b/urls.js @@ -67,6 +67,10 @@ const routes = { label: 'Team Members', path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/users', }, + { + label: 'All Activity', + path: '/activity/services/9c765540-266e-474e-b6bb-8e2e0e32b781', + }, { label: 'Invite User', path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/users/invite',
Sender
Other User
Another User
Different User
Other User
Another User
Different User
No batched job messages found (messages are kept for 7 days).