-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathgithub-stats.py
executable file
·298 lines (219 loc) · 10.3 KB
/
github-stats.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#!/usr/bin/env python
import os, json, requests, sys, argparse, re
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
# Fill in GitHub Token
GITHUB_API_TOKEN_NAME = 'GITHUB_API_TOKEN'
GITHUB_ORG_DEFAULT = 'redhat-cop'
USER_AGENT= 'redhat-cop-stats'
DEFAULT_START_DATE_MONTH = '03'
DEFAULT_START_DATE_DAY = '01'
UNLABELED = 'unlabeled'
def handle_pagination_items(session, url):
# print "pagination called: {}".format(url)
pagination_request = session.get(url)
pagination_request.raise_for_status()
if 'next' in pagination_request.links and pagination_request.links['next']:
return pagination_request.json()['items'] + handle_pagination_items(session, pagination_request.links['next']['url'])
else:
return pagination_request.json()['items']
def generate_start_date():
today_date = datetime.now()
target_start_date = datetime.strptime("{0}-{1}-{02}".format(today_date.year, DEFAULT_START_DATE_MONTH, DEFAULT_START_DATE_DAY), "%Y-%m-%d")
if today_date.month < int(DEFAULT_START_DATE_MONTH):
target_start_date = target_start_date - relativedelta(years=1)
return target_start_date
def valid_date(s):
try:
return datetime.strptime(s, "%Y-%m-%d")
except ValueError:
msg = "Not a valid date: '{0}'.".format(s)
raise argparse.ArgumentTypeError(msg)
def encode_text(text):
if text:
return text.encode("utf-8")
return text
def get_org_repos(session, github_org):
return handle_pagination_items(session, "https://api.github.com/orgs/{0}/repos".format(github_org))
def get_org_members(session, github_org):
return handle_pagination_items(session, "https://api.github.com/orgs/{0}/members".format(github_org))
def get_pr(session, url):
pr_request = session.get(url)
pr_request.raise_for_status()
return pr_request.json()
def get_reviews(session, url):
pr_request = session.get("{0}/reviews".format(url))
pr_request.raise_for_status()
return pr_request.json()
def get_org_search_issues(session, start_date, github_org):
query = "https://api.github.com/search/issues?q=user:{}+updated:>={}+archived:false+state:closed&per_page=200".format(github_org, start_date.date().isoformat())
return handle_pagination_items(session, query)
def process_labels(labels):
label_dict = {}
if labels:
for x in labels.split(','):
label_dict[x.strip()] = None
return label_dict
def process_general_issues(issue, all_prs, label_prs):
author_login = issue['user']['login']
if author_login not in label_prs:
all_author_prs = []
else:
all_author_prs = label_prs[author_login]
all_author_prs.append(issue)
label_prs[author_login] = all_author_prs
return label_prs
def show_label(label_items_key, input_labels):
# Check if length of input labels is 0. If so, show all labels
if len(input_labels) == 0:
return True
# Loop through input labels
for key in input_labels:
# Check if label should be omitted
if key[-1] == "-":
if key[0:-1] == label_items_key:
return False
else:
return True
# Check if label is found
if key == label_items_key:
return True
return False
def repo_is_included(issue, repo_matcher, repo_excluder):
repo_name = issue['repository_url'].split('/')[-1]
repo_name_matches = True if re.match(repo_matcher, repo_name) != None else False
repo_name_excluded = True if None != repo_excluder and re.match(repo_excluder, repo_name) != None else False
#print "{0} - matches? {1}, excluded? {2}".format(repo_name, repo_name_matches, repo_name_excluded)
if repo_name_matches and repo_name_excluded == False:
return True
return False;
parser = argparse.ArgumentParser(description='Gather GitHub Statistics.')
parser.add_argument("-s","--start-date", help="The start date to query from", type=valid_date)
parser.add_argument("-r","--human-readable", action="store_true", help="Human readable format")
parser.add_argument("-u","--username", help="Username to query")
parser.add_argument("-l","--labels", help="Comma separated list to display. Add '-' at end of each label to negate")
parser.add_argument("-o","--organization", help="Organization name", default=GITHUB_ORG_DEFAULT)
parser.add_argument("-m","--repo-matcher", help="Repo Matcher", default=".+")
parser.add_argument("-x","--repo-excluder", help="Repo Excluder")
args = parser.parse_args()
start_date = args.start_date
username = args.username
input_labels = args.labels
repo_matcher = args.repo_matcher
repo_excluder = args.repo_excluder
github_org = args.organization
human_readable=(args.human_readable==True)
if start_date is None:
start_date = generate_start_date()
github_api_token = os.environ.get(GITHUB_API_TOKEN_NAME)
if not github_api_token:
print "Error: GitHub API Key is Required!"
sys.exit(1)
session = requests.Session()
session.headers = {
'Accept': 'application/vnd.github.v3+json',
'Authorization': 'Token {0}'.format(github_api_token),
'User-Agent': USER_AGENT
}
# Produce Label String
input_labels = process_labels(args.labels)
# Initialize Collection Statistics
general_prs = {}
closed_issues = {}
reviewed_prs = {}
org_search_issues = get_org_search_issues(session, start_date, github_org)
for issue in org_search_issues:
if not repo_is_included(issue, repo_matcher, repo_excluder):
continue
# print "{}:".format(issue['id'])
issue_author_id = issue['user']['id']
issue_author_login = issue['user']['login']
# Check if Issue is a Pull Request
if 'pull_request' in issue:
is_pull_request = True
pr_url = issue['pull_request']['url']
pr = get_pr(session, pr_url)
# Check if PR Has Been Merged
if not pr['merged_at']:
continue
# Check for Reviews
pr_reviews = get_reviews(session, pr_url)
for review in pr_reviews:
review_author_login = review['user']['login']
#Filter out unwanted review users
if username is not None and review_author_login != username:
continue
if review_author_login not in reviewed_prs:
review_author_prs = {}
else:
review_author_prs = reviewed_prs[review_author_login]
if issue['id'] not in review_author_prs:
review_author_prs[issue['id']] = issue
reviewed_prs[review_author_login] = review_author_prs
#Filter out unwanted pr users
if username is not None and issue_author_login != username:
continue
# Check if Label exists
if issue['labels']:
for label in issue['labels']:
label_name = label['name']
# Determine if Label Exists
if label_name not in general_prs:
label_issues = {}
else:
label_issues = general_prs[label_name]
general_prs[label_name] = process_general_issues(issue,general_prs, label_issues)
else:
if UNLABELED not in general_prs:
label_issues = {}
else:
label_issues = general_prs[UNLABELED]
general_prs[UNLABELED] = process_general_issues(issue,general_prs, label_issues)
else:
if issue['state'] == 'closed' and issue['assignee'] is not None:
closed_issue_author_id = issue['assignee']['id']
closed_issue_author_login = issue['assignee']['login']
#Filter out unwanted assignees
if username is not None and closed_issue_author_login != username:
continue
# Ignore Self Assigned Issues
if issue_author_id == closed_issue_author_id:
continue
if closed_issue_author_id not in closed_issues:
closed_issue_author = []
else:
closed_issue_author = closed_issues[closed_issue_author_id]
closed_issue_author.append(issue)
closed_issues[closed_issue_author_id] = closed_issue_author
print "=== Statistics for GitHub Organization '{0}' ====".format(github_org)
print "\n== General PR's ==\n"
for key, value in general_prs.iteritems():
# Determine whether to print out Label
if(show_label(key, input_labels)):
if (human_readable):
print "{}:".format(key)
for label_key, label_value in value.iteritems():
if (human_readable):
print " {0} - {1}".format(label_key, len(label_value))
for issue_value in label_value:
if (not human_readable):
print "Pull Requests/GH{0}/{1}/{2} [org={3}, board={4}, linkId={5}]".format(issue_value['id'], label_key, 1, issue_value['repository_url'].split('/')[-2], issue_value['repository_url'].split('/')[-1], issue_value['number'])
else:
print " {0} - {1}".format(encode_text(issue_value['repository_url'].split('/')[-1]), encode_text(issue_value['title']))
print "\n== Reviewed PR's ==\n"
for key, value in reviewed_prs.iteritems():
if (not human_readable):
for issue_key, issue_value in value.iteritems():
print "Reviewed Pull Requests/GH{0}/{1}/{2} [org={3}, board={4}, linkId={5}]".format(issue_value['id'], key, 1, issue_value['repository_url'].split('/')[-2], issue_value['repository_url'].split('/')[-1], issue_value['number'])
else:
print "{0} - {1}".format(key, len(value))
for issue_key, issue_value in value.iteritems():
print " {0} - {1}".format(encode_text(issue_value['repository_url'].split('/')[-1]), encode_text(issue_value['title']))
print "\n== Closed Issues ==\n"
for key, value in closed_issues.iteritems():
if (not human_readable):
print "Closed Issues/GH{0}/{1}/{2} [org={3}, board={4}, linkId={5}]".format(key, value[0]['assignee']['login'], len(value), value[0]['repository_url'].split('/')[-2], value[0]['repository_url'].split('/')[-1], value[0]['number'])
else:
print "{0} - {1}".format(value[0]['assignee']['login'], len(value))
for issue_value in value:
print " {0} - {1}".format(encode_text(value['repository_url'].split('/')[-1]), encode_text(value[0]['title']))