Make verified repair acceptance prominent and simplify confirmation email
This commit is contained in:
@@ -129,6 +129,10 @@ def _activity_metadata(entry: Dict[str, Any]) -> Dict[str, Any]:
|
||||
def _repair_tracking(item_id: int) -> tuple[Dict[str, Any], list[Dict[str, Any]]]:
|
||||
activity = list_portal_item_activity(item_id, limit=500)
|
||||
for entry in reversed(activity):
|
||||
# A rejected repair must not be proposed again simply because the same
|
||||
# replacement file is still present. Wait for a NEW repair attempt.
|
||||
if str(entry.get("event_type") or "") == "resolution_rejected":
|
||||
return {}, activity
|
||||
if str(entry.get("event_type") or "") not in _MEDIA_REPAIR_STARTED_EVENTS:
|
||||
continue
|
||||
tracking = _activity_metadata(entry).get("repairTracking")
|
||||
@@ -205,27 +209,34 @@ async def _contact_reporter(item: Dict[str, Any]) -> Dict[str, Any]:
|
||||
attempt_number = attempts + 1
|
||||
reporter = get_user_by_username(str(item.get("created_by_username") or ""))
|
||||
recipient = resolve_user_delivery_email(reporter)
|
||||
issue_url = _issue_url(int(item["id"]))
|
||||
issue_url = f"{_app_url()}/issues/confirm/{int(item['id'])}"
|
||||
sent = False
|
||||
delivery_error: Optional[str] = None
|
||||
if recipient:
|
||||
subject = f"Is your issue fixed? #{item['id']} {item.get('title') or ''}".strip()
|
||||
subject = f"Ready to try again? Grizzlyflix issue #{item['id']}"
|
||||
body_text = (
|
||||
f"We have marked issue #{item['id']} as fixed and need your confirmation.\n\n"
|
||||
f"Issue: {item.get('title') or 'Untitled issue'}\n"
|
||||
f"Confirmation request: {attempt_number} of {maximum}\n\n"
|
||||
f"Open the issue and choose whether it is fixed or still happening:\n{issue_url}\n\n"
|
||||
"If you do not respond, Magent will close the issue automatically after the configured confirmation period."
|
||||
"Your repair looks ready to test.\n\n"
|
||||
f"{item.get('title') or 'Your reported issue'}\n\n"
|
||||
"Please try the affected content in Grizzlyflix. Is it fixed?\n\n"
|
||||
f"YES — it works: {issue_url}#yes\n"
|
||||
f"NO — still broken: {issue_url}#no\n\n"
|
||||
"Confirm your answer in Magent. You may need to sign in first.\n"
|
||||
"Yes closes the report. No keeps it open for another look.\n\n"
|
||||
f"Reminder {attempt_number} of {maximum}. If we do not hear back after the reminder period, this report will close automatically."
|
||||
)
|
||||
body_html = (
|
||||
'<div style="font-family:Segoe UI,Arial,sans-serif;color:#132033;">'
|
||||
'<h2 style="margin:0 0 12px;">Is your issue fixed?</h2>'
|
||||
f'<p style="line-height:1.6;">We have marked issue <strong>#{int(item["id"])}</strong> as fixed and need your confirmation.</p>'
|
||||
f'<p style="line-height:1.6;"><strong>{escape(str(item.get("title") or "Untitled issue"))}</strong><br>'
|
||||
f'Confirmation request {attempt_number} of {maximum}</p>'
|
||||
f'<a href="{escape(issue_url)}" style="display:inline-block;padding:11px 18px;border-radius:8px;background:#1c6bff;color:#fff;text-decoration:none;font-weight:700;">Confirm the outcome</a>'
|
||||
'<p style="margin-top:18px;color:#64748b;line-height:1.6;">If you do not respond, Magent will close the issue automatically after the configured confirmation period.</p>'
|
||||
'</div>'
|
||||
'<div style="background:#111113;padding:24px 12px;font-family:Arial,sans-serif;color:#f4f4f5;">'
|
||||
'<table role="presentation" style="max-width:560px;width:100%;margin:auto;background:#202023;border:1px solid #45454d;border-radius:18px;"><tr><td style="padding:28px;">'
|
||||
'<p style="margin:0 0 24px;color:#c7baff;font-weight:bold;letter-spacing:2px;">GRIZZLYFLIX · MAGENT</p>'
|
||||
'<h1 style="font-size:32px;line-height:1.2;margin:0 0 16px;color:#fff;">Ready to try again?</h1>'
|
||||
'<p style="font-size:17px;line-height:1.6;color:#e4e4e7;">Your repair looks ready to test. Give the affected content a try, then let us know:</p>'
|
||||
f'<p style="padding:16px;background:#131315;border-radius:10px;color:#fff;">{escape(str(item.get("title") or "Your reported issue"))}</p>'
|
||||
'<h2 style="font-size:26px;color:#fff;margin:24px 0 16px;">Is it fixed?</h2>'
|
||||
f'<a href="{escape(issue_url)}#yes" style="display:block;text-align:center;padding:20px;margin-bottom:12px;border-radius:12px;background:#b4f4d2;color:#10261b;text-decoration:none;font-size:24px;font-weight:bold;">YES — it works</a>'
|
||||
f'<a href="{escape(issue_url)}#no" style="display:block;text-align:center;padding:20px;border-radius:12px;background:#ffc1c5;color:#391318;text-decoration:none;font-size:24px;font-weight:bold;">NO — still broken</a>'
|
||||
'<p style="font-size:14px;line-height:1.6;color:#dedee3;">Confirm your answer in Magent. You may need to sign in first.<br>Yes closes the report. No keeps it open for another look.</p>'
|
||||
f'<p style="font-size:12px;line-height:1.6;color:#b9b9c3;">Reminder {attempt_number} of {maximum} · Issue #{int(item["id"])}<br>If we do not hear back after the reminder period, this report will close automatically.</p>'
|
||||
'</td></tr></table></div>'
|
||||
)
|
||||
try:
|
||||
await send_generic_email(
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import json
|
||||
import unittest
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from backend.app import db
|
||||
from backend.app.services import issue_resolution as service
|
||||
from backend.tests.test_backend_quality import TempDatabaseMixin
|
||||
|
||||
|
||||
class IssueAcceptanceTests(TempDatabaseMixin, unittest.IsolatedAsyncioTestCase):
|
||||
def issue(self):
|
||||
item = db.create_portal_item(kind="issue", title="Broken <movie>", description="Repair",
|
||||
created_by_username="reporter", created_by_id=None, status="in_progress", issue_type="broken_media")
|
||||
self.start(item["id"])
|
||||
return item
|
||||
|
||||
def start(self, item_id):
|
||||
db.add_portal_item_activity(item_id, event_type="replacement_started", actor_username="reporter",
|
||||
actor_role="user", message="New repair", metadata_json=json.dumps({"repairTracking": {"requestId": "12", "actionId": "replace_media"}}))
|
||||
|
||||
async def test_importing_or_unverified_media_does_not_email_reporter(self):
|
||||
self.issue()
|
||||
for phase in ["collecting", "indexing", "unavailable"]:
|
||||
with patch.object(service, "_media_repair_evidence", new=AsyncMock(return_value={"complete": False, "phase": phase})), patch.object(service, "begin_issue_confirmation", new=AsyncMock()) as begin:
|
||||
await service.process_active_media_repairs()
|
||||
begin.assert_not_awaited()
|
||||
|
||||
async def test_verified_repair_emails_once_and_no_requires_a_new_repair(self):
|
||||
item = self.issue()
|
||||
with (
|
||||
patch.object(service, "_media_repair_evidence", new=AsyncMock(return_value={"complete": True, "phase": "complete"})),
|
||||
patch.object(service, "_workflow_settings", return_value=(3, 2, "days")),
|
||||
patch.object(service, "get_user_by_username", return_value={"username": "reporter"}),
|
||||
patch.object(service, "resolve_user_delivery_email", return_value="reporter@example.test"),
|
||||
patch.object(service, "send_generic_email", new=AsyncMock()) as email,
|
||||
):
|
||||
await service.process_active_media_repairs()
|
||||
await service.process_active_media_repairs()
|
||||
self.assertEqual(email.await_count, 1)
|
||||
self.assertEqual(db.get_portal_item(item["id"])["status"], "awaiting_confirmation")
|
||||
content = email.await_args.kwargs
|
||||
self.assertIn("YES — it works", content["body_html"])
|
||||
self.assertIn("NO — still broken", content["body_html"])
|
||||
self.assertIn(f"/issues/confirm/{item['id']}#yes", content["body_html"])
|
||||
self.assertIn("Broken <movie>", content["body_html"])
|
||||
self.assertNotIn("<movie>", content["body_html"])
|
||||
self.assertIn("Confirm your answer in Magent", content["body_text"])
|
||||
service.respond_to_issue_confirmation(item["id"], resolved=False, actor_username="reporter", actor_role="user")
|
||||
await service.process_active_media_repairs()
|
||||
await service.process_due_issue_confirmations()
|
||||
self.assertEqual(email.await_count, 1)
|
||||
self.assertEqual(db.get_portal_item(item["id"])["status"], "in_progress")
|
||||
self.start(item["id"])
|
||||
await service.process_active_media_repairs()
|
||||
self.assertEqual(email.await_count, 2)
|
||||
Reference in New Issue
Block a user