Add bots to page_params and send events on bot creation

(imported from commit ce418b4f056576d57f82d26af621473c730c12d8)
This commit is contained in:
Jason Michalski
2014-02-25 18:12:14 -05:00
parent 494868d21d
commit c17ed8dc8c
7 changed files with 187 additions and 14 deletions

View File

@@ -15,9 +15,9 @@ from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity,
get_stream_cache_key, to_dict_cache_key_id, is_super_user, \
UserActivityInterval, get_active_user_dicts_in_realm, get_active_streams, \
realm_filters_for_domain, RealmFilter, receives_offline_notifications, \
ScheduledJob, realm_filters_for_domain, RealmFilter
ScheduledJob, realm_filters_for_domain, RealmFilter, get_active_bot_dicts_in_realm
from zerver.lib.avatar import get_avatar_url
from zerver.lib.avatar import get_avatar_url, avatar_url
from guardian.shortcuts import assign_perm, remove_perm
from django.db import transaction, IntegrityError
@@ -96,6 +96,15 @@ def stream_user_ids(stream):
return [sub['user_profile_id'] for sub in subscriptions.values('user_profile_id')]
def bot_owner_userids(user_profile):
is_private_bot = (
user_profile.default_sending_stream and user_profile.default_sending_stream.invite_only or
user_profile.default_events_register_stream and user_profile.default_events_register_stream.invite_only)
if is_private_bot:
return (user_profile.bot_owner_id,)
else:
return active_user_ids(user_profile.realm)
def notify_created_user(user_profile):
event = dict(type="realm_user", op="add",
person=dict(email=user_profile.email,
@@ -104,6 +113,27 @@ def notify_created_user(user_profile):
is_bot=user_profile.is_bot))
send_event(event, active_user_ids(user_profile.realm))
def notify_created_bot(user_profile):
def stream_name(stream):
if not stream:
return None
return stream.name
default_sending_stream_name = stream_name(user_profile.default_sending_stream)
default_events_register_stream_name = stream_name(user_profile.default_events_register_stream)
event = dict(type="realm_bot", op="add",
bot=dict(email=user_profile.email,
full_name=user_profile.full_name,
api_key=user_profile.api_key,
default_sending_stream=default_sending_stream_name,
default_events_register_stream=default_events_register_stream_name,
default_all_public_streams=user_profile.default_all_public_streams,
avatar_url=avatar_url(user_profile),
))
send_event(event, bot_owner_userids(user_profile))
def do_create_user(email, password, realm, full_name, short_name,
active=True, bot=False, bot_owner=None,
avatar_source=UserProfile.AVATAR_FROM_GRAVATAR,
@@ -129,6 +159,8 @@ def do_create_user(email, password, realm, full_name, short_name,
default_all_public_streams=default_all_public_streams)
notify_created_user(user_profile)
if bot:
notify_created_bot(user_profile)
return user_profile
def user_sessions(user_profile):
@@ -2131,6 +2163,16 @@ def get_realm_user_dicts(user_profile):
'full_name' : userdict['full_name']}
for userdict in get_active_user_dicts_in_realm(user_profile.realm)]
def get_realm_bot_dicts(user_profile):
return [{'email' : botdict['email'],
'full_name' : botdict['full_name'],
'api_key' : botdict['api_key'],
'default_sending_stream': botdict['default_sending_stream__name'],
'default_events_register_stream': botdict['default_events_register_stream__name'],
'default_all_public_streams': botdict['default_all_public_streams'],
'avatar_url': get_avatar_url(botdict['avatar_source'], botdict['email']),
}
for botdict in get_active_bot_dicts_in_realm(user_profile.realm)]
# Fetch initial data. When event_types is not specified, clients want
# all event types. Whenever you add new code to this function, you
@@ -2181,6 +2223,9 @@ def fetch_initial_state_data(user_profile, event_types, queue_id):
if want('realm_user'):
state['realm_users'] = get_realm_user_dicts(user_profile)
if want('realm_bot'):
state['realm_bots'] = get_realm_bot_dicts(user_profile)
if want('referral'):
state['referrals'] = {'granted': user_profile.invites_granted,
'used': user_profile.invites_used}
@@ -2221,6 +2266,11 @@ def apply_events(state, events, user_profile):
for p in state['realm_users']:
if our_person(p):
p.update(person)
elif event['type'] == 'realm_bot':
if event['op'] == 'add':
state['realm_bots'].append(event['bot'])
elif event['type'] == 'stream':
if event['op'] == 'update':
# For legacy reasons, we call stream data 'subscriptions' in

View File

@@ -5,6 +5,7 @@ from functools import wraps
from django.core.cache import cache as djcache
from django.core.cache import get_cache
from django.conf import settings
from django.db.models import Q
from zerver.lib.utils import statsd, statsd_key, make_safe_digest
import time
@@ -239,6 +240,9 @@ def cache_save_user_profile(user_profile):
def active_user_dicts_in_realm_cache_key(realm):
return "active_user_dicts_in_realm:%s" % (realm.id,)
def active_bot_dicts_in_realm_cache_key(realm):
return "active_bot_dicts_in_realm:%s" % (realm.id,)
def get_stream_cache_key(stream_name, realm):
from zerver.models import Realm
if isinstance(realm, Realm):
@@ -267,6 +271,13 @@ def flush_user_profile(sender, **kwargs):
len(set(['full_name', 'short_name', 'email', 'is_active']) & set(kwargs['update_fields'])) > 0:
cache_delete(active_user_dicts_in_realm_cache_key(user_profile.realm))
# Invalidate our active_bots_in_realm info dict if any bot has changed
bot_fields = {'full_name', 'api_key', 'avatar_source',
'default_all_public_streams', 'is_active',
'default_sending_stream', 'default_events_register_stream'}
if user_profile.is_bot and (kwargs['update_fields'] is None or bot_fields & set(kwargs['update_fields'])):
cache_delete(active_bot_dicts_in_realm_cache_key(user_profile.realm))
# Invalidate realm-wide alert words cache if any user in the realm has changed
# alert words
if kwargs['update_fields'] is None or "alert_words" in kwargs['update_fields']:
@@ -282,6 +293,7 @@ def flush_realm(sender, **kwargs):
if realm.deactivated:
cache_delete(active_user_dicts_in_realm_cache_key(realm))
cache_delete(active_bot_dicts_in_realm_cache_key(realm))
cache_delete(realm_alert_words_cache_key(realm))
def realm_alert_words_cache_key(realm):
@@ -290,7 +302,15 @@ def realm_alert_words_cache_key(realm):
# Called by models.py to flush the stream cache whenever we save a stream
# object.
def flush_stream(sender, **kwargs):
from zerver.models import UserProfile
stream = kwargs['instance']
items_for_memcached = {}
items_for_memcached[get_stream_cache_key(stream.name, stream.realm)] = (stream,)
cache_set_many(items_for_memcached)
if kwargs['update_fields'] is None or 'name' in kwargs['update_fields'] and \
UserProfile.objects.filter(
Q(default_sending_stream=stream) |
Q(default_events_register_stream=stream)
).exists():
cache_delete(active_bot_dicts_in_realm_cache_key(stream.realm))

View File

@@ -41,6 +41,14 @@ def check_bool(var_name, val):
return '%s is not a boolean' % (var_name,)
return None
def check_none_or(sub_validator):
def f(var_name, val):
if val is None:
return
else:
return sub_validator(var_name, val)
return f
def check_list(sub_validator, length=None):
def f(var_name, val):
if not isinstance(val, list):

View File

@@ -9,7 +9,8 @@ from zerver.lib.cache import cache_with_key, flush_user_profile, flush_realm, \
user_profile_by_id_cache_key, user_profile_by_email_cache_key, \
generic_bulk_cached_fetch, cache_set, flush_stream, \
display_recipient_cache_key, cache_delete, \
get_stream_cache_key, active_user_dicts_in_realm_cache_key
get_stream_cache_key, active_user_dicts_in_realm_cache_key, \
active_bot_dicts_in_realm_cache_key
from zerver.lib.utils import make_safe_digest, generate_random_token
from django.db import transaction, IntegrityError
from zerver.lib.avatar import gravatar_hash, get_avatar_url
@@ -1034,6 +1035,17 @@ def get_active_user_dicts_in_realm(realm):
return UserProfile.objects.filter(realm=realm, is_active=True) \
.values('id', 'full_name', 'short_name', 'email', 'is_bot')
@cache_with_key(active_bot_dicts_in_realm_cache_key, timeout=3600*24*7)
def get_active_bot_dicts_in_realm(realm):
return UserProfile.objects.filter(realm=realm, is_active=True, is_bot=True) \
.values('id', 'full_name', 'short_name',
'email', 'default_sending_stream__name',
'default_events_register_stream__name',
'default_all_public_streams', 'api_key',
'avatar_source') \
.select_related('default_sending_stream',
'default_events_register_stream')
def get_prereg_user_by_email(email):
# A user can be invited many times, so only return the result of the latest
# invite.

View File

