Fix test failures and workload/websocket behavior

This commit is contained in:
beabigegg
2026-01-11 08:37:21 +08:00
parent 3bdc6ff1c9
commit f5f870da56
49 changed files with 3006 additions and 1132 deletions

View File

@@ -4,7 +4,7 @@ from sqlalchemy.orm import Session
from typing import Optional
from app.core.database import get_db
from app.models import User, Project, Trigger, TriggerLog
from app.models import User, Project, Trigger, TriggerLog, CustomField
from app.schemas.trigger import (
TriggerCreate, TriggerUpdate, TriggerResponse, TriggerListResponse,
TriggerLogResponse, TriggerLogListResponse, TriggerUserInfo
@@ -16,6 +16,10 @@ from app.services.action_executor import ActionValidationError
router = APIRouter(tags=["triggers"])
FIELD_CHANGE_FIELDS = {"status_id", "assignee_id", "priority", "start_date", "due_date", "custom_fields"}
FIELD_CHANGE_OPERATORS = {"equals", "not_equals", "changed_to", "changed_from", "before", "after", "in"}
DATE_FIELDS = {"start_date", "due_date"}
def trigger_to_response(trigger: Trigger) -> TriggerResponse:
"""Convert Trigger model to TriggerResponse."""
@@ -39,6 +43,96 @@ def trigger_to_response(trigger: Trigger) -> TriggerResponse:
)
def _validate_field_change_conditions(conditions, project_id: str, db: Session) -> None:
rules = []
if conditions.rules is not None:
if conditions.logic != "and":
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Composite conditions only support logic 'and'",
)
if not conditions.rules:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Composite conditions require at least one rule",
)
rules = conditions.rules
else:
if not conditions.field:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Field is required for field_change triggers",
)
rules = [conditions]
for rule in rules:
field = rule.field
operator = rule.operator
value = rule.value
field_id = rule.field_id or getattr(conditions, "field_id", None)
if field not in FIELD_CHANGE_FIELDS:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid condition field. Must be 'status_id', 'assignee_id', 'priority', 'start_date', 'due_date', or 'custom_fields'",
)
if not operator:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Operator is required for field_change triggers",
)
if operator not in FIELD_CHANGE_OPERATORS:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid operator. Must be 'equals', 'not_equals', 'changed_to', 'changed_from', 'before', 'after', or 'in'",
)
if value is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Condition value is required for field_change triggers",
)
field_type = None
if field in DATE_FIELDS:
field_type = "date"
elif field == "custom_fields":
if not field_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Custom field ID is required when field is custom_fields",
)
custom_field = db.query(CustomField).filter(
CustomField.id == field_id,
CustomField.project_id == project_id,
).first()
if not custom_field:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Custom field not found in this project",
)
field_type = custom_field.field_type
if operator in {"before", "after"}:
if field_type not in {"date", "number", "formula"}:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Operator 'before/after' is only valid for date or number fields",
)
if operator == "in":
if field_type == "date":
if not isinstance(value, dict) or "start" not in value or "end" not in value:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Date 'in' operator requires a range with start and end",
)
elif not isinstance(value, list):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Operator 'in' requires a list of values",
)
@router.post("/api/projects/{project_id}/triggers", response_model=TriggerResponse, status_code=status.HTTP_201_CREATED)
async def create_trigger(
project_id: str,
@@ -71,27 +165,7 @@ async def create_trigger(
# Validate conditions based on trigger type
if trigger_data.trigger_type == "field_change":
# Validate field_change conditions
if not trigger_data.conditions.field:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Field is required for field_change triggers",
)
if trigger_data.conditions.field not in ["status_id", "assignee_id", "priority"]:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid condition field. Must be 'status_id', 'assignee_id', or 'priority'",
)
if not trigger_data.conditions.operator:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Operator is required for field_change triggers",
)
if trigger_data.conditions.operator not in ["equals", "not_equals", "changed_to", "changed_from"]:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid operator. Must be 'equals', 'not_equals', 'changed_to', or 'changed_from'",
)
_validate_field_change_conditions(trigger_data.conditions, project_id, db)
elif trigger_data.trigger_type == "schedule":
# Validate schedule conditions
has_cron = trigger_data.conditions.cron_expression is not None
@@ -234,11 +308,7 @@ async def update_trigger(
if trigger_data.conditions is not None:
# Validate conditions based on trigger type
if trigger.trigger_type == "field_change":
if trigger_data.conditions.field and trigger_data.conditions.field not in ["status_id", "assignee_id", "priority"]:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid condition field",
)
_validate_field_change_conditions(trigger_data.conditions, trigger.project_id, db)
elif trigger.trigger_type == "schedule":
# Validate cron expression if provided
if trigger_data.conditions.cron_expression is not None: