feat(telegram): Link zum Artikel und zur Freigabe-Warteschlange
Some checks are pending
🚀 Deploy to Hetzner / deploy (push) Waiting to run
Backend Tests / backend-tests (push) Waiting to run

Die Freigabe-Meldung fuehrte nur in den einzelnen Artikel. Stehen mehrere
an, will man in die Liste - beide Wege stehen jetzt in der Nachricht, und
die Pipeline-Zusammenfassung verlinkt die Warteschlange, sobald etwas
darauf wartet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Oliver 2026-08-24 16:39:06 +02:00
parent de1d62c6f7
commit 208940da3f
No known key found for this signature in database
4 changed files with 64 additions and 4 deletions

View file

@ -8,6 +8,10 @@
- Nach jeder Aktion zurück zur Liste, damit sich eine Warteschlange am Stück abarbeiten lässt - Nach jeder Aktion zurück zur Liste, damit sich eine Warteschlange am Stück abarbeiten lässt
- Verlinkt aus der Navigation aller Admin-Seiten, mit Zähler im Dashboard - Verlinkt aus der Navigation aller Admin-Seiten, mit Zähler im Dashboard
### 📨 Telegram
- Freigabe-Meldung enthält jetzt zwei Links: „Diesen Artikel prüfen" (Detailseite) und „Alle wartenden Artikel" (Warteschlange)
- Pipeline-Zusammenfassung verlinkt die Warteschlange, sobald etwas darauf wartet
### 🧹 Status-Altlasten entfernt ### 🧹 Status-Altlasten entfernt
- Artikelliste zeigte interne Kürzel (`pending_review`, `approved`, `error`) — jetzt überall Klartext - Artikelliste zeigte interne Kürzel (`pending_review`, `approved`, `error`) — jetzt überall Klartext
- Status `review` (Relevanz-Warnzone 6079) wurde als „Rewrite" angezeigt, was etwas anderes bedeutet — heißt jetzt „Niedrige Relevanz" - Status `review` (Relevanz-Warnzone 6079) wurde als „Rewrite" angezeigt, was etwas anderes bedeutet — heißt jetzt „Niedrige Relevanz"

View file

@ -220,9 +220,17 @@ def notify_new_draft(
send_message(text, reply_markup=keyboard) send_message(text, reply_markup=keyboard)
def _portal_article_url(article_id: Any) -> str: def _portal_url(path: str) -> str:
base = (get_settings().portal_base_url or "").rstrip("/") base = (get_settings().portal_base_url or "").rstrip("/")
return f"{base}/admin/articles/{article_id}" return f"{base}{path}"
def _portal_article_url(article_id: Any) -> str:
return _portal_url(f"/admin/articles/{article_id}")
def _portal_queue_url() -> str:
return _portal_url("/admin/freigabe")
def _article_image_url(article: dict[str, Any]) -> str | None: def _article_image_url(article: dict[str, Any]) -> str | None:
@ -257,7 +265,8 @@ def notify_pending_review(article: dict[str, Any], score: int) -> None:
] ]
if tags_str: if tags_str:
text_parts.append(f"🏷 {tags_str}") text_parts.append(f"🏷 {tags_str}")
text_parts.append(f'🔗 <a href="{_portal_article_url(art_id)}">Im Portal prüfen und freigeben</a>') text_parts.append(f'🔗 <a href="{_portal_article_url(art_id)}">Diesen Artikel prüfen</a>')
text_parts.append(f'📋 <a href="{_portal_queue_url()}">Alle wartenden Artikel</a>')
text_parts.append( text_parts.append(
"<i>Erst nach der Freigabe geht der Beitrag nach WordPress und wird eingeplant.</i>" "<i>Erst nach der Freigabe geht der Beitrag nach WordPress und wird eingeplant.</i>"
) )
@ -373,6 +382,7 @@ def notify_pipeline_done(stats: dict[str, Any]) -> None:
lines.append(f"📝 Drafts erstellt: {drafts}") lines.append(f"📝 Drafts erstellt: {drafts}")
if pending_review: if pending_review:
lines.append(f"🕐 Wartet auf Freigabe: {pending_review}") lines.append(f"🕐 Wartet auf Freigabe: {pending_review}")
lines.append(f'📋 <a href="{_portal_queue_url()}">Freigabe-Warteschlange öffnen</a>')
if rejected: if rejected:
lines.append(f"🚫 Abgelehnt (Score): {rejected}") lines.append(f"🚫 Abgelehnt (Score): {rejected}")
if quality_gate_rejected: if quality_gate_rejected:

