blob: 7426b75174fb9dbc0cba047c02f1fe8f3cad4e64 [file] [log] [blame]
George Keishinge7e91712021-09-03 11:28:44 -05001#!/usr/bin/env python3
Sivas SRRa464a7c2017-02-19 23:35:59 -06002
3r"""
4Exports issues from a list of repositories to individual CSV files.
5Uses basic authentication (GitHub username + password) to retrieve issues
6from a repository that username has access to. Supports GitHub API v3.
7"""
Sridevi Ramesh249fbcc2026-01-20 23:37:51 -06008
Sivas SRRa464a7c2017-02-19 23:35:59 -06009import argparse
10import csv
Sivas SRR9fe1b122017-03-05 22:29:28 -060011import getpass
Patrick Williams20f38712022-12-08 06:18:26 -060012
Sivas SRRa464a7c2017-02-19 23:35:59 -060013import requests
14
15auth = None
Patrick Williams20f38712022-12-08 06:18:26 -060016states = "all"
Sivas SRRa464a7c2017-02-19 23:35:59 -060017
18
19def write_issues(response, csv_out):
20 r"""
21 Parses JSON response and writes to CSV.
22 """
vinaybs665dbeaa2019-09-19 01:46:49 -050023 print(response)
Sivas SRRa464a7c2017-02-19 23:35:59 -060024 if response.status_code != 200:
25 raise Exception(response.status_code)
26 for issue in response.json():
Patrick Williams20f38712022-12-08 06:18:26 -060027 if "pull_request" not in issue:
George Keishing4ebb3282025-01-17 10:01:34 +053028 labels = ", ".join([label["name"] for label in issue["labels"]])
Sivas SRRb4eb9ac2017-03-16 01:13:03 -050029
Sivas SRR9fe1b122017-03-05 22:29:28 -060030 # Below lines to overcome "TypeError: 'NoneType' object has
31 # no attribute '__getitem__'"
32
Patrick Williams20f38712022-12-08 06:18:26 -060033 close_date = issue.get("closed_at")
Sivas SRRb4eb9ac2017-03-16 01:13:03 -050034 if close_date:
Patrick Williams20f38712022-12-08 06:18:26 -060035 close_date = issue.get("closed_at").split("T")[0]
Sivas SRRb4eb9ac2017-03-16 01:13:03 -050036
Patrick Williams20f38712022-12-08 06:18:26 -060037 assignee_resp = issue.get("assignees", "Not Assigned")
Sivas SRR9fe1b122017-03-05 22:29:28 -060038 if assignee_resp:
Patrick Williams20f38712022-12-08 06:18:26 -060039 owners = ",".join(
40 [
41 assignee_login["login"]
42 for assignee_login in assignee_resp
43 ]
44 )
Sivas SRR9fe1b122017-03-05 22:29:28 -060045 else:
Sivas SRRb4eb9ac2017-03-16 01:13:03 -050046 owners = "Not Assigned"
47
Patrick Williams20f38712022-12-08 06:18:26 -060048 milestone_resp = issue.get("milestone", "Not Assigned")
Sivas SRRb4eb9ac2017-03-16 01:13:03 -050049 if milestone_resp:
Patrick Williams20f38712022-12-08 06:18:26 -060050 milestone_resp = milestone_resp["title"].encode("utf-8")
Sivas SRR9fe1b122017-03-05 22:29:28 -060051
Sivas SRRa464a7c2017-02-19 23:35:59 -060052 # Change the following line to write out additional fields
Patrick Williams20f38712022-12-08 06:18:26 -060053 csv_out.writerow(
54 [
55 labels.encode("utf-8"),
56 issue.get("title").encode("utf-8"),
57 issue.get("state").encode("utf-8"),
58 issue.get("created_at").split("T")[0],
59 close_date,
60 issue.get("html_url").encode("utf-8"),
61 issue.get("user").get("login").encode("utf-8"),
62 owners,
63 milestone_resp,
64 ]
65 )
Sivas SRRa464a7c2017-02-19 23:35:59 -060066
67
Sivas SRR0419fb02017-05-28 10:09:18 -050068def get_issues_from_github_to_csv(name, response):
Sivas SRRa464a7c2017-02-19 23:35:59 -060069 r"""
70 Requests issues from GitHub API and writes to CSV file.
Sivas SRR0419fb02017-05-28 10:09:18 -050071 Description of argument(s):
72 name Name of the GitHub repository
73 response GitHub repository response
Sivas SRRa464a7c2017-02-19 23:35:59 -060074 """
vinaybs665dbeaa2019-09-19 01:46:49 -050075 print(name)
76 print(states)
Sivas SRRa464a7c2017-02-19 23:35:59 -060077
Sivas SRR0419fb02017-05-28 10:09:18 -050078 # Multiple requests are required if response is paged
Patrick Williams20f38712022-12-08 06:18:26 -060079 if "link" in response.headers:
80 pages = {
81 rel[6:-1]: url[url.index("<") + 1 : -1]
82 for url, rel in (
83 link.split(";") for link in response.headers["link"].split(",")
84 )
85 }
86 while "last" in pages and "next" in pages:
87 pages = {
88 rel[6:-1]: url[url.index("<") + 1 : -1]
89 for url, rel in (
90 link.split(";")
91 for link in response.headers["link"].split(",")
92 )
93 }
94 response = requests.get(pages["next"], auth=auth)
Sivas SRR0419fb02017-05-28 10:09:18 -050095 write_issues(response, csv_out)
Patrick Williams20f38712022-12-08 06:18:26 -060096 if pages["next"] == pages["last"]:
Sivas SRR0419fb02017-05-28 10:09:18 -050097 break
Sivas SRRa464a7c2017-02-19 23:35:59 -060098
Sivas SRRa464a7c2017-02-19 23:35:59 -060099
Patrick Williams20f38712022-12-08 06:18:26 -0600100parser = argparse.ArgumentParser(
101 description="Write GitHub repository issues to CSV file."
102)
Sivas SRRa464a7c2017-02-19 23:35:59 -0600103
Patrick Williams20f38712022-12-08 06:18:26 -0600104parser.add_argument(
105 "username", nargs="?", help="GitHub user name, formatted as 'username'"
106)
Sivas SRRa464a7c2017-02-19 23:35:59 -0600107
Patrick Williams20f38712022-12-08 06:18:26 -0600108parser.add_argument(
109 "repositories",
110 nargs="+",
111 help="Repository names, formatted as 'basereponame/repo'",
112)
Sivas SRRa464a7c2017-02-19 23:35:59 -0600113
Patrick Williams20f38712022-12-08 06:18:26 -0600114parser.add_argument(
115 "--all", action="store_true", help="Returns both open and closed issues."
116)
Sivas SRR0419fb02017-05-28 10:09:18 -0500117
Sivas SRRa464a7c2017-02-19 23:35:59 -0600118args = parser.parse_args()
119
120if args.all:
Patrick Williams20f38712022-12-08 06:18:26 -0600121 state = "all"
Sivas SRRa464a7c2017-02-19 23:35:59 -0600122
Sivas SRR0419fb02017-05-28 10:09:18 -0500123username = args.username
Sivas SRRa464a7c2017-02-19 23:35:59 -0600124
Sivas SRR9fe1b122017-03-05 22:29:28 -0600125password = getpass.getpass("Enter your GitHub Password:")
Sivas SRRa464a7c2017-02-19 23:35:59 -0600126
127auth = (username, password)
128
Sivas SRR0419fb02017-05-28 10:09:18 -0500129# To set the csv filename
130csvfilename = ""
Sivas SRRa464a7c2017-02-19 23:35:59 -0600131for repository in args.repositories:
Patrick Williams20f38712022-12-08 06:18:26 -0600132 csvfilename_temp = "{}".format(repository.replace("/", "-"))
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500133 csvfilename = csvfilename + csvfilename_temp
Patrick Williams20f38712022-12-08 06:18:26 -0600134csvfilename = csvfilename + "-issues.csv"
135with open(csvfilename, "w") as csvfileout:
Sivas SRR0419fb02017-05-28 10:09:18 -0500136 csv_out = csv.writer(csvfileout)
Patrick Williams20f38712022-12-08 06:18:26 -0600137 csv_out.writerow(
138 [
139 "Labels",
140 "Title",
141 "State",
142 "Open Date",
143 "Close Date",
144 "URL",
145 "Author",
146 "Assignees",
147 "Milestone",
148 ]
149 )
Sivas SRR0419fb02017-05-28 10:09:18 -0500150 for repository in args.repositories:
Patrick Williams20f38712022-12-08 06:18:26 -0600151 l_url = "https://api.github.com/repos/{}/issues?state={}"
Sivas SRR0419fb02017-05-28 10:09:18 -0500152 l_url = l_url.format(repository, states)
153 response = requests.get(l_url, auth=auth)
154 write_issues(response, csv_out)
155 get_issues_from_github_to_csv(repository, response)
156csvfileout.close()