Commit 79461722 by Renzo Lucioni

Re-use GA cookie when sending server-side events to Segment.io

parent 665479a8
...@@ -756,6 +756,7 @@ class CourseEnrollment(models.Model): ...@@ -756,6 +756,7 @@ class CourseEnrollment(models.Model):
tracker.emit(event_name, data) tracker.emit(event_name, data)
if settings.FEATURES.get('SEGMENT_IO_LMS') and settings.SEGMENT_IO_LMS_KEY: if settings.FEATURES.get('SEGMENT_IO_LMS') and settings.SEGMENT_IO_LMS_KEY:
tracking_context = tracker.get_tracker().resolve_context()
analytics.track(self.user_id, event_name, { analytics.track(self.user_id, event_name, {
'category': 'conversion', 'category': 'conversion',
'label': self.course_id.to_deprecated_string(), 'label': self.course_id.to_deprecated_string(),
...@@ -763,7 +764,12 @@ class CourseEnrollment(models.Model): ...@@ -763,7 +764,12 @@ class CourseEnrollment(models.Model):
'course': self.course_id.course, 'course': self.course_id.course,
'run': self.course_id.run, 'run': self.course_id.run,
'mode': self.mode, 'mode': self.mode,
}, context={
'Google Analytics': {
'clientId': tracking_context.get('client_id')
}
}) })
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
if event_name and self.course_id: if event_name and self.course_id:
log.exception('Unable to emit event %s for user %s and course %s', event_name, self.user.username, self.course_id) log.exception('Unable to emit event %s for user %s and course %s', event_name, self.user.username, self.course_id)
......
...@@ -87,7 +87,7 @@ class TrackMiddleware(object): ...@@ -87,7 +87,7 @@ class TrackMiddleware(object):
Extract information from the request and add it to the tracking Extract information from the request and add it to the tracking
context. context.
The following fields are injected in to the context: The following fields are injected into the context:
* session - The Django session key that identifies the user's session. * session - The Django session key that identifies the user's session.
* user_id - The numeric ID for the logged in user. * user_id - The numeric ID for the logged in user.
...@@ -96,6 +96,7 @@ class TrackMiddleware(object): ...@@ -96,6 +96,7 @@ class TrackMiddleware(object):
* host - The "SERVER_NAME" header, which should be the name of the server running this code. * host - The "SERVER_NAME" header, which should be the name of the server running this code.
* agent - The client browser identification string. * agent - The client browser identification string.
* path - The path part of the requested URL. * path - The path part of the requested URL.
* client_id - The unique key used by Google Analytics to identify a user
""" """
context = { context = {
'session': self.get_session_key(request), 'session': self.get_session_key(request),
...@@ -105,6 +106,14 @@ class TrackMiddleware(object): ...@@ -105,6 +106,14 @@ class TrackMiddleware(object):
for header_name, context_key in META_KEY_TO_CONTEXT_KEY.iteritems(): for header_name, context_key in META_KEY_TO_CONTEXT_KEY.iteritems():
context[context_key] = request.META.get(header_name, '') context[context_key] = request.META.get(header_name, '')
# Google Analytics uses the clientId to keep track of unique visitors. A GA cookie looks like
# this: _ga=GA1.2.1033501218.1368477899. The clientId is this part: 1033501218.1368477899.
google_analytics_cookie = request.COOKIES.get('_ga')
if google_analytics_cookie is None:
context['client_id'] = None
else:
context['client_id'] = '.'.join(google_analytics_cookie.split('.')[2:])
context.update(contexts.course_context_from_url(request.build_absolute_uri())) context.update(contexts.course_context_from_url(request.build_absolute_uri()))
tracker.get_tracker().enter_context( tracker.get_tracker().enter_context(
......
...@@ -45,6 +45,10 @@ class LegacyFieldMappingProcessor(object): ...@@ -45,6 +45,10 @@ class LegacyFieldMappingProcessor(object):
def remove_shim_context(event): def remove_shim_context(event):
if 'context' in event: if 'context' in event:
context = event['context'] context = event['context']
for field in CONTEXT_FIELDS_TO_INCLUDE: # These fields are present elsewhere in the event at this point
context_fields_to_remove = set(CONTEXT_FIELDS_TO_INCLUDE)
# This field is only used for Segment.io web analytics and does not concern researchers
context_fields_to_remove.add('client_id')
for field in context_fields_to_remove:
if field in context: if field in context:
del context[field] del context[field]
...@@ -64,6 +64,7 @@ class TrackMiddlewareTestCase(TestCase): ...@@ -64,6 +64,7 @@ class TrackMiddlewareTestCase(TestCase):
'path': '/courses/', 'path': '/courses/',
'org_id': '', 'org_id': '',
'course_id': '', 'course_id': '',
'client_id': None,
}) })
def get_context_for_path(self, path): def get_context_for_path(self, path):
......
...@@ -50,6 +50,7 @@ class LegacyFieldMappingProcessorTestCase(TestCase): ...@@ -50,6 +50,7 @@ class LegacyFieldMappingProcessorTestCase(TestCase):
'course_id': sentinel.course_id, 'course_id': sentinel.course_id,
'org_id': sentinel.org_id, 'org_id': sentinel.org_id,
'event_type': sentinel.event_type, 'event_type': sentinel.event_type,
'client_id': sentinel.client_id,
} }
with django_tracker.context('test', context): with django_tracker.context('test', context):
django_tracker.emit(sentinel.name, data) django_tracker.emit(sentinel.name, data)
......
...@@ -145,6 +145,39 @@ class TestTrackViews(TestCase): ...@@ -145,6 +145,39 @@ class TestTrackViews(TestCase):
self.mock_tracker.send.assert_called_once_with(expected_event) self.mock_tracker.send.assert_called_once_with(expected_event)
@freeze_time(expected_time) @freeze_time(expected_time)
def test_server_track_with_middleware_and_google_analytics_cookie(self):
middleware = TrackMiddleware()
request = self.request_factory.get(self.path_with_course)
request.COOKIES['_ga'] = 'GA1.2.1033501218.1368477899'
middleware.process_request(request)
# The middleware emits an event, reset the mock to ignore it since we aren't testing that feature.
self.mock_tracker.reset_mock()
try:
views.server_track(request, str(sentinel.event_type), '{}')
expected_event = {
'username': 'anonymous',
'ip': '127.0.0.1',
'event_source': 'server',
'event_type': str(sentinel.event_type),
'event': '{}',
'agent': '',
'page': None,
'time': expected_time,
'host': 'testserver',
'context': {
'user_id': '',
'course_id': u'foo/bar/baz',
'org_id': 'foo',
'path': u'/courses/foo/bar/baz/xmod/'
},
}
finally:
middleware.process_response(request, None)
self.mock_tracker.send.assert_called_once_with(expected_event)
@freeze_time(expected_time)
def test_server_track_with_no_request(self): def test_server_track_with_no_request(self):
request = None request = None
views.server_track(request, str(sentinel.event_type), '{}') views.server_track(request, str(sentinel.event_type), '{}')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment