Skip to content
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
b7bc659
Add Code Owners file
lugi0 Feb 17, 2025
7b6a985
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 17, 2025
f2895f1
Merge branch 'opendatahub-io:main' into main
lugi0 Feb 19, 2025
bb7b218
feat: add PR auto-approve on '/lgtm' comment
lugi0 Feb 19, 2025
fcb7d69
fix: potential fix for label action
lugi0 Feb 19, 2025
5f94b1f
fix: add pull request number
lugi0 Feb 19, 2025
df24ecb
fix: extract PR from payload
lugi0 Feb 19, 2025
1e72fa2
fix: use bot user personal access token for pr approval
lugi0 Feb 20, 2025
a245e76
Update lgtm-approval.yml
lugi0 Mar 14, 2025
cb88b1a
Merge branch 'opendatahub-io:main' into feature/auto-approve-lgtm
lugi0 Mar 14, 2025
cd5cc1c
Update lgtm-approval.yml
lugi0 Mar 14, 2025
7c33075
Update lgtm-approval.yml
lugi0 Mar 14, 2025
1436f46
Update lgtm-approval.yml
lugi0 Mar 14, 2025
b891c0f
Update add-remove-labels.yml
lugi0 Mar 14, 2025
ff82d6a
Update lgtm-approval.yml
lugi0 Mar 14, 2025
c7e4f48
remove trailing whitespace
lugi0 Mar 14, 2025
c90635c
remove conditional on issue_comment events
lugi0 Mar 14, 2025
c4d7bbc
Try using review comment as well for labels
lugi0 Mar 14, 2025
4c97ed7
fix typo
lugi0 Mar 14, 2025
cf0d05d
change review behaviour in py script
lugi0 Mar 14, 2025
5966ef3
enable label action for review comments as well
lugi0 Mar 14, 2025
39087f7
fix line too long
lugi0 Mar 14, 2025
d4afd19
try adding +1 to review comment with gh api
lugi0 Mar 15, 2025
3fe3188
fix typo
lugi0 Mar 15, 2025
ffb2efb
use bot PAT
lugi0 Mar 15, 2025
3305881
Disable review reaction for the time being
lugi0 Mar 15, 2025
095f94f
approval from 'add-remove-labels'
lugi0 Mar 18, 2025
23c15a9
fix typo
lugi0 Mar 18, 2025
d0f6449
additional checks
lugi0 Mar 18, 2025
1895d8a
try using bot PAT
lugi0 Mar 18, 2025
a0ef243
don't fail with empty comment body
lugi0 Mar 18, 2025
47a4d90
add required arg to dismiss
lugi0 Mar 18, 2025
656472a
remove unused action
lugi0 Mar 18, 2025
e13e80c
check for allowed users
lugi0 Mar 19, 2025
d6abc7b
fix typo, more logging
lugi0 Mar 19, 2025
d467bce
workaround team membership
lugi0 Mar 19, 2025
bd872ce
fix typo, check for submitter/pr owner
lugi0 Mar 19, 2025
69e80ab
don't add lgtm label for owner of PR
lugi0 Mar 19, 2025
79fa746
implement org/team logic
lugi0 Mar 20, 2025
71586ee
fix typo
lugi0 Mar 20, 2025
e5fb8ed
reuse APPROVED constant
lugi0 Mar 25, 2025
bbbbd0c
fix type imports for github library
lugi0 Apr 1, 2025
863dcee
fix typo
lugi0 Apr 1, 2025
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
39 changes: 33 additions & 6 deletions .github/workflows/add-remove-labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ on:
types: [synchronize]

pull_request_review:
types: [submitted, edited]

pull_request_review_comment:
types: [created, edited]

issue_comment:
types: [created, edited, deleted]
if: |
contains(github.event.comment.body, '/wip') ||
contains(github.event.comment.body, '/verified') ||
contains(github.event.comment.body, '/lgtm') ||
contains(github.event.comment.body, '/hold')
# I don't believe the conditional is supported here
# if: |
# contains(github.event.comment.body, '/wip') ||
# contains(github.event.comment.body, '/verified') ||
# contains(github.event.comment.body, '/lgtm') ||
# contains(github.event.comment.body, '/hold')


