mirror of
https://github.com/jazzband/django-analytical.git
synced 2026-03-16 22:20:25 +00:00
Adds intercom.io support, fixes inconsent fails from json ordering.
This commit is contained in:
parent
6763d3e06b
commit
d76a0981b8
15 changed files with 263 additions and 18 deletions
|
|
@ -10,6 +10,7 @@ The application was inspired by and uses ideas from Analytical_, Joshua
|
|||
Krall's all-purpose analytics front-end for Rails.
|
||||
|
||||
The work on Crazy Egg was made possible by `Bateau Knowledge`_.
|
||||
The work on Intercom was made possible by `GreenKahuna`_.
|
||||
|
||||
.. _`Joost Cassee`: mailto:joost@cassee.net
|
||||
.. _`Eric Davis`: https://github.com/edavis
|
||||
|
|
@ -22,5 +23,6 @@ The work on Crazy Egg was made possible by `Bateau Knowledge`_.
|
|||
.. _`Tinnet Coronam`: https://github.com/tinnet
|
||||
.. _`Philippe O. Wagner`: mailto:admin@arteria.ch
|
||||
.. _`Max Arnold`: https://github.com/max-arnold
|
||||
.. _Analytical: https://github.com/jkrall/analytical
|
||||
.. _`Analytical`: https://github.com/jkrall/analytical
|
||||
.. _`Bateau Knowledge`: http://www.bateauknowledge.nl/
|
||||
.. _`GreenKahuna`: http://www.greenkahuna.com/
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
Version 0.17.0
|
||||
--------------
|
||||
* Add support for Intercom.io (Steven Skoczen)
|
||||
|
||||
Version 0.16.0
|
||||
--------------
|
||||
* Add support for GA Display Advertising features (Max Arnold)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ Currently supported services:
|
|||
* `Google Analytics`_ traffic analysis
|
||||
* `GoSquared`_ traffic monitoring
|
||||
* `HubSpot`_ inbound marketing
|
||||
* `Intercom`_ live chat and support
|
||||
* `KISSinsights`_ feedback surveys
|
||||
* `KISSmetrics`_ funnel analysis
|
||||
* `Mixpanel`_ event tracking
|
||||
|
|
@ -59,6 +60,7 @@ an issue to discuss your plans.
|
|||
.. _`Google Analytics`: http://www.google.com/analytics/
|
||||
.. _`GoSquared`: http://www.gosquared.com/
|
||||
.. _`HubSpot`: http://www.hubspot.com/
|
||||
.. _`Intercom`: http://www.intercom.io/
|
||||
.. _`KISSinsights`: http://www.kissinsights.com/
|
||||
.. _`KISSmetrics`: http://www.kissmetrics.com/
|
||||
.. _`Mixpanel`: http://www.mixpanel.com/
|
||||
|
|
|
|||
|
|
@ -10,6 +10,6 @@ Django_ project. See the ``docs`` directory for more information.
|
|||
|
||||
__author__ = "Joost Cassee"
|
||||
__email__ = "joost@cassee.net"
|
||||
__version__ = "0.16.0"
|
||||
__version__ = "0.17.0"
|
||||
__copyright__ = "Copyright (C) 2011-2012 Joost Cassee and others"
|
||||
__license__ = "MIT License"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ TAG_MODULES = [
|
|||
'analytical.google_analytics',
|
||||
'analytical.gosquared',
|
||||
'analytical.hubspot',
|
||||
'analytical.intercom',
|
||||
'analytical.kiss_insights',
|
||||
'analytical.kiss_metrics',
|
||||
'analytical.mixpanel',
|
||||
|
|
|
|||
63
analytical/templatetags/intercom.py
Normal file
63
analytical/templatetags/intercom.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
"""
|
||||
intercom.io template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import time
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import disable_html, get_required_setting, get_user_from_context
|
||||
|
||||
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};
|
||||
</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>
|
||||
"""
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
@register.tag
|
||||
def intercom(parser, token):
|
||||
"""
|
||||
Intercom.io template tag.
|
||||
|
||||
Renders Javascript code to intercom.io testing. You must supply
|
||||
your APP ID account number in the ``INTERCOM_APP_ID``
|
||||
setting.
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
if len(bits) > 1:
|
||||
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
|
||||
return IntercomNode()
|
||||
|
||||
|
||||
class IntercomNode(Node):
|
||||
def __init__(self):
|
||||
self.app_id = get_required_setting(
|
||||
'INTERCOM_APP_ID', APP_ID_RE,
|
||||
"must be a string looking like 'XXXXXXX'")
|
||||
|
||||
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:
|
||||
# Intercom is disabled for non-logged in users.
|
||||
html = disable_html(html, 'Intercom')
|
||||
return html
|
||||
|
||||
|
||||
def contribute_to_analytical(add_node):
|
||||
IntercomNode()
|
||||
add_node('head_bottom', IntercomNode)
|
||||
|
|
@ -11,6 +11,7 @@ from analytical.tests.test_tag_gauges import *
|
|||
from analytical.tests.test_tag_google_analytics import *
|
||||
from analytical.tests.test_tag_gosquared import *
|
||||
from analytical.tests.test_tag_hubspot import *
|
||||
from analytical.tests.test_tag_intercom import *
|
||||
from analytical.tests.test_tag_kiss_insights import *
|
||||
from analytical.tests.test_tag_kiss_metrics import *
|
||||
from analytical.tests.test_tag_mixpanel import *
|
||||
|
|
|
|||
|
|
@ -54,8 +54,12 @@ class ClickyTagTestCase(TagTestCase):
|
|||
def test_custom(self):
|
||||
r = ClickyNode().render(Context({'clicky_var1': 'val1',
|
||||
'clicky_var2': 'val2'}))
|
||||
self.assertTrue(re.search('var clicky_custom = {.*'
|
||||
'"var1": "val1", "var2": "val2".*};', r), r)
|
||||
self.assertTrue(
|
||||
(re.search('var clicky_custom = {.*'
|
||||
'"var1": "val1", "var2": "val2".*};', r) or
|
||||
re.search('var clicky_custom = {.*'
|
||||
'"var2": "val2", "var1": "val1".*};', r)), r
|
||||
)
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
|
|
|
|||
66
analytical/tests/test_tag_intercom.py
Normal file
66
analytical/tests/test_tag_intercom.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"""
|
||||
Tests for the intercom template tags and filters.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context
|
||||
|
||||
from analytical.templatetags.intercom import IntercomNode
|
||||
from analytical.tests.utils import TagTestCase, override_settings, SETTING_DELETED
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
@override_settings(INTERCOM_APP_ID='1234567890abcdef0123456789')
|
||||
class IntercomTagTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``intercom`` template tag.
|
||||
"""
|
||||
|
||||
def test_tag(self):
|
||||
self.assertEqual("""<!-- Intercom disabled on internal IP address
|
||||
|
||||
-->""",
|
||||
self.render_tag('intercom', 'intercom'))
|
||||
|
||||
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>
|
||||
<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)
|
||||
}))
|
||||
)
|
||||
|
||||
@override_settings(INTERCOM_APP_ID=SETTING_DELETED)
|
||||
def test_no_account_number(self):
|
||||
self.assertRaises(AnalyticalException, IntercomNode)
|
||||
|
||||
@override_settings(INTERCOM_APP_ID='123abQ')
|
||||
def test_wrong_account_number(self):
|
||||
self.assertRaises(AnalyticalException, IntercomNode)
|
||||
|
||||
def test_identify_name_email_and_created_at(self):
|
||||
now = datetime.datetime(2014, 4, 9, 15, 15, 0)
|
||||
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)
|
||||
|
||||
def test_disable_for_anonymous_users(self):
|
||||
r = IntercomNode().render(Context({'user': AnonymousUser()}))
|
||||
self.assertTrue(r.startswith('<!-- Intercom disabled on internal IP address'), r)
|
||||
|
|
@ -55,14 +55,19 @@ class KissMetricsTagTestCase(TagTestCase):
|
|||
def test_event(self):
|
||||
r = KissMetricsNode().render(Context({'kiss_metrics_event':
|
||||
('test_event', {'prop1': 'val1', 'prop2': 'val2'})}))
|
||||
self.assertTrue("_kmq.push(['record', 'test_event', "
|
||||
'{"prop1": "val1", "prop2": "val2"}]);' in r, r)
|
||||
self.assertTrue(
|
||||
("_kmq.push(['record', 'test_event', "
|
||||
'{"prop1": "val1", "prop2": "val2"}]);' in r or\
|
||||
"_kmq.push(['record', 'test_event', "
|
||||
'{"prop2": "val2", "prop1": "val1"}]);' in r), r)
|
||||
|
||||
def test_property(self):
|
||||
r = KissMetricsNode().render(Context({'kiss_metrics_properties':
|
||||
{'prop1': 'val1', 'prop2': 'val2'}}))
|
||||
self.assertTrue("_kmq.push(['set', "
|
||||
'{"prop1": "val1", "prop2": "val2"}]);' in r, r)
|
||||
self.assertTrue(
|
||||
('_kmq.push([\'set\', {"prop1": "val1", "prop2": "val2"}]);' in r or
|
||||
'_kmq.push([\'set\', {"prop2": "val2", "prop1": "val1"}]);' in r),
|
||||
r)
|
||||
|
||||
def test_alias(self):
|
||||
r = KissMetricsNode().render(Context({'kiss_metrics_alias':
|
||||
|
|
|
|||
|
|
@ -54,8 +54,9 @@ class MixpanelTagTestCase(TagTestCase):
|
|||
def test_event(self):
|
||||
r = MixpanelNode().render(Context({'mixpanel_event':
|
||||
('test_event', {'prop1': 'val1', 'prop2': 'val2'})}))
|
||||
self.assertTrue("mixpanel.track('test_event', "
|
||||
'{"prop1": "val1", "prop2": "val2"});' in r, r)
|
||||
self.assertTrue(
|
||||
('mixpanel.track(\'test_event\', {"prop1": "val1", "prop2": "val2"});' in r or
|
||||
'mixpanel.track(\'test_event\', {"prop2": "val2", "prop1": "val1"});' in r), r)
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
|
|
|
|||
|
|
@ -89,5 +89,6 @@ class UserVoiceTagTestCase(TagTestCase):
|
|||
}
|
||||
}
|
||||
r = UserVoiceNode().render(Context(vars))
|
||||
self.assertTrue('"custom_fields": {"field2": "val2", "field1": "val1"}'
|
||||
in r, r)
|
||||
self.assertTrue(
|
||||
('"custom_fields": {"field2": "val2", "field1": "val1"}' in r or
|
||||
'"custom_fields": {"field1": "val1", "field2": "val2"}' in r ), r)
|
||||
|
|
|
|||
|
|
@ -37,22 +37,25 @@ class WoopraTagTestCase(TagTestCase):
|
|||
@override_settings(WOOPRA_IDLE_TIMEOUT=1234)
|
||||
def test_idle_timeout(self):
|
||||
r = WoopraNode().render(Context({}))
|
||||
self.assertTrue('var woo_settings = {"domain": "example.com", '
|
||||
'"idle_timeout": "1234"};' in r, r)
|
||||
self.assertTrue(
|
||||
('var woo_settings = {"domain": "example.com", "idle_timeout": "1234"};' in r or
|
||||
'var woo_settings = {"idle_timeout": "1234", "domain": "example.com"};' in r), r)
|
||||
|
||||
def test_custom(self):
|
||||
r = WoopraNode().render(Context({'woopra_var1': 'val1',
|
||||
'woopra_var2': 'val2'}))
|
||||
self.assertTrue('var woo_visitor = {"var1": "val1", "var2": "val2"};'
|
||||
in r, r)
|
||||
self.assertTrue(
|
||||
('var woo_visitor = {"var1": "val1", "var2": "val2"};' in r or
|
||||
'var woo_visitor = {"var2": "val2", "var1": "val1"};' in r), r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify_name_and_email(self):
|
||||
r = WoopraNode().render(Context({'user': User(username='test',
|
||||
first_name='Firstname', last_name='Lastname',
|
||||
email="test@example.com")}))
|
||||
self.assertTrue('var woo_visitor = {"name": "Firstname Lastname", '
|
||||
'"email": "test@example.com"};' in r, r)
|
||||
self.assertTrue(
|
||||
('var woo_visitor = {"name": "Firstname Lastname", "email": "test@example.com"};' in r or
|
||||
'var woo_visitor = {"email": "test@example.com", "name": "Firstname Lastname"};' in r), r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify_username_no_email(self):
|
||||
|
|
|
|||
|
|
@ -128,6 +128,10 @@ settings required to enable each service are listed here:
|
|||
HUBSPOT_PORTAL_ID = '1234'
|
||||
HUBSPOT_DOMAIN = 'somedomain.web101.hubspot.com'
|
||||
|
||||
* :doc:`Intercom <services/intercom>`::
|
||||
|
||||
INTERCOM_APP_ID = '0123456789abcdef0123456789abcdef01234567'
|
||||
|
||||
* :doc:`KISSinsights <services/kiss_insights>`::
|
||||
|
||||
KISS_INSIGHTS_ACCOUNT_NUMBER = '12345'
|
||||
|
|
|
|||
88
docs/services/intercom.rst
Normal file
88
docs/services/intercom.rst
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
=============================
|
||||
Intercom.io -- Real-time tracking
|
||||
=============================
|
||||
|
||||
Intercom.io_ is an easy way to implement real-chat and individual
|
||||
support for a website
|
||||
|
||||
.. _Intercom.io: http://www.intercom.io/
|
||||
|
||||
|
||||
.. intercom-installation:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
To start using the Intercom.io integration, you must have installed the
|
||||
django-analytical package and have added the ``analytical`` application
|
||||
to :const:`INSTALLED_APPS` in your project :file:`settings.py` file.
|
||||
See :doc:`../install` for details.
|
||||
|
||||
Next you need to add the Intercom.io template tag to your templates.
|
||||
This step is only needed if you are not using the generic
|
||||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`intercom-configuration`.
|
||||
|
||||
The Intercom.io Javascript code is inserted into templates using a
|
||||
template tag. Load the :mod:`intercom` template tag library and
|
||||
insert the :ttag:`intercom` tag. Because every page that you want to
|
||||
track must have the tag, it is useful to add it to your base template.
|
||||
Insert the tag at the bottom of the HTML body::
|
||||
|
||||
{% load intercom %}
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<!-- Your page -->
|
||||
{% intercom %}
|
||||
</body>
|
||||
</html>
|
||||
...
|
||||
|
||||
|
||||
.. _intercom-configuration:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Before you can use the Intercom.io integration, you must first set your
|
||||
app id.
|
||||
|
||||
|
||||
.. _intercom-site-id:
|
||||
|
||||
Setting the app id
|
||||
--------------------------
|
||||
|
||||
Intercom.io gives you a unique app id, and the :ttag:`intercom`
|
||||
tag will include it in the rendered Javascript code. You can find your
|
||||
app id by clicking the *Tracking Code* link when logged into
|
||||
the on the intercom.io website. A page will display containing
|
||||
HTML code looking like this::
|
||||
|
||||
<script id="IntercomSettingsScriptTag">
|
||||
window.intercomSettings = { name: "Jill Doe", email: "jill@example.com", created_at: 1234567890, app_id: "XXXXXXXXXXXXXXXXXXXXXXX" };
|
||||
</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>
|
||||
|
||||
The code ``XXXXXXXXXXXXXXXXXXXXXXX`` is your app id. Set
|
||||
:const:`INTERCOM_APP_ID` in the project :file:`settings.py`
|
||||
file::
|
||||
|
||||
INTERCOM_APP_ID = 'XXXXXXXXXXXXXXXXXXXXXXX'
|
||||
|
||||
If you do not set an app id, the Javascript code will not be
|
||||
rendered.
|
||||
|
||||
|
||||
.. _intercom-internal-ips:
|
||||
|
||||
Internal IP addresses
|
||||
---------------------
|
||||
|
||||
Usually you do not want to track clicks from your development or
|
||||
internal IP addresses. By default, if the tags detect that the client
|
||||
comes from any address in the :const:`ANALYTICAL_INTERNAL_IPS` setting
|
||||
(which is :const:`INTERNAL_IPS` by default,) the tracking code is
|
||||
commented out. See :ref:`identifying-visitors` for important information
|
||||
about detecting the visitor IP address.
|
||||
Loading…
Reference in a new issue