From 28526b8eb269654d1116a58a4e6f5709b527d594 Mon Sep 17 00:00:00 2001
From: Maxwell G <maxwell@gtmx.me>
Date: Sat, 30 Dec 2023 20:52:57 +0000
Subject: [PATCH] pr_labeler: refactor new_contributor_welcome code

As of https://github.com/ansible/ansible-documentation/issues/69, the
pr_labeler responds with a welcome message when an issue or PR is opened
by a new contributor. It turns out this never actually worked properly.

The previous method that relied on Github's `author_association` flag
did not work with the app token that the pr_labeler uses. This refactors
the code to figure out whether a user is a new contributor by
searching the list of issues and PRs.

Fixes: https://github.com/ansible/ansible-documentation/issues/204
---
 hacking/pr_labeler/label.py | 60 +++++++++++++++++++++++++++++--------
 1 file changed, 47 insertions(+), 13 deletions(-)

diff --git a/hacking/pr_labeler/label.py b/hacking/pr_labeler/label.py
index 9e2e2f8d0d5..0549dfa9a7d 100644
--- a/hacking/pr_labeler/label.py
+++ b/hacking/pr_labeler/label.py
@@ -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
@@ -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"]
@@ -70,6 +71,7 @@ def get_event_info() -> dict[str, Any]:
 class GlobalArgs:
     owner: str
     repo: str
+    use_author_association: bool
 
     @property
     def full_repo(self) -> str:
@@ -216,24 +218,50 @@ 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 = dict(repo=ctx.global_args.full_repo, author=ctx.issue.user.login)
+    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"))
 
 
@@ -282,11 +310,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")