13
13
import urllib
14
14
import json
15
15
import logging
16
+ import requests
17
+ import csv
18
+ from StringIO import StringIO
16
19
from datetime import datetime
17
20
from pprint import pprint
18
21
40
43
GITHUB_USER = 'cmu-db'
41
44
GITHUB_REPO = 'peloton'
42
45
43
- CACHE_FILEPATH = "/tmp/%d. github-cache"
46
+ CACHE_FILEPATH = "/tmp/github-cache/%s. cache"
44
47
45
-
48
+
46
49
47
50
# ==============================================
48
51
# pr_format
@@ -54,13 +57,59 @@ def pr_format(pr):
54
57
return (ret )
55
58
## DEF
56
59
60
+ # ==============================================
61
+ # get_current_czar
62
+ # ==============================================
63
+ def get_current_czar (url , cache = False ):
64
+
65
+ data = None
66
+ cache_file = CACHE_FILEPATH % "schedule"
67
+ if cache and os .path .exists (cache_file ):
68
+ LOG .debug ("Cached Schedule '%s'" % cache_file )
69
+ with open (cache_file , "r" ) as fd :
70
+ data = fd .read ()
71
+ else :
72
+ LOG .debug ("Downloading schedule from %s" % url )
73
+ response = requests .get (url )
74
+ assert response .status_code == 200 , \
75
+ "Failed to download schedule '%s'" % url
76
+ data = response .content
77
+ LOG .debug ("Creating schedule cached file '%s'" % cache_file )
78
+ with open (cache_file , "w" ) as fd :
79
+ fd .write (data )
80
+ ## IF
81
+
82
+ # The CSV file should have two columns:
83
+ # (1) The date "Month Year"
84
+ # (2) Email address
85
+ current_date = datetime .now ().strftime ("%B %Y" )
86
+ current_czar = None
87
+ first = True
88
+ for row in csv .reader (StringIO (data )):
89
+ if first :
90
+ first = False
91
+ continue
92
+ if row [0 ].strip () == current_date :
93
+ current_czar = row [1 ].strip ()
94
+ break
95
+ ## FOR
96
+ if current_czar is None :
97
+ raise Exception ("Failed to find current assignment for '%s'" % current_date )
98
+
99
+ LOG .debug ("Current Czar ('%s'): %s" % (current_date , current_czar ))
100
+ return (current_czar )
101
+ ## DEF
102
+
103
+
57
104
# ==============================================
58
105
# main
59
106
# ==============================================
60
107
if __name__ == '__main__' :
61
108
aparser = argparse .ArgumentParser ()
62
109
aparser .add_argument ('token' , type = str , help = 'Github API Token' )
63
- aparser .add_argument ('--send' , type = str , help = 'Send status report to given email' )
110
+ aparser .add_argument ('schedule' , type = str , help = 'Schedule CSV URL' )
111
+ aparser .add_argument ('--send' , action = 'store_true' , help = 'Send status report to current czar' )
112
+ aparser .add_argument ('--send-to' , type = str , help = 'Send status report to given email' )
64
113
aparser .add_argument ('--gmail-user' , type = str , help = 'Gmail username' )
65
114
aparser .add_argument ('--gmail-pass' , type = str ,help = 'Gmail password' )
66
115
aparser .add_argument ("--cache" , action = 'store_true' , help = "Enable caching of raw Github requests (for development only)" )
@@ -72,6 +121,20 @@ def pr_format(pr):
72
121
if args ['debug' ]:
73
122
LOG .setLevel (logging .DEBUG )
74
123
124
+ if args ['cache' ]:
125
+ cache_dir = os .path .dirname (CACHE_FILEPATH )
126
+ if not os .path .exists (cache_dir ):
127
+ LOG .debug ("Creating cache directory '%s'" % cache_dir )
128
+ os .makedirs (cache_dir )
129
+ ## IF
130
+
131
+
132
+ ## ----------------------------------------------
133
+
134
+
135
+ # Download the schedule CSV and figure out who is the czar this month
136
+ current_czar = get_current_czar (args ['schedule' ], cache = args ['cache' ])
137
+
75
138
## ----------------------------------------------
76
139
77
140
gh = Github (token = args ['token' ])
@@ -104,7 +167,7 @@ def pr_format(pr):
104
167
105
168
106
169
# Get review comments for this issue
107
- cache_reviews = CACHE_FILEPATH % pr .number
170
+ cache_reviews = CACHE_FILEPATH % str ( pr .number )
108
171
data = None
109
172
if 'cache' in args and args ['cache' ] and os .path .exists (cache_reviews ):
110
173
LOG .debug ("CACHED REVIEW COMMENTS '%s'" % cache_reviews )
@@ -133,7 +196,7 @@ def pr_format(pr):
133
196
all_recieved_reviews = { }
134
197
all_reviewers = { }
135
198
for pr , labels , reviews in open_pulls .values ():
136
- LOG .debug ("Pull Request #%d - LABELS: %s" , pr .number , labels )
199
+ LOG .debug ("PR #%d - LABELS: %s" , pr .number , labels )
137
200
138
201
# Step 1: Get the list of 'ready_for_review' PRs that do not have
139
202
# an assigned reviewer
@@ -171,11 +234,11 @@ def pr_format(pr):
171
234
status ['ReviewMissing' ].append (pr .number )
172
235
173
236
174
- all_reviewers [pr .number ] = reviewers
175
- all_recieved_reviews [pr .number ] = recieved_reviews
237
+ all_reviewers [pr .number ] = map ( str , reviewers )
238
+ all_recieved_reviews [pr .number ] = map ( str , recieved_reviews )
176
239
177
- LOG .debug ("REVIEWERS: %s" , "," .join (reviewers ))
178
- LOG .debug ("RECEIVED REVIEWS: %s" , "," .join (recieved_reviews ))
240
+ LOG .debug ("PR #%d - REVIEWERS: %s" , pr . number , "," .join (reviewers ))
241
+ LOG .debug ("PR #%d - RECEIVED REVIEWS: %s" , pr . number , "," .join (recieved_reviews ))
179
242
# IF
180
243
181
244
# Step 4: Mark any PRs without labels
@@ -190,7 +253,7 @@ def pr_format(pr):
190
253
## NO LABELS
191
254
content += "*NO LABELS*\n \n "
192
255
if len (status ['NoLabels' ]) == 0 :
193
- content += "** NONE* *\n \n "
256
+ content += "*NONE*\n \n "
194
257
else :
195
258
for pr_num in sorted (status ['NoLabels' ]):
196
259
content += pr_format (open_pulls [pr_num ][0 ]) + "\n \n "
@@ -199,7 +262,7 @@ def pr_format(pr):
199
262
## READY TO MERGE
200
263
content += "*READY TO MERGE*\n \n "
201
264
if len (status ['ReadyToMerge' ]) == 0 :
202
- content += "** NONE* *\n \n "
265
+ content += "*NONE*\n \n "
203
266
else :
204
267
for pr_num in sorted (status ['ReadyToMerge' ]):
205
268
content += pr_format (open_pulls [pr_num ][0 ]) + "\n \n "
@@ -208,29 +271,39 @@ def pr_format(pr):
208
271
## READY FOR REVIEW, NO ASSIGNMENT
209
272
content += "*READY FOR REVIEW WITHOUT ASSIGNMENT*\n \n "
210
273
if len (status ['NeedReviewers' ]) == 0 :
211
- content += "** NONE* *\n \n "
274
+ content += "*NONE*\n \n "
212
275
else :
213
276
for pr_num in sorted (status ['NeedReviewers' ]):
214
277
content += pr_format (open_pulls [pr_num ][0 ]) + "\n \n "
215
278
content += linebreak
216
279
217
-
218
280
## MISSING REVIEWS
219
281
content += "*WAITING FOR REVIEW*\n \n "
220
282
if len (status ['ReviewMissing' ]) == 0 :
221
- content += "** NONE* *\n \n "
283
+ content += "*NONE*\n \n "
222
284
else :
223
285
for pr_num in sorted (status ['ReviewMissing' ]):
224
- content += pr_format (open_pulls [pr_num ][0 ]) + "\n \n "
225
- content += " Assigned Reviewers: %s\n " % (list (all_recieved_reviews [pr_num ]))
286
+ if len (all_reviewers [pr_num ]) == 0 :
287
+ all_reviewers [pr_num ] = [ '*NONE*' ]
288
+ if len (all_recieved_reviews [pr_num ]) == 0 :
289
+ all_recieved_reviews [pr_num ] = [ '*NONE*' ]
290
+
291
+ content += pr_format (open_pulls [pr_num ][0 ]) + "\n "
292
+ content += " Assigned Reviewers: %s\n " % ("," .join (all_reviewers [pr_num ]))
293
+ content += " Reviews Provided: %s\n \n " % ("," .join (all_recieved_reviews [pr_num ]))
226
294
#content += linebreak
227
295
228
296
229
297
if "send" in args and args ["send" ]:
298
+ send_to = current_czar
299
+ if "send_to" in args and args ["send_to" ]:
300
+ send_to = args ["send_to" ]
301
+
302
+ LOG .debug ("Sending status report to '%s'" % send_to )
230
303
msg = MIMEText (content )
231
304
msg ['Subject' ] = "%s PR Status Report (%s)" % (GITHUB_REPO .title (), datetime .now ().strftime ("%Y-%m-%d" ))
232
305
msg ['From' ] = EMAIL_FROM
233
- msg ['To' ] = args [ "send" ]
306
+ msg ['To' ] = send_to
234
307
msg [
'Reply-To' ]
= "[email protected] "
235
308
236
309
server_ssl = smtplib .SMTP_SSL ("smtp.gmail.com" , 465 )
@@ -241,7 +314,7 @@ def pr_format(pr):
241
314
#server_ssl.quit()
242
315
server_ssl .close ()
243
316
244
- LOG .info ("Status email sent to '%s'" % args [ "send" ] )
317
+ LOG .info ("Status email sent to '%s'" % send_to )
245
318
else :
246
319
print content
247
320
## IF
0 commit comments