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

Side by Side Diff: my_activity.py

Issue 1176243002: my_activity.py: update to use oauth for projecthosting (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Created 5 years, 6 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
« auth.py ('K') | « auth.py ('k') | 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 24 matching lines...) Expand all
35 import sys 35 import sys
36 import urllib 36 import urllib
37 import urllib2 37 import urllib2
38 38
39 import auth 39 import auth
40 import fix_encoding 40 import fix_encoding
41 import gerrit_util 41 import gerrit_util
42 import rietveld 42 import rietveld
43 from third_party import upload 43 from third_party import upload
44 44
45 import auth
46 from third_party import httplib2
47
45 try: 48 try:
46 from dateutil.relativedelta import relativedelta # pylint: disable=F0401 49 from dateutil.relativedelta import relativedelta # pylint: disable=F0401
47 except ImportError: 50 except ImportError:
48 print 'python-dateutil package required' 51 print 'python-dateutil package required'
49 exit(1) 52 exit(1)
50 53
51 # python-keyring provides easy access to the system keyring. 54 # python-keyring provides easy access to the system keyring.
52 try: 55 try:
53 import keyring # pylint: disable=W0611,F0401 56 import keyring # pylint: disable=W0611,F0401
54 except ImportError: 57 except ImportError:
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
93 gerrit_instances = [ 96 gerrit_instances = [
94 { 97 {
95 'url': 'chromium-review.googlesource.com', 98 'url': 'chromium-review.googlesource.com',
96 'shorturl': 'crosreview.com', 99 'shorturl': 'crosreview.com',
97 }, 100 },
98 { 101 {
99 'url': 'chrome-internal-review.googlesource.com', 102 'url': 'chrome-internal-review.googlesource.com',
100 'shorturl': 'crosreview.com/i', 103 'shorturl': 'crosreview.com/i',
101 }, 104 },
102 { 105 {
103 'host': 'gerrit.chromium.org', 106 'host': 'gerrit.chromium.org',
Vadim Sh. 2015/06/11 01:37:31 this host has been turn off today, can be removed
seanmccullough 2015/06/11 02:05:22 Done.
104 'port': 29418, 107 'port': 29418,
105 }, 108 },
106 ] 109 ]
107 110
108 google_code_projects = [ 111 google_code_projects = [
109 { 112 {
110 'name': 'brillo', 113 'name': 'brillo',
111 'shorturl': 'brbug.com', 114 'shorturl': 'brbug.com',
112 }, 115 },
113 { 116 {
(...skipping 12 matching lines...) Expand all
126 }, 129 },
127 { 130 {
128 'name': 'gyp', 131 'name': 'gyp',
129 }, 132 },
130 { 133 {
131 'name': 'skia', 134 'name': 'skia',
132 }, 135 },
133 ] 136 ]
134 137
135 # Uses ClientLogin to authenticate the user for Google Code issue trackers. 138 # Uses ClientLogin to authenticate the user for Google Code issue trackers.
136 def get_auth_token(email): 139 def get_auth_token(email):
Vadim Sh. 2015/06/11 01:37:31 this should be eventually deleted, it's broken
seanmccullough 2015/06/11 02:05:22 Done.
137 # KeyringCreds will use the system keyring on the first try, and prompt for 140 # KeyringCreds will use the system keyring on the first try, and prompt for
138 # a password on the next ones. 141 # a password on the next ones.
139 creds = upload.KeyringCreds('code.google.com', 'code.google.com', email) 142 creds = upload.KeyringCreds('code.google.com', 'code.google.com', email)
140 for _ in xrange(3): 143 for _ in xrange(3):
141 email, password = creds.GetUserCredentials() 144 email, password = creds.GetUserCredentials()
142 url = 'https://www.google.com/accounts/ClientLogin' 145 url = 'https://www.google.com/accounts/ClientLogin'
143 data = urllib.urlencode({ 146 data = urllib.urlencode({
144 'Email': email, 147 'Email': email,
145 'Passwd': password, 148 'Passwd': password,
146 'service': 'code', 149 'service': 'code',
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
223 self.user = options.user 226 self.user = options.user
224 self.changes = [] 227 self.changes = []
225 self.reviews = [] 228 self.reviews = []
226 self.issues = [] 229 self.issues = []
227 self.check_cookies() 230 self.check_cookies()
228 self.google_code_auth_token = None 231 self.google_code_auth_token = None
229 232
230 # Check the codereview cookie jar to determine which Rietveld instances to 233 # Check the codereview cookie jar to determine which Rietveld instances to
231 # authenticate to. 234 # authenticate to.
232 def check_cookies(self): 235 def check_cookies(self):
233 cookie_file = os.path.expanduser('~/.codereview_upload_cookies') 236 cookie_file = os.path.expanduser('~/.codereview_upload_cookies')
Vadim Sh. 2015/06/11 01:37:31 it's not directly related to OAuth code.google.com
seanmccullough 2015/06/11 02:05:22 removed it, now it just checks "has_cookie"
234 if not os.path.exists(cookie_file): 237 if not os.path.exists(cookie_file):
235 print 'No Rietveld cookie file found.' 238 print 'No Rietveld cookie file found.'
236 cookie_jar = [] 239 cookie_jar = []
237 else: 240 else:
238 cookie_jar = cookielib.MozillaCookieJar(cookie_file) 241 cookie_jar = cookielib.MozillaCookieJar(cookie_file)
239 try: 242 try:
240 cookie_jar.load() 243 cookie_jar.load()
241 print 'Found cookie file: %s' % cookie_file 244 print 'Found cookie file: %s' % cookie_file
242 except (cookielib.LoadError, IOError): 245 except (cookielib.LoadError, IOError):
243 print 'Error loading Rietveld cookie file: %s' % cookie_file 246 print 'Error loading Rietveld cookie file: %s' % cookie_file
244 cookie_jar = [] 247 cookie_jar = []
245 248
246 filtered_instances = [] 249 filtered_instances = []
247 250
248 def has_cookie(instance): 251 def has_cookie(instance):
Vadim Sh. 2015/06/11 01:37:31 this can be reformulated as: a = auth.get_authent
seanmccullough 2015/06/11 02:05:22 Done.
249 for cookie in cookie_jar: 252 for cookie in cookie_jar:
250 if cookie.name == 'SACSID' and cookie.domain == instance['url']: 253 if cookie.name == 'SACSID' and cookie.domain == instance['url']:
251 return True 254 return True
252 if self.options.auth: 255 if self.options.auth:
253 return get_yes_or_no('No cookie found for %s. Authorize for this ' 256 return get_yes_or_no('No cookie found for %s. Authorize for this '
254 'instance? (may require application-specific ' 257 'instance? (may require application-specific '
255 'password)' % instance['url']) 258 'password)' % instance['url'])
256 filtered_instances.append(instance) 259 filtered_instances.append(instance)
257 return False 260 return False
258 261
(...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after
454 replies = filter(lambda r: 'author' in r and 'email' in r['author'], 457 replies = filter(lambda r: 'author' in r and 'email' in r['author'],
455 replies) 458 replies)
456 for reply in replies: 459 for reply in replies:
457 ret.append({ 460 ret.append({
458 'author': reply['author']['email'], 461 'author': reply['author']['email'],
459 'created': datetime_from_gerrit(reply['date']), 462 'created': datetime_from_gerrit(reply['date']),
460 'content': reply['message'], 463 'content': reply['message'],
461 }) 464 })
462 return ret 465 return ret
463 466
464 def google_code_issue_search(self, instance): 467 def project_hosting_issue_search(self, instance):
465 time_format = '%Y-%m-%dT%T' 468 auth_config = auth.extract_auth_config_from_options(self.options)
466 # See http://code.google.com/p/support/wiki/IssueTrackerAPI 469 authenticator = auth.get_authenticator_for_host(
467 # q=<owner>@chromium.org does a full text search for <owner>@chromium.org. 470 "https://code.google.com", auth_config)
Vadim Sh. 2015/06/11 01:37:32 just "code.google.com" here should work too
seanmccullough 2015/06/11 02:05:22 Done.
468 # This will accept the issue if owner is the owner or in the cc list. Might 471 http = authenticator.authorize(httplib2.Http())
469 # have some false positives, though. 472 url = "https://www.googleapis.com/projecthosting/v2/projects/%s/issues" % (
473 instance["name"])
474 epoch = datetime.utcfromtimestamp(0)
475 user_str = '%s@chromium.org' % self.user
470 476
471 # Don't filter normally on modified_before because it can filter out things 477 query_data = urllib.urlencode({
472 # that were modified in the time period and then modified again after it. 478 'maxResults': 10000,
473 gcode_url = ('https://code.google.com/feeds/issues/p/%s/issues/full' % 479 'q': '%s' % user_str,
Vadim Sh. 2015/06/11 01:37:31 'q': user_str
seanmccullough 2015/06/11 02:05:22 Done.
474 instance['name']) 480 'publishedMax': '%d' % (self.modified_before - epoch).total_seconds(),
475 481 'updatedMin': '%d' % (self.modified_after - epoch).total_seconds(),
476 gcode_data = urllib.urlencode({
477 'alt': 'json',
478 'max-results': '100000',
479 'q': '%s' % self.user,
480 'published-max': self.modified_before.strftime(time_format),
481 'updated-min': self.modified_after.strftime(time_format),
482 }) 482 })
483 483 url = url + '?' + query_data
484 opener = urllib2.build_opener() 484 _, body = http.request(url)
Vadim Sh. 2015/06/11 01:37:31 this thing will raise auth.AuthenticationError if
seanmccullough 2015/06/11 02:05:22 Done.
485 if self.google_code_auth_token: 485 content = json.loads(body)
486 opener.addheaders = [('Authorization', 'GoogleLogin auth=%s' % 486 if not content:
487 self.google_code_auth_token)] 487 print "Unable to parse %s response from projecthosting." % (
488 gcode_json = None 488 instance["name"])
489 try:
490 gcode_get = opener.open(gcode_url + '?' + gcode_data)
491 gcode_json = json.load(gcode_get)
492 gcode_get.close()
493 except urllib2.HTTPError, _:
494 print 'Unable to access ' + instance['name'] + ' issue tracker.'
495
496 if not gcode_json or 'entry' not in gcode_json['feed']:
497 return [] 489 return []
498 490
499 issues = gcode_json['feed']['entry'] 491 issues = []
500 issues = map(partial(self.process_google_code_issue, instance), issues) 492 if 'items' in content:
501 issues = filter(self.filter_issue, issues) 493 items = content['items']
502 issues = sorted(issues, key=lambda i: i['modified'], reverse=True) 494 for item in items:
495 issue = {
496 "header": item["title"],
497 "created": item["published"],
498 "modified": item["updated"],
499 "author": item["author"]["name"],
500 "url": "https://code.google.com/p/%s/issues/detail?id=%s" % (
501 instance["name"], item["id"]),
502 "comments": []
503 }
504 if 'owner' in item:
505 issue['owner'] = item['owner']['name']
506 else:
507 issue['owner'] = 'None'
508 if issue['owner'] == user_str or issue['author'] == user_str:
509 issues.append(issue)
510
503 return issues 511 return issues
504 512
505 def process_google_code_issue(self, project, issue):
506 ret = {}
507 ret['created'] = datetime_from_google_code(issue['published']['$t'])
508 ret['modified'] = datetime_from_google_code(issue['updated']['$t'])
509
510 ret['owner'] = ''
511 if 'issues$owner' in issue:
512 ret['owner'] = issue['issues$owner']['issues$username']['$t']
513 ret['author'] = issue['author'][0]['name']['$t']
514
515 if 'shorturl' in project:
516 issue_id = issue['id']['$t']
517 issue_id = issue_id[issue_id.rfind('/') + 1:]
518 ret['url'] = 'http://%s/%d' % (project['shorturl'], int(issue_id))
519 else:
520 issue_url = issue['link'][1]
521 if issue_url['rel'] != 'alternate':
522 raise RuntimeError
523 ret['url'] = issue_url['href']
524 ret['header'] = issue['title']['$t']
525
526 ret['replies'] = self.get_google_code_issue_replies(issue)
527 return ret
528
529 def get_google_code_issue_replies(self, issue):
530 """Get all the comments on the issue."""
531 replies_url = issue['link'][0]
532 if replies_url['rel'] != 'replies':
533 raise RuntimeError
534
535 replies_data = urllib.urlencode({
536 'alt': 'json',
537 'fields': 'entry(published,author,content)',
538 })
539
540 opener = urllib2.build_opener()
541 opener.addheaders = [('Authorization', 'GoogleLogin auth=%s' %
542 self.google_code_auth_token)]
543 try:
544 replies_get = opener.open(replies_url['href'] + '?' + replies_data)
545 except urllib2.HTTPError, _:
546 return []
547
548 replies_json = json.load(replies_get)
549 replies_get.close()
550 return self.process_google_code_issue_replies(replies_json)
551
552 @staticmethod
553 def process_google_code_issue_replies(replies):
554 if 'entry' not in replies['feed']:
555 return []
556
557 ret = []
558 for entry in replies['feed']['entry']:
559 e = {}
560 e['created'] = datetime_from_google_code(entry['published']['$t'])
561 e['content'] = entry['content']['$t']
562 e['author'] = entry['author'][0]['name']['$t']
563 ret.append(e)
564 return ret
565
566 def print_heading(self, heading): 513 def print_heading(self, heading):
567 print 514 print
568 print self.options.output_format_heading.format(heading=heading) 515 print self.options.output_format_heading.format(heading=heading)
569 516
570 def print_change(self, change): 517 def print_change(self, change):
571 optional_values = { 518 optional_values = {
572 'reviewers': ', '.join(change['reviewers']) 519 'reviewers': ', '.join(change['reviewers'])
573 } 520 }
574 self.print_generic(self.options.output_format, 521 self.print_generic(self.options.output_format,
575 self.options.output_format_changes, 522 self.options.output_format_changes,
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after
675 self.reviews += reviews 622 self.reviews += reviews
676 623
677 def print_reviews(self): 624 def print_reviews(self):
678 if self.reviews: 625 if self.reviews:
679 self.print_heading('Reviews') 626 self.print_heading('Reviews')
680 for review in self.reviews: 627 for review in self.reviews:
681 self.print_review(review) 628 self.print_review(review)
682 629
683 def get_issues(self): 630 def get_issues(self):
684 for project in google_code_projects: 631 for project in google_code_projects:
685 self.issues += self.google_code_issue_search(project) 632 self.issues += self.project_hosting_issue_search(project)
686 633
687 def print_issues(self): 634 def print_issues(self):
688 if self.issues: 635 if self.issues:
689 self.print_heading('Issues') 636 self.print_heading('Issues')
690 for issue in self.issues: 637 for issue in self.issues:
691 self.print_issue(issue) 638 self.print_issue(issue)
692 639
693 def print_activity(self): 640 def print_activity(self):
694 self.print_changes() 641 self.print_changes()
695 self.print_reviews() 642 self.print_reviews()
(...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after
863 810
864 if __name__ == '__main__': 811 if __name__ == '__main__':
865 # Fix encoding to support non-ascii issue titles. 812 # Fix encoding to support non-ascii issue titles.
866 fix_encoding.fix_encoding() 813 fix_encoding.fix_encoding()
867 814
868 try: 815 try:
869 sys.exit(main()) 816 sys.exit(main())
870 except KeyboardInterrupt: 817 except KeyboardInterrupt:
871 sys.stderr.write('interrupted\n') 818 sys.stderr.write('interrupted\n')
872 sys.exit(1) 819 sys.exit(1)
OLDNEW
« auth.py ('K') | « auth.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698