Skip to content

Commit 16ee9dc

Browse files
committed
adress review comments
Signed-off-by: ChrsMark <chrismarkou92@gmail.com>
1 parent 87abbcd commit 16ee9dc

File tree

1 file changed

+7
-163
lines changed

1 file changed

+7
-163
lines changed

.github/workflows/scripts/generate-codeowners-activity.js

Lines changed: 7 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
* Generates a monthly report measuring code owner responsiveness. We require (1) at least 75%
66
* of each component's PRs to be reviewed by at least one code owner, and (2) each code owner
77
* to have reviewed/replied to at least 75% / n of the PRs where they were requested (n =
8-
* number of code owners for that component). The report can optionally list code owners with
9-
* below 5% activity over the past 6 months. Uses the same component labels as the weekly
8+
* number of code owners for that component). Uses the same component labels as the weekly
109
* report (e.g. receiver/kubeletstats).
1110
*/
1211

@@ -18,11 +17,6 @@ const REPO_NAME = 'opentelemetry-collector-contrib';
1817
/** Target: at least this % of each component's PRs must be reviewed (by at least one code owner). */
1918
const COMPONENT_TARGET_PCT = 75;
2019
/** Per-code-owner target is COMPONENT_TARGET_PCT / number of code owners for that component (e.g. 75%/3 ≈ 25%). */
21-
/** Code owners below this % activity over the past 6 months are listed in a separate section (when enabled). */
22-
const LOW_ACTIVITY_THRESHOLD_PCT = 5;
23-
const SIX_MONTHS_DAYS = 180;
24-
/** Set to true to include the "Code owners below 5% activity (past 6 months)" section in the report. */
25-
const REPORT_LOW_ACTIVITY_6MO = false;
2620

2721
/** PRs created by these logins are excluded from the report. */
2822
const EXCLUDED_PR_AUTHORS = new Set(['otelbot', 'renovate']);
@@ -39,14 +33,11 @@ const FOCUS_COMPONENT_LABELS = new Set([
3933
]);
4034
// Resourcedetection has sub-labels (e.g. processor/resourcedetection/internal/azure); include those too.
4135
function isAllowedLabel(label) {
42-
if (FOCUS_COMPONENT_LABELS.size === 0) return true;
4336
if (FOCUS_COMPONENT_LABELS.has(label)) return true;
4437
if (label.startsWith('processor/resourcedetection/')) return true;
4538
return false;
4639
}
4740

48-
// Optional: set LIMIT (e.g. 10) to only process that many PRs and issues (for quick local runs)
49-
const PROCESS_LIMIT = process.env.LIMIT ? parseInt(process.env.LIMIT, 10) : null;
5041
const PROGRESS_INTERVAL = 5;
5142

5243
function debug(msg) {
@@ -72,9 +63,7 @@ function genLookbackDates() {
7263
);
7364
const thirtyDaysAgo = new Date(midnightYesterday);
7465
thirtyDaysAgo.setDate(midnightYesterday.getDate() - 30);
75-
const sixMonthsAgo = new Date(midnightYesterday);
76-
sixMonthsAgo.setDate(midnightYesterday.getDate() - SIX_MONTHS_DAYS);
77-
return { thirtyDaysAgo, sixMonthsAgo, midnightYesterday };
66+
return { thirtyDaysAgo, midnightYesterday };
7867
}
7968

