blob: b5eba3695f2559c2e55d8089fe3eae2563cc5f18 [file] [log] [blame]
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Sheriff schedule pages."""
import datetime
import json
from google.appengine.ext import db
from base_page import BasePage
import utils
FETCH_LIMIT = 50
HEADER_CONTENT_TYPE = 'Content-Type'
HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = 'Access-Control-Allow-Origin'
JSON_CONTENT_TYPE = 'application/json'
TEXT_CONTENT_TYPE = 'text/plain'
class BaseSheriffs(db.Model):
"""Contains the list of Sheriffs"""
username = db.StringProperty(required=True)
@classmethod
def get_all_sheriffs(cls):
return cls.all().fetch(limit=FETCH_LIMIT)
class Sheriffs(BaseSheriffs):
"""Contains the list of Skia Sheriffs."""
pass
class GpuSheriffs(BaseSheriffs):
"""Contains the list of GPU Sheriffs."""
pass
class Robocops(BaseSheriffs):
"""Contains the list of Android Robocops."""
pass
class Troopers(BaseSheriffs):
"""Contains the list of Infra Troopers."""
pass
class BaseSheriffSchedules(db.Model):
"""A single sheriff oncall rotation (one person, one time interval)."""
schedule_start = db.DateTimeProperty(required=True)
schedule_end = db.DateTimeProperty(required=True)
username = db.StringProperty(required=True)
@classmethod
def get_upcoming_schedules(cls):
return (cls.all()
.filter('schedule_end >', datetime.datetime.now())
.order('schedule_end')
.fetch(limit=FETCH_LIMIT))
@classmethod
def get_schedules_for_sheriff(cls, sheriff):
return (cls.all()
.filter('username =', sheriff)
.order('schedule_end')
.fetch(limit=FETCH_LIMIT))
@classmethod
def get_current_sheriff(cls):
current_schedule = cls.all().filter(
'schedule_end >', datetime.datetime.now()).get()
if current_schedule:
return current_schedule.username
else:
return 'None'
@classmethod
def delete_schedules_after(cls, datetime_obj):
schedules = cls.all().filter(
'schedule_end >', datetime_obj).fetch(limit=FETCH_LIMIT)
for schedule in schedules:
schedule.delete()
def AsDict(self, display_year=False):
data = super(BaseSheriffSchedules, self).AsDict()
data['username'] = self.username
date_format = '%m/%d/%y' if display_year else '%m/%d'
data['schedule_start'] = self.schedule_start.strftime(date_format)
data['schedule_end'] = self.schedule_end.strftime(date_format)
return data
class SheriffSchedules(BaseSheriffSchedules):
"""A single Skia sheriff oncall rotation (one person, one time interval)."""
pass
class GpuSheriffSchedules(BaseSheriffSchedules):
"""A single GPU sheriff oncall rotation (one person, one time interval)."""
pass
class RobocopSchedules(BaseSheriffSchedules):
"""A single robocop oncall rotation (one person, one time interval)."""
pass
class TrooperSchedules(BaseSheriffSchedules):
"""A single trooper oncall rotation (one person, one time interval)."""
pass
class QuerySheriffPage(BasePage):
"""Displays the schedules of the provided sheriff."""
def get(self):
self.response.headers[HEADER_CONTENT_TYPE] = JSON_CONTENT_TYPE
self.response.headers[HEADER_ACCESS_CONTROL_ALLOW_ORIGIN] = '*'
username = self.request.get('username')
data = json.dumps({})
for sheriff in Sheriffs.get_all_sheriffs():
if sheriff.username.startswith(username):
schedules = []
for schedule in SheriffSchedules.get_schedules_for_sheriff(
sheriff.username):
schedules.append(schedule.AsDict(display_year=True))
data = json.dumps({sheriff.username: schedules})
break
self.response.out.write(data)
class BaseCurrentSheriffPage(BasePage):
"""BasePage to display the current sheriff and schedule in JSON."""
@property
def sheriff_schedules_class(self):
raise NotImplementedError, "Must be implemented by subclasses."
def get(self):
self.response.headers[HEADER_CONTENT_TYPE] = JSON_CONTENT_TYPE
self.response.headers[HEADER_ACCESS_CONTROL_ALLOW_ORIGIN] = '*'
upcoming_schedules = self.sheriff_schedules_class.get_upcoming_schedules()
if upcoming_schedules:
data = json.dumps(upcoming_schedules[0].AsDict())
else:
data = json.dumps({})
callback = self.request.get('callback')
if callback:
data = callback + '(' + data + ')'
self.response.out.write(data)
class CurrentSheriffPage(BaseCurrentSheriffPage):
"""Displays the current sheriff and schedule in JSON."""
@property
def sheriff_schedules_class(self):
return SheriffSchedules
class CurrentGpuSheriffPage(BaseCurrentSheriffPage):
"""Displays the current GPU sheriff and schedule in JSON."""
@property
def sheriff_schedules_class(self):
return GpuSheriffSchedules
class CurrentRobocopPage(BaseCurrentSheriffPage):
"""Displays the current robocop and schedule in JSON."""
@property
def sheriff_schedules_class(self):
return RobocopSchedules
class CurrentTrooperPage(BaseCurrentSheriffPage):
"""Displays the current trooper and schedule in JSON."""
@property
def sheriff_schedules_class(self):
return TrooperSchedules
class BaseNextSheriffPage(BasePage):
"""BasePage to display the next sheriff and schedule in JSON."""
@property
def sheriff_schedules_class(self):
raise NotImplementedError, "Must be implemented by subclasses."
def get(self):
self.response.headers[HEADER_CONTENT_TYPE] = JSON_CONTENT_TYPE
self.response.headers[HEADER_ACCESS_CONTROL_ALLOW_ORIGIN] = '*'
upcoming_schedules = self.sheriff_schedules_class.get_upcoming_schedules()
if upcoming_schedules and len(upcoming_schedules) > 1:
data = json.dumps(upcoming_schedules[1].AsDict())
else:
data = json.dumps({})
callback = self.request.get('callback')
if callback:
data = callback + '(' + data + ')'
self.response.out.write(data)
class NextSheriffPage(BaseNextSheriffPage):
"""Displays the next sheriff and schedule in JSON."""
@property
def sheriff_schedules_class(self):
return SheriffSchedules
class NextGpuSheriffPage(BaseNextSheriffPage):
"""Displays the next GPU sheriff and schedule in JSON."""
@property
def sheriff_schedules_class(self):
return GpuSheriffSchedules
class NextRobocopPage(BaseNextSheriffPage):
"""Displays the next Android robocop and schedule in JSON."""
@property
def sheriff_schedules_class(self):
return RobocopSchedules
class NextTrooperPage(BaseNextSheriffPage):
"""Displays the next Infra trooper and schedule in JSON."""
@property
def sheriff_schedules_class(self):
return TrooperSchedules
class BaseSheriffPage(BasePage):
"""BasePage to display the list and rotation schedule of all sheriffs."""
@property
def sheriff_schedules_class(self):
raise NotImplementedError, "Must be implemented by subclasses."
@property
def sheriff_doc(self):
raise NotImplementedError, "Must be implemented by subclasses."
@property
def sheriff_type(self):
raise NotImplementedError, "Must be implemented by subclasses."
@property
def sheriff_image(self):
raise NotImplementedError, "Must be implemented by subclasses."
@utils.require_user
def get(self):
template_values = self.InitializeTemplate(
self.APP_NAME + ' Sheriff Rotation Schedule')
upcoming_schedules = []
for upcoming_schedule in (
self.sheriff_schedules_class.get_upcoming_schedules()):
schedule_start = upcoming_schedule.schedule_start
schedule_end = upcoming_schedule.schedule_end
upcoming_schedule.readable_range = '%s - %s' % (
schedule_start.strftime('%d %B'), schedule_end.strftime('%d %B'))
upcoming_schedules.append(upcoming_schedule)
if upcoming_schedules:
# The first in the list is the current week.
upcoming_schedules[0].current_week = True
# Set the schedules to the template
template_values['schedules'] = upcoming_schedules
template_values['sheriff_doc'] = self.sheriff_doc
template_values['sheriff_type'] = self.sheriff_type
template_values['sheriff_image'] = self.sheriff_image
self.DisplayTemplate('sheriffs.html', template_values)
class SheriffPage(BaseSheriffPage):
"""Displays the list and rotation schedule of all sheriffs."""
@property
def sheriff_schedules_class(self):
return SheriffSchedules
@property
def sheriff_doc(self):
return 'https://skia.org/dev/sheriffing'
@property
def sheriff_type(self):
return 'Sheriff'
@property
def sheriff_image(self):
return 'clint.jpg'
class GpuSheriffPage(BaseSheriffPage):
"""Displays the list and rotation schedule of all GPU sheriffs."""
@property
def sheriff_schedules_class(self):
return GpuSheriffSchedules
@property
def sheriff_doc(self):
return 'https://skia.org/dev/sheriffing/gpu'
@property
def sheriff_type(self):
return 'GPU Wrangler'
@property
def sheriff_image(self):
return 'wrangler.jpg'
class RobocopPage(BaseSheriffPage):
"""Displays the list and rotation schedule of all Android robocops."""
@property
def sheriff_schedules_class(self):
return RobocopSchedules
@property
def sheriff_doc(self):
return 'https://skia.org/dev/sheriffing/android'
@property
def sheriff_type(self):
return 'Android Robocop'
@property
def sheriff_image(self):
return 'robocop.jpg'
class TrooperPage(BaseSheriffPage):
"""Displays the list and rotation schedule of all Infra troopers."""
@property
def sheriff_schedules_class(self):
return TrooperSchedules
@property
def sheriff_doc(self):
return 'https://skia.org/dev/sheriffing/trooper'
@property
def sheriff_type(self):
return 'Infra Trooper'
@property
def sheriff_image(self):
return 'trooper.jpg'
class BaseUpdateSheriffsSchedulePage(BasePage):
"""BasePage to set the sheriff schedule."""
@property
def sheriff_schedules_class(self):
raise NotImplementedError, "Must be implemented by subclasses."
@property
def sheriff_class(self):
raise NotImplementedError, "Must be implemented by subclasses."
@utils.require_user
def get(self):
# Read the schedule_start and weeks URL get parameters.
schedule_start_tokens = self.request.get('schedule_start').split('/')
schedule_start = datetime.datetime(int(schedule_start_tokens[2]),
int(schedule_start_tokens[0]),
int(schedule_start_tokens[1]))
weeks = int(self.request.get('weeks'))
# Get the list of sheriffs from the Sheriffs table.
sheriffs = []
for sheriff in self.sheriff_class.get_all_sheriffs():
sheriffs.append(sheriff.username)
# Delete all entries greater than the specified start time.
self.sheriff_schedules_class.delete_schedules_after(schedule_start)
# Populate the specified weeks with the sheriffs.
sheriffs_index = 0
while weeks > 0:
curr_schedule_end = schedule_start + datetime.timedelta(days=6)
self.sheriff_schedules_class(
schedule_start=schedule_start,
schedule_end=curr_schedule_end,
username=sheriffs[sheriffs_index]).put()
# Treat sheriffs like a circular array.
sheriffs_index = (sheriffs_index + 1) % len(sheriffs)
# Get the new schedule_start datetime.
schedule_start = schedule_start + datetime.timedelta(days=7)
# Decrement the number of weeks left to be filled.
weeks -= 1
class UpdateSheriffsSchedule(BaseUpdateSheriffsSchedulePage):
"""Sets the sheriff schedule.
Usage:
update_sheriffs_schedules?schedule_start=3/11/2013&weeks=10
The above will populate the schedule for 10 weeks starting on March 11th using
all the sheriffs in a round robin manner from the Sheriffs table.
"""
@property
def sheriff_schedules_class(self):
return SheriffSchedules
@property
def sheriff_class(self):
return Sheriffs
class UpdateGpuSheriffsSchedule(BaseUpdateSheriffsSchedulePage):
"""Sets the GPU sheriff schedule.
Usage:
update_gpu_sheriffs_schedules?schedule_start=3/11/2013&weeks=10
The above will populate the schedule for 10 weeks starting on March 11th using
all the sheriffs in a round robin manner from the GPUSheriffs table.
"""
@property
def sheriff_schedules_class(self):
return GpuSheriffSchedules
@property
def sheriff_class(self):
return GpuSheriffs
class UpdateRobocopsSchedule(BaseUpdateSheriffsSchedulePage):
"""Sets the Android robocop schedule.
Usage:
update_robocop_schedules?schedule_start=3/11/2013&weeks=10
The above will populate the schedule for 10 weeks starting on March 11th using
all the sheriffs in a round robin manner from the Robocops table.
"""
@property
def sheriff_schedules_class(self):
return RobocopSchedules
@property
def sheriff_class(self):
return Robocops
class UpdateTroopersSchedule(BaseUpdateSheriffsSchedulePage):
"""Sets the Infra trooper schedule.
Usage:
update_troopers_schedules?schedule_start=3/11/2013&weeks=10
The above will populate the schedule for 10 weeks starting on March 11th using
all the sheriffs in a round robin manner from the Troopers table.
"""
@property
def sheriff_schedules_class(self):
return TrooperSchedules
@property
def sheriff_class(self):
return Troopers
class ListSheriffsPage(BasePage):
"""Lists all Skia Sheriffs."""
def get(self):
self.response.headers[HEADER_CONTENT_TYPE] = TEXT_CONTENT_TYPE
self.response.headers[HEADER_ACCESS_CONTROL_ALLOW_ORIGIN] = '*'
for sheriff in Sheriffs.get_all_sheriffs():
self.response.out.write(sheriff.username + '\n')
def bootstrap():
# Guarantee that at least one instance exists.
for sheriff_class in (Sheriffs, GpuSheriffs, Robocops, Troopers):
if db.GqlQuery(
'SELECT __key__ FROM ' + sheriff_class.__name__).get() is None:
sheriff_class(username='None').put()
for sheriff_schedules_class in (SheriffSchedules, GpuSheriffSchedules,
RobocopSchedules, TrooperSchedules):
if db.GqlQuery(
'SELECT __key__ FROM ' +
sheriff_schedules_class.__name__).get() is None:
sheriff_schedules_class(
schedule_start=datetime.datetime(1970, 1, 1),
schedule_end=datetime.datetime(1970, 1, 7),
username='None').put()