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:
@@ -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]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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]:
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user