Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions lms/djangoapps/grades/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
UserPersonalData
)
from openedx_events.learning.signals import CCX_COURSE_PASSING_STATUS_UPDATED, COURSE_PASSING_STATUS_UPDATED
from openedx_filters.learning.filters import GradeEventContextRequested

from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.models import CourseEnrollment
Expand All @@ -27,7 +28,6 @@
)
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

log = getLogger(__name__)

Expand Down Expand Up @@ -166,8 +166,11 @@ def course_grade_passed_first_time(user_id, course_id):
"""
event_name = COURSE_GRADE_PASSED_FIRST_TIME_EVENT_TYPE
context = contexts.course_context_from_course_id(course_id)
context_enterprise = get_enterprise_event_context(user_id, course_id)
context.update(context_enterprise)
enriched_context = GradeEventContextRequested.run_filter(
context=context, user_id=user_id, course_id=course_id
)
if enriched_context is not None:
context = enriched_context
# TODO (AN-6134): remove this context manager
with tracker.get_tracker().context(event_name, context):
tracker.emit(
Expand Down
48 changes: 47 additions & 1 deletion lms/djangoapps/grades/tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

from unittest import mock
from unittest.mock import patch

from ccx_keys.locator import CCXLocator
from django.utils.timezone import now
Expand All @@ -23,13 +24,13 @@
from openedx_events.tests.utils import OpenEdxEventsTestMixin

from common.djangoapps.student.tests.factories import AdminFactory, UserFactory
from common.test.utils import assert_dict_contains_subset
from lms.djangoapps.ccx.models import CustomCourseForEdX
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.models import PersistentCourseGrade
from lms.djangoapps.grades.tests.utils import mock_passing_grade
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from common.test.utils import assert_dict_contains_subset


class PersistentGradeEventsTest(SharedModuleStoreTestCase, OpenEdxEventsTestMixin):
Expand Down Expand Up @@ -256,3 +257,48 @@ def test_ccx_course_passing_status_updated_emitted(self):
},
event_receiver.call_args.kwargs,
)


class GradeEventContextFilterTest(SharedModuleStoreTestCase):
"""
Tests that course_grade_passed_first_time invokes the GradeEventContextRequested
filter instead of the old enterprise_support import.
"""

@classmethod
def setUpClass(cls):
super().setUpClass()

def setUp(self):
super().setUp()
self.user = UserFactory.create()
self.course = CourseFactory.create()

@patch('lms.djangoapps.grades.events.GradeEventContextRequested.run_filter')
def test_filter_called_with_context(self, mock_run_filter):
"""
course_grade_passed_first_time should call GradeEventContextRequested.run_filter
and merge the returned context.
"""
enriched = {"org": "test_org", "enterprise_uuid": "abc-123"}
mock_run_filter.return_value = enriched

from lms.djangoapps.grades.events import course_grade_passed_first_time
with patch('lms.djangoapps.grades.events.tracker'):
course_grade_passed_first_time(self.user.id, self.course.id)

mock_run_filter.assert_called_once()
call_kwargs = mock_run_filter.call_args.kwargs
assert call_kwargs['user_id'] == self.user.id
assert str(call_kwargs['course_id']) == str(self.course.id)

@patch('lms.djangoapps.grades.events.GradeEventContextRequested.run_filter')
def test_filter_none_return_leaves_context_intact(self, mock_run_filter):
"""
If run_filter returns None (fail_silently path), context is not overwritten.
"""
mock_run_filter.return_value = None
from lms.djangoapps.grades.events import course_grade_passed_first_time
with patch('lms.djangoapps.grades.events.tracker'):
# Should not raise even when filter returns None
course_grade_passed_first_time(self.user.id, self.course.id)
12 changes: 12 additions & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3172,3 +3172,15 @@ def _should_send_certificate_events(settings):
SSL_AUTH_DN_FORMAT_STRING = (
"/C=US/ST=Massachusetts/O=Massachusetts Institute of Technology/OU=Client CA v1/CN={0}/emailAddress={1}"
)

# .. setting_name: OPEN_EDX_FILTERS_CONFIG
# .. setting_default: {}
# .. setting_description: Configuration dict for openedx-filters pipeline steps.
# Keys are filter type strings; values are dicts with 'fail_silently' (bool) and
# 'pipeline' (list of dotted-path strings to PipelineStep subclasses).
OPEN_EDX_FILTERS_CONFIG = {
"org.openedx.learning.grade.context.requested.v1": {
"fail_silently": True,
"pipeline": ["enterprise.filters.grades.GradeEventContextEnricher"],
},
}
14 changes: 14 additions & 0 deletions lms/envs/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def get_env_setting(setting):
'EVENT_BUS_PRODUCER_CONFIG',
'DEFAULT_FILE_STORAGE',
'STATICFILES_STORAGE',
'OPEN_EDX_FILTERS_CONFIG',
]
})

Expand Down Expand Up @@ -515,6 +516,19 @@ def get_env_setting(setting):
_YAML_TOKENS.get('EVENT_BUS_PRODUCER_CONFIG', {})
)

# Merge OPEN_EDX_FILTERS_CONFIG from YAML into the default defined in common.py.
# Pipeline steps from YAML are appended after steps defined in common.py.
# The fail_silently value from YAML takes precedence over the one in common.py.
for _filter_type, _filter_config in _YAML_TOKENS.get('OPEN_EDX_FILTERS_CONFIG', {}).items():
if _filter_type in OPEN_EDX_FILTERS_CONFIG:
OPEN_EDX_FILTERS_CONFIG[_filter_type]['pipeline'].extend(
_filter_config.get('pipeline', [])
)
if 'fail_silently' in _filter_config:
OPEN_EDX_FILTERS_CONFIG[_filter_type]['fail_silently'] = _filter_config['fail_silently']
else:
OPEN_EDX_FILTERS_CONFIG[_filter_type] = _filter_config

#######################################################################################################################
# HEY! Don't add anything to the end of this file.
# Add your defaults to common.py instead!
Expand Down
Loading