Skip to content
Merged
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
37 changes: 34 additions & 3 deletions .github/scripts/oncall_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
ROTATION_TEAM_SLUG = "mcore-oncall-rotation"
ACTIVE_ONCALL_TEAM_SLUG = "mcore-oncall"
SLACK_USERGROUP_HANDLE = "mcore-oncall"
COMMUNITY_REQUEST_LABEL = "community-request"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we instructed external contributors to add this label?

TARGET_WEEKS = 12

# Caches for email and Slack lookups
Expand Down Expand Up @@ -391,14 +392,44 @@ def ensure_schedule_filled(schedule, repo_owner):
print(f"Appended: {new_entry}")

def assign_reviewer(pr_number):
"""Assigns the mcore-oncall team as the reviewer for the PR."""
"""Assigns mcore-oncall if no reviewers are set or community-request is applied."""
owner, repo = get_repo_info()

pr_url = f"{GITHUB_API_URL}/repos/{owner}/{repo}/pulls/{pr_number}"
pr_resp = requests.get(pr_url, headers=get_headers())

if pr_resp.status_code != 200:
print(f"Failed to fetch PR: {pr_resp.status_code} {pr_resp.text}")
sys.exit(1)

pr_data = pr_resp.json()
requested_reviewers = pr_data.get("requested_reviewers", [])
requested_teams = pr_data.get("requested_teams", [])
labels = {label.get("name") for label in pr_data.get("labels", [])}
requested_team_slugs = {team.get("slug") for team in requested_teams}
has_community_request_label = COMMUNITY_REQUEST_LABEL in labels

if ACTIVE_ONCALL_TEAM_SLUG in requested_team_slugs:
print(
f"Skipping reviewer request: team NVIDIA/{ACTIVE_ONCALL_TEAM_SLUG} "
"is already requested"
)
return

if not has_community_request_label and (requested_reviewers or requested_teams):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if not has_community_request_label and (requested_reviewers or requested_teams):
def needs_oncall_to_review():
if has_community_request_label:
# Oncall needs to trigger CI on behalf of external contributors.
return True
if not requested_reviews and not requested_teams:
# Oncall needs to add proper reviewers.
return True
return False
if not needs_oncall_to_review():

I'm really bad at composite conditions.

print(
"Skipping reviewer request: PR already has "
f"{len(requested_reviewers)} user reviewer(s) and "
f"{len(requested_teams)} team reviewer(s)"
)
return

url = f"{GITHUB_API_URL}/repos/{owner}/{repo}/pulls/{pr_number}/requested_reviewers"

# Assign the oncall team as reviewer
data = {"team_reviewers": [ACTIVE_ONCALL_TEAM_SLUG]}
resp = requests.post(url, headers=get_headers(), json=data)

if resp.status_code in [201, 200]:
print(f"Successfully requested review from team NVIDIA/{ACTIVE_ONCALL_TEAM_SLUG}")
else:
Expand Down
125 changes: 125 additions & 0 deletions tests/test_utils/python_scripts/test_oncall_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import importlib.util
import sys
import types
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).resolve().parents[3]
ONCALL_MANAGER_PATH = REPO_ROOT / ".github" / "scripts" / "oncall_manager.py"


class FakeResponse:
def __init__(self, status_code, json_data=None, text=""):
self.status_code = status_code
self._json_data = json_data
self.text = text

def json(self):
return self._json_data


class FakeRequests:
def __init__(self, pr_data):
self.pr_data = pr_data
self.posts = []

def get(self, url, headers=None):
return FakeResponse(200, self.pr_data)

def post(self, url, headers=None, json=None):
self.posts.append({"url": url, "headers": headers, "json": json})
return FakeResponse(201)


@pytest.fixture
def oncall_manager(monkeypatch):
slack_module = types.ModuleType("slack_sdk")
slack_module.WebClient = object

slack_errors_module = types.ModuleType("slack_sdk.errors")
slack_errors_module.SlackApiError = Exception
requests_module = types.ModuleType("requests")

monkeypatch.setitem(sys.modules, "requests", requests_module)
monkeypatch.setitem(sys.modules, "slack_sdk", slack_module)
monkeypatch.setitem(sys.modules, "slack_sdk.errors", slack_errors_module)
monkeypatch.setenv("GITHUB_REPOSITORY", "NVIDIA/Megatron-LM")
monkeypatch.setenv("GH_TOKEN", "token")

spec = importlib.util.spec_from_file_location("oncall_manager", ONCALL_MANAGER_PATH)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module


@pytest.mark.parametrize(
"pr_data",
[
{
"user": {"login": "maintainer"},
"requested_reviewers": [{"login": "alice"}],
"requested_teams": [],
},
{
"user": {"login": "maintainer"},
"requested_reviewers": [],
"requested_teams": [{"slug": "mcore"}],
},
{
"requested_reviewers": [{"login": "alice"}],
"requested_teams": [{"slug": "mcore-oncall"}],
"labels": [{"name": "community-request"}],
},
],
)
def test_assign_reviewer_skips_when_oncall_is_not_needed(
oncall_manager, monkeypatch, capsys, pr_data
):
fake_requests = FakeRequests(pr_data)
monkeypatch.setattr(oncall_manager, "requests", fake_requests)

oncall_manager.assign_reviewer(123)

assert fake_requests.posts == []
assert "Skipping reviewer request" in capsys.readouterr().out


@pytest.mark.parametrize(
"pr_data",
[
{"user": {"login": "maintainer"}, "requested_reviewers": [], "requested_teams": []},
{
"requested_reviewers": [{"login": "alice"}],
"requested_teams": [{"slug": "mcore"}],
"labels": [{"name": "community-request"}],
},
],
)
def test_assign_reviewer_requests_oncall_when_needed(oncall_manager, monkeypatch, pr_data):
fake_requests = FakeRequests(pr_data)
monkeypatch.setattr(oncall_manager, "requests", fake_requests)

oncall_manager.assign_reviewer(123)

assert fake_requests.posts == [
{
"url": "https://api.github.com/repos/NVIDIA/Megatron-LM/pulls/123/requested_reviewers",
"headers": {"Authorization": "token token", "Accept": "application/vnd.github.v3+json"},
"json": {"team_reviewers": ["mcore-oncall"]},
}
]
Loading