From 208940da3f6dfa681701f0c91988622c0d51243a Mon Sep 17 00:00:00 2001 From: Oliver G Date: Mon, 24 Aug 2026 16:39:06 +0200 Subject: [PATCH] feat(telegram): Link zum Artikel und zur Freigabe-Warteschlange 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 --- CHANGELOG.md | 4 +++ backend/app/telegram_bot.py | 16 ++++++++-- backend/tests/test_editorial_review.py | 42 ++++++++++++++++++++++++++ docs/AUTOMATION.md | 6 +++- 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6caebda..0cbf8c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ - 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 +### 📨 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 - Artikelliste zeigte interne Kürzel (`pending_review`, `approved`, `error`) — jetzt überall Klartext - Status `review` (Relevanz-Warnzone 60–79) wurde als „Rewrite" angezeigt, was etwas anderes bedeutet — heißt jetzt „Niedrige Relevanz" diff --git a/backend/app/telegram_bot.py b/backend/app/telegram_bot.py index d0972c6..bc0ca7f 100644 --- a/backend/app/telegram_bot.py +++ b/backend/app/telegram_bot.py @@ -220,9 +220,17 @@ def notify_new_draft( 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("/") - 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: @@ -257,7 +265,8 @@ def notify_pending_review(article: dict[str, Any], score: int) -> None: ] if tags_str: text_parts.append(f"🏷 {tags_str}") - text_parts.append(f'🔗 Im Portal prüfen und freigeben') + text_parts.append(f'🔗 Diesen Artikel prüfen') + text_parts.append(f'📋 Alle wartenden Artikel') text_parts.append( "Erst nach der Freigabe geht der Beitrag nach WordPress und wird eingeplant." ) @@ -373,6 +382,7 @@ def notify_pipeline_done(stats: dict[str, Any]) -> None: lines.append(f"📝 Drafts erstellt: {drafts}") if pending_review: lines.append(f"🕐 Wartet auf Freigabe: {pending_review}") + lines.append(f'📋 Freigabe-Warteschlange öffnen') if rejected: lines.append(f"🚫 Abgelehnt (Score): {rejected}") if quality_gate_rejected: diff --git a/backend/tests/test_editorial_review.py b/backend/tests/test_editorial_review.py index 068183f..52eb98b 100644 --- a/backend/tests/test_editorial_review.py +++ b/backend/tests/test_editorial_review.py @@ -425,6 +425,48 @@ class TestReviewQueuePage(EditorialReviewTestBase): 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): def test_approved_article_without_stamp_still_publishes(self) -> None: """Altbestand: approved before the gate existed, must not get stuck.""" diff --git a/docs/AUTOMATION.md b/docs/AUTOMATION.md index 776cf9c..46c4f86 100644 --- a/docs/AUTOMATION.md +++ b/docs/AUTOMATION.md @@ -104,10 +104,14 @@ Wenn ein Artikel umgeschrieben wurde und geprüft werden muss: 🟢 Relevanz-Score: 87/100 📄 312 Wörter 🏷 #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. ``` +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, dass der Artikel gelesen wurde. Freigeben, neu schreiben und verwerfen passiert auf der Artikelseite im Portal, einen Tipp auf den Link entfernt.