feat: dual system messages — detailed in task, brief in chat
Some checks failed
Deploy Tracker / deploy (push) Failing after 1s
Some checks failed
Deploy Tracker / deploy (push) Failing after 1s
- Task comments: who, what, when (detailed history) - Chat messages: brief notifications - PATCH /tasks accepts ?actor= for attribution
This commit is contained in:
parent
b527e19db1
commit
2f0c4d1636
@ -80,48 +80,83 @@ class AssignRequest(BaseModel):
|
|||||||
|
|
||||||
# --- Helpers ---
|
# --- Helpers ---
|
||||||
|
|
||||||
async def _system_message(db: AsyncSession, task: Task, content: str, project_slug: str = ""):
|
async def _get_project_chat_id(db: AsyncSession, project_id) -> str | None:
|
||||||
"""Create a system message for a task and broadcast it via WS.
|
"""Find project chat UUID."""
|
||||||
|
|
||||||
Message goes to the project chat (visible in ChatPanel) with task_id reference.
|
|
||||||
"""
|
|
||||||
from tracker.ws.manager import manager
|
|
||||||
|
|
||||||
# Find project chat
|
|
||||||
chat_id = None
|
|
||||||
if task.project_id:
|
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(Chat).where(Chat.project_id == task.project_id, Chat.kind == "project")
|
select(Chat).where(Chat.project_id == project_id, Chat.kind == "project")
|
||||||
)
|
)
|
||||||
chat = result.scalar_one_or_none()
|
chat = result.scalar_one_or_none()
|
||||||
if chat:
|
return chat.id if chat else None
|
||||||
chat_id = chat.id
|
|
||||||
|
|
||||||
msg = Message(
|
|
||||||
chat_id=chat_id,
|
async def _system_message(
|
||||||
task_id=task.id,
|
db: AsyncSession,
|
||||||
author_type="system",
|
task: Task,
|
||||||
author_slug="system",
|
chat_text: str,
|
||||||
content=content,
|
task_text: str | None = None,
|
||||||
mentions=[],
|
project_slug: str = "",
|
||||||
)
|
actor_slug: str = "system",
|
||||||
db.add(msg)
|
):
|
||||||
await db.flush() # get msg.id
|
"""Create system messages: one in project chat + one in task comments.
|
||||||
|
|
||||||
|
chat_text: short message for project chat
|
||||||
|
task_text: detailed message for task history (if None, same as chat_text)
|
||||||
|
"""
|
||||||
|
from tracker.ws.manager import manager
|
||||||
|
import datetime
|
||||||
|
|
||||||
prefix = project_slug[:2].upper() if project_slug else "XX"
|
prefix = project_slug[:2].upper() if project_slug else "XX"
|
||||||
key = f"{prefix}-{task.number}"
|
key = f"{prefix}-{task.number}"
|
||||||
|
task_text = task_text or chat_text
|
||||||
|
now = datetime.datetime.now(datetime.timezone.utc)
|
||||||
|
|
||||||
event_data = {
|
chat_id = await _get_project_chat_id(db, task.project_id)
|
||||||
"id": str(msg.id),
|
|
||||||
"chat_id": str(chat_id) if chat_id else None,
|
# 1. Task comment — detailed history
|
||||||
|
task_msg = Message(
|
||||||
|
task_id=task.id,
|
||||||
|
author_type="system",
|
||||||
|
author_slug=actor_slug,
|
||||||
|
content=task_text,
|
||||||
|
mentions=[],
|
||||||
|
)
|
||||||
|
db.add(task_msg)
|
||||||
|
|
||||||
|
# 2. Chat message — brief notification
|
||||||
|
chat_msg = None
|
||||||
|
if chat_id:
|
||||||
|
chat_msg = Message(
|
||||||
|
chat_id=chat_id,
|
||||||
|
author_type="system",
|
||||||
|
author_slug=actor_slug,
|
||||||
|
content=chat_text,
|
||||||
|
mentions=[],
|
||||||
|
)
|
||||||
|
db.add(chat_msg)
|
||||||
|
|
||||||
|
await db.flush()
|
||||||
|
|
||||||
|
# Broadcast task comment
|
||||||
|
await manager.broadcast_task_event(str(task.project_id), "message.new", {
|
||||||
|
"id": str(task_msg.id),
|
||||||
"task_id": str(task.id),
|
"task_id": str(task.id),
|
||||||
"task_key": key,
|
"task_key": key,
|
||||||
"author_type": "system",
|
"author_type": "system",
|
||||||
"author_slug": "system",
|
"author_slug": actor_slug,
|
||||||
"content": content,
|
"content": task_text,
|
||||||
"created_at": msg.created_at.isoformat() if msg.created_at else "",
|
"created_at": task_msg.created_at.isoformat() if task_msg.created_at else now.isoformat(),
|
||||||
}
|
})
|
||||||
await manager.broadcast_task_event(str(task.project_id), "message.new", event_data)
|
|
||||||
|
# Broadcast chat message
|
||||||
|
if chat_msg and chat_id:
|
||||||
|
await manager.broadcast_task_event(str(task.project_id), "message.new", {
|
||||||
|
"id": str(chat_msg.id),
|
||||||
|
"chat_id": str(chat_id),
|
||||||
|
"author_type": "system",
|
||||||
|
"author_slug": actor_slug,
|
||||||
|
"content": chat_text,
|
||||||
|
"created_at": chat_msg.created_at.isoformat() if chat_msg.created_at else now.isoformat(),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def _task_out(t: Task, project_slug: str = "") -> dict:
|
def _task_out(t: Task, project_slug: str = "") -> dict:
|
||||||
@ -248,21 +283,29 @@ async def create_task(
|
|||||||
# System message
|
# System message
|
||||||
slug = project.slug
|
slug = project.slug
|
||||||
key = f"{slug[:2].upper()}-{task.number}"
|
key = f"{slug[:2].upper()}-{task.number}"
|
||||||
sys_text = f"Задача {key} создана: {task.title}"
|
chat_text = f"{key} создана: {task.title}"
|
||||||
|
task_text = f"Задача создана: {task.title}"
|
||||||
if task.assignee_slug:
|
if task.assignee_slug:
|
||||||
sys_text += f". Назначена на @{task.assignee_slug}"
|
chat_text += f" → @{task.assignee_slug}"
|
||||||
await _system_message(db, task_full, sys_text, slug)
|
task_text += f". Назначена на @{task.assignee_slug}"
|
||||||
|
await _system_message(db, task_full, chat_text=chat_text, task_text=task_text, project_slug=slug)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
return _task_out(task_full, slug)
|
return _task_out(task_full, slug)
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/tasks/{task_id}", response_model=TaskOut)
|
@router.patch("/tasks/{task_id}", response_model=TaskOut)
|
||||||
async def update_task(task_id: str, req: TaskUpdate, db: AsyncSession = Depends(get_db)):
|
async def update_task(
|
||||||
|
task_id: str,
|
||||||
|
req: TaskUpdate,
|
||||||
|
actor: Optional[str] = Query(None, description="Who made the change"),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
task = await _get_task(task_id, db)
|
task = await _get_task(task_id, db)
|
||||||
slug = task.project.slug if task.project else ""
|
slug = task.project.slug if task.project else ""
|
||||||
prefix = slug[:2].upper() if slug else "XX"
|
prefix = slug[:2].upper() if slug else "XX"
|
||||||
key = f"{prefix}-{task.number}"
|
key = f"{prefix}-{task.number}"
|
||||||
|
who = f"@{actor}" if actor else "Кто-то"
|
||||||
|
|
||||||
old_status = task.status
|
old_status = task.status
|
||||||
old_assignee = task.assignee_slug
|
old_assignee = task.assignee_slug
|
||||||
@ -287,13 +330,33 @@ async def update_task(task_id: str, req: TaskUpdate, db: AsyncSession = Depends(
|
|||||||
task.position = req.position
|
task.position = req.position
|
||||||
|
|
||||||
# System messages for important changes
|
# System messages for important changes
|
||||||
|
import datetime
|
||||||
|
now_str = datetime.datetime.now(datetime.timezone.utc).strftime("%H:%M UTC")
|
||||||
if req.status is not None and req.status != old_status:
|
if req.status is not None and req.status != old_status:
|
||||||
await _system_message(db, task, f"{key}: статус изменён {old_status} → {req.status}", slug)
|
await _system_message(
|
||||||
|
db, task,
|
||||||
|
chat_text=f"{key}: {old_status} → {req.status}",
|
||||||
|
task_text=f"{who} изменил статус: {old_status} → {req.status} ({now_str})",
|
||||||
|
project_slug=slug,
|
||||||
|
actor_slug=actor or "system",
|
||||||
|
)
|
||||||
if req.assignee_slug is not None and req.assignee_slug != old_assignee:
|
if req.assignee_slug is not None and req.assignee_slug != old_assignee:
|
||||||
if req.assignee_slug:
|
if req.assignee_slug:
|
||||||
await _system_message(db, task, f"{key}: назначена на @{req.assignee_slug}", slug)
|
await _system_message(
|
||||||
|
db, task,
|
||||||
|
chat_text=f"{key}: назначена на @{req.assignee_slug}",
|
||||||
|
task_text=f"{who} назначил задачу на @{req.assignee_slug} ({now_str})",
|
||||||
|
project_slug=slug,
|
||||||
|
actor_slug=actor or "system",
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await _system_message(db, task, f"{key}: исполнитель снят (был @{old_assignee})", slug)
|
await _system_message(
|
||||||
|
db, task,
|
||||||
|
chat_text=f"{key}: исполнитель снят",
|
||||||
|
task_text=f"{who} снял исполнителя @{old_assignee} ({now_str})",
|
||||||
|
project_slug=slug,
|
||||||
|
actor_slug=actor or "system",
|
||||||
|
)
|
||||||
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
await db.refresh(task)
|
await db.refresh(task)
|
||||||
@ -343,7 +406,15 @@ async def take_task(task_id: str, slug: str = Query(...), db: AsyncSession = Dep
|
|||||||
proj_slug = task.project.slug if task.project else ""
|
proj_slug = task.project.slug if task.project else ""
|
||||||
prefix = proj_slug[:2].upper() if proj_slug else "XX"
|
prefix = proj_slug[:2].upper() if proj_slug else "XX"
|
||||||
key = f"{prefix}-{task.number}"
|
key = f"{prefix}-{task.number}"
|
||||||
await _system_message(db, task, f"{key}: @{slug} взял задачу в работу", proj_slug)
|
import datetime
|
||||||
|
now_str = datetime.datetime.now(datetime.timezone.utc).strftime("%H:%M UTC")
|
||||||
|
await _system_message(
|
||||||
|
db, task,
|
||||||
|
chat_text=f"{key}: @{slug} взял в работу",
|
||||||
|
task_text=f"@{slug} взял задачу в работу ({now_str})",
|
||||||
|
project_slug=proj_slug,
|
||||||
|
actor_slug=slug,
|
||||||
|
)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
# Broadcast task.assigned event
|
# Broadcast task.assigned event
|
||||||
@ -402,7 +473,12 @@ async def assign_task(task_id: str, req: AssignRequest, db: AsyncSession = Depen
|
|||||||
proj_slug = task.project.slug if task.project else ""
|
proj_slug = task.project.slug if task.project else ""
|
||||||
prefix = proj_slug[:2].upper() if proj_slug else "XX"
|
prefix = proj_slug[:2].upper() if proj_slug else "XX"
|
||||||
key = f"{prefix}-{task.number}"
|
key = f"{prefix}-{task.number}"
|
||||||
await _system_message(db, task, f"{key}: назначена на @{req.assignee_slug}", proj_slug)
|
await _system_message(
|
||||||
|
db, task,
|
||||||
|
chat_text=f"{key}: назначена на @{req.assignee_slug}",
|
||||||
|
task_text=f"Задача назначена на @{req.assignee_slug}",
|
||||||
|
project_slug=proj_slug,
|
||||||
|
)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
# Broadcast task.assigned event
|
# Broadcast task.assigned event
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user