Skip to content

Commit

Permalink
feat: adds xApiTransforms for completion aggregator events
Browse files Browse the repository at this point in the history
* adds dependency on edx-event-routing-backends
* adds transformers to xapi.completion and xapi.progress
* adds the completion_aggregator events to the event tracking whitelist
  • Loading branch information
pomegranited committed Jun 12, 2024
1 parent 4a229fd commit 7652429
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 1 deletion.
6 changes: 5 additions & 1 deletion completion_aggregator/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ def ready(self):
"""
Load signal handlers when the app is ready.
"""
# pylint: disable=import-outside-toplevel
from . import signals
signals.register()
from .tasks import aggregation_tasks, handler_tasks # pylint: disable=unused-import

# pylint: disable=unused-import
from . import transformers
from .tasks import aggregation_tasks, handler_tasks
5 changes: 5 additions & 0 deletions completion_aggregator/settings/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@

from __future__ import absolute_import, division, print_function, unicode_literals

from event_routing_backends.settings import production as erb_settings

Check warning on line 7 in completion_aggregator/settings/aws.py

View check run for this annotation

Codecov / codecov/patch

completion_aggregator/settings/aws.py#L7

Added line #L7 was not covered by tests


def plugin_settings(settings):
"""
Modify the provided settings object with settings specific to this plugin.
"""
# Load Event Routing Backend production settings first.
erb_settings.plugin_settings(settings)

Check warning on line 15 in completion_aggregator/settings/aws.py

View check run for this annotation

Codecov / codecov/patch

completion_aggregator/settings/aws.py#L15

Added line #L15 was not covered by tests

