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

Side by Side Diff: Tools/GardeningServer/alerts.py

Issue 633983002: Implemented retrieval of alerts history (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Refactored code and added tests Created 6 years, 2 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 | Tools/GardeningServer/alerts_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import calendar 5 import calendar
6 import datetime 6 import datetime
7 import json 7 import json
8 import logging 8 import logging
9 import webapp2 9 import webapp2
10 import zlib 10 import zlib
11 11
12 from google.appengine.api import memcache 12 from google.appengine.api import memcache
13 from google.appengine.api import users
14 from google.appengine.datastore import datastore_query
13 from google.appengine.ext import ndb 15 from google.appengine.ext import ndb
14 16
15 LOGGER = logging.getLogger(__name__) 17 LOGGER = logging.getLogger(__name__)
16 18
17 19
18 class DateTimeEncoder(json.JSONEncoder): 20 class DateTimeEncoder(json.JSONEncoder):
19 def default(self, obj): 21 def default(self, obj):
20 if isinstance(obj, datetime.datetime): 22 if isinstance(obj, datetime.datetime):
21 return calendar.timegm(obj.timetuple()) 23 return calendar.timegm(obj.timetuple())
22 # Let the base class default method raise the TypeError. 24 # Let the base class default method raise the TypeError.
23 return json.JSONEncoder.default(self, obj) 25 return json.JSONEncoder.default(self, obj)
24 26
25 27
26 class AlertsJSON(ndb.Model): 28 class AlertsJSON(ndb.Model):
29 key = ndb.StringProperty()
Z_DONOTUSE 2014/10/08 01:08:42 key implies uniqueness to me. How about calling th
Sergiy Byelozyorov 2014/10/08 12:50:26 Done.
27 json = ndb.BlobProperty(compressed=True) 30 json = ndb.BlobProperty(compressed=True)
28 date = ndb.DateTimeProperty(auto_now_add=True) 31 date = ndb.DateTimeProperty(auto_now_add=True)
29 32
30 33
31 class AlertsHandler(webapp2.RequestHandler): 34 class AlertsHandler(webapp2.RequestHandler):
32 MEMCACHE_ALERTS_KEY = 'alerts' 35 ALERTS_KEY = 'alerts'
33 36
34 # Has no 'response' member. 37 # Has no 'response' member.
35 # pylint: disable=E1101 38 # pylint: disable=E1101
36 def send_json_headers(self): 39 def send_json_headers(self):
37 self.response.headers.add_header('Access-Control-Allow-Origin', '*') 40 self.response.headers.add_header('Access-Control-Allow-Origin', '*')
38 self.response.headers['Content-Type'] = 'application/json' 41 self.response.headers['Content-Type'] = 'application/json'
39 42
40 # Has no 'response' member. 43 # Has no 'response' member.
41 # pylint: disable=E1101 44 # pylint: disable=E1101
42 def send_json_data(self, data): 45 def send_json_data(self, data):
43 self.send_json_headers() 46 self.send_json_headers()
44 self.response.write(data) 47 self.response.write(data)
45 48
46 def generate_json_dump(self, alerts): 49 def generate_json_dump(self, alerts):
47 return json.dumps(alerts, cls=DateTimeEncoder, indent=1) 50 return json.dumps(alerts, cls=DateTimeEncoder, indent=1)
48 51
49 def get_from_memcache(self, memcache_key): 52 def get_from_memcache(self, memcache_key):
50 compressed = memcache.get(memcache_key) 53 compressed = memcache.get(memcache_key)
51 if not compressed: 54 if not compressed:
52 self.send_json_headers() 55 self.send_json_headers()
53 return 56 return
54 uncompressed = zlib.decompress(compressed) 57 uncompressed = zlib.decompress(compressed)
55 self.send_json_data(uncompressed) 58 self.send_json_data(uncompressed)
56 59
57 def get(self): 60 def get(self):
58 self.get_from_memcache(AlertsHandler.MEMCACHE_ALERTS_KEY) 61 self.get_from_memcache(AlertsHandler.ALERTS_KEY)
59 62
60 def save_alerts_to_history(self, alerts): 63 def post_to_history(self, alerts_key, alerts):
61 last_entry = AlertsJSON.query().order(-AlertsJSON.date).get() 64 last_entry = AlertsJSON.query().order(-AlertsJSON.date).get()
62 last_alerts = json.loads(last_entry.json) if last_entry else {} 65 last_alerts = json.loads(last_entry.json) if last_entry else {}
63 66
64 # Only changes to the fields with 'alerts' in the name should cause a 67 # Only changes to the fields with 'alerts' in the name should cause a
65 # new history entry to be saved. 68 # new history entry to be saved.
66 def alert_fields(alerts_json): 69 def alert_fields(alerts_json):
67 filtered_json = {} 70 filtered_json = {}
68 for key, value in alerts_json.iteritems(): 71 for key, value in alerts_json.iteritems():
69 if 'alerts' in key: 72 if 'alerts' in key:
70 filtered_json[key] = value 73 filtered_json[key] = value
71 return filtered_json 74 return filtered_json
72 75
73 if alert_fields(last_alerts) != alert_fields(alerts): 76 if alert_fields(last_alerts) != alert_fields(alerts):
74 new_entry = AlertsJSON(json=self.generate_json_dump(alerts)) 77 new_entry = AlertsJSON(
78 json=self.generate_json_dump(alerts),
79 key=alerts_key)
75 new_entry.put() 80 new_entry.put()
76 81
77 # Has no 'response' member. 82 # Has no 'response' member.
78 # pylint: disable=E1101 83 # pylint: disable=E1101
79 def post_to_memcache(self, memcache_key, alerts): 84 def post_to_memcache(self, memcache_key, alerts):
80 uncompressed = self.generate_json_dump(alerts) 85 uncompressed = self.generate_json_dump(alerts)
81 compression_level = 1 86 compression_level = 1
82 compressed = zlib.compress(uncompressed, compression_level) 87 compressed = zlib.compress(uncompressed, compression_level)
83 memcache.set(memcache_key, compressed) 88 memcache.set(memcache_key, compressed)
84 89
85 def parse_alerts(self, alerts_json): 90 def parse_alerts(self, alerts_json):
86 try: 91 try:
87 alerts = json.loads(alerts_json) 92 alerts = json.loads(alerts_json)
88 except ValueError: 93 except ValueError:
89 warning = 'content field was not JSON' 94 warning = 'content field was not JSON'
90 self.response.set_status(400, warning) 95 self.response.set_status(400, warning)
91 LOGGER.warn(warning) 96 LOGGER.warn(warning)
92 return 97 return
93 98
94 alerts.update({'date': datetime.datetime.utcnow()}) 99 alerts.update({'date': datetime.datetime.utcnow()})
95 100
96 return alerts 101 return alerts
97 102
98 def update_alerts(self, memcache_key): 103 def update_alerts(self, alerts_key):
99 alerts = self.parse_alerts(self.request.get('content')) 104 alerts = self.parse_alerts(self.request.get('content'))
100 if alerts: 105 if alerts:
101 self.post_to_memcache(memcache_key, alerts) 106 self.post_to_memcache(alerts_key, alerts)
102 self.save_alerts_to_history(alerts) 107 self.post_to_history(alerts_key, alerts)
103 108
104 def post(self): 109 def post(self):
105 self.update_alerts(AlertsHandler.MEMCACHE_ALERTS_KEY) 110 self.update_alerts(AlertsHandler.ALERTS_KEY)
111
112
113 class AlertsHistory(webapp2.RequestHandler):
114 MAX_LIMIT_PER_PAGE = 100
115
116 def get(self):
117 alerts_query = AlertsJSON.query().order(-AlertsJSON.date)
118
119 # Return only public alerts for non-internal users.
120 user = users.get_current_user()
121 if not user or not user.email().endswith('@google.com'):
122 alerts_query = alerts_query.filter(
123 AlertsJSON.key == AlertsHandler.ALERTS_KEY)
124
125 cursor = self.request.get('cursor')
126 if cursor:
127 cursor = datastore_query.Cursor(urlsafe=cursor)
128
129 limit = int(self.request.get('limit', self.MAX_LIMIT_PER_PAGE))
130 limit = min(self.MAX_LIMIT_PER_PAGE, limit)
131
132 if cursor:
133 alerts, next_cursor, has_more = alerts_query.fetch_page(
134 limit, start_cursor=cursor)
135 else:
136 alerts, next_cursor, has_more = alerts_query.fetch_page(limit)
137
138 combined_json = {
139 'has_more': has_more,
140 'cursor': next_cursor.urlsafe() if next_cursor else '',
141 'history': [json.loads(alert.json) for alert in alerts]
142 }
143
144 self.response.headers['Content-Type'] = 'application/json'
145 self.response.out.write(json.dumps(combined_json))
106 146
107 147
108 app = webapp2.WSGIApplication([ 148 app = webapp2.WSGIApplication([
109 ('/alerts', AlertsHandler) 149 ('/alerts', AlertsHandler),
150 ('/alerts-history', AlertsHistory)
110 ]) 151 ])
OLDNEW
« no previous file with comments | « no previous file | Tools/GardeningServer/alerts_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698