Skip to content
This repository has been archived by the owner on Aug 26, 2022. It is now read-only.

Commit

Permalink
Merge pull request #842 from groovecoder/metric-model-834313
Browse files Browse the repository at this point in the history
fix bug 834313 - add kpi app with metric model
  • Loading branch information
darkwing committed Feb 7, 2013
2 parents 7cab02f + 45dec31 commit ff18d8e
Show file tree
Hide file tree
Showing 11 changed files with 331 additions and 0 deletions.
Empty file added apps/kpi/__init__.py
Empty file.
16 changes: 16 additions & 0 deletions apps/kpi/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.contrib import admin

from kpi.models import Metric, MetricKind


class MetricAdmin(admin.ModelAdmin):
list_display = ['kind', 'start', 'end', 'value']
list_filter = ['kind']


class MetricKindAdmin(admin.ModelAdmin):
pass


admin.site.register(Metric, MetricAdmin)
admin.site.register(MetricKind, MetricKindAdmin)
32 changes: 32 additions & 0 deletions apps/kpi/cron.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from datetime import date, timedelta

import cronjobs

from kpi.models import Metric, MetricKind, L10N_METRIC_CODE
from wiki.models import Document


@cronjobs.register
def update_l10n_metric():
"""Calculate new l10n coverage numbers and save.
We don't enforce l10n completeness, so coverage is the % of translated docs
that are modified AFTER the most recent edit to their en-US source.
"""
up_to_date_translations = 0
translations = Document.objects.exclude(locale='en-US')
for translation in translations:
if translation.modified > translation.parent.modified:
up_to_date_translations += 1

coverage = up_to_date_translations / float(translations.count())

# Save the value to Metric table.
metric_kind = MetricKind.objects.get(code=L10N_METRIC_CODE)
start = date.today()
end = start + timedelta(days=1)
metric, created = Metric.objects.get_or_create(kind=metric_kind,
start=start,
end=end)
metric.value = int(coverage*100) # store as a % int
metric.save()
60 changes: 60 additions & 0 deletions apps/kpi/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models

class Migration(SchemaMigration):

def forwards(self, orm):

# Adding model 'MetricKind'
db.create_table('kpi_metrickind', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('code', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)),
))
db.send_create_signal('kpi', ['MetricKind'])

# Adding model 'Metric'
db.create_table('kpi_metric', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('kind', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['kpi.MetricKind'])),
('start', self.gf('django.db.models.fields.DateField')()),
('end', self.gf('django.db.models.fields.DateField')()),
('value', self.gf('django.db.models.fields.PositiveIntegerField')()),
))
db.send_create_signal('kpi', ['Metric'])

# Adding unique constraint on 'Metric', fields ['kind', 'start', 'end']
db.create_unique('kpi_metric', ['kind_id', 'start', 'end'])


def backwards(self, orm):

# Removing unique constraint on 'Metric', fields ['kind', 'start', 'end']
db.delete_unique('kpi_metric', ['kind_id', 'start', 'end'])

# Deleting model 'MetricKind'
db.delete_table('kpi_metrickind')

# Deleting model 'Metric'
db.delete_table('kpi_metric')


