v20260104-14-reports-stats-total-runs-success-rate-fix #28
@ -1 +1 @@
|
||||
v20260104-12-reports-object-name-job-name-columns-fix
|
||||
v20260104-13-reports-jobs-deduplicate-job-name-column
|
||||
|
||||
@ -196,16 +196,43 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (reportColumnsMeta && reportColumnsMeta.defaults && reportColumnsMeta.defaults[view]) {
|
||||
return reportColumnsMeta.defaults[view].slice();
|
||||
return normalizeCols(reportColumnsMeta.defaults[view].slice());
|
||||
}
|
||||
// hard fallback
|
||||
if (view === 'snapshot') return ['object_name','customer_name','job_id','job_name','status','run_at'];
|
||||
return ['object_name','total_runs','success_count','warning_count','failed_count','missed_count','success_rate'];
|
||||
if (view === 'snapshot') return ['job_name','customer_name','job_id','status','run_at'];
|
||||
return ['job_name','total_runs','success_count','warning_count','failed_count','missed_count','success_rate'];
|
||||
}
|
||||
|
||||
function selectedColsFor(view) {
|
||||
@ -218,9 +245,9 @@
|
||||
}
|
||||
if (hasView && Array.isArray(cols)) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
@ -337,7 +364,8 @@
|
||||
return (
|
||||
'<tr>' +
|
||||
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('') +
|
||||
'</tr>'
|
||||
);
|
||||
|
||||
@ -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) ---
|
||||
var jobFiltersMeta = window.__jobFiltersMeta || null;
|
||||
@ -610,6 +706,7 @@
|
||||
if (repColsMeta) {
|
||||
showColsLoading(false);
|
||||
clearColsError();
|
||||
normalizeJobNameColumns();
|
||||
ensureDefaultsFromMeta();
|
||||
qs('rep_cols_tab_summary').addEventListener('click', function () { setColsView('summary'); });
|
||||
qs('rep_cols_tab_snapshot').addEventListener('click', function () { setColsView('snapshot'); });
|
||||
@ -632,6 +729,7 @@
|
||||
}
|
||||
|
||||
repColsMeta = res.json || null;
|
||||
normalizeJobNameColumns();
|
||||
ensureDefaultsFromMeta();
|
||||
|
||||
// bind tabs once metadata is ready
|
||||
|
||||
@ -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).
|
||||
- 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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user