diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 981476d5..381c185f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,10 @@ Change Log Unreleased ~~~~~~~~~~ +[6.2.0] + +* Add support for completion events + [6.1.0] * Add support for exam attempts events diff --git a/docs/event-mapping/Supported_events.rst b/docs/event-mapping/Supported_events.rst index 8aa72245..7b41faf7 100644 --- a/docs/event-mapping/Supported_events.rst +++ b/docs/event-mapping/Supported_events.rst @@ -14,10 +14,14 @@ Enrollment events Course grading events ----------------------- -* edx.course.grade.passed.first_time | edX `sample <../../event_routing_backends/processors/tests/fixtures/current/edx.course.grade.passed.first_time.json>`__ | xAPI `map <./xAPI_mapping.rst#edx-course-grade-passed-first-time>`__ , `sample <../../event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.passed.first_time.json>`__ | Caliper `map <./Caliper_mapping.rst#edx-course-grade-passed-first-time>`__ , `sample <../../event_routing_backends/processors/caliper/tests/fixtures/expected/edx.course.grade.passed.first_time.json>`__ +* `edx.course.grade.passed.first_time`_ | edX `sample <../../event_routing_backends/processors/tests/fixtures/current/edx.course.grade.passed.first_time.json>`__ | xAPI `map <./xAPI_mapping.rst#edx-course-grade-passed-first-time>`__ , `sample <../../event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.passed.first_time.json>`__ | Caliper `map <./Caliper_mapping.rst#edx-course-grade-passed-first-time>`__ , `sample <../../event_routing_backends/processors/caliper/tests/fixtures/expected/edx.course.grade.passed.first_time.json>`__ * `edx.grades.subsection.grade_calculated`_ | edX `sample <../../event_routing_backends/processors/tests/fixtures/current/edx.grades.subsection.grade_calculated.json>`__ | xAPI `map <./xAPI_mapping.rst#edx-grades-subsection-grade-calculated>`__ , `sample <../../event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.subsection.grade_calculated.json>`__ * `edx.grades.course.grade_calculated`_ | edX `sample <../../event_routing_backends/processors/tests/fixtures/current/edx.grades.course.grade_calculated.json>`__ | xAPI `map <./xAPI_mapping.rst#edx-grades-course-grade-calculated>`__ , `sample <../../event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.course.grade_calculated.json>`__ +Completion events + +* `edx.completion.block_completion.changed`_ | edX `sample <../../event_routing_backends/processors/tests/fixtures/current/edx.completion.block_completion.changed.json>`___ | xAPI `map <./xAPI_mapping.rst#edx-completion-block-completion-changed>`__ , `sample <../../event_routing_backends/processors/xapi/tests/fixtures/expected/edx.completion.block_completion.changed.json>`__ + Problem interaction events --------------------------- @@ -84,8 +88,10 @@ Exam events .. _edx.course.enrollment.activated: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/student_event_types.html#edx-course-enrollment-activated-and-edx-course-enrollment-deactivated .. _edx.course.enrollment.deactivated: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/student_event_types.html#edx-course-enrollment-activated-and-edx-course-enrollment-deactivated .. _edx.course.enrollment.mode_changed: https://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/student_event_types.html#edx-course-enrollment-mode-changed +.. _edx.course.grade.passed.first_time: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/course_team_event_types.html#edx-course-grade-passed-first-time .. _edx.grades.subsection.grade_calculated: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/course_team_event_types.html#edx-grades-subsection-grade-calculated .. _edx.grades.course.grade_calculated: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/course_team_event_types.html#edx-grades-course-grade-calculated +.. _edx.completion.block_completion.changed: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/student_event_types.html#edx-completion-block-completion-changed .. _edx.grades.problem.submitted: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/course_team_event_types.html#edx-grades-problem-submitted .. _problem_check: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/student_event_types.html#problem-check .. _showanswer: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/student_event_types.html#showanswer diff --git a/event_routing_backends/__init__.py b/event_routing_backends/__init__.py index 65bf3aef..76a6b185 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__ = '6.1.0' +__version__ = '6.2.0' diff --git a/event_routing_backends/processors/tests/fixtures/current/edx.completion.block_completion.changed.json b/event_routing_backends/processors/tests/fixtures/current/edx.completion.block_completion.changed.json new file mode 100644 index 00000000..ea994689 --- /dev/null +++ b/event_routing_backends/processors/tests/fixtures/current/edx.completion.block_completion.changed.json @@ -0,0 +1,33 @@ +{ + "name": "edx.completion.block_completion.changed", + "timestamp": "2023-08-22T20:16:25.500832Z", + "data": { + "user_id": 4, + "course_id": "course-v1:edX+DemoX+Demo_Course", + "context_key": "course-v1:edX+DemoX+Demo_Course", + "block_id": "block-v1:edX+DemoX+Demo_Course+type@problem+block@7c54b16c8ed34f9f8772015178c7a175", + "block_type": "problem", + "completion": 1.0, + "is_new": false + }, + "context": { + "course_id": "course-v1:edX+DemoX+Demo_Course", + "course_user_tags": {}, + "session": "056aca2a1c6b76742b283e73d3424453", + "user_id": 3, + "username": "edx", + "ip": "172.18.0.1", + "host": "localhost:18000", + "agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36", + "path": "/courses/course-v1:edX+DemoX+Demo_Course/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@7c54b16c8ed34f9f8772015178c7a175/handler/xmodule_handler/problem_check", + "referer": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course+type@vertical+block@dd8110c941b94d929b56841195213797?show_title=0&show_bookmark_button=0&recheck_access=1&view=student_view", + "accept_language": "en-US,en;q=0.9,es;q=0.8", + "client_id": null, + "org_id": "edX", + "enterprise_uuid": "", + "module": { + "display_name": "Checkboxes", + "usage_key": "block-v1:edX+DemoX+Demo_Course+type@problem+block@7c54b16c8ed34f9f8772015178c7a175" + } + } +} diff --git a/event_routing_backends/processors/xapi/constants.py b/event_routing_backends/processors/xapi/constants.py index d6d54b23..0d2a7cec 100644 --- a/event_routing_backends/processors/xapi/constants.py +++ b/event_routing_backends/processors/xapi/constants.py @@ -23,6 +23,7 @@ XAPI_VERB_UNREPORTED = 'https://w3id.org/xapi/openedx/verb/unreported' XAPI_VERB_EARNED = 'http://id.tincanapi.com/verb/earned' +XAPI_VERB_PROGRESSED = 'http://adlnet.gov/expapi/verbs/progressed' XAPI_VERB_TERMINATED = 'http://adlnet.gov/expapi/verbs/terminated' XAPI_VERB_ASKED = 'http://adlnet.gov/expapi/verbs/asked' @@ -53,6 +54,8 @@ XAPI_ACTIVITY_TIMED_ASSESSMENT = 'https://w3id.org/xapi/openedx/activity/timed-assessment' XAPI_ACTIVITY_PRACTICE_ASSESSMENT = 'https://w3id.org/xapi/openedx/activity/practice-assessment' XAPI_ACTIVITY_PROCTORED_ASSESSMENT = 'https://w3id.org/xapi/openedx/activity/proctored-assessment' +XAPI_ACTIVITY_PROGRESS = 'https://w3id.org/xapi/cmi5/result/extensions/progress' + # xAPI context XAPI_CONTEXT_VIDEO_LENGTH = 'https://w3id.org/xapi/video/extensions/length' XAPI_CONTEXT_VIDEO_CC_LANGUAGE = 'https://w3id.org/xapi/video/extensions/cc-subtitle-lang' @@ -112,6 +115,7 @@ REPORTED = 'reported' UNREPORTED = 'unreported' EARNED = 'earned' +PROGRESSED = 'progressed' TERMINATED = 'terminated' NAVIGATED = 'navigated' diff --git a/event_routing_backends/processors/xapi/event_transformers/__init__.py b/event_routing_backends/processors/xapi/event_transformers/__init__.py index 5a285b3b..2383cb8e 100644 --- a/event_routing_backends/processors/xapi/event_transformers/__init__.py +++ b/event_routing_backends/processors/xapi/event_transformers/__init__.py @@ -2,6 +2,7 @@ All xAPI transformers. """ +from event_routing_backends.processors.xapi.event_transformers.completion_events import CompletionCreatedTransformer from event_routing_backends.processors.xapi.event_transformers.enrollment_events import ( EnrollmentActivatedTransformer, EnrollmentDeactivatedTransformer, diff --git a/event_routing_backends/processors/xapi/event_transformers/completion_events.py b/event_routing_backends/processors/xapi/event_transformers/completion_events.py new file mode 100644 index 00000000..d9ce0464 --- /dev/null +++ b/event_routing_backends/processors/xapi/event_transformers/completion_events.py @@ -0,0 +1,50 @@ +""" +Transformers for forum related events. +""" +from tincan import Activity, ActivityDefinition, Extensions, LanguageMap, Result, Verb + +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 + + +@XApiTransformersRegistry.register("edx.completion.block_completion.changed") +class CompletionCreatedTransformer(XApiTransformer): + """ + Transformers for event generated when an student completion is created or updated. + """ + + verb = Verb( + id=constants.XAPI_VERB_PROGRESSED, + display=LanguageMap({constants.EN: constants.PROGRESSED}), + ) + + additional_fields = ('result', ) + + def get_object(self): + """ + Get object for xAPI transformed event related to a thread. + + Returns: + `Activity` + """ + return Activity( + id=self.get_object_iri("xblock", self.get_data("data.block_id")), + definition=ActivityDefinition( + type=constants.XAPI_ACTIVITY_RESOURCE, + ), + ) + + def get_result(self): + """ + Get result for xAPI transformed event related to a thread. + + Returns: + `Result` + """ + return Result( + completion=self.get_data("data.completion") == 1.0, + extensions=Extensions( + {constants.XAPI_ACTIVITY_PROGRESS: self.get_data("data.completion")*100} + ), + ) diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.completion.block_completion.changed.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.completion.block_completion.changed.json new file mode 100644 index 00000000..4ea719af --- /dev/null +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.completion.block_completion.changed.json @@ -0,0 +1,45 @@ +{ + "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "result": { + "completion": true, + "extensions": { "https://w3id.org/xapi/cmi5/result/extensions/progress": 100.0 } + }, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "http://adlnet.gov/expapi/verbs/progressed", + "display": { "en": "progressed" } +}, + "object": { + "id": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@7c54b16c8ed34f9f8772015178c7a175", + "objectType": "Activity", + "definition": { + "type": "http://id.tincanapi.com/activitytype/resource" + } + }, + "timestamp": "2023-08-22T20:16:25.500832+00:00", + "context": { + "contextActivities": { + "parent": [ + { + "id": "http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course", + "objectType": "Activity", + "definition": { + "name": { "en-US": "Demonstration Course" }, + "type": "http://adlnet.gov/expapi/activities/course" + } + } + ] + }, + "extensions": { + "https://w3id.org/xapi/openedx/extension/transformer-version": "event-routing-backends@1.1.1", + "https://w3id.org/xapi/openedx/extensions/session-id": "056aca2a1c6b76742b283e73d3424453" + } + } +} diff --git a/event_routing_backends/settings/common.py b/event_routing_backends/settings/common.py index 67428431..22fe40d4 100644 --- a/event_routing_backends/settings/common.py +++ b/event_routing_backends/settings/common.py @@ -80,6 +80,7 @@ def plugin_settings(settings): 'edx.special_exam.practice.attempt.submitted', 'edx.special_exam.proctored.attempt.created', 'edx.special_exam.proctored.attempt.submitted', + 'edx.completion.block_completion.changed', 'edx.forum.thread.created', 'edx.forum.thread.deleted', 'edx.forum.thread.edited',