From 7ed94cd8fc08e8ad93b6f9f993650e2fc678e087 Mon Sep 17 00:00:00 2001 From: serversdown Date: Fri, 15 May 2026 23:29:44 +0000 Subject: [PATCH] feat(tools): add 'Gantt by Unit' tab to deployment history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third view on /tools/deployment-history. Where 'Gantt by Project' has one row per project showing that project's deployments, 'Gantt by Unit' inverts it — one row per seismograph, bars colored by the project the unit was deployed to. The natural use case: "where has BE11529 been across all my jobs?" Spotting unit rotation patterns, idle gaps, and concurrent assignments gets immediate visually. Service - deployment_history.get_deployment_history_data() now also returns a `units` array. Each unit dict carries: {id, bars[], first_active, assignment_count, any_active} Each bar has the project_name + project_color baked in so the renderer can paint by job without a second lookup. - Units sorted: currently-active first, then by first_active ascending. UI - Third tab "Gantt by Unit" added next to Calendar / Gantt by Project. - Tab switcher refactored to a small registry (_DH_TABS) so adding more views in the future is a one-line addition. - URL hash sync now supports #gantt and #byunit; nav buttons preserve the active tab across month-paging. - SVG layout: 160px label gutter (smaller than the project Gantt's 220px since unit IDs are short), 32px row height, green dot for units with at least one active deployment. Unit ID is clickable → /unit/{id}; each bar is clickable → /projects/{p}. Co-Authored-By: Claude Opus 4.7 --- backend/services/deployment_history.py | 43 +++++ templates/admin/deployment_history.html | 213 +++++++++++++++++++++--- 2 files changed, 230 insertions(+), 26 deletions(-) diff --git a/backend/services/deployment_history.py b/backend/services/deployment_history.py index d9002fc..0186eb8 100644 --- a/backend/services/deployment_history.py +++ b/backend/services/deployment_history.py @@ -229,6 +229,48 @@ def get_deployment_history_data( projects_data.sort(key=lambda p: (p["first_active"] or "9999", p["name"])) + # ── Per-unit view data (Gantt-by-Unit tab) ──────────────────────── + # Same source assignments, re-grouped by unit_id. Each bar carries + # the project's color + name so the renderer can paint by job + # without doing a second lookup. + unit_bars: dict[str, list[dict]] = {} + project_lookup = {p["id"]: p for p in projects_data} + for a in assignments: + start = max(a.assigned_at.date() if a.assigned_at else first_date, first_date) + end_dt = a.assigned_until or now + end = min(end_dt.date(), last_date) + if end < start: + continue + p_info = project_lookup.get(a.project_id, {}) + unit_bars.setdefault(a.unit_id, []).append({ + "project_id": a.project_id, + "project_name": p_info.get("name", "(deleted project)"), + "project_color": p_info.get("color", _color_for_project(a.project_id)), + "location_id": a.location_id, + "location_name": loc_name_map.get(a.location_id, "(unknown location)"), + "start": start.isoformat(), + "end": end.isoformat(), + "is_active": a.assigned_until is None, + "source": a.source, + }) + + # Sort units by first-active date so the most-recently-deployed + # units sit at the top. Reverse if we want oldest-first. + units_data = [] + for uid, bars in unit_bars.items(): + bars.sort(key=lambda b: b["start"]) + first_start = bars[0]["start"] + # "active now" flag = any bar is still active + any_active = any(b["is_active"] for b in bars) + units_data.append({ + "id": uid, + "bars": bars, + "first_active": first_start, + "assignment_count": len(bars), + "any_active": any_active, + }) + units_data.sort(key=lambda u: (not u["any_active"], u["first_active"], u["id"])) + # Now build the months array. months_data = [] cur_year, cur_month = start_year, start_month @@ -267,6 +309,7 @@ def get_deployment_history_data( return { "months": months_data, "projects": projects_data, + "units": units_data, "total_assignments": len(assignments), "total_active_units": len(distinct_units), "window": { diff --git a/templates/admin/deployment_history.html b/templates/admin/deployment_history.html index e77d0b0..e80b6a8 100644 --- a/templates/admin/deployment_history.html +++ b/templates/admin/deployment_history.html @@ -97,7 +97,7 @@ - +
+
@@ -272,6 +276,50 @@ {% endif %} {# /#dh-view-gantt #} + + {# /#dh-view-byunit #} +