Project

General

Profile

Task #1868 ยป esc-mentoring.py

Jan Iversen, 2016-04-15 06:15

 
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

    
    (1-1/1)