Skip to content

Commit

Permalink
Blink component ownners /admin/blink endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
ebidel committed Jun 30, 2017
1 parent 87f10b2 commit e32d0b6
Show file tree
Hide file tree
Showing 13 changed files with 377 additions and 59 deletions.
2 changes: 1 addition & 1 deletion admin.py
Expand Up @@ -329,7 +329,7 @@ def post(self, path, feature_id=None):
if search_tags:
search_tags = filter(bool, [x.strip() for x in search_tags.split(',')])

blink_components = self.request.get('blink_components') or models.DEFAULT_BUG_COMPONENT
blink_components = self.request.get('blink_components') or models.BlinkComponent.DEFAULT_COMPONENT
if blink_components:
blink_components = filter(bool, [x.strip() for x in blink_components.split(',')])

Expand Down
18 changes: 12 additions & 6 deletions app.yaml
Expand Up @@ -50,22 +50,28 @@ handlers:
script: metrics.app

# Admin ------------------------------------------------------------------------
- url: /admin/gae/.*
script: google.appengine.ext.admin.application
login: admin

- url: /cron/.*
script: admin.app
login: admin # Prevents raw access to this handler. Cron runs as admin.

- url: /admin/schedule
script: schedule.app
- url: /admin/gae/.*
script: google.appengine.ext.admin.application
login: admin

- url: /admin/blink.*
script: blink_handler.app
login: required
secure: always

- url: /admin/features/.*
script: admin.app
secure: always

- url: /admin/schedule.*
script: schedule.app
login: required
secure: always

- url: /admin/users/.*
script: users.app
login: admin
Expand Down
61 changes: 61 additions & 0 deletions blink_handler.py
@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License")
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

__author__ = 'ericbidelman@chromium.org (Eric Bidelman)'

import json
import logging
import os
import webapp2

# Appengine imports.
from google.appengine.api import memcache
from google.appengine.api import urlfetch
from google.appengine.api import users

import common
import models
import settings
import util


class BlinkHandler(common.ContentHandler):

@common.require_whitelisted_user
@common.strip_trailing_slash
def get(self, path):
# components = models.BlinkComponent.fetch_all_components()
# for f in features:
# milesstone = f.get('meta').get('milestone_str')
# print f.get('impl_status_chrome')

components = models.BlinkComponent.all().fetch(None)
owners = models.FeatureOwner.all().fetch(None)

owners = [x.format_for_template() for x in sorted(owners, key=lambda o: o.name)]

data = {
#'components': components #json.dumps(components),json.dumps(components),
'owners': owners,
'components': sorted(components, key=lambda c: c.name)
}

self.render(data, template_path=os.path.join('admin/blink.html'))


app = webapp2.WSGIApplication([
('(.*)', BlinkHandler),
], debug=settings.DEBUG)

27 changes: 26 additions & 1 deletion common.py
Expand Up @@ -31,6 +31,30 @@
import settings


def require_whitelisted_user(handler):
"""Handler decorator to require the user be whitelisted."""
def check_login(self, *args, **kwargs):
user = users.get_current_user()
if not user:
return self.redirect(users.create_login_url(self.request.uri))
elif not self._is_user_whitelisted(user):
handle_401(self.request, self.response, Exception)
return

return handler(self, *args, **kwargs) # Call the handler method
return check_login

def strip_trailing_slash(handler):
"""Strips the trailing slash on the URL."""
def remove_slash(self, *args, **kwargs):
path = args[0]
if path[-1] == '/':
return self.redirect(self.request.path.rstrip('/'))

return handler(self, *args, **kwargs) # Call the handler method
return remove_slash


class BaseHandler(webapp2.RequestHandler):

def __init__(self, request, response):
Expand Down Expand Up @@ -113,7 +137,8 @@ def _add_common_template_values(self, d):
'prod': settings.PROD,
'APP_TITLE': settings.APP_TITLE,
'current_path': self.request.path,
'VULCANIZE': settings.VULCANIZE
'VULCANIZE': settings.VULCANIZE,
'TEMPLATE_CACHE_TIME': settings.TEMPLATE_CACHE_TIME
}

