diff --git a/.github/workflows/pr_review_bot.yaml b/.github/workflows/pr_review_bot.yaml new file mode 100644 index 00000000000000..97594f253fbd27 --- /dev/null +++ b/.github/workflows/pr_review_bot.yaml @@ -0,0 +1,30 @@ +name: Bot Review + +on: + pull_request: + types: + - opened + - edited +jobs: + bot_review: + runs-on: ubuntu-latest + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set Up Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Install Dependencies + run: pip install PyGithub requests + + - name: Run Bot Review + run: python scripts/review_bot.py "${{ github.event_path }}" + + diff --git a/scripts/review_bot.py b/scripts/review_bot.py new file mode 100755 index 00000000000000..80c673ab2bb768 --- /dev/null +++ b/scripts/review_bot.py @@ -0,0 +1,108 @@ + +import sys +import json +import re +import os +import requests +from github import Github + +BOT_REVIEW_LABEL = "bot-review" +GRAPHQL_ENDPOINT = "https://api.github.com/graphql" + +# Read the pr event file, which is passed in as first arg +def read_pr_event(): + with open(sys.argv[1], 'r') as file: + event_payload = json.load(file) + return event_payload + +# Read template file +def read_template_file(): + template_file_path = ".github/pull_request_template.md" + with open(template_file_path, 'r') as template_file: + combined_templates = template_file.read() + return combined_templates + +# Separate out each template +template_separator = re.compile(r"", re.DOTALL) +def separate_templates(combined_templates): + matches = template_separator.findall(combined_templates) + for (name,content) in matches: + yield name,content + +# Find fields in a template or pull request. They look like **field name** +field_finder = re.compile(r"\*{2}(.+?)\*{2}") +def find_field_set(content): + return set(field_finder.findall(content)) + +# use GraphQL to get pull request id +def get_pull_request_graphql_id(accessToken,name,number): + headers = {"Authorization": f"Bearer {accessToken}"} + owner,name = name.split('/') + query = f"""query {{ + repository(owner:"{owner}", name:"{name}"){{ + pullRequest(number: {number}) {{ + id + }} + }} + }}""" + r = requests.post(GRAPHQL_ENDPOINT, json={"query": query}, headers=headers) + return r.json()["data"]["repository"]["pullRequest"]["id"] + +# use GraphQL to set pull request as draft +def set_pr_draft(accessToken,id): + headers = {"Authorization": f"Bearer {accessToken}"} + query = f"""mutation {{ + convertPullRequestToDraft(input:{{pullRequestId:"{id}"}}){{ + pullRequest {{ + id + }} + }} + }}""" + requests.post(GRAPHQL_ENDPOINT, json={"query": query}, headers=headers) + +# use GraphQL to set pull request as ready +def set_pr_ready(accessToken,id): + headers = {"Authorization": f"Bearer {accessToken}"} + query = f"""mutation {{ + markPullRequestReadyForReview(input:{{pullRequestId:"{id}"}}){{ + pullRequest {{ + id + }} + }} + }}""" + requests.post(GRAPHQL_ENDPOINT, json={"query": query}, headers=headers) + +if __name__ == "__main__": + accessToken = os.environ['GITHUB_TOKEN'] + g = Github(accessToken) + + pr_event = read_pr_event() + repo_name = pr_event['repository']['full_name'] + pr_number = pr_event['pull_request']['number'] + pr_id = get_pull_request_graphql_id(accessToken,repo_name,pr_number) + pr_body = pr_event["pull_request"]["body"] + pr = g.get_repo(repo_name).get_pull(pr_number) + pr.add_to_labels(BOT_REVIEW_LABEL) + set_pr_draft(accessToken,pr_id) + + fields_in_pr_body = find_field_set(pr_body) + combined_templates = read_template_file() + templates = separate_templates(combined_templates) + + # Calculate which templates match + possible_template_matches = [] + for template_name,template_content in templates: + required_fields = find_field_set(template_content) + if fields_in_pr_body.issuperset(required_fields): + possible_template_matches.append(template_name) + + # Return results + if len(possible_template_matches) > 0: + print("PR matches template(s): ",", ".join(possible_template_matches)) + pr.remove_from_labels(BOT_REVIEW_LABEL) + set_pr_ready(accessToken,pr_id) + sys.exit(0) # Pass + else: + print("PR does not match any known templates") + sys.exit(1) # Faill +