drop global stats and dashboard (#11047)

This commit is contained in:
Andrew Williamson 2019-03-28 17:00:20 +00:00 коммит произвёл GitHub
Родитель 78c16bd645
Коммит a7a6d50be7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
28 изменённых файлов: 27 добавлений и 621 удалений

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

@ -35,7 +35,6 @@ HOME=/tmp
# Once per day after metrics import is done
30 12 * * * %(z_cron)s update_addon_download_totals
35 12 * * * %(z_cron)s weekly_downloads
25 13 * * * %(z_cron)s update_global_totals
30 13 * * * %(z_cron)s update_addon_average_daily_users
00 14 * * * %(z_cron)s index_latest_stats

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

@ -127,7 +127,6 @@ class TestHomepageFeatures(TestCase):
'base/users',
'base/addon_3615',
'base/collections',
'base/global-stats',
'base/featured',
'addons/featured',
'bandwagon/featured_collections']

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

@ -1,20 +0,0 @@
[
{
"pk": 216632,
"model": "stats.globalstat",
"fields": {
"count": 1757933362,
"date": "2010-01-17",
"name": "addon_total_downloads"
}
},
{
"pk": 216653,
"model": "stats.globalstat",
"fields": {
"count": 160702913,
"date": "2010-01-17",
"name": "addon_total_updatepings"
}
}
]

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

@ -8,7 +8,7 @@ from olympia.amo.tests import TestCase
class TestRedirects(TestCase):
fixtures = ['ratings/test_models', 'addons/persona', 'base/global-stats']
fixtures = ['ratings/test_models', 'addons/persona']
def test_persona_category(self):
"""`/personas/film and tv/` should go to /themes/film-and-tv/"""

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

@ -93,7 +93,7 @@ class Test404(TestCase):
class TestCommon(TestCase):
fixtures = ('base/users', 'base/global-stats', 'base/addon_3615')
fixtures = ('base/users', 'base/addon_3615')
def setUp(self):
super(TestCommon, self).setUp()

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

