Add mobile-first kanban board view for Chorus triage #3
4 changed files with 50 additions and 11 deletions
|
|
@ -25,7 +25,7 @@ class ThreadIngestCog(commands.Cog):
|
||||||
return
|
return
|
||||||
|
|
||||||
parsed = parse_ping_embed(parent_message.embeds[0])
|
parsed = parse_ping_embed(parent_message.embeds[0])
|
||||||
await self.backend_client.create_item(
|
item = await self.backend_client.create_item(
|
||||||
modality=parsed["modality"],
|
modality=parsed["modality"],
|
||||||
raw_content=parsed["raw_content"],
|
raw_content=parsed["raw_content"],
|
||||||
captured_at=parsed["captured_at"],
|
captured_at=parsed["captured_at"],
|
||||||
|
|
@ -33,8 +33,9 @@ class ThreadIngestCog(commands.Cog):
|
||||||
sender_id=parsed["sender_id"],
|
sender_id=parsed["sender_id"],
|
||||||
direction=parsed["direction"],
|
direction=parsed["direction"],
|
||||||
)
|
)
|
||||||
|
item_url = f"{self.config.web_base_url}/?item={item['id']}"
|
||||||
await thread.send(
|
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."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class Config:
|
||||||
inbox_channel_id: int
|
inbox_channel_id: int
|
||||||
triage_role_id: int
|
triage_role_id: int
|
||||||
backend_base_url: str
|
backend_base_url: str
|
||||||
|
web_base_url: str
|
||||||
google_voice_imap_host: str
|
google_voice_imap_host: str
|
||||||
google_voice_imap_user: str
|
google_voice_imap_user: str
|
||||||
google_voice_imap_password: str
|
google_voice_imap_password: str
|
||||||
|
|
@ -33,6 +34,7 @@ def load_config() -> Config:
|
||||||
inbox_channel_id=int(env("CHORUS_INBOX_CHANNEL_ID")),
|
inbox_channel_id=int(env("CHORUS_INBOX_CHANNEL_ID")),
|
||||||
triage_role_id=int(env("CHORUS_TRIAGE_ROLE_ID")),
|
triage_role_id=int(env("CHORUS_TRIAGE_ROLE_ID")),
|
||||||
backend_base_url=env("CHORUS_BACKEND_BASE_URL"),
|
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_host=env("CHORUS_GVOICE_IMAP_HOST"),
|
||||||
google_voice_imap_user=env("CHORUS_GVOICE_IMAP_USER"),
|
google_voice_imap_user=env("CHORUS_GVOICE_IMAP_USER"),
|
||||||
google_voice_imap_password=env("CHORUS_GVOICE_IMAP_PASSWORD"),
|
google_voice_imap_password=env("CHORUS_GVOICE_IMAP_PASSWORD"),
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,26 @@ from chorus_bot.ping import build_ping_embed
|
||||||
|
|
||||||
class _FakeConfig:
|
class _FakeConfig:
|
||||||
inbox_channel_id = 111
|
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
|
@pytest.mark.asyncio
|
||||||
async def test_thread_create_passes_direction_to_backend_client():
|
async def test_thread_create_passes_direction_to_backend_client():
|
||||||
bot = MagicMock()
|
bot = MagicMock()
|
||||||
backend_client = AsyncMock()
|
backend_client = AsyncMock()
|
||||||
|
backend_client.create_item.return_value = {"id": 42}
|
||||||
cog = ThreadIngestCog(bot, _FakeConfig(), backend_client)
|
cog = ThreadIngestCog(bot, _FakeConfig(), backend_client)
|
||||||
|
|
||||||
embed = build_ping_embed(
|
embed = build_ping_embed(
|
||||||
|
|
@ -22,16 +36,32 @@ async def test_thread_create_passes_direction_to_backend_client():
|
||||||
tzinfo=__import__("datetime").timezone.utc),
|
tzinfo=__import__("datetime").timezone.utc),
|
||||||
direction="outbound",
|
direction="outbound",
|
||||||
)
|
)
|
||||||
parent_message = MagicMock()
|
thread = _make_thread(embed)
|
||||||
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()
|
|
||||||
|
|
||||||
await cog.on_thread_create(thread)
|
await cog.on_thread_create(thread)
|
||||||
|
|
||||||
backend_client.create_item.assert_awaited_once()
|
backend_client.create_item.assert_awaited_once()
|
||||||
assert backend_client.create_item.call_args.kwargs["direction"] == "outbound"
|
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
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,13 @@ async function onSaved() {
|
||||||
await loadItems()
|
await loadItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(loadItems)
|
onMounted(async () => {
|
||||||
|
await loadItems()
|
||||||
|
const itemId = new URLSearchParams(window.location.search).get('item')
|
||||||
|
if (itemId) {
|
||||||
|
await openItem(itemId)
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue