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
4 changed files with 142 additions and 7 deletions
Showing only changes of commit d51741532a - Show all commits

View File

@ -1 +1 @@
v20260104-12-reports-object-name-job-name-columns-fix v20260104-13-reports-jobs-deduplicate-job-name-column

View File

@ -196,16 +196,43 @@
if (items[j].key === key) return items[j].label || key; if (items[j].key === key) return items[j].label || key;
} }
} }
// Backwards compatibility: object_name was used for job name in older configs.
if (key === 'job_name') {
for (var i2 = 0; i2 < reportColumnsMeta.groups.length; i2++) {
var items2 = reportColumnsMeta.groups[i2].items || [];
for (var j2 = 0; j2 < items2.length; j2++) {
if (items2[j2].key === 'object_name') return items2[j2].label || 'Job name';
}
}
return 'Job name';
}
return key; return key;
} }
function uniqPreserveOrder(arr) {
var out = [];
var seen = {};
(arr || []).forEach(function (k) {
var key = String(k);
if (seen[key]) return;
seen[key] = true;
out.push(k);
});
return out;
}
function normalizeCols(arr) {
if (!Array.isArray(arr)) return [];
return uniqPreserveOrder(arr.map(function (k) { return (k === 'object_name') ? 'job_name' : k; }));
}
function defaultColsFor(view) { function defaultColsFor(view) {
if (reportColumnsMeta && reportColumnsMeta.defaults && reportColumnsMeta.defaults[view]) { if (reportColumnsMeta && reportColumnsMeta.defaults && reportColumnsMeta.defaults[view]) {
return reportColumnsMeta.defaults[view].slice(); return normalizeCols(reportColumnsMeta.defaults[view].slice());
} }
// hard fallback // hard fallback
if (view === 'snapshot') return ['object_name','customer_name','job_id','job_name','status','run_at']; if (view === 'snapshot') return ['job_name','customer_name','job_id','status','run_at'];
return ['object_name','total_runs','success_count','warning_count','failed_count','missed_count','success_rate']; return ['job_name','total_runs','success_count','warning_count','failed_count','missed_count','success_rate'];
} }
function selectedColsFor(view) { function selectedColsFor(view) {
@ -218,9 +245,9 @@
} }
if (hasView && Array.isArray(cols)) { if (hasView && Array.isArray(cols)) {
// If an empty list is saved, keep it empty. // If an empty list is saved, keep it empty.
return cols; return normalizeCols(cols);
} }
if (cols && cols.length) return cols; if (cols && cols.length) return normalizeCols(cols);
return defaultColsFor(view); return defaultColsFor(view);
} }
@ -337,7 +364,8 @@
return ( return (
'<tr>' + '<tr>' +
cols.map(function (k) { cols.map(function (k) {
return td(r[k], (k === 'run_at' || k === 'reviewed_at' || k === 'job_id' || k === 'run_id' || k === 'customer_name')); var val = (k === 'job_name') ? ((r.job_name !== null && r.job_name !== undefined && String(r.job_name).length) ? r.job_name : r.object_name) : r[k];
return td(val, (k === 'run_at' || k === 'reviewed_at' || k === 'job_id' || k === 'run_id' || k === 'customer_name'));
}).join('') + }).join('') +
'</tr>' '</tr>'
); );

View File

