feat: complete LOW priority code quality improvements
Backend: - LOW-002: Add Query validation with max page size limits (100) - LOW-003: Replace magic strings with TaskStatus.is_done flag - LOW-004: Add 'creation' trigger type validation - Add action_executor.py with UpdateFieldAction and AutoAssignAction Frontend: - LOW-005: Replace TypeScript 'any' with 'unknown' + type guards - LOW-006: Add ConfirmModal component with A11Y support - LOW-007: Add ToastContext for user feedback notifications - LOW-009: Add Skeleton components (17 loading states replaced) - LOW-010: Setup Vitest with 21 tests for ConfirmModal and Skeleton Components updated: - App.tsx, ProtectedRoute.tsx, Spaces.tsx, Projects.tsx, Tasks.tsx - ProjectSettings.tsx, AuditPage.tsx, WorkloadPage.tsx, ProjectHealthPage.tsx - Comments.tsx, AttachmentList.tsx, TriggerList.tsx, TaskDetailModal.tsx - NotificationBell.tsx, BlockerDialog.tsx, CalendarView.tsx, WorkloadUserDetail.tsx 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
|
||||
@@ -11,6 +11,8 @@ from app.schemas.trigger import (
|
||||
)
|
||||
from app.middleware.auth import get_current_user, check_project_access, check_project_edit_access
|
||||
from app.services.trigger_scheduler import TriggerSchedulerService
|
||||
from app.services.trigger_service import TriggerService
|
||||
from app.services.action_executor import ActionValidationError
|
||||
|
||||
router = APIRouter(tags=["triggers"])
|
||||
|
||||
@@ -60,10 +62,11 @@ async def create_trigger(
|
||||
)
|
||||
|
||||
# Validate trigger type
|
||||
if trigger_data.trigger_type not in ["field_change", "schedule"]:
|
||||
valid_trigger_types = ["field_change", "schedule", "creation"]
|
||||
if trigger_data.trigger_type not in valid_trigger_types:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid trigger type. Must be 'field_change' or 'schedule'",
|
||||
detail=f"Invalid trigger type. Must be one of: {', '.join(valid_trigger_types)}",
|
||||
)
|
||||
|
||||
# Validate conditions based on trigger type
|
||||
@@ -111,6 +114,16 @@ async def create_trigger(
|
||||
detail=error_msg or "Invalid cron expression",
|
||||
)
|
||||
|
||||
# Validate actions configuration (FEAT-014, FEAT-015)
|
||||
try:
|
||||
actions_dicts = [a.model_dump(exclude_none=True) for a in trigger_data.actions]
|
||||
TriggerService.validate_actions(actions_dicts, db)
|
||||
except ActionValidationError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e),
|
||||
)
|
||||
|
||||
# Create trigger
|
||||
trigger = Trigger(
|
||||
id=str(uuid.uuid4()),
|
||||
@@ -119,7 +132,7 @@ async def create_trigger(
|
||||
description=trigger_data.description,
|
||||
trigger_type=trigger_data.trigger_type,
|
||||
conditions=trigger_data.conditions.model_dump(),
|
||||
actions=[a.model_dump() for a in trigger_data.actions],
|
||||
actions=[a.model_dump(exclude_none=True) for a in trigger_data.actions],
|
||||
is_active=trigger_data.is_active,
|
||||
created_by=current_user.id,
|
||||
)
|
||||
@@ -239,7 +252,16 @@ async def update_trigger(
|
||||
)
|
||||
trigger.conditions = trigger_data.conditions.model_dump(exclude_none=True)
|
||||
if trigger_data.actions is not None:
|
||||
trigger.actions = [a.model_dump() for a in trigger_data.actions]
|
||||
# Validate actions configuration (FEAT-014, FEAT-015)
|
||||
try:
|
||||
actions_dicts = [a.model_dump(exclude_none=True) for a in trigger_data.actions]
|
||||
TriggerService.validate_actions(actions_dicts, db)
|
||||
except ActionValidationError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e),
|
||||
)
|
||||
trigger.actions = [a.model_dump(exclude_none=True) for a in trigger_data.actions]
|
||||
if trigger_data.is_active is not None:
|
||||
trigger.is_active = trigger_data.is_active
|
||||
|
||||
@@ -278,8 +300,8 @@ async def delete_trigger(
|
||||
@router.get("/api/triggers/{trigger_id}/logs", response_model=TriggerLogListResponse)
|
||||
async def list_trigger_logs(
|
||||
trigger_id: str,
|
||||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
limit: int = Query(50, ge=1, le=200, description="Number of logs to return"),
|
||||
offset: int = Query(0, ge=0, description="Number of logs to skip"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user