Redesign editor performance page (bug 541156)
This commit is contained in:
Родитель
57861d8419
Коммит
0d59e31647
|
@ -189,6 +189,31 @@ class ViewPreliminaryQueue(VersionSpecificQueue):
|
|||
return q
|
||||
|
||||
|
||||
class PerformanceGraph(ViewQueue):
|
||||
id = models.IntegerField()
|
||||
yearmonth = models.CharField(max_length=7)
|
||||
approval_created = models.DateTimeField()
|
||||
user_id = models.IntegerField()
|
||||
total = models.IntegerField()
|
||||
|
||||
def base_query(self):
|
||||
return {
|
||||
'select': SortedDict([
|
||||
('yearmonth', "DATE_FORMAT(`approvals`.`created`, '%%Y-%%m')"),
|
||||
('approval_created', '`approvals`.`created`'),
|
||||
('user_id', '`users`.`id`'),
|
||||
('total', 'COUNT(*)')]),
|
||||
'from': [
|
||||
'approvals',
|
||||
'LEFT JOIN `users` ON (`users`.`id`=`approvals`.`user_id`)',
|
||||
"""INNER JOIN `groups_users` ON
|
||||
(`users`.`id` = `groups_users`.`user_id` AND
|
||||
`groups_users`.`group_id` = 2)"""],
|
||||
'where': [],
|
||||
'group_by': 'yearmonth, user_id'
|
||||
}
|
||||
|
||||
|
||||
class EditorSubscription(amo.models.ModelBase):
|
||||
user = models.ForeignKey(UserProfile)
|
||||
addon = models.ForeignKey(Addon)
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
{% extends "editors/base.html" %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{{ editors_breadcrumbs(items=[(None, _('Performance'))]) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{{ _('Add-on Editor Performance', 'editorcp_reviewlog_page_heading') }}</h2>
|
||||
|
||||
<div class="featured">
|
||||
<div class="featured-inner">
|
||||
<table class="data-grid">
|
||||
<tr class="listing-header">
|
||||
<th>{{ _('Range') }}</th>
|
||||
<th>{{ _('My Reviews') }}</th>
|
||||
<th>{{ _('Total Reviews') }}</th>
|
||||
{# <th>{{ _('Team Average') }}</th> #}
|
||||
<th>{{ _('Active Contributors') }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{{ _('This Month') }}</strong></td>
|
||||
<td>{{ performance_month['usercount'] }}</td>
|
||||
<td>{{ performance_month['teamcount'] }}</td>
|
||||
<td>{{ performance_month['teamamt'] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{{ _('Past Year') }}</strong></td>
|
||||
<td>{{ performance_year['usercount'] }}</td>
|
||||
<td>{{ performance_year['teamcount'] }}</td>
|
||||
<td>{{ performance_year['teamamt'] }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="featured">
|
||||
<div class="featured-inner listing">
|
||||
<div class="listing-header"><span>{{ _('Monthly Performance') }}</span></div>
|
||||
<div id="monthly" class="highcharts-container"
|
||||
data-chart="{{ monthly_data }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -743,6 +743,25 @@ class TestModeratedQueue(QueueTest):
|
|||
eq_(doc('.review-saved button').length, 1) # Only show one button.
|
||||
|
||||
|
||||
class TestPerformance(QueueTest):
|
||||
fixtures = ('base/users', 'editors/pending-queue', 'base/approvals')
|
||||
|
||||
"""Test the page at /editors/performance."""
|
||||
def setUp(self):
|
||||
super(TestPerformance, self).setUp()
|
||||
self.url_performance = reverse('editors.performance')
|
||||
|
||||
def test_performance_chart(self):
|
||||
r = self.client.get(self.url_performance)
|
||||
doc = pq(r.content)
|
||||
|
||||
data = {u'2010-08': {u'teamcount': 1, u'teamavg': u'1.0',
|
||||
u'usercount': 1, u'teamamt': 1,
|
||||
u'label': u'Aug 2010'}}
|
||||
|
||||
eq_(json.loads(doc('#monthly').attr('data-chart')), data)
|
||||
|
||||
|
||||
class SearchTest(EditorTest):
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
@ -20,6 +20,7 @@ urlpatterns = patterns('',
|
|||
url(r'^log/(\d+)$', views.eventlog_detail, name='editors.eventlog.detail'),
|
||||
url(r'^reviewlog$', views.reviewlog, name='editors.reviewlog'),
|
||||
url(r'^review/(?P<version_id>\d+)$', views.review, name='editors.review'),
|
||||
url(r'^performance$', views.performance, name='editors.performance'),
|
||||
url(r'^motd$', views.motd, name='editors.motd'),
|
||||
url(r'^motd/save$', views.save_motd, name='editors.save_motd'),
|
||||
url(r'^$', views.home, name='editors.home'),
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
from datetime import date
|
||||
from datetime import date, datetime
|
||||
import functools
|
||||
import json
|
||||
import time
|
||||
|
||||
from django import http
|
||||
from django.conf import settings
|
||||
from django.shortcuts import redirect, get_object_or_404
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
import jingo
|
||||
from tower import ugettext as _
|
||||
|
@ -18,7 +21,7 @@ from devhub.models import ActivityLog
|
|||
from editors import forms
|
||||
from editors.models import (EditorSubscription, ViewPendingQueue,
|
||||
ViewFullReviewQueue, ViewPreliminaryQueue,
|
||||
EventLog, CannedResponse)
|
||||
EventLog, CannedResponse, PerformanceGraph)
|
||||
from editors.helpers import (ViewPendingQueueTable, ViewFullReviewQueueTable,
|
||||
ViewPreliminaryQueueTable)
|
||||
from files.models import Approval
|
||||
|
@ -113,6 +116,82 @@ def _editor_progress():
|
|||
return (progress, percentage)
|
||||
|
||||
|
||||
@editor_required
|
||||
def performance(request):
|
||||
monthly_data = _performanceByMonth(request.amo_user.id)
|
||||
performance_total = _performance_total(monthly_data)
|
||||
|
||||
data = context(monthly_data=json.dumps(monthly_data),
|
||||
performance_month=performance_total['month'],
|
||||
performance_year=performance_total['year'])
|
||||
|
||||
return jingo.render(request, 'editors/performance.html', data)
|
||||
|
||||
|
||||
def _performance_total(data):
|
||||
# TODO(gkoberger): Fix this so it's the past X, rather than this X to date.
|
||||
# (ex: March 15-April 15, not April 1 - April 15)
|
||||
total_yr = dict(usercount=0, teamamt=0, teamcount=0, teamavg=0)
|
||||
total_month = dict(usercount=0, teamamt=0, teamcount=0, teamavg=0)
|
||||
current_year = datetime.now().year
|
||||
|
||||
for k, val in data.items():
|
||||
if k.startswith(str(current_year)):
|
||||
total_yr['usercount'] = total_yr['usercount'] + val['usercount']
|
||||
total_yr['teamamt'] = total_yr['teamamt'] + val['teamamt']
|
||||
total_yr['teamcount'] = total_yr['teamcount'] + val['teamcount']
|
||||
|
||||
current_label_month = datetime.now().isoformat()[:7]
|
||||
if current_label_month in data:
|
||||
total_month = data[current_label_month]
|
||||
|
||||
return dict(month=total_month, year=total_yr)
|
||||
|
||||
|
||||
def _performanceByMonth(user_id, months=12, end_month=None, end_year=None):
|
||||
monthly_data = SortedDict()
|
||||
|
||||
now = datetime.now()
|
||||
if not end_month:
|
||||
end_month = now.month
|
||||
if not end_year:
|
||||
end_year = now.year
|
||||
|
||||
end_time = time.mktime((end_year, end_month + 1, 1, 0, 0, 0, 0, 0, -1))
|
||||
start_time = time.mktime((end_year, end_month + 1 - months,
|
||||
1, 0, 0, 0, 0, 0, -1))
|
||||
|
||||
sql = (PerformanceGraph.objects
|
||||
.filter_raw('approvals.created >=',
|
||||
date.fromtimestamp(start_time).isoformat())
|
||||
.filter_raw('approvals.created <',
|
||||
date.fromtimestamp(end_time).isoformat())
|
||||
)
|
||||
|
||||
for row in sql.all():
|
||||
label = row.approval_created.isoformat()[:7]
|
||||
|
||||
if not label in monthly_data:
|
||||
xaxis = row.approval_created.strftime('%b %Y')
|
||||
monthly_data[label] = dict(teamcount=0, usercount=0,
|
||||
teamamt=0, label=xaxis)
|
||||
|
||||
monthly_data[label]['teamamt'] = monthly_data[label]['teamamt'] + 1
|
||||
monthly_data_count = monthly_data[label]['teamcount']
|
||||
monthly_data[label]['teamcount'] = monthly_data_count + row.total
|
||||
|
||||
if row.user_id == user_id:
|
||||
user_count = monthly_data[label]['usercount']
|
||||
monthly_data[label]['usercount'] = user_count + row.total
|
||||
|
||||
# Calculate averages
|
||||
for i, vals in monthly_data.items():
|
||||
average = round(vals['teamcount'] / float(vals['teamamt']), 1)
|
||||
monthly_data[i]['teamavg'] = str(average) # floats aren't valid json
|
||||
|
||||
return monthly_data;
|
||||
|
||||
|
||||
@editor_required
|
||||
def motd(request):
|
||||
form = None
|
||||
|
|
|
@ -193,7 +193,7 @@ ul.tabnav a:hover {
|
|||
border-bottom-width: 0px;
|
||||
}
|
||||
|
||||
#editors_main .listing .listing-header {
|
||||
.listing .listing-header {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ $(function() {
|
|||
initDailyMessage();
|
||||
}
|
||||
|
||||
|
||||
var show_comments = function(e) {
|
||||
e.preventDefault()
|
||||
var me = e.target;
|
||||
|
@ -31,6 +30,10 @@ $(function() {
|
|||
if($('#review-actions').length > 0) {
|
||||
initReviewActions();
|
||||
}
|
||||
|
||||
if($('#monthly').exists()) {
|
||||
initPerformanceStats();
|
||||
}
|
||||
});
|
||||
|
||||
function initReviewActions() {
|
||||
|
@ -177,3 +180,73 @@ function initQueueSearch(doc) {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function initPerformanceStats() {
|
||||
var container = $('#monthly'),
|
||||
groups = {'usercount': gettext('Your Reviews'),
|
||||
'teamavg': gettext('Average Reviews')}
|
||||
|
||||
createChart(container, groups, JSON.parse(container.attr('data-chart')));
|
||||
|
||||
function createChart(container, groups, data) {
|
||||
var labels = [],
|
||||
data_points = {},
|
||||
chart_series = [];
|
||||
|
||||
|
||||
$.each(groups, function(key, name){
|
||||
data_points[key] = {'name': name, 'data':[]};
|
||||
});
|
||||
|
||||
$.each(data, function(k, vals) {
|
||||
labels.push(vals['label']);
|
||||
$.each(vals, function(group, amount){
|
||||
if(groups[group]){
|
||||
data_points[group]['data'].push(parseFloat(amount));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$.each(data_points, function(k, vals){
|
||||
chart_series.push(vals);
|
||||
});
|
||||
|
||||
var $c = container,
|
||||
chart = new Highcharts.Chart({
|
||||
chart: {
|
||||
renderTo: container[0],
|
||||
defaultSeriesType: 'line',
|
||||
marginRight: 130,
|
||||
marginBottom: 25
|
||||
},
|
||||
title: {
|
||||
text: ' ',
|
||||
x: 0 //center
|
||||
},
|
||||
xAxis: {
|
||||
categories: labels
|
||||
},
|
||||
yAxis: {
|
||||
title: { text: gettext('Number of Reviews') },
|
||||
plotLines: [{value: 0, width: 1, color: '#808080'}]
|
||||
},
|
||||
tooltip: {
|
||||
formatter: function() {
|
||||
return '<b>'+ this.series.name +'</b><br/>' + this.x +': '+ this.y;
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
layout: 'vertical',
|
||||
align: 'right',
|
||||
verticalAlign: 'top',
|
||||
x: -10,
|
||||
y: 100,
|
||||
borderWidth: 0
|
||||
},
|
||||
series: chart_series
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -487,6 +487,7 @@ MINIFY_BUNDLES = {
|
|||
),
|
||||
'zamboni/editors': (
|
||||
'js/zamboni/editors.js',
|
||||
'js/lib/highcharts.src.js'
|
||||
),
|
||||
'zamboni/files': (
|
||||
'js/diff/diff_match_patch_uncompressed.js',
|
||||
|
|
Загрузка…
Ссылка в новой задаче