Repository: python/bedevere
Pull request #555 (2023-05-01 23:51:34)

gh-554: Handle Draft PRs
1. if opened PR is draft - don't add labels
2. if draft PR is published - add labels as a new non-draft PR
3. if a PR is unpublished - clear labels

Mariatta: Thank you!
Can you add test for when a PR went from published to draft?
codecov[bot]: ## [Codecov](https://codecov.io/gh/python/bedevere/pull/555?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python) Report
> Merging [#555](https://codecov.io/gh/python/bedevere/pull/555?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python) (184c174) into [main](https://codecov.io/gh/python/bedevere/commit/be4fece435f8c4fbc69b227bb9bd746e8f21e1bc?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python) (be4fece) will **decrease** coverage by `0.10%`.
> The diff coverage is `93.33%`.

[![Impacted file tree graph](https://codecov.io/gh/python/bedevere/pull/555/graphs/tree.svg?width=650&height=150&src=pr&token=S7kyeapERi&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python)](https://codecov.io/gh/python/bedevere/pull/555?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python)

```diff
@@             Coverage Diff             @@
##              main     #555      +/-   ##
===========================================
- Coverage   100.00%   99.90%   -0.10%     
===========================================
  Files           18       18              
  Lines         2030     2060      +30     
  Branches       231      234       +3     
===========================================
+ Hits          2030     2058      +28     
- Misses           0        1       +1     
- Partials         0        1       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| Python_3.10.11 | `99.90% <93.33%> (-0.10%)` | :arrow_down: |
| Python_3.11.3 | `99.90% <93.33%> (-0.10%)` | :arrow_down: |
| Python_3.8.16 | `99.90% <93.33%> (-0.10%)` | :arrow_down: |
| Python_3.9.16 | `99.90% <93.33%> (-0.10%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/python/bedevere/pull/555?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python) | Coverage Δ | |
|---|---|---|
| [bedevere/stage.py](https://codecov.io/gh/python/bedevere/pull/555?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python#diff-YmVkZXZlcmUvc3RhZ2UucHk=) | `98.57% <81.81%> (-1.43%)` | :arrow_down: |
| [tests/test\_stage.py](https://codecov.io/gh/python/bedevere/pull/555?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python#diff-dGVzdHMvdGVzdF9zdGFnZS5weQ==) | `100.00% <100.00%> (ø)` | |

itamaro: > Can you add test for when a PR went from published to draft?

Sure!

What's the correct way to mock an existing PR with labels and assert they get deleted?
It looks like the labels are associated with "issue data" based on other tests
Mariatta: We would test that there is an API call for removing the labels from PR.
itamaro: updated the PR - is this what you meant? (asserting on `gh.delete_url`)

Patch:
diff --git a/README.md b/README.md
index 77d3b74..1483ec6 100644
--- a/README.md
+++ b/README.md
@@ -11,14 +11,14 @@ If no GitHub issue number is found the status fails and the
 "Details" link points to the relevant
 [section of the devguide](https://devguide.python.org/getting-started/pull-request-lifecycle.html#submitting).
 - ### Links to GitHub issues
-If an issue number is found then the "Details" link points to the relevant issue 
+If an issue number is found then the "Details" link points to the relevant issue
 itself, making it easier to navigate from PR to issue.
 - ### Identifies missing news entry
-If no `Misc/NEWS.d` entry is found or the news entry is formatted incorrectly 
-and the issue doesn't have the `skip news` label, the status fails and a relevant 
+If no `Misc/NEWS.d` entry is found or the news entry is formatted incorrectly
+and the issue doesn't have the `skip news` label, the status fails and a relevant
 description label is added to it.
 - ### Closes invalid PRs
-Closes PRs that try to merge a maintenance branch into the main branch, adds 
+Closes PRs that try to merge a maintenance branch into the main branch, adds
 `invalid` label, and posts a relevant message.
 - ### Labels PRs for docs
 Labels PRs for documentation as `type-documentation`
@@ -43,7 +43,9 @@ blocking the PR from moving forward:
 
 ```mermaid
 flowchart TD
-    A([New PR]):::creator
+    A([Published PR]):::creator
+    A_draft([New Draft PR]):::creator
+    A_draft -- publish draft by contributor --> A:::creator
     A -- by contributor --> B[Awaiting review]:::anyone
     A -- by core dev --> C[Awaiting core review]:::coredev
     B & C -- new review by\nanother contributor --> C
diff --git a/bedevere/stage.py b/bedevere/stage.py
index ccd0a3a..ddd463e 100644
--- a/bedevere/stage.py
+++ b/bedevere/stage.py
@@ -109,6 +109,8 @@ async def opened_pr(event, gh, *arg, **kwargs):
     "awaiting review".
     """
     pull_request = event.data["pull_request"]
+    if pull_request.get("draft"):
+        return
     issue = await util.issue_for_PR(gh, pull_request)
     username = util.user_login(pull_request)
     if await util.is_core_dev(gh, username):
@@ -117,6 +119,18 @@ async def opened_pr(event, gh, *arg, **kwargs):
         await stage(gh, issue, Blocker.review)
 
 
+@router.register("pull_request", action="edited")
+async def edited_pr(event, gh, *arg, **kwargs):
+    pull_request = event.data["pull_request"]
+    issue = await util.issue_for_PR(gh, pull_request)
+    username = util.user_login(pull_request)
+    if pull_request.get("draft"):
+        await _remove_stage_labels(gh, issue)
+    else:
+        blocked_on = Blocker.core_review if await util.is_core_dev(gh, username) else Blocker.review
+        await stage(gh, issue, blocked_on)
+
+
 @router.register("push")
 async def new_commit_pushed(event, gh, *arg, **kwargs):
     """If there is a new commit pushed to the PR branch that is in `awaiting merge` state,


Test patch:
diff --git a/tests/test_stage.py b/tests/test_stage.py
index 2f78dd2..c25ba89 100644
--- a/tests/test_stage.py
+++ b/tests/test_stage.py
@@ -72,6 +72,74 @@ async def test_stage():
     assert post_[1] == [awaiting.Blocker.merge.value]
 
 
+async def test_opened_draft_pr():
+    # New Draft PR from a core dev.
+    username = "brettcannon"
+    issue_url = "https://api.github.com/issue/42"
+    data = {
+        "action": "opened",
+        "pull_request": {
+            "user": {
+                "login": username,
+            },
+            "issue_url": issue_url,
+            "draft": True,
+        },
+    }
+    event = sansio.Event(data, event="pull_request", delivery_id="12345")
+    teams = [{"name": "python core", "id": 6}]
+    items = {
+        f"https://api.github.com/teams/6/memberships/{username}": "OK",
+        issue_url: {"labels": [], "labels_url": "https://api.github.com/labels"},
+    }
+    gh = FakeGH(
+        getiter={"https://api.github.com/orgs/python/teams": teams}, getitem=items
+    )
+    await awaiting.router.dispatch(event, gh)
+    assert len(gh.post_) == 0
+
+    # Draft PR is published
+    data["action"] = "edited"
+    data["pull_request"]["draft"] = False
+    event = sansio.Event(data, event="pull_request", delivery_id="12345")
+    gh = FakeGH(
+        getiter={"https://api.github.com/orgs/python/teams": teams}, getitem=items
+    )
+    await awaiting.router.dispatch(event, gh)
+    assert len(gh.post_) == 1
+    post_ = gh.post_[0]
+    assert post_[0] == "https://api.github.com/labels"
+    assert post_[1] == [awaiting.Blocker.core_review.value]
+
+    # Published PR is unpublished (set back to Draft)
+    data["action"] = "edited"
+    data["pull_request"]["draft"] = True
+    encoded_label = "awaiting%20core%20review"
+    items[issue_url] = {
+        "labels": [
+            {
+                "url": f"https://api.github.com/repos/python/cpython/labels/{encoded_label}",
+                "name": "awaiting core review",
+            },
+            {
+                "url": "https://api.github.com/repos/python/cpython/labels/CLA%20signed",
+                "name": "CLA signed",
+            },
+        ],
+        "labels_url": "https://api.github.com/repos/python/cpython/issues/12345/labels{/name}",
+    }
+    event = sansio.Event(data, event="pull_request", delivery_id="12345")
+    gh = FakeGH(
+        getiter={"https://api.github.com/orgs/python/teams": teams}, getitem=items
+    )
+    await awaiting.router.dispatch(event, gh)
+    assert len(gh.post_) == 0
+    assert (
+        gh.delete_url ==
+        f"https://api.github.com/repos/python/cpython/issues/12345/labels/{encoded_label}"
+    )
+
+
 async def test_opened_pr():
     # New PR from a core dev.
     username = "brettcannon"
@@ -110,6 +178,7 @@ async def test_opened_pr():
                 "login": username,
             },
             "issue_url": issue_url,
+            "draft": False,
         },
     }
     event = sansio.Event(data, event="pull_request", delivery_id="12345")