Skip to content

Commit

Permalink
query history pagination tests (#3700)
Browse files Browse the repository at this point in the history
* dummy pr

* Update prompts.yaml

* fixed tests and added query history pagination test

* done

* fixed

* utils!
  • Loading branch information
hagen-danswer authored Jan 18, 2025
1 parent eec3ce8 commit 896e716
Show file tree
Hide file tree
Showing 9 changed files with 407 additions and 117 deletions.
2 changes: 1 addition & 1 deletion backend/ee/onyx/server/query_history/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def get_user_chat_sessions(
@router.get("/admin/chat-session-history")
def get_chat_session_history(
page_num: int = Query(0, ge=0),
page_size: int = Query(10, ge=10),
page_size: int = Query(10, ge=1),
feedback_type: QAFeedbackType | None = None,
start_time: datetime | None = None,
end_time: datetime | None = None,
Expand Down
53 changes: 18 additions & 35 deletions backend/tests/integration/common_utils/managers/chat.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import json
from datetime import datetime
from urllib.parse import urlencode
from uuid import UUID

import requests
from requests.models import Response

from ee.onyx.server.query_history.models import ChatSessionMinimal
from onyx.configs.constants import QAFeedbackType
from onyx.context.search.models import RetrievalDetails
from onyx.file_store.models import FileDescriptor
from onyx.llm.override_models import LLMOverride
from onyx.llm.override_models import PromptOverride
from onyx.server.documents.models import PaginatedReturn
from onyx.server.query_and_chat.models import ChatSessionCreationRequest
from onyx.server.query_and_chat.models import CreateChatMessageRequest
from tests.integration.common_utils.constants import API_SERVER_URL
Expand Down Expand Up @@ -140,35 +135,23 @@ def get_chat_history(
]

@staticmethod
def get_chat_session_history(
user_performing_action: DATestUser,
page_size: int,
page_num: int,
feedback_type: QAFeedbackType | None = None,
start_time: datetime | None = None,
end_time: datetime | None = None,
) -> PaginatedReturn[ChatSessionMinimal]:
query_params = {
"page_num": page_num,
"page_size": page_size,
"feedback_type": feedback_type if feedback_type else None,
"start_time": start_time if start_time else None,
"end_time": end_time if end_time else None,
}
# Remove None values
query_params = {
key: value for key, value in query_params.items() if value is not None
}

response = requests.get(
f"{API_SERVER_URL}/admin/chat-session-history?{urlencode(query_params, doseq=True)}",
headers=user_performing_action.headers,
def create_chat_message_feedback(
message_id: int,
is_positive: bool,
user_performing_action: DATestUser | None = None,
feedback_text: str | None = None,
predefined_feedback: str | None = None,
) -> None:
response = requests.post(
url=f"{API_SERVER_URL}/chat/create-chat-message-feedback",
json={
"chat_message_id": message_id,
"is_positive": is_positive,
"feedback_text": feedback_text,
"predefined_feedback": predefined_feedback,
},
headers=user_performing_action.headers
if user_performing_action
else GENERAL_HEADERS,
)
response.raise_for_status()
data = response.json()
return PaginatedReturn(
items=[
ChatSessionMinimal(**chat_session) for chat_session in data["items"]
],
total_items=data["total_items"],
)
84 changes: 84 additions & 0 deletions backend/tests/integration/common_utils/managers/query_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from datetime import datetime
from urllib.parse import urlencode
from uuid import UUID

import requests
from requests.models import CaseInsensitiveDict

from ee.onyx.server.query_history.models import ChatSessionMinimal
from ee.onyx.server.query_history.models import ChatSessionSnapshot
from onyx.configs.constants import QAFeedbackType
from onyx.server.documents.models import PaginatedReturn
from tests.integration.common_utils.constants import API_SERVER_URL
from tests.integration.common_utils.constants import GENERAL_HEADERS
from tests.integration.common_utils.test_models import DATestUser


class QueryHistoryManager:
@staticmethod
def get_query_history_page(
page_num: int = 0,
page_size: int = 10,
feedback_type: QAFeedbackType | None = None,
start_time: datetime | None = None,
end_time: datetime | None = None,
user_performing_action: DATestUser | None = None,
) -> PaginatedReturn[ChatSessionMinimal]:
query_params: dict[str, str | int] = {
"page_num": page_num,
"page_size": page_size,
}
if feedback_type:
query_params["feedback_type"] = feedback_type.value
if start_time:
query_params["start_time"] = start_time.isoformat()
if end_time:
query_params["end_time"] = end_time.isoformat()

response = requests.get(
url=f"{API_SERVER_URL}/admin/chat-session-history?{urlencode(query_params, doseq=True)}",
headers=user_performing_action.headers
if user_performing_action
else GENERAL_HEADERS,
)
response.raise_for_status()
data = response.json()
return PaginatedReturn(
items=[ChatSessionMinimal(**item) for item in data["items"]],
total_items=data["total_items"],
)

@staticmethod
def get_chat_session_admin(
chat_session_id: UUID | str,
user_performing_action: DATestUser | None = None,
) -> ChatSessionSnapshot:
response = requests.get(
url=f"{API_SERVER_URL}/admin/chat-session-history/{chat_session_id}",
headers=user_performing_action.headers
if user_performing_action
else GENERAL_HEADERS,
)
response.raise_for_status()
return ChatSessionSnapshot(**response.json())

@staticmethod
def get_query_history_as_csv(
start_time: datetime | None = None,
end_time: datetime | None = None,
user_performing_action: DATestUser | None = None,
) -> tuple[CaseInsensitiveDict[str], str]:
query_params: dict[str, str | int] = {}
if start_time:
query_params["start"] = start_time.isoformat()
if end_time:
query_params["end"] = end_time.isoformat()

response = requests.get(
url=f"{API_SERVER_URL}/admin/query-history-csv?{urlencode(query_params, doseq=True)}",
headers=user_performing_action.headers
if user_performing_action
else GENERAL_HEADERS,
)
response.raise_for_status()
return response.headers, response.content.decode()
15 changes: 7 additions & 8 deletions backend/tests/integration/common_utils/managers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,17 +213,16 @@ def get_user_page(
is_active_filter: bool | None = None,
user_performing_action: DATestUser | None = None,
) -> PaginatedReturn[FullUserSnapshot]:
query_params = {
query_params: dict[str, str | list[str] | int] = {
"page_num": page_num,
"page_size": page_size,
"q": search_query if search_query else None,
"roles": [role.value for role in role_filter] if role_filter else None,
"is_active": is_active_filter if is_active_filter is not None else None,
}
# Remove None values
query_params = {
key: value for key, value in query_params.items() if value is not None
}
if search_query:
query_params["q"] = search_query
if role_filter:
query_params["roles"] = [role.value for role in role_filter]
if is_active_filter is not None:
query_params["is_active"] = is_active_filter

response = requests.get(
url=f"{API_SERVER_URL}/manage/users/accepted?{urlencode(query_params, doseq=True)}",
Expand Down
16 changes: 10 additions & 6 deletions backend/tests/integration/common_utils/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pydantic import Field

from onyx.auth.schemas import UserRole
from onyx.configs.constants import QAFeedbackType
from onyx.context.search.enums import RecencyBiasSetting
from onyx.db.enums import AccessType
from onyx.server.documents.models import DocumentSource
Expand Down Expand Up @@ -130,18 +131,21 @@ class DATestPersona(BaseModel):
label_ids: list[int]


#
class DATestChatMessage(BaseModel):
id: int
chat_session_id: UUID
parent_message_id: int | None
message: str


class DATestChatSession(BaseModel):
id: UUID
persona_id: int
description: str


class DATestChatMessage(BaseModel):
id: int
chat_session_id: UUID
parent_message_id: int | None
message: str
class DAQueryHistoryEntry(DATestChatSession):
feedback_type: QAFeedbackType | None


class StreamedResponse(BaseModel):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
from datetime import timezone

import pytest
import requests

from onyx.configs.constants import QAFeedbackType
from onyx.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.query_history import QueryHistoryManager
from tests.integration.common_utils.managers.user import UserManager
from tests.integration.common_utils.test_models import DATestUser

Expand Down Expand Up @@ -69,66 +68,52 @@ def test_chat_history_endpoints(
) -> None:
admin_user, first_chat_id = setup_chat_session

response = requests.get(
f"{API_SERVER_URL}/admin/chat-session-history",
headers=admin_user.headers,
# Get chat history
history_response = QueryHistoryManager.get_query_history_page(
user_performing_action=admin_user
)
assert response.status_code == 200
history_response = response.json()

# Verify we got back the one chat session we created
assert len(history_response) == 1
assert len(history_response.items) == 1

# Verify the first chat session details
first_session = history_response[0]
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"] == 4 # 2 User messages + 2 AI responses
first_session = history_response.items[0]
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
assert first_session.conversation_length == 4 # 2 User messages + 2 AI responses

# Test date filtering - should return no results
past_end = datetime.now(tz=timezone.utc) - timedelta(days=1)
past_start = past_end - timedelta(days=1)
response = requests.get(
f"{API_SERVER_URL}/admin/chat-session-history",
params={
"start": past_start.isoformat(),
"end": past_end.isoformat(),
},
headers=admin_user.headers,
history_response = QueryHistoryManager.get_query_history_page(
start_time=past_start,
end_time=past_end,
user_performing_action=admin_user,
)
assert response.status_code == 200
history_response = response.json()
assert len(history_response) == 0
assert len(history_response.items) == 0

# Test get specific chat session endpoint
response = requests.get(
f"{API_SERVER_URL}/admin/chat-session-history/{first_chat_id}",
headers=admin_user.headers,
session_details = QueryHistoryManager.get_chat_session_admin(
chat_session_id=first_chat_id,
user_performing_action=admin_user,
)
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
assert str(session_details.id) == first_chat_id
assert len(session_details.messages) > 0
assert session_details.flow_type == SessionType.CHAT

# Test filtering by feedback
response = requests.get(
f"{API_SERVER_URL}/admin/chat-session-history",
params={
"feedback_type": QAFeedbackType.LIKE.value,
},
headers=admin_user.headers,
history_response = QueryHistoryManager.get_query_history_page(
feedback_type=QAFeedbackType.LIKE,
user_performing_action=admin_user,
)
assert response.status_code == 200
history_response = response.json()
assert len(history_response) == 0
assert len(history_response.items) == 0


def test_chat_history_csv_export(
Expand All @@ -137,16 +122,13 @@ def test_chat_history_csv_export(
admin_user, _ = setup_chat_session

# Test CSV export endpoint with date filtering
response = requests.get(
f"{API_SERVER_URL}/admin/query-history-csv",
headers=admin_user.headers,
headers, csv_content = QueryHistoryManager.get_query_history_as_csv(
user_performing_action=admin_user,
)
assert response.status_code == 200
assert response.headers["Content-Type"] == "text/csv; charset=utf-8"
assert "Content-Disposition" in response.headers
assert headers["Content-Type"] == "text/csv; charset=utf-8"
assert "Content-Disposition" in headers

# Verify CSV content
csv_content = response.content.decode()
csv_lines = csv_content.strip().split("\n")
assert len(csv_lines) == 3 # Header + 2 QA pairs
assert "chat_session_id" in csv_content
Expand All @@ -158,15 +140,10 @@ def test_chat_history_csv_export(
# Test CSV export with date filtering - should return no results
past_end = datetime.now(tz=timezone.utc) - timedelta(days=1)
past_start = past_end - timedelta(days=1)
response = requests.get(
f"{API_SERVER_URL}/admin/query-history-csv",
params={
"start": past_start.isoformat(),
"end": past_end.isoformat(),
},
headers=admin_user.headers,
headers, csv_content = QueryHistoryManager.get_query_history_as_csv(
start_time=past_start,
end_time=past_end,
user_performing_action=admin_user,
)
assert response.status_code == 200
csv_content = response.content.decode()
csv_lines = csv_content.strip().split("\n")
assert len(csv_lines) == 1 # Only header, no data rows
Loading

0 comments on commit 896e716

Please sign in to comment.