mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-05-27 09:29:22 -04:00
Bugfix/polling improvements - Pointing polling to new backend endpoint designed for status only (#2949)
* Optimizing polling * Fixed formatting issue * Pointed new endpoint to new backend polling endpoint * Missed a unit test update * Fixed flake issue
This commit is contained in:
@@ -199,7 +199,7 @@ def create_app(application):
|
||||
|
||||
current_app.logger.info(
|
||||
f"FEATURE_SOCKET_ENABLED value in __init__.py coming \
|
||||
from config is {application.config.get('FEATURED_SOCKET_ENABLED')} and \
|
||||
from config is {application.config.get('FEATURE_SOCKET_ENABLED')} and \
|
||||
the ending value is {feature_socket_enabled}"
|
||||
)
|
||||
return dict(
|
||||
|
||||
@@ -126,7 +126,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
await updateNotifications();
|
||||
stopPolling();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (retryCount < 3) {
|
||||
console.debug(`Job polling retry ${retryCount}`, error.message);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from functools import partial
|
||||
|
||||
from flask import (
|
||||
@@ -21,6 +22,7 @@ from markupsafe import Markup
|
||||
from app import (
|
||||
current_service,
|
||||
format_datetime_table,
|
||||
job_api_client,
|
||||
notification_api_client,
|
||||
service_api_client,
|
||||
)
|
||||
@@ -111,25 +113,33 @@ def cancel_job(service_id, job_id):
|
||||
@user_has_permissions()
|
||||
def view_job_status_poll(service_id, job_id):
|
||||
"""
|
||||
Poll status endpoint that only queries jobs table.
|
||||
Poll status endpoint using new lightweight cached API endpoint.
|
||||
Returns minimal data needed for polling.
|
||||
"""
|
||||
import time
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
job = Job.from_id(job_id, service_id=service_id)
|
||||
# Use new lightweight status endpoint
|
||||
try:
|
||||
status_data = job_api_client.get_job_status(service_id, job_id)
|
||||
|
||||
processed_count = job.notifications_delivered + job.notifications_failed
|
||||
total_count = job.notification_count
|
||||
# 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")
|
||||
|
||||
response_data = {
|
||||
"sent_count": job.notifications_delivered,
|
||||
"failed_count": job.notifications_failed,
|
||||
"pending_count": job.notifications_sending,
|
||||
"total_count": total_count,
|
||||
"finished": job.finished_processing,
|
||||
}
|
||||
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)
|
||||
@@ -139,7 +149,7 @@ def view_job_status_poll(service_id, job_id):
|
||||
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}/{total_count}"
|
||||
f"progress={processed_count}/{response_data['total_count']}"
|
||||
)
|
||||
|
||||
return jsonify(response_data)
|
||||
|
||||
@@ -32,6 +32,9 @@ class JobApiClient(NotifyAdminAPIClient):
|
||||
|
||||
return job
|
||||
|
||||
def get_job_status(self, service_id, job_id):
|
||||
return self.get(url=f"/service/{service_id}/job/{job_id}/status")
|
||||
|
||||
def get_jobs(
|
||||
self,
|
||||
service_id,
|
||||
|
||||
@@ -12,8 +12,6 @@ import json
|
||||
|
||||
import pytest
|
||||
|
||||
from tests import job_json, user_json
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("delivered", "failed", "pending", "finished", "js_should_update_notifications", "reason"),
|
||||
@@ -46,25 +44,14 @@ def test_poll_status_notification_update_logic(
|
||||
job is finished (regardless of count)
|
||||
"""
|
||||
total = delivered + failed + pending
|
||||
job_status = "finished" if finished else "sending"
|
||||
|
||||
mock_job = mocker.patch("app.job_api_client.get_job")
|
||||
mock_job.return_value = {
|
||||
"data": {
|
||||
**job_json(
|
||||
service_one["id"],
|
||||
created_by=user_json(),
|
||||
job_id=fake_uuid,
|
||||
job_status=job_status,
|
||||
notification_count=total,
|
||||
notifications_requested=total,
|
||||
),
|
||||
"statistics": [
|
||||
{"status": "delivered", "count": delivered},
|
||||
{"status": "failed", "count": failed},
|
||||
{"status": "pending", "count": pending},
|
||||
],
|
||||
}
|
||||
mock_job_status = mocker.patch("app.job_api_client.get_job_status")
|
||||
mock_job_status.return_value = {
|
||||
"sent_count": delivered,
|
||||
"failed_count": failed,
|
||||
"pending_count": pending,
|
||||
"total_count": total,
|
||||
"processing_finished": finished,
|
||||
}
|
||||
|
||||
response = client_request.get_response(
|
||||
@@ -107,23 +94,13 @@ def test_poll_status_provides_required_fields(
|
||||
fake_uuid,
|
||||
):
|
||||
"""Verify poll status endpoint returns all fields needed for notification update logic."""
|
||||
mock_job = mocker.patch("app.job_api_client.get_job")
|
||||
mock_job.return_value = {
|
||||
"data": {
|
||||
**job_json(
|
||||
service_one["id"],
|
||||
created_by=user_json(),
|
||||
job_id=fake_uuid,
|
||||
job_status="sending",
|
||||
notification_count=25,
|
||||
notifications_requested=25,
|
||||
),
|
||||
"statistics": [
|
||||
{"status": "delivered", "count": 15},
|
||||
{"status": "failed", "count": 5},
|
||||
{"status": "pending", "count": 5},
|
||||
],
|
||||
}
|
||||
mock_job_status = mocker.patch("app.job_api_client.get_job_status")
|
||||
mock_job_status.return_value = {
|
||||
"sent_count": 15,
|
||||
"failed_count": 5,
|
||||
"pending_count": 5,
|
||||
"total_count": 25,
|
||||
"processing_finished": False,
|
||||
}
|
||||
|
||||
response = client_request.get_response(
|
||||
|
||||
@@ -510,23 +510,13 @@ def test_poll_status_endpoint(
|
||||
fake_uuid,
|
||||
):
|
||||
"""Test that the poll status endpoint returns only required data without notifications"""
|
||||
mock_job = mocker.patch("app.job_api_client.get_job")
|
||||
mock_job.return_value = {
|
||||
"data": {
|
||||
**job_json(
|
||||
service_one["id"],
|
||||
created_by=user_json(),
|
||||
job_id=fake_uuid,
|
||||
job_status="finished",
|
||||
notification_count=100,
|
||||
notifications_requested=100,
|
||||
),
|
||||
"statistics": [
|
||||
{"status": "delivered", "count": 90},
|
||||
{"status": "failed", "count": 10},
|
||||
{"status": "pending", "count": 0},
|
||||
],
|
||||
}
|
||||
mock_job_status = mocker.patch("app.job_api_client.get_job_status")
|
||||
mock_job_status.return_value = {
|
||||
"sent_count": 90,
|
||||
"failed_count": 10,
|
||||
"pending_count": 0,
|
||||
"total_count": 100,
|
||||
"processing_finished": True,
|
||||
}
|
||||
|
||||
response = client_request.get_response(
|
||||
@@ -563,19 +553,13 @@ def test_poll_status_with_zero_notifications(
|
||||
fake_uuid,
|
||||
):
|
||||
"""Test poll status endpoint handles edge case of no notifications"""
|
||||
mock_job = mocker.patch("app.job_api_client.get_job")
|
||||
mock_job.return_value = {
|
||||
"data": {
|
||||
**job_json(
|
||||
service_one["id"],
|
||||
created_by=user_json(),
|
||||
job_id=fake_uuid,
|
||||
job_status="pending",
|
||||
notification_count=0,
|
||||
notifications_requested=0,
|
||||
),
|
||||
"statistics": [],
|
||||
}
|
||||
mock_job_status = mocker.patch("app.job_api_client.get_job_status")
|
||||
mock_job_status.return_value = {
|
||||
"sent_count": 0,
|
||||
"failed_count": 0,
|
||||
"pending_count": 0,
|
||||
"total_count": 0,
|
||||
"processing_finished": True,
|
||||
}
|
||||
|
||||
response = client_request.get_response(
|
||||
@@ -588,9 +572,7 @@ def test_poll_status_with_zero_notifications(
|
||||
data = json.loads(response.get_data(as_text=True))
|
||||
|
||||
assert data["total_count"] == 0
|
||||
assert (
|
||||
data["finished"] is True
|
||||
)
|
||||
assert data["finished"] is True
|
||||
|
||||
|
||||
def test_poll_status_endpoint_does_not_query_notifications_table(
|
||||
@@ -602,23 +584,13 @@ def test_poll_status_endpoint_does_not_query_notifications_table(
|
||||
fake_uuid,
|
||||
):
|
||||
"""Critical regression test: ensure poll status endpoint never queries notifications"""
|
||||
mock_job = mocker.patch("app.job_api_client.get_job")
|
||||
mock_job.return_value = {
|
||||
"data": {
|
||||
**job_json(
|
||||
service_one["id"],
|
||||
created_by=user_json(),
|
||||
job_id=fake_uuid,
|
||||
job_status="sending",
|
||||
notification_count=500,
|
||||
notifications_requested=500,
|
||||
),
|
||||
"statistics": [
|
||||
{"status": "delivered", "count": 300},
|
||||
{"status": "failed", "count": 50},
|
||||
{"status": "pending", "count": 150},
|
||||
],
|
||||
}
|
||||
mock_job_status = mocker.patch("app.job_api_client.get_job_status")
|
||||
mock_job_status.return_value = {
|
||||
"sent_count": 300,
|
||||
"failed_count": 50,
|
||||
"pending_count": 150,
|
||||
"total_count": 500,
|
||||
"processing_finished": False,
|
||||
}
|
||||
|
||||
mock_get_notifications = mocker.patch(
|
||||
@@ -639,3 +611,6 @@ def test_poll_status_endpoint_does_not_query_notifications_table(
|
||||
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