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 {