fix: normalize cost field (daily_cost ?? total_cost ?? cost) in getVisibleData, buildDonut, buildSvc, exportCSV

This commit is contained in:
chrischristiansen-glitch 2026-06-08 23:49:36 +02:00
parent e61bf798c7
commit bdbe52a6da

View File

@ -487,7 +487,7 @@ async function checkAuth() {
document.getElementById('user-avatar').src = userProfile.picture;
document.getElementById('user-profile').style.display = 'flex';
switchProvider(currentProvider); // Set initial state
switchProvider(currentProvider);
await fetchBudget();
setInterval(fetchAll, REFRESH_MS);
@ -575,7 +575,7 @@ async function fetchAll(){
const [gcpS, gcpL, gcpA, gcpH, awsS, awsL] = await Promise.all([gcpSR.json(), gcpLR.json(), gcpAR.json(), gcpHR.json(), awsSR.json(), awsLR.json()]);
currentData.gcp = { summary: gcpS.summary || [], forecast: gcpL, anomalies: gcpA.anomalies || [], history: gcpH.history || [] };
currentData.aws = { summary: awsS.summary || [], forecast: awsL, anomalies: [], history: [] }; // AWS history/anomalies not implemented yet
currentData.aws = { summary: awsS.summary || [], forecast: awsL, anomalies: [], history: [] };
document.getElementById('err-banner').classList.remove('show');
dot.style.background='var(--ok)';
@ -591,6 +591,7 @@ async function fetchAll(){
renderAll();
}
function getVisibleData() {
if (currentProvider === 'gcp') {
return currentData.gcp;
@ -603,15 +604,20 @@ function getVisibleData() {
const combined = {
summary: [...(currentData.gcp.summary || []), ...(currentData.aws.summary || [])]
.reduce((acc, service) => {
const cost = service.daily_cost ?? service.total_cost ?? service.cost ?? 0;
const existing = acc.find(s => s.service === service.service);
if (existing) {
existing.total_cost += service.total_cost || service.cost || 0;
existing.total_cost = (existing.total_cost || 0) + cost;
} else {
acc.push({ ...service });
acc.push({ ...service, total_cost: cost });
}
return acc;
}, [])
.sort((a, b) => (b.total_cost || b.cost) - (a.total_cost || a.cost)),
.sort((a, b) => {
const costA = a.daily_cost ?? a.total_cost ?? a.cost ?? 0;
const costB = b.daily_cost ?? b.total_cost ?? b.cost ?? 0;
return costB - costA;
}),
forecast: {
month_to_date_cost: (currentData.gcp.forecast?.month_to_date_cost || 0) + (currentData.aws.forecast?.month_to_date_cost || 0),
daily_average_last_7_days: (currentData.gcp.forecast?.daily_average_last_7_days || 0) + (currentData.aws.forecast?.daily_average_last_7_days || 0),
@ -619,7 +625,7 @@ function getVisibleData() {
remaining_days_in_month: currentData.gcp.forecast?.remaining_days_in_month
},
anomalies: [...(currentData.gcp.anomalies || []), ...(currentData.aws.anomalies || [])],
history: currentData.gcp.history // Only show GCP history for now
history: currentData.gcp.history
};
return combined;
}
@ -642,19 +648,15 @@ function calculateDailyCosts(history) {
let dailyCost;
const entryDate = new Date(entry.date);
// If it's the first day of the month, the daily cost is just the MTD
if (entryDate.getDate() === 1) {
dailyCost = currentMtd;
} else {
// Find the previous day's MTD
const prevDayEntry = history[i-1];
const prevMtd = prevDayEntry ? prevDayEntry.mtd : 0;
dailyCost = currentMtd - prevMtd;
}
// Ensure we don't have negative costs due to billing adjustments
dailyCosts.push(Math.max(0, dailyCost));
labels.push(entryDate.toLocaleDateString('no-NO', {day: 'numeric', month: 'short'}));
}
@ -704,7 +706,7 @@ function buildBar(labels, costs){
}
function buildDonut(summary){
const hasData=summary.some(s=>(s.total_cost||0)>0);
const hasData=summary.some(s=>(s.daily_cost ?? s.total_cost ?? s.cost ?? 0)>0);
if(!hasData){
document.getElementById('donut-empty').style.display='flex';
document.getElementById('donut-chart').style.display='none';
@ -715,22 +717,26 @@ function buildDonut(summary){
document.getElementById('donut-chart').style.display='block';
const ctx=document.getElementById('donut-chart').getContext('2d');
if(donutChart)donutChart.destroy();
const top=summary.filter(s=>(s.total_cost||0)>0).slice(0,7);
donutChart=new Chart(ctx,{type:'doughnut',data:{labels:top.map(s=>s.service),datasets:[{data:top.map(s=>s.total_cost||0),backgroundColor:['#7c5cff','#00d4a8','#ffb454','#ff5c7c','#a78bfa','#5eead4','#c4b5fd'],borderColor:'#13131c',borderWidth:2,hoverBorderWidth:3}]},
const top=summary.filter(s=>(s.daily_cost ?? s.total_cost ?? s.cost ?? 0)>0).slice(0,7);
donutChart=new Chart(ctx,{type:'doughnut',data:{labels:top.map(s=>s.service),datasets:[{data:top.map(s=>s.daily_cost ?? s.total_cost ?? s.cost ?? 0),backgroundColor:['#7c5cff','#00d4a8','#ffb454','#ff5c7c','#a78bfa','#5eead4','#c4b5fd'],borderColor:'#13131c',borderWidth:2,hoverBorderWidth:3}]},
options:{responsive:true,maintainAspectRatio:false,cutout:'70%',
plugins:{legend:{display:false},tooltip:{backgroundColor:'rgba(22,27,34,.97)',borderColor:'rgba(139,148,158,.15)',borderWidth:1,titleColor:'#8b949e',bodyColor:'#e6edf3',callbacks:{label:c=>' '+fmtNOK(c.raw)}}}}});
}
function buildSvc(summary){
const el=document.getElementById('svc-list');
const top=summary.slice(0,8);
const top=[...summary].sort((a,b)=>{
const costA=a.daily_cost ?? a.total_cost ?? a.cost ?? 0;
const costB=b.daily_cost ?? b.total_cost ?? b.cost ?? 0;
return costB-costA;
}).slice(0,8);
if(!top.length){
el.innerHTML='<div class="empty-state"><span class="material-symbols-outlined">list</span>Ingen data</div>';
return;
}
const max=Math.max(...top.map(s=>Math.abs(s.total_cost||0)),0.000001);
const max=Math.max(...top.map(s=>Math.abs(s.daily_cost ?? s.total_cost ?? s.cost ?? 0)),0.000001);
el.innerHTML=top.map((s,i)=>{
const c=s.total_cost||0;
const c=s.daily_cost ?? s.total_cost ?? s.cost ?? 0;
const pct=(Math.abs(c)/max*100).toFixed(1);
return`<div class="svc-row">
<div class="svc-dot" style="background:${['#7c5cff','#00d4a8','#ffb454','#ff5c7c','#a78bfa','#5eead4','#c4b5fd'][i%7]}"></div>
@ -802,12 +808,11 @@ function exportCSV() {
const headers = "Tjeneste,Kostnad (NOK)";
const rows = data.summary.map(service => {
const cost = (service.total_cost || service.cost || 0).toFixed(2);
const cost = (service.daily_cost ?? service.total_cost ?? service.cost ?? 0).toFixed(2);
return `"${service.service}",${cost}`;
});
const csvContent = "data:text/csv;charset=utf-8," + [headers, ...rows].join("
");
const csvContent = "data:text/csv;charset=utf-8," + [headers, ...rows].join("\n");
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");