1e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# Copyright (C) 2013 Google Inc. All rights reserved.
2e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)#
3e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# Redistribution and use in source and binary forms, with or without
4e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# modification, are permitted provided that the following conditions are
5e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# met:
6e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)#
7e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)#     * Redistributions of source code must retain the above copyright
8e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# notice, this list of conditions and the following disclaimer.
9e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)#     * Redistributions in binary form must reproduce the above
10e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# copyright notice, this list of conditions and the following disclaimer
11e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# in the documentation and/or other materials provided with the
12e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# distribution.
13e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)#     * Neither the name of Google Inc. nor the names of its
14e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# contributors may be used to endorse or promote products derived from
15e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# this software without specific prior written permission.
16e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)#
17e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
29e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)import datetime
30e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)import logging
31e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)import webapp2
32e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
33e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)from google.appengine.ext import ndb
34e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)from google.appengine.ext.webapp import template
35f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)from google.appengine.ext.db import BadRequestError
36e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
37e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# A simple log server for rebaseline-o-matic.
38e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)#
39e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# Accepts updates to the same log entry and shows a simple status page.
40e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# Has a special state for the case where there are no NeedsRebaseline
41e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# lines in TestExpectations to avoid cluttering the log with useless
42e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# entries every 30 seconds.
43e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)#
44e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# Other than that, new updatelog calls append to the most recent log
45e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# entry until they have the newentry parameter, in which case, it
46e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)# starts a new log entry.
47e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
48e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)LOG_PARAM = "log"
49e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)NEW_ENTRY_PARAM = "newentry"
505d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles)# FIXME: no_needs_rebaseline is never used anymore. Remove support for it.
515d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles)# Instead, add UI to logs.html to collapse short entries.
52e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)NO_NEEDS_REBASELINE_PARAM = "noneedsrebaseline"
53e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)NUM_LOGS_PARAM = "numlogs"
54e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)BEFORE_PARAM = "before"
55e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
56e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
57e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)class LogEntry(ndb.Model):
58e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)    content = ndb.TextProperty()
59e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)    date = ndb.DateTimeProperty(auto_now_add=True)
60e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)    is_no_needs_rebaseline = ndb.BooleanProperty()
61e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
62e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
63e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)def logs_query():
64e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)    return LogEntry.query().order(-LogEntry.date)
65e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
66e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
67e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)class UpdateLog(webapp2.RequestHandler):
68e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)    def post(self):
69e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        new_log_data = self.request.POST.get(LOG_PARAM)
70e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        # This entry is set to on whenever a new auto-rebaseline run is going to
71e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        # start logging entries. If this is not on, then the log will get appended
72e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        # to the most recent log entry.
73e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        new_entry = self.request.POST.get(NEW_ENTRY_PARAM) == "on"
74e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        # The case of no NeedsRebaseline lines in TestExpectations is special-cased
75e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        # to always overwrite the previous noneedsrebaseline entry in the log to
76e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        # avoid cluttering the log with useless empty posts. It just updates the
77e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        # date of the entry so that users can see that rebaseline-o-matic is still
78e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        # running.
795d92fedcae5e801a8b224de090094f2d9df0b54aTorne (Richard Coles)        # FIXME: no_needs_rebaseline is never used anymore. Remove support for it.
80e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        no_needs_rebaseline = self.request.POST.get(NO_NEEDS_REBASELINE_PARAM) == "on"
81e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
82e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        out = "Wrote new log entry."
83e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        if not new_entry or no_needs_rebaseline:
84e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)            log_entries = logs_query().fetch(1)
85e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)            if log_entries:
86e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                log_entry = log_entries[0]
87e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                log_entry.date = datetime.datetime.now()
88e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                if no_needs_rebaseline:
89e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                    # Don't write out a new log entry for repeated no_needs_rebaseline cases.
90e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                    # The repeated entries just add noise to the logs.
91e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                    if log_entry.is_no_needs_rebaseline:
92e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                        out = "Overwrote existing no needs rebaseline log."
93e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                    else:
94e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                        out = "Wrote new no needs rebaseline log."
95e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                        new_entry = True
96e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                        new_log_data = ""
97e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                elif log_entry.is_no_needs_rebaseline:
98e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                    out = "Previous entry was a no need rebaseline log. Writing a new log."
99e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                    new_entry = True
100e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                else:
101e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                    out = "Added to existing log entry."
102e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)                    log_entry.content = log_entry.content + "\n" + new_log_data
103e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
104e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        if new_entry or not log_entries:
105e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)            log_entry = LogEntry(content=new_log_data, is_no_needs_rebaseline=no_needs_rebaseline)
106e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
107f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)        try:
108f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)            log_entry.put()
109f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)        except BadRequestError:
110f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)            out = "Created new log entry because the previous one exceeded the max length."
111f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)            LogEntry(content=new_log_data, is_no_needs_rebaseline=no_needs_rebaseline).put()
112f6b7aed3f7ce69aca0d7a032d144cbd088b04393Torne (Richard Coles)
113e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        self.response.out.write(out)
114e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
115e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
116e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)class UploadForm(webapp2.RequestHandler):
117e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)    def get(self):
118e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        self.response.out.write(template.render("uploadform.html", {
119e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)            "update_log_url": "/updatelog",
120e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)            "set_no_needs_rebaseline_url": "/noneedsrebaselines",
121e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)            "log_param": LOG_PARAM,
122e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)            "new_entry_param": NEW_ENTRY_PARAM,
123e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)            "no_needs_rebaseline_param": NO_NEEDS_REBASELINE_PARAM,
124e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        }))
125e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
126e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
127e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)class ShowLatest(webapp2.RequestHandler):
128e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)    def get(self):
129e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        query = logs_query()
130e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
131e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        before = self.request.get(BEFORE_PARAM)
132e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        if before:
133e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)            date = datetime.datetime.strptime(before, "%Y-%m-%dT%H:%M:%SZ")
134e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)            query = query.filter(LogEntry.date < date)
135e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
136e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        num_logs = self.request.get(NUM_LOGS_PARAM)
137e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        logs = query.fetch(int(num_logs) if num_logs else 3)
138e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
139e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        self.response.out.write(template.render("logs.html", {
140e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)            "logs": logs,
141e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)            "num_logs_param": NUM_LOGS_PARAM,
142e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)            "before_param": BEFORE_PARAM,
143e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)        }))
144e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
145e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
146e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)routes = [
147e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)    ('/uploadform', UploadForm),
148e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)    ('/updatelog', UpdateLog),
149e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)    ('/', ShowLatest),
150e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)]
151e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)
152e1f1df5f01594c0e62e751e4b46e779b85c2faa5Torne (Richard Coles)app = webapp2.WSGIApplication(routes, debug=True)
153