Skip to content

Commit 8ab4da7

Browse files
committed
refactor: reduce cyclomatic complexity of detectUnlockedLevel
Signed-off-by: Parv Ninama <ninamaparv@gmail.com>
1 parent bc9d987 commit 8ab4da7

1 file changed

Lines changed: 64 additions & 23 deletions

File tree

.github/scripts/shared/core/eligibility.js

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,20 @@ const { CONFIG } = require('../config');
66
const { repoLabelFor } = require('../helpers/utils');
77
const { countClosedIssuesByAssignee } = require('../api/github-api');
88

9-
// Fast path: user has already completed ≥1 issue at this level or higher
9+
/**
10+
* Fast-path eligibility check.
11+
*
12+
* A contributor is considered eligible for `candidate` if they have already
13+
* completed at least one issue at that level OR any higher level.
14+
*
15+
* This avoids forcing strict progression when the user already has experience.
16+
*
17+
* @param {import('@actions/github').GitHub} github
18+
* @param {object} homeRepo
19+
* @param {string} username
20+
* @param {string} candidate - Canonical level key
21+
* @returns {Promise<boolean>}
22+
*/
1023
async function passesBypassCheck(github, homeRepo, username, candidate) {
1124
const idx = CONFIG.skillHierarchy.indexOf(candidate);
1225
if (idx === -1) return false;
@@ -23,10 +36,16 @@ async function passesBypassCheck(github, homeRepo, username, candidate) {
2336
}
2437

2538
/**
26-
* Checks whether a contributor meets the prerequisite count for a given level.
39+
* Checks whether a contributor meets the prerequisite count for a level.
40+
*
41+
* This enforces the normal progression rules:
42+
* e.g. "3 beginner issues required before intermediate".
2743
*
28-
* Unlike the bypass check, this verifies that the contributor has completed
29-
* enough issues at the *previous* level to formally qualify for the candidate.
44+
* @param {import('@actions/github').GitHub} github
45+
* @param {object} homeRepo
46+
* @param {string} username
47+
* @param {{ requiredLevel: string, requiredCount: number }} prereq
48+
* @returns {Promise<boolean|null>} null = API failure
3049
*/
3150
async function passesNormalCheck(github, homeRepo, username, prereq) {
3251
const count = await countClosedIssuesByAssignee(
@@ -116,39 +135,61 @@ function adjustEligibilityForCurrentPR(completedKey, eligibleKey) {
116135
}
117136

118137
/**
119-
* Detects whether the current PR completion crossed the threshold into the next level.
120-
*
121-
* Checks if the contributor has reached *exactly* the required count for the next
122-
* level after this PR. Uses cap = requiredCount + 1 to avoid over-fetching.
138+
* Computes next level metadata for unlock detection.
123139
*
124-
* Example: beginner → intermediate requires 3 beginner completions.
125-
* If the search returns count === 3, this PR was the one that crossed the line.
140+
* Ensures:
141+
* - current level exists
142+
* - next level exists
143+
* - next level depends on current level
126144
*
127-
* @param {import('@actions/github').GitHub} github
128-
* @param {object} homeRepo - Home repo entry from CONFIG.repos.
129-
* @param {string} username - GitHub login of the contributor.
130-
* @param {string} currentLevelKey - Canonical key of the level just completed.
131-
* @returns {Promise<string|null>} Canonical key of the unlocked level, or null.
145+
* @param {string} currentLevelKey
146+
* @returns {{ nextKey: string, nextPrereq: object } | null}
132147
*/
133-
async function detectUnlockedLevel(github, homeRepo, username, currentLevelKey) {
134-
const hierarchy = CONFIG.skillHierarchy;
148+
function getNextLevelInfo(currentLevelKey) {
149+
const hierarchy = CONFIG.skillHierarchy;
135150
const currentIndex = hierarchy.indexOf(currentLevelKey);
136-
137151
if (currentIndex === -1) return null;
138152

139-
const nextKey = hierarchy[currentIndex + 1] ?? null;
153+
const nextKey = hierarchy[currentIndex + 1];
140154
if (!nextKey) return null;
141155

142156
const nextPrereq = CONFIG.skillPrerequisites[nextKey];
143-
if (!nextPrereq?.requiredLevel || nextPrereq.requiredLevel !== currentLevelKey) return null;
157+
if (!nextPrereq || nextPrereq.requiredLevel !== currentLevelKey) return null;
158+
159+
return { nextKey, nextPrereq };
160+
}
161+
162+
/**
163+
* Detects if the contributor just unlocked the next level.
164+
*
165+
* Trigger condition:
166+
* completed count === requiredCount
167+
*
168+
* Uses a capped query (requiredCount + 1) for efficiency.
169+
*
170+
* @param {import('@actions/github').GitHub} github
171+
* @param {object} homeRepo
172+
* @param {string} username
173+
* @param {string} currentLevelKey
174+
* @returns {Promise<string|null>} unlocked level key
175+
*/
176+
async function detectUnlockedLevel(github, homeRepo, username, currentLevelKey) {
177+
const next = getNextLevelInfo(currentLevelKey);
178+
if (!next) return null;
179+
180+
const { nextKey, nextPrereq } = next;
144181

145182
const count = await countClosedIssuesByAssignee(
146-
github, homeRepo.owner, homeRepo.repo, username,
147-
repoLabelFor(homeRepo, currentLevelKey), nextPrereq.requiredCount + 1,
183+
github,
184+
homeRepo.owner,
185+
homeRepo.repo,
186+
username,
187+
repoLabelFor(homeRepo, currentLevelKey),
188+
nextPrereq.requiredCount + 1,
148189
);
149190

150191
if (count === null) return null;
151-
// NOTE: count is derived from first-page results only; safe because requiredCount is small.
192+
152193
return count === nextPrereq.requiredCount ? nextKey : null;
153194
}
154195

0 commit comments

Comments
 (0)