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

pr_labeler: refactor new_contributor_welcome code #990

Merged
merged 3 commits into from
Jan 10, 2024
Merged
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
86 changes: 65 additions & 21 deletions hacking/pr_labeler/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import json
import os
import re
from collections.abc import Collection
from collections.abc import Callable, Collection
from contextlib import suppress
from functools import cached_property
from pathlib import Path
Expand Down Expand Up @@ -36,6 +36,7 @@
trim_blocks=True,
undefined=StrictUndefined,
)
NEW_CONTRIBUTOR_LABEL = "new_contributor"

IssueOrPrCtx = Union["IssueLabelerCtx", "PRLabelerCtx"]
IssueOrPr = Union["github.Issue.Issue", "github.PullRequest.PullRequest"]
Expand All @@ -48,12 +49,12 @@ def log(ctx: IssueOrPrCtx, *args: object) -> None:


def get_repo(
*, authed: bool = True, owner: str, repo: str
args: GlobalArgs, authed: bool = True
) -> tuple[github.Github, github.Repository.Repository]:
gclient = github.Github(
auth=github.Auth.Token(os.environ["GITHUB_TOKEN"]) if authed else None,
)
repo_obj = gclient.get_repo(f"{owner}/{repo}")
repo_obj = gclient.get_repo(args.full_repo)
return gclient, repo_obj


Expand All @@ -70,6 +71,11 @@ def get_event_info() -> dict[str, Any]:
class GlobalArgs:
owner: str
repo: str
use_author_association: bool

@property
def full_repo(self) -> str:
return f"{self.owner}/{self.repo}"


@dataclasses.dataclass()
Expand All @@ -79,6 +85,7 @@ class LabelerCtx:
dry_run: bool
event_info: dict[str, Any]
issue: github.Issue.Issue
global_args: GlobalArgs

TYPE: ClassVar[str]

Expand Down Expand Up @@ -211,24 +218,57 @@ def add_label_if_new(ctx: IssueOrPrCtx, labels: Collection[str] | str) -> None:
ctx.member.add_to_labels(*labels)


def new_contributor_welcome(ctx: IssueOrPrCtx) -> None:
def is_new_contributor_assoc(ctx: IssueOrPrCtx) -> bool:
"""
Welcome a new contributor to the repo with a message and a label
Determine whether a user has previously contributed.
Requires authentication as a regular user and does not work with an app
token.
"""
# This contributor has already been welcomed!
if "new_contributor" in ctx.previously_labeled:
return
author_association = ctx.event_member.get(
"author_association", ctx.member.raw_data["author_association"]
)
log(ctx, "author_association is", author_association)
if author_association not in {
"FIRST_TIMER",
"FIRST_TIME_CONTRIBUTOR",
}:
return author_association in {"FIRST_TIMER", "FIRST_TIME_CONTRIBUTOR"}


def is_new_contributor_manual(ctx: IssueOrPrCtx) -> bool:
"""
Determine whether a user has previously opened an issue or PR in this repo
without needing special API access.
"""
query_data = {
"repo": "ansible/ansible-documentation",
"author": ctx.issue.user.login,
# Avoid potential race condition where a new contributor opens multiple
# PRs or issues at once.
# Better to welcome twice than not at all.
"is": "closed",
}
issues = ctx.client.search_issues("", **query_data)
for issue in issues:
if issue.number != ctx.issue.number:
return False
return True


def new_contributor_welcome(ctx: IssueOrPrCtx) -> None:
"""
Welcome a new contributor to the repo with a message and a label
"""
is_new_contributor: Callable[[IssueOrPrCtx], bool] = (
is_new_contributor_assoc
if ctx.global_args.use_author_association
else is_new_contributor_manual
)
if (
# Contributor has already been welcomed
NEW_CONTRIBUTOR_LABEL in ctx.previously_labeled
#
or not is_new_contributor(ctx)
):
return
log(ctx, "Welcoming new contributor")
add_label_if_new(ctx, "new_contributor")
add_label_if_new(ctx, NEW_CONTRIBUTOR_LABEL)
create_comment(ctx, get_data_file("docs_team_info.md"))


Expand Down Expand Up @@ -277,11 +317,17 @@ def warn_porting_guide_change(ctx: PRLabelerCtx) -> None:


@APP.callback()
def cb(*, click_ctx: typer.Context, owner: str = OWNER, repo: str = REPO):
def cb(
*,
click_ctx: typer.Context,
owner: str = OWNER,
repo: str = REPO,
use_author_association: bool = False,
):
"""
Basic triager for ansible/ansible-documentation
"""
click_ctx.obj = GlobalArgs(owner, repo)
click_ctx.obj = GlobalArgs(owner, repo, use_author_association)


@APP.command(name="pr")
Expand All @@ -300,9 +346,7 @@ def process_pr(
dry_run = True
authed = True

gclient, repo = get_repo(
authed=authed, owner=global_args.owner, repo=global_args.repo
)
gclient, repo = get_repo(global_args, authed)
pr = repo.get_pull(pr_number)
ctx = PRLabelerCtx(
client=gclient,
Expand All @@ -311,6 +355,7 @@ def process_pr(
dry_run=dry_run,
event_info=get_event_info(),
issue=pr.as_issue(),
global_args=global_args,
)
if not force_process_closed and pr.state != "open":
log(ctx, "Refusing to process closed ticket")
Expand All @@ -337,16 +382,15 @@ def process_issue(
if authed_dry_run:
dry_run = True
authed = True
gclient, repo = get_repo(
authed=authed, owner=global_args.owner, repo=global_args.repo
)
gclient, repo = get_repo(global_args, authed)
issue = repo.get_issue(issue_number)
ctx = IssueLabelerCtx(
client=gclient,
repo=repo,
issue=issue,
dry_run=dry_run,
event_info=get_event_info(),
global_args=global_args,
)
if not force_process_closed and issue.state != "open":
log(ctx, "Refusing to process closed ticket")
Expand Down