feat: render real ticket event timeline
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -66,6 +66,7 @@ class TicketEventOut(Schema):
|
||||
from_value: dict
|
||||
to_value: dict
|
||||
remark: str
|
||||
operator: TicketUserOut | None = None
|
||||
created_at: datetime
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user