fix: resolve architecture validation errors and warnings

- Move database operations from API to service layer:
  - Add create_pending_scan() method to code_quality_service
  - Add get_running_scans() method to code_quality_service
  - Update API to use service methods instead of direct db queries
- Replace inline SVG spinner with $icon('spinner', ...) helper
- Add comment documenting custom dropdown exception

Validation now passes with 0 errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-21 21:07:18 +01:00
parent 4f97c00860
commit d50b154823
3 changed files with 46 additions and 21 deletions

View File

@@ -4,7 +4,7 @@ RESTful API for code quality validation and violation management
Supports multiple validator types: architecture, security, performance Supports multiple validator types: architecture, security, performance
""" """
from datetime import UTC, datetime from datetime import datetime
from enum import Enum from enum import Enum
from fastapi import APIRouter, BackgroundTasks, Depends, Query from fastapi import APIRouter, BackgroundTasks, Depends, Query
@@ -214,15 +214,10 @@ async def trigger_scan(
triggered_by = f"manual:{current_user.username}" triggered_by = f"manual:{current_user.username}"
for vtype in request.validator_types: for vtype in request.validator_types:
# Create scan record with pending status # Create scan record with pending status via service
scan = ArchitectureScan( scan = code_quality_service.create_pending_scan(
timestamp=datetime.now(UTC), db, validator_type=vtype.value, triggered_by=triggered_by
validator_type=vtype.value,
status="pending",
triggered_by=triggered_by,
) )
db.add(scan)
db.flush() # Get scan.id
# Queue background task # Queue background task
background_tasks.add_task(execute_code_quality_scan, scan.id) background_tasks.add_task(execute_code_quality_scan, scan.id)
@@ -257,7 +252,7 @@ async def get_scan_status(
Use this endpoint to poll for scan completion. Use this endpoint to poll for scan completion.
""" """
scan = db.query(ArchitectureScan).filter(ArchitectureScan.id == scan_id).first() scan = code_quality_service.get_scan_by_id(db, scan_id)
if not scan: if not scan:
raise ScanNotFoundException(scan_id) raise ScanNotFoundException(scan_id)
@@ -274,12 +269,7 @@ async def get_running_scans(
Returns scans with status 'pending' or 'running'. Returns scans with status 'pending' or 'running'.
""" """
scans = ( scans = code_quality_service.get_running_scans(db)
db.query(ArchitectureScan)
.filter(ArchitectureScan.status.in_(["pending", "running"]))
.order_by(ArchitectureScan.timestamp.desc())
.all()
)
return [_scan_to_response(scan) for scan in scans] return [_scan_to_response(scan) for scan in scans]

View File

@@ -229,6 +229,44 @@ class CodeQualityService:
"""Get scan by ID""" """Get scan by ID"""
return db.query(ArchitectureScan).filter(ArchitectureScan.id == scan_id).first() return db.query(ArchitectureScan).filter(ArchitectureScan.id == scan_id).first()
def create_pending_scan(
self, db: Session, validator_type: str, triggered_by: str
) -> ArchitectureScan:
"""
Create a new scan record with pending status.
Args:
db: Database session
validator_type: Type of validator (architecture, security, performance)
triggered_by: Who triggered the scan (e.g., "manual:username")
Returns:
The created ArchitectureScan record with ID populated
"""
scan = ArchitectureScan(
timestamp=datetime.now(UTC),
validator_type=validator_type,
status="pending",
triggered_by=triggered_by,
)
db.add(scan)
db.flush() # Get scan.id
return scan
def get_running_scans(self, db: Session) -> list[ArchitectureScan]:
"""
Get all currently running scans (pending or running status).
Returns:
List of scans with status 'pending' or 'running', newest first
"""
return (
db.query(ArchitectureScan)
.filter(ArchitectureScan.status.in_(["pending", "running"]))
.order_by(ArchitectureScan.timestamp.desc())
.all()
)
def get_scan_history( def get_scan_history(
self, db: Session, limit: int = 30, validator_type: str = None self, db: Session, limit: int = 30, validator_type: str = None
) -> list[ArchitectureScan]: ) -> list[ArchitectureScan]:

View File

@@ -14,7 +14,7 @@
{% block content %} {% block content %}
{% call page_header_flex(title='Code Quality Dashboard', subtitle='Unified code quality tracking: architecture, security, and performance') %} {% call page_header_flex(title='Code Quality Dashboard', subtitle='Unified code quality tracking: architecture, security, and performance') %}
{{ refresh_button(variant='secondary') }} {{ refresh_button(variant='secondary') }}
<!-- Scan Dropdown --> {# Custom dropdown: disabled state + template switching not supported by macro #}
<div x-data="{ scanDropdownOpen: false }" class="relative"> <div x-data="{ scanDropdownOpen: false }" class="relative">
<button @click="scanDropdownOpen = !scanDropdownOpen" <button @click="scanDropdownOpen = !scanDropdownOpen"
:disabled="scanning" :disabled="scanning"
@@ -65,10 +65,7 @@
x-transition x-transition
class="flex items-center p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400" class="flex items-center p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400"
role="alert"> role="alert">
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <span x-html="$icon('spinner', 'w-5 h-5 mr-3 text-blue-500')"></span>
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span x-text="scanProgress">Running scan...</span> <span x-text="scanProgress">Running scan...</span>
<span class="ml-2 text-xs text-gray-500 dark:text-gray-400">(You can navigate away - scan runs in background)</span> <span class="ml-2 text-xs text-gray-500 dark:text-gray-400">(You can navigate away - scan runs in background)</span>
</div> </div>