132 lines
4.3 KiB
Python
132 lines
4.3 KiB
Python
import json
|
|
from datetime import datetime, timedelta
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from sqlalchemy import delete as sql_delete
|
|
from sqlmodel import Session, select
|
|
|
|
from models.platform import BotActivityEvent
|
|
from schemas.platform import PlatformActivityItem
|
|
from services.platform_settings_service import get_activity_event_retention_days
|
|
|
|
ACTIVITY_EVENT_PRUNE_INTERVAL = timedelta(minutes=10)
|
|
OPERATIONAL_ACTIVITY_EVENT_TYPES = {
|
|
"bot_created",
|
|
"bot_started",
|
|
"bot_stopped",
|
|
"bot_warning",
|
|
"bot_enabled",
|
|
"bot_disabled",
|
|
"bot_deactivated",
|
|
"command_submitted",
|
|
"command_failed",
|
|
"history_cleared",
|
|
}
|
|
|
|
_last_activity_event_prune_at: Optional[datetime] = None
|
|
|
|
|
|
def _utcnow() -> datetime:
|
|
return datetime.utcnow()
|
|
|
|
|
|
def prune_expired_activity_events(session: Session, force: bool = False) -> int:
|
|
global _last_activity_event_prune_at
|
|
|
|
now = _utcnow()
|
|
if not force and _last_activity_event_prune_at and now - _last_activity_event_prune_at < ACTIVITY_EVENT_PRUNE_INTERVAL:
|
|
return 0
|
|
|
|
retention_days = get_activity_event_retention_days(session)
|
|
cutoff = now - timedelta(days=retention_days)
|
|
result = session.exec(sql_delete(BotActivityEvent).where(BotActivityEvent.created_at < cutoff))
|
|
_last_activity_event_prune_at = now
|
|
return int(getattr(result, "rowcount", 0) or 0)
|
|
|
|
|
|
def record_activity_event(
|
|
session: Session,
|
|
bot_id: str,
|
|
event_type: str,
|
|
request_id: Optional[str] = None,
|
|
channel: str = "dashboard",
|
|
detail: Optional[str] = None,
|
|
metadata: Optional[Dict[str, Any]] = None,
|
|
) -> None:
|
|
normalized_event_type = str(event_type or "unknown").strip().lower() or "unknown"
|
|
if normalized_event_type not in OPERATIONAL_ACTIVITY_EVENT_TYPES:
|
|
return
|
|
prune_expired_activity_events(session, force=False)
|
|
row = BotActivityEvent(
|
|
bot_id=bot_id,
|
|
request_id=request_id,
|
|
event_type=normalized_event_type,
|
|
channel=str(channel or "dashboard").strip().lower() or "dashboard",
|
|
detail=(str(detail or "").strip() or None),
|
|
metadata_json=json.dumps(metadata or {}, ensure_ascii=False) if metadata else None,
|
|
created_at=_utcnow(),
|
|
)
|
|
session.add(row)
|
|
|
|
|
|
def list_activity_events(
|
|
session: Session,
|
|
bot_id: Optional[str] = None,
|
|
limit: int = 100,
|
|
) -> List[Dict[str, Any]]:
|
|
deleted = prune_expired_activity_events(session, force=False)
|
|
if deleted > 0:
|
|
session.commit()
|
|
safe_limit = max(1, min(int(limit), 500))
|
|
stmt = select(BotActivityEvent).order_by(BotActivityEvent.created_at.desc(), BotActivityEvent.id.desc()).limit(safe_limit)
|
|
if bot_id:
|
|
stmt = stmt.where(BotActivityEvent.bot_id == bot_id)
|
|
rows = session.exec(stmt).all()
|
|
items: List[Dict[str, Any]] = []
|
|
for row in rows:
|
|
try:
|
|
metadata = json.loads(row.metadata_json or "{}")
|
|
except Exception:
|
|
metadata = {}
|
|
items.append(
|
|
PlatformActivityItem(
|
|
id=int(row.id or 0),
|
|
bot_id=row.bot_id,
|
|
request_id=row.request_id,
|
|
event_type=row.event_type,
|
|
channel=row.channel,
|
|
detail=row.detail,
|
|
metadata=metadata if isinstance(metadata, dict) else {},
|
|
created_at=row.created_at.isoformat() + "Z",
|
|
).model_dump()
|
|
)
|
|
return items
|
|
|
|
|
|
def get_bot_activity_stats(session: Session) -> List[Dict[str, Any]]:
|
|
from sqlalchemy import and_, func
|
|
from models.bot import BotInstance
|
|
|
|
stmt = (
|
|
select(BotInstance.id, BotInstance.name, func.count(BotActivityEvent.id).label("count"))
|
|
.select_from(BotInstance)
|
|
.join(
|
|
BotActivityEvent,
|
|
and_(
|
|
BotActivityEvent.bot_id == BotInstance.id,
|
|
BotActivityEvent.request_id.is_not(None),
|
|
func.length(func.trim(BotActivityEvent.request_id)) > 0,
|
|
),
|
|
isouter=True,
|
|
)
|
|
.where(BotInstance.enabled.is_(True))
|
|
.group_by(BotInstance.id, BotInstance.name)
|
|
.order_by(func.count(BotActivityEvent.id).desc(), BotInstance.name.asc(), BotInstance.id.asc())
|
|
)
|
|
results = session.exec(stmt).all()
|
|
|
|
return [
|
|
{"bot_id": row[0], "name": row[1] or row[0], "count": row[2]}
|
|
for row in results
|
|
]
|