models = {
'kpi.metric': {
'Meta': {'unique_together': "[('kind', 'start', 'end')]", 'object_name': 'Metric'},
'end': ('django.db.models.fields.DateField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['kpi.MetricKind']"}),
'start': ('django.db.models.fields.DateField', [], {}),
'value': ('django.db.models.fields.PositiveIntegerField', [], {})
},
'kpi.metrickind': {
'Meta': {'object_name': 'MetricKind'},
'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
}
}

complete_apps = ['kpi']
40 changes: 40 additions & 0 deletions apps/kpi/migrations/0002_add_metrickinds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models

from kpi.models import L10N_METRIC_CODE

class Migration(DataMigration):

def forwards(self, orm):
"Write your forwards methods here."
l10n_metric = orm.MetricKind(code=L10N_METRIC_CODE)
l10n_metric.save()


def backwards(self, orm):
"Write your backwards methods here."
l10n_metric = orm.MetricKind.get(code=L10N_METRIC_CODE)
l10n_metric.delete()



models = {
'kpi.metric': {
'Meta': {'unique_together': "[('kind', 'start', 'end')]", 'object_name': 'Metric'},
'end': ('django.db.models.fields.DateField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['kpi.MetricKind']"}),
'start': ('django.db.models.fields.DateField', [], {}),
'value': ('django.db.models.fields.PositiveIntegerField', [], {})
},
'kpi.metrickind': {
'Meta': {'object_name': 'MetricKind'},
'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
}
}

complete_apps = ['kpi']
37 changes: 37 additions & 0 deletions apps/kpi/migrations/0003_auto__chg_field_metric_value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models

class Migration(SchemaMigration):

def forwards(self, orm):

# Changing field 'Metric.value'
db.alter_column('kpi_metric', 'value', self.gf('django.db.models.fields.PositiveIntegerField')(null=True))


def backwards(self, orm):

# User chose to not deal with backwards NULL issues for 'Metric.value'
raise RuntimeError("Cannot reverse this migration. 'Metric.value' and its values cannot be restored.")


models = {
'kpi.metric': {
'Meta': {'unique_together': "[('kind', 'start', 'end')]", 'object_name': 'Metric'},
'end': ('django.db.models.fields.DateField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['kpi.MetricKind']"}),
'start': ('django.db.models.fields.DateField', [], {}),
'value': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
},
'kpi.metrickind': {
'Meta': {'object_name': 'MetricKind'},
'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
}
}

complete_apps = ['kpi']
Empty file added apps/kpi/migrations/__init__.py
Empty file.
53 changes: 53 additions & 0 deletions apps/kpi/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from django.db.models import (CharField, DateField, ForeignKey,
PositiveIntegerField)

from sumo.models import ModelBase


L10N_METRIC_CODE = 'general wiki:l10n:coverage'
KB_L10N_CONTRIBUTORS_METRIC_CODE = 'general wiki:l10n:contributors'


class MetricKind(ModelBase):
"""A programmer-readable identifier of a metric, like 'clicks: search'"""
code = CharField(max_length=255, unique=True)

def __unicode__(self):
return self.code


class Metric(ModelBase):
"""A single numeric measurement aggregated over a span of time.
For example, the number of hits to a page during a specific week.
"""
# If we need to (and I would prefer to avoid this, because it wrecks the
# consistent semantics of rows--some will be aggregations and others will
# not), we can lift the unique constraint on kind/start/end for things that
# are collected in realtime and can't be immediately bucketed. However, in
# such cases it would probably be nicer to our future selves to put them in
# a separate store (table or whatever) until bucketing.

# In the design of this table, we trade off constraints for generality.
# There's no way to have the DB prove, for example, that both halves of a
# clickthrough rate ratio will always exist, but the app can make sure it's
# true upon inserting them.

kind = ForeignKey(MetricKind)
start = DateField()

# Not useful yet. Present metrics have spans of known length.
end = DateField()

# Ints should be good enough for all the currently wish-listed metrics.
# Percents can be (even better) represented by 2 separate metrics: one for
# numerator, one for denominator.
value = PositiveIntegerField(blank=True, null=True)

class Meta(object):
unique_together = [('kind', 'start', 'end')]

def __unicode__(self):
return '%s (%s thru %s): %s' % (
self.kind, self.start, self.end, self.value)
19 changes: 19 additions & 0 deletions apps/kpi/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from datetime import date

from kpi.models import MetricKind, Metric


def metric_kind(**kwargs):
mk = MetricKind(code=kwargs.get('code', 'something'))
mk.save()
return mk

def metric(**kwargs):
defaults = {'start': date(1980, 02, 16),
'end': date(1980, 02, 23),
'value': 33}
if 'kind' not in kwargs:
defaults['kind'] = metric_kind(save=True)
defaults.update(kwargs)
m = Metric(**defaults)
return m
73 changes: 73 additions & 0 deletions apps/kpi/tests/test_cron.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from datetime import date
import time

from nose.tools import eq_

from kpi.cron import update_l10n_metric
from kpi.models import Metric, L10N_METRIC_CODE
from kpi.tests import metric_kind
from sumo.tests import TestCase
from wiki.tests import document, revision


class CronJobTests(TestCase):

fixtures = ['test_users.json']

def test_update_l10n_metric_cron(self):
"""Verify the cron job creates the correct metric."""
l10n_kind = metric_kind(code=L10N_METRIC_CODE, save=True)

# Create the en-US document with an approved revision.
doc = document(save=True)
rev = revision(
document=doc,
is_approved=True,
save=True)

time.sleep(1)

# Create an es translation
es_doc = document(parent=doc, locale='es', save=True)
revision(
document=es_doc,
is_approved=True,
based_on=rev,
save=True)

# Run it and verify results.
# Value should be 100%
update_l10n_metric()
metrics = Metric.objects.filter(kind=l10n_kind)
eq_(1, len(metrics))
eq_(100, metrics[0].value)

# Update the en-US document
rev2 = revision(
document=doc,
is_approved=True,
save=True)

# Run it and verify results.
# Value should be 0%
update_l10n_metric()
metrics = Metric.objects.filter(kind=l10n_kind)
eq_(1, len(metrics))
eq_(0, metrics[0].value)

time.sleep(1)

# Create a pt-BR translation
ptBR_doc = document(parent=doc, locale='pt-BR', save=True)
revision(
document=ptBR_doc,
is_approved=True,
based_on=rev,
save=True)

# Run it and verify results.
# Value should be 50%
update_l10n_metric()
metrics = Metric.objects.filter(kind=l10n_kind)
eq_(1, len(metrics))
eq_(50, metrics[0].value)
1 change: 1 addition & 0 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ def lazy_language_deki_map():
'wiki',
#'kbforums',
'dashboards',
'kpi',
'gallery',
#'customercare',
#'twitter',
Expand Down

0 comments on commit ff18d8e

Please sign in to comment.