From e5deedfce6b7c0e039f27563c0154a80799bf70a Mon Sep 17 00:00:00 2001 From: Nicolas Dossou-gbete Date: Wed, 15 Mar 2017 16:26:47 +0000 Subject: [PATCH] Support JSON output in my_activity.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit That JSON output format shows some additional fields such as bug number for CLs, label and component for issues. Also does minor changes like replacing the old code.google.com references and using short urls when possible. BUG=None Change-Id: I988d292dc57b72a2f2c6f12096266df8a09a4dd8 Reviewed-on: https://chromium-review.googlesource.com/422203 Reviewed-by: Aaron Gable Commit-Queue: Nicolas Dossou-Gbété --- my_activity.py | 103 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 6 deletions(-) diff --git a/my_activity.py b/my_activity.py index ecc52960f..7846e56f9 100755 --- a/my_activity.py +++ b/my_activity.py @@ -11,6 +11,7 @@ Example: - my_activity.py -Y for stats for this year. - my_activity.py -b 4/5/12 for stats since 4/5/12. - my_activity.py -b 4/5/12 -e 6/7/12 for stats between 4/5/12 and 6/7/12. + - my_activity.py -jd to output stats for the week to json with deltas data. """ # TODO(vadimsh): This script knows too much about ClientLogin and cookies. It @@ -34,6 +35,7 @@ import subprocess from string import Formatter import sys import urllib +import re import auth import fix_encoding @@ -250,6 +252,26 @@ class MyActivity(object): return issues + def extract_bug_number_from_description(self, issue): + description = None + + if 'description' in issue: + # Getting the description for Rietveld + description = issue['description'] + elif 'revisions' in issue: + # Getting the description for REST Gerrit + revision = issue['revisions'][issue['current_revision']] + description = revision['commit']['message'] + + bugs = [] + if description: + matches = re.findall('BUG=(((\d+)(,\s?)?)+)', description) + if matches: + for match in matches: + bugs.extend(match[0].replace(' ', '').split(',')) + + return bugs + def process_rietveld_issue(self, remote, instance, issue): ret = {} if self.options.deltas: @@ -287,6 +309,9 @@ class MyActivity(object): ret['created'] = datetime_from_rietveld(issue['created']) ret['replies'] = self.process_rietveld_replies(issue['messages']) + ret['bug'] = self.extract_bug_number_from_description(issue) + ret['landed_days_ago'] = issue['landed_days_ago'] + return ret @staticmethod @@ -323,7 +348,8 @@ class MyActivity(object): # Instantiate the generator to force all the requests now and catch the # errors here. return list(gerrit_util.GenerateAllChanges(instance['url'], req, - o_params=['MESSAGES', 'LABELS', 'DETAILED_ACCOUNTS'])) + o_params=['MESSAGES', 'LABELS', 'DETAILED_ACCOUNTS', + 'CURRENT_REVISION', 'CURRENT_COMMIT'])) except gerrit_util.GerritError, e: logging.error('Looking up %r: %s', instance['url'], e) return [] @@ -374,6 +400,7 @@ class MyActivity(object): ret['replies'] = [] ret['reviewers'] = set(r['author'] for r in ret['replies']) ret['reviewers'].discard(ret['author']) + ret['bug'] = self.extract_bug_number_from_description(issue) return ret @staticmethod @@ -412,6 +439,7 @@ class MyActivity(object): ret['replies'] = [] ret['reviewers'] = set(r['author'] for r in ret['replies']) ret['reviewers'].discard(ret['author']) + ret['bug'] = self.extract_bug_number_from_description(issue) return ret @staticmethod @@ -455,15 +483,21 @@ class MyActivity(object): if 'items' in content: items = content['items'] for item in items: + if instance.get('shorturl'): + item_url = 'https://%s/%d' % (instance['shorturl'], item['id']) + else: + item_url = 'https://bugs.chromium.org/p/%s/issues/detail?id=%d' % ( + instance['name'], item['id']) issue = { 'header': item['title'], 'created': dateutil.parser.parse(item['published']), 'modified': dateutil.parser.parse(item['updated']), 'author': item['author']['name'], - 'url': 'https://code.google.com/p/%s/issues/detail?id=%s' % ( - instance['name'], item['id']), + 'url': item_url, 'comments': [], 'status': item['status'], + 'labels': [], + 'components': [] } if 'shorturl' in instance: issue['url'] = 'http://%s/%d' % (instance['shorturl'], item['id']) @@ -474,6 +508,10 @@ class MyActivity(object): issue['owner'] = 'None' if issue['owner'] == user_str or issue['author'] == user_str: issues.append(issue) + if 'labels' in item: + issue['labels'] = item['labels'] + if 'components' in item: + issue['components'] = item['components'] return issues @@ -634,6 +672,35 @@ class MyActivity(object): self.print_reviews() self.print_issues() + def dump_json(self, ignore_keys=None): + if ignore_keys is None: + ignore_keys = ['replies'] + + def format_for_json_dump(in_array): + output = {} + for item in in_array: + url = item.get('url') or item.get('review_url') + if not url: + raise Exception('Dumped item %s does not specify url' % item) + output[url] = dict( + (k, v) for k,v in item.iteritems() if k not in ignore_keys) + return output + + class PythonObjectEncoder(json.JSONEncoder): + def default(self, obj): # pylint: disable=method-hidden + if isinstance(obj, datetime): + return obj.isoformat() + if isinstance(obj, set): + return list(obj) + return json.JSONEncoder.default(self, obj) + + output = { + 'reviews': format_for_json_dump(self.reviews), + 'changes': format_for_json_dump(self.changes), + 'issues': format_for_json_dump(self.issues) + } + print json.dumps(output, indent=2, cls=PythonObjectEncoder) + def main(): # Silence upload.py. @@ -728,6 +795,9 @@ def main(): '-m', '--markdown', action='store_true', help='Use markdown-friendly output (overrides --output-format ' 'and --output-format-heading)') + output_format_group.add_option( + '-j', '--json', action='store_true', + help='Output json data (overrides other format options)') parser.add_option_group(output_format_group) auth.add_auth_options(parser) @@ -746,6 +816,9 @@ def main(): const=logging.ERROR, help='Suppress non-error messages.' ) + parser.add_option( + '-o', '--output', metavar='', + help='Where to output the results. By default prints to stdout.') # Remove description formatting parser.format_description = ( @@ -819,9 +892,27 @@ def main(): except auth.AuthenticationError as e: logging.error('auth.AuthenticationError: %s', e) - my_activity.print_changes() - my_activity.print_reviews() - my_activity.print_issues() + output_file = None + try: + if options.output: + output_file = open(options.output, 'w') + logging.info('Printing output to "%s"', options.output) + sys.stdout = output_file + except (IOError, OSError) as e: + logging.error('Unable to write output: %s', e) + else: + if options.json: + my_activity.dump_json() + else: + my_activity.print_changes() + my_activity.print_reviews() + my_activity.print_issues() + finally: + if output_file: + logging.info('Done printing to file.') + sys.stdout = sys.__stdout__ + output_file.close() + return 0