diff --git a/app/assets/javascripts/activityChart.js b/app/assets/javascripts/activityChart.js
index 62c1e6e3e6..a3f615c655 100644
--- a/app/assets/javascripts/activityChart.js
+++ b/app/assets/javascripts/activityChart.js
@@ -1,7 +1,8 @@
(function (window) {
if (document.getElementById('activityChartContainer')) {
-
+ const tableContainer = document.getElementById('activityContainer');
+ const currentUserName = tableContainer.getAttribute('data-currentUserName');
const COLORS = {
delivered: '#0076d6',
failed: '#fa9441',
@@ -270,27 +271,58 @@
subTitle.textContent = `${selectedText} - last 7 days`;
fetchData(selectedValue);
- // Update ARIA live region
const liveRegion = document.getElementById('aria-live-account');
liveRegion.textContent = `Data updated for ${selectedText} - last 7 days`;
- // Switch tables based on dropdown selection
- const selectedTable = selectedValue === "individual" ? "table1" : "table2";
- const tables = document.querySelectorAll('.table-overflow-x-auto');
- tables.forEach(function(table) {
- table.classList.add('hidden'); // Hide all tables by adding the hidden class
- table.classList.remove('visible'); // Ensure they are not visible
- });
- const tableToShow = document.getElementById(selectedTable);
- tableToShow.classList.remove('hidden'); // Remove hidden class
- tableToShow.classList.add('visible'); // Add visible class
+ const tableHeading = document.querySelector('#tableActivity h2');
+ const senderColumns = document.querySelectorAll('.sender-column');
+ const allRows = document.querySelectorAll('#activity-table tbody tr');
+ const caption = document.querySelector('#activity-table caption');
+
+ if (selectedValue === 'individual') {
+
+ tableHeading.textContent = 'My activity';
+ caption.textContent = `Table showing the sent jobs for ${currentUserName}`;
+
+ senderColumns.forEach(col => {
+ col.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() : '';
+ return rowSender === currentUserName;
+ });
+
+ userRows.slice(0, 5).forEach(row => {
+ row.style.display = '';
+ });
+ } else {
+
+ tableHeading.textContent = 'Service activity';
+ caption.textContent = `Table showing the sent jobs for service`;
+
+ senderColumns.forEach(col => {
+ col.style.display = '';
+ });
+
+ allRows.forEach((row, index) => {
+ row.style.display = (index < 5) ? '' : 'none';
+ });
+ }
};
document.addEventListener('DOMContentLoaded', function() {
// Initialize activityChart chart and table with service data by default
fetchData('service');
- // Add event listener to the dropdown
+ const allRows = Array.from(document.querySelectorAll('#activity-table tbody tr'));
+ allRows.forEach((row, index) => {
+ row.style.display = (index < 5) ? '' : 'none';
+ });
+
const dropdown = document.getElementById('options');
dropdown.addEventListener('change', handleDropdownChange);
});
diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py
index 8013acb9d2..1f8cac5730 100644
--- a/app/main/views/dashboard.py
+++ b/app/main/views/dashboard.py
@@ -14,7 +14,7 @@
service_api_client,
template_statistics_client,
)
-from app.formatters import format_date_numeric, format_datetime_numeric, get_time_left
+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
@@ -62,32 +62,14 @@ def service_dashboard(service_id):
job_response = job_api_client.get_jobs(service_id)["data"]
service_data_retention_days = 7
- jobs = [
- {
- "job_id": job["id"],
- "time_left": get_time_left(job["created_at"]),
- "download_link": url_for(
- ".view_job_csv", service_id=current_service.id, job_id=job["id"]
- ),
- "view_job_link": url_for(
- ".view_job", service_id=current_service.id, job_id=job["id"]
- ),
- "created_at": job["created_at"],
- "processing_finished": job.get("processing_finished"),
- "processing_started": job.get("processing_started"),
- "notification_count": job["notification_count"],
- "created_by": job["created_by"],
- "template_name": job["template_name"],
- "original_file_name": job["original_file_name"],
- }
- for job in job_response
- if job["job_status"] != "cancelled"
- ]
+ filtered_jobs = [job for job in job_response if job["job_status"] != "cancelled"]
+ sorted_jobs = sorted(filtered_jobs, key=lambda job: job["created_at"], reverse=True)
+
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=jobs,
+ jobs=sorted_jobs,
service_data_retention_days=service_data_retention_days,
sms_sent=sms_sent,
sms_allowance_remaining=sms_allowance_remaining,
diff --git a/app/templates/views/dashboard/activity-table.html b/app/templates/views/dashboard/activity-table.html
new file mode 100644
index 0000000000..a40840033d
--- /dev/null
+++ b/app/templates/views/dashboard/activity-table.html
@@ -0,0 +1,72 @@
+
Recent activity
+
+
+
+
+
Service activity
+
+
+ Table showing the sent jobs for {{current_service.name}}
+
+
+
+ Job ID#
+ Template
+ Job status
+ Sender
+
+ # of Recipients
+
+
+
+ {% if jobs %}
+ {% for job in jobs %}
+
+
+
+ {{ job.id[:8] if job.id else 'Manually entered number' }}
+
+
+ {{ job.template_name }}
+
+ {% if job.scheduled_for and not job.processing_finished %}
+ Scheduled for {{ job.scheduled_for|format_datetime_table }}
+ {% elif job.processing_finished and not job.statistics|selectattr('status', 'equalto', 'sending')|list %}
+ Sent on {{ job.processing_finished|format_datetime_table }}
+ {% elif job.processing_started %}
+ Sending since {{ job.processing_started|format_datetime_table }}
+ {% else %}
+ Pending since {{ job.created_at|format_datetime_table }}
+ {% endif %}
+
+ {{ job.created_by.name }}
+ {{ job.notification_count }}
+
+ {% endfor %}
+ {% else %}
+
+ No batched job messages found (messages are
+ kept for {{ service_data_retention_days }} days).
+
+ {% endif %}
+
+
+
+
diff --git a/app/templates/views/dashboard/dashboard.html b/app/templates/views/dashboard/dashboard.html
index 04abe1afcc..bd19585c31 100644
--- a/app/templates/views/dashboard/dashboard.html
+++ b/app/templates/views/dashboard/dashboard.html
@@ -6,140 +6,38 @@
{% from "components/ajax-block.html" import ajax_block %}
{% block service_page_title %}
- Dashboard
+ Dashboard
{% endblock %}
{% block maincolumn_content %}
-
-
-
-
-
-
Dashboard
- {% if current_user.has_permissions('manage_templates') and not current_service.all_templates %}
- {% include 'views/dashboard/write-first-messages.html' %}
- {% endif %}
-
- {{ ajax_block(partials, updates_url, 'upcoming') }}
-
-
{{ current_service.name }} Dashboard
-
- {{ ajax_block(partials, updates_url, 'inbox') }}
-
-
-
-
-
Recent activity
-
-
-
- {% if current_user.has_permissions('manage_service') %}{% endif %}
-
-
-
-
My activity
-
- Table showing the sent jobs for {{current_user.name}}
-
-
- Job ID#
- Template
- Job status
- # of Recipients
-
-
-
- {% if jobs %}
- {% for job in jobs[:5] %}
- {% if job.created_by.name == current_user.name %}
- {% set notification = job.notifications[0] %}
-
-
-
- {{ job.job_id[:8] if job.job_id else 'Manually entered number' }}
-
-
- {{ job.template_name }}
- Sent on
- {{ (job.processing_finished if job.processing_finished else job.processing_started
- if job.processing_started else job.created_at)|format_datetime_table }}
-
- {{ job.notification_count }}
-
- {% endif %}
- {% endfor %}
- {% else %}
-
- No batched job messages found (messages are kept for {{ service_data_retention_days }} days).
-
- {% endif %}
-
-
-
-
-
-
Service activity
-
- Table showing the sent jobs for this service
-
-
- Job ID#
- Template
- Job status
- Sender
- # of Recipients
-
-
-
- {% if jobs %}
- {% for job in jobs[:5] %}
- {% set notification = job.notifications[0] %}
-
-
-
- {{ job.job_id[:8] if job.job_id else 'Manually entered number' }}
-
-
- {{ job.template_name }}
- Sent on
- {{ (job.processing_finished if job.processing_finished else job.processing_started
- if job.processing_started else job.created_at)|format_datetime_table }}
-
- {{ job.created_by.name }}
- {{ job.notification_count }}
-
- {% endfor %}
- {% else %}
-
- No batched job messages found (messages are kept for {{ service_data_retention_days }} days).
-
- {% endif %}
-
-
-
-
- {{ ajax_block(partials, updates_url, 'template-statistics') }}
-
+
+
+
+
+
+
Dashboard
+{% if current_user.has_permissions('manage_templates') and not current_service.all_templates %}
+ {% include 'views/dashboard/write-first-messages.html' %}
+{% endif %}
+
+{{ ajax_block(partials, updates_url, 'upcoming') }}
+
+
{{ current_service.name }} Dashboard
+
+{{ ajax_block(partials, updates_url, 'inbox') }}
+
+
+
+
+{% include 'views/dashboard/activity-table.html' %}
+
+{% if current_user.has_permissions('manage_service') %}{% endif %}
+
+{{ ajax_block(partials, updates_url, 'template-statistics') }}
+
{% endblock %}
diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py
index 97cf42d688..461888c5c4 100644
--- a/tests/app/main/views/test_dashboard.py
+++ b/tests/app/main/views/test_dashboard.py
@@ -657,9 +657,8 @@ def test_should_show_recent_templates_on_dashboard(
]
assert "Total messages" in headers
- table_rows = page.find_all("tbody")[0].find_all("tr")
-
- assert len(table_rows) == 0
+ table_rows = page.find_all("tbody")[1].find_all("tr")
+ assert len(table_rows) == 2
@pytest.mark.parametrize(
@@ -1943,7 +1942,6 @@ def test_service_dashboard_shows_batched_jobs(
job_table_body = page.find("table", class_="job-table")
rows = job_table_body.find_all("tbody")[0].find_all("tr")
-
- assert len(rows) == 0
+ assert len(rows) == 1
assert job_table_body is not None
diff --git a/tests/javascripts/activityChart.test.js b/tests/javascripts/activityChart.test.js
index 8e3d406eac..427c93ac24 100644
--- a/tests/javascripts/activityChart.test.js
+++ b/tests/javascripts/activityChart.test.js
@@ -21,7 +21,7 @@ Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
beforeAll(done => {
// Set up the DOM with the D3 script included
document.body.innerHTML = `
-
+
+
+
`;
// Load the D3 script dynamically
@@ -178,3 +181,59 @@ const data = await fetchData('service');
expect(global.fetch).toHaveBeenCalledWith('/daily_stats.json');
expect(data).toEqual(mockResponse);
});
+
+test('handleDropdownChange updates DOM for individual selection', () => {
+ document.body.innerHTML = `
+
+
+
+
+
+
+
+
+ Test User
+ Other User
+ Test User
+ Test User
+ Other User
+ Test User
+ Test User
+
+
+
+
+ Service
+ Individual
+
+ `;
+
+ window.currentUserName = "Test User";
+
+ jest.spyOn(window, 'fetchData').mockImplementation(() => {});
+
+ const selectElement = document.getElementById('options');
+ selectElement.value = 'individual';
+ const event = { target: selectElement };
+
+ window.handleDropdownChange(event);
+
+ 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 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();
+ expect(sender).toBe('Test User');
+ });
+
+ window.fetchData.mockRestore();
+});