From a5c3683f158d7a41d070062c5f21a66a6b4047ad Mon Sep 17 00:00:00 2001 From: Russell Bryant Date: Wed, 4 Dec 2013 17:51:56 -0500 Subject: [PATCH] Remove text blobs from CSV output Only output the descriptive text in the plain text writer, and not the CSV writer. This should make the CSV output more usable by something wanting to automatically import it. Change-Id: I56ccc4bdaa90069e81608f8f08ce1fd149de141e --- reviewstats/cmd/reviewers.py | 197 ++++++++++++++++++----------------- 1 file changed, 103 insertions(+), 94 deletions(-) diff --git a/reviewstats/cmd/reviewers.py b/reviewstats/cmd/reviewers.py index ca73d08..1710617 100755 --- a/reviewstats/cmd/reviewers.py +++ b/reviewstats/cmd/reviewers.py @@ -102,7 +102,8 @@ def process_patchset(project, patchset, reviewers, ts): reviewers[reviewer]['disagreements'] = cur + 1 -def write_csv(reviewer_data, file_obj): +def write_csv(reviewer_data, file_obj, options, reviewers, projects, + totals, change_stats): """Write out reviewers using CSV.""" writer = csv.writer(file_obj) row = ['Reviewer', 'Reviews', '-2', '-1', '+1', '+2', '+A', '+/- %', @@ -117,8 +118,25 @@ def write_csv(reviewer_data, file_obj): writer.writerow(row) -def write_pretty(reviewer_data, file_obj): +def write_pretty(reviewer_data, file_obj, options, reviewers, projects, + totals, change_stats): """Write out reviewers using PrettyTable.""" + + if options.all: + file_obj.write( + 'Reviews for the last %d days in projects: %s\n' % + (options.days, [project['name'] for project in projects])) + else: + file_obj.write( + 'Reviews for the last %d days in %s\n' + % (options.days, projects[0]['name'])) + if options.all: + file_obj.write( + '** -- Member of at least one core reviewer team\n') + else: + file_obj.write( + '** -- %s-core team member\n' % projects[0]['name']) + columns = ['Reviewer', 'Reviews -2 -1 +1 +2 +A +/- %', 'Disagreements*'] @@ -135,6 +153,67 @@ def write_pretty(reviewer_data, file_obj): table.add_row(row) file_obj.write("%s\n" % table) + file_obj.write( + '\nTotal reviews: %d (%.1f/day)\n' % ( + totals['all'], float(totals['all']) / options.days)) + num_reviewers = len([r for r in reviewers if r[0]['total']]) + file_obj.write( + 'Total reviewers: %d (avg %.1f reviews/day)\n' % ( + num_reviewers, float(totals['all']) / options.days / num_reviewers)) + file_obj.write('Total reviews by core team: %d (%.1f/day)\n' % ( + totals['core'], float(totals['core']) / options.days)) + core_team_size = sum([len(project['core-team']) + for project in projects]) + file_obj.write('Core team size: %d (avg %.1f reviews/day)\n' % ( + core_team_size, + float(totals['core']) / options.days / core_team_size)) + file_obj.write( + 'New patch sets in the last %d days: %d (%.1f/day)\n' + % (options.days, change_stats['patches'], + float(change_stats['patches']) / options.days)) + file_obj.write( + 'Changes involved in the last %d days: %d (%.1f/day)\n' + % (options.days, change_stats['involved'], + float(change_stats['involved']) / options.days)) + file_obj.write( + ' New changes in the last %d days: %d (%.1f/day)\n' + % (options.days, change_stats['created'], + float(change_stats['created']) / options.days)) + file_obj.write( + ' Changes merged in the last %d days: %d (%.1f/day)\n' + % (options.days, change_stats['merged'], + float(change_stats['merged']) / options.days)) + file_obj.write( + ' Changes abandoned in the last %d days: %d (%.1f/day)\n' + % (options.days, change_stats['abandoned'], + float(change_stats['abandoned']) / options.days)) + file_obj.write( + (' Changes left in state WIP in the last %d days: %d ' + '(%.1f/day)\n') + % (options.days, change_stats['wip'], + float(change_stats['wip']) / options.days)) + queue_growth = (change_stats['created'] - change_stats['merged'] - + change_stats['abandoned'] - change_stats['wip']) + file_obj.write( + (' Queue growth in the last %d days: %d ' + '(%.1f/day)\n') + % (options.days, queue_growth, + float(queue_growth) / options.days)) + file_obj.write( + ' Average number of patches per changeset: %.1f\n' + % (float(change_stats['patches']) / change_stats['involved'])) + file_obj.write( + '\n(*) Disagreements are defined as a +1 or +2 vote on a ' + 'patch where a core team member later gave a -1 or -2 vote' + ', or a negative vote overridden with a positive one ' + 'afterwards.\n') + if ENABLE_RECEIVED: + file_obj.write( + '\n(***) Received - number of reviews that this person ' + 'received on their patches in this time period. The given ' + 'ratio is the number of reviews given over the number ' + 'received.\n') + def main(argv=None): if argv is None: @@ -178,12 +257,14 @@ def main(argv=None): ts = calendar.timegm(cut_off.timetuple()) now_ts = calendar.timegm(now.timetuple()) - patches_created = 0 - changes_created = 0 - changes_involved = 0 - changes_merged = 0 - changes_abandoned = 0 - changes_wip = 0 + change_stats = { + 'patches': 0, + 'created': 0, + 'involved': 0, + 'merged': 0, + 'abandoned': 0, + 'wip': 0, + } for project in projects: changes = utils.get_changes([project], options.user, options.key) @@ -194,27 +275,29 @@ def main(argv=None): process_patchset(project, patchset, reviewers, ts) age = utils.get_age_of_patch(patchset, now_ts) if (now_ts - age) > ts: - patches_created += 1 + change_stats['patches'] += 1 patch_for_change = True if first_patchset: - changes_created += 1 + change_stats['created'] += 1 first_patchset = False if patch_for_change: - changes_involved += 1 + change_stats['involved'] += 1 if change['status'] == 'MERGED': - changes_merged += 1 + change_stats['merged'] += 1 elif change['status'] == 'ABANDONED': - changes_abandoned += 1 + change_stats['abandoned'] += 1 elif change['status'] == 'WORKINPROGRESS': - changes_wip += 1 + change_stats['wip'] += 1 reviewers = [(v, k) for k, v in reviewers.iteritems() if k.lower() not in ('jenkins', 'smokestack')] reviewers.sort(reverse=True, key=lambda r: r[0]['total']) # Do logical processing of reviewers. reviewer_data = [] - total = 0 - core_total = 0 + totals = { + 'all': 0, + 'core': 0, + } for k, v in reviewers: in_core_team = False for project in projects: @@ -236,9 +319,9 @@ def main(argv=None): if k['received'] else 0) s = (k['received'], "%5.1f%%" % sratio if k['received'] else 'inf') reviewer_data.append((name, r, d, s)) - total += k['total'] + totals['all'] += k['total'] if in_core_team: - core_total += k['total'] + totals['core'] += k['total'] # And output. writers = { 'csv': write_csv, @@ -256,82 +339,8 @@ def main(argv=None): on_done = file_obj.close try: writer = writers[output] - if options.all: - file_obj.write( - 'Reviews for the last %d days in projects: %s\n' % - (options.days, [project['name'] for project in projects])) - else: - file_obj.write( - 'Reviews for the last %d days in %s\n' - % (options.days, projects[0]['name'])) - if options.all: - file_obj.write( - '** -- Member of at least one core reviewer team\n') - else: - file_obj.write( - '** -- %s-core team member\n' % projects[0]['name']) - writer(reviewer_data, file_obj) - file_obj.write( - '\nTotal reviews: %d (%.1f/day)\n' % ( - total, float(total) / options.days)) - num_reviewers = len([r for r in reviewers if r[0]['total']]) - file_obj.write( - 'Total reviewers: %d (avg %.1f reviews/day)\n' % ( - num_reviewers, float(total) / options.days / num_reviewers)) - file_obj.write('Total reviews by core team: %d (%.1f/day)\n' % ( - core_total, float(core_total) / options.days)) - core_team_size = sum([len(project['core-team']) - for project in projects]) - file_obj.write('Core team size: %d (avg %.1f reviews/day)\n' % ( - core_team_size, - float(core_total) / options.days / core_team_size)) - file_obj.write( - 'New patch sets in the last %d days: %d (%.1f/day)\n' - % (options.days, patches_created, - float(patches_created) / options.days)) - file_obj.write( - 'Changes involved in the last %d days: %d (%.1f/day)\n' - % (options.days, changes_involved, - float(changes_involved) / options.days)) - file_obj.write( - ' New changes in the last %d days: %d (%.1f/day)\n' - % (options.days, changes_created, - float(changes_created) / options.days)) - file_obj.write( - ' Changes merged in the last %d days: %d (%.1f/day)\n' - % (options.days, changes_merged, - float(changes_merged) / options.days)) - file_obj.write( - ' Changes abandoned in the last %d days: %d (%.1f/day)\n' - % (options.days, changes_abandoned, - float(changes_abandoned) / options.days)) - file_obj.write( - (' Changes left in state WIP in the last %d days: %d ' - '(%.1f/day)\n') - % (options.days, changes_wip, - float(changes_wip) / options.days)) - queue_growth = (changes_created - changes_merged - - changes_abandoned - changes_wip) - file_obj.write( - (' Queue growth in the last %d days: %d ' - '(%.1f/day)\n') - % (options.days, queue_growth, - float(queue_growth) / options.days)) - file_obj.write( - ' Average number of patches per changeset: %.1f\n' - % (float(patches_created) / changes_involved)) - file_obj.write( - '\n(*) Disagreements are defined as a +1 or +2 vote on a ' - 'patch where a core team member later gave a -1 or -2 vote' - ', or a negative vote overridden with a positive one ' - 'afterwards.\n') - if ENABLE_RECEIVED: - file_obj.write( - '\n(***) Received - number of reviews that this person ' - 'received on their patches in this time period. The given ' - 'ratio is the number of reviews given over the number ' - 'received.\n') - + writer(reviewer_data, file_obj, options, reviewers, projects, + totals, change_stats) finally: if on_done: on_done()