OLD | NEW |
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""" |
| 27 |
| 28 def __init__(self, text, target=None, is_email=False): |
| 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""" |
| 39 |
| 40 GERRIT_URLS = { |
| 41 'chrome': 'https://chrome-internal-review.googlesource.com', |
| 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)")', |
| 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. |
| 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 Loading... |
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) |
| 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 Loading... |
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() |
OLD | NEW |