@@ -23,6 +23,7 @@ from zerver.lib.actions import (
do_set_muted_topics,
do_set_realm_name,
do_update_pointer,
do_create_user,
fetch_initial_state_data,
)
@@ -30,7 +31,7 @@ from zerver.lib.event_queue import allocate_client_descriptor
from zerver.lib.test_helpers import AuthedTestCase, POSTRequestMock
from zerver.lib.validator import (
check_bool, check_dict, check_int, check_list, check_string,
equals,
equals, check_none_or
)
from zerver.views import _default_all_public_streams, _default_narrow
@@ -202,6 +203,8 @@ class EventsRegisterTest(AuthedTestCase):
state['realm_users'] = {u['email']: u for u in state['realm_users']}
state['subscriptions'] = {u['name']: u for u in state['subscriptions']}
state['unsubscribed'] = {u['name']: u for u in state['unsubscribed']}
if 'realm_bots' in state:
state['realm_bots'] = {u['email']: u for u in state['realm_bots']}
normalize(state1)
normalize(state2)
self.assertEqual(state1, state2)
@@ -243,6 +246,27 @@ class EventsRegisterTest(AuthedTestCase):
"https://realm.com/my_realm_filter/%(id)s"))
self.do_test(lambda: do_remove_realm_filter(get_realm("zulip.com"), "#[123]"))
def test_create_bot(self):
bot_created_checker = check_dict([
('type', equals('realm_bot')),
('op', equals('add')),
('bot', check_dict([
('email', check_string),
('full_name', check_string),
('api_key', check_string),
('default_sending_stream', check_none_or(check_string)),
('default_events_register_stream', check_none_or(check_string)),
('default_all_public_streams', check_bool),
('avatar_url', check_string),
])),
])
action = lambda: do_create_user('test-bot@zulip.com', '123', get_realm('zulip.com'),
'Test Bot', 'test', bot=True, bot_owner=self.user_profile)
events = self.do_test(action)
error = bot_created_checker('events[1]', events[1])
self.assert_on_error(error)
def test_rename_stream(self):
realm = get_realm('zulip.com')
stream, _ = create_stream_if_needed(realm, 'old_name')
@@ -354,7 +378,6 @@ class EventsRegisterTest(AuthedTestCase):
error = stream_update_schema_checker('events[0]', events[0])
self.assert_on_error(error)
from zerver.lib.event_queue import EventQueue
class EventQueueTest(TestCase):
def test_one_event(self):

View File

@@ -361,9 +361,28 @@ class BotTest(AuthedTestCase):
def test_add_bot(self):
self.login("hamlet@zulip.com")
self.assert_num_bots_equal(0)
self.create_bot()
events = []
with tornado_redirected_to_list(events):
result = self.create_bot()
self.assert_num_bots_equal(1)
event = [e for e in events if e['event']['type'] == 'realm_bot'][0]
self.assertEqual(
dict(
type='realm_bot',
op='add',
bot=dict(email='hambot-bot@zulip.com',
full_name='The Bot of Hamlet',
api_key=result['api_key'],
avatar_url=result['avatar_url'],
default_sending_stream=None,
default_events_register_stream=None,
default_all_public_streams=False,
)
),
event['event']
)
def test_add_bot_with_default_sending_stream(self):
self.login("hamlet@zulip.com")
self.assert_num_bots_equal(0)
@@ -392,6 +411,8 @@ class BotTest(AuthedTestCase):
do_make_stream_private(user_profile.realm, "Denmark")
self.assert_num_bots_equal(0)
events = []
with tornado_redirected_to_list(events):
result = self.create_bot(default_sending_stream='Denmark')
self.assert_num_bots_equal(1)
self.assertEqual(result['default_sending_stream'], 'Denmark')
@@ -399,6 +420,24 @@ class BotTest(AuthedTestCase):
profile = get_user_profile_by_email('hambot-bot@zulip.com')
self.assertEqual(profile.default_sending_stream.name, 'Denmark')
event = [e for e in events if e['event']['type'] == 'realm_bot'][0]
self.assertEqual(
dict(
type='realm_bot',
op='add',
bot=dict(email='hambot-bot@zulip.com',
full_name='The Bot of Hamlet',
api_key=result['api_key'],
avatar_url=result['avatar_url'],
default_sending_stream='Denmark',
default_events_register_stream=None,
default_all_public_streams=False,
)
),
event['event']
)
self.assertEqual(event['users'], (user_profile.id,))
def test_add_bot_with_default_sending_stream_private_denied(self):
self.login("hamlet@zulip.com")
user_profile = get_user_profile_by_email("hamlet@zulip.com")
@@ -432,6 +471,8 @@ class BotTest(AuthedTestCase):
do_make_stream_private(user_profile.realm, "Denmark")
self.assert_num_bots_equal(0)
events = []
with tornado_redirected_to_list(events):
result = self.create_bot(default_events_register_stream='Denmark')
self.assert_num_bots_equal(1)
self.assertEqual(result['default_events_register_stream'], 'Denmark')
@@ -439,6 +480,24 @@ class BotTest(AuthedTestCase):
profile = get_user_profile_by_email('hambot-bot@zulip.com')
self.assertEqual(profile.default_events_register_stream.name, 'Denmark')
event = [e for e in events if e['event']['type'] == 'realm_bot'][0]
self.assertEqual(
dict(
type='realm_bot',
op='add',
bot=dict(email='hambot-bot@zulip.com',
full_name='The Bot of Hamlet',
api_key=result['api_key'],
avatar_url=result['avatar_url'],
default_sending_stream=None,
default_events_register_stream='Denmark',
default_all_public_streams=False,
)
),
event['event']
)
self.assertEqual(event['users'], (user_profile.id,))
def test_add_bot_with_default_events_register_stream_private_denied(self):
self.login("hamlet@zulip.com")
user_profile = get_user_profile_by_email("hamlet@zulip.com")

View File

@@ -870,6 +870,7 @@ def home(request):
unsubbed_info = register_ret['unsubscribed'],
email_dict = register_ret['email_dict'],
people_list = register_ret['realm_users'],
bot_list = register_ret['realm_bots'],
initial_pointer = register_ret['pointer'],
initial_presences = register_ret['presences'],
initial_servertime = time.time(), # Used for calculating relative presence age