Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(61)

Side by Side Diff: my_activity.py

Issue 24047003: my_activity.py: Use gerrit new REST API. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: maruel nit Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Get stats about your activity. 6 """Get stats about your activity.
7 7
8 Example: 8 Example:
9 - my_activity.py for stats for the current week (last week on mondays). 9 - my_activity.py for stats for the current week (last week on mondays).
10 - my_activity.py -Q for stats for last quarter. 10 - my_activity.py -Q for stats for last quarter.
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after
109 { 109 {
110 'url': 'breakpad.appspot.com', 110 'url': 'breakpad.appspot.com',
111 'supports_owner_modified_query': False, 111 'supports_owner_modified_query': False,
112 'requires_auth': False, 112 'requires_auth': False,
113 'email_domain': 'chromium.org', 113 'email_domain': 'chromium.org',
114 }, 114 },
115 ] 115 ]
116 116
117 gerrit_instances = [ 117 gerrit_instances = [
118 { 118 {
119 'url': 'gerrit.chromium.org', 119 'url': 'chromium-review.googlesource.com',
120 'port': 29418,
121 'shorturl': 'crosreview.com', 120 'shorturl': 'crosreview.com',
122 }, 121 },
122 # TODO(deymo): chrome-internal-review requires login credentials. Enable once
123 # login support is added to this client. See crbug.com/281695.
124 #{
125 # 'url': 'chrome-internal-review.googlesource.com',
126 # 'shorturl': 'crosreview.com/i',
127 #},
123 { 128 {
124 'url': 'gerrit-int.chromium.org', 129 'host': 'gerrit.chromium.org',
130 'port': 29418,
131 },
132 {
133 'host': 'gerrit-int.chromium.org',
125 'port': 29419, 134 'port': 29419,
126 'shorturl': 'crosreview.com/i',
127 }, 135 },
128 ] 136 ]
129 137
130 google_code_projects = [ 138 google_code_projects = [
131 { 139 {
132 'name': 'chromium', 140 'name': 'chromium',
133 'shorturl': 'crbug.com', 141 'shorturl': 'crbug.com',
134 }, 142 },
135 { 143 {
136 'name': 'chromium-os', 144 'name': 'chromium-os',
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after
232 240
233 def get_yes_or_no(msg): 241 def get_yes_or_no(msg):
234 while True: 242 while True:
235 response = raw_input(msg + ' yes/no [no] ') 243 response = raw_input(msg + ' yes/no [no] ')
236 if response == 'y' or response == 'yes': 244 if response == 'y' or response == 'yes':
237 return True 245 return True
238 elif not response or response == 'n' or response == 'no': 246 elif not response or response == 'n' or response == 'no':
239 return False 247 return False
240 248
241 249
250 def datetime_from_gerrit(date_string):
251 return datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S.%f000')
252
253
242 def datetime_from_rietveld(date_string): 254 def datetime_from_rietveld(date_string):
243 return datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S.%f') 255 return datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S.%f')
244 256
245 257
246 def datetime_from_google_code(date_string): 258 def datetime_from_google_code(date_string):
247 return datetime.strptime(date_string, '%Y-%m-%dT%H:%M:%S.%fZ') 259 return datetime.strptime(date_string, '%Y-%m-%dT%H:%M:%S.%fZ')
248 260
249 261
250 class MyActivity(object): 262 class MyActivity(object):
251 def __init__(self, options): 263 def __init__(self, options):
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after
369 def process_rietveld_replies(replies): 381 def process_rietveld_replies(replies):
370 ret = [] 382 ret = []
371 for reply in replies: 383 for reply in replies:
372 r = {} 384 r = {}
373 r['author'] = reply['sender'] 385 r['author'] = reply['sender']
374 r['created'] = datetime_from_rietveld(reply['date']) 386 r['created'] = datetime_from_rietveld(reply['date'])
375 r['content'] = '' 387 r['content'] = ''
376 ret.append(r) 388 ret.append(r)
377 return ret 389 return ret
378 390
391 @staticmethod
392 def gerrit_changes_over_ssh(instance, filters):
393 # See https://review.openstack.org/Documentation/cmd-query.html
394 # Gerrit doesn't allow filtering by created time, only modified time.
395 gquery_cmd = ['ssh', '-p', str(instance['port']), instance['host'],
396 'gerrit', 'query',
397 '--format', 'JSON',
398 '--comments',
399 '--'] + filters
400 (stdout, _) = subprocess.Popen(gquery_cmd, stdout=subprocess.PIPE,
401 stderr=subprocess.PIPE).communicate()
402 # Drop the last line of the output with the stats.
403 issues = stdout.splitlines()[:-1]
404 return map(json.loads, issues)
405
406 @staticmethod
407 def gerrit_changes_over_rest(instance, filters):
408 # See https://gerrit-review.googlesource.com/Documentation/rest-api.html
409 # Gerrit doesn't allow filtering by created time, only modified time.
410 args = urllib.urlencode([
411 ('q', ' '.join(filters)),
412 ('o', 'MESSAGES'),
413 ('o', 'LABELS')])
414 rest_url = 'https://%s/changes/?%s' % (instance['url'], args)
415
416 req = urllib2.Request(rest_url, headers={'Accept': 'text/plain'})
417 try:
418 response = urllib2.urlopen(req)
419 stdout = response.read()
420 except urllib2.HTTPError, e:
421 print 'ERROR: Looking up %r: %s' % (rest_url, e)
422 return []
423
424 # Check that the returned JSON starts with the right marker.
425 if stdout[:5] != ")]}'\n":
426 print 'ERROR: Marker not found on REST API response: %r' % stdout[:5]
427 return []
428 return json.loads(stdout[5:])
429
379 def gerrit_search(self, instance, owner=None, reviewer=None): 430 def gerrit_search(self, instance, owner=None, reviewer=None):
380 max_age = datetime.today() - self.modified_after 431 max_age = datetime.today() - self.modified_after
381 max_age = max_age.days * 24 * 3600 + max_age.seconds 432 max_age = max_age.days * 24 * 3600 + max_age.seconds
433 user_filter = 'owner:%s' % owner if owner else 'reviewer:%s' % reviewer
434 filters = ['-age:%ss' % max_age, user_filter]
382 435
383 # See https://review.openstack.org/Documentation/cmd-query.html 436 # Determine the gerrit interface to use: SSH or REST API:
384 # Gerrit doesn't allow filtering by created time, only modified time. 437 if 'host' in instance:
385 user_filter = 'owner:%s' % owner if owner else 'reviewer:%s' % reviewer 438 issues = self.gerrit_changes_over_ssh(instance, filters)
386 gquery_cmd = ['ssh', '-p', str(instance['port']), instance['url'], 439 issues = [self.process_gerrit_ssh_issue(instance, issue)
387 'gerrit', 'query', 440 for issue in issues]
388 '--format', 'JSON', 441 elif 'url' in instance:
389 '--comments', 442 issues = self.gerrit_changes_over_rest(instance, filters)
390 '--', 443 issues = [self.process_gerrit_rest_issue(instance, issue)
391 '-age:%ss' % str(max_age), 444 for issue in issues]
392 user_filter] 445 else:
393 [stdout, _] = subprocess.Popen(gquery_cmd, stdout=subprocess.PIPE, 446 raise Exception('Invalid gerrit_instances configuration.')
394 stderr=subprocess.PIPE).communicate()
395 issues = str(stdout).split('\n')[:-2]
396 issues = map(json.loads, issues)
397 447
398 # TODO(cjhopman): should we filter abandoned changes? 448 # TODO(cjhopman): should we filter abandoned changes?
399 issues = [self.process_gerrit_issue(instance, issue) for issue in issues]
400 issues = filter(self.filter_issue, issues) 449 issues = filter(self.filter_issue, issues)
401 issues = sorted(issues, key=lambda i: i['modified'], reverse=True) 450 issues = sorted(issues, key=lambda i: i['modified'], reverse=True)
402 451
403 return issues 452 return issues
404 453
405 def process_gerrit_issue(self, instance, issue): 454 def process_gerrit_ssh_issue(self, instance, issue):
406 ret = {} 455 ret = {}
407 ret['review_url'] = issue['url'] 456 ret['review_url'] = issue['url']
408 if 'shorturl' in instance: 457 if 'shorturl' in instance:
409 ret['review_url'] = 'http://%s/%s' % (instance['shorturl'], 458 ret['review_url'] = 'http://%s/%s' % (instance['shorturl'],
410 issue['number']) 459 issue['number'])
411 ret['header'] = issue['subject'] 460 ret['header'] = issue['subject']
412 ret['owner'] = issue['owner']['email'] 461 ret['owner'] = issue['owner']['email']
413 ret['author'] = ret['owner'] 462 ret['author'] = ret['owner']
414 ret['created'] = datetime.fromtimestamp(issue['createdOn']) 463 ret['created'] = datetime.fromtimestamp(issue['createdOn'])
415 ret['modified'] = datetime.fromtimestamp(issue['lastUpdated']) 464 ret['modified'] = datetime.fromtimestamp(issue['lastUpdated'])
416 if 'comments' in issue: 465 if 'comments' in issue:
417 ret['replies'] = self.process_gerrit_issue_replies(issue['comments']) 466 ret['replies'] = self.process_gerrit_ssh_issue_replies(issue['comments'])
418 else: 467 else:
419 ret['replies'] = [] 468 ret['replies'] = []
420 ret['reviewers'] = set() 469 ret['reviewers'] = set(r['author'] for r in ret['replies'])
421 for reply in ret['replies']: 470 ret['reviewers'].discard(ret['author'])
422 if reply['author'] != ret['author']:
423 ret['reviewers'].add(reply['author'])
424 return ret 471 return ret
425 472
426 @staticmethod 473 @staticmethod
427 def process_gerrit_issue_replies(replies): 474 def process_gerrit_ssh_issue_replies(replies):
428 ret = [] 475 ret = []
429 replies = filter(lambda r: 'email' in r['reviewer'], replies) 476 replies = filter(lambda r: 'email' in r['reviewer'], replies)
430 for reply in replies: 477 for reply in replies:
431 r = {} 478 ret.append({
432 r['author'] = reply['reviewer']['email'] 479 'author': reply['reviewer']['email'],
433 r['created'] = datetime.fromtimestamp(reply['timestamp']) 480 'created': datetime.fromtimestamp(reply['timestamp']),
434 r['content'] = '' 481 'content': '',
435 ret.append(r) 482 })
483 return ret
484
485 def process_gerrit_rest_issue(self, instance, issue):
486 ret = {}
487 ret['review_url'] = 'https://%s/%s' % (instance['url'], issue['_number'])
488 if 'shorturl' in instance:
489 # TODO(deymo): Move this short link to https once crosreview.com supports
490 # it.
491 ret['review_url'] = 'http://%s/%s' % (instance['shorturl'],
492 issue['_number'])
493 ret['header'] = issue['subject']
494 ret['owner'] = issue['owner']['email']
495 ret['author'] = ret['owner']
496 ret['created'] = datetime_from_gerrit(issue['created'])
497 ret['modified'] = datetime_from_gerrit(issue['updated'])
498 if 'messages' in issue:
499 ret['replies'] = self.process_gerrit_rest_issue_replies(issue['messages'])
500 else:
501 ret['replies'] = []
502 ret['reviewers'] = set(r['author'] for r in ret['replies'])
503 ret['reviewers'].discard(ret['author'])
504 return ret
505
506 @staticmethod
507 def process_gerrit_rest_issue_replies(replies):
508 ret = []
509 replies = filter(lambda r: 'email' in r['author'], replies)
510 for reply in replies:
511 ret.append({
512 'author': reply['author']['email'],
513 'created': datetime_from_gerrit(reply['date']),
514 'content': reply['message'],
515 })
436 return ret 516 return ret
437 517
438 def google_code_issue_search(self, instance): 518 def google_code_issue_search(self, instance):
439 time_format = '%Y-%m-%dT%T' 519 time_format = '%Y-%m-%dT%T'
440 # See http://code.google.com/p/support/wiki/IssueTrackerAPI 520 # See http://code.google.com/p/support/wiki/IssueTrackerAPI
441 # q=<owner>@chromium.org does a full text search for <owner>@chromium.org. 521 # q=<owner>@chromium.org does a full text search for <owner>@chromium.org.
442 # This will accept the issue if owner is the owner or in the cc list. Might 522 # This will accept the issue if owner is the owner or in the cc list. Might
443 # have some false positives, though. 523 # have some false positives, though.
444 524
445 # Don't filter normally on modified_before because it can filter out things 525 # Don't filter normally on modified_before because it can filter out things
(...skipping 579 matching lines...) Expand 10 before | Expand all | Expand 10 after
1025 print '\n\n\n' 1105 print '\n\n\n'
1026 1106
1027 my_activity.print_changes() 1107 my_activity.print_changes()
1028 my_activity.print_reviews() 1108 my_activity.print_reviews()
1029 my_activity.print_issues() 1109 my_activity.print_issues()
1030 return 0 1110 return 0
1031 1111
1032 1112
1033 if __name__ == '__main__': 1113 if __name__ == '__main__':
1034 sys.exit(main()) 1114 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698