fix(github): improve label-workstream-rollup efficiency with GraphQL (#17217)

This commit is contained in:
Bryan Morgan
2026-01-21 12:10:19 -05:00
committed by GitHub
parent c43b04b44c
commit 9e3d36c19b
+90 -49
View File
@@ -28,14 +28,27 @@ jobs:
'https://github.com/google-gemini/gemini-cli/issues/17203' 'https://github.com/google-gemini/gemini-cli/issues/17203'
]; ];
async function getIssueParent(owner, repo, number) { // Single issue processing (for event triggers)
async function processSingleIssue(owner, repo, number) {
const query = ` const query = `
query($owner:String!, $repo:String!, $number:Int!) { query($owner:String!, $repo:String!, $number:Int!) {
repository(owner:$owner, name:$repo) { repository(owner:$owner, name:$repo) {
issue(number:$number) { issue(number:$number) {
number
parent { parent {
url url
number parent {
url
parent {
url
parent {
url
parent {
url
}
}
}
}
} }
} }
} }
@@ -43,73 +56,101 @@ jobs:
`; `;
try { try {
const result = await github.graphql(query, { owner, repo, number }); const result = await github.graphql(query, { owner, repo, number });
return result.repository.issue.parent || null; const issue = result.repository.issue;
checkAndLabel(issue, owner, repo);
} catch (error) { } catch (error) {
console.error(`Failed to fetch parent for #${number}:`, error); console.error(`Failed to process issue #${number}:`, error);
return null;
} }
} }
// Determine which issues to process // Bulk processing (for schedule/dispatch)
let issuesToProcess = []; async function processAllOpenIssues(owner, repo) {
const query = `
query($owner:String!, $repo:String!, $cursor:String) {
repository(owner:$owner, name:$repo) {
issues(first: 100, states: OPEN, after: $cursor) {
pageInfo {
hasNextPage
endCursor
}
nodes {
number
parent {
url
parent {
url
parent {
url
parent {
url
parent {
url
}
}
}
}
}
}
}
}
}
`;
if (context.eventName === 'issues') { let hasNextPage = true;
// Context payload for 'issues' event already has the issue object let cursor = null;
issuesToProcess.push({
number: context.payload.issue.number, while (hasNextPage) {
owner: context.repo.owner, try {
repo: context.repo.repo const result = await github.graphql(query, { owner, repo, cursor });
}); const issues = result.repository.issues.nodes;
} else {
// For schedule/dispatch, fetch open issues (lightweight list) console.log(`Processing batch of ${issues.length} issues...`);
console.log(`Running for event: ${context.eventName}. Fetching open issues...`); for (const issue of issues) {
const openIssues = await github.paginate(github.rest.issues.listForRepo, { await checkAndLabel(issue, owner, repo);
owner: context.repo.owner, }
repo: context.repo.repo,
state: 'open' hasNextPage = result.repository.issues.pageInfo.hasNextPage;
}); cursor = result.repository.issues.pageInfo.endCursor;
issuesToProcess = openIssues.map(i => ({ } catch (error) {
number: i.number, console.error('Failed to fetch issues batch:', error);
owner: context.repo.owner, hasNextPage = false;
repo: context.repo.repo }
})); }
} }
console.log(`Processing ${issuesToProcess.length} issue(s)...`); async function checkAndLabel(issue, owner, repo) {
if (!issue || !issue.parent) return;
for (const issue of issuesToProcess) { let currentParent = issue.parent;
let currentNumber = issue.number;
let depth = 0;
const MAX_DEPTH = 5; // Safety limit for recursion
let matched = false;
let tracedParents = []; let tracedParents = [];
let matched = false;
while (depth < MAX_DEPTH) { while (currentParent) {
const parent = await getIssueParent(issue.owner, issue.repo, currentNumber); tracedParents.push(currentParent.url);
if (!parent) { if (allowedParentUrls.includes(currentParent.url)) {
break; console.log(`SUCCESS: Issue #${issue.number} is a descendant of ${currentParent.url}. Trace: ${tracedParents.join(' -> ')}. Adding label.`);
}
tracedParents.push(parent.url);
if (allowedParentUrls.includes(parent.url)) {
console.log(`SUCCESS: Issue #${issue.number} is a descendant of ${parent.url}. Trace: ${tracedParents.join(' -> ')}. Adding label.`);
await github.rest.issues.addLabels({ await github.rest.issues.addLabels({
owner: issue.owner, owner,
repo: issue.repo, repo,
issue_number: issue.number, issue_number: issue.number,
labels: [labelToAdd] labels: [labelToAdd]
}); });
matched = true; matched = true;
break; break;
} }
currentParent = currentParent.parent;
currentNumber = parent.number;
depth++;
} }
if (!matched && context.eventName === 'issues') { if (!matched && context.eventName === 'issues') {
console.log(`Issue #${issue.number} did not match any allowed workstreams after checking ${depth} levels. Trace: ${tracedParents.join(' -> ') || 'None'}.`); console.log(`Issue #${issue.number} did not match any allowed workstreams. Trace: ${tracedParents.join(' -> ') || 'None'}.`);
} }
} }
// Main execution
if (context.eventName === 'issues') {
console.log(`Processing single issue #${context.payload.issue.number}...`);
await processSingleIssue(context.repo.owner, context.repo.repo, context.payload.issue.number);
} else {
console.log(`Running for event: ${context.eventName}. Processing all open issues...`);
await processAllOpenIssues(context.repo.owner, context.repo.repo);