Skip to content

Commit d3d110f

Browse files
committed
Enhanced assign_reviewers.py with detailed feedback for unmapped files and modules
- Modified map_modules() to return both modules and unmapped files - Enhanced gather_reviewers() to track modules without owners - Added comprehensive feedback when no reviewers are assigned: - Warns about files with no module mapping - Warns about modules with no owners - Explains specific reasons for no assignment - Provides actionable guidance for fixing coverage gaps - Updated test cases to cover new functionality - Added test cases for unmapped files and modules without owners Signed-off-by: Venky Ganesh <[email protected]>
1 parent c3ff1b4 commit d3d110f

File tree

2 files changed

+104
-19
lines changed

2 files changed

+104
-19
lines changed

.github/scripts/assign_reviewers.py

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,25 +66,43 @@ def load_json(path: str):
6666
return json.load(f)
6767

6868

69-
def map_modules(changed_files: list[str], module_paths: dict[str,
70-
str]) -> set[str]:
69+
def map_modules(changed_files: list[str],
70+
module_paths: dict[str, str]) -> tuple[set[str], list[str]]:
71+
"""Map changed files to modules and return both modules and unmapped files"""
7172
modules: set[str] = set()
73+
unmapped_files: list[str] = []
74+
7275
for file in changed_files:
76+
mapped = False
7377
for prefix, module in module_paths.items():
7478
if file.startswith(prefix):
7579
modules.add(module)
80+
mapped = True
7681
break
77-
return modules
82+
83+
if not mapped:
84+
unmapped_files.append(file)
85+
86+
return modules, unmapped_files
7887

7988

80-
def gather_reviewers(modules: set[str],
81-
module_owners: dict[str, list[str]],
82-
*,
83-
pr_author: str | None = None,
84-
existing_reviewers: set[str] | None = None) -> list[str]:
89+
def gather_reviewers(
90+
modules: set[str],
91+
module_owners: dict[str, list[str]],
92+
*,
93+
pr_author: str | None = None,
94+
existing_reviewers: set[str] | None = None
95+
) -> tuple[list[str], set[str]]:
96+
"""Gather reviewers and return both reviewers and modules without owners"""
8597
reviewers: set[str] = set()
98+
modules_without_owners: set[str] = set()
99+
86100
for module in modules:
87-
reviewers.update(module_owners.get(module, []))
101+
owners = module_owners.get(module, [])
102+
if owners:
103+
reviewers.update(owners)
104+
else:
105+
modules_without_owners.add(module)
88106

