mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-05-04 00:40:44 +02:00
272 lines
8.4 KiB
Python
272 lines
8.4 KiB
Python
import datetime
|
|
from collections import defaultdict
|
|
from typing import List
|
|
|
|
from fastapi import APIRouter
|
|
from fastapi import Depends
|
|
from fastapi import HTTPException
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.orm import Session
|
|
|
|
from ee.onyx.db.analytics import fetch_assistant_message_analytics
|
|
from ee.onyx.db.analytics import fetch_assistant_unique_users
|
|
from ee.onyx.db.analytics import fetch_assistant_unique_users_total
|
|
from ee.onyx.db.analytics import fetch_onyxbot_analytics
|
|
from ee.onyx.db.analytics import fetch_per_user_query_analytics
|
|
from ee.onyx.db.analytics import fetch_persona_message_analytics
|
|
from ee.onyx.db.analytics import fetch_persona_unique_users
|
|
from ee.onyx.db.analytics import fetch_query_analytics
|
|
from ee.onyx.db.analytics import user_can_view_assistant_stats
|
|
from onyx.auth.users import current_admin_user
|
|
from onyx.auth.users import current_user
|
|
from onyx.db.engine import get_session
|
|
from onyx.db.models import User
|
|
|
|
router = APIRouter(prefix="/analytics")
|
|
|
|
|
|
_DEFAULT_LOOKBACK_DAYS = 30
|
|
|
|
|
|
class QueryAnalyticsResponse(BaseModel):
|
|
total_queries: int
|
|
total_likes: int
|
|
total_dislikes: int
|
|
date: datetime.date
|
|
|
|
|
|
@router.get("/admin/query")
|
|
def get_query_analytics(
|
|
start: datetime.datetime | None = None,
|
|
end: datetime.datetime | None = None,
|
|
_: User | None = Depends(current_admin_user),
|
|
db_session: Session = Depends(get_session),
|
|
) -> list[QueryAnalyticsResponse]:
|
|
daily_query_usage_info = fetch_query_analytics(
|
|
start=start
|
|
or (
|
|
datetime.datetime.utcnow() - datetime.timedelta(days=_DEFAULT_LOOKBACK_DAYS)
|
|
), # default is 30d lookback
|
|
end=end or datetime.datetime.utcnow(),
|
|
db_session=db_session,
|
|
)
|
|
return [
|
|
QueryAnalyticsResponse(
|
|
total_queries=total_queries,
|
|
total_likes=total_likes,
|
|
total_dislikes=total_dislikes,
|
|
date=date,
|
|
)
|
|
for total_queries, total_likes, total_dislikes, date in daily_query_usage_info
|
|
]
|
|
|
|
|
|
class UserAnalyticsResponse(BaseModel):
|
|
total_active_users: int
|
|
date: datetime.date
|
|
|
|
|
|
@router.get("/admin/user")
|
|
def get_user_analytics(
|
|
start: datetime.datetime | None = None,
|
|
end: datetime.datetime | None = None,
|
|
_: User | None = Depends(current_admin_user),
|
|
db_session: Session = Depends(get_session),
|
|
) -> list[UserAnalyticsResponse]:
|
|
daily_query_usage_info_per_user = fetch_per_user_query_analytics(
|
|
start=start
|
|
or (
|
|
datetime.datetime.utcnow() - datetime.timedelta(days=_DEFAULT_LOOKBACK_DAYS)
|
|
), # default is 30d lookback
|
|
end=end or datetime.datetime.utcnow(),
|
|
db_session=db_session,
|
|
)
|
|
|
|
user_analytics: dict[datetime.date, int] = defaultdict(int)
|
|
for __, ___, ____, date, _____ in daily_query_usage_info_per_user:
|
|
user_analytics[date] += 1
|
|
return [
|
|
UserAnalyticsResponse(
|
|
total_active_users=cnt,
|
|
date=date,
|
|
)
|
|
for date, cnt in user_analytics.items()
|
|
]
|
|
|
|
|
|
class OnyxbotAnalyticsResponse(BaseModel):
|
|
total_queries: int
|
|
auto_resolved: int
|
|
date: datetime.date
|
|
|
|
|
|
@router.get("/admin/onyxbot")
|
|
def get_onyxbot_analytics(
|
|
start: datetime.datetime | None = None,
|
|
end: datetime.datetime | None = None,
|
|
_: User | None = Depends(current_admin_user),
|
|
db_session: Session = Depends(get_session),
|
|
) -> list[OnyxbotAnalyticsResponse]:
|
|
daily_onyxbot_info = fetch_onyxbot_analytics(
|
|
start=start
|
|
or (
|
|
datetime.datetime.utcnow() - datetime.timedelta(days=_DEFAULT_LOOKBACK_DAYS)
|
|
), # default is 30d lookback
|
|
end=end or datetime.datetime.utcnow(),
|
|
db_session=db_session,
|
|
)
|
|
|
|
resolution_results = [
|
|
OnyxbotAnalyticsResponse(
|
|
total_queries=total_queries,
|
|
# If it hits negatives, something has gone wrong...
|
|
auto_resolved=max(0, total_queries - total_negatives),
|
|
date=date,
|
|
)
|
|
for total_queries, total_negatives, date in daily_onyxbot_info
|
|
]
|
|
|
|
return resolution_results
|
|
|
|
|
|
class PersonaMessageAnalyticsResponse(BaseModel):
|
|
total_messages: int
|
|
date: datetime.date
|
|
persona_id: int
|
|
|
|
|
|
@router.get("/admin/persona/messages")
|
|
def get_persona_messages(
|
|
persona_id: int,
|
|
start: datetime.datetime | None = None,
|
|
end: datetime.datetime | None = None,
|
|
_: User | None = Depends(current_admin_user),
|
|
db_session: Session = Depends(get_session),
|
|
) -> list[PersonaMessageAnalyticsResponse]:
|
|
"""Fetch daily message counts for a single persona within the given time range."""
|
|
start = start or (
|
|
datetime.datetime.utcnow() - datetime.timedelta(days=_DEFAULT_LOOKBACK_DAYS)
|
|
)
|
|
end = end or datetime.datetime.utcnow()
|
|
|
|
persona_message_counts = []
|
|
for count, date in fetch_persona_message_analytics(
|
|
db_session=db_session,
|
|
persona_id=persona_id,
|
|
start=start,
|
|
end=end,
|
|
):
|
|
persona_message_counts.append(
|
|
PersonaMessageAnalyticsResponse(
|
|
total_messages=count,
|
|
date=date,
|
|
persona_id=persona_id,
|
|
)
|
|
)
|
|
|
|
return persona_message_counts
|
|
|
|
|
|
class PersonaUniqueUsersResponse(BaseModel):
|
|
unique_users: int
|
|
date: datetime.date
|
|
persona_id: int
|
|
|
|
|
|
@router.get("/admin/persona/unique-users")
|
|
def get_persona_unique_users(
|
|
persona_id: int,
|
|
start: datetime.datetime,
|
|
end: datetime.datetime,
|
|
_: User | None = Depends(current_admin_user),
|
|
db_session: Session = Depends(get_session),
|
|
) -> list[PersonaUniqueUsersResponse]:
|
|
"""Get unique users per day for a single persona."""
|
|
unique_user_counts = []
|
|
daily_counts = fetch_persona_unique_users(
|
|
db_session=db_session,
|
|
persona_id=persona_id,
|
|
start=start,
|
|
end=end,
|
|
)
|
|
for count, date in daily_counts:
|
|
unique_user_counts.append(
|
|
PersonaUniqueUsersResponse(
|
|
unique_users=count,
|
|
date=date,
|
|
persona_id=persona_id,
|
|
)
|
|
)
|
|
return unique_user_counts
|
|
|
|
|
|
class AssistantDailyUsageResponse(BaseModel):
|
|
date: datetime.date
|
|
total_messages: int
|
|
total_unique_users: int
|
|
|
|
|
|
class AssistantStatsResponse(BaseModel):
|
|
daily_stats: List[AssistantDailyUsageResponse]
|
|
total_messages: int
|
|
total_unique_users: int
|
|
|
|
|
|
@router.get("/assistant/{assistant_id}/stats")
|
|
def get_assistant_stats(
|
|
assistant_id: int,
|
|
start: datetime.datetime | None = None,
|
|
end: datetime.datetime | None = None,
|
|
user: User | None = Depends(current_user),
|
|
db_session: Session = Depends(get_session),
|
|
) -> AssistantStatsResponse:
|
|
"""
|
|
Returns daily message and unique user counts for a user's assistant,
|
|
along with the overall total messages and total distinct users.
|
|
"""
|
|
start = start or (
|
|
datetime.datetime.utcnow() - datetime.timedelta(days=_DEFAULT_LOOKBACK_DAYS)
|
|
)
|
|
end = end or datetime.datetime.utcnow()
|
|
|
|
if not user_can_view_assistant_stats(db_session, user, assistant_id):
|
|
raise HTTPException(
|
|
status_code=403, detail="Not allowed to access this assistant's stats."
|
|
)
|
|
|
|
# Pull daily usage from the DB calls
|
|
messages_data = fetch_assistant_message_analytics(
|
|
db_session, assistant_id, start, end
|
|
)
|
|
unique_users_data = fetch_assistant_unique_users(
|
|
db_session, assistant_id, start, end
|
|
)
|
|
|
|
# Map each day => (messages, unique_users).
|
|
daily_messages_map = {date: count for count, date in messages_data}
|
|
daily_unique_users_map = {date: count for count, date in unique_users_data}
|
|
all_dates = set(daily_messages_map.keys()) | set(daily_unique_users_map.keys())
|
|
|
|
# Merge both sets of metrics by date
|
|
daily_results: list[AssistantDailyUsageResponse] = []
|
|
for date in sorted(all_dates):
|
|
daily_results.append(
|
|
AssistantDailyUsageResponse(
|
|
date=date,
|
|
total_messages=daily_messages_map.get(date, 0),
|
|
total_unique_users=daily_unique_users_map.get(date, 0),
|
|
)
|
|
)
|
|
|
|
# Now pull a single total distinct user count across the entire time range
|
|
total_msgs = sum(d.total_messages for d in daily_results)
|
|
total_users = fetch_assistant_unique_users_total(
|
|
db_session, assistant_id, start, end
|
|
)
|
|
|
|
return AssistantStatsResponse(
|
|
daily_stats=daily_results,
|
|
total_messages=total_msgs,
|
|
total_unique_users=total_users,
|
|
)
|