feat: DashboardDataGrid migration + FileList 7-feature rewrite + validation API

DashboardDataGrid:
- Add server-side pagination (serverTotal, serverTotalPages)
- Add hideFilter, bulkActions (renamed from children)
- Add header/rowCell snippet slots
- Support {#key} for forced re-render

/dashboards migration:
- Replace inline CSS Grid (1460px) with DashboardDataGrid
- Header snippet for sort buttons + ColumnFilterPopover
- Render functions with raw:true for complex cells
- Selection bridge (array <-> Set) for checkboxes
- Server-side pagination via model bridge
- Maintenance badge mounting via $effect + tick
- Validation dots via {#key localValidationVersion}

Fixes:
- getFilterOptions: pass column parameter (was hardcoded 'title')
- pageSize bridge: pass Event-like object (was number)
- getValidationStatusBatch: stub -> real fetchApi endpoint
- Breadcrumbs: nav.dashboard -> nav.dashboards
- Uncaught (in promise): add .catch() to async calls

Backend:
- New GET /status/batch endpoint for validation batch query

FileList rewrite (7 features):
1. Headers: text-sm font-semibold (was text-xs uppercase)
2. Column sorting (Name, Category, Size, Date)
3. Breadcrumbs with navigate-up button
4. Loading skeleton (animated rows)
5. Client-side pagination (20 per page)
6. Multi-select + bulk delete/download
7. Search by name/category

QA: build passes, 131 dashboard tests pass, 0 console errors
Pre-existing: 3 test failures unrelated (provider_config, api stub)
This commit is contained in:
2026-06-18 12:53:03 +03:00
parent 8aa5c57b60
commit 8016c07ebb
10 changed files with 616 additions and 232 deletions

View File

@@ -121,7 +121,7 @@ async def create_task(
extra={"src": "validation_tasks_routes"},
)
try:
result = service.create_task(payload)
result = await service.create_task(payload)
scheduler.reload_validation_policy(result.id)
return result
except ValueError as e:
@@ -203,7 +203,7 @@ async def update_task(
extra={"src": "validation_tasks_routes"},
)
try:
result = service.update_task(policy_id, payload)
result = await service.update_task(policy_id, payload)
scheduler.reload_validation_policy(result.id)
return result
except ValueError as e:
@@ -259,7 +259,7 @@ async def toggle_task_status(
extra={"src": "validation_tasks_routes"},
)
try:
result = service.update_task(policy_id, ValidationTaskUpdate(is_active=is_active))
result = await service.update_task(policy_id, ValidationTaskUpdate(is_active=is_active))
if not is_active:
scheduler.remove_validation_job(policy_id)
else:
@@ -327,7 +327,7 @@ async def list_all_runs(
extra={"src": "validation_tasks_routes"},
)
try:
total, raw_items = service.list_all_runs(
total, raw_items = await service.list_all_runs(
status=status_filter, environment_id=environment_id, dashboard_id=dashboard_id,
page=page, page_size=page_size,
)
@@ -382,7 +382,7 @@ async def get_run_detail(
extra={"src": "validation_tasks_routes"},
)
try:
result = service.get_run_detail(run_id)
result = await service.get_run_detail(run_id)
return RunDetailResponse(
run=_dict_to_run_response(result["run"]),
records=result.get("records", []),
@@ -490,4 +490,70 @@ async def legacy_llm_report_redirect(
# #endregion legacy_llm_report_redirect
# #region get_validation_status_batch [C:3] [TYPE Function] [SEMANTICS validation,api,status,batch]
# @BRIEF Batch-fetch latest validation status for multiple dashboard IDs.
# @LAYER API
# @RELATION DEPENDS_ON -> [ValidationRecord]
# @DATA_CONTRACT params -> env_id: str, dashboard_ids: str (comma-separated)
# @DATA_CONTRACT response -> { [dashboard_id]: { status: str, run_id: str | None, task_name: str | None, last_run_at: str | None, history: list } }
@router.get("/status/batch")
async def get_validation_status_batch(
env_id: str = Query(...),
dashboard_ids: str = Query(...),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Return latest validation status for each requested dashboard ID."""
from ...models.llm import ValidationRecord, ValidationRun
ids = [id.strip() for id in dashboard_ids.split(",") if id.strip()]
if not ids:
return {}
# Get latest record per dashboard for this environment
from sqlalchemy import and_, desc
records = (
db.query(ValidationRecord)
.filter(
and_(
ValidationRecord.dashboard_id.in_(ids),
ValidationRecord.environment_id == env_id,
)
)
.order_by(desc(ValidationRecord.timestamp))
.all()
)
# Get run info for task names
run_ids = list({r.run_id for r in records if r.run_id})
runs = {}
if run_ids:
run_objs = db.query(ValidationRun).filter(ValidationRun.id.in_(run_ids)).all()
runs = {r.id: r for r in run_objs}
result: dict[str, dict] = {}
for rec in records:
if rec.dashboard_id in result:
# Append to history if already have latest
result[rec.dashboard_id]["history"].append({
"status": rec.status,
"last_run_at": rec.timestamp.isoformat() if rec.timestamp else None,
"task_name": runs.get(rec.run_id, {}).task_id if rec.run_id else None,
"run_id": rec.run_id,
})
else:
# First (latest) record sets the primary status
run = runs.get(rec.run_id)
result[rec.dashboard_id] = {
"status": rec.status,
"last_run_at": rec.timestamp.isoformat() if rec.timestamp else None,
"task_name": rec.task_id or (run.task_id if run else None),
"run_id": rec.run_id,
"history": [],
}
return result
# #endregion get_validation_status_batch
# #endregion ValidationTasksRoutes