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 settings whitelist
  • Loading branch information
pomegranited committed Jun 11, 2024
1 parent 407a1d1 commit f886219
Show file tree
Hide file tree
Showing 14 changed files with 496 additions and 24 deletions.
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 .tasks import aggregation_tasks, handler_tasks
from .xapi import completion, progress
24 changes: 24 additions & 0 deletions completion_aggregator/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,30 @@ def plugin_settings(settings):
'sequential',
'vertical',
}

# Whitelist the allowed xAPI-transformed events for use with the xAPI backends
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]
]
global_processors = settings.EVENT_TRACKING_BACKENDS.get(
'event_transformer', {}).get(
'OPTIONS', {}).get(
'processors', [])
xapi_processors = settings.EVENT_TRACKING_BACKENDS.get(
'event_transformer', {}).get(
'OPTIONS', {}).get(
'backends', {}).get(
'xapi', {}).get(
'OPTIONS', {}).get(
'processors', [])
for processor in global_processors + xapi_processors:
if 'whitelist' in processor.get('OPTIONS', {}):
for event_name in enabled_aggregator_events:
processor['OPTIONS']['whitelist'].append(event_name)

settings.COMPLETION_AGGREGATOR_ASYNC_AGGREGATION = False

# Names of the batch operations locks
Expand Down
Empty file.
30 changes: 30 additions & 0 deletions completion_aggregator/xapi/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
xAPI Transformers for aggregation completion and progress events.
"""

from event_routing_backends.processors.xapi.transformer import XApiTransformer
from tincan import Activity, ActivityDefinition


class BaseAggregatorTransformer(XApiTransformer):
"""
Base transformer for all aggregator events.
"""
object_type = None

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

return Activity(
id=self.get_object_iri("xblock", self.get_data("data.block_id")),
definition=ActivityDefinition(
type=self.object_type,
),
)
51 changes: 51 additions & 0 deletions completion_aggregator/xapi/completion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
xAPI Transformers for aggregation completion events.
"""

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 tincan import LanguageMap, Verb

from .base import BaseAggregatorTransformer


class BaseCompletionTransformer(BaseAggregatorTransformer):
"""
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):
"""
Get object for xAPI transformed event.
Returns:
`Activity`
"""
return super().get_object()


@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
64 changes: 64 additions & 0 deletions completion_aggregator/xapi/progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""
xAPI Transformers for aggregation progress events.
"""

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 tincan import LanguageMap, Result, Verb

from .base import BaseAggregatorTransformer


