Skip to content

Commit 1ab84b7

Browse files
feat: add upload pipeline timeline to CommitAdmin
Add an upload_pipeline_timeline readonly field to the Django admin CommitAdmin page that queries UploadBreadcrumb records for the commit and renders them as an HTML table. The timeline shows: - Timestamp of each breadcrumb - Task name (Celery task that produced the breadcrumb) - Parent task ID (preceding task in the chain) - Detail (milestone, endpoint, error info) - Upload IDs associated with each breadcrumb Depends on PR #714 for new BreadcrumbData fields (task_name, parent_task_id) and lock lifecycle milestones. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 9b95e6b commit 1ab84b7

File tree

1 file changed

+80
-0
lines changed

1 file changed

+80
-0
lines changed

apps/codecov-api/core/admin.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from shared.django_apps.reports.models import ReportType
1919
from shared.django_apps.ta_timeseries.models import Testrun
2020
from shared.django_apps.timeseries.models import Measurement, MeasurementName
21+
from shared.django_apps.upload_breadcrumbs.models import UploadBreadcrumb
2122
from shared.django_apps.utils.paginator import EstimatedCountPaginator
2223
from shared.helpers.redis import get_redis_connection
2324
from shared.reports.enums import UploadState
@@ -270,6 +271,7 @@ class CommitAdmin(AdminMixin, admin.ModelAdmin):
270271
"deleted",
271272
"notified",
272273
"reprocess_actions",
274+
"upload_pipeline_timeline",
273275
)
274276
fields = readonly_fields
275277
paginator = EstimatedCountPaginator
@@ -359,6 +361,84 @@ def reprocess_actions(self, obj):
359361

360362
return format_html("<div>{}</div>", format_html("".join(buttons)))
361363

364+
@admin.display(description="Upload Pipeline Timeline")
365+
def upload_pipeline_timeline(self, obj):
366+
if obj.pk is None:
367+
return ""
368+
369+
breadcrumbs = UploadBreadcrumb.objects.filter(
370+
commit_sha=obj.commitid,
371+
repo_id=obj.repository.repoid,
372+
).order_by("created_at")[:200]
373+
374+
if not breadcrumbs:
375+
return format_html("<em>No breadcrumbs recorded for this commit.</em>")
376+
377+
rows = []
378+
for bc in breadcrumbs:
379+
data = bc.breadcrumb_data or {}
380+
milestone = data.get("milestone", "")
381+
error = data.get("error", "")
382+
error_text = data.get("error_text", "")
383+
task_name = data.get("task_name", "")
384+
parent_task_id = data.get("parent_task_id", "")
385+
endpoint = data.get("endpoint", "")
386+
387+
if error:
388+
color = "#d32f2f"
389+
elif milestone in ("lac", "lr", "uc", "ns"):
390+
color = "#388e3c"
391+
elif milestone in ("la", "pu", "nt"):
392+
color = "#1565c0"
393+
else:
394+
color = "#555"
395+
396+
upload_ids_str = (
397+
", ".join(str(uid) for uid in bc.upload_ids) if bc.upload_ids else ""
398+
)
399+
400+
detail_parts = []
401+
if milestone:
402+
detail_parts.append(f"<strong>{milestone}</strong>")
403+
if endpoint:
404+
detail_parts.append(f"endpoint={endpoint}")
405+
if error:
406+
detail_parts.append(f'<span style="color:#d32f2f">err={error}</span>')
407+
if error_text:
408+
detail_parts.append(
409+
f'<span style="color:#d32f2f">{error_text[:120]}</span>'
410+
)
411+
detail = " &middot; ".join(detail_parts) if detail_parts else "—"
412+
413+
rows.append(
414+
f"<tr>"
415+
f'<td style="white-space:nowrap;color:{color};padding:4px 8px">'
416+
f"{bc.created_at:%Y-%m-%d %H:%M:%S}</td>"
417+
f'<td style="padding:4px 8px;font-family:monospace;font-size:12px">'
418+
f"{task_name}</td>"
419+
f'<td style="padding:4px 8px;font-family:monospace;font-size:12px">'
420+
f"{parent_task_id}</td>"
421+
f'<td style="padding:4px 8px">{detail}</td>'
422+
f'<td style="padding:4px 8px;font-family:monospace;font-size:12px">'
423+
f"{upload_ids_str}</td>"
424+
f"</tr>"
425+
)
426+
427+
table = (
428+
'<table style="border-collapse:collapse;width:100%">'
429+
"<thead><tr>"
430+
'<th style="text-align:left;padding:4px 8px;border-bottom:2px solid #ccc">Time</th>'
431+
'<th style="text-align:left;padding:4px 8px;border-bottom:2px solid #ccc">Task</th>'
432+
'<th style="text-align:left;padding:4px 8px;border-bottom:2px solid #ccc">Parent Task ID</th>'
433+
'<th style="text-align:left;padding:4px 8px;border-bottom:2px solid #ccc">Detail</th>'
434+
'<th style="text-align:left;padding:4px 8px;border-bottom:2px solid #ccc">Upload IDs</th>'
435+
"</tr></thead><tbody>" + "".join(rows) + "</tbody></table>"
436+
)
437+
438+
return format_html(
439+
'<div style="max-height:600px;overflow:auto">{}</div>', format_html(table)
440+
)
441+
362442
def _reprocess_uploads(
363443
self, request, commit: Commit, config: ReprocessConfig
364444
) -> HttpResponseRedirect:

0 commit comments

Comments
 (0)