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

Side by Side Diff: status.py

Issue 84943003: chromium-status: automatically linkify usernames/status messages (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/chromium-status
Patch Set: fix is_email check Created 7 years 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 | Annotate | Revision Log
« no previous file with comments | « no previous file | stylesheets/style.css » ('j') | templates/main.html » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # coding=utf-8 1 # coding=utf-8
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 """Status management pages.""" 6 """Status management pages."""
7 7
8 import datetime 8 import datetime
9 import json 9 import json
10 import re 10 import re
11 11
12 from google.appengine.api import memcache 12 from google.appengine.api import memcache
13 from google.appengine.ext import db 13 from google.appengine.ext import db
14 14
15 from base_page import BasePage 15 from base_page import BasePage
16 import utils 16 import utils
17 17
18 18
19 ALLOWED_ORIGINS = [ 19 ALLOWED_ORIGINS = [
20 'https://gerrit-int.chromium.org', 20 'https://gerrit-int.chromium.org',
21 'https://gerrit.chromium.org', 21 'https://gerrit.chromium.org',
22 ] 22 ]
23 23
24 24
25 class Link(object):
26 """Simple object to hold text that might be linked"""
M-A Ruel 2013/11/25 16:18:14 """Holds text .. No need to state a relative qual
vapier 2013/11/25 17:51:26 i agree it's simple, but i find scanning a one lin
27
28 def __init__(self, text, target=None, is_email=False):
M-A Ruel 2013/11/25 16:18:14 I'd prefer to not use default arguments.
vapier 2013/11/25 17:51:26 any reason why ? the intention is for email to be
29 self.text = text
30 self.target = target
31 self.is_email = is_email
32
33 def __repr__(self):
34 return 'Link({%s->%s})' % (self.text, self.target)
35
36
37 class LinkableText(object):
38 """Turn arbitrary text into a set of links"""
M-A Ruel 2013/11/25 16:18:14 Turns (imperative everywhere)
vapier 2013/11/25 17:51:26 i dont see the point, but i also dont really care
39
40 GERRIT_URLS = {
41 'chrome': 'https://chrome-internal-review.googlesource.com',
M-A Ruel 2013/11/25 16:18:14 Very hardcoded. :/ Could these be entities?
vapier 2013/11/25 17:51:26 i have no idea what "entities" are. plus this fil
42 'chromium': 'https://chromium-review.googlesource.com',
43 }
44
45 WATERFALL_URLS = {
46 'chromeos': 'http://chromegw/i/chromeos',
47 'chromiumos': 'http://build.chromium.org/p/chromiumos',
48 }
49
50 # Automatically linkify convert known strings for the user.
51 _CONVERTS = [
52 # Convert CrOS bug links. Support the forms:
53 # http://crbug.com/1234
54 # http://crosbug.com/1234
55 # crbug/1234
56 # crosbug/p/1234
57 (re.compile(
58 # 1 2 3 4 5 6 7
59 r'\b((http://)?((crbug|crosbug)(\.com)?(/(p/)?[0-9]+)))\b',
60 flags=re.I),
61 r'http://\4.com\6', r'\1', False),
62
63 # Convert e-mail addresses.
64 (re.compile(r'(([-+.a-z0-9_!#$%&*/=?^_`{|}~]+)@[-a-z0-9.]+\.[a-z0-9]+)\b',
65 flags=re.I),
66 r'\1', r'\2', True),
67
68 # Convert SHA1's to gerrit links. Assume all external since
69 # there is no sane way to detect it's an internal CL.
70 (re.compile(r'\b([0-9a-f]{40})\b', flags=re.I),
71 r'%s/#q,\1,n,z' % GERRIT_URLS['chromium'], r'\1', False),
72
73 # Convert public gerrit CL numbers.
74 (re.compile(r'\b(CL:([0-9]+))\b', flags=re.I),
75 r'%s/\2' % GERRIT_URLS['chromium'], r'\1', False),
76 # Convert internal gerrit CL numbers.
77 (re.compile(r'\b(CL:\*([0-9]+))\b', flags=re.I),
78 r'%s/\2' % GERRIT_URLS['chrome'], r'\1', False),
79 ]
80
81 @classmethod
82 def bootstrap(cls, _app_name):
83 """Add conversions specific to |app_name| instance"""
84 cls._CONVERTS += [
85 # Do this for everyone since "cbuildbot" is unique to CrOS.
86 # Match the string:
87 # Automatic: "cbuildbot" on "x86-generic ASAN" from.
88 (re.compile(r'("cbuildbot" on "([^"]+ canary)")',
M-A Ruel 2013/11/25 16:18:14 These should be stated as static lists. There isn'
vapier 2013/11/25 17:51:26 this was needed in a previous CL where i used a fu
89 flags=re.I),
90 r'%s/builders/\2' % cls.WATERFALL_URLS['chromeos'], r'\1', False),
91 (re.compile(r'("cbuildbot" on "([^"]+)")',
92 flags=re.I),
93 r'%s/builders/\2' % cls.WATERFALL_URLS['chromiumos'], r'\1', False),
94 ]
95
96 @classmethod
97 def parse(cls, text):
98 """Create a list of Link objects based on |text|"""
99 if not text:
100 return []
101 for prog, target, pretty_text, is_email in cls._CONVERTS:
102 m = prog.search(text)
103 if m:
104 link = Link(m.expand(pretty_text),
105 target=m.expand(target),
106 is_email=is_email)
107 left_links = cls.parse(text[:m.start()].rstrip())
108 right_links = cls.parse(text[m.end():].lstrip())
109 return left_links + [link] + right_links
110 return [Link(text)]
111
112 def __init__(self, text):
113 self.raw_text = text
114 self.links = self.parse(text.strip())
115
116 def __str__(self):
117 return self.raw_text
118
119
25 class Status(db.Model): 120 class Status(db.Model):
26 """Description for the status table.""" 121 """Description for the status table."""
27 # The username who added this status. 122 # The username who added this status.
28 username = db.StringProperty(required=True) 123 username = db.StringProperty(required=True)
29 # The date when the status got added. 124 # The date when the status got added.
30 date = db.DateTimeProperty(auto_now_add=True) 125 date = db.DateTimeProperty(auto_now_add=True)
31 # The message. It can contain html code. 126 # The message. It can contain html code.
32 message = db.StringProperty(required=True) 127 message = db.StringProperty(required=True)
33 128
129 def __init__(self, *args, **kwargs):
130 # Normalize newlines otherwise the DB store barfs.
M-A Ruel 2013/11/25 16:18:14 I'm fine with adding multiline=True instead. Actua
vapier 2013/11/25 17:51:26 i don't think we want people to insert newlines in
131 kwargs['message'] = kwargs.get('message', '').replace('\n', '')
132
133 super(Status, self).__init__(*args, **kwargs)
134 self.username_links = LinkableText(self.username)
135 self.message_links = LinkableText(self.message)
136
34 @property 137 @property
35 def general_state(self): 138 def general_state(self):
36 """Returns a string representing the state that the status message 139 """Returns a string representing the state that the status message
37 describes. 140 describes.
38 """ 141 """
39 message = self.message 142 message = self.message
40 closed = re.search('close', message, re.IGNORECASE) 143 closed = re.search('close', message, re.IGNORECASE)
41 if closed and re.search('maint', message, re.IGNORECASE): 144 if closed and re.search('maint', message, re.IGNORECASE):
42 return 'maintenance' 145 return 'maintenance'
43 if re.search('throt', message, re.IGNORECASE): 146 if re.search('throt', message, re.IGNORECASE):
(...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after
239 342
240 # NOTE: This is require_login in order to ensure that authentication doesn't 343 # NOTE: This is require_login in order to ensure that authentication doesn't
241 # happen while changing the tree status. 344 # happen while changing the tree status.
242 @utils.requires_login 345 @utils.requires_login
243 @utils.requires_read_access 346 @utils.requires_read_access
244 def get(self): 347 def get(self):
245 return self._handle() 348 return self._handle()
246 349
247 def _handle(self, error_message='', last_message=''): 350 def _handle(self, error_message='', last_message=''):
248 """Sets the information to be displayed on the main page.""" 351 """Sets the information to be displayed on the main page."""
352 LinkableText.bootstrap(self.APP_NAME)
M-A Ruel 2013/11/25 16:18:14 This is a bad idea to do this there. It creates a
vapier 2013/11/25 17:51:26 i'm not terribly familiar with the app engine mode
353
249 try: 354 try:
250 limit = min(max(int(self.request.get('limit')), 1), 1000) 355 limit = min(max(int(self.request.get('limit')), 1), 1000)
251 except ValueError: 356 except ValueError:
252 limit = 25 357 limit = 25
253 status = get_last_statuses(limit) 358 status = get_last_statuses(limit)
254 current_status = get_status() 359 current_status = get_status()
255 if not last_message: 360 if not last_message:
256 last_message = current_status.message 361 last_message = current_status.message
257 362
258 template_values = self.InitializeTemplate(self.APP_NAME + ' Tree Status') 363 template_values = self.InitializeTemplate(self.APP_NAME + ' Tree Status')
(...skipping 30 matching lines...) Expand all
289 return self._handle(error_message, last_message) 394 return self._handle(error_message, last_message)
290 else: 395 else:
291 put_status(Status(message=new_message, username=self.user.email())) 396 put_status(Status(message=new_message, username=self.user.email()))
292 self.redirect("/") 397 self.redirect("/")
293 398
294 399
295 def bootstrap(): 400 def bootstrap():
296 # Guarantee that at least one instance exists. 401 # Guarantee that at least one instance exists.
297 if db.GqlQuery('SELECT __key__ FROM Status').get() is None: 402 if db.GqlQuery('SELECT __key__ FROM Status').get() is None:
298 Status(username='none', message='welcome to status').put() 403 Status(username='none', message='welcome to status').put()
OLDNEW
« no previous file with comments | « no previous file | stylesheets/style.css » ('j') | templates/main.html » ('J')

Powered by Google App Engine
This is Rietveld 408576698