@ -288,6 +288,102 @@
}; };
} }
function uniqPreserveOrder(arr) {
var out = [];
var seen = {};
(arr || []).forEach(function (k) {
var key = String(k);
if (seen[key]) return;
seen[key] = true;
out.push(k);
});
return out;
}
function normalizeJobNameColumns() {
// Merge legacy object_name into job_name (single logical column: "Job name").
if (repColsMeta && repColsMeta.groups) {
repColsMeta.groups.forEach(function (g) {
var items = g.items || [];
var jobItem = null;
var objItem = null;
items.forEach(function (it) {
if (!it) return;
if (it.key === 'job_name') jobItem = it;
if (it.key === 'object_name') objItem = it;
});
if (objItem && !jobItem) {
// convert object_name into job_name
objItem.key = 'job_name';
objItem.label = 'Job name';
jobItem = objItem;
objItem = null;
}
if (jobItem) {
jobItem.label = 'Job name';
// Merge views if object_name existed in the same group
if (objItem && Array.isArray(objItem.views)) {
var merged = (Array.isArray(jobItem.views) ? jobItem.views.slice() : []);
objItem.views.forEach(function (v) {
if (merged.indexOf(v) < 0) merged.push(v);
});
jobItem.views = merged;
}
}
// Remove any remaining object_name items
g.items = (g.items || []).filter(function (it) { return it && it.key !== 'object_name'; });
// Remove duplicate job_name items within the same group
var seenJob = false;
g.items = (g.items || []).filter(function (it) {
if (!it) return false;
if (it.key !== 'job_name') return true;
if (!seenJob) {
seenJob = true;
it.label = 'Job name';
return true;
}
return false;
});
});
// Ensure job_name only appears once across all groups.
var seenGlobalJob = false;
repColsMeta.groups.forEach(function (g2) {
g2.items = (g2.items || []).filter(function (it2) {
if (!it2) return false;
if (it2.key !== 'job_name') return true;
if (!seenGlobalJob) {
seenGlobalJob = true;
it2.label = 'Job name';
return true;
}
return false;
});
});
}
// Defaults: replace object_name -> job_name and de-duplicate
if (repColsMeta && repColsMeta.defaults) {
['summary', 'snapshot', 'jobs'].forEach(function (v) {
var d = repColsMeta.defaults[v];
if (!Array.isArray(d)) return;
repColsMeta.defaults[v] = uniqPreserveOrder(d.map(function (k) { return (k === 'object_name') ? 'job_name' : k; }));
});
}
// Selected: replace object_name -> job_name and de-duplicate
['summary', 'snapshot', 'jobs'].forEach(function (v) {
if (repColsSelected[v] === null || typeof repColsSelected[v] === 'undefined') return;
if (!Array.isArray(repColsSelected[v])) return;
repColsSelected[v] = uniqPreserveOrder(repColsSelected[v].map(function (k) { return (k === 'object_name') ? 'job_name' : k; }));
});
}
// --- Job filters (backup software / backup type) --- // --- Job filters (backup software / backup type) ---
var jobFiltersMeta = window.__jobFiltersMeta || null; var jobFiltersMeta = window.__jobFiltersMeta || null;
@ -610,6 +706,7 @@
if (repColsMeta) { if (repColsMeta) {
showColsLoading(false); showColsLoading(false);
clearColsError(); clearColsError();
normalizeJobNameColumns();
ensureDefaultsFromMeta(); ensureDefaultsFromMeta();
qs('rep_cols_tab_summary').addEventListener('click', function () { setColsView('summary'); }); qs('rep_cols_tab_summary').addEventListener('click', function () { setColsView('summary'); });
qs('rep_cols_tab_snapshot').addEventListener('click', function () { setColsView('snapshot'); }); qs('rep_cols_tab_snapshot').addEventListener('click', function () { setColsView('snapshot'); });
@ -632,6 +729,7 @@
} }
repColsMeta = res.json || null; repColsMeta = res.json || null;
normalizeJobNameColumns();
ensureDefaultsFromMeta(); ensureDefaultsFromMeta();
// bind tabs once metadata is ready // bind tabs once metadata is ready

View File

@ -289,6 +289,15 @@
- Fixed column selection behavior so an explicitly saved empty selection stays empty (defaults are only applied when the view has no configured selection). - Fixed column selection behavior so an explicitly saved empty selection stays empty (defaults are only applied when the view has no configured selection).
- Updated the raw data table and HTML export to show a clear “No columns selected” message when all columns are disabled, instead of still rendering default data. - Updated the raw data table and HTML export to show a clear “No columns selected” message when all columns are disabled, instead of still rendering default data.
---
## v20260104-13-reports-jobs-deduplicate-job-name-column
- Unified `object_name` and `job_name` into a single logical column labeled "Job name".
- Removed duplicate "Job name" entries from available and selected column lists in Summary, Snapshot, and Jobs views.
- Added migration logic to automatically clean up existing report configurations containing duplicate job name columns.
- Ensured report rendering strictly follows the selected columns; when no columns are selected, no table data is shown.
================================================================================================================================================ ================================================================================================================================================
## v0.1.15 ## v0.1.15