v20260104-14-reports-stats-total-runs-success-rate-fix #28

Merged
ivooskamp merged 22 commits from v20260104-14-reports-stats-total-runs-success-rate-fix into main 2026-01-13 10:59:39 +01:00
5 changed files with 31 additions and 6 deletions
Showing only changes of commit fa676a9e4e - Show all commits

View File

@ -1 +1 @@
v20260103-14-reports-percentage-2-decimals v20260103-15-reports-customer-column

View File

@ -193,6 +193,7 @@ def build_report_columns_meta():
"defaults": { "defaults": {
"summary": [ "summary": [
"object_name", "object_name",
"customer_name",
"total_runs", "total_runs",
"success_count", "success_count",
"warning_count", "warning_count",
@ -238,7 +239,7 @@ def build_report_columns_meta():
{"key": "job_id", "label": "Job ID", "views": ["snapshot"]}, {"key": "job_id", "label": "Job ID", "views": ["snapshot"]},
{"key": "backup_software", "label": "Job type", "views": ["snapshot"]}, {"key": "backup_software", "label": "Job type", "views": ["snapshot"]},
{"key": "backup_type", "label": "Repository / Target", "views": ["snapshot"]}, {"key": "backup_type", "label": "Repository / Target", "views": ["snapshot"]},
{"key": "customer_name", "label": "Customer", "views": ["snapshot"]}, {"key": "customer_name", "label": "Customer", "views": ["snapshot", "summary"]},
], ],
}, },
{ {
@ -446,11 +447,14 @@ def api_reports_generate(report_id: int):
text( text(
''' '''
INSERT INTO report_object_summaries INSERT INTO report_object_summaries
(report_id, object_name, total_runs, success_count, success_override_count, (report_id, object_name, customer_id, customer_name,
total_runs, success_count, success_override_count,
warning_count, failed_count, missed_count, success_rate, created_at) warning_count, failed_count, missed_count, success_rate, created_at)
SELECT SELECT
:rid AS report_id, :rid AS report_id,
COALESCE(s.job_name, '(unknown job)') AS object_name, COALESCE(s.job_name, '(unknown job)') AS object_name,
s.customer_id AS customer_id,
s.customer_name AS customer_name,
COUNT(*)::INTEGER AS total_runs, COUNT(*)::INTEGER AS total_runs,
SUM(CASE WHEN (COALESCE(s.status,'') ILIKE 'success%' AND s.override_applied = FALSE) THEN 1 ELSE 0 END)::INTEGER AS success_count, SUM(CASE WHEN (COALESCE(s.status,'') ILIKE 'success%' AND s.override_applied = FALSE) THEN 1 ELSE 0 END)::INTEGER AS success_count,
SUM(CASE WHEN (s.override_applied = TRUE) THEN 1 ELSE 0 END)::INTEGER AS success_override_count, SUM(CASE WHEN (s.override_applied = TRUE) THEN 1 ELSE 0 END)::INTEGER AS success_override_count,
@ -469,7 +473,7 @@ def api_reports_generate(report_id: int):
NOW() AS created_at NOW() AS created_at
FROM report_object_snapshots s FROM report_object_snapshots s
WHERE s.report_id = :rid WHERE s.report_id = :rid
GROUP BY s.job_id, s.job_name GROUP BY s.customer_id, s.customer_name, s.job_id, s.job_name
''' '''
), ),
{"rid": report_id}, {"rid": report_id},
@ -513,7 +517,7 @@ def api_reports_data(report_id: int):
q = db.session.query(ReportObjectSummary).filter(ReportObjectSummary.report_id == report_id) q = db.session.query(ReportObjectSummary).filter(ReportObjectSummary.report_id == report_id)
total = q.count() total = q.count()
rows = ( rows = (
q.order_by(ReportObjectSummary.object_name.asc()) q.order_by(db.func.coalesce(ReportObjectSummary.customer_name, '').asc(), ReportObjectSummary.object_name.asc())
.offset(offset) .offset(offset)
.limit(limit) .limit(limit)
.all() .all()
@ -526,6 +530,8 @@ def api_reports_data(report_id: int):
"items": [ "items": [
{ {
"object_name": r.object_name or "", "object_name": r.object_name or "",
"customer_id": int(r.customer_id) if getattr(r, "customer_id", None) is not None else "",
"customer_name": r.customer_name or "",
"total_runs": int(r.total_runs or 0), "total_runs": int(r.total_runs or 0),
"success_count": int(r.success_count or 0), "success_count": int(r.success_count or 0),
"success_override_count": int(r.success_override_count or 0), "success_override_count": int(r.success_override_count or 0),
@ -732,6 +738,8 @@ def api_reports_export_csv(report_id: int):
if view == "summary": if view == "summary":
writer.writerow([ writer.writerow([
"object_name", "object_name",
"customer_id",
"customer_name",
"total_runs", "total_runs",
"success_count", "success_count",
"success_override_count", "success_override_count",
@ -743,12 +751,14 @@ def api_reports_export_csv(report_id: int):
rows = ( rows = (
db.session.query(ReportObjectSummary) db.session.query(ReportObjectSummary)
.filter(ReportObjectSummary.report_id == report_id) .filter(ReportObjectSummary.report_id == report_id)
.order_by(ReportObjectSummary.object_name.asc()) .order_by(db.func.coalesce(ReportObjectSummary.customer_name, '').asc(), ReportObjectSummary.object_name.asc())
.all() .all()
) )
for r in rows: for r in rows:
writer.writerow([ writer.writerow([
r.object_name or "", r.object_name or "",
r.customer_id or "",
r.customer_name or "",
int(r.total_runs or 0), int(r.total_runs or 0),
int(r.success_count or 0), int(r.success_count or 0),
int(r.success_override_count or 0), int(r.success_override_count or 0),

View File

@ -1371,6 +1371,8 @@ def migrate_reporting_tables() -> None:
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
report_id INTEGER NOT NULL REFERENCES report_definitions(id) ON DELETE CASCADE, report_id INTEGER NOT NULL REFERENCES report_definitions(id) ON DELETE CASCADE,
object_name TEXT NOT NULL, object_name TEXT NOT NULL,
customer_id INTEGER NULL,
customer_name TEXT NULL,
total_runs INTEGER NOT NULL DEFAULT 0, total_runs INTEGER NOT NULL DEFAULT 0,
success_count INTEGER NOT NULL DEFAULT 0, success_count INTEGER NOT NULL DEFAULT 0,
success_override_count INTEGER NOT NULL DEFAULT 0, success_override_count INTEGER NOT NULL DEFAULT 0,
@ -1395,5 +1397,7 @@ def migrate_reporting_tables() -> None:
conn.execute(text("ALTER TABLE report_definitions ADD COLUMN IF NOT EXISTS customer_scope VARCHAR(16) NOT NULL DEFAULT 'all'")) conn.execute(text("ALTER TABLE report_definitions ADD COLUMN IF NOT EXISTS customer_scope VARCHAR(16) NOT NULL DEFAULT 'all'"))
conn.execute(text("ALTER TABLE report_definitions ADD COLUMN IF NOT EXISTS customer_ids TEXT NULL")) conn.execute(text("ALTER TABLE report_definitions ADD COLUMN IF NOT EXISTS customer_ids TEXT NULL"))
conn.execute(text("ALTER TABLE report_object_snapshots ADD COLUMN IF NOT EXISTS customer_id INTEGER NULL")) conn.execute(text("ALTER TABLE report_object_snapshots ADD COLUMN IF NOT EXISTS customer_id INTEGER NULL"))
conn.execute(text("ALTER TABLE report_object_summaries ADD COLUMN IF NOT EXISTS customer_id INTEGER NULL"))
conn.execute(text("ALTER TABLE report_object_summaries ADD COLUMN IF NOT EXISTS customer_name TEXT NULL"))
print("[migrations] reporting tables created/verified.") print("[migrations] reporting tables created/verified.")

View File

@ -612,6 +612,9 @@ class ReportObjectSummary(db.Model):
report_id = db.Column(db.Integer, db.ForeignKey("report_definitions.id"), nullable=False) report_id = db.Column(db.Integer, db.ForeignKey("report_definitions.id"), nullable=False)
object_name = db.Column(db.Text, nullable=False) object_name = db.Column(db.Text, nullable=False)
customer_id = db.Column(db.Integer, nullable=True)
customer_name = db.Column(db.Text, nullable=True)
total_runs = db.Column(db.Integer, nullable=False, default=0) total_runs = db.Column(db.Integer, nullable=False, default=0)
success_count = db.Column(db.Integer, nullable=False, default=0) success_count = db.Column(db.Integer, nullable=False, default=0)
success_override_count = db.Column(db.Integer, nullable=False, default=0) success_override_count = db.Column(db.Integer, nullable=False, default=0)

View File

@ -128,6 +128,14 @@
- CSV export generation - CSV export generation
- Improved readability and consistency of reported percentage values. - Improved readability and consistency of reported percentage values.
---
## v20260103-15-reports-customer-column
- Added **Customer** as a selectable column in report configuration.
- Ensured customer name is included and visible when multiple customers are selected.
- Updated summary and raw data views to correctly display customer information across multi-customer reports.
================================================================================================================================================ ================================================================================================================================================
## v0.1.15 ## v0.1.15