CR + Tooltips + Promedios

This commit is contained in:
kvilches1410
2026-04-04 00:49:55 -03:00
parent c8806a6701
commit 8dfa3bf137
3 changed files with 172 additions and 21 deletions
+76 -10
View File
@@ -24,6 +24,11 @@ const config = {
qaTotalTimeQa: 'customfield_10825',
qaLastDurQa: 'customfield_10826',
timesReturned: 'customfield_11217',
spFinalReal: 'customfield_11216',
crEntry: 'customfield_10818',
crTotalTime: 'customfield_10819',
enCursoEntry: 'customfield_10815',
enCursoTotalTime: 'customfield_10816',
},
jql: process.env.JIRA_JQL || 'project = ${PROJECT} AND ${FIELD_SP_ESTIMATE} is not EMPTY ORDER BY updated DESC',
api: {
@@ -99,7 +104,9 @@ function buildFieldsList() {
config.fields.spEstimate, config.fields.spFinal, config.fields.developer,
config.fields.qaAssignee, config.fields.qaEntryRtqa, config.fields.qaTotalTimeRtqa,
config.fields.qaLastDurRtqa, config.fields.qaEntryQa, config.fields.qaTotalTimeQa,
config.fields.qaLastDurQa, config.fields.timesReturned,
config.fields.qaLastDurQa, config.fields.timesReturned, config.fields.spFinalReal,
config.fields.crEntry, config.fields.crTotalTime, config.fields.enCursoEntry,
config.fields.enCursoTotalTime,
].join(',');
}
@@ -116,15 +123,17 @@ function processIssues(issues) {
const now = new Date();
const qaData = {
tickets: 0,
ticketsReturned: 0,
totalTimeQa: 0,
totalTimeRtqa: 0,
monthlyTotalQa: {},
monthlyCountQa: {},
monthlyTotalRtqa: {},
monthlyCountRtqa: {},
issues: [],
tickets: 0, ticketsReturned: 0, totalTimeQa: 0, totalTimeRtqa: 0,
monthlyTotalQa: {}, monthlyCountQa: {}, monthlyTotalRtqa: {}, monthlyCountRtqa: {}, issues: [],
};
const deliveryData = {
totalLeadTimeDays: 0, totalCycleTimeDays: 0, doneTicketsLead: 0, doneTicketsCycle: 0,
doneSpOriginal: 0, doneSpFinal: 0
};
const crData = {
tickets: 0, totalTimeCr: 0, issues: [], monthlyTotalCr: {}, monthlyCountCr: {}, stalledIssues: []
};
for (const issue of issues) {
@@ -138,6 +147,10 @@ function processIssues(issues) {
} else {
devs = ['Sin asignar'];
}
const INACTIVE_DEVS = ['Felipe Esposito', 'heiner', 'Ciro Bruno'];
devs = devs.filter(d => !INACTIVE_DEVS.includes(d));
if (devs.length === 0) continue;
const sp = (f[config.fields.spFinal] != null ? f[config.fields.spFinal] : f[config.fields.spEstimate]) ?? 0;
const statusKey = f.status?.statusCategory?.key ?? 'new';
@@ -167,6 +180,26 @@ function processIssues(issues) {
monthlyDone[month][dev] = (monthlyDone[month][dev] ?? 0) + sp;
}
}
// Delivery Metrics
const createdDate = f.created ? new Date(f.created) : null;
const resolvedDate = f.resolutiondate ? new Date(f.resolutiondate) : null;
const enCursoDate = f[config.fields.enCursoEntry] ? new Date(f[config.fields.enCursoEntry]) : null;
const spOriginal = f[config.fields.spEstimate] || 0;
const spReal = f[config.fields.spFinalReal] != null ? f[config.fields.spFinalReal] : spOriginal;
if (resolvedDate) {
if (createdDate) {
deliveryData.totalLeadTimeDays += (resolvedDate - createdDate) / (1000 * 60 * 60 * 24);
deliveryData.doneTicketsLead++;
}
if (enCursoDate) {
deliveryData.totalCycleTimeDays += (resolvedDate - enCursoDate) / (1000 * 60 * 60 * 24);
deliveryData.doneTicketsCycle++;
}
deliveryData.doneSpOriginal += spOriginal;
deliveryData.doneSpFinal += spReal;
}
}
issuesList.push({ id: issue.id, key: issue.key, summary: f.summary, status: statusKey, sp, devs, creMonth });
@@ -195,6 +228,25 @@ function processIssues(issues) {
qaData.monthlyTotalRtqa[qaMonth] = (qaData.monthlyTotalRtqa[qaMonth] || 0) + timeRtqaMin;
qaData.monthlyCountRtqa[qaMonth] = (qaData.monthlyCountRtqa[qaMonth] || 0) + 1;
}
const timeCrMin = f[config.fields.crTotalTime] ?? 0;
if (timeCrMin > 0) {
crData.tickets++;
crData.totalTimeCr += timeCrMin;
if (qaMonth) {
crData.monthlyTotalCr[qaMonth] = (crData.monthlyTotalCr[qaMonth] || 0) + timeCrMin;
crData.monthlyCountCr[qaMonth] = (crData.monthlyCountCr[qaMonth] || 0) + 1;
}
crData.issues.push({ key: issue.key, summary: f.summary, timeCr: minsToHrs(timeCrMin), status: statusKey });
}
const crEntryDate = f[config.fields.crEntry];
if (crEntryDate && !isDone && !f[config.fields.qaEntryRtqa]) {
const daysInCr = (now - new Date(crEntryDate)) / (1000 * 60 * 60 * 24);
if (daysInCr > 2) {
crData.stalledIssues.push({ key: issue.key, summary: f.summary, days: Math.round(daysInCr * 10)/10 });
}
}
if (isInProgress && f.updated) {
const days = (now - new Date(f.updated)) / (1000 * 60 * 60 * 24);
@@ -221,9 +273,23 @@ function processIssues(issues) {
}
}
const monthlyAvgCr = {};
for (const m of Object.keys(crData.monthlyTotalCr)) monthlyAvgCr[m] = minsToHrs(crData.monthlyTotalCr[m] / crData.monthlyCountCr[m]);
return {
lastUpdated: new Date().toISOString(),
assigneeData, monthlyDone, issuesList, warnings,
deliveryData: {
avgLeadTime: deliveryData.doneTicketsLead > 0 ? Math.round(deliveryData.totalLeadTimeDays / deliveryData.doneTicketsLead) : 0,
avgCycleTime: deliveryData.doneTicketsCycle > 0 ? Math.round(deliveryData.totalCycleTimeDays / deliveryData.doneTicketsCycle) : 0,
spOriginal: deliveryData.doneSpOriginal,
spFinal: deliveryData.doneSpFinal,
spDeviationPct: deliveryData.doneSpOriginal > 0 ? ((deliveryData.doneSpFinal - deliveryData.doneSpOriginal) / deliveryData.doneSpOriginal * 100).toFixed(1) : 0
},
crData: {
...crData, monthlyAvgCr,
avgTimeCr: crData.tickets > 0 ? minsToHrs(crData.totalTimeCr / crData.tickets) : 0
},
qaData: {
...qaData, monthlyAvgQa, monthlyAvgRtqa,
avgTimeQa: qaData.tickets > 0 ? minsToHrs(qaData.totalTimeQa / qaData.tickets) : 0,