diff --git a/lms/djangoapps/grades/events.py b/lms/djangoapps/grades/events.py index 659401679f..85044f4dc3 100644 --- a/lms/djangoapps/grades/events.py +++ b/lms/djangoapps/grades/events.py @@ -4,6 +4,7 @@ Emits course grade events. from logging import getLogger from crum import get_current_user +from django.conf import settings from eventtracking import tracker from common.djangoapps.course_modes.models import CourseMode @@ -15,6 +16,7 @@ from common.djangoapps.track.event_transaction_utils import ( get_event_transaction_type, set_event_transaction_type ) +from lms.djangoapps.grades.signals.signals import SCHEDULE_FOLLOW_UP_SEGMENT_EVENT_FOR_COURSE_PASSED_FIRST_TIME from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.features.enterprise_support.context import get_enterprise_event_context @@ -30,7 +32,6 @@ 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): @@ -215,7 +216,7 @@ def fire_segment_event_on_course_grade_passed_first_time(user_id, course_locator * Event should be only fired for learners enrolled in paid enrollment modes. """ - event_name = LEARNER_PASSED_COURSE_FIRST_TIME + event_name = 'edx.course.learner.passed.first_time' courserun_key = str(course_locator) courserun_org = course_locator.org paid_enrollment_modes = ( @@ -247,4 +248,13 @@ def fire_segment_event_on_course_grade_passed_first_time(user_id, course_locator } segment.track(user_id, event_name, event_properties) + if getattr(settings, 'OUTCOME_SURVEYS_FOLLOW_UP_SIGNAL_ENABLED', False): + # fire signal so that a follow up event can be scheduled in outcome_surveys app + SCHEDULE_FOLLOW_UP_SEGMENT_EVENT_FOR_COURSE_PASSED_FIRST_TIME.send( + sender=None, + user_id=user_id, + course_id=course_locator, + event_properties=event_properties + ) + log.info("Segment event fired for passed learners. Event: [{}], Data: [{}]".format(event_name, event_properties)) diff --git a/lms/djangoapps/grades/management/commands/tests/test_send_segment_events_for_failed_learners.py b/lms/djangoapps/grades/management/commands/tests/test_send_segment_events_for_failed_learners.py index bed60d34cd..4cc707d252 100644 --- a/lms/djangoapps/grades/management/commands/tests/test_send_segment_events_for_failed_learners.py +++ b/lms/djangoapps/grades/management/commands/tests/test_send_segment_events_for_failed_learners.py @@ -37,7 +37,6 @@ class TestSendSegmentEventsForFailedLearnersCommand(SharedModuleStoreTestCase): # we will create enrollments for paid modes plus `audit` mode enrollment_modes = PAID_ENROLLMENT_MODES + ['audit'] - # import pdb ; pdb.set_trace() cls.course_end = timezone.now() - timedelta(days=31) cls.course_overviews = CourseOverviewFactory.create_batch(4, end=cls.course_end) diff --git a/lms/djangoapps/grades/signals/signals.py b/lms/djangoapps/grades/signals/signals.py index 73a31f937c..ed78923a61 100644 --- a/lms/djangoapps/grades/signals/signals.py +++ b/lms/djangoapps/grades/signals/signals.py @@ -112,3 +112,11 @@ SUBSECTION_OVERRIDE_CHANGED = Signal() # ] COURSE_GRADE_PASSED_FIRST_TIME = Signal() COURSE_GRADE_PASSED_UPDATE_IN_LEARNER_PATHWAY = Signal() + +# This Signal indicates that a segment event has fired for user who has passed a course for the first time +# providing_args=[ +# 'user_id', # User object id +# 'course_id', # Course object id +# 'event_properties', # Segment event properties that will be needed for follow up event +# ] +SCHEDULE_FOLLOW_UP_SEGMENT_EVENT_FOR_COURSE_PASSED_FIRST_TIME = Signal() diff --git a/lms/djangoapps/grades/tests/test_signals.py b/lms/djangoapps/grades/tests/test_signals.py index bc59c0698c..636241de54 100644 --- a/lms/djangoapps/grades/tests/test_signals.py +++ b/lms/djangoapps/grades/tests/test_signals.py @@ -11,15 +11,16 @@ import ddt import pytest import pytz from django.test import TestCase +from django.test.utils import override_settings 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 xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from ..constants import ScoreDatabaseTableEnum from ..signals.handlers import ( @@ -388,8 +389,10 @@ class CourseEventsSignalsTest(ModuleStoreTestCase): } ) + @override_settings(OUTCOME_SURVEYS_FOLLOW_UP_SIGNAL_ENABLED=True) @patch('lms.djangoapps.grades.events.segment.track') - def test_segment_event_on_course_grade_passed_first_time(self, segment_track_mock): + @patch('lms.djangoapps.grades.signals.signals.SCHEDULE_FOLLOW_UP_SEGMENT_EVENT_FOR_COURSE_PASSED_FIRST_TIME.send') + def test_segment_event_on_course_grade_passed_first_time(self, signal_mock, segment_track_mock): course = CourseOverviewFactory() enrollment = CourseEnrollmentFactory( is_active=True, @@ -425,3 +428,15 @@ class CourseEventsSignalsTest(ModuleStoreTestCase): 'COURSE_ORG_NAME': course.org, } ) + + assert signal_mock.call_count == 1 + signal_mock.assert_called_with( + sender=None, + user_id=self.user.id, + course_id=course.id, + event_properties={ + 'LMS_ENROLLMENT_ID': enrollment.id, + 'COURSE_TITLE': course.display_name, + 'COURSE_ORG_NAME': course.org, + } + ) diff --git a/lms/envs/common.py b/lms/envs/common.py index a5662dd5e5..4cd68a3f29 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -5179,3 +5179,6 @@ MFE_CONFIG = {} # .. setting_description: The MFE Config API response will be cached during the # specified time MFE_CONFIG_API_CACHE_TIMEOUT = 60 * 5 + +######################## Settings for Outcome Surveys plugin ######################## +OUTCOME_SURVEYS_FOLLOW_UP_SIGNAL_ENABLED = False diff --git a/requirements/edx/base.in b/requirements/edx/base.in index f4a9f2fe2a..cd5697dcda 100644 --- a/requirements/edx/base.in +++ b/requirements/edx/base.in @@ -126,6 +126,7 @@ openedx-calc # Library supporting mathematical calculatio openedx-events # Open edX Events from Hooks Extension Framework (OEP-50) openedx-filters # Open edX Filters from Hooks Extension Framework (OEP-50) ora2>=4.4.0 +outcome-surveys # edx-platform plugin to send and track segment events needed for surveys path piexif # Exif image metadata manipulation, used in the profile_images app Pillow # Image manipulation library; used for course assets, profile images, invoice PDFs, etc. diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 41203d2fbf..3d373f5013 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -747,6 +747,8 @@ ora2==4.4.4 # via -r requirements/edx/base.in oscrypto==1.3.0 # via snowflake-connector-python +outcome-surveys==1.1.0 + # via -r requirements/edx/base.in packaging==21.3 # via # drf-yasg diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 38d21b8e7d..35bfee2179 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -974,6 +974,8 @@ oscrypto==1.3.0 # via # -r requirements/edx/testing.txt # snowflake-connector-python +outcome-surveys==1.1.0 + # via -r requirements/edx/testing.txt packaging==21.3 # via # -r requirements/edx/testing.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 39142414eb..a5ff69e67c 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -925,6 +925,8 @@ oscrypto==1.3.0 # via # -r requirements/edx/base.txt # snowflake-connector-python +outcome-surveys==1.1.0 + # via -r requirements/edx/base.txt packaging==21.3 # via # -r requirements/edx/base.txt