Commit d499b224 by Jonathan Piacenti

Analytics events for badges.

parent f7862c1d
......@@ -4,9 +4,10 @@ BadgeHandler object-- used to award Badges to users who have completed courses.
import hashlib
import logging
import mimetypes
from eventtracking import tracker
import requests
from django.template.defaultfilters import slugify
from django.utils.translation import ugettext as _
import requests
from django.conf import settings
from django.core.urlresolvers import reverse
......@@ -120,6 +121,13 @@ class BadgeHandler(object):
course_mode=mode,
)
def site_prefix(self):
"""
Get the prefix for the site URL-- protocol and server name.
"""
scheme = u"https" if settings.HTTPS == "on" else u"http"
return u'{}://{}'.format(scheme, settings.SITE_NAME)
def create_badge(self, mode):
"""
Create the badge spec for a course's mode.
......@@ -135,27 +143,48 @@ class BadgeHandler(object):
)
files = {'image': (image.name, image, content_type)}
about_path = reverse('about_course', kwargs={'course_id': unicode(self.course_key)})
scheme = u"https" if settings.HTTPS == "on" else u"http"
data = {
'name': course.display_name,
'criteria': u'{}://{}{}'.format(scheme, settings.SITE_NAME, about_path),
'criteria': u'{}{}'.format(self.site_prefix(), about_path),
'slug': self.course_slug(mode),
'description': self.badge_description(course, mode)
}
result = requests.post(self.badge_create_url, headers=self.get_headers(), data=data, files=files)
self.log_if_raised(result, data)
def send_assertion_created_event(self, user, assertion):
"""
Send an analytics event to record the creation of a badge assertion.
"""
tracker.emit(
'edx.badges.assertion.created', {
'user_id': user.id,
'course_id': unicode(self.course_key),
'enrollment_mode': assertion.mode,
'assertion_id': assertion.id,
'assertion_image_url': assertion.data['image'],
'assertion_json_url': assertion.data['json']['id'],
'issuer': assertion.data['issuer'],
}
)
def create_assertion(self, user, mode):
"""
Register an assertion with the Badgr server for a particular user in a particular course mode for
this course.
"""
data = {'email': user.email}
data = {
'email': user.email,
'evidence': self.site_prefix() + reverse(
'cert_html_view', kwargs={'user_id': user.id, 'course_id': unicode(self.course_key)}
) + '?evidence_visit=1'
}
response = requests.post(self.assertion_url(mode), headers=self.get_headers(), data=data)
self.log_if_raised(response, data)
assertion, __ = BadgeAssertion.objects.get_or_create(course_id=self.course_key, user=user)
assertion, __ = BadgeAssertion.objects.get_or_create(course_id=self.course_key, user=user, mode=mode)
assertion.data = response.json()
assertion.save()
self.send_assertion_created_event(user, assertion)
def award(self, user):
"""
......
......@@ -595,6 +595,7 @@ class BadgeAssertion(models.Model):
"""
Get the image for this assertion.
"""
return self.data['image']
@property
......@@ -608,7 +609,7 @@ class BadgeAssertion(models.Model):
"""
Meta information for Django's construction of the model.
"""
unique_together = (('course_id', 'user'),)
unique_together = (('course_id', 'user', 'mode'),)
def validate_badge_image(image):
......
......@@ -7,6 +7,7 @@ from django.db.models.fields.files import ImageFieldFile
from lazy.lazy import lazy
from mock import patch, Mock, call
from certificates.models import BadgeAssertion, BadgeImageConfiguration
from openedx.core.lib.tests.assertions.events import assert_event_matches
from track.tests import EventTrackingTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from certificates.badge_handler import BadgeHandler
......@@ -155,7 +156,7 @@ class BadgeHandlerTestCase(ModuleStoreTestCase, EventTrackingTestCase):
self.assertTrue(BadgeHandler.badges['edxcourse_testtest_run_honor_fc5519b'])
@patch('requests.post')
def test_create_assertion(self, post):
def test_badge_creation_event(self, post):
result = {
'json': {'id': 'http://www.example.com/example'},
'image': 'http://www.example.com/example.png',
......@@ -174,7 +175,22 @@ class BadgeHandlerTestCase(ModuleStoreTestCase, EventTrackingTestCase):
'edxcourse_testtest_run_honor_fc5519b/assertions'
)
self.check_headers(kwargs['headers'])
self.assertEqual(kwargs['data'], {'email': 'example@example.com'})
badge = BadgeAssertion.objects.get(user=self.user, course_id=self.course.location.course_key)
self.assertEqual(badge.data, result)
self.assertEqual(badge.image_url, 'http://www.example.com/example.png')
assertion = BadgeAssertion.objects.get(user=self.user, course_id=self.course.location.course_key)
self.assertEqual(assertion.data, result)
self.assertEqual(assertion.image_url, 'http://www.example.com/example.png')
self.assertEqual(kwargs['data'], {
'email': 'example@example.com',
'evidence': 'https://edx.org/certificates/user/2/course/edX/course_test/test_run?evidence_visit=1'
})
assert_event_matches({
'name': 'edx.badges.assertion.created',
'data': {
'user_id': self.user.id,
'course_id': unicode(self.course.location.course_key),
'enrollment_mode': 'honor',
'assertion_id': assertion.id,
'assertion_image_url': 'http://www.example.com/example.png',
'assertion_json_url': 'http://www.example.com/example',
'issuer': 'https://example.com/v1/issuer/issuers/test-issuer',
}
}, self.get_event())
......@@ -13,16 +13,20 @@ from django.test.client import Client
from django.test.utils import override_settings
from opaque_keys.edx.locator import CourseLocator
from openedx.core.lib.tests.assertions.events import assert_event_matches
from student.tests.factories import UserFactory
from track.tests import EventTrackingTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from certificates.api import get_certificate_url
from certificates.models import ExampleCertificateSet, ExampleCertificate, GeneratedCertificate
from certificates.models import ExampleCertificateSet, ExampleCertificate, GeneratedCertificate, BadgeAssertion
from certificates.tests.factories import (
CertificateHtmlViewConfigurationFactory,
LinkedInAddToProfileConfigurationFactory
LinkedInAddToProfileConfigurationFactory,
BadgeAssertionFactory,
)
from lms import urls
FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
......@@ -174,7 +178,7 @@ class UpdateExampleCertificateViewTest(TestCase):
@attr('shard_1')
class CertificatesViewsTests(ModuleStoreTestCase):
class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
"""
Tests for the manual refund page
"""
......@@ -416,3 +420,88 @@ class CertificatesViewsTests(ModuleStoreTestCase):
)
response = self.client.get(test_url)
self.assertIn("Invalid Certificate", response.content)
def test_evidence_event_sent(self):
test_url = get_certificate_url(user_id=self.user.id, course_id=self.course_id) + '?evidence_visit=1'
self.recreate_tracker()
assertion = BadgeAssertion(
user=self.user, course_id=self.course_id, mode='honor',
data={
'image': 'http://www.example.com/image.png',
'json': {'id': 'http://www.example.com/assertion.json'},
'issuer': 'http://www.example.com/issuer.json',
}
)
assertion.save()
response = self.client.get(test_url)
self.assertEqual(response.status_code, 200)
assert_event_matches(
{
'name': 'edx.badges.assertion.evidence_visit',
'data': {
'course_id': 'testorg/run1/refundable_course',
# pylint: disable=no-member
'assertion_id': assertion.id,
'assertion_json_url': 'http://www.example.com/assertion.json',
'assertion_image_url': 'http://www.example.com/image.png',
'user_id': self.user.id,
'issuer': 'http://www.example.com/issuer.json',
'enrollment_mode': 'honor',
},
},
self.get_event()
)
class TrackShareRedirectTest(ModuleStoreTestCase, EventTrackingTestCase):
"""
Verifies the badge image share event is sent out.
"""
def setUp(self):
super(TrackShareRedirectTest, self).setUp()
self.client = Client()
self.course = CourseFactory.create(
org='testorg', number='run1', display_name='trackable course'
)
self.assertion = BadgeAssertionFactory(
user=self.user, course_id=self.course.id, data={
'image': 'http://www.example.com/image.png',
'json': {'id': 'http://www.example.com/assertion.json'},
'issuer': 'http://www.example.com/issuer.json',
},
)
# Enabling the feature flag isn't enough to change the URLs-- they're already loaded by this point.
self.old_patterns = urls.urlpatterns
urls.urlpatterns += (urls.BADGE_SHARE_TRACKER_URL,)
def tearDown(self):
super(TrackShareRedirectTest, self).tearDown()
urls.urlpatterns = self.old_patterns
def test_social_event_sent(self):
test_url = '/certificates/badge_share_tracker/{}/social_network/{}/'.format(
unicode(self.course.id),
self.user.username,
)
self.recreate_tracker()
response = self.client.get(test_url)
self.assertEqual(response.status_code, 302)
self.assertEqual(response['Location'], 'http://www.example.com/image.png')
assert_event_matches(
{
'name': 'edx.badges.assertion.shared',
'data': {
'course_id': 'testorg/run1/trackable_course',
'social_network': 'social_network',
# pylint: disable=no-member
'assertion_id': self.assertion.id,
'assertion_json_url': 'http://www.example.com/assertion.json',
'assertion_image_url': 'http://www.example.com/image.png',
'user_id': self.user.id,
'issuer': 'http://www.example.com/issuer.json',
'enrollment_mode': 'honor',
},
},
self.get_event()
)
"""URL handlers related to certificate handling by LMS"""
from datetime import datetime
from uuid import uuid4
from django.shortcuts import redirect, get_object_or_404
from opaque_keys.edx.locator import CourseLocator
from eventtracking import tracker
import dogstats_wrapper as dog_stats_api
import json
import logging
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.http import HttpResponse, Http404, HttpResponseForbidden
from django.utils.translation import ugettext as _
......@@ -24,6 +26,7 @@ from certificates.models import (
BadgeAssertion)
from certificates.queue import XQueueCertInterface
from edxmako.shortcuts import render_to_response
from util.views import ensure_valid_course_key
from xmodule.modulestore.django import modulestore
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
......@@ -518,6 +521,29 @@ def render_html_view(request, user_id, course_id):
except (InvalidKeyError, CourseDoesNotExist, User.DoesNotExist):
return render_to_response(invalid_template_path, context)
if 'evidence_visit' in request.GET:
print "Event request found!"
try:
badge = BadgeAssertion.objects.get(user=user, course_id=course_key)
tracker.emit(
'edx.badges.assertion.evidence_visit',
{
'user_id': user.id,
'course_id': unicode(course_key),
'enrollment_mode': badge.mode,
'assertion_id': badge.id,
'assertion_image_url': badge.data['image'],
'assertion_json_url': badge.data['json']['id'],
'issuer': badge.data['issuer'],
}
)
except BadgeAssertion.DoesNotExist:
logger.warn(
"Could not find badge for %s on course %s.",
user.id,
course_key,
)
# Okay, now we have all of the pieces, time to put everything together
# Get the active certificate configuration for this course
......@@ -541,3 +567,25 @@ def render_html_view(request, user_id, course_id):
context.update(course.cert_html_view_overrides)
return render_to_response("certificates/valid.html", context)
@ensure_valid_course_key
def track_share_redirect(request__unused, course_id, network, student_username):
"""
Tracks when a user downloads a badge for sharing.
"""
course_id = CourseLocator.from_string(course_id)
assertion = get_object_or_404(BadgeAssertion, user__username=student_username, course_id=course_id)
tracker.emit(
'edx.badges.assertion.shared', {
'course_id': unicode(course_id),
'social_network': network,
'assertion_id': assertion.id,
'assertion_json_url': assertion.data['json']['id'],
'assertion_image_url': assertion.image_url,
'user_id': assertion.user.id,
'enrollment_mode': assertion.mode,
'issuer': assertion.data['issuer'],
}
)
return redirect(assertion.image_url)
......@@ -12,6 +12,7 @@ from nose.plugins.attrib import attr
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from course_modes.models import CourseMode
from track.tests import EventTrackingTestCase
from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_CLOSED_MODULESTORE
from student.models import CourseEnrollment
......@@ -34,7 +35,7 @@ SHIB_ERROR_STR = "The currently logged-in user account does not have permission
@attr('shard_1')
class AboutTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
class AboutTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, EventTrackingTestCase):
"""
Tests about xblock.
"""
......
......@@ -637,6 +637,17 @@ if settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):
'certificates.views.render_html_view', name='cert_html_view'),
)
BADGE_SHARE_TRACKER_URL = url(
r'^certificates/badge_share_tracker/{}/(?P<network>[^/]+)/(?P<student_username>[^/]+)/$'.format(
settings.COURSE_ID_PATTERN
),
'certificates.views.track_share_redirect',
name='badge_share_tracker'
)
if settings.FEATURES.get('ENABLE_OPENBADGES', False):
urlpatterns += (BADGE_SHARE_TRACKER_URL,)
# XDomain proxy
urlpatterns += (
url(r'^xdomain_proxy.html$', 'cors_csrf.views.xdomain_proxy', name='xdomain_proxy'),
......
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