From 6a1ec6db998d6d6a12fc6cb7b7081334b9b30173 Mon Sep 17 00:00:00 2001 From: Sarah Edwards Date: Mon, 10 May 2021 16:31:23 -0700 Subject: [PATCH] Add script and workflow to add docs-reviewer reqests to the FR board (#19226) --- .../fr-add-docs-reviewers-requests.py | 232 ++++++++++++++++++ .github/allowed-actions.js | 1 + .github/workflows/docs-review-collect.yml | 35 +++ 3 files changed, 268 insertions(+) create mode 100644 .github/actions-scripts/fr-add-docs-reviewers-requests.py create mode 100644 .github/workflows/docs-review-collect.yml diff --git a/.github/actions-scripts/fr-add-docs-reviewers-requests.py b/.github/actions-scripts/fr-add-docs-reviewers-requests.py new file mode 100644 index 0000000000..0dfd87fcf1 --- /dev/null +++ b/.github/actions-scripts/fr-add-docs-reviewers-requests.py @@ -0,0 +1,232 @@ +# TODO: Convert to JavaScript for language consistency + +import json +import logging +import os +import requests + +# Constants +endpoint = 'https://api.github.com/graphql' + +# ID of the github/github repo +github_repo_id = "MDEwOlJlcG9zaXRvcnkz" + +# ID of the docs-reviewers team +docs_reviewers_id = "MDQ6VGVhbTQzMDMxMzk=" + +# ID of the "Docs content first responder" board +docs_project_id = "MDc6UHJvamVjdDQ1NzI0ODI=" + +# ID of the "OpenAPI review requests" column on the "Docs content first responder" board +docs_column_id = "PC_lAPNJr_OAEXFQs4A2OFq" + +# 100 is an educated guess of how many PRs are opened in a day on the github/github repo +# If we are missing PRs, either increase this number or increase the frequency at which this script is run +num_prs_to_search = 100 + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def find_open_prs_for_repo(repo_id: str, num_prs: int): + """Return data about a specified number of open PRs for a specified repo + + Arguments: + repo_id: The node ID of the repo to search + num_prs: The max number of PRs to return + + Returns: + Returns a JSON object of this structure: + { + "data": { + "node": { + "pullRequests": { + "nodes": [ + { + "id": str, + "isDraft": bool, + "reviewRequests": { + "nodes": [ + { + "requestedReviewer": { + "id": str + } + }... + ] + }, + "projectCards": { + "nodes": [ + { + "project": { + "id": str + } + }... + ] + } + }... + ] + } + } + } + } + """ + + query = """query ($repo_id: ID!, $num_prs: Int!) { + node(id: $repo_id) { + ... on Repository { + pullRequests(last: $num_prs, states: OPEN) { + nodes { + id + isDraft + reviewRequests(first: 10) { + nodes { + requestedReviewer { + ... on Team { + id + } + } + } + } + projectCards(first: 10) { + nodes { + project { + id + } + } + } + } + } + } + } + } + """ + + variables = { + "repo_id": github_repo_id, + "num_prs": num_prs + } + + response = requests.post( + endpoint, + json={'query': query, 'variables': variables}, + headers = {'Authorization': f"bearer {os.environ['TOKEN']}"} + ) + + response.raise_for_status() + + json_response = json.loads(response.text) + + if 'errors' in json_response: + raise RuntimeError(f'Error in GraphQL response: {json_response}') + + return json_response + +def add_prs_to_board(prs_to_add: list, column_id: str): + """Adds PRs to a column of a project board + + Arguments: + prs_to_add: A list of PR node IDs + column_id: The node ID of the column to add the PRs to + + Returns: + Nothing + """ + + logger.info(f"adding: {prs_to_add}") + + mutation = """mutation($pr_id: ID!, $column_id: ID!) { + addProjectCard(input:{contentId: $pr_id, projectColumnId: $column_id}) { + projectColumn { + name + } + } + }""" + + for pr_id in prs_to_add: + logger.info(f"Attempting to add {pr_id} to board") + + variables = { + "pr_id": pr_id, + "column_id": column_id + } + + response = requests.post( + endpoint, + json={'query': mutation, 'variables': variables}, + headers = {'Authorization': f"bearer {os.environ['TOKEN']}"} + ) + + json_response = json.loads(response.text) + + if 'errors' in json_response: + logger.info(f"GraphQL error when adding {pr_id}: {json_response}") + +def filter_prs(data, reviewer_id: str, project_id): + """Given data about the draft state, reviewers, and project boards for PRs, + return just the PRs that are: + - not draft + - are requesting a review for the specified team + - are not already on the specified project board + + Arguments: + data: A JSON object of this structure: + { + "data": { + "node": { + "pullRequests": { + "nodes": [ + { + "id": str, + "isDraft": bool, + "reviewRequests": { + "nodes": [ + { + "requestedReviewer": { + "id": str + } + }... + ] + }, + "projectCards": { + "nodes": [ + { + "project": { + "id": str + } + }... + ] + } + }... + ] + } + } + } + } + reviewer_id: The node ID of the reviewer to filter for + project_id: The project ID of the project to filter against + + Returns: + A list of node IDs of the PRs that met the requirements + """ + + pr_data = data['data']['node']['pullRequests']['nodes'] + + prs_to_add = [] + + for pr in pr_data: + if ( + not pr['isDraft'] and + reviewer_id in [req_rev['requestedReviewer']['id'] for req_rev in pr['reviewRequests']['nodes'] if req_rev['requestedReviewer']] and + project_id not in [proj_card['project']['id'] for proj_card in pr['projectCards']['nodes']] + ): + prs_to_add.append(pr['id']) + + return prs_to_add + +def main(): + query_data = find_open_prs_for_repo(github_repo_id, num_prs_to_search) + prs_to_add = filter_prs(query_data, docs_reviewers_id, docs_project_id) + add_prs_to_board(prs_to_add, docs_column_id) + +if __name__ == "__main__": + main() diff --git a/.github/allowed-actions.js b/.github/allowed-actions.js index e8cce1d06d..71a5037dd7 100644 --- a/.github/allowed-actions.js +++ b/.github/allowed-actions.js @@ -9,6 +9,7 @@ module.exports = [ "actions/github-script@626af12fe9a53dc2972b48385e7fe7dec79145c9", // v3.0.0 "actions/labeler@5f867a63be70efff62b767459b009290364495eb", // v2.2.0 "actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e", // v2.1.4 + "actions/setup-python@dc73133d4da04e56a135ae2246682783cc7c7cb6", // v2.2.2 "ruby/setup-ruby@fdcfbcf14ec9672f6f615cb9589a1bc5dd69d262", // v1.64.1 "actions/stale@9d6f46564a515a9ea11e7762ab3957ee58ca50da", // v3.0.16 "alex-page/github-project-automation-plus@fdb7991b72040d611e1123d2b75ff10eda9372c9", diff --git a/.github/workflows/docs-review-collect.yml b/.github/workflows/docs-review-collect.yml new file mode 100644 index 0000000000..255774a382 --- /dev/null +++ b/.github/workflows/docs-review-collect.yml @@ -0,0 +1,35 @@ +name: Add docs-reviewers request to FR board + +# **What it does**: Adds PRs in github/github that requested a review from docs-reviewers to the FR board +# **Why we have it**: To catch docs-reviewers requests in github/github +# **Who does it impact**: docs-content maintainers + +on: + workflow_dispatch: + schedule: + - cron: '25 4 * * *' + +jobs: + add-requests-to-board: + name: Add requests to board + runs-on: ubuntu-latest + + steps: + - name: Check out repo content + uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f + + - name: Set up Python 3.9 + uses: actions/setup-python@dc73133d4da04e56a135ae2246682783cc7c7cb6 + with: + python-version: '3.9' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install requests + + - name: Run script + run: | + python .github/actions-scripts/fr-add-docs-reviewers-requests.py + env: + TOKEN: ${{ secrets.DOCS_BOT }}