user = users.get_current_user()
Expand Down
106 changes: 88 additions & 18 deletions models.py
Expand Up @@ -8,6 +8,7 @@
from google.appengine.api import urlfetch
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext import ndb
#from google.appengine.ext.db import djangoforms

#from django.forms import ModelForm
Expand Down Expand Up @@ -163,9 +164,6 @@
DEV_STRONG_NEGATIVE: 'Strongly negative',
}

DEFAULT_BUG_COMPONENT = 'Blink'
BLINK_COMPONENTS_URL = 'https://blinkcomponents-b48b5.firebaseapp.com/blinkcomponents'

def del_none(d):
"""
Delete dict keys with None values, and empty lists, recursively.
Expand All @@ -182,18 +180,56 @@ def list_to_chunks(l, n):
for i in xrange(0, len(l), n):
yield l[i:i + n]

def fetch_blink_components():
key = '%s|blinkcomponents' % (settings.MEMCACHE_KEY_PREFIX)

components = memcache.get(key)
if components is None:
components = []
class BlinkComponent(db.Model):

DEFAULT_COMPONENT = 'Blink'
COMPONENTS_URL = 'https://blinkcomponents-b48b5.firebaseapp.com/blinkcomponents'

result = urlfetch.fetch(BLINK_COMPONENTS_URL)
if result.status_code == 200:
components = sorted(json.loads(result.content))
memcache.set(key, components)
return [(x, x) for x in components]
name = db.StringProperty(required=True, default=DEFAULT_COMPONENT)
created = db.DateTimeProperty(auto_now_add=True)
updated = db.DateTimeProperty(auto_now=True)

@property
def owners(self):
q = FeatureOwner.all().filter('blink_components = ', self.key())
return q.fetch(None)

@classmethod
def fetch_all_components(self, update_cache=False):
key = '%s|blinkcomponents' % (settings.MEMCACHE_KEY_PREFIX)

components = memcache.get(key)
if components is None or update_cache:
components = []
result = urlfetch.fetch(self.COMPONENTS_URL)
if result.status_code == 200:
components = sorted(json.loads(result.content))
memcache.set(key, components)
return components

@classmethod
def update_db(self):
"""Updates the db with new Blink components from the json endpoint"""
new_components = self.fetch_all_components(update_cache=True)
existing_comps = self.all().fetch(None)
for name in new_components:
if not len([x.name for x in existing_comps if x.name == name]):
logging.info('Adding new BlinkComponent: ' + name)
print 'Adding new BlinkComponent: ' + name
c = BlinkComponent(name=name)
c.put()

@classmethod
def get_by_name(self, component_name):
"""Fetch blink component with given name."""
q = self.all()
q.filter('name =', component_name)
component = q.fetch(1)
if not component:
logging.error('%s is an unknown BlinkComponent.' % (component_name))
return None
return component[0]


class DictModel(db.Model):
Expand Down Expand Up @@ -243,9 +279,11 @@ class StableInstance(DictModel):
day_percentage = db.FloatProperty()
rolling_percentage = db.FloatProperty()


class AnimatedProperty(StableInstance):
pass


class FeatureObserver(StableInstance):
pass

Expand Down Expand Up @@ -652,7 +690,7 @@ def crbug_number(self):

def new_crbug_url(self):
url = 'https://bugs.chromium.org/p/chromium/issues/entry'
params = ['components=' + self.blink_components[0] or DEFAULT_BUG_COMPONENT]
params = ['components=' + self.blink_components[0] or BlinkComponent.DEFAULT_COMPONENT]
crbug_number = self.crbug_number()
if crbug_number and self.impl_status_chrome in (
NO_ACTIVE_DEV,
Expand All @@ -679,7 +717,7 @@ def new_crbug_url(self):

# Chromium details.
bug_url = db.LinkProperty()
blink_components = db.StringListProperty(required=True, default=[DEFAULT_BUG_COMPONENT])
blink_components = db.StringListProperty(required=True, default=[BlinkComponent.DEFAULT_COMPONENT])

impl_status_chrome = db.IntegerProperty(required=True)
shipped_milestone = db.IntegerProperty()
Expand Down Expand Up @@ -779,9 +817,9 @@ class FeatureForm(forms.Form):
blink_components = forms.ChoiceField(
required=True,
label='Blink component',
help_text='Select the most specific component. If unsure, leave as "%s".' % DEFAULT_BUG_COMPONENT,
choices=fetch_blink_components(),
initial=[DEFAULT_BUG_COMPONENT])
help_text='Select the most specific component. If unsure, leave as "%s".' % BlinkComponent.DEFAULT_COMPONENT,
choices=[(x, x) for x in BlinkComponent.fetch_all_components()],
initial=[BlinkComponent.DEFAULT_COMPONENT])

impl_status_chrome = forms.ChoiceField(required=True,
label='Status in Chromium', choices=IMPLEMENTATION_STATUS.items())
Expand Down Expand Up @@ -900,6 +938,38 @@ def format_for_template(self):
return d


class FeatureOwner(DictModel):
"""Describes owner of a web platform feature."""
created = db.DateTimeProperty(auto_now_add=True)
updated = db.DateTimeProperty(auto_now=True)
name = db.StringProperty(required=True)
email = db.EmailProperty(required=True)
twitter = db.StringProperty()
blink_components = db.ListProperty(db.Key)

def format_for_template(self):
d = self.to_dict()
d['id'] = self.key().id()
return d

def add_as_component_owner(self, component_name):
"""Adds the user to the list of Blink component owners."""
c = BlinkComponent.get_by_name(component_name)
if c:
already_added = len([x for x in self.blink_components if x.id() == c.key().id()])
if not already_added:
self.blink_components.append(c.key())
self.put()

def remove_from_component_owners(self, component_name):
"""Removes the user from the list of Blink component owners."""
c = BlinkComponent.get_by_name(component_name)
if c:
self.blink_components = [x for x in self.blink_components if x.id() != c.key().id()]
self.put()
pass


class HistogramModel(db.Model):
"""Container for a histogram."""

Expand Down
13 changes: 4 additions & 9 deletions schedule.py
Expand Up @@ -68,14 +68,9 @@ def construct_chrome_channels_details(omaha_data):

class ScheduleHandler(common.ContentHandler):

@common.require_whitelisted_user
@common.strip_trailing_slash
def get(self, path):
user = users.get_current_user()
if not user:
return self.redirect(users.create_login_url(self.request.uri))
elif not self._is_user_whitelisted(user):
common.handle_401(self.request, self.response, Exception)
return

omaha_data = util.get_omaha_data()

# features = Feature.get_chronological()
Expand All @@ -88,10 +83,10 @@ def get(self, path):
'channels': json.dumps(construct_chrome_channels_details(omaha_data))
}

self.render(data, template_path=os.path.join(path + '.html'))
self.render(data, template_path=os.path.join('admin/schedule.html'))


app = webapp2.WSGIApplication([
('/(.*)', ScheduleHandler),
('(.*)', ScheduleHandler),
], debug=settings.DEBUG)

1 change: 0 additions & 1 deletion server.py
Expand Up @@ -100,7 +100,6 @@ def get(self, path, feature_id=None):
template_data['STANDARDS_VALS'] = json.dumps([
{'key': k, 'val': v} for k,v in
models.STANDARDIZATION.iteritems()])
template_data['TEMPLATE_CACHE_TIME'] = settings.TEMPLATE_CACHE_TIME

push_urls = http2push.use_push_manifest('push_manifest_features.json')

Expand Down
16 changes: 16 additions & 0 deletions static/sass/_layout.scss
@@ -0,0 +1,16 @@
.layout {
display: flex;
}
.layout.wrap {
flex-wrap: wrap;
}
.layout.center {
align-items: center;
}
.layout.center-center {
align-items: center;
justify-content: center;
}
.layout.vertical {
flex-direction: column;
}

0 comments on commit e32d0b6

Please sign in to comment.