View file

@ -425,6 +425,48 @@ class TestReviewQueuePage(EditorialReviewTestBase):
self.assertIn("/admin/login", response.headers["location"]) self.assertIn("/admin/login", response.headers["location"])
class TestTelegramLinks(EditorialReviewTestBase):
def test_pending_notification_links_to_article_and_to_the_queue(self) -> None:
from backend.app import telegram_bot
os.environ["PORTAL_BASE_URL"] = "https://news.example.de"
config_module.get_settings.cache_clear()
try:
article_id = self._create_article(status="pending_review", rewritten=LONG_TEXT)
article = get_article_by_id(article_id)
with patch.object(telegram_bot, "send_photo_message") as photo, \
patch.object(telegram_bot, "send_message") as plain:
telegram_bot.notify_pending_review(article, score=88)
# Article has an image, so it goes out as a photo message.
caption = photo.call_args.kwargs["caption"]
plain.assert_not_called()
self.assertIn(f'href="https://news.example.de/admin/articles/{article_id}"', caption)
self.assertIn('href="https://news.example.de/admin/freigabe"', caption)
finally:
os.environ.pop("PORTAL_BASE_URL", None)
config_module.get_settings.cache_clear()
def test_pipeline_summary_links_to_the_queue_when_something_waits(self) -> None:
from backend.app import telegram_bot
os.environ["PORTAL_BASE_URL"] = "https://news.example.de"
config_module.get_settings.cache_clear()
try:
with patch.object(telegram_bot, "send_message") as plain:
telegram_bot.notify_pipeline_done({"ingested": 3, "processed": 3, "pending_review": 2})
text = plain.call_args.args[0]
self.assertIn('href="https://news.example.de/admin/freigabe"', text)
with patch.object(telegram_bot, "send_message") as plain:
telegram_bot.notify_pipeline_done({"ingested": 0, "processed": 0, "pending_review": 0})
self.assertNotIn("/admin/freigabe", plain.call_args.args[0])
finally:
os.environ.pop("PORTAL_BASE_URL", None)
config_module.get_settings.cache_clear()
class TestLegacyArticlesKeepPublishing(EditorialReviewTestBase): class TestLegacyArticlesKeepPublishing(EditorialReviewTestBase):
def test_approved_article_without_stamp_still_publishes(self) -> None: def test_approved_article_without_stamp_still_publishes(self) -> None:
"""Altbestand: approved before the gate existed, must not get stuck.""" """Altbestand: approved before the gate existed, must not get stuck."""

View file

@ -104,10 +104,14 @@ Wenn ein Artikel umgeschrieben wurde und geprüft werden muss:
🟢 Relevanz-Score: 87/100 🟢 Relevanz-Score: 87/100
📄 312 Wörter 📄 312 Wörter
🏷 #VanLife #Camping #Wohnmobil 🏷 #VanLife #Camping #Wohnmobil
🔗 Im Portal prüfen und freigeben 🔗 Diesen Artikel prüfen
📋 Alle wartenden Artikel
Erst nach der Freigabe geht der Beitrag nach WordPress und wird eingeplant. Erst nach der Freigabe geht der Beitrag nach WordPress und wird eingeplant.
``` ```
Zwei Links: der erste springt direkt in den gemeldeten Artikel, der zweite auf
die Freigabe-Warteschlange `/admin/freigabe`, wenn mehrere Artikel anstehen.
Diese Nachricht trägt **bewusst keinen Freigabe-Button**: Der Sinn des Gates ist, Diese Nachricht trägt **bewusst keinen Freigabe-Button**: Der Sinn des Gates ist,
dass der Artikel gelesen wurde. Freigeben, neu schreiben und verwerfen passiert dass der Artikel gelesen wurde. Freigeben, neu schreiben und verwerfen passiert
auf der Artikelseite im Portal, einen Tipp auf den Link entfernt. auf der Artikelseite im Portal, einen Tipp auf den Link entfernt.