diff --git a/my_reviews.py b/my_reviews.py new file mode 100755 index 000000000..76a9ee7d3 --- /dev/null +++ b/my_reviews.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Get rietveld stats. + +Example: + - my_reviews.py -o me@chromium.org -Q for stats for last quarter. +""" +import datetime +import optparse +import os +import sys + +import rietveld + + +def print_reviews(owner, reviewer, created_after, created_before): + """Prints issues with the filter. + + Set with_messages=True to search() call bellow if you want each message too. + If you only want issue numbers, use keys_only=True in the search() call. + You can then use remote.get_issue_properties(issue, True) to get the data per + issue. + """ + instance_url = 'codereview.chromium.org' + remote = rietveld.Rietveld(instance_url, None, None) + + # See def search() in rietveld.py to see all the filters you can use. + for issue in remote.search( + owner=owner, + reviewer=reviewer, + created_after=created_after, + created_before=created_before, + keys_only=False, + with_messages=False, + ): + # By default, hide commit-bot and the domain. + reviewers = set(r.split('@', 1)[0] for r in issue['reviewers']) + reviewers -= set(('commit-bot',)) + # Strip time. + timestamp = issue['created'][:10] + + # More information is available, print issue.keys() to see them. + print '%d: %s %s' % (issue['issue'], timestamp, ', '.join(reviewers)) + + +def get_previous_quarter(today): + """There are four quarters, 01-03, 04-06, 07-09, 10-12. + + If today is in the last month of a quarter, assume it's the current quarter + that is requested. + """ + year = today.year + month = today.month - (today.month % 3) + if not month: + month = 12 + year -= 1 + previous_month = month - 2 + return ( + '%d-%02d-01' % (year, previous_month), + '%d-%02d-01' % (year, month)) + + +def main(): + parser = optparse.OptionParser(description=sys.modules[__name__].__doc__) + parser.add_option('-o', '--owner') + parser.add_option('-r', '--reviewer') + parser.add_option('-c', '--created_after') + parser.add_option('-C', '--created_before') + parser.add_option('-Q', '--last_quarter', action='store_true') + # Remove description formatting + parser.format_description = lambda x: parser.description + options, args = parser.parse_args() + if args: + parser.error('Args unsupported') + if not options.owner and not options.reviewer: + options.owner = os.environ['EMAIL_ADDRESS'] + if '@' not in options.owner: + parser.error('Please specify at least -o or -r') + print 'Defaulting to owner=%s' % options.owner + if options.last_quarter: + today = datetime.date.today() + options.created_after, options.created_before = get_previous_quarter(today) + print 'Using range %s to %s' % ( + options.created_after, options.created_before) + print_reviews( + options.owner, options.reviewer, + options.created_after, options.created_before) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/rietveld.py b/rietveld.py index 0b88eb0eb..d13173b32 100644 --- a/rietveld.py +++ b/rietveld.py @@ -242,6 +242,64 @@ class Rietveld(object): ('xsrf_token', self.xsrf_token()), (flag, value)]) + def search( + self, + owner=None, reviewer=None, + base=None, + closed=None, private=None, commit=None, + created_before=None, created_after=None, + modified_before=None, modified_after=None, + per_request=None, keys_only=False, + with_messages=False): + """Yields search results.""" + # These are expected to be strings. + string_keys = { + 'owner': owner, + 'reviewer': reviewer, + 'base': base, + 'created_before': created_before, + 'created_after': created_after, + 'modified_before': modified_before, + 'modified_after': modified_after, + } + # These are either None, False or True. + three_state_keys = { + 'closed': closed, + 'private': private, + 'commit': commit, + } + + url = '/search?format=json' + # Sort the keys mainly to ease testing. + for key in sorted(string_keys): + value = string_keys[key] + if value: + url += '&%s=%s' % (key, urllib2.quote(value)) + for key in sorted(three_state_keys): + value = three_state_keys[key] + if value is not None: + url += '&%s=%d' % (key, int(value) + 1) + + if keys_only: + url += '&keys_only=True' + if with_messages: + url += '&with_messages=True' + if per_request: + url += '&limit=%d' % per_request + + cursor = '' + while True: + output = self.get(url + cursor) + if output.startswith('<'): + # It's an error message. Return as no result. + break + data = json.loads(output) or {} + if not data.get('results'): + break + for i in data['results']: + yield i + cursor = '&cursor=%s' % data['cursor'] + def get(self, request_path, **kwargs): kwargs.setdefault('payload', None) return self._send(request_path, **kwargs) diff --git a/tests/rietveld_test.py b/tests/rietveld_test.py index ff43e459d..3316df55e 100755 --- a/tests/rietveld_test.py +++ b/tests/rietveld_test.py @@ -281,6 +281,70 @@ class RietveldTest(unittest.TestCase): # TODO(maruel): Change with no diff, only svn property change: # http://codereview.chromium.org/6462019/ + def test_search_all_empty(self): + url = ( + '/search?format=json' + '&base=base' + '&created_after=2010-01-02' + '&created_before=2010-01-01' + '&modified_after=2010-02-02' + '&modified_before=2010-02-01' + '&owner=owner%40example.com' + '&reviewer=reviewer%40example.com' + '&closed=2' + '&commit=2' + '&private=2' + '&keys_only=True' + '&with_messages=True' + '&limit=23') + self.requests = [ + (url, '{}'), + ] + results = list(self.rietveld.search( + 'owner@example.com', + 'reviewer@example.com', + 'base', + True, + True, + True, + '2010-01-01', + '2010-01-02', + '2010-02-01', + '2010-02-02', + 23, + True, + True, + )) + self.assertEquals([], results) + + def test_results_cursor(self): + # Verify cursor iteration is transparent. + self.requests = [ + ('/search?format=json&base=base', + rietveld.json.dumps({ + 'cursor': 'MY_CURSOR', + 'results': [{'foo': 'bar'}, {'foo': 'baz'}], + })), + ('/search?format=json&base=base&cursor=MY_CURSOR', + rietveld.json.dumps({ + 'cursor': 'NEXT', + 'results': [{'foo': 'prout'}], + })), + ('/search?format=json&base=base&cursor=NEXT', + rietveld.json.dumps({ + 'cursor': 'VOID', + 'results': [], + })), + ] + expected = [ + {'foo': 'bar'}, + {'foo': 'baz'}, + {'foo': 'prout'}, + ] + for i in self.rietveld.search(base='base'): + self.assertEquals(expected.pop(0), i) + self.assertEquals([], expected) + if __name__ == '__main__': logging.basicConfig(level=logging.ERROR)