89107
if pr_author:
90108
reviewers.discard(pr_author)
@@ -93,7 +111,7 @@ def gather_reviewers(modules: set[str],
93111
if existing_reviewers:
94112
reviewers -= existing_reviewers
95113

96-
return sorted(reviewers)
114+
return sorted(reviewers), modules_without_owners
97115

98116

99117
def main() -> None:
@@ -139,8 +157,8 @@ def main() -> None:
139157
module_paths = load_json(Path(".github") / "module-paths.json")
140158
module_owners = load_json(Path(".github") / "module-owners.json")
141159

142-
modules = map_modules(changed_files, module_paths)
143-
reviewers = gather_reviewers(
160+
modules, unmapped_files = map_modules(changed_files, module_paths)
161+
reviewers, modules_without_owners = gather_reviewers(
144162
modules,
145163
module_owners,
146164
pr_author=pr_author,
@@ -154,6 +172,23 @@ def main() -> None:
154172
print(f"Changed modules: {sorted(modules)}")
155173
print(f"Potential reviewers: {reviewers}")
156174

175+
# Provide detailed feedback about coverage gaps
176+
if unmapped_files:
177+
print(f"⚠️ Files with no module mapping: {unmapped_files}")
178+
print(
179+
f" These files are not covered in .github/module-paths.json")
180+
print(
181+
f" Consider adding appropriate module mappings for these paths."
182+
)
183+
184+
if modules_without_owners:
185+
print(
186+
f"⚠️ Modules with no owners: {sorted(modules_without_owners)}")
187+
print(
188+
f" These modules exist in module-paths.json but have no owners in module-owners.json"
189+
)
190+
print(f" Consider adding owner assignments for these modules.")
191+
157192
if reviewers:
158193
cmd = ["gh", "pr", "edit", pr_number]
159194
for reviewer in reviewers:
@@ -176,6 +211,31 @@ def main() -> None:
176211
else:
177212
print("✅ No new reviewers to assign")
178213

214+
# Explain why no reviewers were assigned
215+
if not modules and not unmapped_files:
216+
print(" Reason: No files were changed in this PR")
217+
elif not modules and unmapped_files:
218+
print(
219+
" Reason: All changed files are unmapped (no module coverage)"
220+
)
221+
print(
222+
" ➜ Action needed: Add module mappings to .github/module-paths.json"
223+
)
224+
elif modules and not reviewers:
225+
if modules_without_owners:
226+
print(" Reason: Matched modules have no assigned owners")
227+
print(
228+
" ➜ Action needed: Add owner assignments to .github/module-owners.json"
229+
)
230+
else:
231+
print(
232+
" Reason: All potential reviewers are already assigned or excluded"
233+
)
234+
else:
235+
print(
236+
" Reason: Complex combination of mapping/ownership issues (see warnings above)"
237+
)
238+
179239
except subprocess.CalledProcessError as e:
180240
print(f"❌ Error processing PR: {e}", file=sys.stderr)
181241
sys.exit(1)

.github/scripts/tests/test_assign_reviewers.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -337,29 +337,54 @@ def test_map_modules_function(self):
337337
"cpp/main.cpp", "cpp/utils.h", "docs/README.md", "unknown/file.txt"
338338
]
339339

340-
modules = assign_reviewers.map_modules(changed_files, self.module_paths)
340+
modules, unmapped_files = assign_reviewers.map_modules(
341+
changed_files, self.module_paths)
341342

342343
self.assertEqual(modules, {"Generic Runtime", "Documentation"})
344+
self.assertEqual(unmapped_files, ["unknown/file.txt"])
343345

344346
def test_gather_reviewers_function(self):
345347
"""Test the pure gather_reviewers function"""
346348
modules = {"Generic Runtime", "Documentation"}
347349

348350
# Test without exclusions
349-
reviewers = assign_reviewers.gather_reviewers(modules,
350-
self.module_owners)
351+
reviewers, modules_without_owners = assign_reviewers.gather_reviewers(
352+
modules, self.module_owners)
351353
self.assertEqual(set(reviewers), {"user1", "user2", "user3", "user9"})
354+
self.assertEqual(modules_without_owners, set())
352355

353356
# Test with author exclusion
354-
reviewers = assign_reviewers.gather_reviewers(modules,
355-
self.module_owners,
356-
pr_author="user1")
357+
reviewers, modules_without_owners = assign_reviewers.gather_reviewers(
358+
modules, self.module_owners, pr_author="user1")
357359
self.assertEqual(set(reviewers), {"user2", "user3", "user9"})
360+
self.assertEqual(modules_without_owners, set())
358361

359362
# Test with existing reviewers exclusion
360-
reviewers = assign_reviewers.gather_reviewers(
363+
reviewers, modules_without_owners = assign_reviewers.gather_reviewers(
361364
modules, self.module_owners, existing_reviewers={"user2", "user9"})
362365
self.assertEqual(set(reviewers), {"user1", "user3"})
366+
self.assertEqual(modules_without_owners, set())
367+
368+
def test_modules_without_owners(self):
369+
"""Test modules that have no owners defined"""
370+
modules = {"Generic Runtime", "NonExistent Module"}
371+
372+
reviewers, modules_without_owners = assign_reviewers.gather_reviewers(
373+
modules, self.module_owners)
374+
375+
self.assertEqual(set(reviewers), {"user1", "user2", "user3"})
376+
self.assertEqual(modules_without_owners, {"NonExistent Module"})
377+
378+
def test_all_files_unmapped(self):
379+
"""Test when all files are unmapped"""
380+
changed_files = ["unmapped/file1.txt", "another/file2.py"]
381+
382+
modules, unmapped_files = assign_reviewers.map_modules(
383+
changed_files, self.module_paths)
384+
385+
self.assertEqual(modules, set())
386+
self.assertEqual(set(unmapped_files),
387+
{"unmapped/file1.txt", "another/file2.py"})
363388

364389
@patch('assign_reviewers.load_json')
365390
@patch('subprocess.run')

0 commit comments

Comments
 (0)