# Copyright (C) 2013 Google Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import datetime import logging import webapp2 from google.appengine.ext import ndb from google.appengine.ext.webapp import template from google.appengine.ext.db import BadRequestError # A simple log server for rebaseline-o-matic. # # Accepts updates to the same log entry and shows a simple status page. # Has a special state for the case where there are no NeedsRebaseline # lines in TestExpectations to avoid cluttering the log with useless # entries every 30 seconds. # # Other than that, new updatelog calls append to the most recent log # entry until they have the newentry parameter, in which case, it # starts a new log entry. LOG_PARAM = "log" NEW_ENTRY_PARAM = "newentry" # FIXME: no_needs_rebaseline is never used anymore. Remove support for it. # Instead, add UI to logs.html to collapse short entries. NO_NEEDS_REBASELINE_PARAM = "noneedsrebaseline" NUM_LOGS_PARAM = "numlogs" BEFORE_PARAM = "before" class LogEntry(ndb.Model): content = ndb.TextProperty() date = ndb.DateTimeProperty(auto_now_add=True) is_no_needs_rebaseline = ndb.BooleanProperty() def logs_query(): return LogEntry.query().order(-LogEntry.date) class UpdateLog(webapp2.RequestHandler): def post(self): new_log_data = self.request.POST.get(LOG_PARAM) # This entry is set to on whenever a new auto-rebaseline run is going to # start logging entries. If this is not on, then the log will get appended # to the most recent log entry. new_entry = self.request.POST.get(NEW_ENTRY_PARAM) == "on" # The case of no NeedsRebaseline lines in TestExpectations is special-cased # to always overwrite the previous noneedsrebaseline entry in the log to # avoid cluttering the log with useless empty posts. It just updates the # date of the entry so that users can see that rebaseline-o-matic is still # running. # FIXME: no_needs_rebaseline is never used anymore. Remove support for it. no_needs_rebaseline = self.request.POST.get(NO_NEEDS_REBASELINE_PARAM) == "on" out = "Wrote new log entry." if not new_entry or no_needs_rebaseline: log_entries = logs_query().fetch(1) if log_entries: log_entry = log_entries[0] log_entry.date = datetime.datetime.now() if no_needs_rebaseline: # Don't write out a new log entry for repeated no_needs_rebaseline cases. # The repeated entries just add noise to the logs. if log_entry.is_no_needs_rebaseline: out = "Overwrote existing no needs rebaseline log." else: out = "Wrote new no needs rebaseline log." new_entry = True new_log_data = "" elif log_entry.is_no_needs_rebaseline: out = "Previous entry was a no need rebaseline log. Writing a new log." new_entry = True else: out = "Added to existing log entry." log_entry.content = log_entry.content + "\n" + new_log_data if new_entry or not log_entries: log_entry = LogEntry(content=new_log_data, is_no_needs_rebaseline=no_needs_rebaseline) try: log_entry.put() except BadRequestError: out = "Created new log entry because the previous one exceeded the max length." LogEntry(content=new_log_data, is_no_needs_rebaseline=no_needs_rebaseline).put() self.response.out.write(out) class UploadForm(webapp2.RequestHandler): def get(self): self.response.out.write(template.render("uploadform.html", { "update_log_url": "/updatelog", "set_no_needs_rebaseline_url": "/noneedsrebaselines", "log_param": LOG_PARAM, "new_entry_param": NEW_ENTRY_PARAM, "no_needs_rebaseline_param": NO_NEEDS_REBASELINE_PARAM, })) class ShowLatest(webapp2.RequestHandler): def get(self): query = logs_query() before = self.request.get(BEFORE_PARAM) if before: date = datetime.datetime.strptime(before, "%Y-%m-%dT%H:%M:%SZ") query = query.filter(LogEntry.date < date) num_logs = self.request.get(NUM_LOGS_PARAM) logs = query.fetch(int(num_logs) if num_logs else 3) self.response.out.write(template.render("logs.html", { "logs": logs, "num_logs_param": NUM_LOGS_PARAM, "before_param": BEFORE_PARAM, })) routes = [ ('/uploadform', UploadForm), ('/updatelog', UpdateLog), ('/', ShowLatest), ] app = webapp2.WSGIApplication(routes, debug=True)