[bug 727086] Add unique visitors chart to KPI dashboard.

* Created a webtrends API helper.
* Refactored existing API calls in dashboards app to use it for getting
  wiki reports.
* Cron job to call webtrends API and save the data to the metrics model.
* API call to get this data.
* UI to display the chart.
This commit is contained in:
Ricky Rosario 2012-02-23 16:12:10 -05:00
Родитель 791d26f076
Коммит 4a9aacc024
20 изменённых файлов: 318 добавлений и 77 удалений

Просмотреть файл

@ -1,6 +1,5 @@
import json
import logging
from urllib2 import HTTPBasicAuthHandler, build_opener
from django.conf import settings
from django.contrib.auth.models import Group
@ -11,22 +10,13 @@ from tower import ugettext_lazy as _lazy
from dashboards import THIS_WEEK, ALL_TIME, PERIODS
from dashboards.personal import GROUP_DASHBOARDS
from sumo.models import ModelBase
from sumo.webtrends import Webtrends, StatsException
from wiki.models import Document
log = logging.getLogger('k.dashboards')
class StatsException(Exception):
"""An error in the stats returned by the third-party analytics package"""
def __init__(self, msg):
self.msg = msg
class StatsIOError(IOError):
"""An error communicating with WebTrends"""
def period_dates():
"""Return when each period begins and ends, relative to now.
@ -119,27 +109,9 @@ class WikiDocumentVisits(ModelBase):
@classmethod
def json_for(cls, period):
"""Return the JSON-formatted WebTrends stats for the given period.
Make one attempt to fetch and reload the data. If something fails, it's
the caller's responsibility to retry.
"""
auth_handler = HTTPBasicAuthHandler()
auth_handler.add_password(realm=settings.WEBTRENDS_REALM,
uri=settings.WEBTRENDS_WIKI_REPORT_URL,
user=settings.WEBTRENDS_USER,
passwd=settings.WEBTRENDS_PASSWORD)
opener = build_opener(auth_handler)
"""Return the JSON-formatted WebTrends stats for the given period."""
start, end = period_dates()[period]
url = (settings.WEBTRENDS_WIKI_REPORT_URL +
'&start_period=%s&end_period=%s' % (start, end))
try:
# TODO: A wrong username or password results in a recursion depth
# error.
return opener.open(url).read()
except IOError, e:
raise StatsIOError(*e.args)
return Webtrends.wiki_report(start, end)
class GroupDashboard(ModelBase):

Просмотреть файл

@ -4,9 +4,9 @@ from django.conf import settings
from mock import patch
from nose.tools import raises, eq_
from dashboards.models import (WikiDocumentVisits, StatsException, THIS_WEEK,
StatsIOError)
from dashboards.models import WikiDocumentVisits, THIS_WEEK
from sumo.tests import TestCase
from sumo.webtrends import StatsException, StatsIOError
from wiki.tests import document, revision

Просмотреть файл

@ -11,7 +11,7 @@ from tastypie.cache import SimpleCache
from tastypie.resources import Resource
from customercare.models import Reply
from kpi.models import Metric, MetricKind
from kpi.models import Metric, MetricKind, VISITORS_METRIC_CODE
from questions.models import Question, Answer, AnswerVote
from wiki.models import HelpfulVote, Revision
@ -167,7 +167,7 @@ class ElasticClickthroughResource(SearchClickthroughResource):
class SolutionResource(CachedResource):
"""Returns the number of questions maked as the solution."""
"""Returns the number of questions marked as solved."""
date = fields.DateField('date')
solved = fields.IntegerField('solved', default=0)
questions = fields.IntegerField('questions', default=0)
@ -364,6 +364,24 @@ class ArmyOfAwesomeContributorResource(CachedResource):
allowed_methods = ['get']
class VisitorsResource(CachedResource):
"""Returns the number of unique visitors per day."""
date = fields.DateField('date')
visitors = fields.IntegerField('visitors', default=0)
def get_object_list(self, request):
# Set up the query for the data we need
kind = MetricKind.objects.get(code=VISITORS_METRIC_CODE)
qs = Metric.objects.filter(kind=kind).order_by('-start')
return [Struct(date=m.start, visitors=m.value) for m in qs]
class Meta(object):
cache = SimpleCache()
resource_name = 'kpi_visitors'
allowed_methods = ['get']
def _monthly_qs_for(model_cls):
"""Return a queryset with the extra select for month and year."""
return model_cls.objects.filter(created__gte=_start_date()).extra(

36
apps/kpi/cron.py Normal file
Просмотреть файл

@ -0,0 +1,36 @@
from datetime import datetime, date, timedelta
import cronjobs
from kpi.models import Metric, MetricKind, VISITORS_METRIC_CODE
from sumo.webtrends import Webtrends
@cronjobs.register
def update_visitors_metric():
"""Get new visitor data from webtrends and save."""
try:
# Get the latest metric value.
last_metric = Metric.objects.filter(
kind__code=VISITORS_METRIC_CODE).order_by('-start')[0]
# Start updating the day after the last updated.
start = last_metric.start + timedelta(days=1)
except IndexError:
# There are no metrics yet, start from 2011-01-01
start = date(2011, 01, 01)
# Collect up until yesterday
end = date.today() - timedelta(days=1)
# Get the visitor data from webtrends.
visitors = Webtrends.visits(start, end)
# Create the metrics.
metric_kind = MetricKind.objects.get(code=VISITORS_METRIC_CODE)
for date_str, visits in visitors.items():
day = datetime.strptime(date_str,"%Y-%m-%d").date()
Metric.objects.create(
kind=metric_kind,
start=day,
end=day + timedelta(days=1),
value=visits)

Просмотреть файл

@ -4,6 +4,9 @@ from django.db.models import (CharField, DateField, ForeignKey,
from sumo.models import ModelBase
VISITORS_METRIC_CODE = 'general keymetrics:visitors'
class MetricKind(ModelBase):
"""A programmer-readable identifier of a metric, like 'clicks: search'"""
code = CharField(max_length=255, unique=True)

Просмотреть файл

@ -15,6 +15,7 @@
data-aoa-contributors-url="{{ url('api_dispatch_list', resource_name='kpi_active_aoa_contributors', api_name ='v1') }}"
data-sphinx-ctr-url="{{ url('api_dispatch_list', resource_name='sphinx-clickthrough-rate', api_name ='v1') }}"
data-elastic-ctr-url="{{ url('api_dispatch_list', resource_name='elastic-clickthrough-rate', api_name ='v1') }}"
data-visitors-url="{{ url('api_dispatch_list', resource_name='kpi_visitors', api_name ='v1') }}"
data-vote-url="{{ url('api_dispatch_list', resource_name='kpi_vote', api_name ='v1') }}">
</div>

Просмотреть файл

@ -5,7 +5,7 @@ import json
from nose.tools import eq_
from customercare.tests import reply
from kpi.models import Metric
from kpi.models import Metric, VISITORS_METRIC_CODE
from kpi.tests import metric, metric_kind
from sumo.tests import TestCase, LocalizingClient
from sumo.urlresolvers import reverse
@ -209,3 +209,20 @@ class KpiApiTests(TestCase):
# Correspnding ElasticSearch APIs are likely correct by dint
# of factoring.
def test_visitors(self):
"""Test unique visitors API call."""
# Create a reply
kind = metric_kind(code=VISITORS_METRIC_CODE, save=True)
metric(kind=kind, start=date.today(), end=date.today(), value=42,
save=True)
# There should be only one active contributor.
url = reverse('api_dispatch_list',
kwargs={'resource_name': 'kpi_visitors',
'api_name': 'v1'})
response = self.client.get(url + '?format=json')
eq_(200, response.status_code)
r = json.loads(response.content)
eq_(r['objects'][0]['visitors'], 42)

Просмотреть файл

@ -0,0 +1,27 @@
from datetime import date
from mock import patch
from nose.tools import eq_
from kpi.cron import update_visitors_metric, Webtrends
from kpi.models import Metric, VISITORS_METRIC_CODE
from kpi.tests import metric_kind
from sumo.tests import TestCase
class UpdateVisitorsTests(TestCase):
@patch.object(Webtrends, 'visits')
def test_update_visitors_cron(self, visits):
"""Verify the cron job inserts the right rows."""
visitor_kind = metric_kind(code=VISITORS_METRIC_CODE, save=True)
visits.return_value = {'2012-01-13': 42,
'2012-01-14': 193,
'2012-01-15': 33}
update_visitors_metric()
metrics = Metric.objects.filter(kind=visitor_kind)
eq_(3, len(metrics))
eq_(42, metrics[0].value)
eq_(193, metrics[1].value)
eq_(date(2012, 01, 15), metrics[2].start)

Просмотреть файл

@ -4,7 +4,7 @@ from tastypie.api import Api
from kpi.api import (SolutionResource, VoteResource, FastResponseResource,
ActiveKbContributorsResource, ActiveAnswerersResource,
SphinxClickthroughResource, ElasticClickthroughResource,
ArmyOfAwesomeContributorResource)
ArmyOfAwesomeContributorResource, VisitorsResource)
v1_api = Api(api_name='v1')
v1_api.register(SolutionResource())
@ -15,6 +15,7 @@ v1_api.register(ActiveAnswerersResource())
v1_api.register(SphinxClickthroughResource())
v1_api.register(ElasticClickthroughResource())
v1_api.register(ArmyOfAwesomeContributorResource())
v1_api.register(VisitorsResource())
urlpatterns = patterns('kpi.views',

Просмотреть файл

@ -0,0 +1,25 @@
from datetime import date
from mock import patch
from nose.tools import eq_
from sumo.tests import TestCase
from sumo.webtrends import Webtrends
KEY_METRICS_JSON_RESPONSE = '{ "definition" : { "accountID" : 123 , "profileID" : "ABC123" , "ID" : "ProfileMetrics" , "name" : "Profile Metrics" , "description" : "" , "language" : null , "timezone" : "UTC -1" , "dimensions" : [ { "ID" : "Profile ID" , "name" : "Profile ID" } ] , "measures" : [ { "name" : "PageViews" , "ID" : "PageViews" , "columnID" : 0 , "measureFormatType" : null },{ "name" : "Visits" , "ID" : "Visits" , "columnID" : 1 , "measureFormatType" : null },{ "name" : "Visitors" , "ID" : "Visitors" , "columnID" : 2 , "measureFormatType" : null },{ "name" : "NewVisitors" , "ID" : "NewVisitors" , "columnID" : 6 , "measureFormatType" : null },{ "name" : "BounceRate" , "ID" : "BounceRate" , "columnID" : 9 , "measureFormatType" : "percent" },{ "name" : "AvgTimeonSite" , "ID" : "AvgTimeOnSite" , "columnID" : 10 , "measureFormatType" : "time_seconds" },{ "name" : "AvgVisitorsperDay" , "ID" : "AvgVisitorsPerDay" , "columnID" : 11 , "measureFormatType" : null },{ "name" : "PageViewsperVisit" , "ID" : "PageViewsPerVisit" , "columnID" : 12 , "measureFormatType" : null },{ "name" : "AvgTimeonSiteperVisitor" , "ID" : "AvgSiteTimePerVisitor" , "columnID" : 13 , "measureFormatType" : "time_seconds" } ] } ,"data" : [ { "ABC123" : { "attributes" : { } , "measures" : { "PageViews" : 10523913 , "Visits" : 4274456 , "Visitors" : 4044284 , "NewVisitors" : 2078406 , "BounceRate" : 65.4970831375969 , "AvgTimeonSite" : 274.68279239068 , "AvgVisitorsperDay" : 577754.857142857 , "PageViewsperVisit" : 2.46204733421048 , "AvgTimeonSiteperVisitor" : 96.5800146577243 } , "SubRows" : [ { "period" : "Day" , "start_date" : "2012-01-01" , "end_date" : "2012-01-01" , "measures" : { "PageViews" : 1258143 , "Visits" : 524606 , "Visitors" : 495974 , "NewVisitors" : 254034 , "BounceRate" : 63.9746781394037 , "AvgTimeonSite" : 276.706781356731 , "AvgVisitorsperDay" : 495974 , "PageViewsperVisit" : 2.39826269619486 , "AvgTimeonSiteperVisitor" : 97.029864468702 } , "SubRows" : null } , { "period" : "Day" , "start_date" : "2012-01-02" , "end_date" : "2012-01-02" , "measures" : { "PageViews" : 1576014 , "Visits" : 649237 , "Visitors" : 614465 , "NewVisitors" : 320101 , "BounceRate" : 65.3790526417934 , "AvgTimeonSite" : 275.88984425623 , "AvgVisitorsperDay" : 614465 , "PageViewsperVisit" : 2.427486418673 , "AvgTimeonSiteperVisitor" : 97.7010993303117 } , "SubRows" : null } , { "period" : "Day" , "start_date" : "2012-01-03" , "end_date" : "2012-01-03" , "measures" : { "PageViews" : 1628215 , "Visits" : 664809 , "Visitors" : 629484 , "NewVisitors" : 326187 , "BounceRate" : 65.9521757376931 , "AvgTimeonSite" : 274.289682249817 , "AvgVisitorsperDay" : 629484 , "PageViewsperVisit" : 2.44914704824995 , "AvgTimeonSiteperVisitor" : 95.4439064376537 } , "SubRows" : null } , { "period" : "Day" , "start_date" : "2012-01-04" , "end_date" : "2012-01-04" , "measures" : { "PageViews" : 1622072 , "Visits" : 648066 , "Visitors" : 613411 , "NewVisitors" : 315226 , "BounceRate" : 65.8422136017011 , "AvgTimeonSite" : 272.315030946065 , "AvgVisitorsperDay" : 613411 , "PageViewsperVisit" : 2.50294260152515 , "AvgTimeonSiteperVisitor" : 95.3973388152478 } , "SubRows" : null } , { "period" : "Day" , "start_date" : "2012-01-05" , "end_date" : "2012-01-05" , "measures" : { "PageViews" : 1561292 , "Visits" : 619564 , "Visitors" : 585760 , "NewVisitors" : 298016 , "BounceRate" : 65.9460523852257 , "AvgTimeonSite" : 274.38456436392 , "AvgVisitorsperDay" : 585760 , "PageViewsperVisit" : 2.51998502172495 , "AvgTimeonSiteperVisitor" : 96.0481818492215 } , "SubRows" : null } , { "period" : "Day" , "start_date" : "2012-01-06" , "end_date" : "2012-01-06" , "measures" : { "PageViews" : 1520252 , "Visits" : 608327 , "Visitors" : 575889 , "NewVisitors" : 294054 , "BounceRate" : 65.7352049144621 , "AvgTimeonSite" : 271.518740910547 , "AvgVisitorsperDay" : 575889 , "PageViewsperVisit" : 2.49907040128089 , "AvgTimeonSiteperVisitor" : 95.6368657848995 } , "SubRows" : null } , { "period" : "Day" , "start_date" : "2012-01-07" , "end_date" : "2012-01-07" , "measures" : { "PageViews" : 1357925 , "Visits" : 559847 , "Visitors" : 529301 , "NewVisitors" : 270788 , "BounceRate" : 65.3650015093409 , "AvgTimeonSite" : 278.304308416466 , "AvgVisitorsperDay" : 529301 , "PageViewsperVisit" : 2.42552876053636 , "AvgTimeonSiteperVisitor" : 99.1935042631697 } , "SubRows" : null } ] } } ] }'
class WebtrendsTests(TestCase):
"""Tests for the Webtrends API helper."""
@patch.object(Webtrends, 'key_metrics')
def test_visits(self, key_metrics):
"""Test Webtrends.visits()."""
key_metrics.return_value = KEY_METRICS_JSON_RESPONSE
visits = Webtrends.visits(date(2012, 01, 01), date(2012, 01, 07))
eq_(7, len(visits))
eq_(495974, visits['2012-01-01'])
eq_(529301, visits['2012-01-07'])

82
apps/sumo/webtrends.py Normal file
Просмотреть файл

@ -0,0 +1,82 @@
from datetime import datetime, date
import json
from urllib2 import HTTPBasicAuthHandler, build_opener
from django.conf import settings
from sumo.helpers import urlparams
class StatsException(Exception):
"""An error in the stats returned by the third-party analytics package"""
def __init__(self, msg):
self.msg = msg
class StatsIOError(IOError):
"""An error communicating with WebTrends"""
class Webtrends(object):
"""Webtrends API helper."""
@classmethod
def request(cls, url, start, end, realm='Webtrends Basic Authentication'):
"""Make an authed request to the webtrends API.
Make one attempt to fetch and reload the data. If something fails, it's
the caller's responsibility to retry.
"""
# If start and/or end are date or datetime, convert to string.
if isinstance(start, (date, datetime)):
start = start.strftime('%Ym%md%d')
if isinstance(end, (date, datetime)):
end = end.strftime('%Ym%md%d')
auth_handler = HTTPBasicAuthHandler()
auth_handler.add_password(realm=realm,
uri=url,
user=settings.WEBTRENDS_USER,
passwd=settings.WEBTRENDS_PASSWORD)
opener = build_opener(auth_handler)
url = urlparams(url, start_period=start, end_period=end)
try:
# TODO: A wrong username or password results in a recursion depth
# error.
return opener.open(url).read()
except IOError, e:
raise StatsIOError(*e.args)
@classmethod
def wiki_report(cls, start, end):
"""Return the json for the wiki article visits report."""
return cls.request(settings.WEBTRENDS_WIKI_REPORT_URL, start, end)
@classmethod
def key_metrics(cls, start, end):
"""Return the json result for the KeyMetrics API call."""
url = ('https://ws.webtrends.com/v3/Reporting/profiles/{profile_id}'
'/KeyMetrics/?period_type=trend')
url = url.format(profile_id=settings.WEBTRENDS_PROFILE_ID)
return cls.request(url, start, end, realm='DX')
@classmethod
def visits(cls, start, end):
"""Return the number of unique visitors.
Returns a dict with daily numbers:
{u'2012-01-22': 404971,
u'2012-01-23': 434618,
u'2012-01-24': 501687,...}
"""
data = json.loads(cls.key_metrics(start, end))
rows = data['data'][0][settings.WEBTRENDS_PROFILE_ID]['SubRows']
if not isinstance(rows, list):
rows = [rows]
visits = {}
for row in rows:
visits[row['start_date']] = row['measures']['Visitors']
return visits

Просмотреть файл

@ -193,8 +193,6 @@ window.StockChartView = Backbone.View.extend({
style: {
width: 200
},
yDecimals: 1,
ySuffix: '%',
shared: true,
pointFormat: '<span style="color:{series.color}">{series.prettyName}</span>: <b>{point.y}</b><br/>'
},
@ -217,6 +215,14 @@ window.StockChartView = Backbone.View.extend({
},
series: []
};
if (this.options.percent) {
this.chartOptions.yAxis.title = {
text: '%'
};
this.chartOptions.tooltip.ySuffix = '%';
this.chartOptions.tooltip.yDecimals = 1;
}
},
render: function() {
@ -225,47 +231,68 @@ window.StockChartView = Backbone.View.extend({
if(data) {
_.each(this.options.series, function(series) {
var seriesData;
var mapper = series.mapper,
seriesData;
if (!mapper) {
mapper = function(o){
return {
x: Date.parse(o['date']),
y: o[series.numerator] / o[series.denominator] * 100
};
};
}
seriesData = _.map(data, function(o){
return [Date.parse(o['date']),
o[series.numerator] / o[series.denominator] * 100];
});
seriesData = _.map(data, mapper);
seriesData.reverse();
// Add the series with 3 different possible groupings:
// daily, weekly, monthly
self.chartOptions.series.push({
name: gettext('Daily'),
data: seriesData,
dataGrouping: {
enabled: false
}
});
self.chartOptions.series.push({
name: gettext('Weekly'),
data: seriesData,
dataGrouping: {
forced: true,
units: [['week', [1]]]
}
});
self.chartOptions.series.push({
name: gettext('Monthly'),
data: seriesData,
dataGrouping: {
forced: true,
units: [['month', [1]]]
}
});
if (!series.addGroupings) {
self.chartOptions.series.push({
name: series.name,
data: seriesData,
dataGrouping: {
enabled: false
}
});
} else {
// Add the series with 3 different possible groupings:
// daily, weekly, monthly
self.chartOptions.series.push({
name: gettext('Daily'),
data: seriesData,
dataGrouping: {
enabled: false
}
});
self.chartOptions.series.push({
name: gettext('Weekly'),
data: seriesData,
dataGrouping: {
forced: true,
units: [['week', [1]]]
}
});
self.chartOptions.series.push({
name: gettext('Monthly'),
data: seriesData,
dataGrouping: {
forced: true,
units: [['month', [1]]]
}
});
}
self.chart = new Highcharts.StockChart(self.chartOptions);
self.chart.series[0].prettyName = self.chart.series[1].prettyName = self.chart.series[2].prettyName = series.name;
if (!series.addGroupings) {
self.chart.series[0].prettyName = series.name;
}
else {
self.chart.series[0].prettyName = self.chart.series[1].prettyName = self.chart.series[2].prettyName = series.name;
// Hide the weekly and monthly series.
self.chart.series[1].hide();
self.chart.series[2].hide();
// Hide the weekly and monthly series.
self.chart.series[1].hide();
self.chart.series[2].hide();
}
});
}
return this;
@ -313,6 +340,10 @@ window.KpiDashboard = Backbone.View.extend({
});
this.elasticCtrChart.name = 'Elastic';
this.visitorsChart = new ChartModel([], {
url: $(this.el).data('visitors-url')
});
// Create the views.
this.solvedChartView = new StockChartView({
model: this.solvedChart,
@ -321,7 +352,8 @@ window.KpiDashboard = Backbone.View.extend({
series: [{
name: gettext('Solved'),
numerator: 'solved',
denominator: 'questions'
denominator: 'questions',
addGroupings: true
}]
});
@ -356,7 +388,8 @@ window.KpiDashboard = Backbone.View.extend({
series: [{
name: gettext('Responsed'),
numerator: 'responded',
denominator: 'questions'
denominator: 'questions',
addGroupings: true
}]
});
@ -425,6 +458,20 @@ window.KpiDashboard = Backbone.View.extend({
});
this.ctrView.addModel(this.elasticCtrChart);
this.visitorsView = new StockChartView({
model: this.visitorsChart,
title: gettext('Daily Unique Visitors'),
series: [{
name: gettext('Visitors'),
mapper: function(o) {
return {
x: Date.parse(o['date']),
y: o['visitors']
};
}
}]
});
// Render the views.
$(this.el)
.append(this.solvedChartView.render().el)
@ -433,7 +480,8 @@ window.KpiDashboard = Backbone.View.extend({
.append(this.activeKbContributorsView.render().el)
.append(this.activeAnswerersView.render().el)
.append(this.aoaContributorsView.render().el)
.append(this.ctrView.render().el);
.append(this.ctrView.render().el)
.append(this.visitorsView.render().el);
// Load up the models.
@ -445,6 +493,7 @@ window.KpiDashboard = Backbone.View.extend({
this.voteChart.fetch();
this.sphinxCtrChart.fetch();
this.elasticCtrChart.fetch();
this.visitorsChart.fetch();
}
});

Просмотреть файл

@ -0,0 +1,2 @@
insert into kpi_metrickind (code) values
('general keymetrics:visitors');

Просмотреть файл

@ -28,6 +28,7 @@ HOME = /tmp
42 0 * * * {{ cron }} update_top_contributors
0 21 * * * {{ cron }} cache_most_unhelpful_kb_articles
47 2 * * * {{ cron }} remove_expired_registration_profiles
0 9 * * * {{ cron }} update_visitors_metric
# Twice per week.
#05 01 * * 1,4 {{ cron }} update_weekly_votes

Просмотреть файл

@ -71,6 +71,7 @@ HOME = /tmp
42 0 * * * $CRON update_top_contributors
0 21 * * * $CRON cache_most_unhelpful_kb_articles
47 2 * * * $CRON remove_expired_registration_profiles
0 9 * * * $CRON update_visitors_metric
# Twice per week.
#05 01 * * 1,4 $CRON update_weekly_votes

Просмотреть файл

@ -28,6 +28,7 @@ HOME = /tmp
42 0 * * * cd /data/www/support.mozilla.com/kitsune; /usr/bin/python26 manage.py cron update_top_contributors
0 21 * * * cd /data/www/support.mozilla.com/kitsune; /usr/bin/python26 manage.py cron cache_most_unhelpful_kb_articles
47 2 * * * cd /data/www/support.mozilla.com/kitsune; /usr/bin/python26 manage.py cron remove_expired_registration_profiles
0 9 * * * cd /data/www/support.mozilla.com/kitsune; /usr/bin/python26 manage.py cron update_visitors_metric
# Twice per week.
#05 01 * * 1,4 cd /data/www/support.mozilla.com/kitsune; /usr/bin/python26 manage.py cron update_weekly_votes

Просмотреть файл

@ -28,6 +28,7 @@ HOME = /tmp
42 0 * * * cd /data/www/support.allizom.org/kitsune; /usr/bin/python26 manage.py cron update_top_contributors
0 21 * * * cd /data/www/support.allizom.org/kitsune; /usr/bin/python26 manage.py cron cache_most_unhelpful_kb_articles
47 2 * * * cd /data/www/support.allizom.org/kitsune; /usr/bin/python26 manage.py cron remove_expired_registration_profiles
0 9 * * * cd /data/www/support.allizom.org/kitsune; /usr/bin/python26 manage.py cron update_visitors_metric
# Twice per week.
#05 01 * * 1,4 cd /data/www/support.allizom.org/kitsune; /usr/bin/python26 manage.py cron update_weekly_votes

Просмотреть файл

@ -28,6 +28,7 @@ HOME = /tmp
42 0 * * * cd /data/www/support-release.allizom.org/kitsune; /usr/bin/python26 manage.py cron update_top_contributors
0 21 * * * cd /data/www/support-release.allizom.org/kitsune; /usr/bin/python26 manage.py cron cache_most_unhelpful_kb_articles
47 2 * * * cd /data/www/support-release.allizom.org/kitsune; /usr/bin/python26 manage.py cron remove_expired_registration_profiles
0 9 * * * cd /data/www/support-release.allizom.org/kitsune; /usr/bin/python26 manage.py cron update_visitors_metric
# Twice per week.
#05 01 * * 1,4 cd /data/www/support-release.allizom.org/kitsune; /usr/bin/python26 manage.py cron update_weekly_votes

Просмотреть файл

@ -769,12 +769,12 @@ TIDINGS_REVERSE = 'sumo.urlresolvers.reverse'
CHAT_SERVER = 'https://chat-support.mozilla.com:9091'
CHAT_CACHE_KEY = 'sumo-chat-queue-status'
WEBTRENDS_PROFILE_ID = 'ABC123' # Profile id for SUMO
WEBTRENDS_WIKI_REPORT_URL = 'https://example.com/see_production.rst'
WEBTRENDS_USER = r'someaccount\someusername'
WEBTRENDS_PASSWORD = 'password'
WEBTRENDS_EPOCH = date(2010, 8, 1) # When WebTrends started gathering stats on
# the KB
WEBTRENDS_REALM = 'Webtrends Basic Authentication'
MOBILE_COOKIE = 'msumo'

Просмотреть файл

@ -20,3 +20,6 @@ REDIS_BACKENDS = {
'karma': 'redis://localhost:6383?socket_timeout=0.5&db=2',
'helpfulvotes': 'redis://localhost:6383?socket_timeout=0.5&db=2',
}
# Use fake webtrends settings.
WEBTRENDS_PROFILE_ID = 'ABC123'