Skip to content

Commit

Permalink
feat: add initialized event transformer
Browse files Browse the repository at this point in the history
  • Loading branch information
andrey-canon committed Dec 12, 2023
1 parent e482a1e commit 2e12c1b
Show file tree
Hide file tree
Showing 18 changed files with 250 additions and 8 deletions.
38 changes: 38 additions & 0 deletions eox_nelp/edxapp_wrapper/backends/event_routing_backends_m_v1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Backend for event-routing-backends library.
This is required since the library has explicit dependencies from openedx platform.
https://github.com/openedx/event-routing-backends
"""
from event_routing_backends.processors.xapi import constants
from event_routing_backends.processors.xapi.registry import XApiTransformersRegistry
from event_routing_backends.processors.xapi.transformer import XApiTransformer


def get_xapi_constants():
"""Allow to get the constants module from
https://github.com/openedx/event-routing-backends/blob/master/event_routing_backends/processors/xapi/constants.py
Returns:
constants module.
"""
return constants


def get_xapi_transformer_registry():
"""Allow to get the XApiTransformersRegistry class from
https://github.com/openedx/event-routing-backends/blob/master/event_routing_backends/processors/xapi/registry.py#L7
Returns:
XApiTransformersRegistry class.
"""
return XApiTransformersRegistry


def get_xapi_transformer():
"""Allow to get the XApiTransformer class from
https://github.com/openedx/event-routing-backends/blob/master/event_routing_backends/processors/xapi/transformer.py#L27
Returns:
XApiTransformer class.
"""
return XApiTransformer
18 changes: 18 additions & 0 deletions eox_nelp/edxapp_wrapper/event_routing_backends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Wrapper for event-routing-backends library.
This contains all the required dependencies from event-routing-backends.
Attributes:
constants: Wrapper constants module.
XApiTransformer: Wrapper for the XApiTransformer class.
XApiTransformersRegistry: Wrapper for the XApiTransformersRegistry class.
"""
from importlib import import_module

from django.conf import settings

backend = import_module(settings.EOX_NELP_EVENT_ROUTING_BACKEND)

constants = backend.get_xapi_constants()
XApiTransformer = backend.get_xapi_transformer()
XApiTransformersRegistry = backend.get_xapi_transformer_registry()
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Backend for event-routing-backends library.
This is required since the library has explicit dependencies from openedx platform.
https://github.com/openedx/event-routing-backends
"""
from mock import Mock


def get_xapi_constants():
"""Test backend for the constants module.
Returns:
Mock class.
"""
constants = Mock()
constants.EN = "en"
constants.XAPI_VERB_INITIALIZED = "http://adlnet.gov/expapi/verbs/initialized"
constants.INITIALIZED = "initialized"

return constants


def get_xapi_transformer_registry():
"""Test backend for the XApiTransformersRegistry class.
Returns:
Mock class.
"""
XApiTransformersRegistry = Mock()
XApiTransformersRegistry.register.return_value = lambda x: x

return XApiTransformersRegistry


def get_xapi_transformer():
"""Test backend for the XApiTransformer class.
Returns:
Object type.
"""
return object
7 changes: 7 additions & 0 deletions eox_nelp/init_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def run_init_pipeline():
"""
patch_user_gender_choices()
set_mako_templates()
register_xapi_transformers()


def patch_user_gender_choices():
Expand Down Expand Up @@ -56,3 +57,9 @@ def set_mako_templates():

if path_to_templates not in edxmako.LOOKUP['main'].directories:
edxmako.paths.add_lookup('main', path_to_templates)


def register_xapi_transformers():
"""This method just import the event transformers in order to register all of them."""
# pylint: disable=import-outside-toplevel, unused-import
from eox_nelp.processors.xapi import event_transformers as xapi_event_transformers # noqa: F401
8 changes: 2 additions & 6 deletions eox_nelp/openedx_filters/tests/xapi/tests_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,8 @@
from tincan import Activity, ActivityDefinition, Agent, LanguageMap

from eox_nelp.edxapp_wrapper.modulestore import modulestore
from eox_nelp.openedx_filters.xapi.filters import (
DEFAULT_LANGUAGE,
XApiActorFilter,
XApiBaseEnrollmentFilter,
XApiBaseProblemsFilter,
)
from eox_nelp.openedx_filters.xapi.filters import XApiActorFilter, XApiBaseEnrollmentFilter, XApiBaseProblemsFilter
from eox_nelp.processors.xapi.constants import DEFAULT_LANGUAGE

