diff --git a/backend/ee/danswer/server/query_history/api.py b/backend/ee/danswer/server/query_history/api.py
index f50b9cc52..7b86c16ab 100644
--- a/backend/ee/danswer/server/query_history/api.py
+++ b/backend/ee/danswer/server/query_history/api.py
@@ -92,10 +92,12 @@ class ChatSessionMinimal(BaseModel):
name: str | None
first_user_message: str
first_ai_message: str
- persona_name: str | None
+ assistant_id: int | None
+ assistant_name: str | None
time_created: datetime
feedback_type: QAFeedbackType | Literal["mixed"] | None
flow_type: SessionType
+ conversation_length: int
class ChatSessionSnapshot(BaseModel):
@@ -103,7 +105,8 @@ class ChatSessionSnapshot(BaseModel):
user_email: str
name: str | None
messages: list[MessageSnapshot]
- persona_name: str | None
+ assistant_id: int | None
+ assistant_name: str | None
time_created: datetime
flow_type: SessionType
@@ -146,7 +149,7 @@ class QuestionAnswerPairSnapshot(BaseModel):
retrieved_documents=ai_message.documents,
feedback_type=ai_message.feedback_type,
feedback_text=ai_message.feedback_text,
- persona_name=chat_session_snapshot.persona_name,
+ persona_name=chat_session_snapshot.assistant_name,
user_email=get_display_email(chat_session_snapshot.user_email),
time_created=user_message.time_created,
flow_type=chat_session_snapshot.flow_type,
@@ -257,12 +260,20 @@ def fetch_and_process_chat_session_history_minimal(
name=chat_session.description,
first_user_message=first_user_message,
first_ai_message=first_ai_message,
- persona_name=chat_session.persona.name
- if chat_session.persona
- else None,
+ assistant_id=chat_session.persona_id,
+ assistant_name=(
+ chat_session.persona.name if chat_session.persona else None
+ ),
time_created=chat_session.time_created,
feedback_type=feedback_type,
flow_type=flow_type,
+ conversation_length=len(
+ [
+ m
+ for m in chat_session.messages
+ if m.message_type != MessageType.SYSTEM
+ ]
+ ),
)
)
@@ -327,7 +338,8 @@ def snapshot_from_chat_session(
for message in messages
if message.message_type != MessageType.SYSTEM
],
- persona_name=chat_session.persona.name if chat_session.persona else None,
+ assistant_id=chat_session.persona_id,
+ assistant_name=chat_session.persona.name if chat_session.persona else None,
time_created=chat_session.time_created,
flow_type=flow_type,
)
diff --git a/backend/tests/integration/tests/query-history/test_query_history.py b/backend/tests/integration/tests/query-history/test_query_history.py
new file mode 100644
index 000000000..bd66bb049
--- /dev/null
+++ b/backend/tests/integration/tests/query-history/test_query_history.py
@@ -0,0 +1,119 @@
+from datetime import datetime
+from datetime import timedelta
+from datetime import timezone
+
+import requests
+
+from danswer.configs.constants import QAFeedbackType
+from danswer.configs.constants import SessionType
+from tests.integration.common_utils.constants import API_SERVER_URL
+from tests.integration.common_utils.managers.api_key import APIKeyManager
+from tests.integration.common_utils.managers.cc_pair import CCPairManager
+from tests.integration.common_utils.managers.chat import ChatSessionManager
+from tests.integration.common_utils.managers.document import DocumentManager
+from tests.integration.common_utils.managers.llm_provider import LLMProviderManager
+from tests.integration.common_utils.managers.user import UserManager
+from tests.integration.common_utils.test_models import DATestUser
+
+
+def test_query_history_endpoints(reset: None) -> None:
+ # Create admin user and required resources
+ admin_user: DATestUser = UserManager.create(name="admin_user")
+ cc_pair = CCPairManager.create_from_scratch(user_performing_action=admin_user)
+ api_key = APIKeyManager.create(user_performing_action=admin_user)
+ LLMProviderManager.create(user_performing_action=admin_user)
+
+ # Seed a document
+ cc_pair.documents = []
+ cc_pair.documents.append(
+ DocumentManager.seed_doc_with_content(
+ cc_pair=cc_pair,
+ content="The company's revenue in Q1 was $1M",
+ api_key=api_key,
+ )
+ )
+
+ # Create chat session and send a message
+ chat_session = ChatSessionManager.create(
+ persona_id=0,
+ description="Test chat session",
+ user_performing_action=admin_user,
+ )
+
+ ChatSessionManager.send_message(
+ chat_session_id=chat_session.id,
+ message="What was the Q1 revenue?",
+ user_performing_action=admin_user,
+ )
+
+ # Test get chat session history endpoint
+ end_time = datetime.now(tz=timezone.utc)
+ start_time = end_time - timedelta(days=1)
+
+ response = requests.get(
+ f"{API_SERVER_URL}/admin/chat-session-history",
+ params={
+ "start": start_time.isoformat(),
+ "end": end_time.isoformat(),
+ },
+ headers=admin_user.headers,
+ )
+ assert response.status_code == 200
+ history_response = response.json()
+
+ # Verify we got back the one chat session we created
+ assert len(history_response) == 1
+
+ # Verify the first chat session details
+ first_session = history_response[0]
+ first_chat_id = first_session["id"]
+ assert first_session["user_email"] == admin_user.email
+ assert first_session["name"] == "Test chat session"
+ assert first_session["first_user_message"] == "What was the Q1 revenue?"
+ assert first_session["first_ai_message"] is not None
+ assert first_session["assistant_id"] == 0
+ assert first_session["feedback_type"] is None
+ assert first_session["flow_type"] == SessionType.CHAT.value
+ assert first_session["conversation_length"] == 2 # User message + AI response
+
+ # Test get specific chat session endpoint
+ response = requests.get(
+ f"{API_SERVER_URL}/admin/chat-session-history/{first_chat_id}",
+ headers=admin_user.headers,
+ )
+ assert response.status_code == 200
+ session_details = response.json()
+
+ # Verify the session details
+ assert session_details["id"] == first_chat_id
+ assert len(session_details["messages"]) > 0
+ assert session_details["flow_type"] == SessionType.CHAT.value
+
+ # Test CSV export endpoint
+ response = requests.get(
+ f"{API_SERVER_URL}/admin/query-history-csv",
+ headers=admin_user.headers,
+ )
+ assert response.status_code == 200
+ assert response.headers["Content-Type"] == "text/csv; charset=utf-8"
+ assert "Content-Disposition" in response.headers
+
+ # Verify CSV content
+ csv_content = response.content.decode()
+ assert "chat_session_id" in csv_content
+ assert "user_message" in csv_content
+ assert "ai_response" in csv_content
+
+ # Test filtering by feedback
+ response = requests.get(
+ f"{API_SERVER_URL}/admin/chat-session-history",
+ params={
+ "feedback_type": QAFeedbackType.LIKE.value,
+ "start": start_time.isoformat(),
+ "end": end_time.isoformat(),
+ },
+ headers=admin_user.headers,
+ )
+ assert response.status_code == 200
+ history_response = response.json()
+ assert len(history_response) == 0
diff --git a/web/src/app/ee/admin/performance/query-history/QueryHistoryTable.tsx b/web/src/app/ee/admin/performance/query-history/QueryHistoryTable.tsx
index b46d77c27..55ede0e8b 100644
--- a/web/src/app/ee/admin/performance/query-history/QueryHistoryTable.tsx
+++ b/web/src/app/ee/admin/performance/query-history/QueryHistoryTable.tsx
@@ -56,7 +56,7 @@ function QueryHistoryTableRow({
{chatSessionMinimal.user_email || "-"}
- {chatSessionMinimal.persona_name || "Unknown"}
+ {chatSessionMinimal.assistant_name || "Unknown"}
{timestampToReadableDate(chatSessionMinimal.time_created)}
diff --git a/web/src/app/ee/admin/performance/usage/types.ts b/web/src/app/ee/admin/performance/usage/types.ts
index 3805d2c5c..163dc54f8 100644
--- a/web/src/app/ee/admin/performance/usage/types.ts
+++ b/web/src/app/ee/admin/performance/usage/types.ts
@@ -38,7 +38,8 @@ export interface ChatSessionSnapshot {
user_email: string | null;
name: string | null;
messages: MessageSnapshot[];
- persona_name: string | null;
+ assistant_id: number | null;
+ assistant_name: string | null;
time_created: string;
flow_type: SessionType;
}
@@ -49,10 +50,12 @@ export interface ChatSessionMinimal {
name: string | null;
first_user_message: string;
first_ai_message: string;
- persona_name: string | null;
+ assistant_id: number | null;
+ assistant_name: string | null;
time_created: string;
feedback_type: Feedback | "mixed" | null;
flow_type: SessionType;
+ conversation_length: number;
}
export interface UsageReport {