class BaseProgressTransformer(BaseAggregatorTransformer):
"""
Base transformer for 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):
"""
Get object for xAPI transformed event.
Returns:
`Activity`
"""
return super().get_object()

def get_result(self):
"""
Get result for xAPI transformed event.
Returns:
`Result`
"""
return Result(
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
1 change: 1 addition & 0 deletions requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ edx-toggles
event-tracking
six
XBlock[django]
edx-event-routing-backends # Provides xAPI transforms for aggregator events
56 changes: 51 additions & 5 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
#
amqp==5.2.0
# via kombu
aniso8601==9.0.1
# via tincan
apache-libcloud==3.8.0
# via edx-event-routing-backends
appdirs==1.4.4
# via fs
asgiref==3.7.2
# via django
async-timeout==4.0.3
# via redis
attrs==23.2.0
# via openedx-events
backports-zoneinfo[tzdata]==0.2.1
Expand Down Expand Up @@ -54,34 +60,47 @@ click-repl==0.3.0
code-annotations==1.6.0
# via edx-toggles
cryptography==42.0.5
# via pyjwt
# via
# django-fernet-fields-v2
# pyjwt
django==3.2.24
# via
# -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt
# -r requirements/base.in
# django-config-models
# django-crum
# django-fernet-fields-v2
# django-model-utils
# django-redis
# django-waffle
# djangorestframework
# drf-jwt
# edx-celeryutils
# edx-completion
# edx-django-utils
# edx-drf-extensions
# edx-event-routing-backends
# edx-toggles
# event-tracking
# jsonfield
# openedx-django-pyfs
# openedx-events
# openedx-filters
django-config-models==2.7.0
# via edx-event-routing-backends
django-crum==0.7.9
# via
# edx-django-utils
# edx-toggles
django-fernet-fields-v2==0.9
# via edx-event-routing-backends
django-model-utils==4.4.0
# via
# -r requirements/base.in
# edx-celeryutils
# edx-completion
django-redis==5.4.0
# via edx-event-routing-backends
django-waffle==4.1.0
# via
# edx-django-utils
Expand All @@ -90,23 +109,29 @@ django-waffle==4.1.0
djangorestframework==3.14.0
# via
# -r requirements/base.in
# django-config-models
# drf-jwt
# edx-completion
# edx-drf-extensions
drf-jwt==1.19.2
# via edx-drf-extensions
edx-celeryutils==1.2.5
# via -r requirements/base.in
# via
# -r requirements/base.in
# edx-event-routing-backends
edx-completion==4.6.0
# via -r requirements/base.in
edx-django-utils==5.10.1
# via
# django-config-models
# edx-drf-extensions
# edx-toggles
# event-tracking
# openedx-events
edx-drf-extensions==10.2.0
# via edx-completion
edx-event-routing-backends==8.3.1
# via -r requirements/base.in
edx-opaque-keys[django]==2.5.1
# via
# -r requirements/base.in
Expand All @@ -117,13 +142,17 @@ edx-toggles==5.1.1
# via
# -r requirements/base.in
# edx-completion
# edx-event-routing-backends
# event-tracking
event-tracking==2.3.0
# via
# -r requirements/base.in
# edx-completion
# edx-completion
# edx-event-routing-backends
fastavro==1.9.4
# via openedx-events
fasteners==0.19
# via edx-event-routing-backends
fs==2.4.16
# via
# fs-s3fs
Expand All @@ -133,14 +162,18 @@ fs-s3fs==1.1.1
# via openedx-django-pyfs
idna==3.6
# via requests
isodate==0.6.1
# via edx-event-routing-backends
jinja2==3.1.3
# via code-annotations
jmespath==1.0.1
# via
# boto3
# botocore
jsonfield==3.1.0
# via edx-celeryutils
# via
# edx-celeryutils
# edx-event-routing-backends
kombu==5.3.5
# via celery
lazy==1.6
Expand All @@ -160,6 +193,8 @@ openedx-django-pyfs==3.5.0
# via xblock
openedx-events==9.5.2
# via event-tracking
openedx-filters==1.8.1
# via edx-event-routing-backends
pbr==6.0.0
# via stevedore
prompt-toolkit==3.0.43
Expand All @@ -182,6 +217,7 @@ python-dateutil==2.8.2
# via
# botocore
# celery
# edx-event-routing-backends
# xblock
python-slugify==8.0.4
# via code-annotations
Expand All @@ -190,14 +226,21 @@ pytz==2024.1
# django
# djangorestframework
# edx-completion
# edx-event-routing-backends
# event-tracking
# tincan
# xblock
pyyaml==6.0.1
# via
# code-annotations
# xblock
redis==5.0.5
# via django-redis
requests==2.31.0
# via edx-drf-extensions
# via
# apache-libcloud
# edx-drf-extensions
# edx-event-routing-backends
s3transfer==0.10.0
# via boto3
semantic-version==2.10.0
Expand All @@ -210,6 +253,7 @@ six==1.16.0
# event-tracking
# fs
# fs-s3fs
# isodate
# python-dateutil
sqlparse==0.4.4
# via django
Expand All @@ -220,6 +264,8 @@ stevedore==5.2.0
# edx-opaque-keys
text-unidecode==1.3
# via python-slugify
tincan==1.0.0
# via edx-event-routing-backends
typing-extensions==4.10.0
# via
# asgiref
Expand Down
4 changes: 1 addition & 3 deletions requirements/ci.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ filelock==3.13.1
packaging==23.2
# via tox
platformdirs==4.2.0
# via
# tox
# virtualenv
# via virtualenv
pluggy==0.13.1
# via
# -c requirements/constraints.txt
Expand Down
Loading

0 comments on commit f886219

Please sign in to comment.