Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
5643101a
Commit
5643101a
authored
Oct 08, 2014
by
Gabe Mulley
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Small changes to the segment.io event handler
parent
03b41032
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
51 additions
and
24 deletions
+51
-24
common/djangoapps/track/views/segmentio.py
+13
-7
common/djangoapps/track/views/tests/test_segmentio.py
+38
-17
No files found.
common/djangoapps/track/views/segmentio.py
View file @
5643101a
...
...
@@ -93,14 +93,19 @@ def track_segmentio_event(request):
# The POST body will contain the JSON encoded event
full_segment_event
=
request
.
json
# We mostly care about the properties
segment_event
=
full_segment_event
.
get
(
'properties'
,
{})
def
logged_failure_response
(
*
args
,
**
kwargs
):
"""Indicate a failure and log information about the event that will aide debugging efforts"""
failed_response
=
failure_response
(
*
args
,
**
kwargs
)
log
.
warning
(
'Unable to process event received from segment.io:
%
s'
,
json
.
dumps
(
full_segment_event
))
return
failed_response
# Selectively listen to particular channels
channel
=
full_segment_event
.
get
(
'channel'
)
# Selectively listen to particular channels, note that the client can set the "event_source" field in the
# "properties" dict to override the channel provided by segment.io. This is necessary because there is a bug in some
# segment.io client libraries that prevented them from sending correct channel fields.
channel
=
segment_event
.
get
(
'event_source'
)
allowed_channels
=
[
c
.
lower
()
for
c
in
getattr
(
settings
,
'TRACKING_SEGMENTIO_ALLOWED_CHANNELS'
,
[])]
if
not
channel
or
channel
.
lower
()
not
in
allowed_channels
:
return
response
(
WARNING_IGNORED_CHANNEL
,
committed
=
False
)
...
...
@@ -111,15 +116,15 @@ def track_segmentio_event(request):
if
not
action
or
action
.
lower
()
not
in
allowed_actions
:
return
response
(
WARNING_IGNORED_ACTION
,
committed
=
False
)
# We mostly care about the properties
segment_event
=
full_segment_event
.
get
(
'properties'
,
{})
context
=
{}
# Start with the context provided by segment.io in the "client" field if it exists
segment_context
=
full_segment_event
.
get
(
'context'
)
if
segment_context
:
context
[
'client'
]
=
segment_context
user_agent
=
segment_context
.
get
(
'userAgent'
,
''
)
else
:
user_agent
=
''
# Overlay any context provided in the properties
context
.
update
(
segment_event
.
get
(
'context'
,
{}))
...
...
@@ -136,7 +141,7 @@ def track_segmentio_event(request):
except
ValueError
:
return
logged_failure_response
(
ERROR_INVALID_USER_ID
)
else
:
context
[
'user_id'
]
=
user
_
id
context
[
'user_id'
]
=
user
.
id
# course_id is expected to be provided in the context when applicable
course_id
=
context
.
get
(
'course_id'
)
...
...
@@ -173,6 +178,7 @@ def track_segmentio_event(request):
event
=
{
"username"
:
user
.
username
,
"event_type"
:
event_type
,
"name"
:
segment_event
.
get
(
'name'
,
''
),
# Will be either "mobile", "browser" or "server". These names happen to be identical to the names we already
# use so no mapping is necessary.
"event_source"
:
channel
,
...
...
@@ -181,7 +187,7 @@ def track_segmentio_event(request):
"context"
:
complete_context
,
"page"
:
segment_event
.
get
(
'page'
),
"host"
:
complete_context
.
get
(
'host'
,
''
),
"agent"
:
''
,
"agent"
:
user_agent
,
"ip"
:
segment_event
.
get
(
'ip'
,
''
),
"event"
:
segment_event
.
get
(
'event'
,
{}),
}
...
...
common/djangoapps/track/views/tests/test_segmentio.py
View file @
5643101a
...
...
@@ -40,7 +40,7 @@ class SegmentIOTrackingTestCase(TestCase):
self
.
mock_tracker
=
patcher
.
start
()
self
.
addCleanup
(
patcher
.
stop
)
def
test_
segmentio_tracking_
get_request
(
self
):
def
test_get_request
(
self
):
request
=
self
.
request_factory
.
get
(
ENDPOINT
)
response
=
segmentio
.
track_segmentio_event
(
request
)
self
.
assertEquals
(
response
.
status_code
,
405
)
...
...
@@ -49,7 +49,7 @@ class SegmentIOTrackingTestCase(TestCase):
@override_settings
(
TRACKING_SEGMENTIO_WEBHOOK_SECRET
=
None
)
def
test_
segmentio_tracking_
no_secret_config
(
self
):
def
test_no_secret_config
(
self
):
request
=
self
.
request_factory
.
post
(
ENDPOINT
)
response
=
segmentio
.
track_segmentio_event
(
request
)
self
.
assert_segmentio_uncommitted_response
(
response
,
segmentio
.
ERROR_UNAUTHORIZED
,
401
)
...
...
@@ -61,12 +61,12 @@ class SegmentIOTrackingTestCase(TestCase):
self
.
assertEquals
(
parsed_content
,
{
'committed'
:
False
,
'message'
:
expected_message
})
self
.
assertFalse
(
self
.
mock_tracker
.
send
.
called
)
# pylint: disable=maybe-no-member
def
test_
segmentio_tracking_
no_secret_provided
(
self
):
def
test_no_secret_provided
(
self
):
request
=
self
.
request_factory
.
post
(
ENDPOINT
)
response
=
segmentio
.
track_segmentio_event
(
request
)
self
.
assert_segmentio_uncommitted_response
(
response
,
segmentio
.
ERROR_UNAUTHORIZED
,
401
)
def
test_se
gmentio_tracking_se
cret_mismatch
(
self
):
def
test_secret_mismatch
(
self
):
request
=
self
.
create_request
(
key
=
'y'
)
response
=
segmentio
.
track_segmentio_event
(
request
)
self
.
assert_segmentio_uncommitted_response
(
response
,
segmentio
.
ERROR_UNAUTHORIZED
,
401
)
...
...
@@ -93,7 +93,7 @@ class SegmentIOTrackingTestCase(TestCase):
@data
(
'server'
,
'browser'
,
'Browser'
)
def
test_segmentio_ignore_channels
(
self
,
channel
):
response
=
self
.
post_segmentio_event
(
channel
=
channel
)
response
=
self
.
post_segmentio_event
(
event_source
=
channel
)
self
.
assert_segmentio_uncommitted_response
(
response
,
segmentio
.
WARNING_IGNORED_CHANNEL
,
200
)
def
create_segmentio_event
(
self
,
**
kwargs
):
...
...
@@ -104,17 +104,20 @@ class SegmentIOTrackingTestCase(TestCase):
"event"
:
"Did something"
,
"properties"
:
{
'event_type'
:
kwargs
.
get
(
'event_type'
,
''
),
'event_source'
:
kwargs
.
get
(
'event_source'
,
'mobile'
),
'event'
:
kwargs
.
get
(
'event'
,
{}),
'context'
:
{
'course_id'
:
kwargs
.
get
(
'course_id'
)
or
''
,
}
},
'name'
:
str
(
sentinel
.
name
),
},
"channel"
:
kwargs
.
get
(
'channel'
,
'mobile'
),
"context"
:
{
"library"
:
{
"name"
:
"unknown"
,
"version"
:
"unknown"
}
},
'userAgent'
:
str
(
sentinel
.
user_agent
),
},
"receivedAt"
:
"2014-08-27T16:33:39.100Z"
,
"timestamp"
:
"2014-08-27T16:33:39.215Z"
,
...
...
@@ -129,22 +132,23 @@ class SegmentIOTrackingTestCase(TestCase):
},
"action"
:
action
}
return
sample_event
def
create_segmentio_event_json
(
self
,
**
kwargs
):
"""Return a json string containing a fake segment.io event"""
return
json
.
dumps
(
self
.
create_segmentio_event
(
**
kwargs
))
def
test_
segmentio_tracking_
no_user_for_user_id
(
self
):
def
test_no_user_for_user_id
(
self
):
response
=
self
.
post_segmentio_event
(
user_id
=
40
)
self
.
assert_segmentio_uncommitted_response
(
response
,
segmentio
.
ERROR_USER_NOT_EXIST
,
400
)
def
test_
segmentio_tracking_
invalid_user_id
(
self
):
def
test_invalid_user_id
(
self
):
response
=
self
.
post_segmentio_event
(
user_id
=
'foobar'
)
self
.
assert_segmentio_uncommitted_response
(
response
,
segmentio
.
ERROR_INVALID_USER_ID
,
400
)
@data
(
'foo/bar/baz'
,
'course-v1:foo+bar+baz'
)
def
test_s
egmentio_tracking
(
self
,
course_id
):
def
test_s
uccess
(
self
,
course_id
):
middleware
=
TrackMiddleware
()
request
=
self
.
create_request
(
...
...
@@ -165,8 +169,9 @@ class SegmentIOTrackingTestCase(TestCase):
'ip'
:
''
,
'event_source'
:
'mobile'
,
'event_type'
:
str
(
sentinel
.
event_type
),
'name'
:
str
(
sentinel
.
name
),
'event'
:
{
'foo'
:
'bar'
},
'agent'
:
''
,
'agent'
:
str
(
sentinel
.
user_agent
)
,
'page'
:
None
,
'time'
:
datetime
.
strptime
(
"2014-08-27T16:33:39.215Z"
,
"
%
Y-
%
m-
%
dT
%
H:
%
M:
%
S.
%
fZ"
),
'host'
:
'testserver'
,
...
...
@@ -179,7 +184,8 @@ class SegmentIOTrackingTestCase(TestCase):
'library'
:
{
'name'
:
'unknown'
,
'version'
:
'unknown'
}
},
'userAgent'
:
str
(
sentinel
.
user_agent
)
},
'received_at'
:
datetime
.
strptime
(
"2014-08-27T16:33:39.100Z"
,
"
%
Y-
%
m-
%
dT
%
H:
%
M:
%
S.
%
fZ"
),
},
...
...
@@ -189,7 +195,7 @@ class SegmentIOTrackingTestCase(TestCase):
self
.
mock_tracker
.
send
.
assert_called_once_with
(
expected_event
)
# pylint: disable=maybe-no-member
def
test_
segmentio_tracking_
invalid_course_id
(
self
):
def
test_invalid_course_id
(
self
):
request
=
self
.
create_request
(
data
=
self
.
create_segmentio_event_json
(
course_id
=
'invalid'
),
content_type
=
'application/json'
...
...
@@ -199,9 +205,9 @@ class SegmentIOTrackingTestCase(TestCase):
self
.
assertEquals
(
response
.
status_code
,
200
)
self
.
assertTrue
(
self
.
mock_tracker
.
send
.
called
)
# pylint: disable=maybe-no-member
def
test_
segmentio_tracking_
missing_event_type
(
self
):
def
test_missing_event_type
(
self
):
sample_event_raw
=
self
.
create_segmentio_event
()
sample_event_raw
[
'properties'
]
=
{}
del
sample_event_raw
[
'properties'
][
'event_type'
]
request
=
self
.
create_request
(
data
=
json
.
dumps
(
sample_event_raw
),
content_type
=
'application/json'
...
...
@@ -211,7 +217,7 @@ class SegmentIOTrackingTestCase(TestCase):
response
=
segmentio
.
track_segmentio_event
(
request
)
self
.
assert_segmentio_uncommitted_response
(
response
,
segmentio
.
ERROR_MISSING_EVENT_TYPE
,
400
)
def
test_
segmentio_tracking_
missing_timestamp
(
self
):
def
test_missing_timestamp
(
self
):
sample_event_raw
=
self
.
create_event_without_fields
(
'timestamp'
)
request
=
self
.
create_request
(
data
=
json
.
dumps
(
sample_event_raw
),
...
...
@@ -232,7 +238,7 @@ class SegmentIOTrackingTestCase(TestCase):
return
event
def
test_
segmentio_tracking_
missing_received_at
(
self
):
def
test_missing_received_at
(
self
):
sample_event_raw
=
self
.
create_event_without_fields
(
'receivedAt'
)
request
=
self
.
create_request
(
data
=
json
.
dumps
(
sample_event_raw
),
...
...
@@ -242,3 +248,18 @@ class SegmentIOTrackingTestCase(TestCase):
response
=
segmentio
.
track_segmentio_event
(
request
)
self
.
assert_segmentio_uncommitted_response
(
response
,
segmentio
.
ERROR_MISSING_RECEIVED_AT
,
400
)
def
test_string_user_id
(
self
):
User
.
objects
.
create
(
pk
=
USER_ID
,
username
=
str
(
sentinel
.
username
))
response
=
self
.
post_segmentio_event
(
user_id
=
str
(
USER_ID
))
result
=
self
.
assert_segmentio_committed_response
(
response
)
self
.
assertEquals
(
result
[
'context'
][
'user_id'
],
USER_ID
)
def
assert_segmentio_committed_response
(
self
,
response
):
"""Assert that an event was emitted"""
self
.
assertEquals
(
response
.
status_code
,
200
)
parsed_content
=
json
.
loads
(
response
.
content
)
self
.
assertEquals
(
parsed_content
,
{
'committed'
:
True
})
self
.
assertTrue
(
self
.
mock_tracker
.
send
.
called
)
# pylint: disable=maybe-no-member
return
self
.
mock_tracker
.
send
.
mock_calls
[
0
][
1
][
0
]
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment