This repository has been archived by the owner on Aug 26, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 679
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[603808] Built out infrastructure for the Localization (and Contribut…
…or) Dashboards. * Markup, CSS, templates, and queries for the Overview and Untranslated Articles localization summaries are in place, though there's still some optimization to do. * Add a `number()` template function for localized thousands formatting, etc. * Stub out Contributors Dash view just so we can reverse() to it. * Add trivial tests. Perhaps horrific ones testing the accuracy of the queries will follow. Perhaps. * To come: WebTrends integration and the rest of the dashboard readouts
- Loading branch information
Showing
18 changed files
with
474 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
"""Data aggregators for dashboards""" | ||
|
||
from django.conf import settings | ||
from django.utils.timesince import timesince | ||
|
||
import jingo | ||
from tower import ugettext as _, ugettext_lazy as _lazy | ||
|
||
from wiki.models import Document | ||
|
||
|
||
def localizable_docs(): | ||
"""Return a Queryset of the Documents which are in the default locale, | ||
approved, and allow translations.""" | ||
return Document.objects.exclude(current_revision=None).filter( | ||
locale=settings.WIKI_DEFAULT_LANGUAGE, is_localizable=True) | ||
|
||
|
||
def overview_rows(locale): | ||
"""Return the iterable of dicts needed to draw the Overview table.""" | ||
# The Overview table is a special case: it has only a static number of | ||
# rows, so it has no expanded, all-rows view, and thus needs no slug, no | ||
# "max" kwarg on rows(), etc. It doesn't fit the Readout signature, so we | ||
# don't shoehorn it in. | ||
|
||
total = localizable_docs().count() | ||
if locale == settings.WIKI_DEFAULT_LANGUAGE: | ||
translated = total | ||
else: | ||
# How many approved documents are there in German that have parents? | ||
# TODO: Make sure caching doesn't foil this. | ||
translated = Document.objects.filter(locale=locale).exclude( | ||
current_revision=None).exclude(parent=None).count() | ||
return [dict(title=_('All Knowledge Base Articles'), | ||
numerator=translated, denominator=total, | ||
percent=(int(round(translated / float(total) * 100)) if total | ||
else 100), | ||
description=_('How many of the approved English ' | ||
'articles which allow translations ' | ||
'have an approved translation into this ' | ||
'language')), | ||
# TODO: Enable after we integrate WebTrends stats: | ||
# dict(title=_('Most Viewed Articles'), | ||
# numerator=0, denominator=1, | ||
# percent=0, | ||
# description=_('These are the top 15-20 most visited English' | ||
# ' articles, which in sum account for over 50%' | ||
# ' of the total traffic to the English ' | ||
# 'Knowledge Base.')) | ||
] | ||
|
||
|
||
class Readout(object): | ||
"""Abstract class representing one table on the Localization Dashboard | ||
Describing these as atoms gives us the whole-page details views for free. | ||
""" | ||
#title = _lazy(u'Localized Title of Readout') | ||
#slug = 'URL slug for detail page and CSS class for table' | ||
|
||
def rows(self, max=None): | ||
"""Return an iterable of dicts containing the data for the table. | ||
Limit to `max` rows. | ||
""" | ||
raise NotImplementedError | ||
|
||
def render(self, rows): | ||
"""Return HTML table rows for the given data. | ||
Default implementation renders a template named after the value of | ||
self.slug in the dashboards/includes/localization directory. | ||
""" | ||
return jingo.render_to_string( | ||
self.request, | ||
'dashboards/includes/localization/%s.html' % self.slug, | ||
{'rows': rows}) | ||
|
||
|
||
class UntranslatedReadout(Readout): | ||
title = _lazy(u'Untranslated Articles') | ||
slug = 'untranslated' | ||
|
||
def __init__(self, request): | ||
"""`request.locale` must not be the default locale.""" | ||
self.request = request | ||
|
||
def rows(self, max=None): | ||
# TODO: Optimize so there isn't another query per doc to get the | ||
# current_revision. Use the method from | ||
# http://www.caktusgroup.com/blog/2009/09/28/custom-joins-with-djangos- | ||
# queryjoin/. | ||
rows = Document.objects.raw('SELECT english.* FROM wiki_document ' | ||
'english RIGHT OUTER JOIN wiki_revision ON ' | ||
'english.current_revision_id=wiki_revision.id LEFT OUTER JOIN ' | ||
'wiki_document translated ON english.id=translated.parent_id AND ' | ||
'translated.locale=%s WHERE translated.id IS NULL AND ' | ||
'english.current_revision_id IS NOT NULL AND ' | ||
'english.is_localizable AND english.locale=%s ORDER BY ' | ||
'wiki_revision.reviewed DESC', | ||
params=[self.request.locale, settings.WIKI_DEFAULT_LANGUAGE]) | ||
for d in rows[:max] if max else rows: | ||
# TODO: i18nize better. Show only 1 unit of time: for example, | ||
# weeks instead of weeks+days or months instead of months+weeks. | ||
# | ||
# Not ideal but free: | ||
ago = (d.current_revision.reviewed and | ||
_('%s ago') % timesince(d.current_revision.reviewed)) | ||
yield (dict(title=d.title, | ||
url=d.get_absolute_url(), | ||
visits=0, percent=0, | ||
updated=ago)) |
20 changes: 20 additions & 0 deletions
20
apps/dashboards/templates/dashboards/includes/localization/untranslated.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<tr> | ||
<th>Article</th> | ||
<th>Visits</th> | ||
<th></th> | ||
<th>Updated</th> | ||
</tr> | ||
{% for row in rows %} | ||
<tr> | ||
<td><a href="{{ row.url }}">{{ row.title }}</a></td> | ||
<td> | ||
{{ number(row.visits) }} | ||
</td> | ||
<td> | ||
<div class="absolute-graph" style="width: {{ row.percent }}%"></div> | ||
</td> | ||
<td> | ||
{{ row.updated }} | ||
</td> | ||
</tr> | ||
{% endfor %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
{# vim: set ts=2 et sts=2 sw=2: #} | ||
{% extends "wiki/base.html" %} | ||
{% from "wiki/includes/sidebar_modules.html" import for_contributors, quick_links %} | ||
{% set title = _('Localizer Dashboard') %} | ||
|
||
{% block content %} | ||
<article id="localize" class="main dashboards"> | ||
<div id="breadcrumbs"> | ||
{{ _('You are here:') }} | ||
{{ breadcrumbs([(None, _(title))]) }} | ||
</div> | ||
|
||
<h1>{{ _(title) }}</h1> | ||
|
||
<div class="choice-list"> | ||
<label>{{ _('Show information for:') }}</label> | ||
<ul> | ||
<li> | ||
<a href="{{ url('dashboards.contributors') }}">{{ default_locale_name }}</a> | ||
</li> | ||
<li> | ||
{{ current_locale_name }} | ||
</li> | ||
</ul> | ||
</div> | ||
|
||
<details class="h2" open="open"> | ||
<summary>{{ _('Overview') }}</summary> | ||
<table class="overview"> | ||
{% for row in overview_rows() %} | ||
<tr> | ||
<td><a href="#">{{ row.title }}</a></td> | ||
<td> | ||
{{ number(row.numerator) }} | ||
<div class="denominator">of {{ number(row.denominator) }}</div> | ||
</td> | ||
<td> | ||
{{ row.percent }}% | ||
<div class="percent-graph"> | ||
<div style="width: {{ row.percent }}%"></div> | ||
</div> | ||
</td> | ||
<td> | ||
{{ row.description }} | ||
</td> | ||
</tr> | ||
{% endfor %} | ||
<tr> | ||
<td>{{ _('User Interface') }}</td> | ||
<td></td> | ||
<td></td> | ||
<td> | ||
{% trans url='https://localize.mozilla.org/' %} | ||
A fully translated user interface improves the experience for | ||
site visitors in your language. Help | ||
<a href="{{ url }}">translate the interface</a>. | ||
{% endtrans %} | ||
</td> | ||
</tr> | ||
</table> | ||
<div id="overview-options" class="choice-list"> | ||
<label>{{ _('More options:') }}</label> | ||
<ul> | ||
<li><a href="#untranslated">{{ _('Untranslated') }}</a></li> | ||
<li><a href="#out-of-date">{{ _('Out-of-Date') }}</a></li> | ||
<li><a href="#updates-needed">{{ _('Updates Needed') }}</a></li> | ||
<li><a href="#unreviewed-changes">{{ _('Unreviewed Changes') }}</a></li> | ||
</ul> | ||
</div> | ||
</details> | ||
|
||
<details class="h2" open="open"> | ||
{% with readout = readouts['untranslated'] %} | ||
<summary><a id="untranslated">{{ readout.title }}</a></summary> | ||
<table class="{{ readout.slug }}"> | ||
{{ readout.render(readout.rows(10))|safe }} | ||
</table> | ||
<div class="table-footer"> | ||
<a href="{{ url('dashboards.localization_detail', readout.slug) }}">{{ _('All untranslated articles...') }}</a> | ||
</div> | ||
{% endwith %} | ||
</details> | ||
</article> | ||
{% endblock %} | ||
|
||
{% block side %} | ||
{{ for_contributors() }} | ||
{{ quick_links() }} | ||
{% endblock %} |
24 changes: 24 additions & 0 deletions
24
apps/dashboards/templates/dashboards/localization_detail.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{# vim: set ts=2 et sts=2 sw=2: #} | ||
{% extends "wiki/base.html" %} | ||
{% from "wiki/includes/sidebar_modules.html" import for_contributors, quick_links %} | ||
{% set title = readout.title %} | ||
|
||
{% block content %} | ||
<article id="localize" class="main dashboards"> | ||
<div id="breadcrumbs"> | ||
{{ _('You are here:') }} | ||
{{ breadcrumbs([(url('dashboards.localization'), _('Localization Dashboard')), (None, readout.title)]) }} | ||
</div> | ||
|
||
<h1>{{ readout.title }}</h2> | ||
|
||
<table class="{{ readout.slug }}"> | ||
{{ readout.render(readout.rows())|safe }} | ||
</table> | ||
</article> | ||
{% endblock %} | ||
|
||
{% block side %} | ||
{{ for_contributors() }} | ||
{{ quick_links() }} | ||
{% endblock %} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from nose.tools import eq_ | ||
|
||
from sumo.tests import TestCase | ||
from sumo.urlresolvers import reverse | ||
|
||
|
||
class LocalizationDashTests(TestCase): | ||
def test_render(self): | ||
"""Assert the main dash renders and doesn't crash.""" | ||
response = self.client.get(reverse('dashboards.localization', | ||
locale='de'), | ||
follow=False) | ||
eq_(200, response.status_code) | ||
|
||
def test_untranslated_detail(self): | ||
"""Assert the whole-page Untranslated Articles view works.""" | ||
# We shouldn't have to write tests for every whole-page view: just | ||
# enough to cover all the different kinds of table templates. | ||
response = self.client.get(reverse('dashboards.localization_detail', | ||
args=['untranslated'], | ||
locale='de')) | ||
eq_(200, response.status_code) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from sumo.tests import TestCase | ||
from sumo.urlresolvers import reverse | ||
|
||
|
||
class LocalizationDashTests(TestCase): | ||
def test_redirect_to_contributor_dash(self): | ||
"""Should redirect to Contributor Dash if the locale is the default""" | ||
response = self.client.get(reverse('dashboards.localization', | ||
locale='en-US'), | ||
follow=True) | ||
self.assertRedirects(response, reverse('dashboards.contributors', | ||
locale='en-US')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,15 @@ | ||
from django.conf.urls.defaults import patterns, url | ||
from django.views.generic.simple import redirect_to | ||
|
||
|
||
urlpatterns = patterns('dashboards.views', | ||
url(r'^$', redirect_to, {'url': 'home'}), | ||
url(r'^home/$', 'home', name='home'), | ||
# TODO: mobile home page | ||
# TODO: live chat page | ||
# TODO: contributor dashboard | ||
# TODO: l10n dashboard (may be part of wiki app instead) | ||
url(r'^contributors$', 'contributors', name='dashboards.contributors'), | ||
# TODO: more contributor dashboard | ||
url(r'^localization$', 'localization', name='dashboards.localization'), | ||
url(r'^localization/(?P<readout>[^/]+)', 'localization_detail', | ||
name='dashboards.localization_detail'), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.