From e8efb6566ff5581747266bc5050c3d3cd3224ff4 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Tue, 14 Jul 2026 11:07:09 -0700 Subject: [PATCH 1/9] feat: link the Chorus item in the bot's post-ingest reply --- bot/chorus_bot/cogs/thread_ingest.py | 5 +-- bot/chorus_bot/config.py | 2 ++ bot/tests/test_thread_ingest.py | 46 +++++++++++++++++++++++----- frontend/src/App.vue | 8 ++++- 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/bot/chorus_bot/cogs/thread_ingest.py b/bot/chorus_bot/cogs/thread_ingest.py index 6278618..f540e66 100644 --- a/bot/chorus_bot/cogs/thread_ingest.py +++ b/bot/chorus_bot/cogs/thread_ingest.py @@ -25,7 +25,7 @@ class ThreadIngestCog(commands.Cog): return parsed = parse_ping_embed(parent_message.embeds[0]) - await self.backend_client.create_item( + item = await self.backend_client.create_item( modality=parsed["modality"], raw_content=parsed["raw_content"], captured_at=parsed["captured_at"], @@ -33,8 +33,9 @@ class ThreadIngestCog(commands.Cog): sender_id=parsed["sender_id"], direction=parsed["direction"], ) + item_url = f"{self.config.web_base_url}/?item={item['id']}" await thread.send( - "Logged in Chorus - open the app to fill in details and track this one." + f"Logged in Chorus - {item_url} to fill in details and track this one." ) diff --git a/bot/chorus_bot/config.py b/bot/chorus_bot/config.py index cfaaf76..3ad796b 100644 --- a/bot/chorus_bot/config.py +++ b/bot/chorus_bot/config.py @@ -9,6 +9,7 @@ class Config: inbox_channel_id: int triage_role_id: int backend_base_url: str + web_base_url: str google_voice_imap_host: str google_voice_imap_user: str google_voice_imap_password: str @@ -33,6 +34,7 @@ def load_config() -> Config: inbox_channel_id=int(env("CHORUS_INBOX_CHANNEL_ID")), triage_role_id=int(env("CHORUS_TRIAGE_ROLE_ID")), backend_base_url=env("CHORUS_BACKEND_BASE_URL"), + web_base_url=os.environ.get("CHORUS_WEB_BASE_URL", "https://chorus.circuitforge.tech"), google_voice_imap_host=env("CHORUS_GVOICE_IMAP_HOST"), google_voice_imap_user=env("CHORUS_GVOICE_IMAP_USER"), google_voice_imap_password=env("CHORUS_GVOICE_IMAP_PASSWORD"), diff --git a/bot/tests/test_thread_ingest.py b/bot/tests/test_thread_ingest.py index 05b439d..950b032 100644 --- a/bot/tests/test_thread_ingest.py +++ b/bot/tests/test_thread_ingest.py @@ -7,12 +7,26 @@ from chorus_bot.ping import build_ping_embed class _FakeConfig: inbox_channel_id = 111 + web_base_url = "https://chorus.circuitforge.tech" + + +def _make_thread(embed): + parent_message = MagicMock() + parent_message.embeds = [embed] + + thread = MagicMock(spec=discord.Thread) + thread.id = 555 + thread.parent_id = 111 + thread.parent.fetch_message = AsyncMock(return_value=parent_message) + thread.send = AsyncMock() + return thread @pytest.mark.asyncio async def test_thread_create_passes_direction_to_backend_client(): bot = MagicMock() backend_client = AsyncMock() + backend_client.create_item.return_value = {"id": 42} cog = ThreadIngestCog(bot, _FakeConfig(), backend_client) embed = build_ping_embed( @@ -22,16 +36,32 @@ async def test_thread_create_passes_direction_to_backend_client(): tzinfo=__import__("datetime").timezone.utc), direction="outbound", ) - parent_message = MagicMock() - parent_message.embeds = [embed] - - thread = MagicMock(spec=discord.Thread) - thread.id = 555 - thread.parent_id = 111 - thread.parent.fetch_message = AsyncMock(return_value=parent_message) - thread.send = AsyncMock() + thread = _make_thread(embed) await cog.on_thread_create(thread) backend_client.create_item.assert_awaited_once() assert backend_client.create_item.call_args.kwargs["direction"] == "outbound" + + +@pytest.mark.asyncio +async def test_thread_create_reply_includes_item_link(): + bot = MagicMock() + backend_client = AsyncMock() + backend_client.create_item.return_value = {"id": 42} + cog = ThreadIngestCog(bot, _FakeConfig(), backend_client) + + embed = build_ping_embed( + modality="lead_found", sender_id=None, snippet="found post", + raw_content="Someone posting about book donation", + captured_at=__import__("datetime").datetime(2026, 7, 13, 7, 0, 0, + tzinfo=__import__("datetime").timezone.utc), + direction="outbound", + ) + thread = _make_thread(embed) + + await cog.on_thread_create(thread) + + thread.send.assert_awaited_once() + reply_text = thread.send.call_args.args[0] + assert "https://chorus.circuitforge.tech/?item=42" in reply_text diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 60a4d35..d056c0e 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -36,7 +36,13 @@ async function onSaved() { await loadItems() } -onMounted(loadItems) +onMounted(async () => { + await loadItems() + const itemId = new URLSearchParams(window.location.search).get('item') + if (itemId) { + await openItem(itemId) + } +})