1
|
#!/usr/bin/env python3
|
2
|
#
|
3
|
# This file is part of the LibreOffice project.
|
4
|
#
|
5
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
6
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
7
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
8
|
#
|
9
|
|
10
|
import sys
|
11
|
import csv
|
12
|
import io
|
13
|
import datetime
|
14
|
import json
|
15
|
import xmltodict
|
16
|
from xml.etree.ElementTree import XML
|
17
|
from urllib.request import urlopen, URLError
|
18
|
|
19
|
|
20
|
|
21
|
def get_count_needsDevEval() :
|
22
|
url = 'https://bugs.documentfoundation.org/buglist.cgi?' \
|
23
|
'bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=VERIFIED&bug_status=NEEDINFO' \
|
24
|
'&columnlist=Cbug_id%2Cassigned_to%2Cbug_status%2Cshort_desc%2Cchangeddate%2Creporter%2Clongdescs.count%2Copendate%2Cstatus_whiteboard' \
|
25
|
'&keywords=needsDevEval%2C%20' \
|
26
|
'&keywords_type=allwords' \
|
27
|
'&query_format=advanced' \
|
28
|
'&resolution=---' \
|
29
|
'&ctype=csv' \
|
30
|
'&human=0'
|
31
|
try:
|
32
|
resp = urlopen(url)
|
33
|
except URLError:
|
34
|
sys.stderr.write('Error fetching {}'.format(url))
|
35
|
sys.exit(1)
|
36
|
xCSV = list(csv.reader(io.TextIOWrapper(resp)))
|
37
|
return len(xCSV) -1
|
38
|
|
39
|
|
40
|
|
41
|
def get_easyHacks() :
|
42
|
url = 'https://bugs.documentfoundation.org/buglist.cgi?' \
|
43
|
'bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=VERIFIED&bug_status=NEEDINFO' \
|
44
|
'&columnlist=Cbug_id%2Cassigned_to%2Cbug_status%2Cshort_desc%2Cchangeddate%2Creporter%2Clongdescs.count%2Copendate%2Cstatus_whiteboard' \
|
45
|
'&keywords=easyHack%2C%20' \
|
46
|
'&keywords_type=allwords' \
|
47
|
'&query_format=advanced' \
|
48
|
'&resolution=---' \
|
49
|
'&ctype=csv' \
|
50
|
'&human=0'
|
51
|
try:
|
52
|
resp = urlopen(url)
|
53
|
except URLError:
|
54
|
sys.stderr.write('Error fetching {}'.format(url))
|
55
|
sys.exit(1)
|
56
|
xCSV = list(csv.reader(io.TextIOWrapper(resp)))[1:]
|
57
|
resp.close()
|
58
|
xCSV.sort()
|
59
|
rawList = {}
|
60
|
for row in xCSV:
|
61
|
id = int(row[0])
|
62
|
if row[1] == 'libreoffice-bugs' :
|
63
|
assign = ''
|
64
|
else :
|
65
|
assign = row[1]
|
66
|
status = row[2]
|
67
|
if status == 'REOPENED' :
|
68
|
status = 'NEW'
|
69
|
rawList[id] = {'id' : id,
|
70
|
'assign' : assign,
|
71
|
'status' : status,
|
72
|
'desc' : row[3],
|
73
|
'change' : datetime.datetime.strptime(row[4].split(' ')[0], '%Y-%m-%d').date(),
|
74
|
'reporter' : row[5],
|
75
|
'comments' : int(row[6]),
|
76
|
'created' : datetime.datetime.strptime(row[7].split(' ')[0], '%Y-%m-%d').date(),
|
77
|
'whiteboard' : row[8]
|
78
|
}
|
79
|
return rawList
|
80
|
|
81
|
|
82
|
|
83
|
def get_gerrit(doNonCom) :
|
84
|
url = 'https://gerrit.libreoffice.org/changes/?' \
|
85
|
'q=status:open'
|
86
|
if (doNonCom) :
|
87
|
url = url + '+-ownerin:committer'
|
88
|
|
89
|
# Add needed fields
|
90
|
url = url + '&o=DETAILED_LABELS&o=MESSAGES&o=DETAILED_ACCOUNTS'
|
91
|
#url = url + '&o=code_review&o=reviewers&pp=0'
|
92
|
|
93
|
try:
|
94
|
resp = urlopen(url)
|
95
|
except URLError:
|
96
|
sys.stderr.write('Error fetching {}'.format(url))
|
97
|
sys.exit(1)
|
98
|
|
99
|
data = resp.read().decode('utf8')[5:]
|
100
|
rawList = json.loads(data)
|
101
|
resp.close()
|
102
|
for row in rawList :
|
103
|
row['updated'] = datetime.datetime.strptime(row['updated'].split(' ')[0], '%Y-%m-%d').date()
|
104
|
return rawList
|
105
|
|
106
|
|
107
|
|
108
|
def get_bug(id) :
|
109
|
url = 'https://bugs.documentfoundation.org/show_bug.cgi?ctype=xml&id=' + str(id)
|
110
|
try:
|
111
|
resp = urlopen(url)
|
112
|
except URLError:
|
113
|
sys.stderr.write('Error fetching {}'.format(url))
|
114
|
sys.exit(1)
|
115
|
bug = xmltodict.parse(resp.read())
|
116
|
resp.close()
|
117
|
return bug
|
118
|
|
119
|
|
120
|
|
121
|
def optimize_bug(bug_org) :
|
122
|
bug = bug_org['bugzilla']['bug']
|
123
|
del bug['bug_file_loc']
|
124
|
del bug['cclist_accessible']
|
125
|
del bug['classification']
|
126
|
del bug['classification_id']
|
127
|
del bug['comment_sort_order']
|
128
|
del bug['creation_ts']
|
129
|
del bug['delta_ts']
|
130
|
del bug['reporter_accessible']
|
131
|
del bug['resolution']
|
132
|
|
133
|
# collect info for new comments:
|
134
|
if 'reporter' not in bug :
|
135
|
newText = 'org_reporter: MISSING'
|
136
|
else :
|
137
|
if type(bug['reporter']) is str:
|
138
|
newText = 'org_reporter: ' + bug['reporter'] + '\n'
|
139
|
else :
|
140
|
newText = 'org_reporter: ' + bug['reporter']['@name'] + '/' + bug['reporter']['#text'] + '\n'
|
141
|
del bug['reporter']
|
142
|
|
143
|
for line in bug['long_desc'] :
|
144
|
if 'who' not in line or type(line) is str:
|
145
|
newText += 'who: UNKNOWN' + '\n' + line
|
146
|
else :
|
147
|
newText += 'who: ' + line['who']['@name'] + '/' + line['who']['#text']
|
148
|
bug['long_desc'] = []
|
149
|
bug['long_desc'].append({'thetext' : newText})
|
150
|
addAlso = 'https://issues.apache.org/ooo/show_bug.cgi?id='+bug['bug_id']
|
151
|
if 'see_also' not in bug :
|
152
|
bug['see_also'] = addAlso
|
153
|
elif not type(bug['see_also']) is list :
|
154
|
x = bug['see_also']
|
155
|
bug['see_also'] = [x, addAlso]
|
156
|
else :
|
157
|
bug['see_also'].append(addAlso)
|
158
|
return bug
|
159
|
|
160
|
|
161
|
|
162
|
def formatEasy(easyHack) :
|
163
|
return 'https://bugs.documentfoundation.org/show_bug.cgi?id={} mentor:{} -> "{}"'.format(easyHack['id'], easyHack['reporter'], easyHack['desc'])
|
164
|
|
165
|
|
166
|
|
167
|
def formatGerrit(patch) :
|
168
|
return 'https://gerrit.libreoffice.org/#/c/{}/ author:{} -> "{}"'.format(patch['_number'], patch['owner']['name'], patch['subject'])
|
169
|
|
170
|
|
171
|
|
172
|
def checkGerrit(checkType, patch, cDate=0, eDate=0) :
|
173
|
if checkType == 1 or checkType == 3:
|
174
|
# True, if there are no -1 and patch is mergeable
|
175
|
# 3 also checks on start/end date
|
176
|
# Optional Check no open comments
|
177
|
|
178
|
# date check (3 days old)
|
179
|
if checkType == 3 and (patch['updated'] < cDate or patch['updated'] > eDate) :
|
180
|
return False
|
181
|
|
182
|
# not mergeable
|
183
|
if not patch['mergeable'] :
|
184
|
return False
|
185
|
|
186
|
# review or verify -1
|
187
|
if 'labels' in patch and 'Code-Review' in patch['labels'] and 'all' in patch['labels']['Code-Review'] :
|
188
|
for chk in patch['labels']['Code-Review']['all'] :
|
189
|
if 'value' in chk and chk['value'] < 0 :
|
190
|
return False
|
191
|
if 'labels' in patch and 'Verified' in patch['labels'] and 'all' in patch['labels']['Verified'] :
|
192
|
for chk in patch['labels']['Verified']['all'] :
|
193
|
if 'value' in chk and chk['value'] < 0 :
|
194
|
return False
|
195
|
return True
|
196
|
elif checkType == 2 :
|
197
|
# True if there are reviewer
|
198
|
if 'labels' in patch and 'Code-Review' in patch['labels'] and 'all' in patch['labels']['Code-Review'] :
|
199
|
for chk in patch['labels']['Code-Review']['all'] :
|
200
|
name = chk['name']
|
201
|
if not name == 'Jenkins' and not name == patch['owner'] :
|
202
|
return True
|
203
|
return False
|
204
|
elif checkType == 4 :
|
205
|
# True if merge conflict and no jani comment
|
206
|
return False
|
207
|
elif checkType == 5 :
|
208
|
# true if last change is older than startDate
|
209
|
if patch['updated'] <= cDate :
|
210
|
return True
|
211
|
return False
|
212
|
return False
|
213
|
|
214
|
|
215
|
|
216
|
def ESC_report(easyHacks, gerritOpen, gerritContributor, needsDevEval) :
|
217
|
# prepare to count easyHacks, and list special status, new hacks (7 days)
|
218
|
xTot = len(easyHacks)
|
219
|
xAssign = 0
|
220
|
xOpen = 0
|
221
|
xInfo = 0
|
222
|
xComm = 0
|
223
|
xRevi = 0
|
224
|
pNew = []
|
225
|
pInfo = []
|
226
|
cDate = datetime.date.today() - datetime.timedelta(days=8)
|
227
|
for key, row in easyHacks.items():
|
228
|
# Calculate type of status
|
229
|
status = row['status']
|
230
|
if status == 'ASSIGNED' :
|
231
|
xAssign += 1
|
232
|
elif status == 'NEEDINFO' :
|
233
|
xInfo += 1
|
234
|
pInfo.append(row)
|
235
|
elif status == 'NEW' :
|
236
|
xOpen += 1
|
237
|
if row['comments'] >= 5 :
|
238
|
xComm += 1
|
239
|
if row['whiteboard'] == 'ToBeReviewed':
|
240
|
xRevi += 1
|
241
|
|
242
|
if row['created'] >= cDate :
|
243
|
pNew.append(row)
|
244
|
print('* Easy Hacks (JanI)')
|
245
|
print(' + total {}: {} not assigned, {} Assigned to contributors, {} need info'.format(xTot, xOpen, xAssign, xInfo))
|
246
|
print(' + needsDevEval {} needs to be evaluated'.format(needsDevEval))
|
247
|
print(' + cleanup: {} has more than 4 comments, {} needs to be reviewed'.format(xComm, xRevi))
|
248
|
print(' + new last 8 days:')
|
249
|
for row in pNew :
|
250
|
print(' ', end='')
|
251
|
print(formatEasy(row))
|
252
|
print (' + <text>')
|
253
|
print('\n\n')
|
254
|
|
255
|
xTot = len(gerritOpen)
|
256
|
xRevi = 0
|
257
|
for row in gerritOpen:
|
258
|
# can be merged (depending comments)
|
259
|
if checkGerrit(1, row) :
|
260
|
xRevi += 1
|
261
|
print ('* Mentoring Update (JanI)')
|
262
|
print (' + total: {} open gerrit patches of which {} are mergeable'.format(xTot, xRevi))
|
263
|
xTot = len(gerritContributor)
|
264
|
xRevi = 0
|
265
|
for row in gerritContributor:
|
266
|
# can be merged (depending comments)
|
267
|
if checkGerrit(1, row) :
|
268
|
xRevi += 1
|
269
|
print (' + contributors: {} open gerrit patches of which {} are mergeable'.format(xTot, xRevi))
|
270
|
print (' + <text>')
|
271
|
|
272
|
|
273
|
|
274
|
def DAY_report(runMsg, easyHacks, gerritOpen, gerritContributor) :
|
275
|
# Day report looks 8 days back
|
276
|
cDate = datetime.date.today() - datetime.timedelta(days=8)
|
277
|
|
278
|
print("*** new easyHacks (verify who created it):")
|
279
|
for key, row in easyHacks.items():
|
280
|
if row['created'] >= cDate :
|
281
|
print(' ', end='')
|
282
|
print(formatEasy(row))
|
283
|
|
284
|
print("\n\n*** Gerrit mangler reviewer:")
|
285
|
for row in gerritContributor:
|
286
|
if not checkGerrit(2, row) :
|
287
|
print(' ', end='')
|
288
|
print(formatGerrit(row))
|
289
|
|
290
|
# Month report looks 30 days back
|
291
|
cDate = datetime.date.today() - datetime.timedelta(days=30)
|
292
|
|
293
|
print("\n\n*** Gerrit to abandon:")
|
294
|
for row in gerritContributor:
|
295
|
# can be merged (depending comments)
|
296
|
if checkGerrit(5, row, cDate=cDate) :
|
297
|
print(' ', end='')
|
298
|
print(formatGerrit(row))
|
299
|
|
300
|
print('\n\n*** assigned easyHacks, no movement')
|
301
|
for key, row in easyHacks.items():
|
302
|
if row['change'] <= cDate and row['status'] == 'ASSIGNED':
|
303
|
print(' ', end='')
|
304
|
print(formatEasy(row))
|
305
|
|
306
|
print("\n\ne*** asyHacks needing review due to whiteboard:")
|
307
|
bugs = []
|
308
|
for key, row in easyHacks.items():
|
309
|
if row['comments'] < 5 and 'ToBeReviewed' in row['whiteboard'] :
|
310
|
print(' ', end='')
|
311
|
print(formatEasy(row))
|
312
|
|
313
|
if runMsg == "dump" :
|
314
|
print("\n\n*** easyHacks with more than 5 comments:")
|
315
|
bugs = []
|
316
|
for key, row in easyHacks.items():
|
317
|
if row['comments'] >= 5 :
|
318
|
bugs.append(optimize_bug(get_bug(key)))
|
319
|
fp = open('bz_comments.json', 'w')
|
320
|
json.dump(bugs, f, ensure_ascii=False, indent=4, sort_keys=True)
|
321
|
fp.close()
|
322
|
xTot = len(bugs)
|
323
|
print(' wrote {} entries to bz_comments.json'.format(xTot))
|
324
|
|
325
|
|
326
|
|
327
|
|
328
|
if __name__ == '__main__':
|
329
|
# check command line options
|
330
|
doESC = True
|
331
|
if len(sys.argv) > 1 :
|
332
|
if sys.argv[1] != 'esc' :
|
333
|
doESC = False
|
334
|
|
335
|
# get data from bugzilla and gerrit
|
336
|
easyHacks = get_easyHacks()
|
337
|
needsDevEval = get_count_needsDevEval()
|
338
|
gerritOpen = get_gerrit(False)
|
339
|
gerritContributor = get_gerrit(True)
|
340
|
|
341
|
if doESC :
|
342
|
ESC_report(easyHacks, gerritOpen, gerritContributor, needsDevEval)
|
343
|
else :
|
344
|
DAY_report(sys.argv[1],easyHacks, gerritOpen, gerritContributor)
|
345
|
print('\n\nend of report')
|
346
|
|