Skip to content

Commit

Permalink
Implementation of workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
raulcd committed Feb 10, 2023
1 parent 27313fc commit a166340
Show file tree
Hide file tree
Showing 13 changed files with 2,407 additions and 10 deletions.
Binary file added .hypothesis/unicode_data/13.0.0/charmap.json.gz
Binary file not shown.
123 changes: 122 additions & 1 deletion dev/archery/archery/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
# specific language governing permissions and limitations
# under the License.

import enum
import os
import shlex
from pathlib import Path
from functools import partial
from functools import lru_cache, partial
import tempfile

import click
Expand All @@ -29,6 +30,10 @@
from .crossbow import Repo, Queue, Config, Target, Job, CommentReport


def cached_property(fn):
return property(lru_cache(maxsize=1)(fn))


class EventError(Exception):
pass

Expand Down Expand Up @@ -82,6 +87,122 @@ def parse_args(self, ctx, args):
group = partial(click.group, cls=Group)


LABEL_PREFIX = "awaiting"


@enum.unique
class PullRequestState(enum.Enum):
"""State of a pull request."""

review = f"{LABEL_PREFIX} review"
committer_review = f"{LABEL_PREFIX} comitter review"
changes = f"{LABEL_PREFIX} changes"
change_review = f"{LABEL_PREFIX} change review"
merge = f"{LABEL_PREFIX} merge"


COMMITTER_ROLES = {'OWNER', 'MEMBER'}


class PullRequestWorkflowBot:

def __init__(self, event_name, event_payload, token=None):
self.github = github.Github(token)
self.event_name = event_name
self.event_payload = event_payload

@cached_property
def pull(self):
if self.event_name == 'push':
return self._get_pr_for_commit()
else:
return self.repo.get_pull(self.event_payload['pull_request']['number'])

@cached_property
def repo(self):
return self.github.get_repo(self.event_payload['repository']['id'], lazy=True)

def handle(self):
current_state = None
try:
current_state = self.get_current_state()
except EventError:
# In case of error (more than one state) we clear state labels
# only possible if a label has been manually added.
self.clear_current_state()
new_state = self.get_target_state(current_state)
if current_state != new_state.value:
if current_state:
self.clear_current_state()
self.set_state(new_state)

def get_current_state(self):
states = [label.name for label in self.pull.get_labels()
if label.name.startswith(LABEL_PREFIX)]
if len(states) > 1:
raise EventError(f"PR cannot be on more than one states - {states}")
elif states:
return states[0]

def clear_current_state(self):
for label in self.pull.get_labels():
if label.name.startswith(LABEL_PREFIX):
self.pull.remove_from_labels(label)

def get_target_state(self, current_state):
if (self.event_name == "pull_request" and
self.event_payload['action'] == 'opened'):
if (self.event_payload['pull_request']['author_association'] in
COMMITTER_ROLES):
return PullRequestState.committer_review
else:
return PullRequestState.review
elif (self.event_name == "pull_request_review" and
self.event_payload["action"] == "submitted"):
review_state = self.event_payload["review"]["state"].lower()
is_committer_review = (self.event_payload['review']['author_association']
in COMMITTER_ROLES)
if not is_committer_review:
# Non-committer reviews cannot change state once committer has already
# reviewed and requested changes.
if current_state in (
PullRequestState.change_review.value,
PullRequestState.changes.value):
return PullRequestState(current_state)
else:
return PullRequestState.committer_review
if review_state == 'approved':
return PullRequestState.merge
elif review_state in ("changes_requested", "commented"):
return PullRequestState.changes
elif (self.event_name == "push" and
current_state == PullRequestState.changes.value):
return PullRequestState.change_review
# TODO: Test push on awaiting review!
# TODO: Implement case of PRs already opened
return PullRequestState(current_state)

def set_state(self, state):
self.pull.add_to_labels(state.value)

def _get_pr_for_commit(self):
"""Find the PR containing the specific commit hash."""
sha = self.event_payload['commits'][-1]['id']
# TODO: Validate this query is working on PR
prs_for_commit = self.github.search_issues(
"",
qualifiers={"type": "pr",
"repo": "raulcd/arrow",
"sha": sha}
)
pull = None
try:
pull = self.repo.get_pull(prs_for_commit[0].number)
except IndexError:
pass
return pull


class CommentBot:

def __init__(self, name, handler, token=None):
Expand Down
12 changes: 4 additions & 8 deletions dev/archery/archery/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,20 +781,16 @@ def integration(with_all=False, random_seed=12345, **args):
@click.option('--arrow-token', envvar='ARROW_GITHUB_TOKEN',
help='OAuth token for responding comment in the arrow repo')
def trigger_bot(event_name, event_payload, arrow_token):
from .bot import CommentBot, actions

class PullRequestWorkflowBot:
def handle(self, event_name, event_payload):
print(f"PullRequestWorkflowBot handle: {event_name} - {event_payload}")

from .bot import CommentBot, PullRequestWorkflowBot, actions

event_payload = json.loads(event_payload.read())
if 'comment' in event_name:
bot = CommentBot(name='github-actions', handler=actions, token=arrow_token)
bot.handle(event_name, event_payload)
else:
bot = PullRequestWorkflowBot()
bot.handle(event_name, event_payload)
# TODO: Make API for both classes more consistent
bot = PullRequestWorkflowBot(event_name, event_payload, token=arrow_token)
bot.handle()


