From a2ead196165c97a06ce96c5491c0143c5d425062 Mon Sep 17 00:00:00 2001 From: andrey-canon Date: Tue, 14 Nov 2023 12:30:47 -0500 Subject: [PATCH 1/2] feat: implement XAPI openedx filters --- docs/_static/theme_overrides.css | 4 ++ docs/conf.py | 4 ++ docs/getting_started.rst | 44 ++++++++++++++++ .../processors/openedx_filters/__init__.py | 0 .../processors/openedx_filters/decorators.py | 49 ++++++++++++++++++ .../processors/openedx_filters/exceptions.py | 10 ++++ .../processors/openedx_filters/filters.py | 50 +++++++++++++++++++ .../tests/openedx_filters/__init__.py | 0 .../tests/openedx_filters/test_filters.py | 40 +++++++++++++++ .../event_transformers/completion_events.py | 6 ++- .../event_transformers/enrollment_events.py | 12 +++-- .../xapi/event_transformers/exam_events.py | 6 ++- .../xapi/event_transformers/forum_events.py | 18 ++++--- .../xapi/event_transformers/grading_events.py | 7 ++- .../event_transformers/navigation_events.py | 4 ++ .../problem_interaction_events.py | 7 +++ .../xapi/event_transformers/video_events.py | 2 + .../processors/xapi/transformer.py | 18 +++++-- requirements/base.in | 1 + requirements/base.txt | 3 ++ requirements/dev.txt | 3 ++ requirements/doc.txt | 3 ++ requirements/quality.txt | 3 ++ requirements/test.txt | 3 ++ 24 files changed, 275 insertions(+), 22 deletions(-) create mode 100644 event_routing_backends/processors/openedx_filters/__init__.py create mode 100644 event_routing_backends/processors/openedx_filters/decorators.py create mode 100644 event_routing_backends/processors/openedx_filters/exceptions.py create mode 100644 event_routing_backends/processors/openedx_filters/filters.py create mode 100644 event_routing_backends/processors/tests/openedx_filters/__init__.py create mode 100644 event_routing_backends/processors/tests/openedx_filters/test_filters.py diff --git a/docs/_static/theme_overrides.css b/docs/_static/theme_overrides.css index aad82457..a4436feb 100644 --- a/docs/_static/theme_overrides.css +++ b/docs/_static/theme_overrides.css @@ -8,3 +8,7 @@ .wy-table-responsive { overflow: visible !important; } + +.bd-page-width { + max-width: 96rem; +} diff --git a/docs/conf.py b/docs/conf.py index 2d01b3d5..6d6f957d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -231,6 +231,10 @@ def get_version(*file_paths): # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] +html_css_files = [ + 'theme_overrides.css', +] + # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 2db9b72b..6b78e47d 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -220,6 +220,47 @@ A sample override for ``xapi`` backend is presented below. Here we are allowing } } +OpenEdx Filters +=============== + +This is an integration that allows to modify current standard outputs by using the `openedx-filters`_ library and is limited to the following filters per processor: + + +xAPI Filters +------------ + ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| Filter | Description | ++=================================================================================================+====================================================================================+ +| event_routing_backends.processors.xapi.transformer.xapi_transformer.get_actor | Intercepts and allows to modify the xAPI actor field, this affects all xAPI events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| event_routing_backends.processors.xapi.transformer.xapi_transformer.get_verb | Intercepts and allows to modify the xAPI actor field, this affects all xAPI events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| event_routing_backends.processors.xapi.completion_events.completion_created.get_object | Allows to modify the xAPI object field, this just affects completion events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| event_routing_backends.processors.xapi.enrollment_events.base_enrollment.get_object | Allows to modify the xAPI object field, this just affects enrollment events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| event_routing_backends.processors.xapi.exam_events.base_exam.get_object | Allows to modify the xAPI object field, this just affects exam events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| event_routing_backends.processors.xapi.forum_events.base_forum_thread.get_object | Allows to modify the xAPI object field, this just affects forum events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| event_routing_backends.processors.xapi.grading_events.subsection_graded.get_object | Allows to modify the xAPI object field, this just affects subsection_graded events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| event_routing_backends.processors.xapi.grading_events.course_graded.get_object | Allows to modify the xAPI object field, this just affects course_graded events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| event_routing_backends.processors.xapi.navigation_events.link_clicked.get_object | Allows to modify the xAPI object field, this just affects link_clicked events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| event_routing_backends.processors.xapi.navigation_events.outline_selected.get_object | Allows to modify the xAPI object field, this just affects outline_selected events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| event_routing_backends.processors.xapi.navigation_events.tab_navigation.get_object | Allows to modify the xAPI object field, this just affects tab_navigation events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| event_routing_backends.processors.xapi.problem_interaction_events.base_problems.get_object | Allows to modify the xAPI object field, this just affects problem events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| event_routing_backends.processors.xapi.problem_interaction_events.base_problem_check.get_object | Allows to modify the xAPI object field, this just affects problem_check events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ +| event_routing_backends.processors.xapi.video_events.base_video.get_object | Allows to modify the xAPI object field, this affects all video events | ++-------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------+ + .. _event-tracking: https://github.com/openedx/event-tracking .. _NameWhitelist: https://github.com/openedx/event-tracking/blob/master/eventtracking/processors/whitelist.py @@ -245,3 +286,6 @@ A sample override for ``xapi`` backend is presented below. Here we are allowing .. _edx-celeryutils: https://github.com/openedx/edx-celeryutils .. _commands: https://github.com/openedx/edx-celeryutils/tree/master/celery_utils/management/commands + +.. _openedx-filters: https://github.com/openedx/openedx-filters + diff --git a/event_routing_backends/processors/openedx_filters/__init__.py b/event_routing_backends/processors/openedx_filters/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/event_routing_backends/processors/openedx_filters/decorators.py b/event_routing_backends/processors/openedx_filters/decorators.py new file mode 100644 index 00000000..ff64a3dc --- /dev/null +++ b/event_routing_backends/processors/openedx_filters/decorators.py @@ -0,0 +1,49 @@ +""" +Decorators that helps to implement the Processor filter functionality. +""" +import functools + +from event_routing_backends.processors.openedx_filters.filters import ProcessorBaseFilter + + +def openedx_filter(filter_type): + """ + This decorator allows to implement the ProcessorBaseFilter on multiple class methods + and intends to modify the returned value from methods like get_actor or get_objects + in cases where the standard output doesn't satisfy the implementation requirements. + + Arguments: + filter_type: String that defines the filter_type attribute of ProcessorBaseFilter, + this allows to identify the configuration setting. + + Example: + + 1. Decorate your method: + + @openedx_filter(filter_type="this.will.be.the.filter.key") + def get_object(self): + ... + + 2. Set the openedx filter config in your environment variables. + + OPEN_EDX_FILTERS_CONFIG = { + "this.will.be.the.filter.key": { + "pipeline": ["path.to.an.external.pipeline.step"], + "fail_silently": False, + } + } + + 3. More details about filters https://github.com/openedx/openedx-filters/ + """ + def wrapper(func): + @functools.wraps(func) + def inner_wrapper(*args, **kwargs): + dynamic_filter = ProcessorBaseFilter.generate_dynamic_filter(filter_type=filter_type) + + return dynamic_filter.run_filter( + result=func(*args, **kwargs), + ) + + return inner_wrapper + + return wrapper diff --git a/event_routing_backends/processors/openedx_filters/exceptions.py b/event_routing_backends/processors/openedx_filters/exceptions.py new file mode 100644 index 00000000..6a6d1a6b --- /dev/null +++ b/event_routing_backends/processors/openedx_filters/exceptions.py @@ -0,0 +1,10 @@ +""" +Custom processors exceptions thrown by filters. +""" +from openedx_filters.exceptions import OpenEdxFilterException + + +class InvalidFilterType(OpenEdxFilterException): + """ + Exception that indicates that the attribute `filter_type` has not been set property. + """ diff --git a/event_routing_backends/processors/openedx_filters/filters.py b/event_routing_backends/processors/openedx_filters/filters.py new file mode 100644 index 00000000..c00028df --- /dev/null +++ b/event_routing_backends/processors/openedx_filters/filters.py @@ -0,0 +1,50 @@ +""" +Processors filters, this file aims to contain all the filters that could modify the +standard transformer results by implementing external pipeline steps. +""" +from openedx_filters.tooling import OpenEdxPublicFilter + +from event_routing_backends.processors.openedx_filters.exceptions import InvalidFilterType + + +class ProcessorBaseFilter(OpenEdxPublicFilter): + """ + This is a general filter class that applies the open edx filter in multiple + scenarios, its functionality is limited to one input one output therefore this + only can be applied in method that returns a unique value. + """ + + @classmethod + def generate_dynamic_filter(cls, filter_type): + """This generates a sub class of ProcessorBaseFilter with the filter_type attribute. + + Arguments: + filter_type: String the defines the filter key on the OPEN_EDX_FILTERS_CONFIG + section + + Returns: + ProcessorBaseFilter sub-class: This new class includes the filter_type attribute. + """ + return type("DynamicFilter", (cls,), {"filter_type": filter_type}) + + @classmethod + def run_filter(cls, result): + """ + Executes a filter after validating the right class configuration. + + Arguments: + result: Result to be modified or extended. + + Returns: + result: This value comes from the dictionary returned by run_pipeline and will vary + depends on the implemented pipelines. + + Raises: + InvalidFilterType: if the ProcessorBaseFilter is used instead of a dynamic filter. + """ + if not cls.filter_type: + raise InvalidFilterType("Parameter filter_type has not been set.") + + data = super().run_pipeline(result=result) + + return data.get("result", result) diff --git a/event_routing_backends/processors/tests/openedx_filters/__init__.py b/event_routing_backends/processors/tests/openedx_filters/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/event_routing_backends/processors/tests/openedx_filters/test_filters.py b/event_routing_backends/processors/tests/openedx_filters/test_filters.py new file mode 100644 index 00000000..eec91e4d --- /dev/null +++ b/event_routing_backends/processors/tests/openedx_filters/test_filters.py @@ -0,0 +1,40 @@ +"""Test cases for the filters file.""" +from django.test import TestCase +from mock import patch +from openedx_filters.tooling import OpenEdxPublicFilter + +from event_routing_backends.processors.openedx_filters.exceptions import InvalidFilterType +from event_routing_backends.processors.openedx_filters.filters import ProcessorBaseFilter + + +class TestProcessorBaseFilter(TestCase): + """General test cases for the ProcessorBaseFilter class.""" + + def test_invalid_configuration(self): + """This test that the exception XApiInvalidFilterType is raised when + the filter_type attribute has not been set. + + Expected behavior: + - InvalidFilterType exception is raised + """ + self.assertRaises(InvalidFilterType, ProcessorBaseFilter.run_filter, "dummy_value") + + @patch.object(OpenEdxPublicFilter, "run_pipeline") + def test_expected_value(self, run_pipeline_mock): + """This checks that the method run_filter returns the value generated by + the parent method `run_pipeline` + + Expected behavior: + - run_pipeline is called with the right key and value + - run_filter returns the value of the result key + """ + run_pipeline_mock.return_value = { + "result": "expected_value" + } + input_value = "dummy_value" + openedx_filter = ProcessorBaseFilter.generate_dynamic_filter(filter_type="test_filter") + + result = openedx_filter.run_filter(result=input_value) + + run_pipeline_mock.assert_called_once_with(result=input_value) + self.assertEqual(run_pipeline_mock()["result"], result) diff --git a/event_routing_backends/processors/xapi/event_transformers/completion_events.py b/event_routing_backends/processors/xapi/event_transformers/completion_events.py index d9ce0464..51917fdc 100644 --- a/event_routing_backends/processors/xapi/event_transformers/completion_events.py +++ b/event_routing_backends/processors/xapi/event_transformers/completion_events.py @@ -3,6 +3,7 @@ """ from tincan import Activity, ActivityDefinition, Extensions, LanguageMap, Result, Verb +from event_routing_backends.processors.openedx_filters.decorators import openedx_filter 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 @@ -14,13 +15,16 @@ class CompletionCreatedTransformer(XApiTransformer): Transformers for event generated when an student completion is created or updated. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_PROGRESSED, display=LanguageMap({constants.EN: constants.PROGRESSED}), ) additional_fields = ('result', ) + @openedx_filter( + filter_type="event_routing_backends.processors.xapi.completion_events.completion_created.get_object", + ) def get_object(self): """ Get object for xAPI transformed event related to a thread. diff --git a/event_routing_backends/processors/xapi/event_transformers/enrollment_events.py b/event_routing_backends/processors/xapi/event_transformers/enrollment_events.py index 09d1f28e..9eaffd2e 100644 --- a/event_routing_backends/processors/xapi/event_transformers/enrollment_events.py +++ b/event_routing_backends/processors/xapi/event_transformers/enrollment_events.py @@ -5,6 +5,7 @@ from tincan import Activity, ActivityDefinition, Extensions, LanguageMap, Verb from event_routing_backends.helpers import get_course_from_id +from event_routing_backends.processors.openedx_filters.decorators import openedx_filter 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 @@ -25,6 +26,7 @@ def get_context_activities(self): return None + @openedx_filter(filter_type="event_routing_backends.processors.xapi.enrollment_events.base_enrollment.get_object") def get_object(self): """ Get object for xAPI transformed event. @@ -55,7 +57,7 @@ class EnrollmentActivatedTransformer(BaseEnrollmentTransformer): """ Transformers for event generated when learner enrolls or gets the enrollment mode changed in a course. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_REGISTERED, display=LanguageMap({constants.EN: constants.REGISTERED}), ) @@ -66,7 +68,7 @@ class EnrollmentDeactivatedTransformer(BaseEnrollmentTransformer): """ Transformers for event generated when learner un-enrolls from a course. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_UNREGISTERED, display=LanguageMap({constants.EN: constants.UNREGISTERED}), ) @@ -77,7 +79,7 @@ class CourseGradePassedFirstTimeTransformer(BaseEnrollmentTransformer): """ Transformers for event generated when learner pass course grade first time from a course. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_PASSED, display=LanguageMap({constants.EN: constants.PASSED}), ) @@ -88,7 +90,7 @@ class CourseGradeNowPassedTransformer(BaseEnrollmentTransformer): """ Transformers for event generated when learner pass course grade first time from a course. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_PASSED, display=LanguageMap({constants.EN: constants.PASSED}), ) @@ -99,7 +101,7 @@ class CourseGradeNowFailedTransformer(BaseEnrollmentTransformer): """ Transformers for event generated when learner pass course grade first time from a course. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_FAILED, display=LanguageMap({constants.EN: constants.FAILED}), ) diff --git a/event_routing_backends/processors/xapi/event_transformers/exam_events.py b/event_routing_backends/processors/xapi/event_transformers/exam_events.py index 66ed0164..57f61ef8 100644 --- a/event_routing_backends/processors/xapi/event_transformers/exam_events.py +++ b/event_routing_backends/processors/xapi/event_transformers/exam_events.py @@ -4,6 +4,7 @@ from tincan import Activity, ActivityDefinition, Extensions, LanguageMap, Verb +from event_routing_backends.processors.openedx_filters.decorators import openedx_filter 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 @@ -16,6 +17,7 @@ class BaseExamTransformer(XApiTransformer): exam_type_activity = None + @openedx_filter(filter_type="event_routing_backends.processors.xapi.exam_events.base_exam.get_object") def get_object(self): """ Get object for xAPI transformed event. @@ -101,7 +103,7 @@ class InitializedMixin: Base transformer for initialized exam events """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_INITIALIZED, display=LanguageMap({constants.EN: constants.INITIALIZED}), ) @@ -112,7 +114,7 @@ class TerminatedMixin: Base transformer for terminated exam events """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_TERMINATED, display=LanguageMap({constants.EN: constants.TERMINATED}), ) diff --git a/event_routing_backends/processors/xapi/event_transformers/forum_events.py b/event_routing_backends/processors/xapi/event_transformers/forum_events.py index 92d3cfdc..3ec99afc 100644 --- a/event_routing_backends/processors/xapi/event_transformers/forum_events.py +++ b/event_routing_backends/processors/xapi/event_transformers/forum_events.py @@ -4,6 +4,7 @@ from django.conf import settings from tincan import Activity, ActivityDefinition, LanguageMap, Verb +from event_routing_backends.processors.openedx_filters.decorators import openedx_filter 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 @@ -14,6 +15,7 @@ class BaseForumThreadTransformer(XApiTransformer): Base transformer for forum thread events. """ + @openedx_filter(filter_type="event_routing_backends.processors.xapi.forum_events.base_forum_thread.get_object") def get_object(self): """ Get object for xAPI transformed event related to a thread. @@ -42,7 +44,7 @@ class ThreadCreatedTransformer(BaseForumThreadTransformer): """ Transformers for event generated when learner creates a thread in discussion forum. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_POSTED, display=LanguageMap({constants.EN: constants.POSTED}), ) @@ -69,7 +71,7 @@ class ThreadEditedTransformer(BaseForumThreadTransformer): Transformers for event generated when learner modifies a thread/response/comment in discussion forum. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_EDITED, display=LanguageMap({constants.EN: constants.EDITED}), ) @@ -80,7 +82,7 @@ class ThreadViewedTransformer(BaseForumThreadTransformer): """ Transformers for event generated when learner viewes a thread in discussion forum. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_VIEWED, display=LanguageMap({constants.EN: constants.VIEWED}), ) @@ -94,7 +96,7 @@ class ThreadDeletedTransformer(BaseForumThreadTransformer): Transformers for event generated when learner deletes a thread/response/comment in discussion forum. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_DELETED, display=LanguageMap({constants.EN: constants.DELETED}), ) @@ -106,7 +108,7 @@ class ThreadVotedTransformer(BaseForumThreadTransformer): """ Transformers for event generated when learner votes on a thread/response in discussion forum. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_VOTED, display=LanguageMap({constants.EN: constants.VOTED}), ) @@ -132,7 +134,7 @@ class ThreadResponseCreatedTransformer(BaseForumThreadTransformer): Transformer for event generated when learner creates a response or comment under a thread in discussion forum. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_POSTED, display=LanguageMap({constants.EN: constants.POSTED}), ) @@ -146,7 +148,7 @@ class ThreadResponseReportedTransformer(BaseForumThreadTransformer): Transformer for event generated when learner reports a thread, response or comment as inappropriate. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_REPORTED, display=LanguageMap({constants.EN: constants.REPORTED}), ) @@ -160,7 +162,7 @@ class ThreadResponseUnReportedTransformer(BaseForumThreadTransformer): Transformer for event generated when learner unreports a thread, response or comment which was earlier reported as inappropriate. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_UNREPORTED, display=LanguageMap({constants.EN: constants.UNREPORTED}), ) diff --git a/event_routing_backends/processors/xapi/event_transformers/grading_events.py b/event_routing_backends/processors/xapi/event_transformers/grading_events.py index 24fed82f..1dbc2afd 100644 --- a/event_routing_backends/processors/xapi/event_transformers/grading_events.py +++ b/event_routing_backends/processors/xapi/event_transformers/grading_events.py @@ -4,6 +4,7 @@ from tincan import Activity, ActivityDefinition, Extensions, LanguageMap, Result, Verb from event_routing_backends.helpers import get_course_from_id +from event_routing_backends.processors.openedx_filters.decorators import openedx_filter 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 @@ -15,13 +16,14 @@ class SubsectionGradedTransformer(XApiTransformer): Transformer for event generated when an subsection is graded. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_EARNED, display=LanguageMap({constants.EN: constants.EARNED}), ) additional_fields = ("result",) + @openedx_filter(filter_type="event_routing_backends.processors.xapi.grading_events.subsection_graded.get_object") def get_object(self): """ Get object for xAPI transformed event related to subsection grading. @@ -69,13 +71,14 @@ class CourseGradedTransformer(XApiTransformer): Transformer for event generated when an course is graded. """ - verb = Verb( + _verb = Verb( id=constants.XAPI_VERB_EARNED, display=LanguageMap({constants.EN: constants.EARNED}), ) additional_fields = ("result",) + @openedx_filter(filter_type="event_routing_backends.processors.xapi.grading_events.course_graded.get_object") def get_object(self): """ Get object for xAPI transformed event related to course grading. diff --git a/event_routing_backends/processors/xapi/event_transformers/navigation_events.py b/event_routing_backends/processors/xapi/event_transformers/navigation_events.py index 8402f1dc..731d2ec9 100644 --- a/event_routing_backends/processors/xapi/event_transformers/navigation_events.py +++ b/event_routing_backends/processors/xapi/event_transformers/navigation_events.py @@ -3,6 +3,7 @@ """ from tincan import Activity, ActivityDefinition, Extensions, LanguageMap +from event_routing_backends.processors.openedx_filters.decorators import openedx_filter 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, XApiVerbTransformerMixin @@ -50,6 +51,7 @@ class LinkClickedTransformer(NavigationTransformersMixin): xAPI transformer for event generated when user clicks a link. """ + @openedx_filter(filter_type="event_routing_backends.processors.xapi.navigation_events.link_clicked.get_object") def get_object(self): """ Get object for xAPI transformed event. @@ -72,6 +74,7 @@ class OutlineSelectedTransformer(NavigationTransformersMixin): xAPI transformer for Navigation events. """ + @openedx_filter(filter_type="event_routing_backends.processors.xapi.navigation_events.outline_selected.get_object") def get_object(self): """ Get object for xAPI transformed event. @@ -96,6 +99,7 @@ class TabNavigationTransformer(NavigationTransformersMixin): xAPI transformer for Navigation events. """ + @openedx_filter(filter_type="event_routing_backends.processors.xapi.navigation_events.tab_navigation.get_object") def get_object(self): """ Get object for xAPI transformed event. diff --git a/event_routing_backends/processors/xapi/event_transformers/problem_interaction_events.py b/event_routing_backends/processors/xapi/event_transformers/problem_interaction_events.py index 7db335bd..b203a931 100644 --- a/event_routing_backends/processors/xapi/event_transformers/problem_interaction_events.py +++ b/event_routing_backends/processors/xapi/event_transformers/problem_interaction_events.py @@ -6,6 +6,7 @@ from tincan import Activity, ActivityDefinition, Extensions, LanguageMap, Result from event_routing_backends.helpers import get_problem_block_id +from event_routing_backends.processors.openedx_filters.decorators import openedx_filter from event_routing_backends.processors.xapi import constants from event_routing_backends.processors.xapi.registry import XApiTransformersRegistry from event_routing_backends.processors.xapi.statements import GroupActivity @@ -78,6 +79,9 @@ class BaseProblemsTransformer(XApiTransformer, XApiVerbTransformerMixin): """ verb_map = VERB_MAP + @openedx_filter( + filter_type="event_routing_backends.processors.xapi.problem_interaction_events.base_problems.get_object", + ) def get_object(self): """ Get object for xAPI transformed event. @@ -212,6 +216,9 @@ class BaseProblemCheckTransformer(BaseProblemsTransformer): """ additional_fields = ('result', ) + @openedx_filter( + filter_type="event_routing_backends.processors.xapi.problem_interaction_events.base_problem_check.get_object", + ) def get_object(self): """ Get object for xAPI transformed event. diff --git a/event_routing_backends/processors/xapi/event_transformers/video_events.py b/event_routing_backends/processors/xapi/event_transformers/video_events.py index 412b3310..6341dee4 100644 --- a/event_routing_backends/processors/xapi/event_transformers/video_events.py +++ b/event_routing_backends/processors/xapi/event_transformers/video_events.py @@ -35,6 +35,7 @@ from tincan import Activity, ActivityDefinition, Extensions, Result from event_routing_backends.helpers import convert_seconds_to_float, make_video_block_id +from event_routing_backends.processors.openedx_filters.decorators import openedx_filter 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, XApiVerbTransformerMixin @@ -133,6 +134,7 @@ class BaseVideoTransformer(XApiTransformer, XApiVerbTransformerMixin): """ verb_map = VERB_MAP + @openedx_filter(filter_type="event_routing_backends.processors.xapi.video_events.base_video.get_object") def get_object(self): """ Get object for xAPI transformed event. diff --git a/event_routing_backends/processors/xapi/transformer.py b/event_routing_backends/processors/xapi/transformer.py index e946d602..d38b2375 100644 --- a/event_routing_backends/processors/xapi/transformer.py +++ b/event_routing_backends/processors/xapi/transformer.py @@ -20,6 +20,7 @@ from event_routing_backends.helpers import get_anonymous_user_id, get_course_from_id, get_user_email, get_uuid5 from event_routing_backends.processors.mixins.base_transformer import BaseTransformerMixin +from event_routing_backends.processors.openedx_filters.decorators import openedx_filter from event_routing_backends.processors.xapi import constants @@ -76,8 +77,9 @@ def get_event_id(self): actor = self.get_actor() event_timestamp = self.get_timestamp() uuid_str = f'{actor.to_json()}-{event_timestamp}' - return get_uuid5(self.verb.to_json(), uuid_str) # pylint: disable=no-member + return get_uuid5(self.get_verb().to_json(), uuid_str) + @openedx_filter(filter_type="event_routing_backends.processors.xapi.transformer.xapi_transformer.get_actor") def get_actor(self): """ Return `Agent` object for the event. @@ -100,6 +102,14 @@ def get_actor(self): ) return agent + @openedx_filter(filter_type="event_routing_backends.processors.xapi.transformer.xapi_transformer.get_verb") + def get_verb(self): + """ + This intercepts the super verb value or the attribute class `_verb` in order to allow the openedx + filters implementation. + """ + return super().get_verb() if hasattr(super(), "get_verb") else self._verb # pylint: disable=no-member + def get_timestamp(self): """ Get the Timestamp for the statement. @@ -167,8 +177,7 @@ class XApiVerbTransformerMixin: """ verb_map = None - @property - def verb(self): + def get_verb(self): """ Get verb for xAPI transformed event. @@ -260,6 +269,7 @@ class OneToManyChildXApiTransformerMixin: The parent event transformer should inherit from OneToManyXApiTransformer. """ + def __init__(self, parent, child_id, *args, **kwargs): """ Stores the parent event transformer, and this child's identifier, @@ -286,7 +296,7 @@ def get_event_id(self): actor = self.get_actor() event_timestamp = self.get_timestamp() name = f'{actor.to_json()}-{event_timestamp}' - namespace_key = f'{self.verb.to_json()}-{self.child_id}' + namespace_key = f'{self.get_verb().to_json()}-{self.child_id}' return get_uuid5(namespace_key, name) def get_context(self): diff --git a/requirements/base.in b/requirements/base.in index d373897b..f9fdf05e 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -15,3 +15,4 @@ event-tracking edx-celeryutils apache-libcloud # For bulk event log loading fasteners # Locking tools, required by apache-libcloud, but somehow not installed with it +openedx-filters diff --git a/requirements/base.txt b/requirements/base.txt index 12b5ea5a..794313f8 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -63,6 +63,7 @@ django==3.2.21 # edx-toggles # event-tracking # jsonfield + # openedx-filters django-config-models==2.5.0 # via -r requirements/base.in django-crum==0.7.9 @@ -108,6 +109,8 @@ markupsafe==2.1.3 # via jinja2 newrelic==9.0.0 # via edx-django-utils +openedx-filters==1.6.0 + # via -r requirements/base.in pbr==5.11.1 # via stevedore prompt-toolkit==3.0.39 diff --git a/requirements/dev.txt b/requirements/dev.txt index 36b67435..f77b24e4 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -128,6 +128,7 @@ django==3.2.21 # edx-toggles # event-tracking # jsonfield + # openedx-filters django-config-models==2.5.0 # via -r requirements/quality.txt django-crum==0.7.9 @@ -237,6 +238,8 @@ newrelic==9.0.0 # via # -r requirements/quality.txt # edx-django-utils +openedx-filters==1.6.0 + # via -r requirements/quality.txt packaging==23.1 # via # -r requirements/ci.txt diff --git a/requirements/doc.txt b/requirements/doc.txt index 788e084a..2b5a6230 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -108,6 +108,7 @@ django==3.2.21 # edx-toggles # event-tracking # jsonfield + # openedx-filters django-config-models==2.5.0 # via -r requirements/test.txt django-crum==0.7.9 @@ -224,6 +225,8 @@ newrelic==9.0.0 # edx-django-utils nh3==0.2.14 # via readme-renderer +openedx-filters==1.6.0 + # via -r requirements/test.txt packaging==23.1 # via # -r requirements/test.txt diff --git a/requirements/quality.txt b/requirements/quality.txt index a0708df2..6d1261e3 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -106,6 +106,7 @@ django==3.2.21 # edx-toggles # event-tracking # jsonfield + # openedx-filters django-config-models==2.5.0 # via -r requirements/test.txt django-crum==0.7.9 @@ -194,6 +195,8 @@ newrelic==9.0.0 # via # -r requirements/test.txt # edx-django-utils +openedx-filters==1.6.0 + # via -r requirements/test.txt packaging==23.1 # via # -r requirements/test.txt diff --git a/requirements/test.txt b/requirements/test.txt index 4cb7c85b..ed0f2d0b 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -93,6 +93,7 @@ ddt==1.6.0 # edx-toggles # event-tracking # jsonfield + # openedx-filters django-config-models==2.5.0 # via -r requirements/base.txt django-crum==0.7.9 @@ -165,6 +166,8 @@ newrelic==9.0.0 # via # -r requirements/base.txt # edx-django-utils +openedx-filters==1.6.0 + # via -r requirements/base.txt packaging==23.1 # via pytest pbr==5.11.1 From e9fd098c9fb72f585829cdfce907256dea2703e5 Mon Sep 17 00:00:00 2001 From: andrey-canon Date: Mon, 20 Nov 2023 15:48:35 -0500 Subject: [PATCH 2/2] feat: bumpversion --- CHANGELOG.rst | 5 +++++ event_routing_backends/__init__.py | 2 +- setup.cfg | 7 +++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4239cb5d..142edd6e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,11 @@ Change Log Unreleased ~~~~~~~~~~ +[7.1.0] +~~~~~~~ + +* Add support for openedx-filter that basically allows to extend or change the standard behavior + [7.0.2] ~~~~~~~ diff --git a/event_routing_backends/__init__.py b/event_routing_backends/__init__.py index 91d6684f..3da601ef 100644 --- a/event_routing_backends/__init__.py +++ b/event_routing_backends/__init__.py @@ -2,4 +2,4 @@ Various backends for receiving edX LMS events.. """ -__version__ = '7.0.2' +__version__ = '7.1.0' diff --git a/setup.cfg b/setup.cfg index 4d843521..0fc6b037 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,10 @@ +[bumpversion] +current_version = 7.1.0 +commit = False +tag = False + +[bumpversion:file:event_routing_backends/__init__.py] + [isort] include_trailing_comma = True indent = ' '