permissions:
Expand All @@ -31,19 +36,41 @@ jobs:
comment-id: ${{ github.event.comment.id }}
reactions: '+1'

# This currently fails with either the bot PAT or the standard github token secret
# gh: Insufficient scopes for reacting to this Pull Request Review Comment. (HTTP 403)
# {"message":"Insufficient scopes for reacting to this Pull Request Review Comment.","documentation_url":"https://docs.github.com/rest/reactions/reactions#create-reaction-for-a-pull-request-review-comment","status":"403"}
# It could work if we had a token with the proper permissions.
# See https://github.com/peter-evans/create-or-update-comment/issues/392 for why the action above doesn't work.
# Confirmed as a bug, see: https://github.com/github/docs/issues/36899
# - name: Acknowledge the review with thumbs up reaction
# if: ${{ github.event.review }}
# env:
# GH_TOKEN: ${{ secrets.OPENDATAHUB_TESTS_BOT_PAT }}
# REVIEW_COMMENT_ID: ${{ github.event.review.id }}
# REPO_NAME: ${{ github.event.repository.name }}
# REPO_OWNER: ${{ github.event.repository.owner.login }}
# run: |
# gh api \
# --method POST \
# -H "Accept: application/vnd.github+json" \
# -H "X-GitHub-Api-Version: 2022-11-28" \
# /repos/$REPO_OWNER/$REPO_NAME/pulls/comments/$REVIEW_COMMENT_ID/reactions \
# -f "content=+1"

- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Run add remove labels
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.OPENDATAHUB_TESTS_BOT_PAT }}
GITHUB_PR_NUMBER: "${{ github.event.pull_request.number || github.event.issue.number }}"
GITHUB_EVENT_ACTION: ${{ github.event.action }}
GITHUB_EVENT_REVIEW_STATE: ${{ github.event.review.state }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
COMMENT_BODY: ${{ github.event.comment.body }}
REVIEW_COMMENT_BODY: ${{ github.event.review.body }}
GITHUB_USER_LOGIN: ${{ github.event.sender.login }}
ACTION: "add-remove-labels"
run: uv run python .github/workflows/scripts/pr_workflow.py
70 changes: 58 additions & 12 deletions .github/workflows/scripts/pr_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class SupportedActions:
def __init__(self) -> None:
self.repo: Repository
self.pr: PullRequest
self.gh_client: GitHub

self.repo_name = os.environ["GITHUB_REPOSITORY"]
self.pr_number = int(os.getenv("GITHUB_PR_NUMBER", 0))
Expand Down Expand Up @@ -77,22 +78,45 @@ def verify_base_config(self) -> None:
)

def set_gh_config(self) -> None:
gh_client: Github = Github(login_or_token=self.github_token)
self.repo = gh_client.get_repo(full_name_or_id=self.repo_name)
self.pr = self.repo.get_pull(number=self.pr_number)
self.gh_client: Github = Github(login_or_token=self.github_token)
self.repo: Repository = self.gh_client.get_repo(full_name_or_id=self.repo_name)
self.pr: PullRequest = self.repo.get_pull(number=self.pr_number)


class PrLabeler(PrBaseClass):
def __init__(self) -> None:
super().__init__()
self.user_login = os.getenv("GITHUB_USER_LOGIN")
self.review_state = os.getenv("GITHUB_EVENT_REVIEW_STATE")
# We don't care if the body of the comment is in the discussion page or a review
self.comment_body = os.getenv("COMMENT_BODY", "")
if self.comment_body == "":
# if it wasn't a discussion page comment, try to get a review comment, otherwise keep empty
self.comment_body = os.getenv("REVIEW_COMMENT_BODY", "")
self.last_commit = list(self.pr.get_commits())[-1]
self.last_commit_sha = self.last_commit.sha

self.verify_allowed_user()
self.verify_labeler_config()

def get_allowed_users(self) -> list[str]:
org: github.Organization = self.gh_client.get_organization("opendatahub-io")
# slug is the team name with replaced special characters,
# all words to lowercase and spaces replace with a -
team: github.Team = org.get_team_by_slug("opendatahub-tests-contributors")
members: PaginatedList[github.NamedUser] = team.get_members()
users = [member.login for member in members]
# TODO: replace once bot user is part of the org and team
# users = ["lugi0", "rnetser", "adolfo-ab", "tarukumar", "dbasunag", "mwaykole"]
return users

