From a5c8f2db3cfea85154966b0150bbc13bc6ffae2f Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Sat, 3 Jan 2026 14:15:04 +0100 Subject: [PATCH] Auto-commit local changes before build (2026-01-03 14:15:04) --- .last-branch | 2 +- .../backend/app/main/routes_reporting_api.py | 111 ------------------ docs/changelog.md | 8 ++ 3 files changed, 9 insertions(+), 112 deletions(-) diff --git a/.last-branch b/.last-branch index 86bbf18..f9f63f4 100644 --- a/.last-branch +++ b/.last-branch @@ -1 +1 @@ -v20260103-07-reports-advanced-reporting-foundation +v20260103-08-reports-stats-endpoint-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 de90ad1..58037da 100644 --- a/containers/backupchecks/src/backend/app/main/routes_reporting_api.py +++ b/containers/backupchecks/src/backend/app/main/routes_reporting_api.py @@ -229,117 +229,6 @@ def api_reports_columns(): } -@main_bp.route("/api/reports//stats", methods=["GET"]) -@login_required -def api_reports_stats(report_id: int): - """Return lightweight KPI + chart datasets for a report. - - Data is derived from report_object_snapshots, which is generated by - POST /api/reports//generate. - """ - err = _require_reporting_role() - if err is not None: - return err - - ReportDefinition.query.get_or_404(report_id) - - # KPI counts - # We treat missed runs as their own bucket, regardless of status string. - row = db.session.execute( - text( - """ - SELECT - COUNT(*)::INTEGER AS total_runs, - SUM(CASE WHEN missed = TRUE THEN 1 ELSE 0 END)::INTEGER AS missed_runs, - SUM(CASE WHEN missed = FALSE AND COALESCE(status,'') ILIKE 'success%' THEN 1 ELSE 0 END)::INTEGER AS success_runs, - SUM(CASE WHEN missed = FALSE AND COALESCE(status,'') ILIKE 'warning%' THEN 1 ELSE 0 END)::INTEGER AS warning_runs, - SUM(CASE WHEN missed = FALSE AND COALESCE(status,'') ILIKE 'fail%' THEN 1 ELSE 0 END)::INTEGER AS failed_runs, - SUM(CASE WHEN override_applied = TRUE THEN 1 ELSE 0 END)::INTEGER AS override_runs - FROM report_object_snapshots - WHERE report_id = :rid - """ - ), - {"rid": report_id}, - ).fetchone() - - total_runs = int(row.total_runs or 0) if row else 0 - success_runs = int(row.success_runs or 0) if row else 0 - warning_runs = int(row.warning_runs or 0) if row else 0 - failed_runs = int(row.failed_runs or 0) if row else 0 - missed_runs = int(row.missed_runs or 0) if row else 0 - override_runs = int(row.override_runs or 0) if row else 0 - - success_rate = 0.0 - if total_runs > 0: - # Consider overrides as success for success_rate. - success_rate = ((success_runs + override_runs) / float(total_runs)) * 100.0 - - # Trend datasets (per day) - trend_rows = db.session.execute( - text( - """ - SELECT - DATE_TRUNC('day', run_at) AS day, - COUNT(*)::INTEGER AS total, - SUM(CASE WHEN missed = TRUE THEN 1 ELSE 0 END)::INTEGER AS missed, - SUM(CASE WHEN missed = FALSE AND COALESCE(status,'') ILIKE 'success%' THEN 1 ELSE 0 END)::INTEGER AS success, - SUM(CASE WHEN missed = FALSE AND COALESCE(status,'') ILIKE 'warning%' THEN 1 ELSE 0 END)::INTEGER AS warning, - SUM(CASE WHEN missed = FALSE AND COALESCE(status,'') ILIKE 'fail%' THEN 1 ELSE 0 END)::INTEGER AS failed - FROM report_object_snapshots - WHERE report_id = :rid - AND run_at IS NOT NULL - GROUP BY DATE_TRUNC('day', run_at) - ORDER BY DATE_TRUNC('day', run_at) ASC - """ - ), - {"rid": report_id}, - ).fetchall() - - trend = [] - for tr in trend_rows or []: - day = tr.day - day_iso = day.date().isoformat() if hasattr(day, "date") else str(day) - total = int(tr.total or 0) - succ = int(tr.success or 0) - fail = int(tr.failed or 0) - succ_rate = 0.0 - if total > 0: - succ_rate = (succ / float(total)) * 100.0 - trend.append( - { - "day": day_iso, - "total": total, - "success": succ, - "warning": int(tr.warning or 0), - "failed": fail, - "missed": int(tr.missed or 0), - "success_rate": succ_rate, - } - ) - - return { - "kpis": { - "total_runs": total_runs, - "success_runs": success_runs, - "warning_runs": warning_runs, - "failed_runs": failed_runs, - "missed_runs": missed_runs, - "override_runs": override_runs, - "success_rate": float(success_rate), - }, - "charts": { - "status_distribution": { - "success": success_runs + override_runs, - "warning": warning_runs, - "failed": failed_runs, - "missed": missed_runs, - }, - "trend": trend, - }, - } - - - @main_bp.route("/api/reports/", methods=["DELETE"]) @login_required def api_reports_delete(report_id: int): diff --git a/docs/changelog.md b/docs/changelog.md index 5d7692e..e201701 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -62,6 +62,14 @@ ### Fixed - Ensured report deletion flow remains compatible with extended report definition handling. +--- + +## v20260103-08-reports-stats-endpoint-fix + +- Fixed application startup crash caused by duplicate registration of the `api_reports_stats` endpoint. +- Removed the redundant `/api/reports//stats` route definition to ensure the endpoint is registered only once. +- Restored proper Gunicorn boot sequence by resolving Flask endpoint name collision. + ================================================================================================================================================ ## v0.1.15