Skip to content
Browse files

[bug 889913] Track l10n coverage % for all locale/product combos.

* Created new model for these metrics
* Added cronjob to calculate and store values
  • Loading branch information...
1 parent 7ac3826 commit 88cdfc6d07e6abca07deeefac1ecba7ac11b2aea @rlr rlr committed Jul 17, 2013
View
11 kitsune/dashboards/admin.py
@@ -0,0 +1,11 @@
+from django.contrib import admin
+
+from kitsune.dashboards.models import WikiMetric
+
+
+class WikiMetricAdmin(admin.ModelAdmin):
+ list_display = ['code', 'date', 'locale', 'product', 'value']
+ list_filter = ['code', 'product', 'locale']
+
+
+admin.site.register(WikiMetric, WikiMetricAdmin)
View
53 kitsune/dashboards/cron.py
@@ -1,9 +1,14 @@
+from datetime import date
+
from django.conf import settings
-from django.db import transaction, connection
+from django.db import connection
import cronjobs
-from kitsune.dashboards.models import PERIODS, WikiDocumentVisits
+from kitsune.dashboards.models import (
+ PERIODS, WikiDocumentVisits, WikiMetric, L10N_TOP20_CODE, L10N_ALL_CODE)
+from kitsune.dashboards.readouts import overview_rows
+from kitsune.products.models import Product
from kitsune.sumo.redis_utils import redis_client
from kitsune.wiki.models import Document
@@ -19,6 +24,50 @@ def reload_wiki_traffic_stats():
WikiDocumentVisits.reload_period_from_analytics(period)
+@cronjobs.register
+def update_l10n_coverage_metrics():
+ """Calculate and store the l10n metrics for each locale/product.
+
+ The metrics are:
+ * Percent localized of top 20 articles
+ * Percent localized of all articles
+ """
+ today = date.today()
+
+ # Loop through all locales.
+ for locale in settings.SUMO_LANGUAGES:
+
+ # Skip en-US, it is always 100% localized.
+ if locale == settings.WIKI_DEFAULT_LANGUAGE:
+ continue
+
+ # Loop through all enabled products, including None (really All).
+ for product in [None] + list(Product.objects.filter(visible=True)):
+
+ # (Ab)use the overview_rows helper from the readouts.
+ rows = overview_rows(locale=locale, product=product)
+
+ # % of top 20 articles
+ top20 = rows['most-visited']
+ percent = 100.0 * float(top20['numerator']) / top20['denominator']
+ WikiMetric.objects.create(
+ code=L10N_TOP20_CODE,
+ locale=locale,
+ product=product,
+ date=today,
+ value=percent)
+
+ # % of all articles
+ all_ = rows['all']
+ percent = 100.0 * float(all_['numerator']) / all_['denominator']
+ WikiMetric.objects.create(
+ code=L10N_ALL_CODE,
+ locale=locale,
+ product=product,
+ date=today,
+ value=percent)
+
+
def _get_old_unhelpful():
"""
Gets the data from 2 weeks ago and formats it as output so that we can
View
33 kitsune/dashboards/models.py
@@ -4,9 +4,12 @@
from django.conf import settings
from django.db import models
+from tower import ugettext_lazy as _lazy
+
from kitsune.dashboards import (LAST_7_DAYS, LAST_30_DAYS, LAST_90_DAYS,
ALL_TIME, PERIODS)
-from kitsune.sumo.models import ModelBase
+from kitsune.products.models import Product
+from kitsune.sumo.models import ModelBase, LocaleField
from kitsune.sumo import googleanalytics
from kitsune.wiki.models import Document
@@ -56,3 +59,31 @@ def reload_period_from_analytics(cls, period):
# Don't erase interesting data if there's nothing to replace it:
log.warning('Google Analytics returned no interesting data,'
' so I kept what I had.')
+
+
+L10N_TOP20_CODE = 'percent_localized_top20'
+L10N_ALL_CODE = 'percent_localized_all'
+METRIC_CODE_CHOICES = (
+ (L10N_TOP20_CODE, _lazy(u'Percent Localized: Top 20')),
+ (L10N_ALL_CODE, _lazy(u'Percent Localized: All')),
+)
+
+
+class WikiMetric(ModelBase):
+ """A single numeric measurement for a locale, product and date.
+
+ For example, the percentage of all FxOS articles localized to Spanish."""
+ code = models.CharField(
+ db_index=True, max_length=255, choices=METRIC_CODE_CHOICES)
+ locale = LocaleField(db_index=True, null=True, blank=True)
+ product = models.ForeignKey(Product, null=True, blank=True)
+ date = models.DateField()
+ value = models.FloatField()
+
+ class Meta(object):
+ unique_together = ('code', 'product', 'locale', 'date')
+
+ def __unicode__(self):
+ return '[{date}][{locale}][{product}] {code}: {value}'.format(
+ date=self.date, code=self.code, locale=self.locale,
+ value=self.value, product=self.product)
View
87 kitsune/dashboards/tests/test_cron.py
@@ -8,11 +8,14 @@
from kitsune.dashboards.cron import (
cache_most_unhelpful_kb_articles, _get_old_unhelpful,
- _get_current_unhelpful)
+ _get_current_unhelpful, update_l10n_coverage_metrics)
+from kitsune.dashboards.models import (
+ WikiMetric, L10N_TOP20_CODE, L10N_ALL_CODE)
+from kitsune.products.tests import product
from kitsune.sumo.redis_utils import redis_client, RedisError
from kitsune.sumo.tests import TestCase
-from kitsune.wiki.models import HelpfulVote
-from kitsune.wiki.tests import revision
+from kitsune.wiki.models import HelpfulVote, Revision
+from kitsune.wiki.tests import revision, document
def _add_vote_in_past(rev, vote, days_back):
@@ -235,3 +238,81 @@ def test_caching_sorting(self):
assert '%d::%.1f:' % (r2.id, 242.0) in result[0]
assert '%d::%.1f:' % (r3.id, 122.0) in result[1]
assert '%d::%.1f:' % (r.id, 102.0) in result[2]
+
+
+class L10nCoverageMetricsTests(TestCase):
+
+ def test_update_l10n_coverage_metrics(self):
+ """Test the cron job that updates l10n coverage metrics."""
+ p = product(save=True)
+
+ # Create en-US documents.
+ for i in range(20):
+ r = revision(
+ is_approved=True, is_ready_for_localization=True, save=True)
+ r.document.products.add(p)
+
+ r1 = Revision.objects.all()[0]
+ r2 = Revision.objects.all()[1]
+
+ # Translate one to es.
+ d = document(parent=r1.document, locale='es', save=True)
+ revision(document=d, based_on=r1, is_approved=True, save=True)
+
+ # Translate two to de.
+ d = document(parent=r1.document, locale='de', save=True)
+ revision(document=d, based_on=r1, is_approved=True, save=True)
+ d = document(parent=r2.document, locale='de', save=True)
+ revision(document=d, based_on=r2, is_approved=True, save=True)
+
+ # Translate all to ak.
+ for r in Revision.objects.filter(document__locale='en-US'):
+ d = document(parent=r.document, locale='ak', save=True)
+ revision(document=d, based_on=r, is_approved=True, save=True)
+
+ # Call the cronjob
+ update_l10n_coverage_metrics()
+
+ # Verify es metrics.
+ eq_(4, WikiMetric.objects.filter(locale='es').count())
+ eq_(5.0, WikiMetric.objects.get(
+ locale='es', product=p, code=L10N_TOP20_CODE).value)
+ eq_(5.0, WikiMetric.objects.get(
+ locale='es', product=p, code=L10N_ALL_CODE).value)
+ eq_(5.0, WikiMetric.objects.get(
+ locale='es', product=None, code=L10N_TOP20_CODE).value)
+ eq_(5.0, WikiMetric.objects.get(
+ locale='es', product=None, code=L10N_ALL_CODE).value)
+
+ # Verify de metrics.
+ eq_(4, WikiMetric.objects.filter(locale='de').count())
+ eq_(10.0, WikiMetric.objects.get(
+ locale='de', product=p, code=L10N_TOP20_CODE).value)
+ eq_(10.0, WikiMetric.objects.get(
+ locale='de', product=p, code=L10N_ALL_CODE).value)
+ eq_(10.0, WikiMetric.objects.get(
+ locale='de', product=None, code=L10N_TOP20_CODE).value)
+ eq_(10.0, WikiMetric.objects.get(
+ locale='de', product=None, code=L10N_ALL_CODE).value)
+
+ # Verify ak metrics.
+ eq_(4, WikiMetric.objects.filter(locale='de').count())
+ eq_(100.0, WikiMetric.objects.get(
+ locale='ak', product=p, code=L10N_TOP20_CODE).value)
+ eq_(100.0, WikiMetric.objects.get(
+ locale='ak', product=p, code=L10N_ALL_CODE).value)
+ eq_(100.0, WikiMetric.objects.get(
+ locale='ak', product=None, code=L10N_TOP20_CODE).value)
+ eq_(100.0, WikiMetric.objects.get(
+ locale='ak', product=None, code=L10N_ALL_CODE).value)
+
+ # Verify it metrics.
+ eq_(4, WikiMetric.objects.filter(locale='it').count())
+ eq_(0.0, WikiMetric.objects.get(
+ locale='it', product=p, code=L10N_TOP20_CODE).value)
+ eq_(0.0, WikiMetric.objects.get(
+ locale='it', product=p, code=L10N_ALL_CODE).value)
+ eq_(0.0, WikiMetric.objects.get(
+ locale='it', product=None, code=L10N_TOP20_CODE).value)
+ eq_(0.0, WikiMetric.objects.get(
+ locale='it', product=None, code=L10N_ALL_CODE).value)
View
23 migrations/224-wiki-metrics-models.sql
@@ -0,0 +1,23 @@
+CREATE TABLE `dashboards_wikimetric` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `code` varchar(255) NOT NULL,
+ `locale` varchar(7),
+ `product_id` integer,
+ `date` date NOT NULL,
+ `value` double precision NOT NULL,
+ UNIQUE (`code`, `product_id`, `locale`, `date`)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+ALTER TABLE `dashboards_wikimetric` ADD CONSTRAINT `product_id_refs_id_7aaf7d8c` FOREIGN KEY (`product_id`) REFERENCES `products_product` (`id`);
+CREATE INDEX `dashboards_wikimetric_65da3d2c` ON `dashboards_wikimetric` (`code`);
+CREATE INDEX `dashboards_wikimetric_928541cb` ON `dashboards_wikimetric` (`locale`);
+CREATE INDEX `dashboards_wikimetric_bb420c12` ON `dashboards_wikimetric` (`product_id`);
+
+INSERT INTO `django_content_type` (`name`, `app_label`, `model`) VALUES ('wikimetric','dashboards','wikimetric');
+
+SELECT (@id:=`id`) FROM `django_content_type` WHERE `name` = 'wikimetric';
+
+INSERT INTO `auth_permission` (`name`, `content_type_id`, `codename`)
+ VALUES ('Can add wikimetric',@id,'add_wikimetric'),
+ ('Can change wikimetric',@id,'change_wikimetric'),
+ ('Can delete wikimetric',@id,'delete_wikimetric');
View
1 scripts/crontab/crontab.tpl
@@ -32,6 +32,7 @@ HOME = /tmp
0 2 * * * {{ cron }} update_search_ctr_metric
0 4 * * * {{ cron }} auto_lock_old_questions
0 5 * * * {{ cron }} reindex_kb
+0 1 * * * {{ cron }} update_l10n_coverage_metrics
# Twice per week.
#05 01 * * 1,4 {{ cron }} update_weekly_votes

0 comments on commit 88cdfc6

Please sign in to comment.
Something went wrong with that request. Please try again.