Adds support for custom variables, in a similar pattern to woopra's tag.

This commit is contained in:
skoczen 2014-04-28 10:10:23 -07:00
parent 6ebdb3de98
commit a94f0d8bb0
3 changed files with 150 additions and 32 deletions

View file

@ -3,17 +3,19 @@ intercom.io template tags and filters.
"""
from __future__ import absolute_import
import json
import time
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import disable_html, get_required_setting, get_user_from_context
from analytical.utils import disable_html, get_required_setting, is_internal_ip,\
get_user_from_context, get_identity
APP_ID_RE = re.compile(r'[\da-f]+$')
TRACKING_CODE = """
<script id="IntercomSettingsScriptTag">
window.intercomSettings = {'app_id': '%(app_id)s', 'full_name': '%(full_name)s', 'email': '%(email)s', 'created_at': %(created_at)s};
window.intercomSettings = %(settings_json)s;
</script>
<script>(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://static.intercomcdn.com/intercom.v1.js';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()</script>
"""
@ -42,17 +44,40 @@ class IntercomNode(Node):
'INTERCOM_APP_ID', APP_ID_RE,
"must be a string looking like 'XXXXXXX'")
def _identify(self, user):
name = user.get_full_name()
if not name:
name = user.username
return name
def _get_custom_attrs(self, context):
vars = {}
for dict_ in context:
for var, val in dict_.items():
if var.startswith('intercom_'):
vars[var[9:]] = val
user = get_user_from_context(context)
if user is not None and user.is_authenticated():
if 'full_name' not in vars:
vars['full_name'] = get_identity(context, 'intercom', self._identify, user)
if 'email' not in vars and user.email:
vars['email'] = user.email
vars['created_at'] = int(time.mktime(user.date_joined.timetuple()))
else:
vars['created_at'] = None
return vars
def render(self, context):
html = ""
user = get_user_from_context(context)
if user is not None and user.is_authenticated():
html = TRACKING_CODE % {
'app_id': self.app_id,
'full_name': "%s %s" % (user.first_name, user.last_name),
'email': user.email,
'created_at': int(time.mktime(user.date_joined.timetuple())),
}
else:
vars = self._get_custom_attrs(context)
vars["app_id"] = self.app_id
html = TRACKING_CODE % {"settings_json": json.dumps(vars)}
if is_internal_ip(context, 'INTERCOM') or not user or not user.is_authenticated():
# Intercom is disabled for non-logged in users.
html = disable_html(html, 'Intercom')
return html

View file

@ -20,29 +20,29 @@ class IntercomTagTestCase(TagTestCase):
"""
def test_tag(self):
self.assertEqual("""<!-- Intercom disabled on internal IP address
-->""",
self.render_tag('intercom', 'intercom'))
rendered_tag = self.render_tag('intercom', 'intercom')
self.assertTrue(rendered_tag.startswith('<!-- Intercom disabled on internal IP address'))
def test_node(self):
now = datetime.datetime(2014, 4, 9, 15, 15, 0)
self.assertEqual(
"""
<script id="IntercomSettingsScriptTag">
window.intercomSettings = {'app_id': '1234567890abcdef0123456789', 'full_name': 'Firstname Lastname', 'email': 'test@example.com', 'created_at': 1397074500};
</script>
rendered_tag = IntercomNode().render(Context({
'user': User(
username='test',
first_name='Firstname',
last_name='Lastname',
email="test@example.com",
date_joined=now)
}))
# Because the json isn't predictably ordered, we can't just test the whole thing verbatim.
self.assertIn("""<script id="IntercomSettingsScriptTag">
window.intercomSettings = {""", rendered_tag)
self.assertIn('"app_id": "1234567890abcdef0123456789"', rendered_tag)
self.assertIn('"full_name": "Firstname Lastname"', rendered_tag)
self.assertIn('"email": "test@example.com"', rendered_tag)
self.assertIn('"created_at": 1397074500', rendered_tag)
self.assertIn("""</script>
<script>(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://static.intercomcdn.com/intercom.v1.js';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()</script>
""",
IntercomNode().render(Context({
'user': User(
username='test',
first_name='Firstname',
last_name='Lastname',
email="test@example.com",
date_joined=now)
}))
)
""", rendered_tag)
@override_settings(INTERCOM_APP_ID=SETTING_DELETED)
def test_no_account_number(self):
@ -57,9 +57,43 @@ class IntercomTagTestCase(TagTestCase):
r = IntercomNode().render(Context({'user': User(username='test',
first_name='Firstname', last_name='Lastname',
email="test@example.com", date_joined=now)}))
self.assertTrue("window.intercomSettings = {'app_id': '1234567890abcdef0123456789', "
"'full_name': 'Firstname Lastname', "
"'email': 'test@example.com', 'created_at': 1397074500};" in r, r)
self.assertTrue("window.intercomSettings = {" in r, r)
self.assertTrue('"app_id": "1234567890abcdef0123456789"' in r)
self.assertTrue('"full_name": "Firstname Lastname"' in r)
self.assertTrue('"email": "test@example.com"' in r)
self.assertTrue('"created_at": 1397074500' in r)
def test_custom(self):
r = IntercomNode().render(Context({
'intercom_var1': 'val1',
'intercom_var2': 'val2'
}))
self.assertTrue('"var1": "val1"' in r)
self.assertTrue('"var2": "val2"' in r)
def test_identify_name_and_email(self):
r = IntercomNode().render(Context({
'user': User(username='test',
first_name='Firstname',
last_name='Lastname',
email="test@example.com")
}))
self.assertTrue('"full_name": "Firstname Lastname"' in r)
self.assertTrue('"email": "test@example.com"' in r)
def test_identify_username_no_email(self):
r = IntercomNode().render(Context({'user': User(username='test')}))
self.assertTrue('"full_name": "test"' in r, r)
def test_no_identify_when_explicit_name(self):
r = IntercomNode().render(Context({'intercom_full_name': 'explicit',
'user': User(username='implicit')}))
self.assertTrue('"full_name": "explicit"' in r, r)
def test_no_identify_when_explicit_email(self):
r = IntercomNode().render(Context({'intercom_email': 'explicit',
'user': User(username='implicit')}))
self.assertTrue('"email": "explicit"' in r, r)
def test_disable_for_anonymous_users(self):
r = IntercomNode().render(Context({'user': AnonymousUser()}))

View file

@ -75,6 +75,65 @@ If you do not set an app id, the Javascript code will not be
rendered.
Custom data
-----------
As described in the Intercom documentation on `custom visitor data`_,
the data that is tracked by Intercom can be customized. Using template
context variables, you can let the :ttag:`intercom` tag pass custom data
to Intercom automatically. You can set the context variables in your view
when your render a template containing the tracking code::
context = RequestContext({'intercom_cart_value': cart.total_price})
return some_template.render(context)
For some data, it is annoying to do this for every view, so you may want
to set variables in a context processor that you add to the
:data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`::
from django.utils.hashcompat import md5_constructor as md5
GRAVATAR_URL = 'http://www.gravatar.com/avatar/'
def intercom_custom_data(request):
try:
email = request.user.email
except AttributeError:
return {}
email_hash = md5(email).hexdigest()
avatar_url = "%s%s" % (GRAVATAR_URL, email_hash)
return {'intercom_avatar': avatar_url}
Just remember that if you set the same context variable in the
:class:`~django.template.context.RequestContext` constructor and in a
context processor, the latter clobbers the former.
Standard variables that will be displayed in the Intercom live visitor
data are listed in the table below, but you can define any ``intercom_*``
variable you like and have that detail passed from within the visitor
live stream data when viewing Intercom.
==================== ===========================================
Context variable Description
==================== ===========================================
``intercom_name`` The visitor's full name.
-------------------- -------------------------------------------
``intercom_email`` The visitor's email address.
-------------------- -------------------------------------------
``created_at`` The date the visitor created an account
==================== ===========================================
.. _`custom visitor data`: http://docs.intercom.io/custom-data/adding-custom-data
Identifying authenticated users
-------------------------------
If you have not set the ``intercom_name`` or ``intercom_email`` variables
explicitly, the username and email address of an authenticated user are
passed to Intercom automatically. See :ref:`identifying-visitors`.
.. _intercom-internal-ips:
Internal IP addresses