diff --git a/analytical/templatetags/analytical.py b/analytical/templatetags/analytical.py index 7851268..8ab2cdb 100644 --- a/analytical/templatetags/analytical.py +++ b/analytical/templatetags/analytical.py @@ -24,6 +24,7 @@ TAG_MODULES = [ 'analytical.gauges', 'analytical.google_analytics', 'analytical.gosquared', + 'analytical.hotjar', 'analytical.hubspot', 'analytical.intercom', 'analytical.kiss_insights', diff --git a/analytical/templatetags/hotjar.py b/analytical/templatetags/hotjar.py new file mode 100644 index 0000000..b85a126 --- /dev/null +++ b/analytical/templatetags/hotjar.py @@ -0,0 +1,65 @@ +""" +Hotjar template tags and filters. +""" +from __future__ import absolute_import + +import re + +from django.template import Library, Node, TemplateSyntaxError + +from analytical.utils import get_required_setting, is_internal_ip, disable_html + + +HOTJAR_TRACKING_CODE = """\ + +""" + + +register = Library() + + +def _validate_no_args(token): + bits = token.split_contents() + if len(bits) > 1: + raise TemplateSyntaxError("'%s' takes no arguments" % bits[0]) + + +@register.tag +def hotjar(parser, token): + """ + Hotjar template tag. + """ + _validate_no_args(token) + return HotjarNode() + + +class HotjarNode(Node): + + def __init__(self): + self.site_id = get_required_setting( + 'HOTJAR_SITE_ID', + re.compile(r'^\d+$'), + "must be (a string containing) a number", + ) + + def render(self, context): + html = HOTJAR_TRACKING_CODE % {'HOTJAR_SITE_ID': self.site_id} + if is_internal_ip(context, 'HOTJAR'): + return disable_html(html, 'Hotjar') + else: + return html + + +def contribute_to_analytical(add_node): + # ensure properly configured + HotjarNode() + add_node('head_bottom', HotjarNode) diff --git a/analytical/tests/test_tag_hotjar.py b/analytical/tests/test_tag_hotjar.py new file mode 100644 index 0000000..c7e656d --- /dev/null +++ b/analytical/tests/test_tag_hotjar.py @@ -0,0 +1,84 @@ +""" +Tests for the Hotjar template tags. +""" +from django.http import HttpRequest +from django.template import Context, Template, TemplateSyntaxError +from django.test import override_settings + +from analytical.templatetags.analytical import _load_template_nodes +from analytical.templatetags.hotjar import HotjarNode +from analytical.tests.utils import TagTestCase +from analytical.utils import AnalyticalException + + +expected_html = """\ + +""" + + +@override_settings(HOTJAR_SITE_ID='123456789') +class HotjarTagTestCase(TagTestCase): + + maxDiff = None + + def test_tag(self): + html = self.render_tag('hotjar', 'hotjar') + self.assertEqual(expected_html, html) + + def test_node(self): + html = HotjarNode().render(Context({})) + self.assertEqual(expected_html, html) + + def test_tags_take_no_args(self): + self.assertRaisesRegexp( + TemplateSyntaxError, + r"^'hotjar' takes no arguments$", + lambda: (Template('{% load hotjar %}{% hotjar "arg" %}') + .render(Context({}))), + ) + + @override_settings(HOTJAR_SITE_ID=None) + def test_no_id(self): + expected_pattern = r'^HOTJAR_SITE_ID setting is not set$' + self.assertRaisesRegexp(AnalyticalException, expected_pattern, HotjarNode) + + @override_settings(HOTJAR_SITE_ID='invalid') + def test_invalid_id(self): + expected_pattern = ( + r"^HOTJAR_SITE_ID setting: must be \(a string containing\) a number: 'invalid'$") + self.assertRaisesRegexp(AnalyticalException, expected_pattern, HotjarNode) + + @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) + def test_render_internal_ip(self): + request = HttpRequest() + request.META['REMOTE_ADDR'] = '1.1.1.1' + context = Context({'request': request}) + + actual_html = HotjarNode().render(context) + disabled_html = '\n'.join([ + '', + ]) + self.assertEqual(disabled_html, actual_html) + + def test_contribute_to_analytical(self): + """ + `hotjar.contribute_to_analytical` registers the head and body nodes. + """ + template_nodes = _load_template_nodes() + self.assertEqual({ + 'head_top': [], + 'head_bottom': [HotjarNode], + 'body_top': [], + 'body_bottom': [], + }, template_nodes)