feat: add created_at__gte filter to search_app_conversation_start_tasks (#11740)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Tim O'Farrell
2025-11-14 14:08:34 +00:00
committed by GitHub
parent 7263657937
commit 8115d82f96
5 changed files with 176 additions and 2 deletions

View File

@@ -111,11 +111,11 @@ class V1ConversationService {
* Search for start tasks (ongoing tasks that haven't completed yet)
* Use this to find tasks that were started but the user navigated away
*
* Note: Backend only supports filtering by limit. To filter by repository/trigger,
* Note: Backend supports filtering by limit and created_at__gte. To filter by repository/trigger,
* filter the results client-side after fetching.
*
* @param limit Maximum number of tasks to return (max 100)
* @returns Array of start tasks
* @returns Array of start tasks from the last 20 minutes
*/
static async searchStartTasks(
limit: number = 100,
@@ -123,6 +123,10 @@ class V1ConversationService {
const params = new URLSearchParams();
params.append("limit", limit.toString());
// Only get tasks from the last 20 minutes
const twentyMinutesAgo = new Date(Date.now() - 20 * 60 * 1000);
params.append("created_at__gte", twentyMinutesAgo.toISOString());
const { data } = await openHands.get<V1AppConversationStartTaskPage>(
`/api/v1/app-conversations/start-tasks/search?${params.toString()}`,
);

View File

@@ -207,6 +207,10 @@ async def search_app_conversation_start_tasks(
UUID | None,
Query(title='Filter by conversation ID equal to this value'),
] = None,
created_at__gte: Annotated[
datetime | None,
Query(title='Filter by created_at greater than or equal to this datetime'),
] = None,
sort_order: Annotated[
AppConversationStartTaskSortOrder,
Query(title='Sort order for the results'),
@@ -233,6 +237,7 @@ async def search_app_conversation_start_tasks(
return (
await app_conversation_start_task_service.search_app_conversation_start_tasks(
conversation_id__eq=conversation_id__eq,
created_at__gte=created_at__gte,
sort_order=sort_order,
page_id=page_id,
limit=limit,
@@ -246,6 +251,10 @@ async def count_app_conversation_start_tasks(
UUID | None,
Query(title='Filter by conversation ID equal to this value'),
] = None,
created_at__gte: Annotated[
datetime | None,
Query(title='Filter by created_at greater than or equal to this datetime'),
] = None,
app_conversation_start_task_service: AppConversationStartTaskService = (
app_conversation_start_task_service_dependency
),
@@ -253,6 +262,7 @@ async def count_app_conversation_start_tasks(
"""Count conversation start tasks matching the given filters."""
return await app_conversation_start_task_service.count_app_conversation_start_tasks(
conversation_id__eq=conversation_id__eq,
created_at__gte=created_at__gte,
)

View File

@@ -1,5 +1,6 @@
import asyncio
from abc import ABC, abstractmethod
from datetime import datetime
from uuid import UUID
from openhands.app_server.app_conversation.app_conversation_models import (
@@ -18,6 +19,7 @@ class AppConversationStartTaskService(ABC):
async def search_app_conversation_start_tasks(
self,
conversation_id__eq: UUID | None = None,
created_at__gte: datetime | None = None,
sort_order: AppConversationStartTaskSortOrder = AppConversationStartTaskSortOrder.CREATED_AT_DESC,
page_id: str | None = None,
limit: int = 100,
@@ -28,6 +30,7 @@ class AppConversationStartTaskService(ABC):
async def count_app_conversation_start_tasks(
self,
conversation_id__eq: UUID | None = None,
created_at__gte: datetime | None = None,
) -> int:
"""Count conversation start tasks."""

View File

@@ -18,6 +18,7 @@ from __future__ import annotations
import logging
from dataclasses import dataclass
from datetime import datetime
from typing import AsyncGenerator
from uuid import UUID
@@ -75,6 +76,7 @@ class SQLAppConversationStartTaskService(AppConversationStartTaskService):
async def search_app_conversation_start_tasks(
self,
conversation_id__eq: UUID | None = None,
created_at__gte: datetime | None = None,
sort_order: AppConversationStartTaskSortOrder = AppConversationStartTaskSortOrder.CREATED_AT_DESC,
page_id: str | None = None,
limit: int = 100,
@@ -95,6 +97,12 @@ class SQLAppConversationStartTaskService(AppConversationStartTaskService):
== conversation_id__eq
)
# Apply created_at__gte filter
if created_at__gte is not None:
query = query.where(
StoredAppConversationStartTask.created_at >= created_at__gte
)
# Add sort order
if sort_order == AppConversationStartTaskSortOrder.CREATED_AT:
query = query.order_by(StoredAppConversationStartTask.created_at)
@@ -139,6 +147,7 @@ class SQLAppConversationStartTaskService(AppConversationStartTaskService):
async def count_app_conversation_start_tasks(
self,
conversation_id__eq: UUID | None = None,
created_at__gte: datetime | None = None,
) -> int:
"""Count conversation start tasks."""
query = select(func.count(StoredAppConversationStartTask.id))
@@ -156,6 +165,12 @@ class SQLAppConversationStartTaskService(AppConversationStartTaskService):
== conversation_id__eq
)
# Apply created_at__gte filter
if created_at__gte is not None:
query = query.where(
StoredAppConversationStartTask.created_at >= created_at__gte
)
result = await self.session.execute(query)
count = result.scalar()
return count or 0

View File

@@ -639,3 +639,145 @@ class TestSQLAppConversationStartTaskService:
user2_count = await user2_service.count_app_conversation_start_tasks()
assert user2_count == 1
async def test_search_app_conversation_start_tasks_with_created_at_gte_filter(
self,
service: SQLAppConversationStartTaskService,
sample_request: AppConversationStartRequest,
):
"""Test search with created_at__gte filter."""
from datetime import timedelta
from openhands.agent_server.models import utc_now
# Create tasks with different creation times
base_time = utc_now()
# Task 1: created 2 hours ago
task1 = AppConversationStartTask(
id=uuid4(),
created_by_user_id='user1',
status=AppConversationStartTaskStatus.WORKING,
request=sample_request,
)
task1.created_at = base_time - timedelta(hours=2)
await service.save_app_conversation_start_task(task1)
# Task 2: created 1 hour ago
task2 = AppConversationStartTask(
id=uuid4(),
created_by_user_id='user1',
status=AppConversationStartTaskStatus.READY,
request=sample_request,
)
task2.created_at = base_time - timedelta(hours=1)
await service.save_app_conversation_start_task(task2)
# Task 3: created 30 minutes ago
task3 = AppConversationStartTask(
id=uuid4(),
created_by_user_id='user1',
status=AppConversationStartTaskStatus.WORKING,
request=sample_request,
)
task3.created_at = base_time - timedelta(minutes=30)
await service.save_app_conversation_start_task(task3)
# Search for tasks created in the last 90 minutes
filter_time = base_time - timedelta(minutes=90)
result = await service.search_app_conversation_start_tasks(
created_at__gte=filter_time
)
# Should return task2 and task3 (created within last 90 minutes)
assert len(result.items) == 2
task_ids = [task.id for task in result.items]
assert task2.id in task_ids
assert task3.id in task_ids
assert task1.id not in task_ids
# Test count with the same filter
count = await service.count_app_conversation_start_tasks(
created_at__gte=filter_time
)
assert count == 2
# Search for tasks created in the last 45 minutes
filter_time_recent = base_time - timedelta(minutes=45)
result_recent = await service.search_app_conversation_start_tasks(
created_at__gte=filter_time_recent
)
# Should return only task3
assert len(result_recent.items) == 1
assert result_recent.items[0].id == task3.id
# Test count with recent filter
count_recent = await service.count_app_conversation_start_tasks(
created_at__gte=filter_time_recent
)
assert count_recent == 1
async def test_search_app_conversation_start_tasks_combined_filters(
self,
service: SQLAppConversationStartTaskService,
sample_request: AppConversationStartRequest,
):
"""Test search with both conversation_id and created_at__gte filters."""
from datetime import timedelta
from openhands.agent_server.models import utc_now
conversation_id1 = uuid4()
conversation_id2 = uuid4()
base_time = utc_now()
# Task 1: conversation_id1, created 2 hours ago
task1 = AppConversationStartTask(
id=uuid4(),
created_by_user_id='user1',
status=AppConversationStartTaskStatus.WORKING,
app_conversation_id=conversation_id1,
request=sample_request,
)
task1.created_at = base_time - timedelta(hours=2)
await service.save_app_conversation_start_task(task1)
# Task 2: conversation_id1, created 30 minutes ago
task2 = AppConversationStartTask(
id=uuid4(),
created_by_user_id='user1',
status=AppConversationStartTaskStatus.READY,
app_conversation_id=conversation_id1,
request=sample_request,
)
task2.created_at = base_time - timedelta(minutes=30)
await service.save_app_conversation_start_task(task2)
# Task 3: conversation_id2, created 30 minutes ago
task3 = AppConversationStartTask(
id=uuid4(),
created_by_user_id='user1',
status=AppConversationStartTaskStatus.WORKING,
app_conversation_id=conversation_id2,
request=sample_request,
)
task3.created_at = base_time - timedelta(minutes=30)
await service.save_app_conversation_start_task(task3)
# Search for tasks with conversation_id1 created in the last hour
filter_time = base_time - timedelta(hours=1)
result = await service.search_app_conversation_start_tasks(
conversation_id__eq=conversation_id1, created_at__gte=filter_time
)
# Should return only task2 (conversation_id1 and created within last hour)
assert len(result.items) == 1
assert result.items[0].id == task2.id
assert result.items[0].app_conversation_id == conversation_id1
# Test count with combined filters
count = await service.count_app_conversation_start_tasks(
conversation_id__eq=conversation_id1, created_at__gte=filter_time
)
assert count == 1