feat: implement background task architecture for code quality scans
- Add status fields to ArchitectureScan model (status, started_at, completed_at, error_message, progress_message) - Create database migration for new status fields - Create background task function execute_code_quality_scan() - Update API to return 202 with job IDs and support polling - Add code quality scans to unified BackgroundTasksService - Integrate scans into background tasks API and page - Implement frontend polling with 3-second interval - Add progress banner showing scan status - Users can navigate away while scans run in background - Document the implementation in architecture docs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,7 @@ class BackgroundTaskResponse(BaseModel):
|
||||
"""Unified background task response"""
|
||||
|
||||
id: int
|
||||
task_type: str # 'import' or 'test_run'
|
||||
task_type: str # 'import', 'test_run', or 'code_quality_scan'
|
||||
status: str
|
||||
started_at: str | None
|
||||
completed_at: str | None
|
||||
@@ -46,6 +46,7 @@ class BackgroundTasksStatsResponse(BaseModel):
|
||||
# By type
|
||||
import_jobs: dict
|
||||
test_runs: dict
|
||||
code_quality_scans: dict
|
||||
|
||||
|
||||
def _convert_import_to_response(job) -> BackgroundTaskResponse:
|
||||
@@ -107,11 +108,47 @@ def _convert_test_run_to_response(run) -> BackgroundTaskResponse:
|
||||
)
|
||||
|
||||
|
||||
def _convert_scan_to_response(scan) -> BackgroundTaskResponse:
|
||||
"""Convert ArchitectureScan to BackgroundTaskResponse"""
|
||||
duration = scan.duration_seconds
|
||||
if scan.status in ["pending", "running"] and scan.started_at:
|
||||
duration = (datetime.now(UTC) - scan.started_at).total_seconds()
|
||||
|
||||
# Map validator type to human-readable name
|
||||
validator_names = {
|
||||
"architecture": "Architecture",
|
||||
"security": "Security",
|
||||
"performance": "Performance",
|
||||
}
|
||||
validator_name = validator_names.get(scan.validator_type, scan.validator_type)
|
||||
|
||||
return BackgroundTaskResponse(
|
||||
id=scan.id,
|
||||
task_type="code_quality_scan",
|
||||
status=scan.status,
|
||||
started_at=scan.started_at.isoformat() if scan.started_at else None,
|
||||
completed_at=scan.completed_at.isoformat() if scan.completed_at else None,
|
||||
duration_seconds=duration,
|
||||
description=f"{validator_name} code quality scan",
|
||||
triggered_by=scan.triggered_by,
|
||||
error_message=scan.error_message,
|
||||
details={
|
||||
"validator_type": scan.validator_type,
|
||||
"total_files": scan.total_files,
|
||||
"total_violations": scan.total_violations,
|
||||
"errors": scan.errors,
|
||||
"warnings": scan.warnings,
|
||||
"git_commit_hash": scan.git_commit_hash,
|
||||
"progress_message": scan.progress_message,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/tasks", response_model=list[BackgroundTaskResponse])
|
||||
async def list_background_tasks(
|
||||
status: str | None = Query(None, description="Filter by status"),
|
||||
task_type: str | None = Query(
|
||||
None, description="Filter by type (import, test_run)"
|
||||
None, description="Filter by type (import, test_run, code_quality_scan)"
|
||||
),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
db: Session = Depends(get_db),
|
||||
@@ -120,7 +157,7 @@ async def list_background_tasks(
|
||||
"""
|
||||
List all background tasks across the system
|
||||
|
||||
Returns a unified view of import jobs and test runs.
|
||||
Returns a unified view of import jobs, test runs, and code quality scans.
|
||||
"""
|
||||
tasks = []
|
||||
|
||||
@@ -138,6 +175,13 @@ async def list_background_tasks(
|
||||
)
|
||||
tasks.extend([_convert_test_run_to_response(run) for run in test_runs])
|
||||
|
||||
# Get code quality scans
|
||||
if task_type is None or task_type == "code_quality_scan":
|
||||
scans = background_tasks_service.get_code_quality_scans(
|
||||
db, status=status, limit=limit
|
||||
)
|
||||
tasks.extend([_convert_scan_to_response(scan) for scan in scans])
|
||||
|
||||
# Sort by start time (most recent first)
|
||||
tasks.sort(
|
||||
key=lambda t: t.started_at or "1970-01-01T00:00:00",
|
||||
@@ -157,22 +201,31 @@ async def get_background_tasks_stats(
|
||||
"""
|
||||
import_stats = background_tasks_service.get_import_stats(db)
|
||||
test_stats = background_tasks_service.get_test_run_stats(db)
|
||||
scan_stats = background_tasks_service.get_scan_stats(db)
|
||||
|
||||
# Combined stats
|
||||
total_running = import_stats["running"] + test_stats["running"]
|
||||
total_completed = import_stats["completed"] + test_stats["completed"]
|
||||
total_failed = import_stats["failed"] + test_stats["failed"]
|
||||
total_tasks = import_stats["total"] + test_stats["total"]
|
||||
total_running = (
|
||||
import_stats["running"] + test_stats["running"] + scan_stats["running"]
|
||||
)
|
||||
total_completed = (
|
||||
import_stats["completed"] + test_stats["completed"] + scan_stats["completed"]
|
||||
)
|
||||
total_failed = (
|
||||
import_stats["failed"] + test_stats["failed"] + scan_stats["failed"]
|
||||
)
|
||||
total_tasks = import_stats["total"] + test_stats["total"] + scan_stats["total"]
|
||||
tasks_today = import_stats["today"] + test_stats["today"] + scan_stats["today"]
|
||||
|
||||
return BackgroundTasksStatsResponse(
|
||||
total_tasks=total_tasks,
|
||||
running=total_running,
|
||||
completed=total_completed,
|
||||
failed=total_failed,
|
||||
tasks_today=import_stats["today"] + test_stats["today"],
|
||||
tasks_today=tasks_today,
|
||||
avg_duration_seconds=test_stats.get("avg_duration"),
|
||||
import_jobs=import_stats,
|
||||
test_runs=test_stats,
|
||||
code_quality_scans=scan_stats,
|
||||
)
|
||||
|
||||
|
||||
@@ -194,4 +247,8 @@ async def list_running_tasks(
|
||||
running_tests = background_tasks_service.get_running_test_runs(db)
|
||||
tasks.extend([_convert_test_run_to_response(run) for run in running_tests])
|
||||
|
||||
# Running code quality scans
|
||||
running_scans = background_tasks_service.get_running_scans(db)
|
||||
tasks.extend([_convert_scan_to_response(scan) for scan in running_scans])
|
||||
|
||||
return tasks
|
||||
|
||||
Reference in New Issue
Block a user