Merge pull request #30466 from openedx/ammar/send-segment-event-for-first-time-passed-learners
feat: send segment event for learners who passed a course first time
This commit is contained in:
@@ -1,19 +1,25 @@
|
||||
"""
|
||||
Emits course grade events.
|
||||
"""
|
||||
from logging import getLogger
|
||||
|
||||
from crum import get_current_user
|
||||
from eventtracking import tracker
|
||||
|
||||
from common.djangoapps.track import contexts
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.track import contexts, segment
|
||||
from common.djangoapps.track.event_transaction_utils import (
|
||||
create_new_event_transaction_id,
|
||||
get_event_transaction_id,
|
||||
get_event_transaction_type,
|
||||
set_event_transaction_type
|
||||
)
|
||||
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.features.enterprise_support.context import get_enterprise_event_context
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
COURSE_GRADE_CALCULATED = 'edx.grades.course.grade_calculated'
|
||||
GRADES_OVERRIDE_EVENT_TYPE = 'edx.grades.problem.score_overridden'
|
||||
GRADES_RESCORE_EVENT_TYPE = 'edx.grades.problem.rescored'
|
||||
@@ -24,6 +30,7 @@ SUBSECTION_GRADE_CALCULATED = 'edx.grades.subsection.grade_calculated'
|
||||
COURSE_GRADE_PASSED_FIRST_TIME_EVENT_TYPE = 'edx.course.grade.passed.first_time'
|
||||
COURSE_GRADE_NOW_PASSED_EVENT_TYPE = 'edx.course.grade.now_passed'
|
||||
COURSE_GRADE_NOW_FAILED_EVENT_TYPE = 'edx.course.grade.now_failed'
|
||||
LEARNER_PASSED_COURSE_FIRST_TIME = 'edx.course.learner.passed.first_time'
|
||||
|
||||
|
||||
def grade_updated(**kwargs):
|
||||
@@ -200,3 +207,42 @@ def course_grade_now_failed(user, course_id):
|
||||
'event_transaction_type': str(get_event_transaction_type())
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def fire_segment_event_on_course_grade_passed_first_time(user_id, course_locator):
|
||||
"""
|
||||
Fire a segment event `edx.course.grade.passed.first_time` with the desired data.
|
||||
|
||||
* Event should be only fired for learners enrolled in paid enrollment modes.
|
||||
"""
|
||||
event_name = LEARNER_PASSED_COURSE_FIRST_TIME
|
||||
courserun_key = str(course_locator)
|
||||
courserun_org = course_locator.org
|
||||
paid_enrollment_modes = (
|
||||
CourseMode.MASTERS,
|
||||
CourseMode.VERIFIED,
|
||||
CourseMode.CREDIT_MODE,
|
||||
CourseMode.PROFESSIONAL,
|
||||
CourseMode.NO_ID_PROFESSIONAL_MODE,
|
||||
)
|
||||
|
||||
try:
|
||||
__ = CourseEnrollment.objects.get(user_id=user_id, course_id=courserun_key, mode__in=paid_enrollment_modes)
|
||||
except CourseEnrollment.DoesNotExist:
|
||||
return
|
||||
|
||||
try:
|
||||
courserun_display_name = CourseOverview.objects.values_list('display_name', flat=True).get(id=courserun_key)
|
||||
except CourseOverview.DoesNotExist:
|
||||
return
|
||||
|
||||
event_properties = {
|
||||
'LMS_USER_ID': user_id,
|
||||
'COURSERUN_KEY': courserun_key,
|
||||
'COURSE_TITLE': courserun_display_name,
|
||||
'COURSE_ORG_NAME': courserun_org,
|
||||
'PASSED': 1,
|
||||
}
|
||||
segment.track(user_id, event_name, event_properties)
|
||||
|
||||
log.info("Segment event fired for passed learners. Event: [{}], Data: [{}]".format(event_name, event_properties))
|
||||
|
||||
@@ -299,3 +299,4 @@ def listen_for_course_grade_passed_first_time(sender, user_id, course_id, **kwar
|
||||
Emits an event edx.course.grade.passed.first_time
|
||||
"""
|
||||
events.course_grade_passed_first_time(user_id, course_id)
|
||||
events.fire_segment_event_on_course_grade_passed_first_time(user_id, course_id)
|
||||
|
||||
@@ -130,7 +130,7 @@ class TestCourseGradeFactory(GradeTestBase):
|
||||
with self.assertNumQueries(4), mock_get_score(1, 2):
|
||||
_assert_read(expected_pass=False, expected_percent=0) # start off with grade of 0
|
||||
|
||||
num_queries = 41
|
||||
num_queries = 42
|
||||
with self.assertNumQueries(num_queries), mock_get_score(1, 2):
|
||||
grade_factory.update(self.request.user, self.course, force_update_subsections=True)
|
||||
|
||||
|
||||
@@ -11,21 +11,25 @@ import ddt
|
||||
import pytest
|
||||
import pytz
|
||||
from django.test import TestCase
|
||||
from submissions.models import score_reset, score_set
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from submissions.models import score_reset, score_set
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
|
||||
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
from common.djangoapps.track.event_transaction_utils import get_event_transaction_id, get_event_transaction_type
|
||||
from common.djangoapps.util.date_utils import to_timestamp
|
||||
from lms.djangoapps.grades.models import PersistentCourseGrade
|
||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
||||
|
||||
from ..constants import ScoreDatabaseTableEnum
|
||||
from ..signals.handlers import (
|
||||
disconnect_submissions_signal_receiver,
|
||||
listen_for_course_grade_passed_first_time,
|
||||
listen_for_failing_grade,
|
||||
listen_for_passing_grade,
|
||||
problem_raw_score_changed_handler,
|
||||
submissions_score_reset_handler,
|
||||
submissions_score_set_handler,
|
||||
listen_for_course_grade_passed_first_time,
|
||||
listen_for_passing_grade,
|
||||
listen_for_failing_grade
|
||||
submissions_score_set_handler
|
||||
)
|
||||
from ..signals.signals import PROBLEM_RAW_SCORE_CHANGED
|
||||
|
||||
@@ -266,7 +270,7 @@ class ScoreChangedSignalRelayTest(TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class CourseEventsSignalsTest(TestCase):
|
||||
class CourseEventsSignalsTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests to ensure that the courseware module correctly catches
|
||||
course grades passed/failed signal and emit course related event
|
||||
@@ -281,10 +285,6 @@ class CourseEventsSignalsTest(TestCase):
|
||||
Configure mocks for all the dependencies of the render method
|
||||
"""
|
||||
super().setUp()
|
||||
self.signal_mock = self.setup_patch(
|
||||
'lms.djangoapps.grades.signals.signals.COURSE_GRADE_PASSED_FIRST_TIME.send',
|
||||
None,
|
||||
)
|
||||
self.user_mock = MagicMock()
|
||||
self.user_mock.id = 42
|
||||
self.get_user_mock = self.setup_patch(
|
||||
@@ -296,6 +296,8 @@ class CourseEventsSignalsTest(TestCase):
|
||||
course='some_course',
|
||||
run='some_run'
|
||||
)
|
||||
self.user = UserFactory.create(username="Bob", email="bob@example.com", password="edx")
|
||||
self.client.login(username=self.user.username, password="edx")
|
||||
|
||||
def setup_patch(self, function_name, return_value):
|
||||
"""
|
||||
@@ -385,3 +387,43 @@ class CourseEventsSignalsTest(TestCase):
|
||||
'event_transaction_type': str(get_event_transaction_type()),
|
||||
}
|
||||
)
|
||||
|
||||
@patch('lms.djangoapps.grades.events.segment.track')
|
||||
def test_segment_event_on_course_grade_passed_first_time(self, segment_track_mock):
|
||||
course = CourseOverviewFactory()
|
||||
__ = CourseEnrollmentFactory(
|
||||
is_active=True,
|
||||
mode='verified',
|
||||
course=course,
|
||||
user=self.user
|
||||
)
|
||||
params = {
|
||||
"user_id": self.user.id,
|
||||
"course_id": course.id,
|
||||
"course_version": "JoeMcEwing",
|
||||
"course_edited_timestamp": datetime(
|
||||
year=2016,
|
||||
month=8,
|
||||
day=1,
|
||||
hour=18,
|
||||
minute=53,
|
||||
second=24,
|
||||
microsecond=354741,
|
||||
tzinfo=pytz.UTC,
|
||||
),
|
||||
"percent_grade": 77.7,
|
||||
"letter_grade": "Great job",
|
||||
"passed": True,
|
||||
}
|
||||
__ = PersistentCourseGrade.update_or_create(**params)
|
||||
segment_track_mock.assert_called_with(
|
||||
self.user.id,
|
||||
'edx.course.learner.passed.first_time',
|
||||
{
|
||||
'LMS_USER_ID': self.user.id,
|
||||
'COURSERUN_KEY': str(course.id),
|
||||
'COURSE_TITLE': course.display_name,
|
||||
'COURSE_ORG_NAME': course.org,
|
||||
'PASSED': 1,
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user