diff --git a/app/config.py b/app/config.py index 6fc83946f..68b780290 100644 --- a/app/config.py +++ b/app/config.py @@ -90,7 +90,7 @@ class Config(object): ], } - FEATURE_SOCKET_ENABLED = getenv("FEATURE_SOCKET_ENABLED", "true") == "true" + FEATURE_SOCKET_ENABLED = getenv("FEATURE_SOCKET_ENABLED", "false") == "true" def _s3_credentials_from_env(bucket_prefix): diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index 79f043c16..e58651f8e 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -import json import os -import time from functools import partial from flask import ( @@ -22,7 +20,6 @@ from markupsafe import Markup from app import ( current_service, format_datetime_table, - job_api_client, notification_api_client, service_api_client, ) @@ -113,46 +110,50 @@ def cancel_job(service_id, job_id): @user_has_permissions() def view_job_status_poll(service_id, job_id): """ - Poll status endpoint using new lightweight cached API endpoint. - Returns minimal data needed for polling. + DISABLED: Lightweight polling endpoint no longer supported. + Use manual page refresh instead. """ - start_time = time.time() + current_app.logger.info(f"Disabled lightweight polling endpoint accessed for job {job_id[:8]} - returning 410 Gone") + abort(410, "Lightweight polling endpoint disabled. Please refresh the page manually.") - # Use new lightweight status endpoint - try: - status_data = job_api_client.get_job_status(service_id, job_id) - - # Validate the response has expected fields - required_fields = ["sent_count", "failed_count", "pending_count", "total_count", "processing_finished"] - if all(key in status_data for key in required_fields): - response_data = { - "sent_count": status_data["sent_count"], - "failed_count": status_data["failed_count"], - "pending_count": status_data["pending_count"], - "total_count": status_data["total_count"], - "finished": status_data["processing_finished"], - } - processed_count = status_data["sent_count"] + status_data["failed_count"] - else: - current_app.logger.error(f"Status endpoint returned invalid response for job {job_id[:8]}: {status_data}") - abort(500, "Invalid status response from API") - - except Exception as e: - current_app.logger.error(f"Status endpoint failed for job {job_id[:8]}: {e}") - abort(500, "Status endpoint unavailable") - - response_time_ms = round((time.time() - start_time) * 1000, 2) - response_json = json.dumps(response_data) - response_size_bytes = len(response_json.encode("utf-8")) - - current_app.logger.info( - f"Poll status request - job_id={job_id[:8]} " - f"response_size={response_size_bytes}b " - f"response_time={response_time_ms}ms " - f"progress={processed_count}/{response_data['total_count']}" - ) - - return jsonify(response_data) + # Original polling code - commented out for manual refresh mode + # start_time = time.time() + # + # # Use new lightweight status endpoint + # try: + # status_data = job_api_client.get_job_status(service_id, job_id) + # + # # Validate the response has expected fields + # required_fields = ["sent_count", "failed_count", "pending_count", "total_count", "processing_finished"] + # if all(key in status_data for key in required_fields): + # response_data = { + # "sent_count": status_data["sent_count"], + # "failed_count": status_data["failed_count"], + # "pending_count": status_data["pending_count"], + # "total_count": status_data["total_count"], + # "finished": status_data["processing_finished"], + # } + # processed_count = status_data["sent_count"] + status_data["failed_count"] + # else: + # current_app.logger.error(f"Status endpoint returned invalid response for job {job_id[:8]}: {status_data}") + # abort(500, "Invalid status response from API") + # + # except Exception as e: + # current_app.logger.error(f"Status endpoint failed for job {job_id[:8]}: {e}") + # abort(500, "Status endpoint unavailable") + # + # response_time_ms = round((time.time() - start_time) * 1000, 2) + # response_json = json.dumps(response_data) + # response_size_bytes = len(response_json.encode("utf-8")) + # + # current_app.logger.info( + # f"Poll status request - job_id={job_id[:8]} " + # f"response_size={response_size_bytes}b " + # f"response_time={response_time_ms}ms " + # f"progress={processed_count}/{response_data['total_count']}" + # ) + # + # return jsonify(response_data) @main.route("/services//jobs/.json") diff --git a/app/templates/views/jobs/job.html b/app/templates/views/jobs/job.html index 755f6b3bc..c11908764 100644 --- a/app/templates/views/jobs/job.html +++ b/app/templates/views/jobs/job.html @@ -12,7 +12,13 @@ {{ page_header("Message status") }} {% if not job.finished_processing %} -

This page refreshes automatically to show the latest message activity delivery rates, details, and reports.
You can watch it in progress or check back later.

+

This page no longer refreshes automatically. Use the refresh button below to check the latest message status.

+
+ +
+

{% endif %}
{% if not job.finished_processing and FEATURE_SOCKET_ENABLED %} diff --git a/gulpfile.js b/gulpfile.js index 220627fe6..832967a07 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -79,7 +79,7 @@ const javascripts = () => { paths.src + 'javascripts/activityChart.js', paths.src + 'javascripts/sidenav.js', paths.src + 'javascripts/validation.js', - paths.src + 'javascripts/job-status-polling.js', + // paths.src + 'javascripts/job-status-polling.js', // Disabled for manual refresh mode paths.src + 'javascripts/scrollPosition.js', ]) .pipe(plugins.prettyerror()) @@ -105,6 +105,7 @@ const copySetTimezone = () => { return src(paths.src + 'js/setTimezone.js').pipe(dest(paths.dist + 'js/')); }; + // Task to copy images const copyImages = () => { return src(paths.src + 'images/**/*', { encoding: false }).pipe( diff --git a/tests/app/main/views/test_job_notification_updates.py b/tests/app/main/views/test_job_notification_updates.py index a07f4d07a..d3839b699 100644 --- a/tests/app/main/views/test_job_notification_updates.py +++ b/tests/app/main/views/test_job_notification_updates.py @@ -1,15 +1,11 @@ """ -Tests for job notification update logic during polling. +Tests for disabled job polling endpoint. -These tests verify the poll status endpoint behavior and document -the JavaScript notification refresh logic: -1. Notifications update for first 50 messages -2. Notifications stop updating after 50 messages (to prevent performance issues) -3. Notifications always update when job finishes +These tests verify that the poll status endpoint is properly disabled +and returns 410 Gone status. The JavaScript notification refresh logic +is no longer used as polling has been replaced with manual refresh. """ -import json - import pytest @@ -58,31 +54,14 @@ def test_poll_status_notification_update_logic( "main.view_job_status_poll", service_id=service_one["id"], job_id=fake_uuid, + _expected_status=410, ) - assert response.status_code == 200 - data = json.loads(response.get_data(as_text=True)) + assert response.status_code == 410 + # Endpoint is disabled, so no data to verify - # Verify the response - assert data["sent_count"] == delivered - assert data["failed_count"] == failed - assert data["pending_count"] == pending - assert data["total_count"] == total - assert data["finished"] is finished - - processed_count = delivered + failed - - if js_should_update_notifications: - # JavaScript would call: await updateNotifications() - if finished: - assert finished, f"JS updates notifications: {reason}" - else: - assert processed_count <= 50, f"JS updates notifications: {reason}" - assert not finished, f"JS updates notifications: {reason}" - else: - # JavaScript would NOT update notifications - assert processed_count > 50, f"JS skips notification update: {reason}" - assert not finished, f"JS skips notification update: {reason}" + # Since the polling endpoint is disabled, the JavaScript logic being tested + # is no longer relevant. The test now verifies the endpoint is disabled. def test_poll_status_provides_required_fields( @@ -107,12 +86,7 @@ def test_poll_status_provides_required_fields( "main.view_job_status_poll", service_id=service_one["id"], job_id=fake_uuid, + _expected_status=410, ) - data = json.loads(response.get_data(as_text=True)) - - required_fields = {"sent_count", "failed_count", "finished", "pending_count", "total_count"} - assert set(data.keys()) == required_fields - - response_size = len(response.get_data(as_text=True)) - assert response_size < 200, f"Response too large: {response_size} bytes" + assert response.status_code == 410 diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py index 3f1db6f5f..97b123f79 100644 --- a/tests/app/main/views/test_jobs.py +++ b/tests/app/main/views/test_jobs.py @@ -358,7 +358,11 @@ def test_should_show_scheduled_job( assert page.select("main p a")[0]["href"] == url_for( "main.message_status", ) - assert page.select_one("main button[type=submit]").text.strip() == "Cancel sending" + # Test that both buttons are present + buttons = page.select("main button[type=submit]") + button_texts = [b.text.strip() for b in buttons] + assert "Refresh Status" in button_texts + assert "Cancel sending" in button_texts def test_should_cancel_job( @@ -523,25 +527,10 @@ def test_poll_status_endpoint( "main.view_job_status_poll", service_id=service_one["id"], job_id=fake_uuid, + _expected_status=410, ) - assert response.status_code == 200 - data = json.loads(response.get_data(as_text=True)) - - expected_keys = { - "sent_count", - "failed_count", - "pending_count", - "total_count", - "finished", - } - assert set(data.keys()) == expected_keys - - assert data["sent_count"] == 90 - assert data["failed_count"] == 10 - assert data["pending_count"] == 0 - assert data["total_count"] == 100 - assert data["finished"] is True + assert response.status_code == 410 def test_poll_status_with_zero_notifications( @@ -566,13 +555,10 @@ def test_poll_status_with_zero_notifications( "main.view_job_status_poll", service_id=service_one["id"], job_id=fake_uuid, + _expected_status=410, ) - assert response.status_code == 200 - data = json.loads(response.get_data(as_text=True)) - - assert data["total_count"] == 0 - assert data["finished"] is True + assert response.status_code == 410 def test_poll_status_endpoint_does_not_query_notifications_table( @@ -601,16 +587,10 @@ def test_poll_status_endpoint_does_not_query_notifications_table( "main.view_job_status_poll", service_id=service_one["id"], job_id=fake_uuid, + _expected_status=410, ) - assert response.status_code == 200 + assert response.status_code == 410 - # Verify no notifications were fetched + # Verify no notifications were fetched (since endpoint is disabled) mock_get_notifications.assert_not_called() - - data = json.loads(response.get_data(as_text=True)) - assert data["total_count"] == 500 - assert data["sent_count"] == 300 - assert data["failed_count"] == 50 - assert data["pending_count"] == 150 - assert data["finished"] is False