Removed all traces of Monolith (bug 974854)
This commit is contained in:
Родитель
4c595839d6
Коммит
6306be44eb
|
@ -63,19 +63,6 @@ def update_global_totals(date=None):
|
|||
TaskSet(ts).apply_async()
|
||||
|
||||
|
||||
@cronjobs.register
|
||||
def update_monolith_stats(date=None):
|
||||
"""Update monolith statistics."""
|
||||
if date:
|
||||
date = datetime.datetime.strptime(date, '%Y-%m-%d').date()
|
||||
today = date or datetime.date.today()
|
||||
jobs = [{'metric': metric,
|
||||
'date': today} for metric in tasks._get_monolith_jobs(date)]
|
||||
|
||||
ts = [tasks.update_monolith_stats.subtask(kwargs=kw) for kw in jobs]
|
||||
TaskSet(ts).apply_async()
|
||||
|
||||
|
||||
@cronjobs.register
|
||||
def update_google_analytics(date=None):
|
||||
"""
|
||||
|
|
|
@ -7,14 +7,11 @@ import mock
|
|||
from nose.tools import eq_
|
||||
|
||||
import amo.tests
|
||||
from addons.models import Addon, AddonUser
|
||||
from addons.models import Addon
|
||||
from bandwagon.models import Collection, CollectionAddon
|
||||
from mkt.constants.regions import REGIONS_CHOICES_SLUG
|
||||
from reviews.models import Review
|
||||
from stats import cron, tasks
|
||||
from stats.models import (AddonCollectionCount, Contribution, DownloadCount,
|
||||
GlobalStat, ThemeUserCount, UpdateCount)
|
||||
from users.models import UserProfile
|
||||
|
||||
|
||||
class TestGlobalStats(amo.tests.TestCase):
|
||||
|
@ -195,142 +192,3 @@ class TestUpdateDownloads(amo.tests.TestCase):
|
|||
eq_(CollectionAddon.objects.get(addon_id=3615,
|
||||
collection_id=80).downloads,
|
||||
15)
|
||||
|
||||
|
||||
class TestMonolithStats(amo.tests.TestCase):
|
||||
|
||||
@mock.patch('stats.tasks.MonolithRecord')
|
||||
def test_mmo_user_total_count_updates_monolith(self, record):
|
||||
UserProfile.objects.create(source=amo.LOGIN_SOURCE_MMO_BROWSERID)
|
||||
metric = 'mmo_user_count_total'
|
||||
|
||||
tasks.update_monolith_stats(metric, datetime.date.today())
|
||||
self.assertTrue(record.objects.create.called)
|
||||
eq_(record.objects.create.call_args[1]['value'], '{"count": 1}')
|
||||
|
||||
def test_app_new(self):
|
||||
Addon.objects.create(type=amo.ADDON_WEBAPP)
|
||||
eq_(tasks._get_monolith_jobs()['apps_count_new'][0]['count'](), 1)
|
||||
|
||||
def test_app_added_counts(self):
|
||||
today = datetime.date(2013, 1, 25)
|
||||
app = Addon.objects.create(type=amo.ADDON_WEBAPP)
|
||||
app.update(created=today)
|
||||
|
||||
package_type = 'packaged' if app.is_packaged else 'hosted'
|
||||
premium_type = amo.ADDON_PREMIUM_API[app.premium_type]
|
||||
|
||||
# Add a region exclusion.
|
||||
regions = dict(REGIONS_CHOICES_SLUG)
|
||||
excluded_region = regions['br']
|
||||
app.addonexcludedregion.create(region=excluded_region.id)
|
||||
|
||||
jobs = tasks._get_monolith_jobs(today)
|
||||
|
||||
# Check package type counts.
|
||||
for job in jobs['apps_added_by_package_type']:
|
||||
r = job['dimensions']['region']
|
||||
p = job['dimensions']['package_type']
|
||||
if r != excluded_region.slug and p == package_type:
|
||||
expected_count = 1
|
||||
else:
|
||||
expected_count = 0
|
||||
count = job['count']()
|
||||
eq_(count, expected_count,
|
||||
'Incorrect count for region %s, package type %s. '
|
||||
'Got %d, expected %d.' % (r, p, count, expected_count))
|
||||
|
||||
# Check premium type counts.
|
||||
for job in jobs['apps_added_by_premium_type']:
|
||||
r = job['dimensions']['region']
|
||||
p = job['dimensions']['premium_type']
|
||||
if r != excluded_region.slug and p == premium_type:
|
||||
expected_count = 1
|
||||
else:
|
||||
expected_count = 0
|
||||
count = job['count']()
|
||||
eq_(count, expected_count,
|
||||
'Incorrect count for region %s, premium type %s. '
|
||||
'Got %d, expected %d.' % (r, p, count, expected_count))
|
||||
|
||||
def test_app_avail_counts(self):
|
||||
today = datetime.date(2013, 1, 25)
|
||||
app = Addon.objects.create(type=amo.ADDON_WEBAPP,
|
||||
status=amo.STATUS_PUBLIC)
|
||||
# Create a couple more to test the counts.
|
||||
Addon.objects.create(type=amo.ADDON_WEBAPP, status=amo.STATUS_PENDING)
|
||||
Addon.objects.create(type=amo.ADDON_WEBAPP, status=amo.STATUS_PUBLIC,
|
||||
disabled_by_user=True)
|
||||
|
||||
package_type = 'packaged' if app.is_packaged else 'hosted'
|
||||
premium_type = amo.ADDON_PREMIUM_API[app.premium_type]
|
||||
|
||||
# Add a region exclusion.
|
||||
regions = dict(REGIONS_CHOICES_SLUG)
|
||||
excluded_region = regions['br']
|
||||
app.addonexcludedregion.create(region=excluded_region.id)
|
||||
|
||||
jobs = tasks._get_monolith_jobs(today)
|
||||
|
||||
# Check package type counts.
|
||||
for job in jobs['apps_available_by_package_type']:
|
||||
r = job['dimensions']['region']
|
||||
p = job['dimensions']['package_type']
|
||||
if r != excluded_region.slug and p == package_type:
|
||||
expected_count = 1
|
||||
else:
|
||||
expected_count = 0
|
||||
count = job['count']()
|
||||
eq_(count, expected_count,
|
||||
'Incorrect count for region %s, package type %s. '
|
||||
'Got %d, expected %d.' % (r, p, count, expected_count))
|
||||
|
||||
# Check premium type counts.
|
||||
for job in jobs['apps_available_by_premium_type']:
|
||||
r = job['dimensions']['region']
|
||||
p = job['dimensions']['premium_type']
|
||||
if r != excluded_region.slug and p == premium_type:
|
||||
expected_count = 1
|
||||
else:
|
||||
expected_count = 0
|
||||
count = job['count']()
|
||||
eq_(count, expected_count,
|
||||
'Incorrect count for region %s, premium type %s. '
|
||||
'Got %d, expected %d.' % (r, p, count, expected_count))
|
||||
|
||||
def test_app_reviews(self):
|
||||
addon = Addon.objects.create(type=amo.ADDON_WEBAPP)
|
||||
user = UserProfile.objects.create(username='foo')
|
||||
Review.objects.create(addon=addon, user=user)
|
||||
eq_(tasks._get_monolith_jobs()['apps_review_count_new'][0]['count'](),
|
||||
1)
|
||||
|
||||
def test_user_total(self):
|
||||
day = datetime.date(2009, 1, 1)
|
||||
p = UserProfile.objects.create(username='foo',
|
||||
source=amo.LOGIN_SOURCE_MMO_BROWSERID)
|
||||
p.update(created=day)
|
||||
eq_(tasks._get_monolith_jobs(day)['mmo_user_count_total'][0]['count'](),
|
||||
1)
|
||||
eq_(tasks._get_monolith_jobs()['mmo_user_count_total'][0]['count'](),
|
||||
1)
|
||||
eq_(tasks._get_monolith_jobs()['mmo_user_count_new'][0]['count'](), 0)
|
||||
|
||||
def test_user_new(self):
|
||||
UserProfile.objects.create(username='foo',
|
||||
source=amo.LOGIN_SOURCE_MMO_BROWSERID)
|
||||
eq_(tasks._get_monolith_jobs()['mmo_user_count_new'][0]['count'](), 1)
|
||||
|
||||
def test_dev_total(self):
|
||||
p1 = UserProfile.objects.create(username='foo',
|
||||
source=amo.LOGIN_SOURCE_MMO_BROWSERID)
|
||||
p2 = UserProfile.objects.create(username='bar',
|
||||
source=amo.LOGIN_SOURCE_MMO_BROWSERID)
|
||||
a1 = amo.tests.addon_factory()
|
||||
a2 = amo.tests.app_factory()
|
||||
AddonUser.objects.create(addon=a1, user=p1)
|
||||
AddonUser.objects.create(addon=a1, user=p2)
|
||||
AddonUser.objects.create(addon=a2, user=p1)
|
||||
|
||||
eq_(tasks._get_monolith_jobs()['mmo_developer_count_total'][0]['count'](),
|
||||
1)
|
||||
|
|
|
@ -3,16 +3,16 @@ from functools import partial
|
|||
|
||||
from django import http
|
||||
from django.conf import settings
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.shortcuts import (get_list_or_404, get_object_or_404,
|
||||
redirect, render)
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth.forms import PasswordResetForm
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.shortcuts import (get_list_or_404, get_object_or_404, redirect,
|
||||
render)
|
||||
from django.template import Context, loader
|
||||
from django.utils.http import base36_to_int, is_safe_url
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.utils.http import base36_to_int, is_safe_url
|
||||
|
||||
import commonware.log
|
||||
import waffle
|
||||
|
@ -25,6 +25,7 @@ from tower import ugettext as _
|
|||
import amo
|
||||
import users.notifications as notifications
|
||||
from abuse.models import send_abuse_report
|
||||
from access import acl
|
||||
from access.middleware import ACLMiddleware
|
||||
from addons.decorators import addon_view_factory
|
||||
from addons.models import Addon, Category
|
||||
|
@ -35,19 +36,17 @@ from amo.forms import AbuseForm
|
|||
from amo.helpers import loc
|
||||
from amo.urlresolvers import get_url_prefix, reverse
|
||||
from amo.utils import escape_all, log_cef, send_mail
|
||||
from access import acl
|
||||
from bandwagon.models import Collection
|
||||
from browse.views import PersonasFilter
|
||||
from translations.query import order_by_translation
|
||||
from users.models import UserNotification
|
||||
|
||||
from lib.metrics import record_action
|
||||
|
||||
import tasks
|
||||
from . import forms
|
||||
from .models import UserProfile
|
||||
from .signals import logged_out
|
||||
from . import forms
|
||||
from .utils import autocreate_username, EmailResetCode, UnsubscribeCode
|
||||
import tasks
|
||||
|
||||
|
||||
log = commonware.log.getLogger('z.users')
|
||||
|
||||
|
@ -380,8 +379,6 @@ def browserid_authenticate(request, assertion, is_mobile=False,
|
|||
log_cef('New Account', 5, request, username=username,
|
||||
signature='AUTHNOTICE',
|
||||
msg='User created a new account (from Persona)')
|
||||
if settings.MARKETPLACE:
|
||||
record_action('new-user', request)
|
||||
return profile, None
|
||||
|
||||
|
||||
|
|
|
@ -443,11 +443,6 @@ class TestVersion(amo.tests.TestCase):
|
|||
amo.tests.version_factory(addon=addon)
|
||||
assert inv_mock.called
|
||||
|
||||
def test_app_feature_creation_app(self):
|
||||
app = Addon.objects.create(type=amo.ADDON_WEBAPP)
|
||||
ver = Version.objects.create(addon=app)
|
||||
assert ver.features, 'AppFeatures was not created with version.'
|
||||
|
||||
|
||||
class TestViews(amo.tests.TestCase):
|
||||
fixtures = ['addons/eula+contrib-addon', 'base/apps']
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import threading
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
import commonware.log
|
||||
|
||||
from mkt.monolith import record_stat
|
||||
|
||||
|
||||
log = commonware.log.getLogger('z.metrics')
|
||||
|
||||
|
||||
def record_action(action, request, data=None):
|
||||
"""Records the given action by sending it to the metrics servers.
|
||||
|
||||
Currently this is storing the data internally in the monolith temporary
|
||||
table.
|
||||
|
||||
:param action: the action related to this request.
|
||||
:param request: the request that triggered this call.
|
||||
:param data: some optional additional data about this call.
|
||||
|
||||
"""
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
data['user-agent'] = request.META.get('HTTP_USER_AGENT')
|
||||
data['locale'] = request.LANG
|
||||
data['src'] = request.GET.get('src', '')
|
||||
record_stat(action, request, **data)
|
||||
|
||||
|
||||
def get_monolith_client():
|
||||
_locals = threading.local()
|
||||
if not hasattr(_locals, 'monolith'):
|
||||
server = getattr(settings, 'MONOLITH_SERVER', None)
|
||||
index = getattr(settings, 'MONOLITH_INDEX', 'time_*')
|
||||
if server is None:
|
||||
raise ValueError('You need to configure MONOLITH_SERVER')
|
||||
|
||||
statsd = {'statsd.host': getattr(settings, 'STATSD_HOST', 'localhost'),
|
||||
'statsd.port': getattr(settings, 'STATSD_PORT', 8125)}
|
||||
|
||||
from monolith.client import Client as MonolithClient
|
||||
_locals.monolith = MonolithClient(server, index, **statsd)
|
||||
|
||||
return _locals.monolith
|
|
@ -1,18 +0,0 @@
|
|||
# -*- coding: utf8 -*-
|
||||
import mock
|
||||
|
||||
import amo.tests
|
||||
from lib.metrics import record_action
|
||||
|
||||
|
||||
class TestMetrics(amo.tests.TestCase):
|
||||
|
||||
@mock.patch('lib.metrics.record_stat')
|
||||
def test_record_action(self, record_stat):
|
||||
request = mock.Mock()
|
||||
request.GET = {'src': 'foo'}
|
||||
request.LANG = 'en'
|
||||
request.META = {'HTTP_USER_AGENT': 'py'}
|
||||
record_action('install', request, {})
|
||||
record_stat.assert_called_with('install', request,
|
||||
**{'locale': 'en', 'src': 'foo', 'user-agent': 'py'})
|
|
@ -1062,8 +1062,7 @@ CELERY_RESULT_BACKEND = 'amqp'
|
|||
CELERY_IGNORE_RESULT = True
|
||||
CELERY_SEND_TASK_ERROR_EMAILS = True
|
||||
CELERYD_HIJACK_ROOT_LOGGER = False
|
||||
CELERY_IMPORTS = ('lib.video.tasks', 'lib.metrics',
|
||||
'lib.es.management.commands.reindex')
|
||||
CELERY_IMPORTS = ('lib.video.tasks', 'lib.es.management.commands.reindex')
|
||||
|
||||
# We have separate celeryds for processing devhub & images as fast as possible
|
||||
# Some notes:
|
||||
|
@ -1079,8 +1078,6 @@ CELERY_ROUTES = {
|
|||
'addons.tasks.save_theme_reupload': {'queue': 'priority'},
|
||||
'bandwagon.tasks.index_collections': {'queue': 'priority'},
|
||||
'bandwagon.tasks.unindex_collections': {'queue': 'priority'},
|
||||
'lib.crypto.packaged.sign': {'queue': 'priority'},
|
||||
'stats.tasks.update_monolith_stats': {'queue': 'priority'},
|
||||
'users.tasks.index_users': {'queue': 'priority'},
|
||||
'users.tasks.unindex_users': {'queue': 'priority'},
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
from mkt.monolith.models import record_stat # NOQA
|
|
@ -1,9 +0,0 @@
|
|||
from django import forms
|
||||
|
||||
import happyforms
|
||||
|
||||
|
||||
class MonolithForm(happyforms.Form):
|
||||
key = forms.CharField(required=False)
|
||||
start = forms.DateField(required=False)
|
||||
end = forms.DateField(required=False)
|
|
@ -1,22 +0,0 @@
|
|||
from optparse import make_option
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
from mkt.site.management.commands.add_test_users import create_user
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = """Create an user with access to the monolith API"""
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option(
|
||||
'--overwrite', action='store_true',
|
||||
dest='overwrite', default=False,
|
||||
help='Overwrite the user access token if it already exists'),)
|
||||
|
||||
def handle(self, *args, **kw):
|
||||
create_user('monolith@mozilla.com',
|
||||
overwrite=kw['overwrite'],
|
||||
password=settings.MONOLITH_PASSWORD,
|
||||
group_name='Monolith API')
|
|
@ -1,61 +0,0 @@
|
|||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class MonolithRecord(models.Model):
|
||||
"""Data stored temporarily for monolith.
|
||||
|
||||
It contains a key (e.g. "app.install"), the date of the record, a user (a
|
||||
string representing a unique user) and a value (which internally is stored
|
||||
as a JSON object).
|
||||
"""
|
||||
key = models.CharField(max_length=255)
|
||||
recorded = models.DateTimeField()
|
||||
user_hash = models.CharField(max_length=255, blank=True)
|
||||
value = models.TextField()
|
||||
|
||||
class Meta:
|
||||
db_table = 'monolith_record'
|
||||
|
||||
|
||||
def get_user_hash(request):
|
||||
"""Get a hash identifying an user.
|
||||
|
||||
It's a hash of session key, ip and user agent
|
||||
"""
|
||||
ip = request.META.get('REMOTE_ADDR', '')
|
||||
ua = request.META.get('User-Agent', '')
|
||||
session_key = request.session.session_key or ''
|
||||
|
||||
return hashlib.sha1('-'.join(map(str, (ip, ua, session_key)))).hexdigest()
|
||||
|
||||
|
||||
def record_stat(key, request, **data):
|
||||
"""Create a new record in the database with the given values.
|
||||
|
||||
:param key:
|
||||
The type of stats you're sending, e.g. "app.install".
|
||||
|
||||
:param request:
|
||||
The request associated with this call. It will be used to define who
|
||||
the user is.
|
||||
|
||||
:para: data:
|
||||
The data you want to store. You can pass the data to this function as
|
||||
named arguments.
|
||||
"""
|
||||
if '__recorded' in data:
|
||||
recorded = data.pop('__recorded')
|
||||
else:
|
||||
recorded = datetime.datetime.utcnow()
|
||||
|
||||
if not data:
|
||||
raise ValueError('You should at least define one value')
|
||||
|
||||
record = MonolithRecord(key=key, user_hash=get_user_hash(request),
|
||||
recorded=recorded, value=json.dumps(data))
|
||||
record.save()
|
||||
return record
|
|
@ -1,147 +0,0 @@
|
|||
import datetime
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.db.models import Avg, Count
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework.generics import ListAPIView
|
||||
from rest_framework.response import Response
|
||||
|
||||
import amo
|
||||
from abuse.models import AbuseReport
|
||||
from reviews.models import Review
|
||||
|
||||
from mkt.api.authentication import RestOAuthAuthentication
|
||||
from mkt.api.authorization import GroupPermission
|
||||
from mkt.api.base import CORSMixin, MarketplaceView
|
||||
|
||||
from .forms import MonolithForm
|
||||
from .models import MonolithRecord
|
||||
|
||||
|
||||
log = logging.getLogger('z.monolith')
|
||||
|
||||
|
||||
# TODO: Move the stats that can be calculated on the fly from
|
||||
# apps/stats/tasks.py here.
|
||||
STATS = {
|
||||
'apps_ratings': {
|
||||
'qs': Review.objects
|
||||
.filter(editorreview=0, addon__type=amo.ADDON_WEBAPP)
|
||||
.values('addon')
|
||||
.annotate(count=Count('addon')),
|
||||
'type': 'slice',
|
||||
'field_map': {
|
||||
'count': 'count',
|
||||
'app-id': 'addon'},
|
||||
},
|
||||
'apps_average_rating': {
|
||||
'qs': Review.objects
|
||||
.filter(editorreview=0, addon__type=amo.ADDON_WEBAPP)
|
||||
.values('addon')
|
||||
.annotate(avg=Avg('rating')),
|
||||
'type': 'total',
|
||||
'field_map': {
|
||||
'count': 'avg',
|
||||
'app-id': 'addon'},
|
||||
},
|
||||
'apps_abuse_reports': {
|
||||
'qs': AbuseReport.objects
|
||||
.filter(addon__type=amo.ADDON_WEBAPP)
|
||||
.values('addon')
|
||||
.annotate(count=Count('addon')),
|
||||
'type': 'slice',
|
||||
'field_map': {
|
||||
'count': 'count',
|
||||
'app-id': 'addon'},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def daterange(start, end):
|
||||
for n in range((end - start).days):
|
||||
yield start + datetime.timedelta(n)
|
||||
|
||||
|
||||
class MonolithSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = MonolithRecord
|
||||
fields = ('key', 'recorded', 'user_hash', 'value')
|
||||
|
||||
def transform_value(self, obj, value):
|
||||
if not isinstance(value, basestring):
|
||||
return value
|
||||
return json.loads(value)
|
||||
|
||||
|
||||
def _get_query_result(key, start, end):
|
||||
# To do on-the-fly queries we have to produce results as if they
|
||||
# were calculated daily, which means we need to iterate over each
|
||||
# day in the range and perform an aggregation on this date.
|
||||
|
||||
data = []
|
||||
today = datetime.date.today()
|
||||
stat = STATS[key]
|
||||
|
||||
# Choose start and end dates that make sense if none provided.
|
||||
if not start:
|
||||
raise ParseError('`start` was not provided')
|
||||
if not end:
|
||||
end = today
|
||||
|
||||
for day in daterange(start, end):
|
||||
if stat['type'] == 'total':
|
||||
# If it's a totalling queryset, we want to filter by the
|
||||
# end date until the beginning of time to get the total
|
||||
# objects up until this point in time.
|
||||
date_filtered = stat['qs'].filter(
|
||||
created__lt=(day + datetime.timedelta(days=1)))
|
||||
else:
|
||||
# Otherwise, we want to filter by both start/end to get
|
||||
# counts on a specific day.
|
||||
date_filtered = stat['qs'].filter(
|
||||
created__gte=day,
|
||||
created__lt=(day + datetime.timedelta(days=1)))
|
||||
|
||||
data.extend([{
|
||||
'key': key,
|
||||
'recorded': day,
|
||||
'user_hash': None,
|
||||
'value': {'count': d.get(stat['field_map']['count']),
|
||||
'app-id': d.get(stat['field_map']['app-id'])}}
|
||||
for d in date_filtered])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class MonolithView(CORSMixin, MarketplaceView, ListAPIView):
|
||||
cors_allowed_methods = ['get']
|
||||
permission_classes = [GroupPermission('Monolith', 'API')]
|
||||
authentication_classes = [RestOAuthAuthentication]
|
||||
serializer_class = MonolithSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
form = MonolithForm(self.request.QUERY_PARAMS)
|
||||
if not form.is_valid():
|
||||
return Response(form.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
key = form.cleaned_data['key']
|
||||
start = form.cleaned_data['start']
|
||||
end = form.cleaned_data['end']
|
||||
|
||||
log.info('[Monolith] Querying key:%s [%s:%s]' % (key, start, end))
|
||||
|
||||
if key in STATS:
|
||||
return _get_query_result(key, start, end)
|
||||
|
||||
else:
|
||||
qs = MonolithRecord.objects.all()
|
||||
if key:
|
||||
qs = qs.filter(key=key)
|
||||
if start is not None:
|
||||
qs = qs.filter(recorded__gte=start)
|
||||
if end is not None:
|
||||
qs = qs.filter(recorded__lt=end)
|
||||
|
||||
return qs
|
|
@ -1,228 +0,0 @@
|
|||
import datetime
|
||||
import json
|
||||
import uuid
|
||||
from collections import namedtuple
|
||||
|
||||
import mock
|
||||
from nose.tools import eq_, ok_
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import client
|
||||
|
||||
from amo.tests import TestCase
|
||||
from mkt.api.tests.test_oauth import RestOAuth
|
||||
from mkt.site.fixtures import fixture
|
||||
|
||||
from .models import MonolithRecord, record_stat
|
||||
from .resources import daterange
|
||||
|
||||
|
||||
class RequestFactory(client.RequestFactory):
|
||||
|
||||
def __init__(self, session_key=None, user_agent=None,
|
||||
remote_addr=None, anonymous=True, *args, **kwargs):
|
||||
|
||||
class User(object):
|
||||
def is_anonymous(self):
|
||||
return anonymous
|
||||
|
||||
Session = namedtuple('Session', 'session_key')
|
||||
self.session = Session(session_key or str(uuid.uuid1()))
|
||||
self.user = User()
|
||||
self.META = {}
|
||||
if remote_addr:
|
||||
self.META['REMOTE_ADDR'] = remote_addr
|
||||
if user_agent:
|
||||
self.META['User-Agent'] = user_agent
|
||||
|
||||
super(RequestFactory, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def total_seconds(td):
|
||||
# not present in 2.6
|
||||
return ((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) /
|
||||
10 ** 6)
|
||||
|
||||
|
||||
class TestModels(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestModels, self).setUp()
|
||||
self.request = RequestFactory()
|
||||
|
||||
def test_record_stat(self):
|
||||
now = datetime.datetime.utcnow()
|
||||
record_stat('app.install', self.request, value=1)
|
||||
|
||||
# we should have only one record
|
||||
record = MonolithRecord.objects.get()
|
||||
|
||||
eq_(record.key, 'app.install')
|
||||
eq_(record.value, json.dumps({'value': 1}))
|
||||
self.assertTrue(total_seconds(record.recorded - now) < 1)
|
||||
|
||||
def test_record_stat_without_data(self):
|
||||
with self.assertRaises(ValueError):
|
||||
record_stat('app.install', self.request)
|
||||
|
||||
|
||||
class TestMonolithResource(RestOAuth):
|
||||
fixtures = fixture('user_2519')
|
||||
|
||||
def setUp(self):
|
||||
super(TestMonolithResource, self).setUp()
|
||||
self.grant_permission(self.profile, 'Monolith:API')
|
||||
self.list_url = reverse('monolith-list')
|
||||
self.now = datetime.datetime(2013, 02, 12, 17, 34)
|
||||
self.last_month = self.now - datetime.timedelta(days=30)
|
||||
self.last_week = self.now - datetime.timedelta(days=7)
|
||||
self.yesterday = self.now - datetime.timedelta(days=1)
|
||||
self.date_format = '%Y-%m-%d'
|
||||
self.request = RequestFactory()
|
||||
|
||||
def test_normal_call_with_no_records(self):
|
||||
res = self.client.get(self.list_url)
|
||||
eq_(res.status_code, 200)
|
||||
data = json.loads(res.content)
|
||||
|
||||
eq_(data['objects'], [])
|
||||
|
||||
def test_normal_call(self):
|
||||
record_stat('app.install', self.request, value=2)
|
||||
|
||||
res = self.client.get(self.list_url)
|
||||
eq_(res.status_code, 200)
|
||||
data = json.loads(res.content)
|
||||
|
||||
eq_(len(data['objects']), 1)
|
||||
obj = data['objects'][0]
|
||||
|
||||
eq_(obj['key'], 'app.install')
|
||||
eq_(obj['value'], {'value': 2})
|
||||
|
||||
# Check other fields we want to exist but ignore their value here.
|
||||
for field in ('recorded', 'user_hash'):
|
||||
assert field in obj
|
||||
|
||||
def test_filter_by_date(self):
|
||||
for id_, date in enumerate((self.last_week, self.yesterday, self.now)):
|
||||
record_stat('app.install', self.request, __recorded=date,
|
||||
value=id_)
|
||||
|
||||
res = self.client.get(self.list_url, data={
|
||||
'end': self.now.strftime(self.date_format)})
|
||||
eq_(res.status_code, 200)
|
||||
data = json.loads(res.content)
|
||||
eq_(len(data['objects']), 2)
|
||||
|
||||
res = self.client.get(self.list_url, data={
|
||||
'start': self.yesterday.strftime(self.date_format),
|
||||
'end': self.now.strftime(self.date_format)})
|
||||
data = json.loads(res.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
|
||||
def test_filter_by_key(self):
|
||||
record_stat('apps_added_us_free', self.request, value=3)
|
||||
record_stat('apps_added_uk_free', self.request, value=1)
|
||||
|
||||
# Exact match.
|
||||
res = self.client.get(self.list_url,
|
||||
data={'key': 'apps_added_us_free'})
|
||||
eq_(res.status_code, 200)
|
||||
data = json.loads(res.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
|
||||
@mock.patch('mkt.monolith.resources._get_query_result')
|
||||
def test_on_the_fly_query(self, _get_query):
|
||||
key = 'apps_ratings'
|
||||
_get_query.return_value = [{
|
||||
'key': key,
|
||||
'recorded': datetime.date.today(),
|
||||
'user_hash': None,
|
||||
'value': {'count': 1, 'app-id': 123}}]
|
||||
|
||||
res = self.client.get(self.list_url, data={'key': key})
|
||||
eq_(res.status_code, 200)
|
||||
data = json.loads(res.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
|
||||
obj = data['objects'][0]
|
||||
eq_(obj['key'], key)
|
||||
eq_(obj['recorded'], datetime.date.today().strftime(self.date_format))
|
||||
eq_(obj['value']['count'], 1)
|
||||
eq_(obj['value']['app-id'], 123)
|
||||
|
||||
def test_on_the_fly_missing_start(self):
|
||||
key = 'apps_ratings'
|
||||
res = self.client.get(self.list_url, data={'key': key})
|
||||
eq_(res.status_code, 400)
|
||||
data = json.loads(res.content)
|
||||
eq_(data['detail'], '`start` was not provided')
|
||||
|
||||
@mock.patch('mkt.monolith.resources._get_query_result')
|
||||
def test_on_the_fly_query_pagination(self, _get_query):
|
||||
key = 'apps_ratings'
|
||||
_get_query.return_value = [
|
||||
{'key': key, 'recorded': datetime.date.today(), 'user_hash': None,
|
||||
'value': {'count': 1, 'app-id': 123}},
|
||||
{'key': key, 'recorded': datetime.date.today(), 'user_hash': None,
|
||||
'value': {'count': 1, 'app-id': 234}},
|
||||
{'key': key, 'recorded': datetime.date.today(), 'user_hash': None,
|
||||
'value': {'count': 1, 'app-id': 345}},
|
||||
]
|
||||
|
||||
res = self.client.get(self.list_url, data={'key': key, 'limit': 2})
|
||||
eq_(res.status_code, 200)
|
||||
data = json.loads(res.content)
|
||||
eq_(len(data['objects']), 2)
|
||||
|
||||
ok_(data['meta']['next'] is not None)
|
||||
ok_(data['meta']['previous'] is None)
|
||||
eq_(data['meta']['total_count'], 3)
|
||||
eq_(data['meta']['offset'], 0)
|
||||
eq_(data['meta']['limit'], 2)
|
||||
|
||||
eq_(data['objects'][0]['value']['app-id'], 123)
|
||||
eq_(data['objects'][1]['value']['app-id'], 234)
|
||||
|
||||
res = self.client.get(self.list_url, data={'key': key, 'limit': 2,
|
||||
'offset': 2})
|
||||
eq_(res.status_code, 200)
|
||||
data = json.loads(res.content)
|
||||
eq_(len(data['objects']), 1)
|
||||
eq_(data['objects'][0]['value']['app-id'], 345)
|
||||
|
||||
ok_(data['meta']['next'] is None)
|
||||
ok_(data['meta']['previous'] is not None)
|
||||
eq_(data['meta']['total_count'], 3)
|
||||
eq_(data['meta']['offset'], 2)
|
||||
eq_(data['meta']['limit'], 2)
|
||||
|
||||
def test_pagination(self):
|
||||
record_stat('app.install', self.request, value=2)
|
||||
record_stat('app.install', self.request, value=4)
|
||||
record_stat('app.install', self.request, value=6)
|
||||
|
||||
res = self.client.get(self.list_url, data={'limit': 2})
|
||||
eq_(res.status_code, 200)
|
||||
data = json.loads(res.content)
|
||||
eq_(len(data['objects']), 2)
|
||||
|
||||
ok_(data['meta']['next'] is not None)
|
||||
ok_(data['meta']['previous'] is None)
|
||||
eq_(data['meta']['total_count'], 3)
|
||||
eq_(data['meta']['offset'], 0)
|
||||
eq_(data['meta']['limit'], 2)
|
||||
|
||||
|
||||
class TestDateRange(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.today = datetime.datetime.now().replace(microsecond=0)
|
||||
self.week_ago = self.days_ago(7)
|
||||
|
||||
def test_date_range(self):
|
||||
range = list(daterange(self.week_ago, self.today))
|
||||
eq_(len(range), 7)
|
||||
eq_(range[0], self.week_ago)
|
||||
ok_(self.today not in range)
|
|
@ -1,8 +0,0 @@
|
|||
from django.conf.urls import patterns, url
|
||||
|
||||
from .resources import MonolithView
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^monolith/data/', MonolithView.as_view(), name='monolith-list'),
|
||||
)
|
|
@ -59,7 +59,6 @@ heka-py==0.30.3
|
|||
heka-py-cef==0.3.1
|
||||
heka-py-raven==0.6
|
||||
mock==1.0.1
|
||||
monolith.client==0.9
|
||||
moz-addon-packager==1.0.1
|
||||
mozilla-logger==0.2
|
||||
mozpay==2.0.0
|
||||
|
|
|
@ -38,7 +38,6 @@ HOME=/tmp
|
|||
|
||||
#once per day
|
||||
05 8 * * * %(z_cron)s email_daily_ratings --settings=settings_local_mkt
|
||||
10 8 * * * %(z_cron)s update_monolith_stats `/bin/date -d 'yesterday' +\%%Y-\%%m-\%%d`
|
||||
15 8 * * * %(z_cron)s process_iarc_changes --settings=settings_local_mkt
|
||||
30 8 * * * %(z_cron)s dump_user_installs_cron --settings=settings_local_mkt
|
||||
00 9 * * * %(z_cron)s update_app_downloads --settings=settings_local_mkt
|
||||
|
|
|
@ -186,8 +186,6 @@ ALLOW_SELF_REVIEWS = True
|
|||
GOOGLE_ANALYTICS_CREDENTIALS = private.GOOGLE_ANALYTICS_CREDENTIALS
|
||||
GOOGLE_API_CREDENTIALS = private.GOOGLE_API_CREDENTIALS
|
||||
|
||||
MONOLITH_SERVER = 'https://monolith-dev.allizom.org'
|
||||
|
||||
GEOIP_URL = 'http://geo-dev.marketplace.allizom.org'
|
||||
|
||||
AWS_ACCESS_KEY_ID = private.AWS_ACCESS_KEY_ID
|
||||
|
|
|
@ -187,8 +187,6 @@ ALLOW_SELF_REVIEWS = True
|
|||
GOOGLE_ANALYTICS_CREDENTIALS = private.GOOGLE_ANALYTICS_CREDENTIALS
|
||||
GOOGLE_API_CREDENTIALS = private.GOOGLE_API_CREDENTIALS
|
||||
|
||||
MONOLITH_SERVER = 'https://monolith-dev.allizom.org'
|
||||
|
||||
GEOIP_URL = 'https://geo-dev-marketplace.allizom.org'
|
||||
|
||||
AWS_ACCESS_KEY_ID = private.AWS_ACCESS_KEY_ID
|
||||
|
|
|
@ -178,8 +178,6 @@ XSENDFILE_HEADER = 'X-Accel-Redirect'
|
|||
|
||||
ALLOW_SELF_REVIEWS = True
|
||||
|
||||
MONOLITH_SERVER = 'https://monolith.allizom.org'
|
||||
|
||||
GEOIP_URL = 'http://geo.marketplace.allizom.org'
|
||||
|
||||
API_THROTTLE = False
|
||||
|
|
|
@ -169,8 +169,6 @@ XSENDFILE_HEADER = 'X-Accel-Redirect'
|
|||
GOOGLE_ANALYTICS_CREDENTIALS = private.GOOGLE_ANALYTICS_CREDENTIALS
|
||||
GOOGLE_API_CREDENTIALS = private.GOOGLE_API_CREDENTIALS
|
||||
|
||||
MONOLITH_SERVER = 'https://monolith.firefox.com'
|
||||
|
||||
GEOIP_URL = 'http://geo.marketplace.firefox.com'
|
||||
|
||||
NEWRELIC_WHITELIST = ['web1.addons.phx1.mozilla.com',
|
||||
|
|
|
@ -178,8 +178,6 @@ XSENDFILE_HEADER = 'X-Accel-Redirect'
|
|||
|
||||
ALLOW_SELF_REVIEWS = True
|
||||
|
||||
MONOLITH_SERVER = 'https://monolith.allizom.org'
|
||||
|
||||
GEOIP_URL = 'http://geo.marketplace.allizom.org'
|
||||
|
||||
API_THROTTLE = False
|
||||
|
|
Загрузка…
Ссылка в новой задаче