User = get_user_model()

Expand Down
2 changes: 1 addition & 1 deletion eox_nelp/openedx_filters/xapi/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
from tincan import Agent, LanguageMap

from eox_nelp.edxapp_wrapper.modulestore import modulestore
from eox_nelp.processors.xapi.constants import DEFAULT_LANGUAGE
from eox_nelp.utils import extract_course_id_from_string, get_course_from_id, get_item_label

User = get_user_model()
DEFAULT_LANGUAGE = "en"


class XApiActorFilter(PipelineStep):
Expand Down
Empty file added eox_nelp/processors/__init__.py
Empty file.
Empty file.
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""This file contains all the test for the initialized_events.py file.
Classes:
InitializedCourseTransformerTestCase: Tests cases for InitializedCourseTransformer class.
"""
from django.test import TestCase
from mock import Mock, patch
from tincan import ActivityDefinition, LanguageMap

from eox_nelp.edxapp_wrapper.event_routing_backends import constants
from eox_nelp.processors.xapi import constants as eox_nelp_constants
from eox_nelp.processors.xapi.event_transformers import InitializedCourseTransformer


class InitializedCourseTransformerTestCase(TestCase):
"""Test class for InitializedCourseTransformer class."""

def setUp(self):
"""Setup common conditions for every test case"""
self.get_data_mock = Mock()
self.get_object_iri_mock = Mock()

InitializedCourseTransformer.get_data = self.get_data_mock
InitializedCourseTransformer.get_object_iri = self.get_object_iri_mock

def test_verb_attribute(self):
""" Test case that checks that the _verb attribute has the right values.
Expected behavior:
- Verb id is the expected value.
- Verb display is the expected value.
"""
verb = InitializedCourseTransformer._verb # pylint: disable=protected-access

self.assertEqual(constants.XAPI_VERB_INITIALIZED, verb.id)
self.assertEqual(LanguageMap({constants.EN: constants.INITIALIZED}), verb.display)

@patch("eox_nelp.processors.xapi.event_transformers.initialized_events.get_course_from_id")
def test_expected_result(self, get_course_mock):
""" Test case that verifies that the get_object method returns the expected Activity
when all the course attributes are found.
Expected behavior:
- get_data method is called with the right value.
- get_object_iri method is called with the right value.
- get_course_from_id method is called with the right value.
- Activity id is the expected value.
- Activity definition is the expected value.
"""
course_id = "course-v1:edx+CS105+2023-T3"
self.get_data_mock.return_value = course_id
object_id = f"http://exemple.com/course/{course_id}"
self.get_object_iri_mock.return_value = object_id
course = {
"display_name": "great-course",
"language": "fr",
"short_description": "This is the best course",
}
get_course_mock.return_value = course
transformer = InitializedCourseTransformer()

activity = transformer.get_object()

self.get_data_mock.assert_called_once_with("data.course_id", True)
self.get_object_iri_mock.assert_called_once_with("course", course_id)
get_course_mock.assert_called_once_with(course_id)
self.assertEqual(object_id, activity.id)
self.assertEqual(
ActivityDefinition(
type=eox_nelp_constants.XAPI_ACTIVITY_COURSE,
name=LanguageMap({course["language"]: course["display_name"]}),
description=LanguageMap({course["language"]: course["short_description"]}),
),
activity.definition,
)
Empty file.
6 changes: 6 additions & 0 deletions eox_nelp/processors/xapi/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""
Constants for xAPI specifications, this contains the NELC required values.
"""
DEFAULT_LANGUAGE = "en"

XAPI_ACTIVITY_COURSE = "https://w3id.org/xapi/cmi5/activitytype/course"
4 changes: 4 additions & 0 deletions eox_nelp/processors/xapi/event_transformers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""
All xAPI transformers.
"""
from eox_nelp.processors.xapi.event_transformers.initialized_events import InitializedCourseTransformer # noqa: F401
47 changes: 47 additions & 0 deletions eox_nelp/processors/xapi/event_transformers/initialized_events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
Transformers for initialized events.
Classes:
InitializedCourseTransformer: Transformer for the event nelc.eox_nelp.initialized.course
"""

from tincan import Activity, ActivityDefinition, LanguageMap, Verb

from eox_nelp.edxapp_wrapper.event_routing_backends import XApiTransformer, XApiTransformersRegistry, constants
from eox_nelp.processors.xapi import constants as eox_nelp_constants
from eox_nelp.utils import get_course_from_id


@XApiTransformersRegistry.register("nelc.eox_nelp.initialized.course")
class InitializedCourseTransformer(XApiTransformer):
"""
Transformers for event generated when an student start a course.
"""
_verb = Verb(
id=constants.XAPI_VERB_INITIALIZED,
display=LanguageMap({constants.EN: constants.INITIALIZED}),
)

def get_object(self):
"""
Get object for xAPI transformed event.
Returns:
`Activity`
"""
course_id = self.get_data('data.course_id', True)
object_id = self.get_object_iri('course', course_id)
course = get_course_from_id(course_id)
display_name = course["display_name"]
description = course["short_description"]
# Set default value if language is not found
course_language = course["language"] or eox_nelp_constants.DEFAULT_LANGUAGE

return Activity(
id=object_id,
definition=ActivityDefinition(
type=eox_nelp_constants.XAPI_ACTIVITY_COURSE,
name=LanguageMap(**({course_language: display_name} if display_name is not None else {})),
description=LanguageMap(**({course_language: description} if description is not None else {}))
),
)
1 change: 1 addition & 0 deletions eox_nelp/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def plugin_settings(settings):
settings.EOX_NELP_BRANDING_BACKEND = 'eox_nelp.edxapp_wrapper.backends.branding_m_v1'
settings.EOX_NELP_CERTIFICATES_BACKEND = 'eox_nelp.edxapp_wrapper.backends.certificates_m_v1'
settings.EOX_NELP_CMS_API_BACKEND = 'eox_nelp.edxapp_wrapper.backends.cms_api_m_v1'
settings.EOX_NELP_EVENT_ROUTING_BACKEND = 'eox_nelp.edxapp_wrapper.backends.event_routing_backends_m_v1'

settings.FUTUREX_API_URL = 'https://testing-site.com'
settings.FUTUREX_API_CLIENT_ID = 'my-test-client-id'
Expand Down
1 change: 1 addition & 0 deletions eox_nelp/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def plugin_settings(settings): # pylint: disable=function-redefined
settings.EOX_NELP_BRANDING_BACKEND = 'eox_nelp.edxapp_wrapper.test_backends.branding_m_v1'
settings.EOX_NELP_CERTIFICATES_BACKEND = 'eox_nelp.edxapp_wrapper.test_backends.certificates_m_v1'
settings.EOX_NELP_CMS_API_BACKEND = 'eox_nelp.edxapp_wrapper.test_backends.cms_api_m_v1'
settings.EOX_NELP_EVENT_ROUTING_BACKEND = 'eox_nelp.edxapp_wrapper.test_backends.event_routing_backends_m_v1'

settings.FUTUREX_API_URL = 'https://testing.com'
settings.FUTUREX_API_CLIENT_ID = 'my-test-client-id'
Expand Down
9 changes: 8 additions & 1 deletion eox_nelp/tests/test_init_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@
class RunInitPipelineTestCase(TestCase):
"""Test class for run_init_pipeline method."""

@patch("eox_nelp.init_pipeline.register_xapi_transformers")
@patch("eox_nelp.init_pipeline.patch_user_gender_choices")
@patch("eox_nelp.init_pipeline.set_mako_templates")
def test_pipeline_execute_expected_methods(self, set_mako_templates_mock, patch_user_gender_choices_mock):
def test_pipeline_execute_expected_methods(
self,
set_mako_templates_mock,
patch_user_gender_choices_mock,
register_xapi_transformers_mock,
):
""" Test that method calls the expected methods during the pipeline execution.
Expected behavior:
Expand All @@ -32,6 +38,7 @@ def test_pipeline_execute_expected_methods(self, set_mako_templates_mock, patch_

set_mako_templates_mock.assert_called_once()
patch_user_gender_choices_mock.assert_called_once()
register_xapi_transformers_mock.assert_called_once()


class SetMakoTemplatesTestCase(TestCase):
Expand Down

0 comments on commit 2e12c1b

Please sign in to comment.