diff --git a/.last-branch b/.last-branch index 7f51ecc..563c3b1 100644 --- a/.last-branch +++ b/.last-branch @@ -1 +1 @@ -v20260104-03-reports-html-view-selection-fix +v20260104-04-reports-html-jobs-table-fix diff --git a/containers/backupchecks/src/backend/app/main/routes_reporting_api.py b/containers/backupchecks/src/backend/app/main/routes_reporting_api.py index 8eca7bf..208fcc1 100644 --- a/containers/backupchecks/src/backend/app/main/routes_reporting_api.py +++ b/containers/backupchecks/src/backend/app/main/routes_reporting_api.py @@ -1096,18 +1096,117 @@ def _export_html_response(report: ReportDefinition, report_id: int, view: str): # - when html_content is not set, pick a sensible default based on scope + view scope = (getattr(report, "customer_scope", None) or "all").strip().lower() if not html_content: - if scope == "single" and (view or "summary").strip().lower() == "summary": - html_content = "customers" - else: - html_content = "jobs" if scope == "single" else "customers" + # For a single-customer report, the most useful summary is a job list. + # For all-customer reports, the summary defaults to performance by customer. + html_content = "jobs" if scope == "single" else "customers" include_customers = html_content in ("customers", "both") include_jobs = html_content in ("jobs", "both") - # Snapshot preview table can be requested either explicitly via view=snapshot - # or via the report config (include_jobs). + # Snapshot preview table can be requested explicitly via view=snapshot. want_snapshot_table = (view or "summary").strip().lower() == "snapshot" + jobs_table_html = "" + if include_jobs and not want_snapshot_table: + # Job table (latest snapshot per job/object) for a single-customer report. + jobs_rows = [] + with db.engine.connect() as conn: + rows = conn.execute( + text( + """ + SELECT DISTINCT ON (COALESCE(object_name,''), COALESCE(job_name,''), COALESCE(backup_software,''), COALESCE(backup_type,'')) + COALESCE(object_name,'') AS object_name, + COALESCE(customer_name,'') AS customer_name, + COALESCE(job_name,'') AS job_name, + COALESCE(backup_software,'') AS backup_software, + COALESCE(backup_type,'') AS backup_type, + run_at, + COALESCE(status,'') AS status, + missed, + override_applied, + COALESCE(ticket_number,'') AS ticket_number, + COALESCE(remark,'') AS remark + FROM report_object_snapshots + WHERE report_id = :rid + ORDER BY + COALESCE(object_name,''), + COALESCE(job_name,''), + COALESCE(backup_software,''), + COALESCE(backup_type,''), + run_at DESC NULLS LAST + LIMIT 1000 + """ + ), + {"rid": report_id}, + ).fetchall() + + for r in rows or []: + jobs_rows.append( + { + "object_name": r.object_name or "", + "customer_name": r.customer_name or "", + "job_name": r.job_name or "", + "backup_software": r.backup_software or "", + "backup_type": r.backup_type or "", + "run_at": r.run_at.isoformat() if r.run_at else "", + "status": r.status or "", + "missed": bool(r.missed), + "override_applied": bool(r.override_applied), + "ticket_number": r.ticket_number or "", + "remark": (r.remark or "").replace("\r", " ").replace("\n", " ").strip(), + } + ) + + job_row_html = [] + for j in jobs_rows: + job_row_html.append( + "
| Object | +Job | +Software | +Type | +Last run | +Status | +Missed | +Override | +Ticket | +Remark | +
|---|