From 66c69bc84e12dc6e863928d616e61d21fadee965 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Mon, 24 May 2021 14:17:54 +0530 Subject: [PATCH] feat(event): approve workflow run Conditions: - Coming from a pull request - Only if it's required - Pull request associated for the workflow run is still open --- algorithms_keeper/event/__init__.py | 2 ++ algorithms_keeper/event/workflow.py | 34 +++++++++++++++++++ algorithms_keeper/utils.py | 52 +++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 algorithms_keeper/event/workflow.py diff --git a/algorithms_keeper/event/__init__.py b/algorithms_keeper/event/__init__.py index b8b1a60..3e06580 100644 --- a/algorithms_keeper/event/__init__.py +++ b/algorithms_keeper/event/__init__.py @@ -5,6 +5,7 @@ from algorithms_keeper.event.installation import installation_router from algorithms_keeper.event.issues import issues_router from algorithms_keeper.event.pull_request import pull_request_router +from algorithms_keeper.event.workflow import workflow_router main_router: Router = Router( check_run_router, @@ -12,6 +13,7 @@ installation_router, issues_router, pull_request_router, + workflow_router, ) __all__ = ["main_router"] diff --git a/algorithms_keeper/event/workflow.py b/algorithms_keeper/event/workflow.py new file mode 100644 index 0000000..85957eb --- /dev/null +++ b/algorithms_keeper/event/workflow.py @@ -0,0 +1,34 @@ +import logging +from typing import Any + +from gidgethub import routing +from gidgethub.sansio import Event + +from algorithms_keeper import utils +from algorithms_keeper.api import GitHubAPI + +workflow_router = routing.Router() + +logger = logging.getLogger(__package__) + + +@workflow_router.register("workflow_run", action="requested") +async def approve_workflow_run( + event: Event, gh: GitHubAPI, *args: Any, **kwargs: Any +) -> None: + """Approve a workflow run from a first-time contributor.""" + workflow_run = event.data["workflow_run"] + + if workflow_run["conclusion"] != "action_required": + return None + + pull_request = await utils.get_pr_for_workflow_run(gh, workflow_run=workflow_run) + if pull_request is None: + return None + + logger.info( + "Approving the workflow %d (PR #%d)", + workflow_run["id"], + pull_request["number"], + ) + await utils.approve_workflow_run(gh, workflow_run=workflow_run) diff --git a/algorithms_keeper/utils.py b/algorithms_keeper/utils.py index d4c9199..f65d5d7 100644 --- a/algorithms_keeper/utils.py +++ b/algorithms_keeper/utils.py @@ -12,6 +12,7 @@ maintain consistency throughout the module and improve readability in files that uses all the given functions. """ +import logging import urllib.parse from base64 import b64decode from dataclasses import dataclass @@ -21,6 +22,8 @@ from algorithms_keeper.api import GitHubAPI from algorithms_keeper.constants import PR_REVIEW_BODY +logger = logging.getLogger(__package__) + @dataclass(frozen=True) class File: @@ -271,3 +274,52 @@ async def get_pr_for_issue(gh: GitHubAPI, *, issue: Mapping[str, Any]) -> Any: async def update_pr(gh: GitHubAPI, *, pull_request: Mapping[str, Any]) -> Any: """Get the updated pull request object for the given pull request.""" return await gh.getitem(pull_request["url"], oauth_token=await gh.access_token) + + +async def get_pr_for_workflow_run( + gh: GitHubAPI, *, workflow_run: Mapping[str, Any] +) -> Optional[Any]: + """Return the pull request object for the given workflow run object. + + If the workflow run is not triggered by a pull request, then return None. + If the workflow run contains the pull request information, then return the first + pull request object, otherwise make a request to the GitHub API to get the pull + request associated with the workflow run. + """ + if workflow_run["event"] != "pull_request": + return None + + if workflow_run["pull_requests"]: + return await gh.getitem( + workflow_run["pull_requests"][0]["url"], + oauth_token=await gh.access_token, + ) + + # Format: "user/organization:branch" + head = ( + workflow_run["head_repository"]["owner"]["login"] + + ":" + + workflow_run["head_branch"] + ) + + prs = await gh.getitem( + workflow_run["repository"]["pulls_url"] + + "?" + + urllib.parse.urlencode({"state": "open", "head": head}), + oauth_token=await gh.access_token, + ) + if not prs: + logger.info("No open pull request found for %r", head) + return None + + # There can be only one open pull request for a given branch. + return prs[0] + + +async def approve_workflow_run( + gh: GitHubAPI, *, workflow_run: Mapping[str, Any] +) -> None: + """Approve the given workflow run.""" + await gh.post( + workflow_run["url"] + "/approve", data={}, oauth_token=await gh.access_token + )