mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-09-23 05:46:50 +00:00
WIP
This commit is contained in:
@@ -67,7 +67,7 @@ def construct_blueprint():
|
||||
message = "✓ Notification sent successfully and removed from queue."
|
||||
flash(message, 'notice')
|
||||
else:
|
||||
message = "Failed to send notification. It remains scheduled for automatic retry."
|
||||
message = "Failed to send notification. It has been re-queued for automatic retry."
|
||||
flash(message, 'error')
|
||||
|
||||
return redirect(url_for('notification_dashboard.dashboard'))
|
||||
@@ -108,7 +108,7 @@ def construct_blueprint():
|
||||
@notification_dashboard.route("/clear-all", methods=['POST'])
|
||||
@login_optionally_required
|
||||
def clear_all_notifications():
|
||||
"""Clear ALL notifications (pending, retrying, and failed)"""
|
||||
"""Clear ALL notifications (delivered, pending, retrying, and failed)"""
|
||||
from changedetectionio.notification.task_queue import clear_all_notifications
|
||||
|
||||
result = clear_all_notifications()
|
||||
@@ -116,8 +116,8 @@ def construct_blueprint():
|
||||
if 'error' in result:
|
||||
flash(f"Error clearing notifications: {result['error']}", 'error')
|
||||
else:
|
||||
total_cleared = result.get('queue', 0) + result.get('schedule', 0) + result.get('results', 0)
|
||||
flash(f"Cleared {total_cleared} notification(s) from queue.", 'notice')
|
||||
total_cleared = result.get('queue', 0) + result.get('schedule', 0) + result.get('results', 0) + result.get('delivered', 0)
|
||||
flash(f"Cleared {total_cleared} notification(s) (delivered, queued, retrying, and failed).", 'notice')
|
||||
|
||||
return redirect(url_for('notification_dashboard.dashboard'))
|
||||
|
||||
|
||||
@@ -1067,14 +1067,17 @@ def retry_notification_now(task_id):
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
# Notification failed - but we already revoked it, so it won't retry automatically
|
||||
# The failure will be logged and visible in the dashboard
|
||||
# Notification failed - re-queue it so it doesn't disappear and can retry automatically
|
||||
logger.warning(f"Failed to send notification for task {task_id}: {e}")
|
||||
logger.info(f"Task {task_id} already removed from queue (user requested immediate execution)")
|
||||
logger.info(f"Re-queueing notification for automatic retry after manual send failed")
|
||||
|
||||
# Clean up metadata and result
|
||||
_delete_result(task_id)
|
||||
_delete_task_metadata(task_id)
|
||||
# Re-queue the notification for automatic retry with exponential backoff
|
||||
# This ensures it doesn't disappear from the dashboard and will retry later
|
||||
try:
|
||||
result = send_notification_task(notification_data)
|
||||
logger.info(f"Re-queued notification after failed manual send")
|
||||
except Exception as queue_error:
|
||||
logger.error(f"Failed to re-queue notification: {queue_error}")
|
||||
|
||||
return False
|
||||
|
||||
@@ -1429,9 +1432,14 @@ def send_notification_task(n_object: NotificationContextData):
|
||||
"""
|
||||
from changedetectionio.notification.handler import process_notification
|
||||
from changedetectionio.flask_app import datastore, notification_debug_log, app
|
||||
from changedetectionio.notification_service import NotificationContextData
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
# Wrap dict in NotificationContextData if needed (for retried tasks from Huey)
|
||||
if not isinstance(n_object, NotificationContextData):
|
||||
n_object = NotificationContextData(n_object)
|
||||
|
||||
# Load watch
|
||||
watch = datastore.data['watching'].get(n_object.get('uuid'))
|
||||
if not watch:
|
||||
|
||||
@@ -132,7 +132,8 @@ class FileStorageTaskManager(HueyTaskManager):
|
||||
'schedule': 0,
|
||||
'results': 0,
|
||||
'retry_attempts': 0,
|
||||
'task_metadata': 0
|
||||
'task_metadata': 0,
|
||||
'delivered': 0
|
||||
}
|
||||
|
||||
if not self.storage_path:
|
||||
@@ -181,6 +182,14 @@ class FileStorageTaskManager(HueyTaskManager):
|
||||
os.remove(os.path.join(metadata_dir, f))
|
||||
cleared['task_metadata'] += 1
|
||||
|
||||
# Clear delivered (success) notifications
|
||||
success_dir = os.path.join(self.storage_path, 'success')
|
||||
if os.path.exists(success_dir):
|
||||
for f in os.listdir(success_dir):
|
||||
if f.startswith('success-') and f.endswith('.json'):
|
||||
os.remove(os.path.join(success_dir, f))
|
||||
cleared['delivered'] += 1
|
||||
|
||||
return cleared
|
||||
|
||||
def store_task_metadata(self, task_id, metadata):
|
||||
|
||||
@@ -769,37 +769,39 @@ def test_send_now_button(client, live_server, measure_memory_usage, datastore_pa
|
||||
# Click "Send Now" button (GET request)
|
||||
res = client.get(url_for("notification_dashboard.send_now", task_id=task_id), follow_redirects=True)
|
||||
|
||||
# Should redirect back to notification dashboard with message
|
||||
# The notification will still fail (bad SMTP server), but should be executed immediately
|
||||
# and removed from the retry schedule
|
||||
# Should redirect back to notification dashboard with error message
|
||||
# The notification will fail (bad SMTP server) and be re-queued for automatic retry
|
||||
assert b'Failed to send notification' in res.data, "Should show error message"
|
||||
assert b're-queued for automatic retry' in res.data, "Should indicate notification was re-queued"
|
||||
|
||||
# Wait for the task to be removed from schedule (give revoke time to propagate)
|
||||
# Wait for the notification to be re-queued (old task revoked, new task created)
|
||||
# The re-queued notification will have a different task_id
|
||||
max_wait = 5
|
||||
start_time = time.time()
|
||||
task_removed = False
|
||||
notification_requeued = False
|
||||
|
||||
while time.time() - start_time < max_wait:
|
||||
pending_after = get_pending_notifications(limit=100)
|
||||
all_retrying = [n for n in pending_after if n.get('status') == 'retrying']
|
||||
retrying_after = [n for n in all_retrying if n.get('task_id') == task_id]
|
||||
|
||||
logging.info(f"[{time.time() - start_time:.1f}s] Total retrying: {len(all_retrying)}, Matching task_id {task_id[:8]}: {len(retrying_after)}")
|
||||
# Check if there's still a retrying notification for this watch
|
||||
# (it will have a different task_id after re-queueing)
|
||||
retrying_for_watch = [n for n in all_retrying if n.get('watch_uuid') == uuid]
|
||||
|
||||
if len(retrying_after) == 0:
|
||||
task_removed = True
|
||||
logging.info(f"✓ Task {task_id} removed from retry schedule after {time.time() - start_time:.1f}s")
|
||||
logging.info(f"[{time.time() - start_time:.1f}s] Total retrying: {len(all_retrying)}, For this watch: {len(retrying_for_watch)}")
|
||||
|
||||
if len(retrying_for_watch) > 0:
|
||||
notification_requeued = True
|
||||
new_task_id = retrying_for_watch[0].get('task_id')
|
||||
logging.info(f"✓ Notification re-queued with new task_id {new_task_id[:8]}... after {time.time() - start_time:.1f}s")
|
||||
break
|
||||
|
||||
# Log details of the found notification
|
||||
if retrying_after:
|
||||
logging.info(f" Found retrying notification: {retrying_after[0].get('retry_at')}")
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
# The task should be gone from schedule (either succeeded or moved to dead letter)
|
||||
assert task_removed, f"Notification should be removed from retry schedule after 'Send Now', but still found after {max_wait}s"
|
||||
# The notification should be re-queued (not disappeared)
|
||||
assert notification_requeued, f"Notification should be re-queued after failed 'Send Now', but not found after {max_wait}s"
|
||||
|
||||
logging.info("✓ Send Now button successfully executed notification immediately")
|
||||
logging.info("✓ Send Now button correctly re-queued failed notification")
|
||||
|
||||
client.get(url_for("ui.form_delete", uuid="all"), follow_redirects=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user