Merge pull request #19206 from edx/brian/segment-addpage

Provide more context to calls to Segment.
This commit is contained in:
brianhw
2018-11-19 15:29:52 -05:00
committed by GitHub
23 changed files with 322 additions and 207 deletions

View File

@@ -21,7 +21,6 @@ from functools import total_ordering
from importlib import import_module
from urllib import urlencode
import analytics
from config_models.models import ConfigurationModel
from django.apps import apps
from django.conf import settings
@@ -66,7 +65,7 @@ from openedx.core.djangoapps.content.course_overviews.models import CourseOvervi
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.xmodule_django.models import NoneToEmptyManager
from openedx.core.djangolib.model_mixins import DeletableByUserValue
from track import contexts
from track import contexts, segment
from util.milestones_helpers import is_entrance_exams_enabled
from util.model_utils import emit_field_changed_events, get_changed_fields_dict
from util.query import use_read_replica_if_available
@@ -739,7 +738,7 @@ class Registration(models.Model):
has_segment_key = getattr(settings, 'LMS_SEGMENT_KEY', None)
has_mailchimp_id = hasattr(settings, 'MAILCHIMP_NEW_USER_LIST_ID')
if has_segment_key and has_mailchimp_id:
identity_args = [
segment.identify(
self.user.id, # pylint: disable=no-member
{
'email': self.user.email,
@@ -751,8 +750,7 @@ class Registration(models.Model):
"listId": settings.MAILCHIMP_NEW_USER_LIST_ID
}
}
]
analytics.identify(*identity_args)
)
class PendingNameChange(DeletableByUserValue, models.Model):
@@ -1429,25 +1427,17 @@ class CourseEnrollment(models.Model):
'course_id': text_type(self.course_id),
'mode': self.mode,
}
segment_properties = {
'category': 'conversion',
'label': text_type(self.course_id),
'org': self.course_id.org,
'course': self.course_id.course,
'run': self.course_id.run,
'mode': self.mode,
}
with tracker.get_tracker().context(event_name, context):
tracker.emit(event_name, data)
if hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY:
tracking_context = tracker.get_tracker().resolve_context()
analytics.track(self.user_id, event_name, {
'category': 'conversion',
'label': text_type(self.course_id),
'org': self.course_id.org,
'course': self.course_id.course,
'run': self.course_id.run,
'mode': self.mode,
}, context={
'ip': tracking_context.get('ip'),
'Google Analytics': {
'clientId': tracking_context.get('client_id')
}
})
segment.track(self.user_id, event_name, segment_properties)
except: # pylint: disable=bare-except
if event_name and self.course_id:

View File

@@ -72,7 +72,7 @@ class TestActivateAccount(TestCase):
LMS_SEGMENT_KEY="testkey",
MAILCHIMP_NEW_USER_LIST_ID="listid"
)
@patch('student.models.analytics.identify')
@patch('student.models.segment.identify')
def test_activation_with_keys(self, mock_segment_identify):
expected_segment_payload = {
'email': self.email,
@@ -98,16 +98,16 @@ class TestActivateAccount(TestCase):
)
@override_settings(LMS_SEGMENT_KEY="testkey")
@patch('student.models.analytics.identify')
@patch('student.models.segment.identify')
def test_activation_without_mailchimp_key(self, mock_segment_identify):
self.assert_no_tracking(mock_segment_identify)
@override_settings(MAILCHIMP_NEW_USER_LIST_ID="listid")
@patch('student.models.analytics.identify')
@patch('student.models.segment.identify')
def test_activation_without_segment_key(self, mock_segment_identify):
self.assert_no_tracking(mock_segment_identify)
@patch('student.models.analytics.identify')
@patch('student.models.segment.identify')
def test_activation_without_keys(self, mock_segment_identify):
self.assert_no_tracking(mock_segment_identify)

View File

@@ -66,7 +66,6 @@ from collections import OrderedDict
from logging import getLogger
from smtplib import SMTPException
import analytics
from django.conf import settings
from django.contrib.auth.models import User
from django.core.mail.message import EmailMessage
@@ -79,12 +78,12 @@ from social_core.pipeline import partial
from social_core.pipeline.social_auth import associate_by_email
from edxmako.shortcuts import render_to_string
from eventtracking import tracker
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.user_authn import cookies as user_authn_cookies
from lms.djangoapps.verify_student.models import SSOVerification
from lms.djangoapps.verify_student.utils import earliest_allowed_verification_date
from third_party_auth.utils import user_exists
from track import segment
from . import provider
@@ -657,23 +656,12 @@ def login_analytics(strategy, auth_entry, current_partial=None, *args, **kwargs)
elif auth_entry in [AUTH_ENTRY_ACCOUNT_SETTINGS]:
event_name = 'edx.bi.user.account.linked'
if event_name is not None and hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY:
tracking_context = tracker.get_tracker().resolve_context()
analytics.track(
kwargs['user'].id,
event_name,
{
'category': "conversion",
'label': None,
'provider': kwargs['backend'].name
},
context={
'ip': tracking_context.get('ip'),
'Google Analytics': {
'clientId': tracking_context.get('client_id')
}
}
)
if event_name is not None:
segment.track(kwargs['user'].id, event_name, {
'category': "conversion",
'label': None,
'provider': kwargs['backend'].name
})
@partial.partial

View File

@@ -522,13 +522,13 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin):
def test_canceling_authentication_redirects_to_root_when_auth_entry_not_set(self):
self.assert_exception_redirect_looks_correct('/')
def test_full_pipeline_succeeds_for_linking_account(self):
@mock.patch('third_party_auth.pipeline.segment.track')
def test_full_pipeline_succeeds_for_linking_account(self, _mock_segment_track):
# First, create, the request and strategy that store pipeline state,
# configure the backend, and mock out wire traffic.
request, strategy = self.get_request_and_strategy(
auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
pipeline.analytics.track = mock.MagicMock()
request.user = self.create_user_models_for_existing_account(
strategy, 'user@example.com', 'password', self.get_username(), skip_social_auth=True)
@@ -677,13 +677,13 @@ class IntegrationTest(testutil.TestCase, test.TestCase, HelperMixin):
self.assert_account_settings_context_looks_correct(
account_settings_context(request), duplicate=True, linked=True)
def test_full_pipeline_succeeds_for_signing_in_to_existing_active_account(self):
@mock.patch('third_party_auth.pipeline.segment.track')
def test_full_pipeline_succeeds_for_signing_in_to_existing_active_account(self, _mock_segment_track):
# First, create, the request and strategy that store pipeline state,
# configure the backend, and mock out wire traffic.
request, strategy = self.get_request_and_strategy(
auth_entry=pipeline.AUTH_ENTRY_LOGIN, redirect_uri='social:complete')
strategy.request.backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
pipeline.analytics.track = mock.MagicMock()
user = self.create_user_models_for_existing_account(
strategy, 'user@example.com', 'password', self.get_username())
self.assert_social_auth_exists_for_user(user, strategy)

View File

@@ -0,0 +1,64 @@
"""
Wrapper methods for emitting events to Segment directly (rather than through tracking log events).
These take advantage of properties that are extracted from incoming requests by track middleware,
stored in tracking context objects, and extracted here to be passed to Segment as part of context
required by server-side events.
To use, call "from track import segment", then call segment.track() or segment.identify().
"""
from urlparse import urljoin
import analytics
from django.conf import settings
from eventtracking import tracker
def track(user_id, event_name, properties=None, context=None):
"""Wrapper for emitting Segment track event, including augmenting context information from middleware."""
if event_name is not None and hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY:
properties = properties or {}
segment_context = dict(context) if context else {}
tracking_context = tracker.get_tracker().resolve_context()
if 'ip' not in segment_context and 'ip' in tracking_context:
segment_context['ip'] = tracking_context.get('ip')
if ('Google Analytics' not in segment_context or 'clientId' not in segment_context['Google Analytics']) and 'client_id' in tracking_context:
segment_context['Google Analytics'] = {
'clientId': tracking_context.get('client_id')
}
if 'userAgent' not in segment_context and 'agent' in tracking_context:
segment_context['userAgent'] = tracking_context.get('agent')
path = tracking_context.get('path')
referer = tracking_context.get('referer')
page = tracking_context.get('page')
if path and not page:
# Try to put together a url from host and path:
host = tracking_context.get('host')
if host:
page = urljoin("//{host}".format(host=host), path)
if path is not None or referer is not None or page is not None:
if 'page' not in segment_context:
segment_context['page'] = {}
if path is not None and 'path' not in segment_context['page']:
segment_context['page']['path'] = path
if referer is not None and 'referrer' not in segment_context['page']:
segment_context['page']['referrer'] = referer
if page is not None and 'url' not in segment_context['page']:
segment_context['page']['url'] = page
analytics.track(user_id, event_name, properties, segment_context)
def identify(user_id, properties, context=None):
"""Wrapper for emitting Segment identify event."""
if hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY:
segment_context = dict(context) if context else {}
analytics.identify(user_id, properties, segment_context)

View File

@@ -0,0 +1,150 @@
"""Ensure emitted events contain the fields legacy processors expect to find."""
import ddt
from django.test import TestCase
from django.test.utils import override_settings
from eventtracking import tracker
from eventtracking.django import DjangoTracker
from mock import sentinel, patch
from track import segment
@ddt.ddt
class SegmentTrackTestCase(TestCase):
"""Ensure emitted events contain the expected context values."""
def setUp(self):
super(SegmentTrackTestCase, self).setUp()
self.tracker = DjangoTracker()
tracker.register_tracker(self.tracker)
self.properties = {sentinel.key: sentinel.value}
patcher = patch('track.segment.analytics.track')
self.mock_segment_track = patcher.start()
self.addCleanup(patcher.stop)
def test_missing_key(self):
segment.track(sentinel.user_id, sentinel.name, self.properties)
self.assertFalse(self.mock_segment_track.called)
@override_settings(LMS_SEGMENT_KEY=None)
def test_null_key(self):
segment.track(sentinel.user_id, sentinel.name, self.properties)
self.assertFalse(self.mock_segment_track.called)
@override_settings(LMS_SEGMENT_KEY="testkey")
def test_missing_name(self):
segment.track(sentinel.user_id, None, self.properties)
self.assertFalse(self.mock_segment_track.called)
@override_settings(LMS_SEGMENT_KEY="testkey")
def test_track_without_tracking_context(self):
segment.track(sentinel.user_id, sentinel.name, self.properties)
self.assertTrue(self.mock_segment_track.called)
args, kwargs = self.mock_segment_track.call_args
expected_segment_context = {}
self.assertEqual((sentinel.user_id, sentinel.name, self.properties, expected_segment_context), args)
@ddt.unpack
@ddt.data(
({'ip': sentinel.ip}, {'ip': sentinel.provided_ip}, {'ip': sentinel.ip}),
({'agent': sentinel.agent}, {'userAgent': sentinel.provided_agent}, {'userAgent': sentinel.agent}),
({'path': sentinel.path}, {'page': {'path': sentinel.provided_path}}, {'page': {'path': sentinel.path}}),
({'referer': sentinel.referer}, {'page': {'referrer': sentinel.provided_referer}}, {'page': {'referrer': sentinel.referer}}),
({'page': sentinel.page}, {'page': {'url': sentinel.provided_page}}, {'page': {'url': sentinel.page}}),
({'client_id': sentinel.client_id}, {'Google Analytics': {'clientId': sentinel.provided_client_id}}, {'Google Analytics': {'clientId': sentinel.client_id}}),
)
@override_settings(LMS_SEGMENT_KEY="testkey")
def test_track_context_with_stuff(self, tracking_context, provided_context, expected_segment_context):
# Test first with tracking and no provided context.
with self.tracker.context('test', tracking_context):
segment.track(sentinel.user_id, sentinel.name, self.properties)
args, kwargs = self.mock_segment_track.call_args
self.assertEqual((sentinel.user_id, sentinel.name, self.properties, expected_segment_context), args)
# Test with provided context and no tracking context.
segment.track(sentinel.user_id, sentinel.name, self.properties, provided_context)
args, kwargs = self.mock_segment_track.call_args
self.assertEqual((sentinel.user_id, sentinel.name, self.properties, provided_context), args)
# Test with provided context and also tracking context.
with self.tracker.context('test', tracking_context):
segment.track(sentinel.user_id, sentinel.name, self.properties, provided_context)
self.assertTrue(self.mock_segment_track.called)
args, kwargs = self.mock_segment_track.call_args
self.assertEqual((sentinel.user_id, sentinel.name, self.properties, provided_context), args)
@override_settings(LMS_SEGMENT_KEY="testkey")
def test_track_with_standard_context(self):
# Note that 'host' and 'path' will be urlparsed, so must be strings.
tracking_context = {
'accept_language': sentinel.accept_language,
'referer': sentinel.referer,
'username': sentinel.username,
'session': sentinel.session,
'ip': sentinel.ip,
'host': 'hostname',
'agent': sentinel.agent,
'path': '/this/is/a/path',
'user_id': sentinel.user_id,
'course_id': sentinel.course_id,
'org_id': sentinel.org_id,
'client_id': sentinel.client_id,
}
with self.tracker.context('test', tracking_context):
segment.track(sentinel.user_id, sentinel.name, self.properties)
self.assertTrue(self.mock_segment_track.called)
args, kwargs = self.mock_segment_track.call_args
expected_segment_context = {
'ip': sentinel.ip,
'Google Analytics': {
'clientId': sentinel.client_id,
},
'userAgent': sentinel.agent,
'page': {
'path': '/this/is/a/path',
'referrer': sentinel.referer,
'url': '//hostname/this/is/a/path' # Synthesized URL value.
}
}
self.assertEqual((sentinel.user_id, sentinel.name, self.properties, expected_segment_context), args)
class SegmentIdentifyTestCase(TestCase):
"""Ensure emitted events contain the fields legacy processors expect to find."""
def setUp(self):
super(SegmentIdentifyTestCase, self).setUp()
patcher = patch('track.segment.analytics.identify')
self.mock_segment_identify = patcher.start()
self.addCleanup(patcher.stop)
self.properties = {sentinel.key: sentinel.value}
def test_missing_key(self):
segment.identify(sentinel.user_id, self.properties)
self.assertFalse(self.mock_segment_identify.called)
@override_settings(LMS_SEGMENT_KEY=None)
def test_null_key(self):
segment.identify(sentinel.user_id, self.properties)
self.assertFalse(self.mock_segment_identify.called)
@override_settings(LMS_SEGMENT_KEY="testkey")
def test_normal_call(self):
segment.identify(sentinel.user_id, self.properties)
self.assertTrue(self.mock_segment_identify.called)
args, kwargs = self.mock_segment_identify.call_args
self.assertEqual((sentinel.user_id, self.properties, {}), args)
@override_settings(LMS_SEGMENT_KEY="testkey")
def test_call_with_context(self):
provided_context = {sentinel.context_key: sentinel.context_value}
segment.identify(sentinel.user_id, self.properties, provided_context)
self.assertTrue(self.mock_segment_identify.called)
args, kwargs = self.mock_segment_identify.call_args
self.assertEqual((sentinel.user_id, self.properties, provided_context), args)

View File

@@ -38,13 +38,13 @@ class TestCourseGoalsAPI(EventTrackingTestCase, SharedModuleStoreTestCase):
self.apiUrl = reverse('course_goals_api:v0:course_goal-list')
@mock.patch('lms.djangoapps.course_goals.views.update_google_analytics')
@mock.patch('lms.djangoapps.course_goals.views.segment.track')
@override_settings(LMS_SEGMENT_KEY="foobar")
def test_add_valid_goal(self, ga_call):
""" Ensures a correctly formatted post succeeds."""
response = self.post_course_goal(valid=True, goal_key='certify')
self.assertEqual(self.get_event(-1)['name'], EVENT_NAME_ADDED)
ga_call.assert_called_with(EVENT_NAME_ADDED, self.user.id)
ga_call.assert_called_with(self.user.id, EVENT_NAME_ADDED)
self.assertEqual(response.status_code, 201)
current_goals = CourseGoal.objects.filter(user=self.user, course_key=self.course.id)
@@ -68,7 +68,7 @@ class TestCourseGoalsAPI(EventTrackingTestCase, SharedModuleStoreTestCase):
status_code=400
)
@mock.patch('lms.djangoapps.course_goals.views.update_google_analytics')
@mock.patch('lms.djangoapps.course_goals.views.segment.track')
@override_settings(LMS_SEGMENT_KEY="foobar")
def test_update_goal(self, ga_call):
""" Ensures that repeated course goal post events do not create new instances of the goal. """
@@ -77,7 +77,7 @@ class TestCourseGoalsAPI(EventTrackingTestCase, SharedModuleStoreTestCase):
self.post_course_goal(valid=True, goal_key='unsure')
self.assertEqual(self.get_event(-1)['name'], EVENT_NAME_UPDATED)
ga_call.assert_called_with(EVENT_NAME_UPDATED, self.user.id)
ga_call.assert_called_with(self.user.id, EVENT_NAME_UPDATED)
current_goals = CourseGoal.objects.filter(user=self.user, course_key=self.course.id)
self.assertEqual(len(current_goals), 1)
self.assertEqual(current_goals[0].goal_key, 'unsure')

View File

@@ -2,7 +2,6 @@
Course Goals Views - includes REST API
"""
from __future__ import absolute_import
import analytics
from django.contrib.auth import get_user_model
from django.conf import settings
@@ -17,6 +16,8 @@ from rest_framework import permissions, serializers, viewsets, status
from rest_framework.authentication import SessionAuthentication
from rest_framework.response import Response
from track import segment
from .api import get_course_goal_options
from .models import CourseGoal, GOAL_KEY_CHOICES
@@ -110,6 +111,7 @@ class CourseGoalViewSet(viewsets.ModelViewSet):
@receiver(post_save, sender=CourseGoal, dispatch_uid="emit_course_goals_event")
def emit_course_goal_event(sender, instance, **kwargs):
"""Emit events for both tracking logs and for Segment."""
name = 'edx.course.goal.added' if kwargs.get('created', False) else 'edx.course.goal.updated'
tracker.emit(
name,
@@ -117,21 +119,4 @@ def emit_course_goal_event(sender, instance, **kwargs):
'goal_key': instance.goal_key,
}
)
if settings.LMS_SEGMENT_KEY:
update_google_analytics(name, instance.user.id)
def update_google_analytics(name, user_id):
""" Update student course goal for Google Analytics using Segment. """
tracking_context = tracker.get_tracker().resolve_context()
context = {
'ip': tracking_context.get('ip'),
'Google Analytics': {
'clientId': tracking_context.get('client_id')
}
}
analytics.track(
user_id,
name,
context=context
)
segment.track(instance.user.id, name)

View File

@@ -2191,9 +2191,9 @@ class GenerateUserCertTests(ModuleStoreTestCase):
def test_user_with_passing_grade(self, mock_is_course_passed):
# If user has above passing grading then json will return cert generating message and
# status valid code
# mocking xqueue and analytics
# mocking xqueue and Segment analytics
analytics_patcher = patch('courseware.views.views.analytics')
analytics_patcher = patch('courseware.views.views.segment')
mock_tracker = analytics_patcher.start()
self.addCleanup(analytics_patcher.stop)
@@ -2211,11 +2211,6 @@ class GenerateUserCertTests(ModuleStoreTestCase):
'category': 'certificates',
'label': unicode(self.course.id)
},
context={
'ip': '127.0.0.1',
'Google Analytics': {'clientId': None}
}
)
mock_tracker.reset_mock()

View File

@@ -7,7 +7,6 @@ import urllib
from collections import OrderedDict, namedtuple
from datetime import datetime
import analytics
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import AnonymousUser, User
@@ -98,6 +97,7 @@ from openedx.features.enterprise_support.api import data_sharing_consent_require
from openedx.features.journals.api import get_journals_context
from shoppingcart.utils import is_shopping_cart_enabled
from student.models import CourseEnrollment, UserTestGroup
from track import segment
from util.cache import cache, cache_if_anonymous
from util.db import outer_atomic
from util.milestones_helpers import get_prerequisite_courses_display
@@ -1429,24 +1429,11 @@ def _track_successful_certificate_generation(user_id, course_id):
None
"""
if settings.LMS_SEGMENT_KEY:
event_name = 'edx.bi.user.certificate.generate'
tracking_context = tracker.get_tracker().resolve_context()
analytics.track(
user_id,
event_name,
{
'category': 'certificates',
'label': text_type(course_id)
},
context={
'ip': tracking_context.get('ip'),
'Google Analytics': {
'clientId': tracking_context.get('client_id')
}
}
)
event_name = 'edx.bi.user.certificate.generate'
segment.track(user_id, event_name, {
'category': 'certificates',
'label': text_type(course_id)
})
@require_http_methods(["GET", "POST"])

View File

@@ -11,7 +11,6 @@ from datetime import datetime, timedelta
from decimal import Decimal
from io import BytesIO
import analytics
import pytz
from boto.exception import BotoServerError # this is a super-class of SESError and catches connection errors
from config_models.models import ConfigurationModel
@@ -37,9 +36,11 @@ from courseware.courses import get_course_by_id
from edxmako.shortcuts import render_to_string
from eventtracking import tracker
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangolib.markup import HTML, Text
from shoppingcart.pdf import PDFInvoice
from student.models import CourseEnrollment, EnrollStatusChange
from student.signals import UNENROLL_DONE
from track import segment
from util.query import use_read_replica_if_available
from xmodule.modulestore.django import modulestore
@@ -520,19 +521,12 @@ class Order(models.Model):
"""
try:
if settings.LMS_SEGMENT_KEY:
tracking_context = tracker.get_tracker().resolve_context()
analytics.track(self.user.id, event_name, {
'orderId': self.id,
'total': str(self.total_cost),
'currency': self.currency,
'products': [item.analytics_data() for item in orderitems]
}, context={
'ip': tracking_context.get('ip'),
'Google Analytics': {
'clientId': tracking_context.get('client_id')
}
})
segment.track(self.user.id, event_name, {
'orderId': self.id,
'total': str(self.total_cost),
'currency': self.currency,
'products': [item.analytics_data() for item in orderitems]
})
except Exception: # pylint: disable=broad-except
# Capturing all exceptions thrown while tracking analytics events. We do not want
@@ -1578,7 +1572,7 @@ class PaidCourseRegistration(OrderItem):
item.unit_cost = cost
item.list_price = cost
item.line_desc = _(u'Registration for Course: {course_name}').format(
course_name=course.display_name_with_default_escaped)
course_name=course.display_name_with_default)
item.currency = currency
order.currency = currency
item.report_comments = item.csv_report_comments
@@ -1618,12 +1612,12 @@ class PaidCourseRegistration(OrderItem):
Generates instructions when the user has purchased a PaidCourseRegistration.
Basically tells the user to visit the dashboard to see their new classes
"""
notification = _(
notification = Text(_(
u"Please visit your {link_start}dashboard{link_end} "
u"to see your new course."
).format(
link_start=u'<a href="{url}">'.format(url=reverse('dashboard')),
link_end=u'</a>',
)).format(
link_start=HTML(u'<a href="{url}">').format(url=reverse('dashboard')),
link_end=HTML(u'</a>'),
)
return self.pk_with_subclass, set([notification])
@@ -1761,7 +1755,7 @@ class CourseRegCodeItem(OrderItem):
item.list_price = cost
item.qty = qty
item.line_desc = _(u'Enrollment codes for Course: {course_name}').format(
course_name=course.display_name_with_default_escaped)
course_name=course.display_name_with_default)
item.currency = currency
order.currency = currency
item.report_comments = item.csv_report_comments

View File

@@ -81,7 +81,7 @@ class OrderTest(ModuleStoreTestCase):
self.cost = 40
# Add mock tracker for event testing.
patcher = patch('shoppingcart.models.analytics')
patcher = patch('shoppingcart.models.segment')
self.mock_tracker = patcher.start()
self.addCleanup(patcher.stop)
@@ -288,7 +288,6 @@ class OrderTest(ModuleStoreTestCase):
}
]
},
context={'ip': None, 'Google Analytics': {'clientId': None}}
)
def test_purchase_item_failure(self):
@@ -862,7 +861,7 @@ class CertificateItemTest(ModuleStoreTestCase):
self.mock_tracker = patcher.start()
self.addCleanup(patcher.stop)
analytics_patcher = patch('shoppingcart.models.analytics')
analytics_patcher = patch('shoppingcart.models.segment')
self.mock_analytics_tracker = analytics_patcher.start()
self.addCleanup(analytics_patcher.stop)
@@ -888,7 +887,6 @@ class CertificateItemTest(ModuleStoreTestCase):
}
]
},
context={'ip': None, 'Google Analytics': {'clientId': None}}
)
def test_existing_enrollment(self):
@@ -912,7 +910,7 @@ class CertificateItemTest(ModuleStoreTestCase):
@override_settings(LMS_SEGMENT_KEY="foobar")
@patch.dict(settings.FEATURES, {'STORE_BILLING_INFO': True})
@patch('lms.djangoapps.course_goals.views.update_google_analytics', Mock(return_value=True))
@patch('lms.djangoapps.course_goals.views.segment.track', Mock(return_value=True))
@patch('student.models.CourseEnrollment.refund_cutoff_date')
def test_refund_cert_callback_no_expiration(self, cutoff_date):
# When there is no expiration date on a verified mode, the user can always get a refund
@@ -951,7 +949,7 @@ class CertificateItemTest(ModuleStoreTestCase):
@override_settings(LMS_SEGMENT_KEY="foobar")
@patch.dict(settings.FEATURES, {'STORE_BILLING_INFO': True})
@patch('lms.djangoapps.course_goals.views.update_google_analytics', Mock(return_value=True))
@patch('lms.djangoapps.course_goals.views.segment.track', Mock(return_value=True))
@patch('student.models.CourseEnrollment.refund_cutoff_date')
def test_refund_cert_callback_before_expiration(self, cutoff_date):
# If the expiration date has not yet passed on a verified mode, the user can be refunded

View File

@@ -8,7 +8,6 @@ import json
import logging
import urllib
import analytics
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.staticfiles.storage import staticfiles_storage
@@ -49,6 +48,7 @@ from openedx.core.lib.log_utils import audit_log
from shoppingcart.models import CertificateItem, Order
from shoppingcart.processors import get_purchase_endpoint, get_signed_purchase_params
from student.models import CourseEnrollment
from track import segment
from util.db import outer_atomic
from util.json_request import JsonResponse
from xmodule.modulestore.django import modulestore
@@ -913,7 +913,7 @@ class SubmitPhotosView(View):
return response
# Submit the attempt
attempt = self._submit_attempt(request.user, face_image, photo_id_image, initial_verification)
self._submit_attempt(request.user, face_image, photo_id_image, initial_verification)
self._fire_event(request.user, "edx.bi.verify.submitted", {"category": "verification"})
self._send_confirmation_email(request.user)
@@ -1096,15 +1096,7 @@ class SubmitPhotosView(View):
Returns: None
"""
if settings.LMS_SEGMENT_KEY:
tracking_context = tracker.get_tracker().resolve_context()
context = {
'ip': tracking_context.get('ip'),
'Google Analytics': {
'clientId': tracking_context.get('client_id')
}
}
analytics.track(user.id, event_name, parameters, context=context)
segment.track(user.id, event_name, parameters)
@require_POST

View File

@@ -2,7 +2,6 @@ import datetime
import logging
import random
import analytics
from django.db.models.signals import post_save
from django.dispatch import receiver
@@ -17,6 +16,7 @@ from openedx.core.djangoapps.schedules.models import ScheduleExperience
from openedx.core.djangoapps.schedules.content_highlights import course_has_highlights
from openedx.core.djangoapps.theming.helpers import get_current_site
from student.models import CourseEnrollment
from track import segment
from .config import CREATE_SCHEDULE_WAFFLE_FLAG
from .models import Schedule, ScheduleConfig
from .tasks import update_course_schedules
@@ -53,7 +53,7 @@ def create_schedule(sender, **kwargs): # pylint: disable=unused-argument
))
def update_schedules_on_course_start_changed(sender, updated_course_overview, previous_start_date, **kwargs):
def update_schedules_on_course_start_changed(sender, updated_course_overview, previous_start_date, **kwargs): # pylint: disable=unused-argument
"""
Updates all course schedules start and upgrade_deadline dates based off of
the new course overview start date.
@@ -136,9 +136,9 @@ def _should_randomly_suppress_schedule_creation(
upgrade_deadline_str = None
if upgrade_deadline:
upgrade_deadline_str = upgrade_deadline.isoformat()
analytics.track(
segment.track(
user_id=enrollment.user.id,
event='edx.bi.schedule.suppressed',
event_name='edx.bi.schedule.suppressed',
properties={
'course_id': unicode(enrollment.course_id),
'experience_type': experience_type,

View File

@@ -97,7 +97,7 @@ class CreateScheduleTests(SharedModuleStoreTestCase):
self.assert_schedule_created(experience_type=ScheduleExperience.EXPERIENCES.course_updates)
@override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True)
@patch('analytics.track')
@patch('openedx.core.djangoapps.schedules.signals.segment.track')
@patch('openedx.core.djangoapps.schedules.signals.random.random', return_value=0.2)
@ddt.data(
(0, True),
@@ -121,7 +121,7 @@ class CreateScheduleTests(SharedModuleStoreTestCase):
else:
self.assert_schedule_not_created()
mock_track.assert_called_once()
assert mock_track.call_args[1].get('event') == 'edx.bi.schedule.suppressed'
assert mock_track.call_args[1].get('event_name') == 'edx.bi.schedule.suppressed'
@patch('openedx.core.djangoapps.schedules.signals.log.exception')
@patch('openedx.core.djangoapps.schedules.signals.Schedule.objects.create')

View File

@@ -2,8 +2,6 @@
API for managing user preferences.
"""
import logging
import analytics
from eventtracking import tracker
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
@@ -17,6 +15,7 @@ from pytz import common_timezones, common_timezones_set, country_timezones
from six import text_type
from student.models import User, UserProfile
from track import segment
from ..errors import (
UserAPIInternalError, UserAPIRequestError, UserNotFound, UserNotAuthorized,
PreferenceValidationError, PreferenceUpdateError, CountryCodeError
@@ -284,21 +283,13 @@ def _track_update_email_opt_in(user_id, organization, opt_in):
"""
event_name = 'edx.bi.user.org_email.opted_in' if opt_in else 'edx.bi.user.org_email.opted_out'
tracking_context = tracker.get_tracker().resolve_context()
analytics.track(
segment.track(
user_id,
event_name,
{
'category': 'communication',
'label': organization
},
context={
'ip': tracking_context.get('ip'),
'Google Analytics': {
'clientId': tracking_context.get('client_id')
}
}
)

View File

@@ -6,7 +6,6 @@ Much of this file was broken out from views.py, previous history can be found th
import logging
import analytics
from django.conf import settings
from django.contrib.auth import authenticate, login as django_login
from django.contrib.auth.decorators import login_required
@@ -19,7 +18,6 @@ from django.views.decorators.http import require_http_methods
from ratelimitbackend.exceptions import RateLimitException
from edxmako.shortcuts import render_to_response
from eventtracking import tracker
from openedx.core.djangoapps.user_authn.cookies import set_logged_in_cookies, refresh_jwt_cookies
from openedx.core.djangoapps.user_authn.exceptions import AuthFailedError
import openedx.core.djangoapps.external_auth.views
@@ -34,6 +32,7 @@ from student.models import (
)
from student.views import send_reactivation_email_for_user
from student.forms import send_password_reset_email_for_user
from track import segment
import third_party_auth
from third_party_auth import pipeline, provider
from util.json_request import JsonResponse
@@ -274,37 +273,28 @@ def _track_user_login(user, request):
"""
Sends a tracking event for a successful login.
"""
if hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY:
tracking_context = tracker.get_tracker().resolve_context()
analytics.identify(
user.id,
{
'email': request.POST['email'],
'username': user.username
},
{
# Disable MailChimp because we don't want to update the user's email
# and username in MailChimp on every page load. We only need to capture
# this data on registration/activation.
'MailChimp': False
}
)
analytics.track(
user.id,
"edx.bi.user.account.authenticated",
{
'category': "conversion",
'label': request.POST.get('course_id'),
'provider': None
},
context={
'ip': tracking_context.get('ip'),
'Google Analytics': {
'clientId': tracking_context.get('client_id')
}
}
)
segment.identify(
user.id,
{
'email': request.POST.get('email'),
'username': user.username
},
{
# Disable MailChimp because we don't want to update the user's email
# and username in MailChimp on every page load. We only need to capture
# this data on registration/activation.
'MailChimp': False
}
)
segment.track(
user.id,
"edx.bi.user.account.authenticated",
{
'category': "conversion",
'label': request.POST.get('course_id'),
'provider': None
},
)
@login_required

View File

@@ -6,7 +6,6 @@ import datetime
import json
import logging
import analytics
import dogstats_wrapper as dog_stats_api
from django.conf import settings
from django.contrib.auth import login as django_login
@@ -17,7 +16,6 @@ from django.db import transaction
from django.dispatch import Signal
from django.utils.translation import get_language
from django.utils.translation import ugettext as _
from eventtracking import tracker
# Note that this lives in LMS, so this dependency should be refactored.
from notification_prefs.views import enable_notifications
from pytz import UTC
@@ -44,6 +42,7 @@ from student.models import (
create_comments_service_user,
)
from student.views import compose_and_send_activation_email
from track import segment
import third_party_auth
from third_party_auth import pipeline, provider
from third_party_auth.saml import SAP_SUCCESSFACTORS_SAML_KEY
@@ -316,7 +315,6 @@ def _link_user_to_third_party_provider(
def _track_user_registration(user, profile, params, third_party_provider):
""" Track the user's registration. """
if hasattr(settings, 'LMS_SEGMENT_KEY') and settings.LMS_SEGMENT_KEY:
tracking_context = tracker.get_tracker().resolve_context()
identity_args = [
user.id,
{
@@ -332,7 +330,7 @@ def _track_user_registration(user, profile, params, third_party_provider):
'country': text_type(profile.country),
}
]
# Provide additional context only if needed.
if hasattr(settings, 'MAILCHIMP_NEW_USER_LIST_ID'):
identity_args.append({
"MailChimp": {
@@ -340,9 +338,8 @@ def _track_user_registration(user, profile, params, third_party_provider):
}
})
analytics.identify(*identity_args)
analytics.track(
segment.identify(*identity_args)
segment.track(
user.id,
"edx.bi.user.account.registered",
{
@@ -350,12 +347,6 @@ def _track_user_registration(user, profile, params, third_party_provider):
'label': params.get('course_id'),
'provider': third_party_provider.name if third_party_provider else None
},
context={
'ip': tracking_context.get('ip'),
'Google Analytics': {
'clientId': tracking_context.get('client_id')
}
}
)

View File

@@ -181,8 +181,8 @@ class TestCreateAccount(SiteMixin, TestCase):
"Microsites not implemented in this environment"
)
@override_settings(LMS_SEGMENT_KEY="testkey")
@mock.patch('openedx.core.djangoapps.user_authn.views.register.analytics.track')
@mock.patch('openedx.core.djangoapps.user_authn.views.register.analytics.identify')
@mock.patch('openedx.core.djangoapps.user_authn.views.register.segment.track')
@mock.patch('openedx.core.djangoapps.user_authn.views.register.segment.identify')
def test_segment_tracking(self, mock_segment_identify, _):
year = datetime.now().year
year_of_birth = year - 14

View File

@@ -23,7 +23,7 @@
# 4. If the package is not needed in production, add it to another file such
# as development.in or testing.in instead.
analytics-python==1.1.0 # Used for Segment analytics
analytics-python==1.2.9 # Used for Segment analytics
attrs # Reduces boilerplate code involving class attributes
Babel==1.3 # Internationalization utilities, used for date formatting in a few places
bleach==2.1.4 # Allowed-list-based HTML sanitizing library that escapes or strips markup and attributes; used for capa and LTI

View File

@@ -40,7 +40,7 @@ git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.1.6#egg=xblock-d
git+https://github.com/open-craft/xblock-poll@add89e14558c30f3c8dc7431e5cd6536fff6d941#egg=xblock-poll==1.5.1
-e common/lib/xmodule
amqp==1.4.9 # via kombu
analytics-python==1.1.0
analytics-python==1.2.9
aniso8601==4.0.1 # via tincan
anyjson==0.3.3 # via kombu
appdirs==1.4.3 # via fs
@@ -133,7 +133,7 @@ edx-user-state-client==1.0.4
edxval==0.1.22
elasticsearch==1.9.0 # via edx-search
enum34==1.1.6
event-tracking==0.2.5
event-tracking==0.2.6
feedparser==5.1.3
firebase-token-generator==1.3.2
fs-s3fs==0.1.8

View File

@@ -44,7 +44,7 @@ git+https://github.com/open-craft/xblock-poll@add89e14558c30f3c8dc7431e5cd6536ff
-e common/lib/xmodule
alabaster==0.7.12 # via sphinx
amqp==1.4.9
analytics-python==1.1.0
analytics-python==1.2.9
aniso8601==4.0.1
anyjson==0.3.3
apipkg==1.5
@@ -154,7 +154,7 @@ edx-user-state-client==1.0.4
edxval==0.1.22
elasticsearch==1.9.0
enum34==1.1.6
event-tracking==0.2.5
event-tracking==0.2.6
execnet==1.5.0
extras==1.0.0
factory_boy==2.8.1

View File

@@ -41,7 +41,7 @@ git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.1.6#egg=xblock-d
git+https://github.com/open-craft/xblock-poll@add89e14558c30f3c8dc7431e5cd6536fff6d941#egg=xblock-poll==1.5.1
-e common/lib/xmodule
amqp==1.4.9
analytics-python==1.1.0
analytics-python==1.2.9
aniso8601==4.0.1
anyjson==0.3.3
apipkg==1.5 # via execnet
@@ -148,7 +148,7 @@ edx-user-state-client==1.0.4
edxval==0.1.22
elasticsearch==1.9.0
enum34==1.1.6
event-tracking==0.2.5
event-tracking==0.2.6
execnet==1.5.0 # via pytest-xdist
extras==1.0.0 # via python-subunit, testtools
factory_boy==2.8.1