8069
function filterOnDateRange({ created_at, thirtyDaysAgo, midnightYesterday }) {
@@ -164,39 +153,6 @@ async function searchIssuesAndPrs(octokit, query, thirtyDaysAgo, midnightYesterd
164153
return items;
165154
}
166155

167-
async function getPrsInWindow(octokit, since, until) {
168-
return searchIssuesAndPrs(octokit, 'is:pr', since, until);
169-
}
170-
171-
/**
172-
* Fetch PRs in date-range chunks to avoid GitHub search 1000-result limit.
173-
* Returns combined, deduplicated PRs for the full [since, until] window.
174-
*/
175-
async function getPrsInWindowChunked(octokit, since, until, chunkDays = 30) {
176-
const results = [];
177-
const seen = new Set();
178-
let start = new Date(since);
179-
const end = new Date(until);
180-
while (start < end) {
181-
const chunkEnd = new Date(start);
182-
chunkEnd.setDate(chunkEnd.getDate() + chunkDays);
183-
if (chunkEnd > end) chunkEnd.setTime(end.getTime());
184-
progress(`Fetching PRs ${start.toISOString().slice(0, 10)}${chunkEnd.toISOString().slice(0, 10)}...`);
185-
const chunk = await searchIssuesAndPrs(octokit, 'is:pr', start, chunkEnd);
186-
for (const pr of chunk) {
187-
if (seen.has(pr.id)) continue;
188-
seen.add(pr.id);
189-
results.push(pr);
190-
}
191-
start = chunkEnd;
192-
}
193-
return results;
194-
}
195-
196-
async function getIssuesInWindow(octokit, thirtyDaysAgo, midnightYesterday) {
197-
return searchIssuesAndPrs(octokit, 'is:issue', thirtyDaysAgo, midnightYesterday);
198-
}
199-
200156
function getComponentLabelsOnItem(item, componentLabels) {
201157
const names = (item.labels || []).map((l) => l.name);
202158
return names.filter((name) => componentLabels.has(name) && isAllowedLabel(name));
@@ -238,23 +194,6 @@ async function getReviewAndRequestedLogins(octokit, owner, repo, prNumber) {
238194
return { requested, respondents, draft };
239195
}
240196

241-
async function getIssueCommentLogins(octokit, owner, repo, issueNumber) {
242-
const logins = new Set();
243-
try {
244-
const { data: comments } = await octokit.issues.listComments({
245-
owner,
246-
repo,
247-
issue_number: issueNumber,
248-
});
249-
for (const c of comments) {
250-
if (c.user && c.user.login) logins.add(c.user.login);
251-
}
252-
} catch (e) {
253-
debug({ msg: 'getIssueCommentLogins error', owner, repo, issueNumber, error: e.message });
254-
}
255-
return logins;
256-
}
257-
258197
/**
259198
* Get per-label code owners: for each label in labelsOnItem, add (label, login) pairs
260199
* to the given map. Returns Map<login, Set<label>> for quick lookup.
@@ -275,16 +214,13 @@ function getCodeOwnersByLabel(labelsOnItem, labelToOwners) {
275214
/**
276215
* Stats aggregated by (code owner, component). Shape: byCodeOwner[login][componentLabel] = { total, responded }.
277216
* Also returns componentPrStats: per component, how many PRs had at least one code-owner review (for the 75% target).
278-
* @param {number|null} [processLimitOverride] - If null, process all PRs; if undefined, use global PROCESS_LIMIT.
279217
*/
280-
async function computePrStats(octokit, prs, labelToOwners, componentLabels, thirtyDaysAgo, midnightYesterday, processLimitOverride) {
218+
async function computePrStats(octokit, prs, labelToOwners, componentLabels, thirtyDaysAgo, midnightYesterday) {
281219
const byCodeOwnerAndComponent = {};
282220
const componentPrStats = {}; // { [component]: { prsTotal, prsWithResponse } }
283221
let processed = 0;
284-
const limit = processLimitOverride !== undefined ? processLimitOverride : PROCESS_LIMIT;
285222

286223
for (const pr of prs) {
287-
if (limit !== null && processed >= limit) break;
288224
if (!filterOnDateRange({ created_at: pr.created_at, thirtyDaysAgo, midnightYesterday })) continue;
289225
const authorLogin = pr.user?.login || null;
290226
if (authorLogin && EXCLUDED_PR_AUTHORS.has(authorLogin)) continue;
@@ -334,42 +270,6 @@ async function computePrStats(octokit, prs, labelToOwners, componentLabels, thir
334270
return { byCodeOwnerAndComponent, componentPrStats };
335271
}
336272

337-
async function computeIssueStats(octokit, issues, labelToOwners, componentLabels, thirtyDaysAgo, midnightYesterday) {
338-
const byCodeOwnerAndComponent = {};
339-
let processed = 0;
340-
341-
for (const issue of issues) {
342-
if (PROCESS_LIMIT !== null && processed >= PROCESS_LIMIT) break;
343-
if (!filterOnDateRange({ created_at: issue.created_at, thirtyDaysAgo, midnightYesterday })) continue;
344-
const labelsOnIssue = getComponentLabelsOnItem(issue, componentLabels);
345-
if (labelsOnIssue.length === 0) continue;
346-
347-
const ownerToLabels = getCodeOwnersByLabel(labelsOnIssue, labelToOwners);
348-
if (ownerToLabels.size === 0) continue;
349-
350-
if (processed === 0 || processed % PROGRESS_INTERVAL === 0) progress(`Issues: fetching #${issue.number} (${processed + 1})...`);
351-
const respondents = await getIssueCommentLogins(octokit, REPO_OWNER, REPO_NAME, issue.number);
352-
processed++;
353-
354-
const authorLogin = issue.user?.login || null;
355-
for (const [login, labels] of ownerToLabels) {
356-
if (authorLogin && login === authorLogin) continue; // do not count this issue for the author (they opened it)
357-
for (const label of labels) {
358-
if (!byCodeOwnerAndComponent[login]) byCodeOwnerAndComponent[login] = {};
359-
if (!byCodeOwnerAndComponent[login][label]) {
360-
byCodeOwnerAndComponent[login][label] = { total: 0, responded: 0 };
361-
}
362-
byCodeOwnerAndComponent[login][label].total++;
363-
if (respondents.has(login)) {
364-
byCodeOwnerAndComponent[login][label].responded++;
365-
}
366-
}
367-
}
368-
}
369-
370-
return byCodeOwnerAndComponent;
371-
}
372-
373273
/**
374274
* Flatten byCodeOwnerAndComponent into rows: one row per (code owner, component).
375275
* Per-code-owner target is 75% / (number of code owners for that component).
@@ -447,59 +347,19 @@ ${content}
447347
</details>`;
448348
}
449349

450-
/**
451-
* Code owners with &lt; LOW_ACTIVITY_THRESHOLD_PCT response rate over the past 6 months, per component.
452-
*/
453-
function formatLowActivityCodeOwners(sixMonthByCodeOwner) {
454-
const rows = [];
455-
for (const [login, byComponent] of Object.entries(sixMonthByCodeOwner)) {
456-
for (const [component, v] of Object.entries(byComponent)) {
457-
if (v.total === 0) continue;
458-
const pct = (100 * v.responded) / v.total;
459-
if (pct >= LOW_ACTIVITY_THRESHOLD_PCT) continue;
460-
rows.push([login, component, String(v.total), String(v.responded), `${pct.toFixed(1)}%`]);
461-
}
462-
}
463-
rows.sort((a, b) => {
464-
const cmpLogin = a[0].localeCompare(b[0]);
465-
if (cmpLogin !== 0) return cmpLogin;
466-
return a[1].localeCompare(b[1]);
467-
});
468-
469-
if (rows.length === 0) {
470-
return `No code owners below ${LOW_ACTIVITY_THRESHOLD_PCT}% activity in the past 6 months.\n`;
471-
}
472-
const header = `| Code owner | Component | Total requested | Responded | % |`;
473-
const sep = '| --- | --- | --- | --- | --- |';
474-
const body = rows.map((r) => `| ${r.join(' | ')} |`).join('\n');
475-
return `${header}\n${sep}\n${body}\n`;
476-
}
477-
478-
function generateReport(prStats, componentPrStats, issueStats, lookbackData, lowActivityMarkdown) {
479-
const focusNote = FOCUS_COMPONENT_LABELS.size > 0
480-
? `\n\n**Components in scope:** ${[...FOCUS_COMPONENT_LABELS].sort().join(', ')} (and \`processor/resourcedetection/*\` sub-labels).\n`
481-
: '';
350+
function generateReport(prStats, componentPrStats, lookbackData) {
482351
const out = [
483352
`## Code owner activity report`,
484353
``,
485354
`Period: ${lookbackData.thirtyDaysAgo.toISOString().slice(0, 10)}${lookbackData.midnightYesterday.toISOString().slice(0, 10)}`,
486355
``,
487-
`We target **at least ${COMPONENT_TARGET_PCT}% of each component's PRs** to be reviewed by a code owner, and each code owner to respond to at least **${COMPONENT_TARGET_PCT}% / n** of their requested PRs (n = number of code owners for that component) ([at least 3 code owners for components aiming for stable](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta-to-stable)).${focusNote}`,
356+
`We target **at least ${COMPONENT_TARGET_PCT}% of each component's PRs** to be reviewed by a code owner, and each code owner to respond to at least **${COMPONENT_TARGET_PCT}% / n** of their requested PRs (n = number of code owners for that component) ([at least 3 code owners for components aiming for stable](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta-to-stable)).`,
488357
``,
489358
`### Component PR review rate (${COMPONENT_TARGET_PCT}% target)`,
490359
``,
491360
formatComponentSummaryTable(prStats, componentPrStats),
492361
``,
493362
collapsibleSection('PRs (per code owner)', formatTable(prStats, 'PRs')),
494-
...(lowActivityMarkdown
495-
? [
496-
``,
497-
`### Code owners below ${LOW_ACTIVITY_THRESHOLD_PCT}% activity (past 6 months)`,
498-
``,
499-
lowActivityMarkdown,
500-
]
501-
: []),
502-
// collapsibleSection('Issues', formatTable(issueStats, 'issues')),
503363
];
504364
return out.join('\n');
505365
}
@@ -534,28 +394,12 @@ async function main({ github, context }) {
534394
? new Set([...allLabels].filter(isAllowedLabel))
535395
: allLabels;
536396

537-
const prs30 = await getPrsInWindow(octokit, lookbackData.thirtyDaysAgo, lookbackData.midnightYesterday);
538-
// const issues = await getIssuesInWindow(octokit, lookbackData.thirtyDaysAgo, lookbackData.midnightYesterday);
539-
const issues = [];
540-
541-
if (PROCESS_LIMIT !== null) {
542-
progress(`Limit set: processing up to ${PROCESS_LIMIT} PRs for 30-day stats (set LIMIT= or unset for full run).`);
543-
}
397+
const prs30 = await searchIssuesAndPrs(octokit, 'is:pr', lookbackData.thirtyDaysAgo, lookbackData.midnightYesterday);
544398
progress(`Fetched ${prs30.length} PRs (30 days). Processing...`);
545399

546400
const { byCodeOwnerAndComponent: prStats, componentPrStats } = await computePrStats(octokit, prs30, labelToOwners, componentLabels, lookbackData.thirtyDaysAgo, lookbackData.midnightYesterday);
547401

548-
let lowActivityMarkdown = '';
549-
if (REPORT_LOW_ACTIVITY_6MO) {
550-
const prs6Mo = await getPrsInWindowChunked(octokit, lookbackData.sixMonthsAgo, lookbackData.midnightYesterday);
551-
progress(`Fetched ${prs6Mo.length} PRs (6 months). Computing low-activity stats...`);
552-
const { byCodeOwnerAndComponent: sixMonthPrStats } = await computePrStats(octokit, prs6Mo, labelToOwners, componentLabels, lookbackData.sixMonthsAgo, lookbackData.midnightYesterday, null);
553-
lowActivityMarkdown = formatLowActivityCodeOwners(sixMonthPrStats);
554-
}
555-
// const issueStats = await computeIssueStats(octokit, issues, labelToOwners, componentLabels, lookbackData.thirtyDaysAgo, lookbackData.midnightYesterday);
556-
const issueStats = {};
557-
558-
const report = generateReport(prStats, componentPrStats, issueStats, lookbackData, lowActivityMarkdown);
402+
const report = generateReport(prStats, componentPrStats, lookbackData);
559403

560404
// Dry run: set DRY_RUN=true or DRY_RUN=1 to print the report without creating an issue.
561405
const dryRun = process.env.DRY_RUN === 'true' || process.env.DRY_RUN === '1';

0 commit comments

Comments
 (0)