def verify_allowed_user(self) -> None:
allowed_users = self.get_allowed_users()
if self.user_login not in allowed_users:
LOGGER.info(f"User {self.user_login} is not allowed for this action. Exiting.")
sys.exit(0)
LOGGER.info(f"User {self.user_login} is allowed")

def verify_labeler_config(self) -> None:
if self.action == self.SupportedActions.add_remove_labels_action_name and self.event_name in (
"issue_comment",
Expand All @@ -101,11 +125,11 @@ def verify_labeler_config(self) -> None:
if not self.user_login:
sys.exit("`GITHUB_USER_LOGIN` is not set")

if self.event_name == "issue_comment" and not self.comment_body:
sys.exit("`COMMENT_BODY` is not set")

if self.event_name == "pull_request_review" and not self.review_state:
sys.exit("`GITHUB_EVENT_REVIEW_STATE` is not set")
if (
self.event_name == "issue_comment" or self.event_name == "pull_request_review"
) and not self.comment_body:
LOGGER.info("No comment, nothing to do. Exiting.")
sys.exit(0)

def run_pr_label_action(self) -> None:
if self.action == self.SupportedActions.pr_size_action_name:
Expand Down Expand Up @@ -223,6 +247,7 @@ def add_remove_pr_labels(self) -> None:

elif self.event_name == "pull_request_review":
self.pull_request_review_label_actions()
self.issue_comment_label_actions()

return

Expand All @@ -239,15 +264,15 @@ def pull_request_review_label_actions(
label_to_remove = None
label_to_add = None

if self.review_state == "approved":
if self.review_state == "APPROVED":
label_to_remove = change_requested_label
label_to_add = lgtm_label

elif self.review_state == "changes_requested":
elif self.review_state == "CHANGES_REQUESTED":
label_to_add = change_requested_label
label_to_remove = lgtm_label

elif self.review_state == "commented":
elif self.review_state == "COMMENTED":
label_to_add = f"{COMMENTED_BY_LABEL_PREFIX}{self.user_login}"

if label_to_add and label_to_add not in self.pr_labels:
Expand Down Expand Up @@ -276,12 +301,20 @@ def issue_comment_label_actions(
LOGGER.info(f"Processing labels: {labels}")
for label, action in labels.items():
if label == LGTM_LABEL_STR:
label = f"{LGTM_BY_LABEL_PREFIX}{self.user_login}"
if self.user_login == self.pr.user.login:
LOGGER.info("PR submitter cannot approve for their own PR")
continue
else:
label = f"{LGTM_BY_LABEL_PREFIX}{self.user_login}"
if not action[CANCEL_ACTION] or self.event_action == "deleted":
self.approve_pr()

label_in_pr = any([label == _label.lower() for _label in self.pr_labels])
LOGGER.info(f"Processing label: {label}, action: {action}")

if action[CANCEL_ACTION] or self.event_action == "deleted":
if label == LGTM_LABEL_STR:
self.dismiss_pr_approval()
if label_in_pr:
LOGGER.info(f"Removing label {label}")
self.pr.remove_from_labels(label=label)
Expand All @@ -297,6 +330,19 @@ def issue_comment_label_actions(
def add_welcome_comment(self) -> None:
self.pr.create_issue_comment(body=WELCOME_COMMENT)

def approve_pr(self) -> None:
self.pr.create_review(event="APPROVE")

def dismiss_pr_approval(self) -> None:
all_reviews = self.pr.get_reviews()
current_user = self.gh_client.get_user().login
LOGGER.info(f"Looking for approving review by user {current_user}")
# The reviews are paginated in chronological order. We need to get the newest by our account
for review in all_reviews.reversed:
if review.user.login == current_user and review.state == "APPROVED":
LOGGER.info(f"found review by user {current_user} with id {review.id}")
review.dismiss(message="Dismissing review due to '/lgtm cancel' comment")


def main() -> None:
labeler = PrLabeler()
Expand Down
Loading