George Keishing | e7e9171 | 2021-09-03 11:28:44 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 2 | |
| 3 | r""" |
| 4 | Exports issues from a list of repositories to individual CSV files. |
| 5 | Uses basic authentication (GitHub username + password) to retrieve issues |
| 6 | from a repository that username has access to. Supports GitHub API v3. |
| 7 | """ |
| 8 | import argparse |
| 9 | import csv |
Sivas SRR | 9fe1b12 | 2017-03-05 22:29:28 -0600 | [diff] [blame] | 10 | import getpass |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 11 | |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 12 | import requests |
| 13 | |
| 14 | auth = None |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 15 | states = "all" |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 16 | |
| 17 | |
| 18 | def write_issues(response, csv_out): |
| 19 | r""" |
| 20 | Parses JSON response and writes to CSV. |
| 21 | """ |
vinaybs6 | 65dbeaa | 2019-09-19 01:46:49 -0500 | [diff] [blame] | 22 | print(response) |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 23 | if response.status_code != 200: |
| 24 | raise Exception(response.status_code) |
| 25 | for issue in response.json(): |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 26 | if "pull_request" not in issue: |
| 27 | labels = ", ".join([lable["name"] for lable in issue["labels"]]) |
Sivas SRR | b4eb9ac | 2017-03-16 01:13:03 -0500 | [diff] [blame] | 28 | |
Sivas SRR | 9fe1b12 | 2017-03-05 22:29:28 -0600 | [diff] [blame] | 29 | # Below lines to overcome "TypeError: 'NoneType' object has |
| 30 | # no attribute '__getitem__'" |
| 31 | |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 32 | close_date = issue.get("closed_at") |
Sivas SRR | b4eb9ac | 2017-03-16 01:13:03 -0500 | [diff] [blame] | 33 | if close_date: |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 34 | close_date = issue.get("closed_at").split("T")[0] |
Sivas SRR | b4eb9ac | 2017-03-16 01:13:03 -0500 | [diff] [blame] | 35 | |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 36 | assignee_resp = issue.get("assignees", "Not Assigned") |
Sivas SRR | 9fe1b12 | 2017-03-05 22:29:28 -0600 | [diff] [blame] | 37 | if assignee_resp: |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 38 | owners = ",".join( |
| 39 | [ |
| 40 | assignee_login["login"] |
| 41 | for assignee_login in assignee_resp |
| 42 | ] |
| 43 | ) |
Sivas SRR | 9fe1b12 | 2017-03-05 22:29:28 -0600 | [diff] [blame] | 44 | else: |
Sivas SRR | b4eb9ac | 2017-03-16 01:13:03 -0500 | [diff] [blame] | 45 | owners = "Not Assigned" |
| 46 | |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 47 | milestone_resp = issue.get("milestone", "Not Assigned") |
Sivas SRR | b4eb9ac | 2017-03-16 01:13:03 -0500 | [diff] [blame] | 48 | if milestone_resp: |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 49 | milestone_resp = milestone_resp["title"].encode("utf-8") |
Sivas SRR | 9fe1b12 | 2017-03-05 22:29:28 -0600 | [diff] [blame] | 50 | |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 51 | # Change the following line to write out additional fields |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 52 | csv_out.writerow( |
| 53 | [ |
| 54 | labels.encode("utf-8"), |
| 55 | issue.get("title").encode("utf-8"), |
| 56 | issue.get("state").encode("utf-8"), |
| 57 | issue.get("created_at").split("T")[0], |
| 58 | close_date, |
| 59 | issue.get("html_url").encode("utf-8"), |
| 60 | issue.get("user").get("login").encode("utf-8"), |
| 61 | owners, |
| 62 | milestone_resp, |
| 63 | ] |
| 64 | ) |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 65 | |
| 66 | |
Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 67 | def get_issues_from_github_to_csv(name, response): |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 68 | r""" |
| 69 | Requests issues from GitHub API and writes to CSV file. |
Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 70 | Description of argument(s): |
| 71 | name Name of the GitHub repository |
| 72 | response GitHub repository response |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 73 | """ |
vinaybs6 | 65dbeaa | 2019-09-19 01:46:49 -0500 | [diff] [blame] | 74 | print(name) |
| 75 | print(states) |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 76 | |
Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 77 | # Multiple requests are required if response is paged |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 78 | if "link" in response.headers: |
| 79 | pages = { |
| 80 | rel[6:-1]: url[url.index("<") + 1 : -1] |
| 81 | for url, rel in ( |
| 82 | link.split(";") for link in response.headers["link"].split(",") |
| 83 | ) |
| 84 | } |
| 85 | while "last" in pages and "next" in pages: |
| 86 | pages = { |
| 87 | rel[6:-1]: url[url.index("<") + 1 : -1] |
| 88 | for url, rel in ( |
| 89 | link.split(";") |
| 90 | for link in response.headers["link"].split(",") |
| 91 | ) |
| 92 | } |
| 93 | response = requests.get(pages["next"], auth=auth) |
Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 94 | write_issues(response, csv_out) |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 95 | if pages["next"] == pages["last"]: |
Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 96 | break |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 97 | |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 98 | |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 99 | parser = argparse.ArgumentParser( |
| 100 | description="Write GitHub repository issues to CSV file." |
| 101 | ) |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 102 | |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 103 | parser.add_argument( |
| 104 | "username", nargs="?", help="GitHub user name, formatted as 'username'" |
| 105 | ) |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 106 | |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 107 | parser.add_argument( |
| 108 | "repositories", |
| 109 | nargs="+", |
| 110 | help="Repository names, formatted as 'basereponame/repo'", |
| 111 | ) |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 112 | |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 113 | parser.add_argument( |
| 114 | "--all", action="store_true", help="Returns both open and closed issues." |
| 115 | ) |
Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 116 | |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 117 | args = parser.parse_args() |
| 118 | |
| 119 | if args.all: |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 120 | state = "all" |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 121 | |
Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 122 | username = args.username |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 123 | |
Sivas SRR | 9fe1b12 | 2017-03-05 22:29:28 -0600 | [diff] [blame] | 124 | password = getpass.getpass("Enter your GitHub Password:") |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 125 | |
| 126 | auth = (username, password) |
| 127 | |
Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 128 | # To set the csv filename |
| 129 | csvfilename = "" |
Sivas SRR | a464a7c | 2017-02-19 23:35:59 -0600 | [diff] [blame] | 130 | for repository in args.repositories: |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 131 | csvfilename_temp = "{}".format(repository.replace("/", "-")) |
Joy Onyerikwu | 004ad3c | 2018-06-11 16:29:56 -0500 | [diff] [blame] | 132 | csvfilename = csvfilename + csvfilename_temp |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 133 | csvfilename = csvfilename + "-issues.csv" |
| 134 | with open(csvfilename, "w") as csvfileout: |
Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 135 | csv_out = csv.writer(csvfileout) |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 136 | csv_out.writerow( |
| 137 | [ |
| 138 | "Labels", |
| 139 | "Title", |
| 140 | "State", |
| 141 | "Open Date", |
| 142 | "Close Date", |
| 143 | "URL", |
| 144 | "Author", |
| 145 | "Assignees", |
| 146 | "Milestone", |
| 147 | ] |
| 148 | ) |
Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 149 | for repository in args.repositories: |
Patrick Williams | 20f3871 | 2022-12-08 06:18:26 -0600 | [diff] [blame] | 150 | l_url = "https://api.github.com/repos/{}/issues?state={}" |
Sivas SRR | 0419fb0 | 2017-05-28 10:09:18 -0500 | [diff] [blame] | 151 | l_url = l_url.format(repository, states) |
| 152 | response = requests.get(l_url, auth=auth) |
| 153 | write_issues(response, csv_out) |
| 154 | get_issues_from_github_to_csv(repository, response) |
| 155 | csvfileout.close() |