142 lines
4.6 KiB
Python
142 lines
4.6 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 func
|
|
from models.bot import BotInstance
|
|
from models.platform import BotRequestUsage
|
|
|
|
today = _utcnow().date()
|
|
first_day = today - timedelta(days=6)
|
|
first_started_at = datetime.combine(first_day, datetime.min.time())
|
|
|
|
activity_counts = (
|
|
select(
|
|
BotRequestUsage.bot_id.label("bot_id"),
|
|
func.count(BotRequestUsage.id).label("count"),
|
|
)
|
|
.where(BotRequestUsage.started_at >= first_started_at)
|
|
.group_by(BotRequestUsage.bot_id)
|
|
.subquery()
|
|
)
|
|
|
|
stmt = (
|
|
select(
|
|
BotInstance.id,
|
|
BotInstance.name,
|
|
func.coalesce(activity_counts.c.count, 0).label("count"),
|
|
)
|
|
.select_from(BotInstance)
|
|
.join(activity_counts, activity_counts.c.bot_id == BotInstance.id, isouter=True)
|
|
.where(BotInstance.enabled.is_(True))
|
|
.order_by(func.coalesce(activity_counts.c.count, 0).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
|
|
]
|