@archery.group("linking")
Expand Down
80 changes: 80 additions & 0 deletions dev/archery/archery/tests/fixtures/commit-search.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"total_count": 1,
"incomplete_results": false,
"items": [
{
"url": "https://api.github.com/repos/raulcd/arrow/issues/74",
"repository_url": "https://api.github.com/repos/raulcd/arrow",
"labels_url": "https://api.github.com/repos/raulcd/arrow/issues/74/labels{/name}",
"comments_url": "https://api.github.com/repos/raulcd/arrow/issues/74/comments",
"events_url": "https://api.github.com/repos/raulcd/arrow/issues/74/events",
"html_url": "https://github.com/raulcd/arrow/pull/74",
"id": 1572348024,
"node_id": "PR_kwDOHHgT285JU-KQ",
"number": 74,
"title": "Pr workflow automation poc",
"user": {
"login": "raulcd",
"id": 639755,
"node_id": "MDQ6VXNlcjYzOTc1NQ==",
"avatar_url": "https://avatars.githubusercontent.com/u/639755?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/raulcd",
"html_url": "https://github.com/raulcd",
"followers_url": "https://api.github.com/users/raulcd/followers",
"following_url": "https://api.github.com/users/raulcd/following{/other_user}",
"gists_url": "https://api.github.com/users/raulcd/gists{/gist_id}",
"starred_url": "https://api.github.com/users/raulcd/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/raulcd/subscriptions",
"organizations_url": "https://api.github.com/users/raulcd/orgs",
"repos_url": "https://api.github.com/users/raulcd/repos",
"events_url": "https://api.github.com/users/raulcd/events{/privacy}",
"received_events_url": "https://api.github.com/users/raulcd/received_events",
"type": "User",
"site_admin": false
},
"labels": [

],
"state": "open",
"locked": false,
"assignee": null,
"assignees": [

],
"milestone": null,
"comments": 1,
"created_at": "2023-02-06T10:56:32Z",
"updated_at": "2023-02-10T11:30:31Z",
"closed_at": null,
"author_association": "OWNER",
"active_lock_reason": null,
"draft": true,
"pull_request": {
"url": "https://api.github.com/repos/raulcd/arrow/pulls/74",
"html_url": "https://github.com/raulcd/arrow/pull/74",
"diff_url": "https://github.com/raulcd/arrow/pull/74.diff",
"patch_url": "https://github.com/raulcd/arrow/pull/74.patch",
"merged_at": null
},
"body": "<!--\r\nThanks for opening a pull request!\r\nIf this is your first pull request you can find detailed information on how \r\nto contribute here:\r\n * [New Contributor's Guide](https://arrow.apache.org/docs/dev/developers/guide/step_by_step/pr_lifecycle.html#reviews-and-merge-of-the-pull-request)\r\n * [Contributing Overview](https://arrow.apache.org/docs/dev/developers/overview.html)\r\n\r\n\r\nIf this is not a [minor PR](https://github.com/apache/arrow/blob/master/CONTRIBUTING.md#Minor-Fixes). Could you open an issue for this pull request on GitHub? https://github.com/apache/arrow/issues/new/choose\r\n\r\nOpening GitHub issues ahead of time contributes to the [Openness](http://theapacheway.com/open/#:~:text=Openness%20allows%20new%20users%20the,must%20happen%20in%20the%20open.) of the Apache Arrow project.\r\n\r\nThen could you also rename the pull request title in the following format?\r\n\r\n GH-${GITHUB_ISSUE_ID}: [${COMPONENT}] ${SUMMARY}\r\n\r\nor\r\n\r\n MINOR: [${COMPONENT}] ${SUMMARY}\r\n\r\nIn the case of PARQUET issues on JIRA the title also supports:\r\n\r\n PARQUET-${JIRA_ISSUE_ID}: [${COMPONENT}] ${SUMMARY}\r\n\r\n-->\r\n\r\n### Rationale for this change\r\n\r\n<!--\r\n Why are you proposing this change? If this is already explained clearly in the issue then this section is not needed.\r\n Explaining clearly why changes are proposed helps reviewers understand your changes and offer better suggestions for fixes. \r\n-->\r\n\r\n### What changes are included in this PR?\r\n\r\n<!--\r\nThere is no need to duplicate the description in the issue here but it is sometimes worth providing a summary of the individual changes in this PR.\r\n-->\r\n\r\n### Are these changes tested?\r\n\r\n<!--\r\nWe typically require tests for all PRs in order to:\r\n1. Prevent the code from being accidentally broken by subsequent changes\r\n2. Serve as another way to document the expected behavior of the code\r\n\r\nIf tests are not included in your PR, please explain why (for example, are they covered by existing tests)?\r\n-->\r\n\r\n### Are there any user-facing changes?\r\n\r\n<!--\r\nIf there are user-facing changes then we may require documentation to be updated before approving the PR.\r\n-->\r\n\r\n<!--\r\nIf there are any breaking changes to public APIs, please uncomment the line below and explain which changes are breaking.\r\n-->\r\n<!-- **This PR includes breaking changes to public APIs.** -->\r\n\r\n<!--\r\nPlease uncomment the line below (and provide explanation) if the changes fix either (a) a security vulnerability, (b) a bug that caused incorrect or invalid data to be produced, or (c) a bug that causes a crash (even when the API contract is upheld). We use this to highlight fixes to issues that may affect users without their knowledge. For this reason, fixing bugs that cause errors don't count, since those are usually obvious.\r\n-->\r\n<!-- **This PR contains a \"Critical Fix\".** -->",
"reactions": {
"url": "https://api.github.com/repos/raulcd/arrow/issues/74/reactions",
"total_count": 0,
"+1": 0,
"-1": 0,
"laugh": 0,
"hooray": 0,
"confused": 0,
"heart": 0,
"rocket": 0,
"eyes": 0
},
"timeline_url": "https://api.github.com/repos/raulcd/arrow/issues/74/timeline",
"performed_via_github_app": null,
"state_reason": null,
"score": 1.0
}
]
}

Loading

0 comments on commit a166340

Please sign in to comment.