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

@@ -13,9 +13,9 @@ from typing import Optional, List, Dict, Any, Tuple, Set
from croniter import croniter
from sqlalchemy.orm import Session
from sqlalchemy import and_
from sqlalchemy import func
from app.models import Trigger, TriggerLog, Task, Project
from app.models import Trigger, TriggerLog, Task, Project, ProjectMember, User, Role
from app.services.notification_service import NotificationService
logger = logging.getLogger(__name__)
@@ -408,41 +408,77 @@ class TriggerSchedulerService:
logger.warning(f"Trigger {trigger.id} has no associated project")
return
target_user_id = TriggerSchedulerService._resolve_target(project, target)
if not target_user_id:
recipient_ids = TriggerSchedulerService._resolve_target(db, project, target)
if not recipient_ids:
logger.debug(f"No target user resolved for trigger {trigger.id} with target '{target}'")
return
# Format message with variables
message = TriggerSchedulerService._format_template(template, trigger, project)
NotificationService.create_notification(
db=db,
user_id=target_user_id,
notification_type="scheduled_trigger",
reference_type="trigger",
reference_id=trigger.id,
title=f"Scheduled: {trigger.name}",
message=message,
)
for user_id in recipient_ids:
NotificationService.create_notification(
db=db,
user_id=user_id,
notification_type="scheduled_trigger",
reference_type="trigger",
reference_id=trigger.id,
title=f"Scheduled: {trigger.name}",
message=message,
)
@staticmethod
def _resolve_target(project: Project, target: str) -> Optional[str]:
def _resolve_target(db: Session, project: Project, target: str) -> List[str]:
"""
Resolve notification target to user ID.
Resolve notification target to user IDs.
Args:
project: The project context
target: Target specification (e.g., "project_owner", "user:<id>")
Returns:
User ID or None
List of user IDs
"""
recipients: Set[str] = set()
if target == "project_owner":
return project.owner_id
if project.owner_id:
recipients.add(project.owner_id)
elif target == "project_members":
if project.owner_id:
recipients.add(project.owner_id)
member_rows = db.query(ProjectMember.user_id).join(
User,
User.id == ProjectMember.user_id,
).filter(
ProjectMember.project_id == project.id,
User.is_active == True,
).all()
recipients.update(row[0] for row in member_rows if row and row[0])
elif target.startswith("department:"):
department_id = target.split(":", 1)[1]
if department_id:
user_rows = db.query(User.id).filter(
User.department_id == department_id,
User.is_active == True,
).all()
recipients.update(row[0] for row in user_rows if row and row[0])
elif target.startswith("role:"):
role_name = target.split(":", 1)[1].strip()
if role_name:
role = db.query(Role).filter(func.lower(Role.name) == role_name.lower()).first()
if role:
user_rows = db.query(User.id).filter(
User.role_id == role.id,
User.is_active == True,
).all()
recipients.update(row[0] for row in user_rows if row and row[0])
elif target.startswith("user:"):
return target.split(":", 1)[1]
return None
user_id = target.split(":", 1)[1]
if user_id:
recipients.add(user_id)
return list(recipients)
@staticmethod
def _format_template(template: str, trigger: Trigger, project: Project) -> str:
@@ -718,8 +754,8 @@ class TriggerSchedulerService:
)
# Resolve target user
target_user_id = TriggerSchedulerService._resolve_deadline_target(task, target)
if not target_user_id:
recipient_ids = TriggerSchedulerService._resolve_deadline_target(db, task, target)
if not recipient_ids:
logger.debug(
f"No target user resolved for deadline reminder, task {task.id}, target '{target}'"
)
@@ -730,18 +766,19 @@ class TriggerSchedulerService:
template, trigger, task, reminder_days
)
NotificationService.create_notification(
db=db,
user_id=target_user_id,
notification_type="deadline_reminder",
reference_type="task",
reference_id=task.id,
title=f"Deadline Reminder: {task.title}",
message=message,
)
for user_id in recipient_ids:
NotificationService.create_notification(
db=db,
user_id=user_id,
notification_type="deadline_reminder",
reference_type="task",
reference_id=task.id,
title=f"Deadline Reminder: {task.title}",
message=message,
)
@staticmethod
def _resolve_deadline_target(task: Task, target: str) -> Optional[str]:
def _resolve_deadline_target(db: Session, task: Task, target: str) -> List[str]:
"""
Resolve notification target for deadline reminders.
@@ -750,17 +787,55 @@ class TriggerSchedulerService:
target: Target specification
Returns:
User ID or None
List of user IDs
"""
recipients: Set[str] = set()
if target == "assignee":
return task.assignee_id
if task.assignee_id:
recipients.add(task.assignee_id)
elif target == "creator":
return task.created_by
if task.created_by:
recipients.add(task.created_by)
elif target == "project_owner":
return task.project.owner_id if task.project else None
if task.project and task.project.owner_id:
recipients.add(task.project.owner_id)
elif target == "project_members":
if task.project:
if task.project.owner_id:
recipients.add(task.project.owner_id)
member_rows = db.query(ProjectMember.user_id).join(
User,
User.id == ProjectMember.user_id,
).filter(
ProjectMember.project_id == task.project_id,
User.is_active == True,
).all()
recipients.update(row[0] for row in member_rows if row and row[0])
elif target.startswith("department:"):
department_id = target.split(":", 1)[1]
if department_id:
user_rows = db.query(User.id).filter(
User.department_id == department_id,
User.is_active == True,
).all()
recipients.update(row[0] for row in user_rows if row and row[0])
elif target.startswith("role:"):
role_name = target.split(":", 1)[1].strip()
if role_name:
role = db.query(Role).filter(func.lower(Role.name) == role_name.lower()).first()
if role:
user_rows = db.query(User.id).filter(
User.role_id == role.id,
User.is_active == True,
).all()
recipients.update(row[0] for row in user_rows if row and row[0])
elif target.startswith("user:"):
return target.split(":", 1)[1]
return None
user_id = target.split(":", 1)[1]
if user_id:
recipients.add(user_id)
return list(recipients)
@staticmethod
def _format_deadline_template(