mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-07-16 19:30:13 -04:00
Merge pull request #2950 from GSA/hotfix/remove-all-polling
Hotfix/remove all polling
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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/<uuid:service_id>/jobs/<uuid:job_id>.json")
|
||||
|
||||
@@ -12,7 +12,13 @@
|
||||
|
||||
{{ page_header("Message status") }}
|
||||
{% if not job.finished_processing %}
|
||||
<p class="max-width-full">This page refreshes automatically to show the latest message activity delivery rates, details, and reports.<br>You can watch it in progress or check back later.</p>
|
||||
<p class="max-width-full">This page no longer refreshes automatically. Use the refresh button below to check the latest message status.</p>
|
||||
<form method="get" style="display: inline-block;">
|
||||
<button type="submit" class="usa-button usa-button--outline">
|
||||
Refresh Status
|
||||
</button>
|
||||
</form>
|
||||
<br><br>
|
||||
{% endif %}
|
||||
<div data-job-id="{{ job.id }}" data-host="{{ api_public_url }}">
|
||||
{% if not job.finished_processing and FEATURE_SOCKET_ENABLED %}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user