feat: release 1.5.1 with human review and batch filters

This commit is contained in:
chenmingxuan committed 2026-10-12 15:31:38 +08:00
1 parent 53c5680260
commit bdd1aa2c14
36 files changed
+672 -57

No files matched your search

+61 -7
View File
@@ -13,7 +13,7 @@ from apps.admin_console.services import automation_service as service
from apps.admin_console.services import batch_archive as archive_service
from apps.admin_console.services import report_export, task_import
from artemis.config.settings import settings
from artemis.runtime.case_result import read_case_result
from apps.admin_console.services.case_review import read_reviewed_result
router = APIRouter(prefix="/api/automation", tags=["automation"])
@@ -24,6 +24,12 @@ class ReportPreferences(BaseModel):
)
class CaseReviewRequest(BaseModel):
model_config = {"extra": "forbid"}
status: Literal["passed", "failed"]
reason: str = Field(default="", max_length=2000)
class BatchRequest(ReportPreferences):
request_id: str | None = Field(default=None, pattern=r"^[A-Za-z0-9_-]{1,100}$")
name: str = Field(min_length=1, max_length=200)
@@ -125,8 +131,8 @@ def batch_by_request(request_id: str):
def batch_detail(batch_id: str):
jobs = store.jobs(batch_id)
for job in jobs:
job["test_result"] = read_case_result(
job["session_id"], job["status"], job.get("error") or ""
job["test_result"] = read_reviewed_result(
job["session_id"], job["status"], job.get("error") or "", storage=store
)
return {**require("batch", batch_id), "jobs": jobs}
@@ -207,6 +213,42 @@ async def download_report(
raise HTTPException(404, str(exc)) from exc
@router.put("/reports/{session_id}/review")
def review_case(session_id: str, value: CaseReviewRequest):
report(session_id)
job = store.job(session_id) or {}
batch_id = job.get("batch_id")
if (
batch_id
and archive_service.status(batch_id, require("batch", batch_id))["status"] == "preparing"
):
raise HTTPException(409, "批次报告正在打包,请完成后再保存人工复验")
with report_export.EXPORT_LOCK:
current = report(session_id)
if current["status"] not in {
"completed",
"success",
"failed",
"cancelled",
"interrupted",
"skipped",
}:
raise HTTPException(409, "请在任务结束后进行人工复验")
try:
store.save_case_review(session_id, value.status, value.reason)
except ValueError as exc:
raise HTTPException(409, str(exc)) from exc
if batch_id:
archive_service.forget(batch_id)
# A finishing worker can publish report_ready just after export;
# removing only the generated ZIP also prevents that stale cache hit.
try:
archive_service.archive_path(batch_id).unlink(missing_ok=True)
except OSError:
pass # An open download may hold the file; report_ready is already invalidated.
return report(session_id)
@router.get("/history")
def history(
q: str = Query(default="", max_length=200),
@@ -215,6 +257,7 @@ def history(
end: float | None = None,
page: int = Query(default=1, ge=1),
size: int = Query(default=30, ge=1, le=100),
batch_id: str = Query(default="", max_length=100),
):
# SQL filtering/pagination searches the entire persisted history, not a UI cache.
query = """WITH history AS (
@@ -226,7 +269,7 @@ def history(
COALESCE(json_extract(j.payload,'$.case.category'),'') category,
COALESCE(json_extract(j.payload,'$.case.subcategory'),'') subcategory,
COALESCE(json_extract(j.payload,'$.case.preconditions'),'') preconditions,
COALESCE(json_extract(j.payload,'$.error'),'') error
COALESCE(json_extract(j.payload,'$.error'),'') error, j.batch_id
FROM sessions s LEFT JOIN automation_jobs j ON s.session_id=j.session_id
UNION ALL
SELECT j.session_id,json_extract(j.payload,'$.case.goal'),
@@ -237,11 +280,22 @@ def history(
COALESCE(json_extract(j.payload,'$.case.category'),''),
COALESCE(json_extract(j.payload,'$.case.subcategory'),''),
COALESCE(json_extract(j.payload,'$.case.preconditions'),''),
COALESCE(json_extract(j.payload,'$.error'),'')
COALESCE(json_extract(j.payload,'$.error'),''), j.batch_id
FROM automation_jobs j WHERE NOT EXISTS (SELECT 1 FROM sessions s WHERE s.session_id=j.session_id)
) """
where = " WHERE status IN ('completed','success','failed','cancelled','interrupted','skipped')"
args = []
if batch_id == "__batch__":
where += " AND batch_id IS NOT NULL"
elif batch_id == "__single__":
where += " AND batch_id IS NULL"
elif batch_id:
try:
identifier(batch_id)
except ValueError as exc:
raise HTTPException(400, str(exc)) from exc
where += " AND batch_id = ?"
args.append(batch_id)
if q:
where += " AND (name LIKE ? ESCAPE '\\' OR initial_goal LIKE ? ESCAPE '\\' OR session_id LIKE ? ESCAPE '\\' OR tags LIKE ? ESCAPE '\\' OR case_id LIKE ? ESCAPE '\\' OR category LIKE ? ESCAPE '\\' OR subcategory LIKE ? ESCAPE '\\' OR preconditions LIKE ? ESCAPE '\\')"
escaped = "%" + q.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") + "%"
@@ -266,8 +320,8 @@ def history(
items = [dict(row) for row in rows]
jobs = store.jobs_for_sessions([item["session_id"] for item in items])
for item in items:
item["test_result"] = read_case_result(
item["session_id"], item["status"], item.get("error") or ""
item["test_result"] = read_reviewed_result(
item["session_id"], item["status"], item.get("error") or "", storage=store
)
if job := jobs.get(item["session_id"]):
item["name"] = task_import.execution_title(job)
+12 -5
View File
@@ -20,7 +20,7 @@ from fastapi import APIRouter, HTTPException
from artemis.config import DB_PATH, TRACES_PATH
from artemis.runtime import trace_store
from artemis.runtime.case_result import read_case_result
from apps.admin_console.services.case_review import read_reviewed_result
try:
from admin_console.core.state import state
@@ -83,6 +83,7 @@ def _list_sessions_sync():
job = jobs_by_id.get(s_id)
if job:
case = job.get("case") or {}
row_dict["batch_id"] = job.get("batch_id")
row_dict["case_id"] = case.get("case_id") or ""
row_dict["display_name"] = execution_title(job) or row_dict.get("initial_goal")
row_dict["iteration"] = job.get("iteration", 1)
@@ -131,8 +132,12 @@ def _list_sessions_sync():
else:
row_dict["video_url"] = None
row_dict["recording_status"] = recording_status
row_dict["test_result"] = read_case_result(
s_id, row_dict.get("status") or "", row_dict.get("error") or "", root=TRACES_PATH
row_dict["test_result"] = read_reviewed_result(
s_id,
row_dict.get("status") or "",
row_dict.get("error") or "",
root=TRACES_PATH,
storage=store,
)
agent_names = agent_names_by_session.get(s_id, [])
@@ -194,8 +199,10 @@ async def get_session_details(session_id: str):
if not row:
raise HTTPException(status_code=404, detail=f"Session {session_id} not found")
result = dict(row)
result["test_result"] = read_case_result(
session_id, result.get("status") or "", root=TRACES_PATH
job = store.job(session_id) or {}
result["batch_id"] = job.get("batch_id")
result["test_result"] = read_reviewed_result(
session_id, result.get("status") or "", root=TRACES_PATH, storage=store
)
return result
+11
View File
@@ -20,6 +20,8 @@ from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
from artemis.core.diagnostics import readiness_engine
from apps.admin_console.services.automation_store import store
from apps.admin_console.services.task_import import execution_title
from artemis.runtime import DeviceExecutionLock, device_pool
from artemis.runtime.task_control import (
add_instruction,
@@ -423,6 +425,15 @@ async def get_status():
seen_ids.add(sid)
queue_data.append(g_item)
batch_jobs = store.jobs_for_sessions(
[str(item["session_id"]) for item in [*queue_data, *active_tasks] if item.get("session_id")]
)
queue_data = [{**item} for item in queue_data]
for item in [*queue_data, *active_tasks]:
if job := batch_jobs.get(str(item.get("session_id"))):
item["batch_id"] = job.get("batch_id")
item["display_name"] = execution_title(job)
if is_running:
is_paused = state.is_paused
return {