feat(meta-data extraction): add client field support

This commit is contained in:
Othmane Ataallah 2026-05-05 16:07:52 +01:00
parent 658307f4be
commit 5e748ad682
2 changed files with 42 additions and 7 deletions

View File

@ -29,6 +29,7 @@ const ISSUES_HEADER = [
"IssueURL", "IssueURL",
"EstimatedDays", "EstimatedDays",
"Importance", "Importance",
"Client",
"TotalTimeMinutes", "TotalTimeMinutes",
"TimeSummary", "TimeSummary",
"_IssueKey", "_IssueKey",
@ -160,6 +161,7 @@ async function ensureSheet(sheets, spreadsheetId, sheetName, header) {
IssueURL: 260, IssueURL: 260,
EstimatedDays: 90, EstimatedDays: 90,
Importance: 90, Importance: 90,
Client: 200,
TotalTimeMinutes: 110, TotalTimeMinutes: 110,
TimeSummary: 260, TimeSummary: 260,
_IssueKey: 220, _IssueKey: 220,
@ -203,10 +205,11 @@ async function ensureSheet(sheets, spreadsheetId, sheetName, header) {
}, },
}); });
} else if (numberCols.has(key)) { } else if (numberCols.has(key)) {
const numPattern = key === 'EstimatedDays' ? '#,##0.##' : '#,##0';
requests.push({ requests.push({
repeatCell: { repeatCell: {
range: colRange, range: colRange,
cell: { userEnteredFormat: { numberFormat: { type: "NUMBER", pattern: "#,##0" }, horizontalAlignment: "CENTER" } }, cell: { userEnteredFormat: { numberFormat: { type: "NUMBER", pattern: numPattern }, horizontalAlignment: "CENTER" } },
fields: "userEnteredFormat(numberFormat,horizontalAlignment)", fields: "userEnteredFormat(numberFormat,horizontalAlignment)",
}, },
}); });
@ -238,6 +241,30 @@ async function ensureSheet(sheets, spreadsheetId, sheetName, header) {
} catch (e) { } catch (e) {
logger.warn("sheetsAdapter styling failed", { sheetName, err: e && e.message ? e.message : 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); ensuredSheets.add(cacheKey);
@ -380,6 +407,7 @@ function mapIssueToRow(issue, action, meta = {}) {
(issue.html_url || issue.url) || "", (issue.html_url || issue.url) || "",
meta.estimatedDays != null ? meta.estimatedDays : "", meta.estimatedDays != null ? meta.estimatedDays : "",
meta.importance != null ? meta.importance : "", meta.importance != null ? meta.importance : "",
meta.client || "",
meta.totalTimeMinutes != null ? meta.totalTimeMinutes : "", meta.totalTimeMinutes != null ? meta.totalTimeMinutes : "",
timeSummary || "", timeSummary || "",
meta.issueKey || `${(issue.repository && issue.repository.full_name) || ''}#${issue.number}`, meta.issueKey || `${(issue.repository && issue.repository.full_name) || ''}#${issue.number}`,

View File

@ -36,8 +36,8 @@ export async function handleWebhook(req, res, next) {
// extract repo full name // 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}`))) || ""; 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 // 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 }; 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 : ""; 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) { for (const line of lines) {
const idx = line.indexOf(":"); const idx = line.indexOf(":");
if (idx === -1) continue; 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(); const val = line.slice(idx + 1).trim();
obj[key] = val; obj[key] = val;
} }
@ -67,6 +67,8 @@ export async function handleWebhook(req, res, next) {
if (md) out.d = md[1]; if (md) out.d = md[1];
const mi = name.match(/i\s*[:=]\s*(\d+)/i); const mi = name.match(/i\s*[:=]\s*(\d+)/i);
if (mi) out.i = mi[1]; 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; return Object.keys(out).length ? out : null;
} }
@ -78,31 +80,36 @@ export async function handleWebhook(req, res, next) {
if (md) out.d = md[1]; if (md) out.d = md[1];
const mi = title.match(/i\s*=\s*(\d+)/i); const mi = title.match(/i\s*=\s*(\d+)/i);
if (mi) out.i = mi[1]; 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; return Object.keys(out).length ? out : null;
} }
// precedence: frontmatter > labels > title // precedence: frontmatter > labels > title
const fm = parseFrontmatter(body); const fm = parseFrontmatter(body);
if (fm && (fm.d || fm.i)) { if (fm && (fm.d || fm.i || fm.c)) {
if (fm.d) { if (fm.d) {
const d = String(fm.d).replace(/,/g, '.'); const d = String(fm.d).replace(/,/g, '.');
meta.estimatedDays = Number(d); meta.estimatedDays = Number(d);
meta.estimateRaw = fm.d; meta.estimateRaw = fm.d;
} }
if (fm.i) meta.importance = parseInt(fm.i, 10); if (fm.i) meta.importance = parseInt(fm.i, 10);
if (fm.c) meta.client = fm.c;
meta.estimateSource = 'frontmatter'; meta.estimateSource = 'frontmatter';
} else { } else {
const lb = parseFromLabels(event.issue && event.issue.labels); 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.d) meta.estimatedDays = Number(String(lb.d).replace(/,/g, '.'));
if (lb.i) meta.importance = parseInt(lb.i, 10); if (lb.i) meta.importance = parseInt(lb.i, 10);
if (lb.c) meta.client = lb.c;
meta.estimateSource = 'label'; meta.estimateSource = 'label';
meta.estimateRaw = lb.d || lb.i || null; meta.estimateRaw = lb.d || lb.i || null;
} else { } else {
const tt = parseFromTitle(event.issue && event.issue.title); 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.d) meta.estimatedDays = Number(String(tt.d).replace(/,/g, '.'));
if (tt.i) meta.importance = parseInt(tt.i, 10); if (tt.i) meta.importance = parseInt(tt.i, 10);
if (tt.c) meta.client = tt.c;
meta.estimateSource = 'title'; meta.estimateSource = 'title';
meta.estimateRaw = tt.d || tt.i || null; meta.estimateRaw = tt.d || tt.i || null;
} }