2016-10-08 02:27:50 +02:00
|
|
|
from django.db import models
|
2016-07-29 21:52:45 +02:00
|
|
|
from django.test import TestCase
|
2016-10-08 02:27:50 +02:00
|
|
|
from django.utils import timezone
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2016-10-11 02:23:42 +02:00
|
|
|
from analytics.lib.counts import CountStat, COUNT_STATS, process_count_stat, \
|
2016-07-29 21:52:45 +02:00
|
|
|
zerver_count_user_by_realm, zerver_count_message_by_user, \
|
2016-10-12 23:40:48 +02:00
|
|
|
zerver_count_message_by_stream, zerver_count_stream_by_realm, \
|
|
|
|
do_fill_count_stat_at_hour, ZerverCountQuery
|
2016-10-08 02:27:50 +02:00
|
|
|
from analytics.models import BaseCount, InstallationCount, RealmCount, \
|
2017-01-07 09:19:37 +01:00
|
|
|
UserCount, StreamCount, FillState, installation_epoch
|
2016-10-08 02:27:50 +02:00
|
|
|
from zerver.models import Realm, UserProfile, Message, Stream, Recipient, \
|
2017-01-12 21:04:55 +01:00
|
|
|
Huddle, get_user_profile_by_email, get_client
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2016-10-08 02:27:50 +02:00
|
|
|
from datetime import datetime, timedelta
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2017-01-12 21:04:55 +01:00
|
|
|
from typing import Any, Type, Optional, Text, Tuple
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2016-10-07 01:29:57 +02:00
|
|
|
class AnalyticsTestCase(TestCase):
|
|
|
|
MINUTE = timedelta(seconds = 60)
|
|
|
|
HOUR = MINUTE * 60
|
|
|
|
DAY = HOUR * 24
|
2016-12-18 18:54:20 +01:00
|
|
|
TIME_ZERO = datetime(1988, 3, 14).replace(tzinfo=timezone.utc)
|
2016-10-07 01:29:57 +02:00
|
|
|
TIME_LAST_HOUR = TIME_ZERO - HOUR
|
|
|
|
|
2016-10-07 02:47:05 +02:00
|
|
|
def setUp(self):
|
|
|
|
# type: () -> None
|
2016-10-27 03:05:21 +02:00
|
|
|
self.default_realm = Realm.objects.create(
|
2016-10-28 07:21:53 +02:00
|
|
|
string_id='realmtest', name='Realm Test',
|
2016-12-17 03:26:39 +01:00
|
|
|
domain='test.analytics', date_created=self.TIME_ZERO - 2*self.DAY)
|
2017-01-13 07:23:47 +01:00
|
|
|
self.name_counter = 100
|
2016-10-07 02:47:05 +02:00
|
|
|
|
|
|
|
# Lightweight creation of users, streams, and messages
|
2017-01-13 07:23:47 +01:00
|
|
|
def create_user(self, **kwargs):
|
|
|
|
# type: (**Any) -> UserProfile
|
|
|
|
self.name_counter += 1
|
2016-10-07 02:47:05 +02:00
|
|
|
defaults = {
|
2017-01-13 07:23:47 +01:00
|
|
|
'email': 'user%s@domain.tld' % (self.name_counter,),
|
2016-10-07 02:47:05 +02:00
|
|
|
'date_joined': self.TIME_LAST_HOUR,
|
|
|
|
'full_name': 'full_name',
|
|
|
|
'short_name': 'short_name',
|
|
|
|
'pointer': -1,
|
|
|
|
'last_pointer_updater': 'seems unused?',
|
|
|
|
'realm': self.default_realm,
|
|
|
|
'api_key': '42'}
|
2016-07-29 21:52:45 +02:00
|
|
|
for key, value in defaults.items():
|
|
|
|
kwargs[key] = kwargs.get(key, value)
|
2017-01-13 07:23:47 +01:00
|
|
|
return UserProfile.objects.create(**kwargs)
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2017-01-12 21:04:55 +01:00
|
|
|
def create_stream_with_recipient(self, **kwargs):
|
|
|
|
# type: (**Any) -> Tuple[Stream, Recipient]
|
2017-01-13 07:23:47 +01:00
|
|
|
self.name_counter += 1
|
|
|
|
defaults = {'name': 'stream name %s' % (self.name_counter,),
|
2016-10-07 02:47:05 +02:00
|
|
|
'realm': self.default_realm,
|
|
|
|
'date_created': self.TIME_LAST_HOUR}
|
2016-07-29 21:52:45 +02:00
|
|
|
for key, value in defaults.items():
|
|
|
|
kwargs[key] = kwargs.get(key, value)
|
2017-01-12 21:04:55 +01:00
|
|
|
stream = Stream.objects.create(**kwargs)
|
|
|
|
recipient = Recipient.objects.create(type_id=stream.id, type=Recipient.STREAM)
|
|
|
|
return stream, recipient
|
|
|
|
|
2017-01-13 07:23:47 +01:00
|
|
|
def create_huddle_with_recipient(self, **kwargs):
|
|
|
|
# type: (**Any) -> Tuple[Huddle, Recipient]
|
|
|
|
self.name_counter += 1
|
|
|
|
defaults = {'huddle_hash': 'hash%s' % (self.name_counter,)}
|
2017-01-12 21:04:55 +01:00
|
|
|
for key, value in defaults.items():
|
|
|
|
kwargs[key] = kwargs.get(key, value)
|
|
|
|
huddle = Huddle.objects.create(**kwargs)
|
|
|
|
recipient = Recipient.objects.create(type_id=huddle.id, type=Recipient.HUDDLE)
|
|
|
|
return huddle, recipient
|
2016-07-29 21:52:45 +02:00
|
|
|
|
|
|
|
def create_message(self, sender, recipient, **kwargs):
|
|
|
|
# type: (UserProfile, Recipient, **Any) -> Message
|
|
|
|
defaults = {
|
|
|
|
'sender': sender,
|
|
|
|
'recipient': recipient,
|
2016-10-07 02:47:05 +02:00
|
|
|
'subject': 'subject',
|
|
|
|
'content': 'hi',
|
|
|
|
'pub_date': self.TIME_LAST_HOUR,
|
|
|
|
'sending_client': get_client("website")}
|
2016-07-29 21:52:45 +02:00
|
|
|
for key, value in defaults.items():
|
|
|
|
kwargs[key] = kwargs.get(key, value)
|
2016-10-07 02:47:05 +02:00
|
|
|
return Message.objects.create(**kwargs)
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2016-10-08 02:27:50 +02:00
|
|
|
# kwargs should only ever be a UserProfile or Stream.
|
2016-12-17 02:26:46 +01:00
|
|
|
def assertCountEquals(self, table, property, value, subgroup=None, end_time=TIME_ZERO,
|
|
|
|
interval=CountStat.HOUR, realm=None, **kwargs):
|
|
|
|
# type: (Type[BaseCount], Text, int, Optional[Text], datetime, str, Optional[Realm], **models.Model) -> None
|
2016-12-17 02:25:01 +01:00
|
|
|
queryset = table.objects.filter(property=property, interval=interval, end_time=end_time) \
|
|
|
|
.filter(**kwargs)
|
|
|
|
if table is not InstallationCount:
|
|
|
|
if realm is None:
|
|
|
|
realm = self.default_realm
|
|
|
|
queryset = queryset.filter(realm=realm)
|
2016-12-17 02:26:46 +01:00
|
|
|
if subgroup is not None:
|
|
|
|
queryset = queryset.filter(subgroup=subgroup)
|
2016-12-17 02:25:01 +01:00
|
|
|
self.assertEqual(queryset.values_list('value', flat=True)[0], value)
|
2016-07-29 21:52:45 +02:00
|
|
|
|
2016-10-08 03:23:24 +02:00
|
|
|
class TestProcessCountStat(AnalyticsTestCase):
|
2016-11-03 08:27:32 +01:00
|
|
|
def make_dummy_count_stat(self, current_time):
|
|
|
|
# type: (datetime) -> CountStat
|
|
|
|
dummy_query = """INSERT INTO analytics_realmcount (realm_id, property, end_time, interval, value)
|
2016-11-14 09:15:35 +01:00
|
|
|
VALUES (1, 'test stat', '%(end_time)s','hour', 22)""" % {'end_time': current_time}
|
2016-11-03 08:27:32 +01:00
|
|
|
count_stat = CountStat('test stat', ZerverCountQuery(Recipient, UserCount, dummy_query),
|
|
|
|
{}, None, CountStat.HOUR, False)
|
|
|
|
return count_stat
|
|
|
|
|
2016-10-12 23:40:48 +02:00
|
|
|
def assertFillStateEquals(self, end_time, state = FillState.DONE, property = None):
|
2016-12-08 05:06:51 +01:00
|
|
|
# type: (datetime, int, Optional[Text]) -> None
|
2016-11-03 08:27:32 +01:00
|
|
|
count_stat = self.make_dummy_count_stat(end_time)
|
2016-10-12 23:40:48 +02:00
|
|
|
if property is None:
|
2016-11-03 08:27:32 +01:00
|
|
|
property = count_stat.property
|
2017-01-07 09:19:37 +01:00
|
|
|
fill_state = FillState.objects.filter(property=property).first()
|
|
|
|
self.assertEqual(fill_state.end_time, end_time)
|
|
|
|
self.assertEqual(fill_state.state, state)
|
2016-10-12 23:40:48 +02:00
|
|
|
|
|
|
|
def test_process_stat(self):
|
|
|
|
# type: () -> None
|
|
|
|
# process new stat
|
|
|
|
current_time = installation_epoch() + self.HOUR
|
2016-11-03 08:27:32 +01:00
|
|
|
count_stat = self.make_dummy_count_stat(current_time)
|
|
|
|
process_count_stat(count_stat, current_time)
|
2016-10-12 23:40:48 +02:00
|
|
|
self.assertFillStateEquals(current_time)
|
2016-11-03 08:27:32 +01:00
|
|
|
self.assertEqual(InstallationCount.objects.filter(property = count_stat.property,
|
analytics: Simplify frequency and measurement interval options.
Change the CountStat object to take an is_gauge variable instead of a
smallest_interval variable. Previously, (smallest_interval, frequency)
could be any of (hour, hour), (hour, day), (hour, gauge), (day, hour),
(day, day), or (day, gauge).
The current change is equivalent to excluding (hour, day) and (day, hour)
from the list above.
This change, along with other recent changes, allows us to simplify how we
handle time intervals. This commit also removes the TimeInterval object.
2016-10-14 00:15:46 +02:00
|
|
|
interval = CountStat.HOUR).count(), 1)
|
2016-10-12 23:40:48 +02:00
|
|
|
|
|
|
|
# dirty stat
|
2016-11-03 08:27:32 +01:00
|
|
|
FillState.objects.filter(property=count_stat.property).update(state=FillState.STARTED)
|
|
|
|
process_count_stat(count_stat, current_time)
|
2016-10-12 23:40:48 +02:00
|
|
|
self.assertFillStateEquals(current_time)
|
2016-11-03 08:27:32 +01:00
|
|
|
self.assertEqual(InstallationCount.objects.filter(property = count_stat.property,
|
analytics: Simplify frequency and measurement interval options.
Change the CountStat object to take an is_gauge variable instead of a
smallest_interval variable. Previously, (smallest_interval, frequency)
could be any of (hour, hour), (hour, day), (hour, gauge), (day, hour),
(day, day), or (day, gauge).
The current change is equivalent to excluding (hour, day) and (day, hour)
from the list above.
This change, along with other recent changes, allows us to simplify how we
handle time intervals. This commit also removes the TimeInterval object.
2016-10-14 00:15:46 +02:00
|
|
|
interval = CountStat.HOUR).count(), 1)
|
2016-10-12 23:40:48 +02:00
|
|
|
|
|
|
|
# clean stat, no update
|
2016-11-03 08:27:32 +01:00
|
|
|
process_count_stat(count_stat, current_time)
|
2016-10-12 23:40:48 +02:00
|
|
|
self.assertFillStateEquals(current_time)
|
2016-11-03 08:27:32 +01:00
|
|
|
self.assertEqual(InstallationCount.objects.filter(property = count_stat.property,
|
analytics: Simplify frequency and measurement interval options.
Change the CountStat object to take an is_gauge variable instead of a
smallest_interval variable. Previously, (smallest_interval, frequency)
could be any of (hour, hour), (hour, day), (hour, gauge), (day, hour),
(day, day), or (day, gauge).
The current change is equivalent to excluding (hour, day) and (day, hour)
from the list above.
This change, along with other recent changes, allows us to simplify how we
handle time intervals. This commit also removes the TimeInterval object.
2016-10-14 00:15:46 +02:00
|
|
|
interval = CountStat.HOUR).count(), 1)
|
2016-10-12 23:40:48 +02:00
|
|
|
|
|
|
|
# clean stat, with update
|
|
|
|
current_time = current_time + self.HOUR
|
2016-11-03 08:27:32 +01:00
|
|
|
count_stat = self.make_dummy_count_stat(current_time)
|
|
|
|
process_count_stat(count_stat, current_time)
|
2016-10-12 23:40:48 +02:00
|
|
|
self.assertFillStateEquals(current_time)
|
2016-11-03 08:27:32 +01:00
|
|
|
self.assertEqual(InstallationCount.objects.filter(property = count_stat.property,
|
analytics: Simplify frequency and measurement interval options.
Change the CountStat object to take an is_gauge variable instead of a
smallest_interval variable. Previously, (smallest_interval, frequency)
could be any of (hour, hour), (hour, day), (hour, gauge), (day, hour),
(day, day), or (day, gauge).
The current change is equivalent to excluding (hour, day) and (day, hour)
from the list above.
This change, along with other recent changes, allows us to simplify how we
handle time intervals. This commit also removes the TimeInterval object.
2016-10-14 00:15:46 +02:00
|
|
|
interval = CountStat.HOUR).count(), 2)
|
2016-10-12 23:40:48 +02:00
|
|
|
|
2016-10-08 03:23:24 +02:00
|
|
|
class TestCountStats(AnalyticsTestCase):
|
2016-12-17 03:26:39 +01:00
|
|
|
def setUp(self):
|
|
|
|
# type: () -> None
|
|
|
|
super(TestCountStats, self).setUp()
|
|
|
|
self.second_realm = Realm.objects.create(
|
|
|
|
string_id='second-realm', name='Second Realm',
|
|
|
|
domain='second.analytics', date_created=self.TIME_ZERO-2*self.DAY)
|
2017-01-13 07:23:47 +01:00
|
|
|
user = self.create_user(realm=self.second_realm)
|
2017-01-12 21:04:55 +01:00
|
|
|
stream, recipient = self.create_stream_with_recipient(realm=self.second_realm)
|
2016-12-17 03:26:39 +01:00
|
|
|
self.create_message(user, recipient)
|
|
|
|
|
2017-01-13 07:23:47 +01:00
|
|
|
future_user = self.create_user(realm=self.second_realm, date_joined=self.TIME_ZERO)
|
|
|
|
future_stream, future_recipient = self.create_stream_with_recipient(
|
|
|
|
realm=self.second_realm, date_created=self.TIME_ZERO)
|
2016-12-17 03:26:39 +01:00
|
|
|
self.create_message(future_user, future_recipient, pub_date=self.TIME_ZERO)
|
|
|
|
|
|
|
|
def test_active_users_by_is_bot(self):
|
2016-10-08 03:23:24 +02:00
|
|
|
# type: () -> None
|
2016-12-17 03:26:39 +01:00
|
|
|
property = 'active_users:is_bot'
|
|
|
|
stat = COUNT_STATS[property]
|
2016-10-08 03:23:24 +02:00
|
|
|
|
2016-12-17 03:26:39 +01:00
|
|
|
# To be included
|
2017-01-13 07:23:47 +01:00
|
|
|
self.create_user(is_bot=True)
|
|
|
|
self.create_user(is_bot=True, date_joined=self.TIME_ZERO-25*self.HOUR)
|
|
|
|
self.create_user(is_bot=False)
|
2016-10-08 03:23:24 +02:00
|
|
|
|
2016-12-17 03:26:39 +01:00
|
|
|
# To be excluded
|
2017-01-13 07:23:47 +01:00
|
|
|
self.create_user(is_active=False)
|
2016-12-17 03:26:39 +01:00
|
|
|
|
|
|
|
do_fill_count_stat_at_hour(stat, self.TIME_ZERO)
|
2016-10-08 03:23:24 +02:00
|
|
|
|
2016-12-17 03:26:39 +01:00
|
|
|
self.assertCountEquals(RealmCount, property, 2, subgroup='true', interval=stat.interval)
|
|
|
|
self.assertCountEquals(RealmCount, property, 1, subgroup='false', interval=stat.interval)
|
|
|
|
self.assertCountEquals(RealmCount, property, 1, subgroup='false', interval=stat.interval, realm=self.second_realm)
|
|
|
|
self.assertEqual(RealmCount.objects.count(), 3)
|
2016-12-19 23:45:53 +01:00
|
|
|
self.assertCountEquals(InstallationCount, property, 2, subgroup='true', interval=stat.interval)
|
|
|
|
self.assertCountEquals(InstallationCount, property, 2, subgroup='false', interval=stat.interval)
|
|
|
|
self.assertEqual(InstallationCount.objects.count(), 2)
|
2016-12-17 03:26:39 +01:00
|
|
|
self.assertFalse(UserCount.objects.exists())
|
|
|
|
self.assertFalse(StreamCount.objects.exists())
|