Files
edx-platform/common/djangoapps/track/views/__init__.py
Ken Clary 8c8450f6dc feat: add explicit courserun_key parameter to /event endpoint
We add 'courserun_key' (aka "course_id" though that's technically a
misnomer) as an optional parameter to the /event endpoint url. If it
is not present, it will still be parsed out of the url, if the url is
of the right format.

Additionally, Logger.log() in js adds this parameter to its /event
call, pulling it from the $$course_id global.

This provides opportunity for MFEs to (separately) provide the key
without concern about url parsing.

TNL-7752
2021-08-11 14:40:25 -04:00

165 lines
5.6 KiB
Python

# lint-amnesty, pylint: disable=missing-module-docstring
import json
import six
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
from django.http import HttpResponse
from eventtracking import tracker as eventtracker
from ipware.ip import get_client_ip
from common.djangoapps.track import contexts, shim, tracker
def _get_request_header(request, header_name, default=''):
"""Helper method to get header values from a request's META dict, if present."""
if request is not None and hasattr(request, 'META') and header_name in request.META:
return request.META[header_name]
else:
return default
def _get_request_ip(request, default=''):
"""Helper method to get IP from a request's META dict, if present."""
if request is not None and hasattr(request, 'META'):
return get_client_ip(request)[0]
else:
return default
def _get_request_value(request, value_name, default=''):
"""Helper method to get header values from a request's GET/POST dict, if present."""
if request is not None:
if request.method == 'GET':
return request.GET.get(value_name, default)
elif request.method == 'POST':
return request.POST.get(value_name, default)
return default
def _get_course_context(page):
"""Return the course context from the provided page.
If the context has no/empty course_id, return empty context
"""
course_context = contexts.course_context_from_url(page)
if course_context.get('course_id', '') == '':
course_context = {}
return course_context
def _add_user_id_for_username(data):
"""
If data contains a username, adds the corresponding user_id to the data.
In certain use cases, the caller may have the username and not the
user_id. This enables us to standardize on user_id in event data,
even when the caller only has access to the username.
"""
if data and ('username' in data) and ('user_id' not in data):
try:
user = User.objects.get(username=data.get('username'))
data['user_id'] = user.id
except User.DoesNotExist:
pass
def user_track(request):
"""
Log when POST call to "event" URL is made by a user.
GET or POST call should provide "event_type", "event", and "page" arguments. It may optionally provide
a "courserun_key" argument (otherwise may be extracted from the page).
"""
try:
username = request.user.username
except: # lint-amnesty, pylint: disable=bare-except
username = "anonymous"
name = _get_request_value(request, 'event_type')
data = _get_request_value(request, 'event', {})
course_id_string = _get_request_value(request, 'courserun_key', None)
page = _get_request_value(request, 'page')
if isinstance(data, str) and len(data) > 0:
try:
data = json.loads(data)
_add_user_id_for_username(data)
except ValueError:
pass
context_override = contexts.course_context_from_url(page, course_id_string)
context_override['username'] = username
context_override['event_source'] = 'browser'
context_override['page'] = page
with eventtracker.get_tracker().context('edx.course.browser', context_override):
eventtracker.emit(name=name, data=data)
return HttpResponse('success')
def server_track(request, event_type, event, page=None):
"""
Log events related to server requests.
Handle the situation where the request may be NULL, as may happen with management commands.
"""
if event_type.startswith("/event_logs") and request.user.is_staff:
return # don't log
try:
username = request.user.username
except: # lint-amnesty, pylint: disable=bare-except
username = "anonymous"
context_override = _get_course_context(page)
context_override.update({
'username': username,
'event_source': 'server',
'page': page
})
event_tracker = eventtracker.get_tracker()
with event_tracker.context('edx.course.server', context_override):
eventtracker.emit(name=event_type, data=event)
def task_track(request_info, task_info, event_type, event, page=None):
"""
Logs tracking information for events occuring within celery tasks.
The `event_type` is a string naming the particular event being logged,
while `event` is a dict containing whatever additional contextual information
is desired.
The `request_info` is a dict containing information about the original
task request. Relevant keys are `username`, `ip`, `agent`, and `host`.
While the dict is required, the values in it are not, so that {} can be
passed in.
In addition, a `task_info` dict provides more information about the current
task, to be stored with the `event` dict. This may also be an empty dict.
The `page` parameter is optional, and allows the name of the page to
be provided.
"""
# supplement event information with additional information
# about the task in which it is running.
data = dict(event, **task_info)
context_override = contexts.course_context_from_url(page)
context_override.update({
'username': request_info.get('username', 'unknown'),
'ip': request_info.get('ip', 'unknown'),
'agent': request_info.get('agent', 'unknown'),
'host': request_info.get('host', 'unknown'),
'event_source': 'task',
'page': page,
})
with eventtracker.get_tracker().context('edx.course.task', context_override):
eventtracker.emit(name=event_type, data=data)