178 lines
5.5 KiB
Python
178 lines
5.5 KiB
Python
"""Analytics service for platform-wide statistics."""
|
|
import logging
|
|
from datetime import datetime, timedelta, timezone
|
|
from gatehouse_app.models.organization.organization import Organization
|
|
from gatehouse_app.models.user.user import User
|
|
from gatehouse_app.models.user.session import Session
|
|
from gatehouse_app.models.superadmin_audit_log import SuperadminAuditLog
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SuperadminAnalyticsService:
|
|
"""Service for platform-wide analytics and statistics."""
|
|
|
|
@staticmethod
|
|
def get_dashboard_stats() -> dict:
|
|
"""Get dashboard statistics for the overview page.
|
|
|
|
Returns:
|
|
Dashboard stats including org count, user count, etc.
|
|
"""
|
|
now = datetime.now(timezone.utc)
|
|
thirty_days_ago = now - timedelta(days=30)
|
|
|
|
# Total organizations
|
|
total_orgs = Organization.query.filter(Organization.deleted_at.is_(None)).count()
|
|
active_orgs = Organization.query.filter(
|
|
Organization.deleted_at.is_(None),
|
|
Organization.is_active == True, # noqa: E712
|
|
).count()
|
|
|
|
# Total users
|
|
total_users = User.query.filter(User.deleted_at.is_(None)).count()
|
|
|
|
# Active sessions
|
|
active_sessions = Session.query.filter(
|
|
Session.deleted_at.is_(None),
|
|
Session.status == "active",
|
|
).count()
|
|
|
|
# New signups in last 30 days
|
|
new_users_30d = User.query.filter(
|
|
User.deleted_at.is_(None),
|
|
User.created_at >= thirty_days_ago,
|
|
).count()
|
|
|
|
# New organizations in last 30 days
|
|
new_orgs_30d = Organization.query.filter(
|
|
Organization.deleted_at.is_(None),
|
|
Organization.created_at >= thirty_days_ago,
|
|
).count()
|
|
|
|
# Suspended organizations
|
|
suspended_orgs = Organization.query.filter(
|
|
Organization.deleted_at.is_(None),
|
|
Organization.is_active == False, # noqa: E712
|
|
).count()
|
|
|
|
return {
|
|
"total_organizations": total_orgs,
|
|
"active_organizations": active_orgs,
|
|
"suspended_organizations": suspended_orgs,
|
|
"total_users": total_users,
|
|
"active_sessions": active_sessions,
|
|
"new_users_30d": new_users_30d,
|
|
"new_orgs_30d": new_orgs_30d,
|
|
"generated_at": now.isoformat() + "Z",
|
|
}
|
|
|
|
@staticmethod
|
|
def get_signup_trends(days: int = 30) -> dict:
|
|
"""Get signup trends over time.
|
|
|
|
Args:
|
|
days: Number of days to analyze
|
|
|
|
Returns:
|
|
Daily signup data
|
|
"""
|
|
now = datetime.now(timezone.utc)
|
|
start_date = now - timedelta(days=days)
|
|
|
|
# Get all users created in period
|
|
users = User.query.filter(
|
|
User.deleted_at.is_(None),
|
|
User.created_at >= start_date,
|
|
).all()
|
|
|
|
# Group by day
|
|
daily_signups = {}
|
|
for i in range(days):
|
|
date = (start_date + timedelta(days=i)).strftime("%Y-%m-%d")
|
|
daily_signups[date] = 0
|
|
|
|
for user in users:
|
|
date = user.created_at.strftime("%Y-%m-%d")
|
|
if date in daily_signups:
|
|
daily_signups[date] += 1
|
|
|
|
# Convert to list
|
|
history = [
|
|
{"date": date, "value": count}
|
|
for date, count in sorted(daily_signups.items())
|
|
]
|
|
|
|
return {
|
|
"period_start": start_date.isoformat() + "Z",
|
|
"period_end": now.isoformat() + "Z",
|
|
"total": len(users),
|
|
"history": history,
|
|
}
|
|
|
|
@staticmethod
|
|
def get_org_distribution() -> dict:
|
|
"""Get distribution of organizations by size.
|
|
|
|
Returns:
|
|
Organization size distribution
|
|
"""
|
|
orgs = Organization.query.filter(Organization.deleted_at.is_(None)).all()
|
|
|
|
distribution = {
|
|
"solo": 0, # 1 user
|
|
"small": 0, # 2-10 users
|
|
"medium": 0, # 11-50 users
|
|
"large": 0, # 51-200 users
|
|
"enterprise": 0, # 200+ users
|
|
}
|
|
|
|
for org in orgs:
|
|
count = org.get_member_count()
|
|
if count == 1:
|
|
distribution["solo"] += 1
|
|
elif count <= 10:
|
|
distribution["small"] += 1
|
|
elif count <= 50:
|
|
distribution["medium"] += 1
|
|
elif count <= 200:
|
|
distribution["large"] += 1
|
|
else:
|
|
distribution["enterprise"] += 1
|
|
|
|
return {
|
|
"distribution": distribution,
|
|
"total_orgs": len(orgs),
|
|
}
|
|
|
|
@staticmethod
|
|
def get_recent_activity(limit: int = 20) -> list:
|
|
"""Get recent superadmin actions.
|
|
|
|
Args:
|
|
limit: Maximum number of actions to return
|
|
|
|
Returns:
|
|
List of recent audit log entries
|
|
"""
|
|
logs = SuperadminAuditLog.query.filter(
|
|
SuperadminAuditLog.deleted_at.is_(None),
|
|
).order_by(
|
|
SuperadminAuditLog.created_at.desc()
|
|
).limit(limit).all()
|
|
|
|
return [
|
|
{
|
|
"id": log.id,
|
|
"superadmin_id": log.superadmin_id,
|
|
"action": log.action,
|
|
"resource_type": log.resource_type,
|
|
"resource_id": log.resource_id,
|
|
"extra_data": log.extra_data,
|
|
"ip_address": log.ip_address,
|
|
"user_agent": log.user_agent,
|
|
"created_at": log.created_at.isoformat() + "Z" if log.created_at else None,
|
|
}
|
|
for log in logs
|
|
]
|