|
18 | 18 | from shared.django_apps.reports.models import ReportType |
19 | 19 | from shared.django_apps.ta_timeseries.models import Testrun |
20 | 20 | from shared.django_apps.timeseries.models import Measurement, MeasurementName |
| 21 | +from shared.django_apps.upload_breadcrumbs.models import UploadBreadcrumb |
21 | 22 | from shared.django_apps.utils.paginator import EstimatedCountPaginator |
22 | 23 | from shared.helpers.redis import get_redis_connection |
23 | 24 | from shared.reports.enums import UploadState |
@@ -270,6 +271,7 @@ class CommitAdmin(AdminMixin, admin.ModelAdmin): |
270 | 271 | "deleted", |
271 | 272 | "notified", |
272 | 273 | "reprocess_actions", |
| 274 | + "upload_pipeline_timeline", |
273 | 275 | ) |
274 | 276 | fields = readonly_fields |
275 | 277 | paginator = EstimatedCountPaginator |
@@ -359,6 +361,84 @@ def reprocess_actions(self, obj): |
359 | 361 |
|
360 | 362 | return format_html("<div>{}</div>", format_html("".join(buttons))) |
361 | 363 |
|
| 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 = " · ".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 | + |
362 | 442 | def _reprocess_uploads( |
363 | 443 | self, request, commit: Commit, config: ReprocessConfig |
364 | 444 | ) -> HttpResponseRedirect: |
|
0 commit comments