73:a9c9da814fe3
Anton Shestakov <av6@dwimlabs.net>, Tue, 28 Jun 2016 18:52:52 +0800
viewer: setup and clone tasks without errors/warnings are collapsed by default In addition to errors and warnings, which leave task expanded, target line also does the same, so sharing a link to a specific line doesn't surprise people by hiding the line somewhere.

next change 78:06a1f6536049
previous change 41:8d6436a284d2

viewer.py

Permissions: -rwxr-xr-x

Other formats: Feeds:
#!/usr/bin/env python
from __future__ import absolute_import, division
import logging
import os
import traceback
from peewee import DoesNotExist
from tornado.escape import json_decode
from tornado.ioloop import IOLoop
from tornado.options import define, options
from tornado.web import Application, HTTPError, RequestHandler, URLSpec
from tornado.web import ErrorHandler as BaseErrorHandler
from candolint import uimodules
from candolint.models import database, Project, Change, Check
rel = lambda *x: os.path.abspath(os.path.join(os.path.dirname(__file__), *x))
define('listen', metavar='IP', default='127.0.0.1')
define('port', metavar='PORT', default=8033, type=int)
define('xheaders', metavar='True|False', default=False, type=bool)
define('debug', metavar='True|False', default=False, type=bool)
def get_or_404(query, *args, **kwargs):
try:
return query.get(*args, **kwargs)
except DoesNotExist:
raise HTTPError(404)
class BaseHandler(RequestHandler):
def prepare(self):
database.connect()
super(BaseHandler, self).prepare()
def on_finish(self):
if not database.is_closed():
database.close()
def write_error(self, status_code, **kwargs):
data = {
'code': status_code,
'message': self._reason,
'debug_message': ''
}
if 'exc_info' in kwargs:
if self.settings.get('serve_traceback'):
fexc = traceback.format_exception(*kwargs['exc_info'])
data['debug_message'] = '\n'.join(fexc)
if status_code == 404:
self.render('404.html', **data)
else:
self.render('500.html', **data)
class IndexHandler(BaseHandler):
def get(self):
checks = (Check
.select(Check, Project, Change)
.join(Project)
.switch(Check)
.join(Change)
.group_by(Check.project))
self.render('index.html', checks=checks)
class ProjectHandler(BaseHandler):
def get(self, domain, user, name):
pq = Project.select().where(
Project.domain == domain,
Project.user == (user if user != '-' else None),
Project.name == name)
project = get_or_404(pq)
checks = (Check
.select(Check, Change)
.join(Change)
.where(Check.project == project)
.limit(10))
self.render('project.html', project=project, checks=checks)
class CheckHandler(BaseHandler):
def get(self, domain, user, name, check_num):
pq = Project.select().where(
Project.domain == domain,
Project.user == (user if user != '-' else None),
Project.name == name)
project = get_or_404(pq)
if check_num == 'latest':
check = get_or_404(Check, project=project)
else:
check = get_or_404(Check, project=project, ordinal=check_num)
lines = json_decode(check.lines)
self.render('check.html', project=project, check=check, lines=lines)
class StatusHandler(BaseHandler):
def get(self, domain, user, name):
pq = Project.select().where(
Project.domain == domain,
Project.user == (user if user != '-' else None),
Project.name == name)
project = get_or_404(pq)
check = get_or_404(Check, project=project)
parts = [('#555', 30, 14.5, 'lint')]
if not check.success:
parts.append(('#777', 62, 30.5, 'unknown'))
elif check.errors or check.warnings:
if check.errors:
msg = self.locale.translate('{} error', '{} errors', check.errors)
text = msg.format(check.errors)
width = 7 + 6 * len(text) + 7
parts.append(('#da314b', width, width // 2 - 0.5, text))
if check.warnings:
msg = self.locale.translate('{} warning', '{} warnings', check.warnings)
text = msg.format(check.warnings)
width = 7 + 6 * len(text) + 9
parts.append(('#faa732', width, width // 2 + 0.5, text))
else:
parts.append(('#8cc14c', 40, 19.5, 'none'))
width = sum(p[1] for p in parts)
self.set_header('Content-Type', 'image/svg+xml; charset=utf-8')
self.render('status.svg', width=width, parts=parts, height=20)
class ErrorHandler(BaseHandler, BaseErrorHandler):
pass
class CandolintViewer(Application):
def __init__(self):
handlers = [
URLSpec(r'/', IndexHandler),
URLSpec(r'/([.a-z0-9_-]+)/([^/]+)/([^/]+)', ProjectHandler),
URLSpec(r'/([.a-z0-9_-]+)/([^/]+)/([^/]+)/([\d]+|latest)', CheckHandler),
URLSpec(r'/([.a-z0-9_-]+)/([^/]+)/([^/]+)/status\.svg', StatusHandler),
URLSpec(r'.*', ErrorHandler, {'status_code': 404})
]
settings = dict(
static_path=rel('static'),
template_path=rel('templates'),
ui_modules=uimodules,
debug=options.debug
)
super(CandolintViewer, self).__init__(handlers, **settings)
if options.debug:
logging.getLogger('peewee').setLevel(logging.DEBUG)
database.init(rel('database.sqlite'))
def listen(self, port, address='', **kwargs):
name = self.__class__.__name__
logging.info('%s is serving on %s:%d', name, address, port)
super(CandolintViewer, self).listen(port, address, **kwargs)
def main():
options.parse_command_line()
application = CandolintViewer()
application.listen(options.port, address=options.listen, xheaders=options.xheaders)
IOLoop.instance().start()
if __name__ == '__main__':
main()