settings.COMPLETION_AGGREGATOR_BLOCK_TYPES = set(settings.ENV_TOKENS.get(
'COMPLETION_AGGREGATOR_BLOCK_TYPES',
settings.COMPLETION_AGGREGATOR_BLOCK_TYPES,
Expand Down
16 changes: 16 additions & 0 deletions completion_aggregator/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from __future__ import absolute_import, division, print_function, unicode_literals

from event_routing_backends.utils.settings import event_tracking_backends_config


def plugin_settings(settings):
"""
Expand Down Expand Up @@ -32,6 +34,7 @@ def plugin_settings(settings):
'sequential',
'vertical',
}

settings.COMPLETION_AGGREGATOR_ASYNC_AGGREGATION = False

# Names of the batch operations locks
Expand All @@ -52,3 +55,16 @@ def plugin_settings(settings):
# 1. All courses should be reaggregated for the changes to take effect.
# 2. It's not possible to revert this change by reaggregation without manually removing existing Aggregators.
settings.COMPLETION_AGGREGATOR_AGGREGATE_UNRELEASED_BLOCKS = False

# Whitelist the aggregator events for use with event routing backends xAPI backend.
enabled_aggregator_events = [
f'openedx.completion_aggregator.{event_type}.{block_type}'

for event_type in settings.ALLOWED_COMPLETION_AGGREGATOR_EVENT_TYPES
for block_type in settings.ALLOWED_COMPLETION_AGGREGATOR_EVENT_TYPES[event_type]
]
settings.EVENT_TRACKING_BACKENDS_ALLOWED_XAPI_EVENTS += enabled_aggregator_events
settings.EVENT_TRACKING_BACKENDS.update(event_tracking_backends_config(
settings.EVENT_TRACKING_BACKENDS_ALLOWED_XAPI_EVENTS,
settings.EVENT_TRACKING_BACKENDS_ALLOWED_CALIPER_EVENTS,
))
121 changes: 121 additions & 0 deletions completion_aggregator/transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
except ImportError:
BlockStructureTransformer = object

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
from tincan import Activity, ActivityDefinition, LanguageMap, Result, Verb
from xblock.completable import XBlockCompletionMode


Expand Down Expand Up @@ -79,3 +84,119 @@ def transform(self, usage_info, block_structure): # pylint: disable=unused-argu
if completion_mode != XBlockCompletionMode.EXCLUDED:
aggregators = self.calculate_aggregators(block_structure, block_key)
block_structure.set_transformer_block_field(block_key, self, self.AGGREGATORS, aggregators)


class BaseAggregatorXApiTransformer(XApiTransformer):
"""
Base transformer for all completion aggregator events.
"""

object_type = None

def get_object(self) -> Activity:
"""
Get object for xAPI transformed event.
"""
if not self.object_type:
raise NotImplementedError()

Check warning on line 101 in completion_aggregator/transformers.py

View check run for this annotation

Codecov / codecov/patch

completion_aggregator/transformers.py#L101

Added line #L101 was not covered by tests

return Activity(

Check warning on line 103 in completion_aggregator/transformers.py

View check run for this annotation

Codecov / codecov/patch

completion_aggregator/transformers.py#L103

Added line #L103 was not covered by tests
id=self.get_object_iri("xblock", self.get_data("data.block_id")),
definition=ActivityDefinition(
type=self.object_type,
),
)


class BaseProgressTransformer(BaseAggregatorXApiTransformer):
"""
Base transformer for completion aggregator progress events.
"""

_verb = Verb(
id=constants.XAPI_VERB_PROGRESSED,
display=LanguageMap({constants.EN: constants.PROGRESSED}),
)
object_type = None
additional_fields = ('result', )

@openedx_filter(
filter_type="completion_aggregator.xapi.base_progress.get_object",
)
def get_object(self) -> Activity:
"""
Get object for xAPI transformed event.
"""
return super().get_object()

Check warning on line 130 in completion_aggregator/transformers.py

View check run for this annotation

Codecov / codecov/patch

completion_aggregator/transformers.py#L130

Added line #L130 was not covered by tests

def get_result(self) -> Result:
"""
Get result for xAPI transformed event.
"""
return Result(

Check warning on line 136 in completion_aggregator/transformers.py

View check run for this annotation

Codecov / codecov/patch

completion_aggregator/transformers.py#L136

Added line #L136 was not covered by tests
completion=self.get_data("data.percent") == 1.0,
score={
"scaled": self.get_data("data.percent") or 0
}
)


@XApiTransformersRegistry.register("openedx.completion_aggregator.progress.chapter")
@XApiTransformersRegistry.register("openedx.completion_aggregator.progress.sequential")
@XApiTransformersRegistry.register("openedx.completion_aggregator.progress.vertical")
class ModuleProgressTransformer(BaseProgressTransformer):
"""
Transformer for event generated when a user makes progress in a section, subsection or unit.
"""

object_type = constants.XAPI_ACTIVITY_MODULE


@XApiTransformersRegistry.register("openedx.completion_aggregator.progress.course")
class CourseProgressTransformer(BaseProgressTransformer):
"""
Transformer for event generated when a user makes progress in a course.
"""

object_type = constants.XAPI_ACTIVITY_COURSE


class BaseCompletionTransformer(BaseAggregatorXApiTransformer):
"""
Base transformer for aggregator completion events.
"""

_verb = Verb(
id=constants.XAPI_VERB_COMPLETED,
display=LanguageMap({constants.EN: constants.COMPLETED}),
)
object_type = None

@openedx_filter(
filter_type="completion_aggregator.xapi.base_completion.get_object",
)
def get_object(self) -> Activity:
"""
Get object for xAPI transformed event.
"""
return super().get_object()

Check warning on line 182 in completion_aggregator/transformers.py

View check run for this annotation

Codecov / codecov/patch

completion_aggregator/transformers.py#L182

Added line #L182 was not covered by tests


@XApiTransformersRegistry.register("openedx.completion_aggregator.completion.chapter")
@XApiTransformersRegistry.register("openedx.completion_aggregator.completion.sequential")
@XApiTransformersRegistry.register("openedx.completion_aggregator.completion.vertical")
class ModuleCompletionTransformer(BaseCompletionTransformer):
"""
Transformer for events generated when a user completes a section, subsection or unit.
"""

object_type = constants.XAPI_ACTIVITY_MODULE


@XApiTransformersRegistry.register("openedx.completion_aggregator.completion.course")
class CourseCompletionTransformer(BaseCompletionTransformer):
"""
Transformer for event generated when a user completes a course.
"""

object_type = constants.XAPI_ACTIVITY_COURSE
39 changes: 39 additions & 0 deletions tests/test_plugin_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""
Test the aggregator plugin settings.
"""
from event_routing_backends.settings import common as erb_settings

from django.conf import settings

from completion_aggregator.settings import common as common_settings


def test_event_tracking_backends():
"""
Test that the completion aggregator events are whitelisted on the ERB backends.
"""
# Event Routing Backend settings must be loaded first.
erb_settings.plugin_settings(settings)
common_settings.plugin_settings(settings)

transformer_options = settings.EVENT_TRACKING_BACKENDS['event_transformer']['OPTIONS']
toplevel_whitelist = set(transformer_options['processors'][0]['OPTIONS']['whitelist'])
xapi_whitelist = set(transformer_options['backends']['xapi']['OPTIONS']['processors'][0]['OPTIONS']['whitelist'])

assert toplevel_whitelist, "No whitelist found in event_transformer processors?"
assert xapi_whitelist, "No whitelist found in event_transformer processors?"

expected_events = {
'openedx.completion_aggregator.progress.course',
'openedx.completion_aggregator.progress.chapter',
'openedx.completion_aggregator.progress.sequential',
'openedx.completion_aggregator.progress.vertical',
'openedx.completion_aggregator.completion.course',
'openedx.completion_aggregator.completion.chapter',
'openedx.completion_aggregator.completion.sequential',
'openedx.completion_aggregator.completion.vertical',
}

# Ensure expected_events is a subset of these whitelists
assert expected_events < toplevel_whitelist, "Aggregator events not found in event_transformer whitelist"
assert expected_events < xapi_whitelist, "Aggregator events not found in xapi whitelist"

0 comments on commit 7652429

Please sign in to comment.