Skip to content

Instantly share code, notes, and snippets.

@victorlin
Last active March 24, 2022 09:28
Show Gist options
  • Save victorlin/8ce4fe5f7e9ab6c2aa6210f373a7857d to your computer and use it in GitHub Desktop.
Save victorlin/8ce4fe5f7e9ab6c2aa6210f373a7857d to your computer and use it in GitHub Desktop.
Helper functions to add all issues of a repository to a GitHub project (beta), with example usage at bottom of script. For https://github.com/github/feedback/discussions/6765
import requests
import json
GH_GRAPHQL_URL = 'https://api.github.com/graphql'
TOKEN = '' # add your PAT with read:org and write:org scope
def get_json_result(query):
headers={'Authorization': f'token {TOKEN}'}
r = requests.post(GH_GRAPHQL_URL, json={'query': query}, headers=headers)
if r.status_code != 200:
raise Exception()
return json.loads(r.text)
def add_issue_to_project(issue_id, project_id):
mutation = f"""mutation {{
addProjectNextItem(input: {{projectId: "{project_id}" contentId: "{issue_id}"}}) {{
projectNextItem {{
id
}}
}}
}}
"""
return get_json_result(mutation)
def get_project_id(org_name, project_number):
query = f"""query {{
organization(login: "{org_name}") {{
projectNext(number: {project_number}) {{
id
}}
}}
}}
"""
result = get_json_result(query)
return result['data']['organization']['projectNext']['id']
def get_issue_ids(repo_name, owner_name, n=20):
query = f"""query {{
repository(owner: "{owner_name}", name: "{repo_name}") {{
openIssues: issues(first: {n}) {{
edges {{
node {{
number
id
}}
}}
}}
}}
}}
"""
result = get_json_result(query)
return [item['node']['id'] for item in result['data']['repository']['openIssues']['edges']]
# example: add 30 issues from serratus-bio/serratus.io to serratus-bio/projects/2.
project_id = get_project_id(org_name="serratus-bio", project_number=2)
issue_ids = get_issue_ids(repo_name="serratus.io", owner_name="serratus-bio", n=30)
for issue_id in issue_ids:
add_issue_to_project(issue_id, project_id)
@cschreib
Copy link

Thanks for this! The code needs changing if using a private personal project rather than an organization project.

  • get_project_id should read user instead of organization on line 28.
  • the personal access token also needs the repo permission (access private repos; apparently needed)

The maximum value for n on line 57 is 100 (can't get more than a hundred issues at once). Weirdly, even though the code says openIssues, it will get closed issues too... Anyway, this only gets the first "page" of results; the other issues after the first n are ignored, and the code doesn't allow fetching them. I don't know enough about the GraphQL API to know how to do this.

@cschreib
Copy link

Ok, to get pagination working, you need to add the cursor field to the query for issues, then read the value of cursor for the last returned issue, and set this to the after parameter of the issues query.

def get_issue_ids(repo_name, owner_name, n=100, next_cursor=None):
    after_query = ''
    if next_cursor is not None:
        after_query = f'after: "{next_cursor}"'

    query = f"""query {{
        repository(owner: "{owner_name}", name: "{repo_name}") {{
            openIssues: issues(first: {n} {after_query}) {{
                edges {{
                    node {{
                        number
                        id
                    }}
                    cursor
                }}
            }}
        }}
    }}
    """

    result = get_json_result(query)
    edges = result['data']['repository']['openIssues']['edges']
    if len(edges) == 0:
        return [], None

    return [item['node']['id'] for item in edges], edges[-1]['cursor']

def get_all_issue_ids(repo_name, owner_name):
    issues = []
    next_cursor = None
    while True:
        new_issues, next_cursor = get_issue_ids(repo_name, owner_name, next_cursor=next_cursor)
        if len(new_issues) == 0:
            break

        issues.append(new_issues)

    return issues

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment