MA-272 Updated analytics shim for mobile video events
Shim has been updated to handle currents builds of the android and iOS apps which are not returning the correct semantics/events. Shim now includes the seek event.
This commit is contained in:
@@ -76,6 +76,8 @@ NAME_TO_EVENT_TYPE_MAP = {
|
||||
'edx.video.paused': 'pause_video',
|
||||
'edx.video.stopped': 'stop_video',
|
||||
'edx.video.loaded': 'load_video',
|
||||
'edx.video.position.changed': 'seek_video',
|
||||
'edx.video.seeked': 'seek_video',
|
||||
'edx.video.transcript.shown': 'show_transcript',
|
||||
'edx.video.transcript.hidden': 'hide_transcript',
|
||||
}
|
||||
@@ -101,11 +103,14 @@ class VideoEventProcessor(object):
|
||||
if name not in NAME_TO_EVENT_TYPE_MAP:
|
||||
return
|
||||
|
||||
# Convert edx.video.seeked to edx.video.positiion.changed
|
||||
if name == "edx.video.seeked":
|
||||
event['name'] = "edx.video.position.changed"
|
||||
|
||||
event['event_type'] = NAME_TO_EVENT_TYPE_MAP[name]
|
||||
|
||||
if 'event' not in event:
|
||||
return
|
||||
|
||||
payload = event['event']
|
||||
|
||||
if 'module_id' in payload:
|
||||
@@ -122,13 +127,38 @@ class VideoEventProcessor(object):
|
||||
if 'current_time' in payload:
|
||||
payload['currentTime'] = payload.pop('current_time')
|
||||
|
||||
if 'context' in event:
|
||||
context = event['context']
|
||||
|
||||
# Converts seek_type to seek and skip|slide to onSlideSeek|onSkipSeek
|
||||
if 'seek_type' in payload:
|
||||
seek_type = payload['seek_type']
|
||||
if seek_type == 'slide':
|
||||
payload['type'] = "onSlideSeek"
|
||||
elif seek_type == 'skip':
|
||||
payload['type'] = "onSkipSeek"
|
||||
del payload['seek_type']
|
||||
|
||||
# For the iOS build that is returning a +30 for back skip 30
|
||||
if (
|
||||
context['application']['version'] == "1.0.02" and
|
||||
context['application']['name'] == "edx.mobileapp.iOS"
|
||||
):
|
||||
if 'requested_skip_interval' in payload and 'type' in payload:
|
||||
if (
|
||||
payload['requested_skip_interval'] == 30 and
|
||||
payload['type'] == "onSkipSeek"
|
||||
):
|
||||
payload['requested_skip_interval'] = -30
|
||||
|
||||
# For the Android build that isn't distinguishing between skip/seek
|
||||
if 'requested_skip_interval' in payload:
|
||||
if abs(payload['requested_skip_interval']) != 30:
|
||||
if 'type' in payload:
|
||||
payload['type'] = 'onSlideSeek'
|
||||
|
||||
if 'open_in_browser_url' in context:
|
||||
page, _sep, _tail = context.pop('open_in_browser_url').rpartition('/')
|
||||
event['page'] = page
|
||||
|
||||
event['event'] = json.dumps(payload)
|
||||
|
||||
if 'context' not in event:
|
||||
return
|
||||
|
||||
context = event['context']
|
||||
|
||||
if 'open_in_browser_url' in context:
|
||||
page, _sep, _tail = context.pop('open_in_browser_url').rpartition('/')
|
||||
event['page'] = page
|
||||
|
||||
@@ -316,6 +316,7 @@ class SegmentIOTrackingTestCase(EventTrackingTestCase):
|
||||
('edx.video.paused', 'pause_video'),
|
||||
('edx.video.stopped', 'stop_video'),
|
||||
('edx.video.loaded', 'load_video'),
|
||||
('edx.video.position.changed', 'seek_video'),
|
||||
('edx.video.transcript.shown', 'show_transcript'),
|
||||
('edx.video.transcript.hidden', 'hide_transcript'),
|
||||
)
|
||||
@@ -404,3 +405,127 @@ class SegmentIOTrackingTestCase(EventTrackingTestCase):
|
||||
|
||||
self.assertEqualUnicode(actual_event, expected_event_without_payload)
|
||||
self.assertEqualUnicode(payload, expected_payload)
|
||||
|
||||
@data(
|
||||
# Verify positive slide case. Verify slide to onSlideSeek. Verify edx.video.seeked emitted from iOS v1.0.02 is changed to edx.video.position.changed.
|
||||
(1, 1, "seek_type", "slide", "onSlideSeek", "edx.video.seeked", "edx.video.position.changed", 'edx.mobileapp.iOS', '1.0.02'),
|
||||
# Verify negative slide case. Verify slide to onSlideSeek. Verify edx.video.seeked to edx.video.position.changed.
|
||||
(-2, -2, "seek_type", "slide", "onSlideSeek", "edx.video.seeked", "edx.video.position.changed", 'edx.mobileapp.iOS', '1.0.02'),
|
||||
# Verify +30 is changed to -30 which is incorrectly emitted in iOS v1.0.02. Verify skip to onSkipSeek
|
||||
(30, -30, "seek_type", "skip", "onSkipSeek", "edx.video.position.changed", "edx.video.position.changed", 'edx.mobileapp.iOS', '1.0.02'),
|
||||
# Verify the correct case of -30 is also handled as well. Verify skip to onSkipSeek
|
||||
(-30, -30, "seek_type", "skip", "onSkipSeek", "edx.video.position.changed", "edx.video.position.changed", 'edx.mobileapp.iOS', '1.0.02'),
|
||||
# Verify positive slide case where onSkipSeek is changed to onSlideSkip. Verify edx.video.seeked emitted from Android v1.0.02 is changed to edx.video.position.changed.
|
||||
(1, 1, "type", "onSkipSeek", "onSlideSeek", "edx.video.seeked", "edx.video.position.changed", 'edx.mobileapp.android', '1.0.02'),
|
||||
# Verify positive slide case where onSkipSeek is changed to onSlideSkip. Verify edx.video.seeked emitted from Android v1.0.02 is changed to edx.video.position.changed.
|
||||
(-2, -2, "type", "onSkipSeek", "onSlideSeek", "edx.video.seeked", "edx.video.position.changed", 'edx.mobileapp.android', '1.0.02'),
|
||||
# Verify positive skip case where onSkipSeek is not changed and does not become negative.
|
||||
(30, 30, "type", "onSkipSeek", "onSkipSeek", "edx.video.position.changed", "edx.video.position.changed", 'edx.mobileapp.android', '1.0.02'),
|
||||
# Verify positive skip case where onSkipSeek is not changed.
|
||||
(-30, -30, "type", "onSkipSeek", "onSkipSeek", "edx.video.position.changed", "edx.video.position.changed", 'edx.mobileapp.android', '1.0.02')
|
||||
)
|
||||
@unpack
|
||||
def test_previous_builds(self,
|
||||
requested_skip_interval,
|
||||
expected_skip_interval,
|
||||
seek_type_key,
|
||||
seek_type,
|
||||
expected_seek_type,
|
||||
name,
|
||||
expected_name,
|
||||
platform,
|
||||
version,
|
||||
):
|
||||
"""
|
||||
Test backwards compatibility of previous app builds
|
||||
|
||||
iOS version 1.0.02: Incorrectly emits the skip back 30 seconds as +30
|
||||
instead of -30.
|
||||
Android version 1.0.02: Skip and slide were both being returned as a
|
||||
skip. Skip or slide is determined by checking if the skip time is == -30
|
||||
Additionally, for both of the above mentioned versions, edx.video.seeked
|
||||
was sent instead of edx.video.position.changed
|
||||
"""
|
||||
course_id = 'foo/bar/baz'
|
||||
middleware = TrackMiddleware()
|
||||
input_payload = {
|
||||
"code": "mobile",
|
||||
"new_time": 89.699177437,
|
||||
"old_time": 119.699177437,
|
||||
seek_type_key: seek_type,
|
||||
"requested_skip_interval": requested_skip_interval,
|
||||
'module_id': 'i4x://foo/bar/baz/some_module',
|
||||
}
|
||||
request = self.create_request(
|
||||
data=self.create_segmentio_event_json(
|
||||
name=name,
|
||||
data=input_payload,
|
||||
context={
|
||||
'open_in_browser_url': 'https://testserver/courses/foo/bar/baz/courseware/Week_1/Activity/2',
|
||||
'course_id': course_id,
|
||||
'application': {
|
||||
'name': platform,
|
||||
'version': version,
|
||||
'component': 'videoplayer'
|
||||
}
|
||||
},
|
||||
),
|
||||
content_type='application/json'
|
||||
)
|
||||
User.objects.create(pk=USER_ID, username=str(sentinel.username))
|
||||
|
||||
middleware.process_request(request)
|
||||
try:
|
||||
response = segmentio.segmentio_event(request)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
expected_event_without_payload = {
|
||||
'accept_language': '',
|
||||
'referer': '',
|
||||
'username': str(sentinel.username),
|
||||
'ip': '',
|
||||
'session': '',
|
||||
'event_source': 'mobile',
|
||||
'event_type': "seek_video",
|
||||
'name': expected_name,
|
||||
'agent': str(sentinel.user_agent),
|
||||
'page': 'https://testserver/courses/foo/bar/baz/courseware/Week_1/Activity',
|
||||
'time': datetime.strptime("2014-08-27T16:33:39.215Z", "%Y-%m-%dT%H:%M:%S.%fZ"),
|
||||
'host': 'testserver',
|
||||
'context': {
|
||||
'user_id': USER_ID,
|
||||
'course_id': course_id,
|
||||
'org_id': 'foo',
|
||||
'path': ENDPOINT,
|
||||
'client': {
|
||||
'library': {
|
||||
'name': 'test-app',
|
||||
'version': 'unknown'
|
||||
},
|
||||
'app': {
|
||||
'version': '1.0.1',
|
||||
},
|
||||
},
|
||||
'application': {
|
||||
'name': platform,
|
||||
'version': version,
|
||||
'component': 'videoplayer'
|
||||
},
|
||||
'received_at': datetime.strptime("2014-08-27T16:33:39.100Z", "%Y-%m-%dT%H:%M:%S.%fZ"),
|
||||
},
|
||||
}
|
||||
expected_payload = {
|
||||
"code": "mobile",
|
||||
"new_time": 89.699177437,
|
||||
"old_time": 119.699177437,
|
||||
"type": expected_seek_type,
|
||||
"requested_skip_interval": expected_skip_interval,
|
||||
'id': 'i4x-foo-bar-baz-some_module',
|
||||
}
|
||||
finally:
|
||||
middleware.process_response(request, None)
|
||||
|
||||
actual_event = dict(self.get_event())
|
||||
payload = json.loads(actual_event.pop('event'))
|
||||
self.assertEqualUnicode(actual_event, expected_event_without_payload)
|
||||
self.assertEqualUnicode(payload, expected_payload)
|
||||
|
||||
Reference in New Issue
Block a user