@ -1179,7 +1179,6 @@ CELERY_TASK_ROUTES = {
'olympia.stats.tasks.index_download_counts': {'queue': 'stats'},
'olympia.stats.tasks.index_theme_user_counts': {'queue': 'stats'},
'olympia.stats.tasks.index_update_counts': {'queue': 'stats'},
'olympia.stats.tasks.update_global_totals': {'queue': 'stats'},
# Tags
'olympia.tags.tasks.update_all_tag_stats': {'queue': 'tags'},
@ -1777,7 +1776,6 @@ CRON_JOBS = {
'cleanup_extracted_file': 'olympia.files.cron',
'cleanup_validation_results': 'olympia.files.cron',
'update_global_totals': 'olympia.stats.cron',
'index_latest_stats': 'olympia.stats.cron',
'update_user_ratings': 'olympia.users.cron',

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

@ -1,17 +1,13 @@
import datetime
from django.core.management import call_command
from django.db.models import Max
import waffle
from celery import group
import olympia.core.logger
from olympia.lib.es.utils import raise_if_reindex_in_progress
from . import tasks
from .models import UpdateCount
@ -19,26 +15,6 @@ task_log = olympia.core.logger.getLogger('z.task')
cron_log = olympia.core.logger.getLogger('z.cron')
def update_global_totals(date=None):
"""Update global statistics totals."""
raise_if_reindex_in_progress('amo')
if date:
date = datetime.datetime.strptime(date, '%Y-%m-%d').date()
# Assume that we want to populate yesterday's stats by default.
today = date or datetime.date.today() - datetime.timedelta(days=1)
today_jobs = [{'job': job, 'date': today} for job in
tasks._get_daily_jobs(date)]
max_update = date or UpdateCount.objects.aggregate(max=Max('date'))['max']
metrics_jobs = [{'job': job, 'date': max_update} for job in
tasks._get_metrics_jobs(date)]
ts = [tasks.update_global_totals.subtask(kwargs=kw)
for kw in today_jobs + metrics_jobs]
group(ts).apply_async()
def index_latest_stats(index=None):
if not waffle.switch_is_active('local-statistics-processing'):
return False

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

@ -105,18 +105,6 @@ class ThemeUpdateCountBulk(models.Model):
db_table = 'theme_update_counts_bulk'
class GlobalStat(models.Model):
id = PositiveAutoField(primary_key=True)
name = models.CharField(max_length=255)
count = models.IntegerField()
date = models.DateField()
class Meta:
db_table = 'global_stats'
unique_together = ('name', 'date')
get_latest_by = 'date'
class ThemeUserCount(StatsSearchMixin, models.Model):
"""Theme popularity (weekly average of users).

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

@ -1,23 +1,9 @@
import datetime
from django.db import connection
from django.db.models import Max, Sum
import six
from dateutil.parser import parse as dateutil_parser
from elasticsearch.helpers import bulk as bulk_index
import olympia.core.logger
from olympia import amo
from olympia.addons.models import Addon
from olympia.amo import search as amo_search
from olympia.amo.celery import task
from olympia.bandwagon.models import Collection
from olympia.ratings.models import Rating
from olympia.users.models import UserProfile
from olympia.versions.models import Version
from . import search
from .models import DownloadCount, ThemeUserCount, UpdateCount
@ -26,140 +12,6 @@ from .models import DownloadCount, ThemeUserCount, UpdateCount
log = olympia.core.logger.getLogger('z.task')
@task
def update_global_totals(job, date, **kw):
log.info('Updating global statistics totals (%s) for (%s)' % (job, date))
if isinstance(date, six.string_types):
# Because of celery serialization, date is not date object, it has been
# transformed into a string, we need the date object back.
date = dateutil_parser(date).date()
jobs = _get_daily_jobs(date)
jobs.update(_get_metrics_jobs(date))
num = jobs[job]()
q = """REPLACE INTO global_stats (`name`, `count`, `date`)
VALUES (%s, %s, %s)"""
p = [job, num or 0, date]
try:
cursor = connection.cursor()
cursor.execute(q, p)
except Exception as e:
log.critical('Failed to update global stats: (%s): %s' % (p, e))
else:
log.debug('Committed global stats details: (%s) has (%s) for (%s)'
% tuple(p))
finally:
cursor.close()
def _get_daily_jobs(date=None):
"""Return a dictionary of statistics queries.
If a date is specified and applies to the job it will be used. Otherwise
the date will default to the previous day.
"""
if not date:
date = datetime.date.today() - datetime.timedelta(days=1)
# Passing through a datetime would not generate an error,
# but would pass and give incorrect values.
if isinstance(date, datetime.datetime):
raise ValueError('This requires a valid date object, not a datetime')
# Testing on lte created date doesn't get you todays date, you need to do
# less than next date. That's because 2012-1-1 becomes 2012-1-1 00:00
next_date = date + datetime.timedelta(days=1)
date_str = date.strftime('%Y-%m-%d')
extra = {'where': ['DATE(created)=%s'], 'params': [date_str]}
# If you're editing these, note that you are returning a function! This
# cheesy hackery was done so that we could pass the queries to celery
# lazily and not hammer the db with a ton of these all at once.
stats = {
# Add-on Downloads
'addon_total_downloads': lambda: DownloadCount.objects.filter(
date__lt=next_date).aggregate(sum=Sum('count'))['sum'],
'addon_downloads_new': lambda: DownloadCount.objects.filter(
date=date).aggregate(sum=Sum('count'))['sum'],
# Listed Add-on counts
'addon_count_new': Addon.objects.valid().extra(**extra).count,
# Listed Version counts
'version_count_new': Version.objects.filter(
channel=amo.RELEASE_CHANNEL_LISTED).extra(**extra).count,
# User counts
'user_count_total': UserProfile.objects.filter(
created__lt=next_date).count,
'user_count_new': UserProfile.objects.extra(**extra).count,
# Rating counts
'review_count_total': Rating.objects.filter(created__lte=date,
editorreview=0).count,
# We can't use "**extra" here, because this query joins on reviews
# itself, and thus raises the following error:
# "Column 'created' in where clause is ambiguous".
'review_count_new': Rating.objects.filter(editorreview=0).extra(
where=['DATE(reviews.created)=%s'], params=[date_str]).count,
# Collection counts
'collection_count_total': Collection.objects.filter(
created__lt=next_date).count,
'collection_count_new': Collection.objects.extra(**extra).count,
}
# If we're processing today's stats, we'll do some extras. We don't do
# these for re-processed stats because they change over time (eg. add-ons
# move from sandbox -> public
if date == (datetime.date.today() - datetime.timedelta(days=1)):
stats.update({
'addon_count_nominated': Addon.objects.filter(
created__lte=date, status=amo.STATUS_NOMINATED,
disabled_by_user=0).count,
'addon_count_public': Addon.objects.filter(
created__lte=date, status=amo.STATUS_PUBLIC,
disabled_by_user=0).count,
'addon_count_pending': Version.objects.filter(
created__lte=date, files__status=amo.STATUS_PENDING).count,
'collection_count_private': Collection.objects.filter(
created__lte=date, listed=0).count,
'collection_count_public': Collection.objects.filter(
created__lte=date, listed=1).count,
'collection_count_editorspicks': Collection.objects.filter(
created__lte=date, type=amo.COLLECTION_FEATURED).count,
'collection_count_normal': Collection.objects.filter(
created__lte=date, type=amo.COLLECTION_NORMAL).count,
})
return stats
def _get_metrics_jobs(date=None):
"""Return a dictionary of statistics queries.
If a date is specified and applies to the job it will be used. Otherwise
the date will default to the last date metrics put something in the db.
"""
if not date:
date = UpdateCount.objects.aggregate(max=Max('date'))['max']
# If you're editing these, note that you are returning a function!
stats = {
'addon_total_updatepings': lambda: UpdateCount.objects.filter(
date=date).aggregate(sum=Sum('count'))['sum'],
}
return stats
@task
def index_update_counts(ids, index=None, **kw):
index = index or search.get_alias()

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

@ -1,2 +0,0 @@
{% extends "stats/stats.html" %}

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

@ -1,5 +0,0 @@
{# L10n: This is an email. Whitespace matters! #}
{{ thankyou_note }}
{% trans %}Learn more about the future of {{ addon_name }} by clicking here:{% endtrans %}
{{ learn_url }}

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

@ -1,3 +0,0 @@
{% extends 'stats/report.html' %}
{% set title = _('Add-on Statistics') %}

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

@ -1,37 +0,0 @@
<nav id="side-nav">
<ul>
{# Set li class="type" from 'metricTypes' in chart.js for active toggle #}
<li class="addons_in_use">
<a href="{{ url('stats.addons_in_use') }}">
{{ _('Add-ons in Use') }}</a>
</li>
<li class="addons_created">
<a href="{{ url('stats.addons_created') }}">
{{ _('Add-ons Created') }}</a>
</li>
<li class="addons_downloaded">
<a href="{{ url('stats.addons_downloaded') }}">
{{ _('Add-ons Downloaded') }}</a>
</li>
<li class="addons_updated">
<a href="{{ url('stats.addons_updated') }}">
{{ _('Add-ons Updated') }}</a>
</li>
<li class="collections_created">
<a href="{{ url('stats.collections_created') }}">
{{ _('Collections Created') }}</a>
</li>
<li class="reviews_created">
<a href="{{ url('stats.reviews_created') }}">
{{ _('Reviews Written') }}</a>
</li>
<li class="users_created">
<a href="{{ url('stats.users_created') }}">
{{ _('User Signups') }}</a>
</li>
<li>
<a href="https://addons-server.readthedocs.io/en/latest/topics/api/stats.html#archiving">
{{ _('Archived Data') }}</a>
</li>
</ul>
</nav>

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

@ -1,3 +0,0 @@
{% extends 'stats/report.html' %}
{% set title = _('Add-ons Created by Date') %}

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

@ -1,3 +0,0 @@
{% extends 'stats/report.html' %}
{% set title = _('Add-ons Downloaded by Date') %}

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

@ -1,3 +0,0 @@
{% extends 'stats/report.html' %}
{% set title = _('Add-ons in Use by Date') %}

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

@ -1,3 +0,0 @@
{% extends 'stats/report.html' %}
{% set title = _('Add-ons Updated by Date') %}

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

@ -1,3 +0,0 @@
{% extends 'stats/report.html' %}
{% set title = _('Collections Created by Date') %}

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

@ -1,3 +0,0 @@
{% extends 'stats/report.html' %}
{% set title = _('Ratings by Date') %}

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

@ -1,3 +0,0 @@
{% extends 'stats/report.html' %}
{% set title = _('Reviews Created by Date') %}

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

@ -1,3 +0,0 @@
{% extends 'stats/report.html' %}
{% set title = _('Subscribers by Date') %}

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

@ -1,3 +0,0 @@
{% extends 'stats/report.html' %}
{% set title = _('User Signups by Date') %}

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

@ -12,27 +12,23 @@ from olympia.bandwagon.models import Collection
@library.global_function
@jinja2.contextfunction
def report_menu(context, request, report, obj=None):
def report_menu(context, request, report, obj):
"""Reports Menu. navigation for the various statistic reports."""
if obj:
if isinstance(obj, Addon):
has_privs = False
if (request.user.is_authenticated and (
acl.action_allowed(request, amo.permissions.STATS_VIEW) or
obj.has_author(request.user))):
has_privs = True
t = loader.get_template('stats/addon_report_menu.html')
c = {
'addon': obj,
'has_privs': has_privs
}
return jinja2.Markup(t.render(c))
if isinstance(obj, Collection):
t = loader.get_template('stats/collection_report_menu.html')
c = {
'collection': obj,
}
return jinja2.Markup(t.render(c))
t = loader.get_template('stats/global_report_menu.html')
return jinja2.Markup(t.render())
if isinstance(obj, Addon):
has_privs = False
if (request.user.is_authenticated and (
acl.action_allowed(request, amo.permissions.STATS_VIEW) or
obj.has_author(request.user))):
has_privs = True
t = loader.get_template('stats/addon_report_menu.html')
c = {
'addon': obj,
'has_privs': has_privs
}
return jinja2.Markup(t.render(c))
if isinstance(obj, Collection):
t = loader.get_template('stats/collection_report_menu.html')
c = {
'collection': obj,
}
return jinja2.Markup(t.render(c))

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

@ -3,92 +3,12 @@ import datetime
from django.core.management import call_command
import mock
from freezegun import freeze_time
from olympia import amo
from olympia.amo.tests import TestCase, addon_factory, version_factory
from olympia.stats import cron, tasks
from olympia.amo.tests import TestCase
from olympia.stats import cron
from olympia.stats.models import (
DownloadCount, GlobalStat, ThemeUserCount, UpdateCount)
class TestGlobalStats(TestCase):
fixtures = ['stats/test_models']
def test_stats_for_date(self):
date = datetime.date(2009, 6, 1)
job = 'addon_total_downloads'
assert GlobalStat.objects.filter(
date=date, name=job).count() == 0
tasks.update_global_totals(job, date)
assert len(GlobalStat.objects.filter(
date=date, name=job)) == 1
def test_count_stats_for_date(self):
# Add a listed add-on, it should show up in "addon_count_new".
listed_addon = addon_factory(created=datetime.datetime.now())
# Add an unlisted version to that add-on, it should *not* increase the
# "version_count_new" count.
version_factory(
addon=listed_addon, channel=amo.RELEASE_CHANNEL_UNLISTED)
# Add an unlisted add-on, it should not show up in either
# "addon_count_new" or "version_count_new".
addon_factory(version_kw={
'channel': amo.RELEASE_CHANNEL_UNLISTED
})
date = datetime.date.today()
job = 'addon_count_new'
tasks.update_global_totals(job, date)
global_stat = GlobalStat.objects.get(date=date, name=job)
assert global_stat.count == 1
# Should still work if the date is passed as a datetime string (what
# celery serialization does).
job = 'version_count_new'
tasks.update_global_totals(job, datetime.datetime.now().isoformat())
global_stat = GlobalStat.objects.get(date=date, name=job)
assert global_stat.count == 1
def test_through_cron(self):
# Yesterday, create some stuff.
with freeze_time(datetime.datetime.now() - datetime.timedelta(days=1)):
yesterday = datetime.date.today()
# Add a listed add-on, it should show up in "addon_count_new".
listed_addon = addon_factory(created=datetime.datetime.now())
# Add an unlisted version to that add-on, it should *not* increase
# the "version_count_new" count.
version_factory(
addon=listed_addon, channel=amo.RELEASE_CHANNEL_UNLISTED)
# Add an unlisted add-on, it should not show up in either
# "addon_count_new" or "version_count_new".
addon_factory(version_kw={
'channel': amo.RELEASE_CHANNEL_UNLISTED
})
# Launch the cron.
cron.update_global_totals()
job = 'addon_count_new'
global_stat = GlobalStat.objects.get(date=yesterday, name=job)
assert global_stat.count == 1
job = 'version_count_new'
global_stat = GlobalStat.objects.get(date=yesterday, name=job)
assert global_stat.count == 1
def test_input(self):
for x in ['2009-1-1',
datetime.datetime(2009, 1, 1),
datetime.datetime(2009, 1, 1, 11, 0)]:
with self.assertRaises((TypeError, ValueError)):
tasks._get_daily_jobs(x)
DownloadCount, ThemeUserCount, UpdateCount)
@mock.patch('olympia.stats.management.commands.index_stats.group')

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

@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
import csv
import datetime
import json
from django.http import Http404
from django.test.client import RequestFactory
from django.utils.encoding import force_text
import mock
import six
from pyquery import PyQuery as pq
@ -18,8 +16,7 @@ from olympia.addons.models import Addon, AddonUser
from olympia.amo.tests import TestCase, version_factory
from olympia.amo.urlresolvers import reverse
from olympia.stats import tasks, views
from olympia.stats.models import (
DownloadCount, GlobalStat, ThemeUserCount, UpdateCount)
from olympia.stats.models import DownloadCount, ThemeUserCount, UpdateCount
from olympia.users.models import UserProfile
@ -720,82 +717,6 @@ class TestResponses(ESStatsTest):
2009-06-01,10,2,3""")
# Test the SQL query by using known dates, for weeks and months etc.
class TestSiteQuery(TestCase):
def setUp(self):
super(TestSiteQuery, self).setUp()
self.start = datetime.date(2012, 1, 1)
self.end = datetime.date(2012, 1, 31)
for k in range(0, 15):
for name in ['addon_count_new', 'version_count_new']:
date_ = self.start + datetime.timedelta(days=k)
GlobalStat.objects.create(date=date_, name=name, count=k)
def test_day_grouping(self):
res = views._site_query('date', self.start, self.end)[0]
assert len(res) == 14
assert res[0]['data']['addons_created'] == 14
# Make sure we are returning counts as integers, otherwise
# DjangoJSONSerializer will map them to strings.
assert type(res[0]['data']['addons_created']) == int
assert res[0]['date'] == '2012-01-15'
def test_week_grouping(self):
res = views._site_query('week', self.start, self.end)[0]
assert len(res) == 3
assert res[1]['data']['addons_created'] == 70
assert res[1]['date'] == '2012-01-08'
def test_month_grouping(self):
res = views._site_query('month', self.start, self.end)[0]
assert len(res) == 1
assert res[0]['data']['addons_created'] == (14 * (14 + 1)) / 2
assert res[0]['date'] == '2012-01-02'
def test_period(self):
self.assertRaises(AssertionError, views._site_query, 'not_period',
self.start, self.end)
@mock.patch('olympia.stats.views._site_query')
class TestSite(TestCase):
def tests_period(self, _site_query):
_site_query.return_value = ['.', '.']
for period in ['date', 'week', 'month']:
self.client.get(reverse('stats.site', args=['json', period]))
assert _site_query.call_args[0][0] == period
def tests_period_day(self, _site_query):
_site_query.return_value = ['.', '.']
start = (datetime.date.today() - datetime.timedelta(days=3))
end = datetime.date.today()
self.client.get(reverse('stats.site.new',
args=['day', start.strftime('%Y%m%d'),
end.strftime('%Y%m%d'), 'json']))
assert _site_query.call_args[0][0] == 'date'
assert _site_query.call_args[0][1] == start
assert _site_query.call_args[0][2] == end
def test_csv(self, _site_query):
_site_query.return_value = [[], []]
res = self.client.get(reverse('stats.site', args=['csv', 'date']))
assert res._headers['content-type'][1].startswith('text/csv')
def test_json(self, _site_query):
_site_query.return_value = [[], []]
res = self.client.get(reverse('stats.site', args=['json', 'date']))
assert res._headers['content-type'][1].startswith('application/json')
def tests_no_date(self, _site_query):
_site_query.return_value = ['.', '.']
self.client.get(reverse('stats.site', args=['json', 'date']))
assert _site_query.call_args[0][1] == (
datetime.date.today() - datetime.timedelta(days=365))
assert _site_query.call_args[0][2] == datetime.date.today()
class TestXss(amo.tests.TestXss):
def test_stats_page(self):

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

@ -1,5 +1,4 @@
from django.conf.urls import url
from django.shortcuts import redirect
from . import views
@ -10,31 +9,6 @@ range_re = r'(?P<start>\d{8})-(?P<end>\d{8})'
format_re = r'(?P<format>' + '|'.join(views.SERIES_FORMATS) + ')'
series_re = r'%s-%s\.%s$' % (group_re, range_re, format_re)
series = dict((type, r'%s-%s' % (type, series_re)) for type in views.SERIES)
global_series = dict((type, r'%s-%s' % (type, series_re))
for type in views.GLOBAL_SERIES)
urlpatterns = [
url(r'^$', lambda r: redirect('stats.addons_in_use', permanent=False),
name='stats.dashboard'),
url(r'^site%s/%s$' % (format_re, group_date_re),
views.site, name='stats.site'),
url(r'^site-%s' % series_re, views.site, name='stats.site.new'),
]
# These are the front end pages, so that when you click the links on the
# navigation page, you end up on the correct stats page for AMO.
keys = ['addons_in_use', 'addons_updated', 'addons_downloaded',
'addons_created', 'collections_created',
'reviews_created', 'users_created']
for key in keys:
urlpatterns.append(url(
r'^%s/$' % key, views.site_stats_report,
name='stats.%s' % key, kwargs={'report': key}))
urlpatterns.append(url(
global_series[key], views.site_series,
kwargs={'field': key}))
# Addon specific stats.

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

@ -2,14 +2,11 @@ import csv
import json
import time
from collections import OrderedDict
from datetime import date, timedelta
from datetime import timedelta
from django import http
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.core.files.storage import get_storage_class
from django.db import connection
from django.db.transaction import non_atomic_requests
from django.utils.cache import add_never_cache_headers, patch_cache_control
from django.utils.encoding import force_text
@ -27,7 +24,6 @@ from olympia.amo.decorators import allow_cross_site_request
from olympia.amo.urlresolvers import reverse
from olympia.amo.utils import AMOJSONEncoder, render
from olympia.core.languages import ALL_LANGUAGES
from olympia.lib.cache import memoize
from olympia.stats.decorators import addon_view_stats
from olympia.stats.forms import DateForm
@ -42,23 +38,11 @@ SERIES_GROUPS_DATE = ('date', 'week', 'month') # Backwards compat.
SERIES_FORMATS = ('json', 'csv')
SERIES = ('downloads', 'usage', 'overview', 'sources', 'os',
'locales', 'statuses', 'versions', 'apps')
GLOBAL_SERIES = ('addons_in_use', 'addons_updated', 'addons_downloaded',
'collections_created', 'reviews_created', 'addons_created',
'users_created', 'my_apps')
storage = get_storage_class()()
@non_atomic_requests
def dashboard(request):
stats_base_url = reverse('stats.dashboard')
view = get_report_view(request)
return render(request, 'stats/dashboard.html',
{'report': 'site', 'view': view,
'stats_base_url': stats_base_url})
def get_series(model, extra_field=None, source=None, **filters):
"""
Get a generator of dicts for the stats model given by the filters.
@ -341,15 +325,6 @@ def stats_report(request, addon, report):
'stats_base_url': stats_base_url})
@non_atomic_requests
def site_stats_report(request, report):
stats_base_url = reverse('stats.dashboard')
view = get_report_view(request)
return render(request, 'stats/reports/%s.html' % report,
{'report': report, 'view': view,
'stats_base_url': stats_base_url})
def get_report_view(request):
"""Parse and validate a pair of YYYMMDD date strings."""
dates = DateForm(data=request.GET)
@ -387,101 +362,6 @@ def get_daterange_or_404(start, end):
)
def daterange(start_date, end_date):
for n in range((end_date - start_date).days):
yield start_date + timedelta(n)
# Cached lookup of the keys and the SQL.
# Taken from remora, a mapping of the old values.
_KEYS = {
'addon_downloads_new': 'addons_downloaded',
'addon_total_updatepings': 'addons_in_use',
'addon_count_new': 'addons_created',
'version_count_new': 'addons_updated',
'user_count_new': 'users_created',
'review_count_new': 'reviews_created',
'collection_count_new': 'collections_created',
}
_ALL_KEYS = list(_KEYS)
_CACHED_KEYS = sorted(_KEYS.values())
@memoize(prefix='global_stats', timeout=60 * 60)
def _site_query(period, start, end, field=None, request=None):
with connection.cursor() as cursor:
# Let MySQL make this fast. Make sure we prevent SQL injection with the
# assert.
if period not in SERIES_GROUPS_DATE:
raise AssertionError('%s period is not valid.' % period)
sql = ("SELECT name, MIN(date), SUM(count) "
"FROM global_stats "
"WHERE date > %%s AND date <= %%s "
"AND name IN (%s) "
"GROUP BY %s(date), name "
"ORDER BY %s(date) DESC;"
% (', '.join(['%s' for key in _ALL_KEYS]), period, period))
cursor.execute(sql, [start, end] + _ALL_KEYS)
# Process the results into a format that is friendly for render_*.
default = {k: 0 for k in _CACHED_KEYS}
result = OrderedDict()
for name, date_, count in cursor.fetchall():
date_ = date_.strftime('%Y-%m-%d')
if date_ not in result:
result[date_] = default.copy()
result[date_]['date'] = date_
result[date_]['data'] = {}
result[date_]['data'][_KEYS[name]] = int(count)
return list(result.values()), _CACHED_KEYS
@non_atomic_requests
def site(request, format, group, start=None, end=None):
"""Site data from the global_stats table."""
if not start and not end:
start = (date.today() - timedelta(days=365)).strftime('%Y%m%d')
end = date.today().strftime('%Y%m%d')
group = 'date' if group == 'day' else group
start, end = get_daterange_or_404(start, end)
series, keys = _site_query(group, start, end, request)
if format == 'csv':
return render_csv(request, None, series, ['date'] + keys,
title='addons.mozilla.org week Site Statistics',
show_disclaimer=True)
return render_json(request, None, series)
@non_atomic_requests
def site_series(request, format, group, start, end, field):
"""Pull a single field from the site_query data"""
start, end = get_daterange_or_404(start, end)
group = 'date' if group == 'day' else group
series = []
full_series, keys = _site_query(group, start, end, field, request)
for row in full_series:
if field in row['data']:
series.append({
'date': row['date'],
'count': row['data'][field],
'data': {},
})
# TODO: (dspasovski) check whether this is the CSV data we really want
if format == 'csv':
series, fields = csv_fields(series)
return render_csv(request, None, series,
['date', 'count'] + list(fields),
title='%s week Site Statistics' % settings.DOMAIN,
show_disclaimer=True)
return render_json(request, None, series)
def fudge_headers(response, stats):
"""Alter cache headers. Don't cache content where data could be missing."""
if not stats:

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

@ -84,10 +84,7 @@ urlpatterns = [
# API v3+.
url(r'^api/', include('olympia.api.urls')),
# Site statistics that we are going to catch, the rest will fall through.
url(r'^statistics/', include('olympia.stats.urls')),
# Fall through for any URLs not matched above stats dashboard.
# Redirect for all global stats URLs.
url(r'^statistics/', lambda r: redirect('/'), name='statistics.dashboard'),
# Redirect patterns.