Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci(*:skip) Add automatic reviewer assignment based on labels #3517

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/CODEREVIEWERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# The format should be similar to the CODEOWNERS file format:
# https://help.github.com/en/articles/about-code-owners
# except paths are replaced with labels
#
# Labels that should be mapped to reviewers should all be added
# in this file and on the GitHub UI (with the exact same name)

simulation @jafermarq

documentation @charlesbvll
27 changes: 27 additions & 0 deletions .github/workflows/assign_reviewer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: PR management

on:
pull_request:
types: [ready_for_review]

jobs:
assign-reviewers:
runs-on: ubuntu-latest
name: Assign reviewers
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Bootstrap
uses: ./.github/actions/bootstrap
with:
python-version: 3.11
poetry-skip: 'true'
- name: Parse PR labels and assign reviewers
run: |
python dev/assign_reviewers.py .github/CODEREVIEWERS \
${{ github.repository }} \
${{ github.event.pull_request.number }} \
${{ github.event_path }}
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
81 changes: 81 additions & 0 deletions dev/assign_reviewers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Assigns a reviewer to a PR based on its label."""

import json
import random
import subprocess
import sys


def _get_pr_labels(event_path):
with open(event_path, encoding="utf-8") as f:
event_data = json.load(f)
pr_author = event_data["pull_request"]["user"]["login"]
labels = [label["name"] for label in event_data["pull_request"]["labels"]]
return pr_author, labels


def _parse_codereviewers(file_path):
reviewers_map = {}
with open(file_path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split()
label = parts[0]
reviewers = [part.lstrip("@") for part in parts[1:]]
reviewers_map[label] = reviewers
return reviewers_map


def _assign_reviewer(label, reviewers, pr_author, repo, pr_number):
reviewers = [reviewer for reviewer in reviewers if reviewer != pr_author]
if not reviewers:
print(f"No reviewers to assign for label {label}")
return
random_reviewer = random.choice(reviewers)
try:
subprocess.run(
[
"gh",
"api",
"--method",
"POST",
"-H",
"Accept: application/vnd.github+json",
"-H",
"X-GitHub-Api-Version: 2022-11-28",
f"/repos/{repo}/pulls/{pr_number}/requested_reviewers",
"-f",
f"reviewers[]={random_reviewer}",
],
check=True,
)
except subprocess.CalledProcessError as e:
print(f"Error assigning reviewer @{random_reviewer} for label {label}: {e}")
sys.exit(1)
else:
print(f"Assigned reviewer @{random_reviewer} for label {label}")


if __name__ == "__main__":
if len(sys.argv) != 5:
print(
"Usage: python assign_reviewers.py <CODEREVIEWERS file path>"
"<REPO> <PR_NUMBER> <GH_EVENT file path>"
)
sys.exit(1)

codereviewers_path = sys.argv[1]
repo = sys.argv[2]
pr_number = sys.argv[3]
event_path = sys.argv[4]

pr_author, pr_labels = _get_pr_labels(event_path)
reviewers_map = _parse_codereviewers(codereviewers_path)

for label in pr_labels:
if label in reviewers_map:
_assign_reviewer(label, reviewers_map[label], pr_author, repo, pr_number)
else:
print(f"No reviewers found for label {label}")