2017-01-14 01:27:19 +01:00
|
|
|
from __future__ import absolute_import
|
|
|
|
|
2016-10-08 02:27:50 +02:00
|
|
|
from django.db import models
|
2017-02-15 17:26:22 +01:00
|
|
|
from django.db.models import Sum
|
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, \
|
2017-02-15 17:26:22 +01:00
|
|
|
do_fill_count_stat_at_hour, do_increment_logging_stat, ZerverCountQuery, \
|
2017-02-17 22:38:03 +01:00
|
|
|
LoggingCountStat, do_aggregate_to_summary_table
|
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
|
2017-02-15 17:26:22 +01:00
|
|
|
from zerver.lib.actions import do_create_user, do_deactivate_user, \
|
|
|
|
do_activate_user, do_reactivate_user
|
2016-10-08 02:27:50 +02:00
|
|
|
from zerver.models import Realm, UserProfile, Message, Stream, Recipient, \
|
2017-01-14 00:19:28 +01:00
|
|
|
Huddle, Client, 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-14 01:27:19 +01:00
|
|
|
from six.moves import range
|
2017-03-03 19:01:52 +01:00
|
|
|
from typing import Any, Dict, List, Optional, Text, Tuple, Type, Union
|
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 08:12:39 +01:00
|
|
|
# used to generate unique names in self.create_*
|
2017-01-13 07:23:47 +01:00
|
|
|
self.name_counter = 100
|
2017-01-13 08:12:39 +01:00
|
|
|
# used as defaults in self.assertCountEquals
|
|
|
|
self.current_property = None # type: Optional[str]
|
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.
|
2017-01-13 08:12:39 +01:00
|
|
|
def assertCountEquals(self, table, value, property=None, subgroup=None,
|
2017-01-16 21:32:34 +01:00
|
|
|
end_time=TIME_ZERO, realm=None, **kwargs):
|
|
|
|
# type: (Type[BaseCount], int, Optional[Text], Optional[Text], datetime, Optional[Realm], **models.Model) -> None
|
2017-01-13 08:12:39 +01:00
|
|
|
if property is None:
|
|
|
|
property = self.current_property
|
2017-01-16 21:32:34 +01:00
|
|
|
queryset = table.objects.filter(property=property, end_time=end_time).filter(**kwargs)
|
2016-12-17 02:25:01 +01:00
|
|
|
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
|
|
|
|
2017-01-14 01:27:19 +01:00
|
|
|
def assertTableState(self, table, arg_keys, arg_values):
|
2017-02-15 17:26:22 +01:00
|
|
|
# type: (Type[BaseCount], List[str], List[List[Union[int, str, bool, datetime, Realm, UserProfile, Stream]]]) -> None
|
2017-01-14 01:27:19 +01:00
|
|
|
"""Assert that the state of a *Count table is what it should be.
|
|
|
|
|
|
|
|
Example usage:
|
|
|
|
self.assertTableState(RealmCount, ['property', 'subgroup', 'realm'],
|
|
|
|
[['p1', 4], ['p2', 10, self.alt_realm]])
|
|
|
|
|
|
|
|
table -- A *Count table.
|
|
|
|
arg_keys -- List of columns of <table>.
|
|
|
|
arg_values -- List of "rows" of <table>.
|
|
|
|
Each entry of arg_values (e.g. ['p1', 4]) represents a row of <table>.
|
|
|
|
The i'th value of the entry corresponds to the i'th arg_key, so e.g.
|
|
|
|
the first arg_values entry here corresponds to a row of RealmCount
|
|
|
|
with property='p1' and subgroup=10.
|
|
|
|
Any columns not specified (in this case, every column of RealmCount
|
|
|
|
other than property and subgroup) are either set to default values,
|
|
|
|
or are ignored.
|
|
|
|
|
|
|
|
The function checks that every entry of arg_values matches exactly one
|
|
|
|
row of <table>, and that no additional rows exist. Note that this means
|
|
|
|
checking a table with duplicate rows is not supported.
|
|
|
|
"""
|
|
|
|
defaults = {
|
|
|
|
'property': self.current_property,
|
|
|
|
'subgroup': None,
|
2017-01-16 21:32:34 +01:00
|
|
|
'end_time': self.TIME_ZERO}
|
2017-01-14 01:27:19 +01:00
|
|
|
for values in arg_values:
|
|
|
|
kwargs = {} # type: Dict[str, Any]
|
|
|
|
for i in range(len(values)):
|
|
|
|
kwargs[arg_keys[i]] = values[i]
|
|
|
|
for key, value in defaults.items():
|
|
|
|
kwargs[key] = kwargs.get(key, value)
|
|
|
|
if table is not InstallationCount:
|
|
|
|
if 'realm' not in kwargs:
|
|
|
|
if 'user' in kwargs:
|
|
|
|
kwargs['realm'] = kwargs['user'].realm
|
|
|
|
elif 'stream' in kwargs:
|
|
|
|
kwargs['realm'] = kwargs['stream'].realm
|
|
|
|
else:
|
|
|
|
kwargs['realm'] = self.default_realm
|
|
|
|
self.assertEqual(table.objects.filter(**kwargs).count(), 1)
|
|
|
|
self.assertEqual(table.objects.count(), len(arg_values))
|
|
|
|
|
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
|
2017-01-16 22:05:51 +01:00
|
|
|
dummy_query = """INSERT INTO analytics_realmcount (realm_id, property, end_time, value)
|
|
|
|
VALUES (1, 'test stat', '%(end_time)s', 22)""" % {'end_time': current_time}
|
2017-02-19 01:59:45 +01:00
|
|
|
stat = CountStat('test stat', ZerverCountQuery(Recipient, UserCount, dummy_query),
|
|
|
|
{}, None, CountStat.HOUR, False)
|
|
|
|
return stat
|
2016-11-03 08:27:32 +01:00
|
|
|
|
2017-02-19 01:59:45 +01: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
|
2017-02-19 01:59:45 +01:00
|
|
|
stat = self.make_dummy_count_stat(end_time)
|
2016-10-12 23:40:48 +02:00
|
|
|
if property is None:
|
2017-02-19 01:59:45 +01:00
|
|
|
property = 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
|
2017-02-19 01:59:45 +01:00
|
|
|
stat = self.make_dummy_count_stat(current_time)
|
|
|
|
property = stat.property
|
|
|
|
process_count_stat(stat, current_time)
|
2016-10-12 23:40:48 +02:00
|
|
|
self.assertFillStateEquals(current_time)
|
2017-01-16 21:32:34 +01:00
|
|
|
self.assertEqual(InstallationCount.objects.filter(property=property).count(), 1)
|
2016-10-12 23:40:48 +02:00
|
|
|
|
|
|
|
# dirty stat
|
2017-01-16 21:32:34 +01:00
|
|
|
FillState.objects.filter(property=property).update(state=FillState.STARTED)
|
2017-02-19 01:59:45 +01:00
|
|
|
process_count_stat(stat, current_time)
|
2016-10-12 23:40:48 +02:00
|
|
|
self.assertFillStateEquals(current_time)
|
2017-01-16 21:32:34 +01:00
|
|
|
self.assertEqual(InstallationCount.objects.filter(property=property).count(), 1)
|
2016-10-12 23:40:48 +02:00
|
|
|
|
|
|
|
# clean stat, no update
|
2017-02-19 01:59:45 +01:00
|
|
|
process_count_stat(stat, current_time)
|
2016-10-12 23:40:48 +02:00
|
|
|
self.assertFillStateEquals(current_time)
|
2017-01-16 21:32:34 +01:00
|
|
|
self.assertEqual(InstallationCount.objects.filter(property=property).count(), 1)
|
2016-10-12 23:40:48 +02:00
|
|
|
|
|
|
|
# clean stat, with update
|
|
|
|
current_time = current_time + self.HOUR
|
2017-02-19 01:59:45 +01:00
|
|
|
stat = self.make_dummy_count_stat(current_time)
|
|
|
|
process_count_stat(stat, current_time)
|
2016-10-12 23:40:48 +02:00
|
|
|
self.assertFillStateEquals(current_time)
|
2017-01-16 21:32:34 +01:00
|
|
|
self.assertEqual(InstallationCount.objects.filter(property=property).count(), 2)
|
2016-10-12 23:40:48 +02:00
|
|
|
|
2017-02-15 17:26:22 +01:00
|
|
|
# This tests the is_logging branch of the code in do_delete_counts_at_hour.
|
|
|
|
# It is important that do_delete_counts_at_hour not delete any of the collected
|
|
|
|
# logging data!
|
|
|
|
def test_process_logging_stat(self):
|
|
|
|
# type: () -> None
|
|
|
|
end_time = self.TIME_ZERO
|
|
|
|
|
|
|
|
user_stat = LoggingCountStat('user stat', UserCount, CountStat.DAY)
|
|
|
|
stream_stat = LoggingCountStat('stream stat', StreamCount, CountStat.DAY)
|
|
|
|
realm_stat = LoggingCountStat('realm stat', RealmCount, CountStat.DAY)
|
|
|
|
user = self.create_user()
|
|
|
|
stream = self.create_stream_with_recipient()[0]
|
|
|
|
realm = self.default_realm
|
|
|
|
UserCount.objects.create(
|
|
|
|
user=user, realm=realm, property=user_stat.property, end_time=end_time, value=5)
|
|
|
|
StreamCount.objects.create(
|
|
|
|
stream=stream, realm=realm, property=stream_stat.property, end_time=end_time, value=5)
|
|
|
|
RealmCount.objects.create(
|
|
|
|
realm=realm, property=realm_stat.property, end_time=end_time, value=5)
|
|
|
|
|
|
|
|
# Normal run of process_count_stat
|
|
|
|
for stat in [user_stat, stream_stat, realm_stat]:
|
|
|
|
process_count_stat(stat, end_time)
|
|
|
|
self.assertTableState(UserCount, ['property', 'value'], [[user_stat.property, 5]])
|
|
|
|
self.assertTableState(StreamCount, ['property', 'value'], [[stream_stat.property, 5]])
|
|
|
|
self.assertTableState(RealmCount, ['property', 'value'],
|
|
|
|
[[user_stat.property, 5], [stream_stat.property, 5], [realm_stat.property, 5]])
|
|
|
|
self.assertTableState(InstallationCount, ['property', 'value'],
|
|
|
|
[[user_stat.property, 5], [stream_stat.property, 5], [realm_stat.property, 5]])
|
|
|
|
|
|
|
|
# Change the logged data and mark FillState as dirty
|
|
|
|
UserCount.objects.update(value=6)
|
|
|
|
StreamCount.objects.update(value=6)
|
|
|
|
RealmCount.objects.filter(property=realm_stat.property).update(value=6)
|
|
|
|
FillState.objects.update(state=FillState.STARTED)
|
|
|
|
|
|
|
|
# Check that the change propagated (and the collected data wasn't deleted)
|
|
|
|
for stat in [user_stat, stream_stat, realm_stat]:
|
|
|
|
process_count_stat(stat, end_time)
|
|
|
|
self.assertTableState(UserCount, ['property', 'value'], [[user_stat.property, 6]])
|
|
|
|
self.assertTableState(StreamCount, ['property', 'value'], [[stream_stat.property, 6]])
|
|
|
|
self.assertTableState(RealmCount, ['property', 'value'],
|
|
|
|
[[user_stat.property, 6], [stream_stat.property, 6], [realm_stat.property, 6]])
|
|
|
|
self.assertTableState(InstallationCount, ['property', 'value'],
|
|
|
|
[[user_stat.property, 6], [stream_stat.property, 6], [realm_stat.property, 6]])
|
|
|
|
|
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()
|
2017-01-13 01:55:46 +01:00
|
|
|
# This tests two things for each of the queries/CountStats: Handling
|
|
|
|
# more than 1 realm, and the time bounds (time_start and time_end in
|
|
|
|
# the queries).
|
2016-12-17 03:26:39 +01:00
|
|
|
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 01:55:46 +01:00
|
|
|
for minutes_ago in [0, 1, 61, 60*24+1]:
|
|
|
|
creation_time = self.TIME_ZERO - minutes_ago*self.MINUTE
|
2017-01-14 01:42:32 +01:00
|
|
|
user = self.create_user(email='user-%s@second.analytics' % (minutes_ago,),
|
2017-01-13 01:55:46 +01:00
|
|
|
realm=self.second_realm, date_joined=creation_time)
|
|
|
|
recipient = self.create_stream_with_recipient(
|
|
|
|
name='stream %s' % (minutes_ago,), realm=self.second_realm,
|
|
|
|
date_created=creation_time)[1]
|
|
|
|
self.create_message(user, recipient, pub_date=creation_time)
|
2017-01-14 01:42:32 +01:00
|
|
|
self.hourly_user = UserProfile.objects.get(email='user-1@second.analytics')
|
|
|
|
self.daily_user = UserProfile.objects.get(email='user-61@second.analytics')
|
2017-01-13 01:55:46 +01:00
|
|
|
|
|
|
|
# This realm should not show up in the *Count tables for any of the
|
|
|
|
# messages_* CountStats
|
|
|
|
self.no_message_realm = Realm.objects.create(
|
|
|
|
string_id='no-message-realm', name='No Message Realm',
|
|
|
|
domain='no.message', date_created=self.TIME_ZERO-2*self.DAY)
|
|
|
|
self.create_user(realm=self.no_message_realm)
|
|
|
|
self.create_stream_with_recipient(realm=self.no_message_realm)
|
|
|
|
# This huddle should not show up anywhere
|
|
|
|
self.create_huddle_with_recipient()
|
2016-12-17 03:26:39 +01:00
|
|
|
|
|
|
|
def test_active_users_by_is_bot(self):
|
2016-10-08 03:23:24 +02:00
|
|
|
# type: () -> None
|
2017-01-16 22:05:16 +01:00
|
|
|
stat = COUNT_STATS['active_users:is_bot:day']
|
2017-01-13 08:12:39 +01:00
|
|
|
self.current_property = stat.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
|
|
|
|
2017-01-14 01:27:19 +01:00
|
|
|
self.assertTableState(RealmCount, ['value', 'subgroup', 'realm'],
|
|
|
|
[[2, 'true'], [1, 'false'],
|
|
|
|
[3, 'false', self.second_realm],
|
|
|
|
[1, 'false', self.no_message_realm]])
|
|
|
|
self.assertTableState(InstallationCount, ['value', 'subgroup'], [[2, 'true'], [5, 'false']])
|
|
|
|
self.assertTableState(UserCount, [], [])
|
|
|
|
self.assertTableState(StreamCount, [], [])
|
2017-01-11 02:37:43 +01:00
|
|
|
|
2017-01-14 00:19:28 +01:00
|
|
|
def test_messages_sent_by_is_bot(self):
|
|
|
|
# type: () -> None
|
2017-01-16 22:39:03 +01:00
|
|
|
stat = COUNT_STATS['messages_sent:is_bot:hour']
|
2017-01-14 00:19:28 +01:00
|
|
|
self.current_property = stat.property
|
|
|
|
|
|
|
|
bot = self.create_user(is_bot=True)
|
|
|
|
human1 = self.create_user()
|
|
|
|
human2 = self.create_user()
|
|
|
|
recipient_human1 = Recipient.objects.create(type_id=human1.id, type=Recipient.PERSONAL)
|
|
|
|
|
|
|
|
recipient_stream = self.create_stream_with_recipient()[1]
|
|
|
|
recipient_huddle = self.create_huddle_with_recipient()[1]
|
|
|
|
|
|
|
|
self.create_message(bot, recipient_human1)
|
|
|
|
self.create_message(bot, recipient_stream)
|
|
|
|
self.create_message(bot, recipient_huddle)
|
|
|
|
self.create_message(human1, recipient_human1)
|
|
|
|
self.create_message(human2, recipient_human1)
|
|
|
|
|
|
|
|
do_fill_count_stat_at_hour(stat, self.TIME_ZERO)
|
|
|
|
|
2017-01-14 01:27:19 +01:00
|
|
|
self.assertTableState(UserCount, ['value', 'subgroup', 'user'],
|
|
|
|
[[1, 'false', human1], [1, 'false', human2], [3, 'true', bot],
|
2017-01-16 22:39:03 +01:00
|
|
|
[1, 'false', self.hourly_user]])
|
2017-01-14 01:27:19 +01:00
|
|
|
self.assertTableState(RealmCount, ['value', 'subgroup', 'realm'],
|
2017-01-16 22:39:03 +01:00
|
|
|
[[2, 'false'], [3, 'true'], [1, 'false', self.second_realm]])
|
|
|
|
self.assertTableState(InstallationCount, ['value', 'subgroup'], [[3, 'false'], [3, 'true']])
|
2017-01-14 01:27:19 +01:00
|
|
|
self.assertTableState(StreamCount, [], [])
|
2017-01-14 00:19:28 +01:00
|
|
|
|
2017-01-11 02:37:43 +01:00
|
|
|
def test_messages_sent_by_message_type(self):
|
|
|
|
# type: () -> None
|
2017-01-16 22:05:16 +01:00
|
|
|
stat = COUNT_STATS['messages_sent:message_type:day']
|
2017-01-11 02:37:43 +01:00
|
|
|
self.current_property = stat.property
|
|
|
|
|
|
|
|
# Nothing currently in this stat that is bot related, but so many of
|
|
|
|
# the rest of our stats make the human/bot distinction that one can
|
|
|
|
# imagine a later refactoring that will intentionally or
|
|
|
|
# unintentionally change this. So make one of our users a bot.
|
|
|
|
user1 = self.create_user(is_bot=True)
|
|
|
|
user2 = self.create_user()
|
|
|
|
user3 = self.create_user()
|
|
|
|
|
|
|
|
# private streams
|
|
|
|
recipient_stream1 = self.create_stream_with_recipient(invite_only=True)[1]
|
|
|
|
recipient_stream2 = self.create_stream_with_recipient(invite_only=True)[1]
|
|
|
|
self.create_message(user1, recipient_stream1)
|
|
|
|
self.create_message(user2, recipient_stream1)
|
|
|
|
self.create_message(user2, recipient_stream2)
|
|
|
|
|
|
|
|
# public streams
|
|
|
|
recipient_stream3 = self.create_stream_with_recipient()[1]
|
|
|
|
recipient_stream4 = self.create_stream_with_recipient()[1]
|
|
|
|
self.create_message(user1, recipient_stream3)
|
|
|
|
self.create_message(user1, recipient_stream4)
|
|
|
|
self.create_message(user2, recipient_stream3)
|
|
|
|
|
|
|
|
# huddles
|
|
|
|
recipient_huddle1 = self.create_huddle_with_recipient()[1]
|
|
|
|
recipient_huddle2 = self.create_huddle_with_recipient()[1]
|
|
|
|
self.create_message(user1, recipient_huddle1)
|
|
|
|
self.create_message(user2, recipient_huddle2)
|
|
|
|
|
|
|
|
# private messages
|
|
|
|
recipient_user1 = Recipient.objects.create(type_id=user1.id, type=Recipient.PERSONAL)
|
|
|
|
recipient_user2 = Recipient.objects.create(type_id=user2.id, type=Recipient.PERSONAL)
|
|
|
|
recipient_user3 = Recipient.objects.create(type_id=user3.id, type=Recipient.PERSONAL)
|
|
|
|
self.create_message(user1, recipient_user2)
|
|
|
|
self.create_message(user2, recipient_user1)
|
|
|
|
self.create_message(user3, recipient_user3)
|
|
|
|
|
|
|
|
do_fill_count_stat_at_hour(stat, self.TIME_ZERO)
|
|
|
|
|
2017-01-14 01:27:19 +01:00
|
|
|
self.assertTableState(UserCount, ['value', 'subgroup', 'user'],
|
|
|
|
[[1, 'private_stream', user1],
|
|
|
|
[2, 'private_stream', user2],
|
|
|
|
[2, 'public_stream', user1],
|
|
|
|
[1, 'public_stream', user2],
|
|
|
|
[2, 'private_message', user1],
|
|
|
|
[2, 'private_message', user2],
|
|
|
|
[1, 'private_message', user3],
|
|
|
|
[1, 'public_stream', self.hourly_user],
|
|
|
|
[1, 'public_stream', self.daily_user]])
|
|
|
|
self.assertTableState(RealmCount, ['value', 'subgroup', 'realm'],
|
|
|
|
[[3, 'private_stream'], [3, 'public_stream'], [5, 'private_message'],
|
|
|
|
[2, 'public_stream', self.second_realm]])
|
|
|
|
self.assertTableState(InstallationCount, ['value', 'subgroup'],
|
|
|
|
[[3, 'private_stream'], [5, 'public_stream'], [5, 'private_message']])
|
|
|
|
self.assertTableState(StreamCount, [], [])
|
2017-01-11 02:37:43 +01:00
|
|
|
|
|
|
|
def test_messages_sent_to_recipients_with_same_id(self):
|
|
|
|
# type: () -> None
|
2017-01-16 22:05:16 +01:00
|
|
|
stat = COUNT_STATS['messages_sent:message_type:day']
|
2017-01-11 02:37:43 +01:00
|
|
|
self.current_property = stat.property
|
|
|
|
|
|
|
|
user = self.create_user(id=1000)
|
|
|
|
user_recipient = Recipient.objects.create(type_id=user.id, type=Recipient.PERSONAL)
|
|
|
|
stream_recipient = self.create_stream_with_recipient(id=1000)[1]
|
|
|
|
huddle_recipient = self.create_huddle_with_recipient(id=1000)[1]
|
|
|
|
|
|
|
|
self.create_message(user, user_recipient)
|
|
|
|
self.create_message(user, stream_recipient)
|
|
|
|
self.create_message(user, huddle_recipient)
|
|
|
|
|
|
|
|
do_fill_count_stat_at_hour(stat, self.TIME_ZERO)
|
|
|
|
|
|
|
|
self.assertCountEquals(UserCount, 2, subgroup='private_message')
|
|
|
|
self.assertCountEquals(UserCount, 1, subgroup='public_stream')
|
2017-01-13 06:03:20 +01:00
|
|
|
|
2017-01-14 00:19:28 +01:00
|
|
|
def test_messages_sent_by_client(self):
|
|
|
|
# type: () -> None
|
2017-01-16 22:05:16 +01:00
|
|
|
stat = COUNT_STATS['messages_sent:client:day']
|
2017-01-14 00:19:28 +01:00
|
|
|
self.current_property = stat.property
|
|
|
|
|
|
|
|
user1 = self.create_user(is_bot=True)
|
|
|
|
user2 = self.create_user()
|
|
|
|
recipient_user2 = Recipient.objects.create(type_id=user2.id, type=Recipient.PERSONAL)
|
|
|
|
|
|
|
|
recipient_stream = self.create_stream_with_recipient()[1]
|
|
|
|
recipient_huddle = self.create_huddle_with_recipient()[1]
|
|
|
|
|
|
|
|
client2 = Client.objects.create(name='client2')
|
|
|
|
|
|
|
|
self.create_message(user1, recipient_user2, sending_client=client2)
|
|
|
|
self.create_message(user1, recipient_stream)
|
|
|
|
self.create_message(user1, recipient_huddle)
|
|
|
|
self.create_message(user2, recipient_user2, sending_client=client2)
|
|
|
|
self.create_message(user2, recipient_user2, sending_client=client2)
|
|
|
|
|
|
|
|
do_fill_count_stat_at_hour(stat, self.TIME_ZERO)
|
|
|
|
|
|
|
|
client2_id = str(client2.id)
|
|
|
|
website_client_id = str(get_client('website').id) # default for self.create_message
|
2017-01-14 01:27:19 +01:00
|
|
|
self.assertTableState(UserCount, ['value', 'subgroup', 'user'],
|
|
|
|
[[2, website_client_id, user1],
|
|
|
|
[1, client2_id, user1], [2, client2_id, user2],
|
2017-01-16 21:13:46 +01:00
|
|
|
[1, website_client_id, self.hourly_user],
|
|
|
|
[1, website_client_id, self.daily_user]])
|
2017-01-14 01:27:19 +01:00
|
|
|
self.assertTableState(RealmCount, ['value', 'subgroup', 'realm'],
|
|
|
|
[[2, website_client_id], [3, client2_id],
|
2017-01-16 21:13:46 +01:00
|
|
|
[2, website_client_id, self.second_realm]])
|
2017-01-14 01:27:19 +01:00
|
|
|
self.assertTableState(InstallationCount, ['value', 'subgroup'],
|
2017-01-16 21:13:46 +01:00
|
|
|
[[4, website_client_id], [3, client2_id]])
|
2017-01-14 01:27:19 +01:00
|
|
|
self.assertTableState(StreamCount, [], [])
|
2017-01-14 00:19:28 +01:00
|
|
|
|
2017-01-13 06:03:20 +01:00
|
|
|
def test_messages_sent_to_stream_by_is_bot(self):
|
|
|
|
# type: () -> None
|
2017-02-15 04:10:03 +01:00
|
|
|
stat = COUNT_STATS['messages_in_stream:is_bot:day']
|
2017-01-13 06:03:20 +01:00
|
|
|
self.current_property = stat.property
|
|
|
|
|
|
|
|
bot = self.create_user(is_bot=True)
|
|
|
|
human1 = self.create_user()
|
|
|
|
human2 = self.create_user()
|
|
|
|
recipient_human1 = Recipient.objects.create(type_id=human1.id, type=Recipient.PERSONAL)
|
|
|
|
|
|
|
|
stream1, recipient_stream1 = self.create_stream_with_recipient()
|
|
|
|
stream2, recipient_stream2 = self.create_stream_with_recipient()
|
|
|
|
|
|
|
|
# To be included
|
|
|
|
self.create_message(human1, recipient_stream1)
|
|
|
|
self.create_message(human2, recipient_stream1)
|
|
|
|
self.create_message(human1, recipient_stream2)
|
|
|
|
self.create_message(bot, recipient_stream2)
|
|
|
|
self.create_message(bot, recipient_stream2)
|
|
|
|
|
|
|
|
# To be excluded
|
|
|
|
self.create_message(human2, recipient_human1)
|
|
|
|
self.create_message(bot, recipient_human1)
|
|
|
|
recipient_huddle = self.create_huddle_with_recipient()[1]
|
|
|
|
self.create_message(human1, recipient_huddle)
|
|
|
|
|
|
|
|
do_fill_count_stat_at_hour(stat, self.TIME_ZERO)
|
|
|
|
|
2017-01-14 01:27:19 +01:00
|
|
|
self.assertTableState(StreamCount, ['value', 'subgroup', 'stream'],
|
|
|
|
[[2, 'false', stream1], [1, 'false', stream2], [2, 'true', stream2],
|
2017-02-15 04:10:03 +01:00
|
|
|
# "hourly" and "daily" stream, from TestCountStats.setUp
|
|
|
|
[1, 'false', Stream.objects.get(name='stream 1')],
|
|
|
|
[1, 'false', Stream.objects.get(name='stream 61')]])
|
2017-01-14 01:27:19 +01:00
|
|
|
self.assertTableState(RealmCount, ['value', 'subgroup', 'realm'],
|
2017-02-15 04:10:03 +01:00
|
|
|
[[3, 'false'], [2, 'true'], [2, 'false', self.second_realm]])
|
|
|
|
self.assertTableState(InstallationCount, ['value', 'subgroup'], [[5, 'false'], [2, 'true']])
|
2017-01-14 01:27:19 +01:00
|
|
|
self.assertTableState(UserCount, [], [])
|
2017-02-15 17:26:22 +01:00
|
|
|
|
2017-02-17 22:38:03 +01:00
|
|
|
class TestDoAggregateToSummaryTable(AnalyticsTestCase):
|
|
|
|
# do_aggregate_to_summary_table is mostly tested by the end to end
|
|
|
|
# nature of the tests in TestCountStats. But want to highlight one
|
|
|
|
# feature important for keeping the size of the analytics tables small,
|
|
|
|
# which is that if there is no relevant data in the table being
|
|
|
|
# aggregated, the aggregation table doesn't get a row with value 0.
|
|
|
|
def test_no_aggregated_zeros(self):
|
|
|
|
# type: () -> None
|
|
|
|
stat = LoggingCountStat('test stat', UserCount, CountStat.HOUR)
|
|
|
|
do_aggregate_to_summary_table(stat, self.TIME_ZERO)
|
|
|
|
self.assertFalse(RealmCount.objects.exists())
|
|
|
|
self.assertFalse(InstallationCount.objects.exists())
|
|
|
|
|
2017-02-15 17:26:22 +01:00
|
|
|
class TestDoIncrementLoggingStat(AnalyticsTestCase):
|
|
|
|
def test_table_and_id_args(self):
|
|
|
|
# type: () -> None
|
|
|
|
# For realms, streams, and users, tests that the new rows are going to
|
|
|
|
# the appropriate *Count table, and that using a different zerver_object
|
|
|
|
# results in a new row being created
|
|
|
|
self.current_property = 'test'
|
|
|
|
second_realm = Realm.objects.create(string_id='moo', name='moo', domain='moo')
|
|
|
|
stat = LoggingCountStat('test', RealmCount, CountStat.DAY)
|
|
|
|
do_increment_logging_stat(self.default_realm, stat, None, self.TIME_ZERO)
|
|
|
|
do_increment_logging_stat(second_realm, stat, None, self.TIME_ZERO)
|
|
|
|
self.assertTableState(RealmCount, ['realm'], [[self.default_realm], [second_realm]])
|
|
|
|
|
|
|
|
user1 = self.create_user()
|
|
|
|
user2 = self.create_user()
|
|
|
|
stat = LoggingCountStat('test', UserCount, CountStat.DAY)
|
|
|
|
do_increment_logging_stat(user1, stat, None, self.TIME_ZERO)
|
|
|
|
do_increment_logging_stat(user2, stat, None, self.TIME_ZERO)
|
|
|
|
self.assertTableState(UserCount, ['user'], [[user1], [user2]])
|
|
|
|
|
|
|
|
stream1 = self.create_stream_with_recipient()[0]
|
|
|
|
stream2 = self.create_stream_with_recipient()[0]
|
|
|
|
stat = LoggingCountStat('test', StreamCount, CountStat.DAY)
|
|
|
|
do_increment_logging_stat(stream1, stat, None, self.TIME_ZERO)
|
|
|
|
do_increment_logging_stat(stream2, stat, None, self.TIME_ZERO)
|
|
|
|
self.assertTableState(StreamCount, ['stream'], [[stream1], [stream2]])
|
|
|
|
|
|
|
|
def test_frequency(self):
|
|
|
|
# type: () -> None
|
|
|
|
times = [self.TIME_ZERO - self.MINUTE*i for i in [0, 1, 61, 24*60+1]]
|
|
|
|
|
|
|
|
stat = LoggingCountStat('day test', RealmCount, CountStat.DAY)
|
|
|
|
for time_ in times:
|
|
|
|
do_increment_logging_stat(self.default_realm, stat, None, time_)
|
|
|
|
stat = LoggingCountStat('hour test', RealmCount, CountStat.HOUR)
|
|
|
|
for time_ in times:
|
|
|
|
do_increment_logging_stat(self.default_realm, stat, None, time_)
|
|
|
|
|
|
|
|
self.assertTableState(RealmCount, ['value', 'property', 'end_time'],
|
|
|
|
[[3, 'day test', self.TIME_ZERO],
|
|
|
|
[1, 'day test', self.TIME_ZERO - self.DAY],
|
|
|
|
[2, 'hour test', self.TIME_ZERO],
|
|
|
|
[1, 'hour test', self.TIME_LAST_HOUR],
|
|
|
|
[1, 'hour test', self.TIME_ZERO - self.DAY]])
|
|
|
|
|
|
|
|
def test_get_or_create(self):
|
|
|
|
# type: () -> None
|
|
|
|
stat = LoggingCountStat('test', RealmCount, CountStat.HOUR)
|
|
|
|
# All these should trigger the create part of get_or_create.
|
|
|
|
# property is tested in test_frequency, and id_args are tested in test_id_args,
|
|
|
|
# so this only tests a new subgroup and end_time
|
|
|
|
do_increment_logging_stat(self.default_realm, stat, 'subgroup1', self.TIME_ZERO)
|
|
|
|
do_increment_logging_stat(self.default_realm, stat, 'subgroup2', self.TIME_ZERO)
|
|
|
|
do_increment_logging_stat(self.default_realm, stat, 'subgroup1', self.TIME_LAST_HOUR)
|
|
|
|
self.current_property = 'test'
|
|
|
|
self.assertTableState(RealmCount, ['value', 'subgroup', 'end_time'],
|
|
|
|
[[1, 'subgroup1', self.TIME_ZERO], [1, 'subgroup2', self.TIME_ZERO],
|
|
|
|
[1, 'subgroup1', self.TIME_LAST_HOUR]])
|
|
|
|
# This should trigger the get part of get_or_create
|
|
|
|
do_increment_logging_stat(self.default_realm, stat, 'subgroup1', self.TIME_ZERO)
|
|
|
|
self.assertTableState(RealmCount, ['value', 'subgroup', 'end_time'],
|
|
|
|
[[2, 'subgroup1', self.TIME_ZERO], [1, 'subgroup2', self.TIME_ZERO],
|
|
|
|
[1, 'subgroup1', self.TIME_LAST_HOUR]])
|
|
|
|
|
|
|
|
def test_increment(self):
|
|
|
|
# type: () -> None
|
|
|
|
stat = LoggingCountStat('test', RealmCount, CountStat.DAY)
|
|
|
|
self.current_property = 'test'
|
|
|
|
do_increment_logging_stat(self.default_realm, stat, None, self.TIME_ZERO, increment=-1)
|
|
|
|
self.assertTableState(RealmCount, ['value'], [[-1]])
|
|
|
|
do_increment_logging_stat(self.default_realm, stat, None, self.TIME_ZERO, increment=3)
|
|
|
|
self.assertTableState(RealmCount, ['value'], [[2]])
|
|
|
|
do_increment_logging_stat(self.default_realm, stat, None, self.TIME_ZERO)
|
|
|
|
self.assertTableState(RealmCount, ['value'], [[3]])
|
|
|
|
|
|
|
|
class TestLoggingCountStats(AnalyticsTestCase):
|
|
|
|
def test_aggregation(self):
|
|
|
|
# type: () -> None
|
|
|
|
stat = LoggingCountStat('realm test', RealmCount, CountStat.DAY)
|
|
|
|
do_increment_logging_stat(self.default_realm, stat, None, self.TIME_ZERO)
|
|
|
|
process_count_stat(stat, self.TIME_ZERO)
|
|
|
|
|
|
|
|
user = self.create_user()
|
|
|
|
stat = LoggingCountStat('user test', UserCount, CountStat.DAY)
|
|
|
|
do_increment_logging_stat(user, stat, None, self.TIME_ZERO)
|
|
|
|
process_count_stat(stat, self.TIME_ZERO)
|
|
|
|
|
|
|
|
stream = self.create_stream_with_recipient()[0]
|
|
|
|
stat = LoggingCountStat('stream test', StreamCount, CountStat.DAY)
|
|
|
|
do_increment_logging_stat(stream, stat, None, self.TIME_ZERO)
|
|
|
|
process_count_stat(stat, self.TIME_ZERO)
|
|
|
|
|
|
|
|
self.assertTableState(InstallationCount, ['property', 'value'],
|
|
|
|
[['realm test', 1], ['user test', 1], ['stream test', 1]])
|
|
|
|
self.assertTableState(RealmCount, ['property', 'value'],
|
|
|
|
[['realm test', 1], ['user test', 1], ['stream test', 1]])
|
|
|
|
self.assertTableState(UserCount, ['property', 'value'], [['user test', 1]])
|
|
|
|
self.assertTableState(StreamCount, ['property', 'value'], [['stream test', 1]])
|
|
|
|
|
|
|
|
def test_active_users_log_by_is_bot(self):
|
|
|
|
# type: () -> None
|
|
|
|
property = 'active_users_log:is_bot:day'
|
|
|
|
user = do_create_user('email', 'password', self.default_realm, 'full_name', 'short_name')
|
|
|
|
self.assertEqual(1, RealmCount.objects.filter(property=property, subgroup=False)
|
|
|
|
.aggregate(Sum('value'))['value__sum'])
|
|
|
|
do_deactivate_user(user)
|
|
|
|
self.assertEqual(0, RealmCount.objects.filter(property=property, subgroup=False)
|
|
|
|
.aggregate(Sum('value'))['value__sum'])
|
|
|
|
do_activate_user(user)
|
|
|
|
self.assertEqual(1, RealmCount.objects.filter(property=property, subgroup=False)
|
|
|
|
.aggregate(Sum('value'))['value__sum'])
|
|
|
|
do_deactivate_user(user)
|
|
|
|
self.assertEqual(0, RealmCount.objects.filter(property=property, subgroup=False)
|
|
|
|
.aggregate(Sum('value'))['value__sum'])
|
|
|
|
do_reactivate_user(user)
|
|
|
|
self.assertEqual(1, RealmCount.objects.filter(property=property, subgroup=False)
|
|
|
|
.aggregate(Sum('value'))['value__sum'])
|