feat: log bot command invocations to the event stream
Bot commands were the only user-initiated path that didn't surface in the dashboard. They now produce ``command_handled`` / ``command_rate_limited`` / ``command_failed`` rows in ``EventLog`` alongside tracker and action events. Backend - ``EventLog`` gains nullable ``command_tracker_id`` / ``telegram_bot_id`` FKs plus deletion-snapshot name columns (idempotent migration). - New ``_log_command_event`` helper emits one row per invocation at the three branches in ``handle_command``. Logging failures are swallowed so they cannot block the user-visible reply. - Telegram ``from`` is captured in poller and webhook, whitelisted to identity fields by ``_normalize_issuer`` (drops ``language_code`` and any future PII), persisted under ``details.issuer``. - ``/api/status`` resolves live ``CommandTracker`` / ``TelegramBot`` names (mirroring the action pattern) and exposes ``tracker_id``, ``command_tracker_id``, ``telegram_bot_id`` so the frontend can deep-link. Frontend - Event rows are now clickable and open a detail modal with full provenance (bot → chat → issuer → provider), raw ``details`` JSON, and per-entity action buttons. - Buttons use the existing ``requestHighlight`` + ``goto`` crosslink pattern, so clicking lands on the entity's list page with that specific card scrolled into view and pulsing. - Auto-refresh dropdown (Off / 10s / 30s / 1m / 5m) persisted in ``localStorage``; ticker pauses while the tab is hidden. - Event-type filter, dashboard verb labels, and gradients extended for the three new ``command_*`` types. - Filled in pre-existing missing i18n keys (``common.hide`` / ``common.show`` / ``commandConfig.noCommandsForProvider``). Tests - New ``test_command_event_logging.py`` covers subject formatting, issuer normalization, the three event branches, and graceful failure when the DB is unreachable. ``pytest packages/server/tests/`` → 96/96.
This commit is contained in:
@@ -232,7 +232,9 @@ async def _poll_bot(bot_id: int) -> None:
|
||||
# Copy attributes before session closes to avoid detached-instance errors
|
||||
from types import SimpleNamespace
|
||||
bot_token = bot.token
|
||||
bot_obj = SimpleNamespace(id=bot.id, name=bot.name, token=bot.token)
|
||||
bot_obj = SimpleNamespace(
|
||||
id=bot.id, name=bot.name, token=bot.token, user_id=bot.user_id,
|
||||
)
|
||||
|
||||
offset = _last_update_id.get(bot_id, 0)
|
||||
|
||||
@@ -331,7 +333,11 @@ async def _poll_bot(bot_id: int) -> None:
|
||||
async with telegram_chat_action(
|
||||
bot_token, chat_id, classify_command_chat_action(text),
|
||||
):
|
||||
responses = await handle_command(bot_obj, chat_id, text, language_code=effective_lang)
|
||||
responses = await handle_command(
|
||||
bot_obj, chat_id, text,
|
||||
language_code=effective_lang,
|
||||
issuer=from_user or None,
|
||||
)
|
||||
if not responses:
|
||||
_LOGGER.info(
|
||||
"Command produced no response (cmd=%r, poll) after %.0f ms",
|
||||
|
||||
Reference in New Issue
Block a user