Skip to content

Commit 0869e9d

Browse files
authored
fix: intermediate assignment guard to count beginner issues instead of GFI and to use GraphQl counting (hiero-ledger#1443)
Signed-off-by: Parv Ninama <[email protected]> Signed-off-by: Parv <[email protected]>
1 parent bb89fa7 commit 0869e9d

File tree

2 files changed

+59
-47
lines changed

2 files changed

+59
-47
lines changed

.github/scripts/bot-intermediate-assignment.js

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
const COMMENT_MARKER = process.env.INTERMEDIATE_COMMENT_MARKER || '<!-- Intermediate Issue Guard -->';
22
const INTERMEDIATE_LABEL = process.env.INTERMEDIATE_LABEL?.trim() || 'intermediate';
3-
const GFI_LABEL = process.env.GFI_LABEL?.trim() || 'Good First Issue';
3+
const BEGINNER_LABEL = process.env.BEGINNER_LABEL?.trim() || 'beginner';
44
const EXEMPT_PERMISSION_LEVELS = (process.env.INTERMEDIATE_EXEMPT_PERMISSIONS || 'admin,maintain,write,triage')
55
.split(',')
66
.map((entry) => entry.trim().toLowerCase())
77
.filter(Boolean);
88
const DRY_RUN = /^true$/i.test(process.env.DRY_RUN || '');
9+
const REQUIRED_BEGINNER_ISSUE_COUNT = 0 ;
10+
11+
function isSafeSearchToken(value) {
12+
return typeof value === 'string' && /^[a-zA-Z0-9._/-]+$/.test(value);
13+
}
914

1015
function hasLabel(issue, labelName) {
1116
if (!issue?.labels?.length) {
@@ -51,51 +56,54 @@ async function hasExemptPermission(github, owner, repo, username) {
5156
}
5257
}
5358

54-
async function countCompletedGfiIssues(github, owner, repo, username) {
55-
try {
56-
console.log(`Checking closed '${GFI_LABEL}' issues in ${owner}/${repo} for ${username}.`);
57-
const iterator = github.paginate.iterator(github.rest.issues.listForRepo, {
59+
async function countCompletedBeginnerIssues(github, owner, repo, username) {
60+
if (
61+
!isSafeSearchToken(owner) ||
62+
!isSafeSearchToken(repo) ||
63+
!isSafeSearchToken(username) ||
64+
!isSafeSearchToken(BEGINNER_LABEL)
65+
) {
66+
console.log('Invalid search inputs', {
5867
owner,
5968
repo,
60-
state: 'closed',
61-
labels: GFI_LABEL,
62-
assignee: username,
63-
sort: 'updated',
64-
direction: 'desc',
65-
per_page: 100,
69+
username,
70+
label: BEGINNER_LABEL,
6671
});
72+
return null;
73+
}
6774

68-
const normalizedAssignee = username.toLowerCase();
69-
let pageCount = 0;
70-
const MAX_PAGES = 8;
71-
72-
for await (const { data: issues } of iterator) {
73-
pageCount += 1;
74-
if (pageCount > MAX_PAGES) {
75-
console.log(`Reached pagination safety cap (${MAX_PAGES}) while checking GFIs for ${username}.`);
76-
break;
77-
}
78-
79-
console.log(`Scanning page ${pageCount} of closed '${GFI_LABEL}' issues for ${username} (items: ${issues.length}).`);
80-
const match = issues.find((issue) => {
81-
if (issue.pull_request) {
82-
return false;
75+
try {
76+
const searchQuery = [
77+
`repo:${owner}/${repo}`,
78+
`label:${BEGINNER_LABEL}`,
79+
'is:issue',
80+
'is:closed',
81+
`assignee:${username}`,
82+
].join(' ');
83+
84+
console.log('GraphQL search query:', searchQuery);
85+
86+
const result = await github.graphql(
87+
`
88+
query ($searchQuery: String!) {
89+
search(type: ISSUE, query: $searchQuery) {
90+
issueCount
8391
}
92+
}
93+
`,
94+
{ searchQuery }
95+
);
8496

85-
const assignees = Array.isArray(issue.assignees) ? issue.assignees : [];
86-
return assignees.some((assignee) => assignee?.login?.toLowerCase() === normalizedAssignee);
87-
});
97+
const count = result?.search?.issueCount ?? 0;
8898

89-
if (match) {
90-
console.log(`Found matching GFI issue #${match.number} (${match.html_url || 'no url'}) for ${username}.`);
91-
return 1;
92-
}
93-
}
99+
console.log(`Completed Beginner issues for ${username}: ${count}`);
94100

95-
return 0;
101+
return count;
96102
} catch (error) {
97103
const message = error instanceof Error ? error.message : String(error);
98-
console.log(`Unable to verify completed GFIs for ${username}: ${message}`);
104+
console.log(
105+
`Failed to count Beginner issues for ${username}: ${message}`
106+
);
99107
return null;
100108
}
101109
}
@@ -127,10 +135,10 @@ function buildRejectionComment({ mentee, completedCount }) {
127135
Hi @${mentee}! Thanks for your interest in contributing 💡
128136
129137
This issue is labeled as intermediate, which means it requires a bit more familiarity with the SDK.
130-
Before you can take it on, please complete at least one Good First Issue so we can make sure you have a smooth on-ramp.
138+
Before you can take it on, please complete at least one Beginner Issue so we can make sure you have a smooth on-ramp.
131139
132-
You've completed **${completedCount}** Good First Issue${plural} so far.
133-
Once you wrap up your first GFI, feel free to come back and we’ll gladly help you get rolling here!`;
140+
You've completed **${completedCount}** Beginner Issue${plural} so far.
141+
Once you wrap up your first Beginner issue, feel free to come back and we’ll gladly help you get rolling here!`;
134142
}
135143

136144
module.exports = async ({ github, context }) => {
@@ -164,30 +172,34 @@ module.exports = async ({ github, context }) => {
164172
return;
165173
}
166174

167-
const completedCount = await countCompletedGfiIssues(github, owner, repo, mentee);
175+
const completedCount = await countCompletedBeginnerIssues(github, owner, repo, mentee);
168176

169177
if (completedCount === null) {
170-
return console.log(`Skipping guard for @${mentee} on issue #${issue.number} due to API error when verifying GFIs.`);
178+
return console.log(`Skipping guard for @${mentee} on issue #${issue.number} due to API error when verifying Beginner issues.`);
179+
}
180+
181+
if(REQUIRED_BEGINNER_ISSUE_COUNT === 0){
182+
return console.log(`Skipping guard for @${mentee} on issue #${issue.number}: beginner requirement temporarily disabled`)
171183
}
172184

173-
if (completedCount >= 1) {
174-
console.log(`✅ ${mentee} has completed ${completedCount} GFI(s). Assignment allowed.`);
185+
if (completedCount >= REQUIRED_BEGINNER_ISSUE_COUNT) {
186+
console.log(`✅ ${mentee} has completed ${completedCount} Beginner issues. Assignment allowed.`);
175187
return;
176188
}
177189

178-
console.log(`❌ ${mentee} has completed ${completedCount} GFI(s). Assignment not allowed; proceeding with removal and comment.`);
190+
console.log(`❌ ${mentee} has completed ${completedCount} Beginner issues. Assignment not allowed; proceeding with removal and comment.`);
179191

180192
try {
181193
if (DRY_RUN) {
182-
console.log(`[dry-run] Would remove @${mentee} from issue #${issue.number} due to missing GFI completion.`);
194+
console.log(`[dry-run] Would remove @${mentee} from issue #${issue.number} due to missing Beginner issue completion.`);
183195
} else {
184196
await github.rest.issues.removeAssignees({
185197
owner,
186198
repo,
187199
issue_number: issue.number,
188200
assignees: [mentee],
189201
});
190-
console.log(`Removed @${mentee} from issue #${issue.number} due to missing GFI completion.`);
202+
console.log(`Removed @${mentee} from issue #${issue.number} due to missing Beginner issue completion.`);
191203
}
192204
} catch (error) {
193205
const message = error instanceof Error ? error.message : String(error);

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
173173
- Enhance TopicInfo `__str__` method and tests with additional coverage, and update the format_key function in `key_format.py` to handle objects with a _to_proto method.
174174

175175
### Fixed
176-
176+
- Refined intermediate assignment guard to validate Beginner issue completion with improved logging and GraphQL-based counting. (#1424)
177177
- Improved filename-related error handling with clearer and more descriptive error messages.(#1413)
178178
- Good First Issue bot no longer posts `/assign` reminders for repository collaborators. (#1367)
179179
- GFI workflow casing

0 commit comments

Comments
 (0)