Commit 7a18fc3b authored by Andrew Gerrand's avatar Andrew Gerrand

dashboard: list "most installed this week" with rolling count

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/4631085
parent 95117d30
...@@ -11,9 +11,13 @@ handlers: ...@@ -11,9 +11,13 @@ handlers:
- url: /static - url: /static
static_dir: static static_dir: static
- url: /package.* - url: /package
script: package.py script: package.py
- url: /package/daily
script: package.py
login: admin
- url: /project.* - url: /project.*
script: package.py script: package.py
......
cron:
- description: daily package maintenance
url: /package/daily
schedule: every 24 hours
...@@ -32,6 +32,20 @@ ...@@ -32,6 +32,20 @@
<a href="http://blog.golang.org/2011/03/godoc-documenting-go-code.html">package doc comment</a>. <a href="http://blog.golang.org/2011/03/godoc-documenting-go-code.html">package doc comment</a>.
</p> </p>
<h2>Most Installed Packages (this week)</h2>
<table class="alternate" cellpadding="0" cellspacing="0">
<tr><th>last install</th><th>count</th><th>build</th><th>path</th><th>info</th></tr>
{% for r in by_week_count %}
<tr>
<td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td>
<td class="count">{{r.week_count}}</td>
<td class="ok">{% if r.ok %}<a title="{{r.last_ok|date:"Y-M-d H:i"}}">ok</a>{% else %}&nbsp;{% endif %}</td>
<td class="path"><a href="{{r.web_url}}">{{r.path}}</a></td>
<td class="info">{% if r.info %}{{r.info|escape}}{% endif %}</td>
</tr>
{% endfor %}
</table>
<h2>Recently Installed Packages</h2> <h2>Recently Installed Packages</h2>
<table class="alternate" cellpadding="0" cellspacing="0"> <table class="alternate" cellpadding="0" cellspacing="0">
<tr><th>last install</th><th>count</th><th>build</th><th>path</th><th>info</th></tr> <tr><th>last install</th><th>count</th><th>build</th><th>path</th><th>info</th></tr>
...@@ -46,7 +60,7 @@ ...@@ -46,7 +60,7 @@
{% endfor %} {% endfor %}
</table> </table>
<h2>Most Installed Packages</h2> <h2>Most Installed Packages (all time)</h2>
<table class="alternate" cellpadding="0" cellspacing="0"> <table class="alternate" cellpadding="0" cellspacing="0">
<tr><th>last install</th><th>count</th><th>build</th><th>path</th><th>info</th></tr> <tr><th>last install</th><th>count</th><th>build</th><th>path</th><th>info</th></tr>
{% for r in by_count %} {% for r in by_count %}
......
...@@ -5,34 +5,36 @@ ...@@ -5,34 +5,36 @@
# This is the server part of the package dashboard. # This is the server part of the package dashboard.
# It must be run by App Engine. # It must be run by App Engine.
from google.appengine.api import mail
from google.appengine.api import memcache from google.appengine.api import memcache
from google.appengine.api import taskqueue
from google.appengine.api import urlfetch
from google.appengine.api import users
from google.appengine.ext import db from google.appengine.ext import db
from google.appengine.ext import webapp from google.appengine.ext import webapp
from google.appengine.ext.webapp import template from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.api import users
from google.appengine.api import mail
from google.appengine.api import urlfetch
import datetime import datetime
import logging import logging
import os import os
import re import re
import urllib2
import sets import sets
import urllib2
# local imports # local imports
from auth import auth
import toutf8 import toutf8
import const import const
from auth import auth
template.register_template_library('toutf8') template.register_template_library('toutf8')
# Storage model for package info recorded on server. # Storage model for package info recorded on server.
# Just path, count, and time of last install.
class Package(db.Model): class Package(db.Model):
path = db.StringProperty() path = db.StringProperty()
web_url = db.StringProperty() # derived from path web_url = db.StringProperty() # derived from path
count = db.IntegerProperty() count = db.IntegerProperty() # grand total
week_count = db.IntegerProperty() # rolling weekly count
day_count = db.TextProperty(default='') # daily count
last_install = db.DateTimeProperty() last_install = db.DateTimeProperty()
# data contributed by gobuilder # data contributed by gobuilder
...@@ -40,6 +42,67 @@ class Package(db.Model): ...@@ -40,6 +42,67 @@ class Package(db.Model):
ok = db.BooleanProperty() ok = db.BooleanProperty()
last_ok = db.DateTimeProperty() last_ok = db.DateTimeProperty()
def get_day_count(self):
counts = {}
if not self.day_count:
return counts
for d in str(self.day_count).split('\n'):
date, count = d.split(' ')
counts[date] = int(count)
return counts
def set_day_count(self, count):
days = []
for day, count in count.items():
days.append('%s %d' % (day, count))
days.sort(reverse=True)
days = days[:28]
self.day_count = '\n'.join(days)
def inc(self):
count = self.get_day_count()
today = str(datetime.date.today())
count[today] = count.get(today, 0) + 1
self.set_day_count(count)
self.update_week_count(count)
self.count += 1
def update_week_count(self, count=None):
if count is None:
count = self.get_day_count()
total = 0
today = datetime.date.today()
for i in range(7):
day = str(today - datetime.timedelta(days=i))
if day in count:
total += count[day]
self.week_count = total
# PackageDaily kicks off the daily package maintenance cron job
# and serves the associated task queue.
class PackageDaily(webapp.RequestHandler):
def get(self):
# queue a task to update each package with a week_count > 0
keys = Package.all(keys_only=True).filter('week_count >', 0)
for key in keys:
taskqueue.add(url='/package/daily', params={'key': key.name()})
def post(self):
# update a single package (in a task queue)
def update(key):
p = Package.get_by_key_name(key)
if not p:
return
p.update_week_count()
p.put()
key = self.request.get('key')
if not key:
return
db.run_in_transaction(update, key)
class Project(db.Model): class Project(db.Model):
name = db.StringProperty(indexed=True) name = db.StringProperty(indexed=True)
descr = db.StringProperty() descr = db.StringProperty()
...@@ -49,6 +112,7 @@ class Project(db.Model): ...@@ -49,6 +112,7 @@ class Project(db.Model):
tags = db.ListProperty(str) tags = db.ListProperty(str)
approved = db.BooleanProperty(indexed=True) approved = db.BooleanProperty(indexed=True)
re_bitbucket = re.compile(r'^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-zA-Z0-9_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$') re_bitbucket = re.compile(r'^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-zA-Z0-9_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$')
re_googlecode = re.compile(r'^[a-z0-9\-]+\.googlecode\.com/(svn|hg)(/[a-z0-9A-Z_.\-/]+)?$') re_googlecode = re.compile(r'^[a-z0-9\-]+\.googlecode\.com/(svn|hg)(/[a-z0-9A-Z_.\-/]+)?$')
re_github = re.compile(r'^github\.com/[a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)+$') re_github = re.compile(r'^github\.com/[a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)+$')
...@@ -115,29 +179,30 @@ class PackagePage(webapp.RequestHandler): ...@@ -115,29 +179,30 @@ class PackagePage(webapp.RequestHandler):
html = memcache.get('view-package') html = memcache.get('view-package')
if not html: if not html:
tdata = {}
q = Package.all().filter('week_count >', 0)
q.order('-week_count')
tdata['by_week_count'] = q.fetch(50)
q = Package.all() q = Package.all()
q.order('-last_install') q.order('-last_install')
by_time = q.fetch(100) tdata['by_time'] = q.fetch(20)
q = Package.all() q = Package.all()
q.order('-count') q.order('-count')
by_count = q.fetch(100) tdata['by_count'] = q.fetch(100)
self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
path = os.path.join(os.path.dirname(__file__), 'package.html') path = os.path.join(os.path.dirname(__file__), 'package.html')
html = template.render( html = template.render(path, tdata)
path,
{"by_time": by_time, "by_count": by_count}
)
memcache.set('view-package', html, time=CacheTimeout) memcache.set('view-package', html, time=CacheTimeout)
self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
self.response.out.write(html) self.response.out.write(html)
def json(self): def json(self):
json = memcache.get('view-package-json') json = memcache.get('view-package-json')
if not json: if not json:
self.response.set_status(200)
self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
q = Package.all() q = Package.all()
s = '{"packages": [' s = '{"packages": ['
sep = '' sep = ''
...@@ -147,6 +212,8 @@ class PackagePage(webapp.RequestHandler): ...@@ -147,6 +212,8 @@ class PackagePage(webapp.RequestHandler):
s += '\n]}\n' s += '\n]}\n'
json = s json = s
memcache.set('view-package-json', json, time=CacheTimeout) memcache.set('view-package-json', json, time=CacheTimeout)
self.response.set_status(200)
self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
self.response.out.write(json) self.response.out.write(json)
def can_get_url(self, url): def can_get_url(self, url):
...@@ -181,14 +248,15 @@ class PackagePage(webapp.RequestHandler): ...@@ -181,14 +248,15 @@ class PackagePage(webapp.RequestHandler):
return False return False
p = Package(key_name = key, path = path, count = 0, web_url = web) p = Package(key_name = key, path = path, count = 0, web_url = web)
# is this the builder updating package metadata?
if auth(self.request): if auth(self.request):
# builder updating package metadata
p.info = self.request.get('info') p.info = self.request.get('info')
p.ok = self.request.get('ok') == "true" p.ok = self.request.get('ok') == "true"
if p.ok: if p.ok:
p.last_ok = datetime.datetime.utcnow() p.last_ok = datetime.datetime.utcnow()
else: else:
p.count += 1 # goinstall reporting an install
p.inc()
p.last_install = datetime.datetime.utcnow() p.last_install = datetime.datetime.utcnow()
# update package object # update package object
...@@ -347,6 +415,7 @@ class ProjectPage(webapp.RequestHandler): ...@@ -347,6 +415,7 @@ class ProjectPage(webapp.RequestHandler):
def main(): def main():
app = webapp.WSGIApplication([ app = webapp.WSGIApplication([
('/package', PackagePage), ('/package', PackagePage),
('/package/daily', PackageDaily),
('/project.*', ProjectPage), ('/project.*', ProjectPage),
], debug=True) ], debug=True)
run_wsgi_app(app) run_wsgi_app(app)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment