|
@@ -13,6 +13,7 @@ from schemas.task import (
|
|
|
TaskAssignmentResponse, MyTasksResponse,
|
|
TaskAssignmentResponse, MyTasksResponse,
|
|
|
AssignmentPreviewRequest, AssignmentPreviewResponse,
|
|
AssignmentPreviewRequest, AssignmentPreviewResponse,
|
|
|
DispatchRequest, DispatchResponse,
|
|
DispatchRequest, DispatchResponse,
|
|
|
|
|
+ TaskListPaginationResponse,
|
|
|
)
|
|
)
|
|
|
from models import Task
|
|
from models import Task
|
|
|
from datetime import datetime
|
|
from datetime import datetime
|
|
@@ -36,33 +37,36 @@ def calculate_progress(data_str: str, annotation_count: int) -> float:
|
|
|
return 0.0
|
|
return 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
-@router.get("", response_model=List[TaskResponse])
|
|
|
|
|
|
|
+@router.get("", response_model=TaskListPaginationResponse)
|
|
|
async def list_tasks(
|
|
async def list_tasks(
|
|
|
request: Request,
|
|
request: Request,
|
|
|
project_id: Optional[str] = Query(None, description="Filter by project ID"),
|
|
project_id: Optional[str] = Query(None, description="Filter by project ID"),
|
|
|
status_filter: Optional[str] = Query(None, alias="status", description="Filter by status"),
|
|
status_filter: Optional[str] = Query(None, alias="status", description="Filter by status"),
|
|
|
- assigned_to: Optional[str] = Query(None, description="Filter by assigned user")
|
|
|
|
|
|
|
+ assigned_to: Optional[str] = Query(None, description="Filter by assigned user"),
|
|
|
|
|
+ page: int = Query(1, ge=1, description="Page number"),
|
|
|
|
|
+ page_size: int = Query(20, ge=1, le=100, description="Items per page")
|
|
|
):
|
|
):
|
|
|
"""
|
|
"""
|
|
|
- List tasks with optional filters.
|
|
|
|
|
-
|
|
|
|
|
|
|
+ List tasks with optional filters and pagination.
|
|
|
|
|
+
|
|
|
For admin users: Returns all tasks matching the filters.
|
|
For admin users: Returns all tasks matching the filters.
|
|
|
For annotator users: Returns only tasks assigned to them (ignores assigned_to filter).
|
|
For annotator users: Returns only tasks assigned to them (ignores assigned_to filter).
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Requires authentication.
|
|
Requires authentication.
|
|
|
"""
|
|
"""
|
|
|
user = request.state.user
|
|
user = request.state.user
|
|
|
user_id = user["id"]
|
|
user_id = user["id"]
|
|
|
user_role = user["role"]
|
|
user_role = user["role"]
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
with get_db_connection() as conn:
|
|
with get_db_connection() as conn:
|
|
|
cursor = conn.cursor()
|
|
cursor = conn.cursor()
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# Build query with filters
|
|
# Build query with filters
|
|
|
- query = """
|
|
|
|
|
- SELECT
|
|
|
|
|
|
|
+ base_query = """
|
|
|
|
|
+ SELECT
|
|
|
t.id,
|
|
t.id,
|
|
|
t.project_id,
|
|
t.project_id,
|
|
|
|
|
+ p.name as project_name,
|
|
|
t.name,
|
|
t.name,
|
|
|
t.data,
|
|
t.data,
|
|
|
t.status,
|
|
t.status,
|
|
@@ -71,40 +75,75 @@ async def list_tasks(
|
|
|
COUNT(a.id) as annotation_count
|
|
COUNT(a.id) as annotation_count
|
|
|
FROM tasks t
|
|
FROM tasks t
|
|
|
LEFT JOIN annotations a ON t.id = a.task_id
|
|
LEFT JOIN annotations a ON t.id = a.task_id
|
|
|
|
|
+ LEFT JOIN projects p ON t.project_id = p.id
|
|
|
WHERE 1=1
|
|
WHERE 1=1
|
|
|
"""
|
|
"""
|
|
|
params = []
|
|
params = []
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if project_id:
|
|
if project_id:
|
|
|
- query += " AND t.project_id = ?"
|
|
|
|
|
|
|
+ base_query += " AND t.project_id = ?"
|
|
|
params.append(project_id)
|
|
params.append(project_id)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if status_filter:
|
|
if status_filter:
|
|
|
- query += " AND t.status = ?"
|
|
|
|
|
|
|
+ base_query += " AND t.status = ?"
|
|
|
params.append(status_filter)
|
|
params.append(status_filter)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# 标注员只能看到分配给自己的任务
|
|
# 标注员只能看到分配给自己的任务
|
|
|
if user_role != "admin":
|
|
if user_role != "admin":
|
|
|
- query += " AND t.assigned_to = ?"
|
|
|
|
|
|
|
+ base_query += " AND t.assigned_to = ?"
|
|
|
params.append(user_id)
|
|
params.append(user_id)
|
|
|
elif assigned_to:
|
|
elif assigned_to:
|
|
|
# 管理员可以按 assigned_to 过滤
|
|
# 管理员可以按 assigned_to 过滤
|
|
|
- query += " AND t.assigned_to = ?"
|
|
|
|
|
|
|
+ base_query += " AND t.assigned_to = ?"
|
|
|
params.append(assigned_to)
|
|
params.append(assigned_to)
|
|
|
-
|
|
|
|
|
- query += " GROUP BY t.id, t.project_id, t.name, t.data, t.status, t.assigned_to, t.created_at ORDER BY t.created_at DESC"
|
|
|
|
|
-
|
|
|
|
|
- cursor.execute(query, tuple(params))
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # 计算总数
|
|
|
|
|
+ count_query = f"""
|
|
|
|
|
+ SELECT COUNT(DISTINCT t.id) as total
|
|
|
|
|
+ FROM tasks t
|
|
|
|
|
+ LEFT JOIN projects p ON t.project_id = p.id
|
|
|
|
|
+ WHERE 1=1
|
|
|
|
|
+ """
|
|
|
|
|
+ count_params = []
|
|
|
|
|
+
|
|
|
|
|
+ if project_id:
|
|
|
|
|
+ count_query += " AND t.project_id = ?"
|
|
|
|
|
+ count_params.append(project_id)
|
|
|
|
|
+
|
|
|
|
|
+ if status_filter:
|
|
|
|
|
+ count_query += " AND t.status = ?"
|
|
|
|
|
+ count_params.append(status_filter)
|
|
|
|
|
+
|
|
|
|
|
+ if user_role != "admin":
|
|
|
|
|
+ count_query += " AND t.assigned_to = ?"
|
|
|
|
|
+ count_params.append(user_id)
|
|
|
|
|
+ elif assigned_to:
|
|
|
|
|
+ count_query += " AND t.assigned_to = ?"
|
|
|
|
|
+ count_params.append(assigned_to)
|
|
|
|
|
+
|
|
|
|
|
+ cursor.execute(count_query, tuple(count_params))
|
|
|
|
|
+ total = cursor.fetchone()["total"]
|
|
|
|
|
+
|
|
|
|
|
+ # 计算分页
|
|
|
|
|
+ total_pages = (total + page_size - 1) // page_size
|
|
|
|
|
+ offset = (page - 1) * page_size
|
|
|
|
|
+
|
|
|
|
|
+ # 添加排序和分页
|
|
|
|
|
+ base_query += " GROUP BY t.id, t.project_id, p.name, t.name, t.data, t.status, t.assigned_to, t.created_at ORDER BY t.created_at DESC LIMIT ? OFFSET ?"
|
|
|
|
|
+ params.extend([page_size, offset])
|
|
|
|
|
+
|
|
|
|
|
+ cursor.execute(base_query, tuple(params))
|
|
|
rows = cursor.fetchall()
|
|
rows = cursor.fetchall()
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
tasks = []
|
|
tasks = []
|
|
|
for row in rows:
|
|
for row in rows:
|
|
|
data = json.loads(row["data"]) if isinstance(row["data"], str) else row["data"]
|
|
data = json.loads(row["data"]) if isinstance(row["data"], str) else row["data"]
|
|
|
progress = calculate_progress(row["data"], row["annotation_count"])
|
|
progress = calculate_progress(row["data"], row["annotation_count"])
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
tasks.append(TaskResponse(
|
|
tasks.append(TaskResponse(
|
|
|
id=row["id"],
|
|
id=row["id"],
|
|
|
project_id=row["project_id"],
|
|
project_id=row["project_id"],
|
|
|
|
|
+ project_name=row["project_name"],
|
|
|
name=row["name"],
|
|
name=row["name"],
|
|
|
data=data,
|
|
data=data,
|
|
|
status=row["status"],
|
|
status=row["status"],
|
|
@@ -112,8 +151,16 @@ async def list_tasks(
|
|
|
created_at=row["created_at"],
|
|
created_at=row["created_at"],
|
|
|
progress=progress
|
|
progress=progress
|
|
|
))
|
|
))
|
|
|
-
|
|
|
|
|
- return tasks
|
|
|
|
|
|
|
+
|
|
|
|
|
+ return TaskListPaginationResponse(
|
|
|
|
|
+ tasks=tasks,
|
|
|
|
|
+ total=total,
|
|
|
|
|
+ page=page,
|
|
|
|
|
+ page_size=page_size,
|
|
|
|
|
+ total_pages=total_pages,
|
|
|
|
|
+ has_next=page < total_pages,
|
|
|
|
|
+ has_prev=page > 1
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("", response_model=TaskResponse, status_code=status.HTTP_201_CREATED)
|
|
@router.post("", response_model=TaskResponse, status_code=status.HTTP_201_CREATED)
|
|
@@ -168,39 +215,72 @@ async def create_task(request: Request, task: TaskCreate):
|
|
|
async def get_my_tasks(
|
|
async def get_my_tasks(
|
|
|
request: Request,
|
|
request: Request,
|
|
|
project_id: Optional[str] = Query(None, description="Filter by project ID"),
|
|
project_id: Optional[str] = Query(None, description="Filter by project ID"),
|
|
|
- status_filter: Optional[str] = Query(None, alias="status", description="Filter by status")
|
|
|
|
|
|
|
+ status_filter: Optional[str] = Query(None, alias="status", description="Filter by status"),
|
|
|
|
|
+ page: int = Query(1, ge=1, description="Page number"),
|
|
|
|
|
+ page_size: int = Query(20, ge=1, le=100, description="Items per page")
|
|
|
):
|
|
):
|
|
|
"""
|
|
"""
|
|
|
Get tasks assigned to the current user.
|
|
Get tasks assigned to the current user.
|
|
|
Requires authentication.
|
|
Requires authentication.
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
标注人员只能看到分配给自己的任务。
|
|
标注人员只能看到分配给自己的任务。
|
|
|
"""
|
|
"""
|
|
|
user = request.state.user
|
|
user = request.state.user
|
|
|
user_id = user["id"]
|
|
user_id = user["id"]
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
with get_db_connection() as conn:
|
|
with get_db_connection() as conn:
|
|
|
cursor = conn.cursor()
|
|
cursor = conn.cursor()
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
# 构建查询条件
|
|
# 构建查询条件
|
|
|
where_clauses = ["t.assigned_to = ?"]
|
|
where_clauses = ["t.assigned_to = ?"]
|
|
|
params = [user_id]
|
|
params = [user_id]
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if project_id:
|
|
if project_id:
|
|
|
where_clauses.append("t.project_id = ?")
|
|
where_clauses.append("t.project_id = ?")
|
|
|
params.append(project_id)
|
|
params.append(project_id)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if status_filter:
|
|
if status_filter:
|
|
|
where_clauses.append("t.status = ?")
|
|
where_clauses.append("t.status = ?")
|
|
|
params.append(status_filter)
|
|
params.append(status_filter)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
where_sql = " AND ".join(where_clauses)
|
|
where_sql = " AND ".join(where_clauses)
|
|
|
-
|
|
|
|
|
- # 查询任务列表
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # 查询总数
|
|
|
|
|
+ count_query = f"""
|
|
|
|
|
+ SELECT COUNT(*) as total
|
|
|
|
|
+ FROM tasks t
|
|
|
|
|
+ WHERE {where_sql}
|
|
|
|
|
+ """
|
|
|
|
|
+ cursor.execute(count_query, tuple(params))
|
|
|
|
|
+ total = cursor.fetchone()["total"]
|
|
|
|
|
+
|
|
|
|
|
+ # 查询各状态的任务数(不受分页影响)
|
|
|
|
|
+ status_count_query = f"""
|
|
|
|
|
+ SELECT
|
|
|
|
|
+ SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,
|
|
|
|
|
+ SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) as in_progress,
|
|
|
|
|
+ SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending
|
|
|
|
|
+ FROM tasks t
|
|
|
|
|
+ WHERE {where_sql}
|
|
|
|
|
+ """
|
|
|
|
|
+ cursor.execute(status_count_query, tuple(params))
|
|
|
|
|
+ status_row = cursor.fetchone()
|
|
|
|
|
+ completed = int(status_row["completed"] or 0)
|
|
|
|
|
+ in_progress = int(status_row["in_progress"] or 0)
|
|
|
|
|
+ pending = int(status_row["pending"] or 0)
|
|
|
|
|
+
|
|
|
|
|
+ # 计算分页信息
|
|
|
|
|
+ total_pages = (total + page_size - 1) // page_size
|
|
|
|
|
+ has_next = page < total_pages
|
|
|
|
|
+ has_prev = page > 1
|
|
|
|
|
+ skip = (page - 1) * page_size
|
|
|
|
|
+
|
|
|
|
|
+ # 查询任务列表(带项目名称)
|
|
|
query = f"""
|
|
query = f"""
|
|
|
- SELECT
|
|
|
|
|
|
|
+ SELECT
|
|
|
t.id,
|
|
t.id,
|
|
|
t.project_id,
|
|
t.project_id,
|
|
|
|
|
+ p.name as project_name,
|
|
|
t.name,
|
|
t.name,
|
|
|
t.data,
|
|
t.data,
|
|
|
t.status,
|
|
t.status,
|
|
@@ -209,34 +289,28 @@ async def get_my_tasks(
|
|
|
COUNT(a.id) as annotation_count
|
|
COUNT(a.id) as annotation_count
|
|
|
FROM tasks t
|
|
FROM tasks t
|
|
|
LEFT JOIN annotations a ON t.id = a.task_id
|
|
LEFT JOIN annotations a ON t.id = a.task_id
|
|
|
|
|
+ LEFT JOIN projects p ON t.project_id = p.id
|
|
|
WHERE {where_sql}
|
|
WHERE {where_sql}
|
|
|
- GROUP BY t.id, t.project_id, t.name, t.data, t.status, t.assigned_to, t.created_at
|
|
|
|
|
|
|
+ GROUP BY t.id, t.project_id, p.name, t.name, t.data, t.status, t.assigned_to, t.created_at
|
|
|
ORDER BY t.created_at DESC
|
|
ORDER BY t.created_at DESC
|
|
|
|
|
+ LIMIT ? OFFSET ?
|
|
|
"""
|
|
"""
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ params.extend([page_size, skip])
|
|
|
cursor.execute(query, tuple(params))
|
|
cursor.execute(query, tuple(params))
|
|
|
rows = cursor.fetchall()
|
|
rows = cursor.fetchall()
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
tasks = []
|
|
tasks = []
|
|
|
- completed = 0
|
|
|
|
|
- in_progress = 0
|
|
|
|
|
- pending = 0
|
|
|
|
|
-
|
|
|
|
|
for row in rows:
|
|
for row in rows:
|
|
|
data = json.loads(row["data"]) if isinstance(row["data"], str) else row["data"]
|
|
data = json.loads(row["data"]) if isinstance(row["data"], str) else row["data"]
|
|
|
progress = calculate_progress(row["data"], row["annotation_count"])
|
|
progress = calculate_progress(row["data"], row["annotation_count"])
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
task_status = row["status"]
|
|
task_status = row["status"]
|
|
|
- if task_status == "completed":
|
|
|
|
|
- completed += 1
|
|
|
|
|
- elif task_status == "in_progress":
|
|
|
|
|
- in_progress += 1
|
|
|
|
|
- else:
|
|
|
|
|
- pending += 1
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
tasks.append(TaskResponse(
|
|
tasks.append(TaskResponse(
|
|
|
id=row["id"],
|
|
id=row["id"],
|
|
|
project_id=row["project_id"],
|
|
project_id=row["project_id"],
|
|
|
|
|
+ project_name=row["project_name"],
|
|
|
name=row["name"],
|
|
name=row["name"],
|
|
|
data=data,
|
|
data=data,
|
|
|
status=task_status,
|
|
status=task_status,
|
|
@@ -244,13 +318,18 @@ async def get_my_tasks(
|
|
|
created_at=row["created_at"],
|
|
created_at=row["created_at"],
|
|
|
progress=progress
|
|
progress=progress
|
|
|
))
|
|
))
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return MyTasksResponse(
|
|
return MyTasksResponse(
|
|
|
tasks=tasks,
|
|
tasks=tasks,
|
|
|
- total=len(tasks),
|
|
|
|
|
|
|
+ total=total,
|
|
|
completed=completed,
|
|
completed=completed,
|
|
|
in_progress=in_progress,
|
|
in_progress=in_progress,
|
|
|
- pending=pending
|
|
|
|
|
|
|
+ pending=pending,
|
|
|
|
|
+ page=page,
|
|
|
|
|
+ page_size=page_size,
|
|
|
|
|
+ total_pages=total_pages,
|
|
|
|
|
+ has_next=has_next,
|
|
|
|
|
+ has_prev=has_prev
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|