From 5e748ad6828259cf72a18fdc90efd763a7e8e12d Mon Sep 17 00:00:00 2001 From: Othmane Ataallah <”othmane.ataallah.triztech@gmail.com”> Date: Tue, 5 May 2026 16:07:52 +0100 Subject: [PATCH] feat(meta-data extraction): add client field support --- src/adapters/sheetsAdapter.js | 30 +++++++++++++++++++++++++++- src/controllers/webhookController.js | 19 ++++++++++++------ 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/adapters/sheetsAdapter.js b/src/adapters/sheetsAdapter.js index f45456a..321bf83 100644 --- a/src/adapters/sheetsAdapter.js +++ b/src/adapters/sheetsAdapter.js @@ -29,6 +29,7 @@ const ISSUES_HEADER = [ "IssueURL", "EstimatedDays", "Importance", + "Client", "TotalTimeMinutes", "TimeSummary", "_IssueKey", @@ -160,6 +161,7 @@ async function ensureSheet(sheets, spreadsheetId, sheetName, header) { IssueURL: 260, EstimatedDays: 90, Importance: 90, + Client: 200, TotalTimeMinutes: 110, TimeSummary: 260, _IssueKey: 220, @@ -203,10 +205,11 @@ async function ensureSheet(sheets, spreadsheetId, sheetName, header) { }, }); } else if (numberCols.has(key)) { + const numPattern = key === 'EstimatedDays' ? '#,##0.##' : '#,##0'; requests.push({ repeatCell: { range: colRange, - cell: { userEnteredFormat: { numberFormat: { type: "NUMBER", pattern: "#,##0" }, horizontalAlignment: "CENTER" } }, + cell: { userEnteredFormat: { numberFormat: { type: "NUMBER", pattern: numPattern }, horizontalAlignment: "CENTER" } }, fields: "userEnteredFormat(numberFormat,horizontalAlignment)", }, }); @@ -238,6 +241,30 @@ async function ensureSheet(sheets, spreadsheetId, sheetName, header) { } catch (e) { logger.warn("sheetsAdapter styling failed", { sheetName, err: e && e.message ? e.message : e }); } + // Ensure EstimatedDays column displays decimals for existing sheets + try { + const estimatedIdx = header.indexOf('EstimatedDays'); + if (estimatedIdx >= 0) { + // re-fetch to get the sheetId + const meta2 = await requestWithRetry(() => sheets.spreadsheets.get({ spreadsheetId, fields: "sheets.properties" })); + const sheetObj = (meta2 && meta2.data && meta2.data.sheets || []).find((s) => s.properties && s.properties.title === sheetName); + const sheetId = sheetObj && sheetObj.properties && sheetObj.properties.sheetId; + if (sheetId != null) { + const colRange = { sheetId, startRowIndex: 1, endRowIndex: 10000, startColumnIndex: estimatedIdx, endColumnIndex: estimatedIdx + 1 }; + const req = { + repeatCell: { + range: colRange, + cell: { userEnteredFormat: { numberFormat: { type: "NUMBER", pattern: "#,##0.##" }, horizontalAlignment: "CENTER" } }, + fields: "userEnteredFormat(numberFormat,horizontalAlignment)", + }, + }; + await requestWithRetry(() => sheets.spreadsheets.batchUpdate({ spreadsheetId, requestBody: { requests: [req] } })); + logger.info('sheetsAdapter ensured EstimatedDays decimal formatting', { sheetName }); + } + } + } catch (e) { + logger.warn('sheetsAdapter failed ensuring EstimatedDays format', { sheetName, err: e && e.message ? e.message : e }); + } } ensuredSheets.add(cacheKey); @@ -380,6 +407,7 @@ function mapIssueToRow(issue, action, meta = {}) { (issue.html_url || issue.url) || "", meta.estimatedDays != null ? meta.estimatedDays : "", meta.importance != null ? meta.importance : "", + meta.client || "", meta.totalTimeMinutes != null ? meta.totalTimeMinutes : "", timeSummary || "", meta.issueKey || `${(issue.repository && issue.repository.full_name) || ''}#${issue.number}`, diff --git a/src/controllers/webhookController.js b/src/controllers/webhookController.js index 8fadcb5..c0b692b 100644 --- a/src/controllers/webhookController.js +++ b/src/controllers/webhookController.js @@ -36,8 +36,8 @@ export async function handleWebhook(req, res, next) { // extract repo full name const repoFullName = (event.repository && (event.repository.full_name || (event.repository.owner && event.repository.owner.login && event.repository.name && `${event.repository.owner.login}/${event.repository.name}`))) || ""; - // parse meta (estimated days d, importance i) from frontmatter, labels, or title - const meta = { estimatedDays: null, importance: null, estimateSource: null, estimateRaw: null, eventHash, actor: (event.sender && event.sender.login) || null }; + // parse meta (estimated days d, importance i, client c) from frontmatter, labels, or title + const meta = { estimatedDays: null, importance: null, estimateSource: null, estimateRaw: null, eventHash, actor: (event.sender && event.sender.login) || null, client: null }; const body = event.issue && event.issue.body ? event.issue.body : ""; @@ -51,7 +51,7 @@ export async function handleWebhook(req, res, next) { for (const line of lines) { const idx = line.indexOf(":"); if (idx === -1) continue; - const key = line.slice(0, idx).trim(); + const key = line.slice(0, idx).trim().toLowerCase(); const val = line.slice(idx + 1).trim(); obj[key] = val; } @@ -67,6 +67,8 @@ export async function handleWebhook(req, res, next) { if (md) out.d = md[1]; const mi = name.match(/i\s*[:=]\s*(\d+)/i); if (mi) out.i = mi[1]; + const mc = name.match(/c\s*[:=]\s*(.+)/i); + if (mc) out.c = mc[1].trim(); } return Object.keys(out).length ? out : null; } @@ -78,31 +80,36 @@ export async function handleWebhook(req, res, next) { if (md) out.d = md[1]; const mi = title.match(/i\s*=\s*(\d+)/i); if (mi) out.i = mi[1]; + const mc = title.match(/c\s*[:=]\s*(.+)/i); + if (mc) out.c = mc[1].trim(); return Object.keys(out).length ? out : null; } // precedence: frontmatter > labels > title const fm = parseFrontmatter(body); - if (fm && (fm.d || fm.i)) { + if (fm && (fm.d || fm.i || fm.c)) { if (fm.d) { const d = String(fm.d).replace(/,/g, '.'); meta.estimatedDays = Number(d); meta.estimateRaw = fm.d; } if (fm.i) meta.importance = parseInt(fm.i, 10); + if (fm.c) meta.client = fm.c; meta.estimateSource = 'frontmatter'; } else { const lb = parseFromLabels(event.issue && event.issue.labels); - if (lb && (lb.d || lb.i)) { + if (lb && (lb.d || lb.i || lb.c)) { if (lb.d) meta.estimatedDays = Number(String(lb.d).replace(/,/g, '.')); if (lb.i) meta.importance = parseInt(lb.i, 10); + if (lb.c) meta.client = lb.c; meta.estimateSource = 'label'; meta.estimateRaw = lb.d || lb.i || null; } else { const tt = parseFromTitle(event.issue && event.issue.title); - if (tt && (tt.d || tt.i)) { + if (tt && (tt.d || tt.i || tt.c)) { if (tt.d) meta.estimatedDays = Number(String(tt.d).replace(/,/g, '.')); if (tt.i) meta.importance = parseInt(tt.i, 10); + if (tt.c) meta.client = tt.c; meta.estimateSource = 'title'; meta.estimateRaw = tt.d || tt.i || null; }