-
Notifications
You must be signed in to change notification settings - Fork 7
feat: Add a skill that shows PRs that need to be reviewed #59
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
Merged
+190
−0
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| --- | ||
| name: gh-review-requests | ||
| description: Fetch unread GitHub notifications for open PRs where review is requested from a specified team or opened by a team member. Use when asked to "find PRs I need to review", "show my review requests", "what needs my review", "fetch GitHub review requests", or "check team review queue". | ||
| allowed-tools: Bash | ||
| --- | ||
|
|
||
| # GitHub Review Requests | ||
|
|
||
| Fetch unread `review_requested` notifications for open (unmerged) PRs, filtered by a GitHub team. | ||
|
|
||
| **Requires**: GitHub CLI (`gh`) authenticated. | ||
|
|
||
| ## Step 1: Identify the Team | ||
|
|
||
| If the user has not specified a team, ask: | ||
|
|
||
| > Which GitHub team should I filter by? (e.g. `streaming-platform`) | ||
|
|
||
| Accept either a team slug (`streaming-platform`) or a display name ("Streaming Platform") — convert to lowercase-hyphenated slug before passing to the script. | ||
|
|
||
| ## Step 2: Run the Script | ||
|
|
||
| ```bash | ||
| uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_review_requests.py --org getsentry --teams <team-slug> | ||
| ``` | ||
|
|
||
| To filter by multiple teams, pass a comma-separated list: | ||
|
|
||
| ```bash | ||
| uv run ${CLAUDE_SKILL_ROOT}/scripts/fetch_review_requests.py --org getsentry --teams <team slugs> | ||
| ``` | ||
|
|
||
| ### Script output | ||
|
|
||
| ```json | ||
| { | ||
| "total": 3, | ||
| "prs": [ | ||
| { | ||
| "notification_id": "12345", | ||
| "title": "feat(kafka): add workflow to restart a broker", | ||
| "url": "https://github.com/getsentry/ops/pull/19144", | ||
| "repo": "getsentry/ops", | ||
| "pr_number": 19144, | ||
| "author": "bmckerry", | ||
| "reasons": ["opened by: bmckerry"] | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| `reasons` will contain one or both of: | ||
| - `"review requested from: <Team Name>"` — the team is a requested reviewer | ||
| - `"opened by: <login>"` — the PR author is a team member | ||
|
|
||
| ## Step 3: Present Results | ||
|
|
||
| Display results as a markdown table with full URLs: | ||
|
|
||
| | # | Title | URL | Reason | | ||
| |---|-------|-----|--------| | ||
| | 1 | feat(kafka): add workflow to restart a broker | https://github.com/getsentry/ops/pull/19144 | opened by: evanh | | ||
|
|
||
| If `total` is 0, say: "No unread review requests found for that team." | ||
|
|
||
| ## Fallback | ||
|
|
||
| If the script fails, run manually: | ||
|
|
||
| ```bash | ||
| gh api notifications --paginate | ||
| ``` | ||
|
|
||
| Then for each `review_requested` notification, check: | ||
| - `gh api repos/{repo}/pulls/{number}` — skip if `state == "closed"` or `merged_at` is set | ||
| - `gh api repos/{repo}/pulls/{number}/requested_reviewers` — check `teams[].name` | ||
| - `gh api orgs/{org}/teams/{slug}/members` — check if author is a member |
113 changes: 113 additions & 0 deletions
113
plugins/sentry-skills/skills/gh-review-requests/scripts/fetch_review_requests.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| # /// script | ||
| # requires-python = ">=3.12" | ||
| # dependencies = [] | ||
| # /// | ||
| """ | ||
| Fetch unread GitHub review-requested notifications for open (unmerged) PRs, | ||
| filtered by team membership and/or team review requests. | ||
|
|
||
| Usage: | ||
| uv run fetch_review_requests.py --org ORG --teams TEAM1,TEAM2 | ||
|
|
||
| Arguments: | ||
| --org GitHub organization slug (default: getsentry) | ||
| --teams Comma-separated team slugs to filter by (e.g. streaming-platform) | ||
|
|
||
| Output: JSON to stdout | ||
| """ | ||
|
|
||
| import argparse | ||
| import json | ||
| import subprocess | ||
| import sys | ||
|
|
||
|
|
||
| def gh(path: str, paginate: bool = False) -> list | dict: | ||
| cmd = ["gh", "api", path] | ||
| if paginate: | ||
| cmd.append("--paginate") | ||
| result = subprocess.run(cmd, capture_output=True, text=True) | ||
| if result.returncode != 0 or not result.stdout: | ||
| print(f"Error running gh {' '.join(cmd)}: {result.stderr}", file=sys.stderr) | ||
| return [] if paginate else {} | ||
| return json.loads(result.stdout) | ||
|
|
||
|
|
||
| def main(): | ||
| parser = argparse.ArgumentParser() | ||
| parser.add_argument("--org", default="getsentry") | ||
| parser.add_argument("--teams", required=True, help="Comma-separated team slugs") | ||
| args = parser.parse_args() | ||
|
|
||
| team_slugs = [t.strip() for t in args.teams.split(",")] | ||
|
|
||
| # Resolve team members for all specified teams | ||
| members: set[str] = set() | ||
| team_display_names: dict[str, str] = {} | ||
| for slug in team_slugs: | ||
| data = gh(f"orgs/{args.org}/teams/{slug}/members", paginate=True) | ||
| for m in data: | ||
| members.add(m["login"]) | ||
| # Get display name | ||
| team_data = gh(f"orgs/{args.org}/teams/{slug}") | ||
| team_display_names[slug] = team_data.get("name", slug) | ||
|
|
||
| # Fetch unread notifications (GitHub API default: unread only) | ||
| all_notifs = gh("notifications", paginate=True) | ||
| review_notifs = [ | ||
| n for n in all_notifs | ||
| if n["reason"] == "review_requested" and n["unread"] | ||
| ] | ||
|
|
||
| prs = [] | ||
| for n in review_notifs: | ||
| url = n["subject"]["url"] | ||
| repo_path = url.replace("https://api.github.com/repos/", "") | ||
| repo = repo_path.rsplit("/pulls/", 1)[0] | ||
| pr_num = repo_path.rsplit("/", 1)[-1] | ||
| html_url = f"https://github.com/{repo}/pull/{pr_num}" | ||
|
|
||
| pr_data = gh(f"repos/{repo}/pulls/{pr_num}") | ||
| if not pr_data: | ||
| continue | ||
|
|
||
| # Skip merged or closed PRs | ||
| if pr_data.get("merged_at") or pr_data.get("state") == "closed": | ||
| continue | ||
|
|
||
| author = pr_data["user"]["login"] | ||
|
|
||
| reviewers_data = gh(f"repos/{repo}/pulls/{pr_num}/requested_reviewers") | ||
| requested_team_names = [t["slug"] for t in reviewers_data.get("teams", [])] | ||
| matching_teams = [ | ||
| t for t in requested_team_names | ||
| if any(slug.lower() == t.lower() for slug in team_slugs) | ||
| ] | ||
|
|
||
| by_team_member = author in members | ||
| review_from_team = len(matching_teams) > 0 | ||
|
|
||
| if not (by_team_member or review_from_team): | ||
| continue | ||
|
|
||
| reasons = [] | ||
| if review_from_team: | ||
| reasons.append(f"review requested from: {', '.join(matching_teams)}") | ||
| if by_team_member: | ||
| reasons.append(f"opened by: {author}") | ||
|
|
||
| prs.append({ | ||
| "notification_id": n["id"], | ||
| "title": n["subject"]["title"], | ||
| "url": html_url, | ||
| "repo": repo, | ||
| "pr_number": int(pr_num), | ||
| "author": author, | ||
| "reasons": reasons, | ||
| }) | ||
|
|
||
| print(json.dumps({"total": len(prs), "prs": prs}, indent=2)) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.