feat: render real ticket event timeline

This commit is contained in:
hailin
2026-07-02 10:36:52 +08:00
parent a6df96ebb0
commit 399a5bdb73
8 changed files with 196 additions and 30 deletions

View File

@@ -16,6 +16,7 @@ from apps.tickets.schemas import (
TicketAttachmentOut,
TicketCommentIn,
TicketCommentOut,
TicketEventOut,
TicketIn,
TicketListOut,
TicketOut,
@@ -63,6 +64,18 @@ def serialize_ticket(ticket: Ticket) -> dict:
}
def serialize_ticket_event(event: TicketEvent) -> dict:
return {
"id": event.id,
"event_type": event.event_type,
"from_value": event.from_value,
"to_value": event.to_value,
"remark": event.remark,
"operator": serialize_ticket_user(event.operator),
"created_at": event.created_at,
}
@router.post("", response=TicketOut)
def create_ticket(request, payload: TicketIn):
require_permission(request, "ticket.create")
@@ -85,6 +98,7 @@ def create_ticket(request, payload: TicketIn):
ticket=ticket,
event_type="created",
to_value={"status": ticket.status},
operator=request.user if request.user.is_authenticated else None,
)
return serialize_ticket(ticket)
@@ -175,6 +189,13 @@ def get_ticket(request, ticket_id: int):
)
@router.get("/{ticket_id}/events", response=list[TicketEventOut])
def list_ticket_events(request, ticket_id: int):
require_permission(request, "ticket.view")
events = TicketEvent.objects.select_related("operator").filter(ticket_id=ticket_id)
return [serialize_ticket_event(event) for event in events]
@router.patch("/{ticket_id}", response=TicketOut)
def update_ticket(request, ticket_id: int, payload: TicketUpdateIn):
require_permission(request, "ticket.edit")
@@ -247,5 +268,6 @@ def add_ticket_attachment(request, ticket_id: int, payload: TicketAttachmentIn):
ticket=ticket,
event_type="attachment_added",
to_value={"attachment_id": attachment.id, "file_name": attachment.file_name},
operator=request.user if request.user.is_authenticated else None,
)
return attachment

View File

@@ -66,6 +66,7 @@ class TicketEventOut(Schema):
from_value: dict
to_value: dict
remark: str
operator: TicketUserOut | None = None
created_at: datetime

View File

@@ -127,3 +127,38 @@ def test_transition_ticket_requires_non_empty_remark(admin_client, db):
)
assert response.status_code == 400
def test_get_ticket_events_returns_real_timeline_with_operator(admin_client, db):
user_model = get_user_model()
operator = user_model.objects.get(username="admin")
ticket = Ticket.objects.create(
ticket_no="TK202607020200",
title="A",
description="A",
status="in_progress",
)
TicketEvent.objects.create(
ticket=ticket,
event_type="created",
to_value={"status": "pending"},
operator=operator,
)
TicketEvent.objects.create(
ticket=ticket,
event_type="status_change",
from_value={"status": "pending"},
to_value={"status": "in_progress"},
operator=operator,
remark="开始排查认证链路",
)
response = admin_client.get(f"/api/tickets/{ticket.id}/events")
assert response.status_code == 200
payload = response.json()
assert [event["event_type"] for event in payload] == ["created", "status_change"]
assert payload[1]["from_value"] == {"status": "pending"}
assert payload[1]["to_value"] == {"status": "in_progress"}
assert payload[1]["remark"] == "开始排查认证链路"
assert payload[1]["operator"]["display_name"] == "admin"