From f4a459b7b00c81092b90df1eca54b3970f8f642f Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Tue, 25 Jul 2023 18:45:10 +0930 Subject: [PATCH 1/9] test: adds tests for problem_check event with multiple questions Expected fixtures record the current behavior --- ...ck(server,multiple_questions,correct).json | 160 ++++++++++++++++++ ...(server,multiple_questions,incorrect).json | 127 ++++++++++++++ ...r,multiple_questions,partial_correct).json | 159 +++++++++++++++++ ...ck(server,multiple_questions,correct).json | 45 +++++ ...(server,multiple_questions,incorrect).json | 45 +++++ ...r,multiple_questions,partial_correct).json | 45 +++++ 6 files changed, 581 insertions(+) create mode 100644 event_routing_backends/processors/tests/fixtures/current/problem_check(server,multiple_questions,correct).json create mode 100644 event_routing_backends/processors/tests/fixtures/current/problem_check(server,multiple_questions,incorrect).json create mode 100644 event_routing_backends/processors/tests/fixtures/current/problem_check(server,multiple_questions,partial_correct).json create mode 100644 event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json create mode 100644 event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json create mode 100644 event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json diff --git a/event_routing_backends/processors/tests/fixtures/current/problem_check(server,multiple_questions,correct).json b/event_routing_backends/processors/tests/fixtures/current/problem_check(server,multiple_questions,correct).json new file mode 100644 index 00000000..d6f92dfb --- /dev/null +++ b/event_routing_backends/processors/tests/fixtures/current/problem_check(server,multiple_questions,correct).json @@ -0,0 +1,160 @@ +{ + "name":"problem_check", + "context":{ + "course_id":"course-v1:edX+DemoX+Demo_Course", + "course_user_tags":{ + + }, + "user_id":8, + "path":"/courses/course-v1:edX+DemoX+Demo_Course/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4/handler/xmodule_handler/problem_check", + "org_id":"edX", + "enterprise_uuid":"", + "module":{ + "display_name":"Multiple Choice Questions", + "usage_key":"block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4" + }, + "asides":{ + + } + }, + "username":"staff", + "session":"97662bef7c463c187b8fd91e0f580468", + "ip":"172.18.0.1", + "agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0", + "host":"localhost:18000", + "referer":"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@vertical+block@54bb9b142c6c4c22afc62bcb628f0e68?exam_access=&format=Homework&recheck_access=1&show_bookmark=0&show_title=0&view=student_view", + "accept_language":"en-US,en;q=0.5", + "event":{ + "state":{ + "seed":1, + "student_answers":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":"blue", + "a0effb954cca4759994f1ac9e9434bf4_4_1":[ + "choice_0", + "choice_2" + ], + "a0effb954cca4759994f1ac9e9434bf4_3_1":"choice_3" + }, + "has_saved_answers":false, + "correct_map":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":{ + "correctness":"correct", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1":{ + "correctness":"incorrect", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + }, + "a0effb954cca4759994f1ac9e9434bf4_4_1":{ + "correctness":"correct", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + } + }, + "input_state":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":{ + + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1":{ + + }, + "a0effb954cca4759994f1ac9e9434bf4_4_1":{ + + } + }, + "done":true + }, + "problem_id":"block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "answers":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":"blue", + "a0effb954cca4759994f1ac9e9434bf4_4_1":[ + "choice_0", + "choice_2" + ], + "a0effb954cca4759994f1ac9e9434bf4_3_1":"choice_2" + }, + "grade":3, + "max_grade":3, + "correct_map":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":{ + "correctness":"correct", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1":{ + "correctness":"correct", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + }, + "a0effb954cca4759994f1ac9e9434bf4_4_1":{ + "correctness":"correct", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + } + }, + "success":"correct", + "attempts":3, + "submission":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":{ + "question":"What color is the open ocean on a sunny day?", + "answer":"blue", + "response_type":"optionresponse", + "input_type":"optioninput", + "correct":true, + "variant":"", + "group_label":"" + }, + "a0effb954cca4759994f1ac9e9434bf4_4_1":{ + "question":"Which of the following are musical instruments?", + "answer":[ + "a piano", + "a guitar" + ], + "response_type":"choiceresponse", + "input_type":"checkboxgroup", + "correct":true, + "variant":"", + "group_label":"" + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1":{ + "question":"Which piece of furniture is built for sitting?", + "answer":"a chair", + "response_type":"multiplechoiceresponse", + "input_type":"choicegroup", + "correct":true, + "variant":"", + "group_label":"" + } + } + }, + "time":"2023-08-03T06:32:31.431355+00:00", + "event_type":"problem_check", + "event_source":"server", + "page":"x_module" +} diff --git a/event_routing_backends/processors/tests/fixtures/current/problem_check(server,multiple_questions,incorrect).json b/event_routing_backends/processors/tests/fixtures/current/problem_check(server,multiple_questions,incorrect).json new file mode 100644 index 00000000..2552a1e8 --- /dev/null +++ b/event_routing_backends/processors/tests/fixtures/current/problem_check(server,multiple_questions,incorrect).json @@ -0,0 +1,127 @@ +{ + "name":"problem_check", + "context":{ + "course_id":"course-v1:edX+DemoX+Demo_Course", + "course_user_tags":{ + + }, + "user_id":8, + "path":"/courses/course-v1:edX+DemoX+Demo_Course/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4/handler/xmodule_handler/problem_check", + "org_id":"edX", + "enterprise_uuid":"", + "module":{ + "display_name":"Multiple Choice Questions", + "usage_key":"block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4" + }, + "asides":{ + + } + }, + "username":"staff", + "session":"97662bef7c463c187b8fd91e0f580468", + "ip":"172.18.0.1", + "agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0", + "host":"localhost:18000", + "referer":"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@vertical+block@54bb9b142c6c4c22afc62bcb628f0e68?exam_access=&format=Homework&recheck_access=1&show_bookmark=0&show_title=0&view=student_view", + "accept_language":"en-US,en;q=0.5", + "event":{ + "state":{ + "seed":1, + "student_answers":{ + + }, + "has_saved_answers":false, + "correct_map":{ + + }, + "input_state":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":{ + + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1":{ + + }, + "a0effb954cca4759994f1ac9e9434bf4_4_1":{ + + } + }, + "done":false + }, + "problem_id":"block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "answers":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":"yellow", + "a0effb954cca4759994f1ac9e9434bf4_4_1":[ + "choice_3" + ], + "a0effb954cca4759994f1ac9e9434bf4_3_1":"choice_3" + }, + "grade":0, + "max_grade":3, + "correct_map":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":{ + "correctness":"incorrect", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1":{ + "correctness":"incorrect", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + }, + "a0effb954cca4759994f1ac9e9434bf4_4_1":{ + "correctness":"incorrect", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + } + }, + "success":"incorrect", + "attempts":1, + "submission":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":{ + "question":"What color is the open ocean on a sunny day?", + "answer":"yellow", + "response_type":"optionresponse", + "input_type":"optioninput", + "correct":false, + "variant":"", + "group_label":"" + }, + "a0effb954cca4759994f1ac9e9434bf4_4_1":{ + "question":"Which of the following are musical instruments?", + "answer":[ + "a window" + ], + "response_type":"choiceresponse", + "input_type":"checkboxgroup", + "correct":false, + "variant":"", + "group_label":"" + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1":{ + "question":"Which piece of furniture is built for sitting?", + "answer":"a bookshelf", + "response_type":"multiplechoiceresponse", + "input_type":"choicegroup", + "correct":false, + "variant":"", + "group_label":"" + } + } + }, + "time":"2023-08-03T06:30:32.026903+00:00", + "event_type":"problem_check", + "event_source":"server", + "page":"x_module" +} diff --git a/event_routing_backends/processors/tests/fixtures/current/problem_check(server,multiple_questions,partial_correct).json b/event_routing_backends/processors/tests/fixtures/current/problem_check(server,multiple_questions,partial_correct).json new file mode 100644 index 00000000..341eae8a --- /dev/null +++ b/event_routing_backends/processors/tests/fixtures/current/problem_check(server,multiple_questions,partial_correct).json @@ -0,0 +1,159 @@ +{ + "name":"problem_check", + "context":{ + "course_id":"course-v1:edX+DemoX+Demo_Course", + "course_user_tags":{ + + }, + "user_id":8, + "path":"/courses/course-v1:edX+DemoX+Demo_Course/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4/handler/xmodule_handler/problem_check", + "org_id":"edX", + "enterprise_uuid":"", + "module":{ + "display_name":"Multiple Choice Questions", + "usage_key":"block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4" + }, + "asides":{ + + } + }, + "username":"staff", + "session":"2c65c4e9e6a637feb42426fd35b5dac3", + "ip":"172.18.0.1", + "agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0", + "host":"localhost:18000", + "referer":"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@vertical+block@54bb9b142c6c4c22afc62bcb628f0e68?exam_access=&format=Homework&recheck_access=1&show_bookmark=0&show_title=0&view=student_view", + "accept_language":"en-US,en;q=0.5", + "event":{ + "state":{ + "seed":1, + "student_answers":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":"yellow", + "a0effb954cca4759994f1ac9e9434bf4_4_1":[ + "choice_3" + ], + "a0effb954cca4759994f1ac9e9434bf4_3_1":"choice_3" + }, + "has_saved_answers":false, + "correct_map":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":{ + "correctness":"incorrect", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1":{ + "correctness":"incorrect", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + }, + "a0effb954cca4759994f1ac9e9434bf4_4_1":{ + "correctness":"incorrect", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + } + }, + "input_state":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":{ + + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1":{ + + }, + "a0effb954cca4759994f1ac9e9434bf4_4_1":{ + + } + }, + "done":true + }, + "problem_id":"block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "answers":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":"blue", + "a0effb954cca4759994f1ac9e9434bf4_4_1":[ + "choice_0", + "choice_2" + ], + "a0effb954cca4759994f1ac9e9434bf4_3_1":"choice_3" + }, + "grade":2, + "max_grade":3, + "correct_map":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":{ + "correctness":"correct", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1":{ + "correctness":"incorrect", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + }, + "a0effb954cca4759994f1ac9e9434bf4_4_1":{ + "correctness":"correct", + "npoints":null, + "msg":"", + "hint":"", + "hintmode":null, + "queuestate":null, + "answervariable":null + } + }, + "success":"incorrect", + "attempts":2, + "submission":{ + "a0effb954cca4759994f1ac9e9434bf4_2_1":{ + "question":"What color is the open ocean on a sunny day?", + "answer":"blue", + "response_type":"optionresponse", + "input_type":"optioninput", + "correct":true, + "variant":"", + "group_label":"" + }, + "a0effb954cca4759994f1ac9e9434bf4_4_1":{ + "question":"Which of the following are musical instruments?", + "answer":[ + "a piano", + "a guitar" + ], + "response_type":"choiceresponse", + "input_type":"checkboxgroup", + "correct":true, + "variant":"", + "group_label":"" + }, + "a0effb954cca4759994f1ac9e9434bf4_3_1":{ + "question":"Which piece of furniture is built for sitting?", + "answer":"a bookshelf", + "response_type":"multiplechoiceresponse", + "input_type":"choicegroup", + "correct":false, + "variant":"", + "group_label":"" + } + } + }, + "time":"2023-08-03T06:31:39.005451+00:00", + "event_type":"problem_check", + "event_source":"server", + "page":"x_module" +} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json new file mode 100644 index 00000000..f3fe2a7b --- /dev/null +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json @@ -0,0 +1,45 @@ +{ + "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "result": {"score": {"min": 0.0}, "success": false}, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "objectType": "Activity", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "interactionType": "choice" + } + }, + "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": "97662bef7c463c187b8fd91e0f580468" + } + } +} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json new file mode 100644 index 00000000..f3fe2a7b --- /dev/null +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json @@ -0,0 +1,45 @@ +{ + "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "result": {"score": {"min": 0.0}, "success": false}, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "objectType": "Activity", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "interactionType": "choice" + } + }, + "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": "97662bef7c463c187b8fd91e0f580468" + } + } +} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json new file mode 100644 index 00000000..b6c3ad1c --- /dev/null +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json @@ -0,0 +1,45 @@ +{ + "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "result": {"score": {"min": 0.0}, "success": false}, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "objectType": "Activity", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "interactionType": "choice" + } + }, + "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": "2c65c4e9e6a637feb42426fd35b5dac3" + } + } +} From 0d57d03c2883cb03e782ee744aff24d4849cb7a6 Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Mon, 31 Jul 2023 15:42:27 +0930 Subject: [PATCH 2/9] fix: mocked test4.com host Test was failing locally because test4.com is a real URL that responded with a 200 when we posted events to it. Not sure why it was succeeding in CI? --- .../backends/tests/test_events_router.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/event_routing_backends/backends/tests/test_events_router.py b/event_routing_backends/backends/tests/test_events_router.py index b4d8bfa6..098a6270 100644 --- a/event_routing_backends/backends/tests/test_events_router.py +++ b/event_routing_backends/backends/tests/test_events_router.py @@ -642,7 +642,14 @@ def test_duplicate_xapi_event_id(self, mocked_logger): mocked_logger.info.mock_calls ) - def test_unsuccessful_routing_of_event_http(self): + @patch('event_routing_backends.utils.http_client.requests.post') + def test_unsuccessful_routing_of_event_http(self, mocked_post): + mock_response = MagicMock() + mock_response.status_code = 500 + mock_response.request.method = "POST" + mock_response.text = "Fake Server Error" + mocked_post.return_value = mock_response + host_configurations = { 'url': 'http://test4.com', 'auth_scheme': 'bearer', From a06c03fc932c715d1322f01302e0c250f41b568c Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Mon, 31 Jul 2023 15:43:43 +0930 Subject: [PATCH 3/9] feat: allow processor chains to handle multiple events Updates the event processor change to allow for the possibility that some event transformers may generate multiple events from a single event, and these events need to be carried down the processor chain. --- .../backends/events_router.py | 28 +++++++++++-------- .../backends/tests/test_events_router.py | 10 +++---- .../processors/caliper/envelope_processor.py | 23 ++++++++------- .../processors/caliper/tests/test_caliper.py | 18 ++++++------ .../caliper/tests/test_envelope_processor.py | 6 ++-- .../mixins/base_transformer_processor.py | 28 +++++++++++-------- .../processors/xapi/tests/test_xapi.py | 12 ++++---- .../processors/xapi/transformer_processor.py | 2 +- 8 files changed, 69 insertions(+), 58 deletions(-) diff --git a/event_routing_backends/backends/events_router.py b/event_routing_backends/backends/events_router.py index 1e12a33b..8a8b774f 100644 --- a/event_routing_backends/backends/events_router.py +++ b/event_routing_backends/backends/events_router.py @@ -79,7 +79,7 @@ def prepare_to_send(self, events): 'Processing edx event "{}" for router with backend {}'.format(event_name, self.backend_name) ) - processed_event = self.process_event(event) + processed_events = self.process_event(event) except (EventEmissionExit, ValueError): logger.error( 'Could not process edx event "%s" for backend %s\'s router', @@ -90,10 +90,10 @@ def prepare_to_send(self, events): continue logger.debug( - 'Successfully processed edx event "%s" for router with backend %s. Processed event: %s', + 'Successfully processed edx event "%s" for router with backend %s. Processed events: %s', event_name, self.backend_name, - processed_event + processed_events ) for router in routers: @@ -107,11 +107,13 @@ def prepare_to_send(self, events): ) else: host = self.configure_host(host, router) - updated_event = self.overwrite_event_data(processed_event, host, event_name) - is_business_critical = event_name in business_critical_events - if router_pk not in route_events: - route_events[router_pk] = [(event_name, updated_event, host, is_business_critical),] - else: + + if processed_events and router_pk not in route_events: + route_events[router_pk] = [] + + for processed_event in processed_events: + updated_event = self.overwrite_event_data(processed_event, host, event_name) + is_business_critical = event_name in business_critical_events route_events[router_pk].append((event_name, updated_event, host, is_business_critical)) return route_events @@ -176,17 +178,19 @@ def process_event(self, event): """ Process the event through this router's processors. + This single event may produce multiple processed events, and so we return a list of events here. + Arguments: event (dict): Event to be processed Returns - dict + list of ANY """ - event = event.copy() + events = [event.copy()] for processor in self.processors: - event = processor(event) + events = processor(events) - return event + return events def overwrite_event_data(self, event, host, event_name): """ diff --git a/event_routing_backends/backends/tests/test_events_router.py b/event_routing_backends/backends/tests/test_events_router.py index 098a6270..a8bc1ccc 100644 --- a/event_routing_backends/backends/tests/test_events_router.py +++ b/event_routing_backends/backends/tests/test_events_router.py @@ -153,9 +153,9 @@ def setUp(self): @patch('event_routing_backends.models.RouterConfiguration.get_enabled_routers') def test_with_processor_exception(self, mocked_get_enabled_routers, mocked_logger, mocked_post): processors = [ - MagicMock(return_value=self.transformed_event), - MagicMock(side_effect=EventEmissionExit, return_value=self.transformed_event), - MagicMock(return_value=self.transformed_event), + MagicMock(return_value=[self.transformed_event]), + MagicMock(side_effect=EventEmissionExit, return_value=[self.transformed_event]), + MagicMock(return_value=[self.transformed_event]), ] processors[1].side_effect = EventEmissionExit @@ -164,8 +164,8 @@ def test_with_processor_exception(self, mocked_get_enabled_routers, mocked_logge router = EventsRouter(processors=processors, backend_name='test') router.send(self.transformed_event) - processors[0].assert_called_once_with(self.transformed_event) - processors[1].assert_called_once_with(self.transformed_event) + processors[0].assert_called_once_with([self.transformed_event]) + processors[1].assert_called_once_with([self.transformed_event]) processors[2].assert_not_called() mocked_post.assert_not_called() diff --git a/event_routing_backends/processors/caliper/envelope_processor.py b/event_routing_backends/processors/caliper/envelope_processor.py index 27f5d59d..4d1f2819 100644 --- a/event_routing_backends/processors/caliper/envelope_processor.py +++ b/event_routing_backends/processors/caliper/envelope_processor.py @@ -19,19 +19,22 @@ def __init__(self, sensor_id): """ self.sensor_id = sensor_id - def __call__(self, event): + def __call__(self, events): """ - Envelope the caliper transformed event. + Envelope the caliper transformed events. Arguments: - event (dict): IMS Caliper compliant event dict + events (list of dicts): List of IMS Caliper compliant event dicts Returns: - dict + list of dicts """ - return { - 'sensor': self.sensor_id, - 'sendTime': convert_datetime_to_iso(datetime.now(UTC)), - 'data': [event], - 'dataVersion': CALIPER_EVENT_CONTEXT - } + enveloped_events = [] + for event in events: + enveloped_events.append({ + 'sensor': self.sensor_id, + 'sendTime': convert_datetime_to_iso(datetime.now(UTC)), + 'data': [event], + 'dataVersion': CALIPER_EVENT_CONTEXT + }) + return enveloped_events diff --git a/event_routing_backends/processors/caliper/tests/test_caliper.py b/event_routing_backends/processors/caliper/tests/test_caliper.py index 3857da23..0c6a4a9b 100644 --- a/event_routing_backends/processors/caliper/tests/test_caliper.py +++ b/event_routing_backends/processors/caliper/tests/test_caliper.py @@ -35,7 +35,7 @@ def test_skip_event_when_disabled(self): @patch('event_routing_backends.processors.mixins.base_transformer_processor.logger') def test_send_method_with_no_transformer_implemented(self, mocked_logger): with self.assertRaises(EventEmissionExit): - self.processor(self.sample_event) + self.processor([self.sample_event]) mocked_logger.error.assert_called_once_with( 'Could not get transformer for %s event.', @@ -49,7 +49,7 @@ def test_send_method_with_no_transformer_implemented(self, mocked_logger): @patch('event_routing_backends.processors.mixins.base_transformer_processor.logger') def test_send_method_with_unknown_exception(self, mocked_logger, _): with self.assertRaises(ValueError): - self.processor(self.sample_event) + self.processor([self.sample_event]) mocked_logger.exception.assert_called_once_with( 'There was an error while trying to transform event "sentinel.name" using CaliperProcessor' @@ -66,14 +66,14 @@ def test_send_method_with_successfull_flow( mocked_logger, mocked_get_transformer ): - transformed_event = { + transformed_event = [{ 'transformed_key': 'transformed_value' - } + }] mocked_transformer = MagicMock() mocked_transformer.transform.return_value = transformed_event mocked_get_transformer.return_value = mocked_transformer - self.processor(self.sample_event) + self.processor([self.sample_event]) self.assertIn( call( @@ -102,14 +102,14 @@ def test_send_method_with_successfull_flow_logging_disabled( mocked_logger, mocked_get_transformer ): - transformed_event = { + transformed_event = [{ 'transformed_key': 'transformed_value' - } + }] mocked_transformer = MagicMock() mocked_transformer.transform.return_value = transformed_event mocked_get_transformer.return_value = mocked_transformer - self.processor(self.sample_event) + self.processor([self.sample_event]) self.assertIn( call( @@ -131,5 +131,5 @@ def test_with_no_registry(self, mocked_logger): backend = CaliperProcessor() backend.registry = None with self.assertRaises(EventEmissionExit): - self.assertIsNone(backend(self.sample_event)) + self.assertIsNone(backend([self.sample_event])) mocked_logger.exception.assert_called_once() diff --git a/event_routing_backends/processors/caliper/tests/test_envelope_processor.py b/event_routing_backends/processors/caliper/tests/test_envelope_processor.py index fe19abcf..9876b80f 100644 --- a/event_routing_backends/processors/caliper/tests/test_envelope_processor.py +++ b/event_routing_backends/processors/caliper/tests/test_envelope_processor.py @@ -30,10 +30,10 @@ def setUp(self): def test_caliper_envelope_processor(self, mocked_datetime): mocked_datetime.now.return_value = FROZEN_TIME - result = CaliperEnvelopeProcessor(sensor_id=self.sensor_id)(self.sample_event) - self.assertEqual(result, { + result = CaliperEnvelopeProcessor(sensor_id=self.sensor_id)([self.sample_event]) + self.assertEqual(result, [{ 'sensor': self.sensor_id, 'sendTime': convert_datetime_to_iso(str(FROZEN_TIME)), 'data': [self.sample_event], 'dataVersion': CALIPER_EVENT_CONTEXT - }) + }]) diff --git a/event_routing_backends/processors/mixins/base_transformer_processor.py b/event_routing_backends/processors/mixins/base_transformer_processor.py index f1d2110f..fa558771 100644 --- a/event_routing_backends/processors/mixins/base_transformer_processor.py +++ b/event_routing_backends/processors/mixins/base_transformer_processor.py @@ -18,33 +18,37 @@ class BaseTransformerProcessorMixin: registry = None - def __call__(self, event): + def __call__(self, events): """ - Transform and then route the event to the configured routers. + Transform the given events, and return the transformed events. Arguments: - event (dict): Event to be transformed and delivered. + event (list of dicts): Events to be transformed. Returns: - transformed event (dict) + transformed events (list of ANY) Raises: EventEmissionExit except.on: if no transformer is registered for an event. ANY exception: if raised. """ - transformed_event = self.transform_event(event) - - if transformed_event: - return transformed_event - - raise EventEmissionExit + returned_events = [] + for event in events: + transformed_event = self.transform_event(event) + if not transformed_event: + raise EventEmissionExit + if isinstance(transformed_event, list): + returned_events += transformed_event + else: + returned_events.append(transformed_event) + return returned_events def transform_event(self, event): """ Transform the event. - Transformer method can return transformed event in any data structure format - (dict or any custom class) but the configured router(s) MUST support it. + Transformer method can return transformed events in any data structure format + (dict, list, or any custom class) but the configured router(s) MUST support it. Arguments: event (dict): Event to be transformed. diff --git a/event_routing_backends/processors/xapi/tests/test_xapi.py b/event_routing_backends/processors/xapi/tests/test_xapi.py index 9bb044ba..6e5e5f75 100644 --- a/event_routing_backends/processors/xapi/tests/test_xapi.py +++ b/event_routing_backends/processors/xapi/tests/test_xapi.py @@ -31,7 +31,7 @@ def test_skip_event_when_disabled(self): @patch('event_routing_backends.processors.mixins.base_transformer_processor.logger') def test_send_method_with_no_transformer_implemented(self, mocked_logger): with self.assertRaises(EventEmissionExit): - self.processor(self.sample_event) + self.processor([self.sample_event]) mocked_logger.error.assert_called_once_with( 'Could not get transformer for %s event.', @@ -45,7 +45,7 @@ def test_send_method_with_no_transformer_implemented(self, mocked_logger): @patch('event_routing_backends.processors.mixins.base_transformer_processor.logger') def test_send_method_with_unknown_exception(self, mocked_logger, _): with self.assertRaises(ValueError): - self.processor(self.sample_event) + self.processor([self.sample_event]) mocked_logger.exception.assert_called_once_with( 'There was an error while trying to transform event "sentinel.name" using XApiProcessor' @@ -62,7 +62,7 @@ def test_send_method_with_successfull_flow(self, mocked_logger, mocked_get_trans mocked_transformer.transform.return_value = transformed_event mocked_get_transformer.return_value = mocked_transformer - self.processor(self.sample_event) + self.processor([self.sample_event]) self.assertIn(call(transformed_event.to_json()), mocked_logger.mock_calls) @@ -77,7 +77,7 @@ def test_send_method_with_invalid_object(self, mocked_logger, mocked_get_transfo mocked_get_transformer.return_value = mocked_transformer with self.assertRaises(EventEmissionExit): - self.processor(self.sample_event) + self.processor([self.sample_event]) self.assertNotIn(call(transformed_event.to_json()), mocked_logger.mock_calls) @@ -93,7 +93,7 @@ def test_send_method_with_successfull_flow_no_logger(self, mocked_logger, mocked mocked_transformer.transform.return_value = transformed_event mocked_get_transformer.return_value = mocked_transformer - self.processor(self.sample_event) + self.processor([self.sample_event]) self.assertNotIn(call(transformed_event.to_json()), mocked_logger.mock_calls) @@ -102,5 +102,5 @@ def test_with_no_registry(self, mocked_logger): backend = XApiProcessor() backend.registry = None with self.assertRaises(EventEmissionExit): - self.assertIsNone(backend(self.sample_event)) + self.assertIsNone(backend([self.sample_event])) mocked_logger.exception.assert_called_once() diff --git a/event_routing_backends/processors/xapi/transformer_processor.py b/event_routing_backends/processors/xapi/transformer_processor.py index 71ac712d..418e204c 100644 --- a/event_routing_backends/processors/xapi/transformer_processor.py +++ b/event_routing_backends/processors/xapi/transformer_processor.py @@ -34,7 +34,7 @@ def transform_event(self, event): event (dict): Event to be transformed. Returns: - dict: transformed event + ANY: transformed event Raises: Any Exception From 7f646314dbed91fbba58f0f49671574a8f2f677f Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Mon, 31 Jul 2023 17:35:45 +0930 Subject: [PATCH 4/9] refactor: applies functional programming principles to event transformation This reduces the side-effects of event transformation, which will allow us to re-use base event transformation methods when generating multiple events from a single source event. * Removes the BaseTransformerMixin.transformed_event instance variable in favor of passing an event through to base_transform() to be modified and returned. * Adds BaseTransformerMixin.get_object() so that caliper events don't need to reference self.transformed_event when updating the object data. * Adds BaseTransformerMixin.get_extensions() so that caliper events can don't have to hack in their transformerVersion during BaseTransformerMixin.transform() --- .../event_transformers/enrollment_events.py | 5 +- .../event_transformers/navigation_events.py | 2 +- .../problem_interaction_events.py | 2 +- .../event_transformers/video_events.py | 10 +-- .../processors/caliper/transformer.py | 67 +++++++++++++------ .../processors/mixins/base_transformer.py | 32 +++++---- .../processors/xapi/transformer.py | 14 ++-- 7 files changed, 86 insertions(+), 46 deletions(-) diff --git a/event_routing_backends/processors/caliper/event_transformers/enrollment_events.py b/event_routing_backends/processors/caliper/event_transformers/enrollment_events.py index dfdda372..74c27a55 100644 --- a/event_routing_backends/processors/caliper/event_transformers/enrollment_events.py +++ b/event_routing_backends/processors/caliper/event_transformers/enrollment_events.py @@ -50,10 +50,11 @@ def get_object(self): # TODO: replace with anonymous enrollment id? course_root_url = self.get_object_iri('course', self.get_data('data.course_id', True)) - caliper_object = { + caliper_object = super().get_object() + caliper_object.update({ 'id': course_root_url, 'type': 'CourseOffering', 'name': course['display_name'], 'extensions': {'mode': self.get_data('data.mode')} if self.get_data('data.mode') is not None else None, - } + }) return caliper_object diff --git a/event_routing_backends/processors/caliper/event_transformers/navigation_events.py b/event_routing_backends/processors/caliper/event_transformers/navigation_events.py index 8c322ecc..50171dc8 100644 --- a/event_routing_backends/processors/caliper/event_transformers/navigation_events.py +++ b/event_routing_backends/processors/caliper/event_transformers/navigation_events.py @@ -39,7 +39,7 @@ def get_object(self): dict """ self.backend_name = 'caliper' - caliper_object = self.transformed_event['object'] + caliper_object = super().get_object() data = self.get_data('data') extensions = {} diff --git a/event_routing_backends/processors/caliper/event_transformers/problem_interaction_events.py b/event_routing_backends/processors/caliper/event_transformers/problem_interaction_events.py index 38f0b36d..23e007f5 100644 --- a/event_routing_backends/processors/caliper/event_transformers/problem_interaction_events.py +++ b/event_routing_backends/processors/caliper/event_transformers/problem_interaction_events.py @@ -149,7 +149,7 @@ def get_object(self): else: iri_url = object_id - caliper_object = self.transformed_event['object'] + caliper_object = super().get_object() caliper_object.update({ 'id': self.get_object_iri('xblock', iri_url), 'type': OBJECT_TYPE_MAP.get(key, 'Attempt'), diff --git a/event_routing_backends/processors/caliper/event_transformers/video_events.py b/event_routing_backends/processors/caliper/event_transformers/video_events.py index d202a348..87bc3516 100644 --- a/event_routing_backends/processors/caliper/event_transformers/video_events.py +++ b/event_routing_backends/processors/caliper/event_transformers/video_events.py @@ -82,7 +82,7 @@ def get_object(self): dict """ self.backend_name = 'caliper' - caliper_object = self.transformed_event['object'] + caliper_object = super().get_object() data = self.get_data('data') course_id = self.get_data('context.course_id', True) video_id = self.get_data('data.id', True) @@ -176,10 +176,12 @@ class VideoSpeedChangedTransformer(BaseVideoTransformer): """ Transform the event fired when a video's speed is changed. """ - additional_fields = ('target', 'extensions',) + additional_fields = ('target',) def get_extensions(self): - return { + extensions = super().get_extensions() + extensions.update({ 'oldSpeed': self.get_data('old_speed'), 'newSpeed': self.get_data('new_speed'), - } + }) + return extensions diff --git a/event_routing_backends/processors/caliper/transformer.py b/event_routing_backends/processors/caliper/transformer.py index 0ec532eb..0f7e9496 100644 --- a/event_routing_backends/processors/caliper/transformer.py +++ b/event_routing_backends/processors/caliper/transformer.py @@ -19,41 +19,35 @@ class CaliperTransformer(BaseTransformerMixin): required_fields = ( 'type', 'object', - 'action' + 'action', + 'extensions', ) - def base_transform(self): + def base_transform(self, transformed_event): """ Transform common Caliper fields. """ - self._add_generic_fields() - self._add_actor_info() - self._add_session_info() + transformed_event = super().base_transform(transformed_event) + self._add_generic_fields(transformed_event) + self._add_actor_info(transformed_event) + self._add_session_info(transformed_event) + return transformed_event - def _add_generic_fields(self): + def _add_generic_fields(self, transformed_event): """ Add all of the generic fields to the transformed_event object. """ - self.transformed_event.update({ + transformed_event.update({ '@context': CALIPER_EVENT_CONTEXT, 'id': uuid.uuid4().urn, 'eventTime': convert_datetime_to_iso(self.get_data('timestamp', True)), - 'extensions': {} }) - self.transformed_event['object'] = {} - course_id = self.get_data('context.course_id') - if course_id is not None: - extensions = {"isPartOf": {}} - extensions['isPartOf']['id'] = self.get_object_iri('course', course_id) - extensions['isPartOf']['type'] = 'CourseOffering' - self.transformed_event['object']['extensions'] = {} - self.transformed_event['object']['extensions'].update(extensions) - def _add_actor_info(self): + def _add_actor_info(self, transformed_event): """ - Add all generic information related to `actor`. + Add all generic information related to `actor` to the transformed_event. """ - self.transformed_event['actor'] = { + transformed_event['actor'] = { 'id': self.get_object_iri( 'user', get_anonymous_user_id(self.extract_username_or_userid(), 'CALIPER'), @@ -61,16 +55,45 @@ def _add_actor_info(self): 'type': 'Person' } - def _add_session_info(self): + def _add_session_info(self, transformed_event): """ - Add session info related to the event + Add session info related to the transformed_event. """ sessionid = self.extract_sessionid() if sessionid: - self.transformed_event['session'] = { + transformed_event['session'] = { 'id': self.get_object_iri( 'sessions', sessionid, ), 'type': 'Session' } + + def get_object(self): + """ + Return object for the event. + + Returns: + dict + """ + caliper_object = super().get_object() + course_id = self.get_data('context.course_id') + if course_id is not None: + extensions = {"isPartOf": {}} + extensions['isPartOf']['id'] = self.get_object_iri('course', course_id) + extensions['isPartOf']['type'] = 'CourseOffering' + caliper_object['extensions'] = {} + caliper_object['extensions'].update(extensions) + + return caliper_object + + def get_extensions(self): + """ + Return extensions for the event. + + Returns: + dict + """ + return { + 'transformerVersion': self.transformer_version, + } diff --git a/event_routing_backends/processors/mixins/base_transformer.py b/event_routing_backends/processors/mixins/base_transformer.py index 78cf8398..a4c78743 100644 --- a/event_routing_backends/processors/mixins/base_transformer.py +++ b/event_routing_backends/processors/mixins/base_transformer.py @@ -30,7 +30,6 @@ def __init__(self, event): event (dict) : event to be transformed """ self.event = event.copy() - self.transformed_event = {} @staticmethod def find_nested(source_dict, key): @@ -65,14 +64,17 @@ def _find_nested(event_dict): return _find_nested(source_dict) - def base_transform(self): + def base_transform(self, transformed_event): """ Transform the fields that are common for all events. Other classes can override this method to add common transformation code for events. + + Returns: + ANY """ - return + return transformed_event or {} @property def transformer_version(self): @@ -92,18 +94,18 @@ def transform(self): Transform the edX event. Returns: - dict + ANY """ - self.base_transform() + transformed_event = self.base_transform({}) transforming_fields = self.required_fields + self.additional_fields for key in transforming_fields: if hasattr(self, key): value = getattr(self, key) - self.transformed_event[key] = value + transformed_event[key] = value elif hasattr(self, f'get_{key}'): value = getattr(self, f'get_{key}')() - self.transformed_event[key] = value + transformed_event[key] = value else: raise ValueError( 'Cannot find value for "{}" in transformer {} for the edx event "{}"'.format( @@ -111,12 +113,9 @@ def transform(self): ) ) - if self.backend_name == 'caliper': - self.transformed_event['extensions']['transformerVersion'] = self.transformer_version - - self.transformed_event = self.del_none(self.transformed_event) + transformed_event = self.del_none(transformed_event) - return self.transformed_event + return transformed_event def extract_username_or_userid(self): """ @@ -218,3 +217,12 @@ def get_object_iri(self, object_type, object_id): object_type=object_type, object_id=object_id ) + + def get_object(self): + """ + Return object for the event. + + Returns: + dict + """ + return {} diff --git a/event_routing_backends/processors/xapi/transformer.py b/event_routing_backends/processors/xapi/transformer.py index 74f2ff0e..ee2860ad 100644 --- a/event_routing_backends/processors/xapi/transformer.py +++ b/event_routing_backends/processors/xapi/transformer.py @@ -46,22 +46,28 @@ def transform(self): transformed_props["version"] = constants.XAPI_SPECIFICATION_VERSION return Statement(**transformed_props) - def base_transform(self): + def base_transform(self, transformed_event): """ Transform the fields that are common for all events. """ + transformed_event = super().base_transform(transformed_event) actor = self.get_actor() event_timestamp = self.get_timestamp() - self.transformed_event = { + transformed_event.update({ 'actor': actor, 'context': self.get_context(), 'timestamp': event_timestamp, - } + }) + transformed_event['actor'] = self.get_actor() + transformed_event['context'] = self.get_context() + transformed_event['timestamp'] = self.get_timestamp() + # Warning! changing anything in these 2 lines or changing the "base_uuid" can invalidate # billions of rows in the database. Please have a community discussion first before introducing # any change in generation of UUID. uuid_str = f'{actor.to_json()}-{event_timestamp}' - self.transformed_event['id'] = get_uuid5(self.verb.to_json(), uuid_str) # pylint: disable=no-member + transformed_event['id'] = get_uuid5(self.verb.to_json(), uuid_str) # pylint: disable=no-member + return transformed_event def get_actor(self): """ From 675f77d8bcecb573ef34aec39eb49c1844f597b9 Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Mon, 7 Aug 2023 18:43:00 +0930 Subject: [PATCH 5/9] feat: multi-question problem_check events produce multiple xAPI events Single-question problem_check events still only produce one xAPI event. Changes to the top-level multi-question problem_check event data: * object.type changed from Activity to GroupActivity * object.id shows the base problem usage_key * object.definition.interaction_type is "other" New events emitted for each child problem are identical the top-level event, except for: * object.type is Activity * object.id shows the base problem usage_key including the child usage string * object.definition.interaction_type is determined by the child problem response_type * result.score is omitted -- only relevant to the parent problem * result.response is provided, pulled from the child question submission * result.success is provided, pulled from the child question submission Related fixes to all problem_check events: * object.definition.name now shows the problem display_name * object.id now uses shows the problem usage_key * result.score max and raw are now always provided if present in the source event (bug fix) --- .coveragerc | 4 + event_routing_backends/__init__.py | 2 +- .../problem_interaction_events.py | 203 +++++++++++++++-- .../processors/xapi/statements.py | 15 ++ .../expected/problem_check(browser).json | 1 + .../problem_check(browser)legacy.json | 1 + ...roblem_check(server)_no_response_type.json | 1 + ...ck(server,multiple_questions,correct).json | 204 ++++++++++++++++- ...(server,multiple_questions,incorrect).json | 206 +++++++++++++++++- ...r,multiple_questions,partial_correct).json | 204 ++++++++++++++++- .../xapi/tests/test_transformers.py | 22 ++ .../processors/xapi/transformer.py | 117 ++++++++++ 12 files changed, 958 insertions(+), 22 deletions(-) create mode 100644 event_routing_backends/processors/xapi/statements.py diff --git a/.coveragerc b/.coveragerc index c890c53f..adb924a7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -8,3 +8,7 @@ omit = *admin.py *static* *templates* +[report] +exclude_lines = + pragma: no cover + raise NotImplementedError diff --git a/event_routing_backends/__init__.py b/event_routing_backends/__init__.py index 79fb3904..2fbe0f36 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__ = '5.5.5' +__version__ = '5.6.0' 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 6536b70f..3f9d2806 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,7 +6,13 @@ from event_routing_backends.helpers import get_problem_block_id 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 +from event_routing_backends.processors.xapi.statements import GroupActivity +from event_routing_backends.processors.xapi.transformer import ( + OneToManyChildXApiTransformerMixin, + OneToManyXApiTransformerMixin, + XApiTransformer, + XApiVerbTransformerMixin, +) # map open edx problems interation types to xAPI valid interaction types INTERACTION_TYPES_MAP = { @@ -76,19 +82,41 @@ def get_object(self): Returns: `Activity` """ + object_id = self.get_object_id() + definition = self.get_object_definition() + return Activity( + id=object_id, + definition=definition, + ) + + def get_object_id(self): + """ + Returns the object.id + + Returns: + str + """ object_id = None data = self.get_data('data') if data and isinstance(data, dict): object_id = self.get_data('data.problem_id') or self.get_data('data.module_id', True) + else: + object_id = self.get_data('usage_key') + + return object_id + def get_object_definition(self): + """ + Returns the definition portion of the object stanza. + + Returns: + ActivityDefinition + """ event_name = self.get_data('name', True) - return Activity( - id=object_id, - definition=ActivityDefinition( - type=EVENT_OBJECT_DEFINITION_TYPE[event_name] if event_name in EVENT_OBJECT_DEFINITION_TYPE else - constants.XAPI_ACTIVITY_INTERACTION, - ), + return ActivityDefinition( + type=EVENT_OBJECT_DEFINITION_TYPE[event_name] if event_name in EVENT_OBJECT_DEFINITION_TYPE else + constants.XAPI_ACTIVITY_INTERACTION, ) @@ -169,10 +197,16 @@ def get_result(self): ) -@XApiTransformersRegistry.register('problem_check') -class ProblemCheckTransformer(BaseProblemsTransformer): +class BaseProblemCheckTransformer(BaseProblemsTransformer): """ - Transform problem interaction related events into xAPI format. + Transform problem check events into one or more xAPI statements. + + If there is only one question in the source event problem, then transform() returns a single Activity. + + But if there are multiple questions in the source event problem, transform() will return: + + * 1 parent GroupActivity + * N "child" Activity which reference the parent, where N>=0 """ additional_fields = ('result', ) @@ -201,19 +235,33 @@ def get_object(self): if xapi_object.id: xapi_object.id = self.get_object_iri('xblock', xapi_object.id) + return xapi_object + + def get_object_definition(self): + """ + Returns the definition portion of the object stanza. + + Returns: + ActivityDefinition + """ + definition = super().get_object_definition() + if self.get_data('data.attempts'): - xapi_object.definition.extensions = Extensions({ + definition.extensions = Extensions({ constants.XAPI_ACTIVITY_ATTEMPT: self.get_data('data.attempts') }) interaction_type = self._get_interaction_type() + display_name = self.get_data('display_name') submission = self._get_submission() if submission: - interaction_type = INTERACTION_TYPES_MAP[submission['response_type']] - xapi_object.definition.description = LanguageMap({constants.EN_US: submission['question']}) + interaction_type = INTERACTION_TYPES_MAP.get(submission.get('response_type'), DEFAULT_INTERACTION_TYPE) + definition.description = LanguageMap({constants.EN_US: submission['question']}) + elif display_name: + definition.name = LanguageMap({constants.EN_US: display_name}) - xapi_object.definition.interaction_type = interaction_type + definition.interaction_type = interaction_type - return xapi_object + return definition def _get_submission(self): """ @@ -265,11 +313,13 @@ def get_result(self): submission = self._get_submission() if submission: response = submission["answer"] + correct = submission.get("correct") else: response = event_data.get('answers', None) + correct = self.get_data('success') == 'correct' - max_grade = event_data.get('max_grade', None) - grade = event_data.get('grade', None) + max_grade = self.get_data('max_grade') + grade = self.get_data('grade') scaled = None if max_grade is not None and grade is not None: @@ -279,7 +329,7 @@ def get_result(self): scaled = 0 return Result( - success=event_data.get('success', None) == 'correct', + success=correct, score={ 'min': 0, 'max': max_grade, @@ -288,3 +338,120 @@ def get_result(self): }, response=response ) + + +@XApiTransformersRegistry.register('problem_check') +class ProblemCheckTransformer(OneToManyXApiTransformerMixin, BaseProblemCheckTransformer): + """ + Transform problem check events into one or more xAPI statements. + + If there is only one question in the source event problem, then transform() returns a single Activity. + + But if there are multiple questions in the source event problem, transform() will return: + + * 1 parent GroupActivity + * N "child" Activity which reference the parent, where N>=0 + """ + @property + def child_transformer_class(self): + """ + Returns the ProblemCheckChildTransformer class. + + Returns: + Type + """ + return ProblemCheckChildTransformer + + def get_child_ids(self): + """ + Returns the list of "child" event IDs. + + In this context, "child" events relate to multiple submissions to sub-questions in the problem. + + If <1 children are found on this event, then <1 child events are returned in the list. + Otherwise, we say that "this event has no children", and so this method returns an empty list. + + Returns: + list of strings + """ + submissions = self.get_data('submission') or {} + child_ids = submissions.keys() + if len(child_ids) > 1: + return child_ids + return [] + + def get_object(self): + """ + Get object for xAPI transformed event or group of events. + + Returns: + `Activity` or `GroupActivity` + """ + activity = super().get_object() + definition = self.get_object_definition() + + if self.get_child_ids(): + activity = GroupActivity( + id=activity.id, + definition=definition, + ) + + return activity + + def get_object_definition(self): + """ + Returns the definition portion of the object stanza. + + Returns: + ActivityDefinition + """ + definition = super().get_object_definition() + + if self.get_child_ids(): + definition.interaction_type = DEFAULT_INTERACTION_TYPE + + return definition + + +class ProblemCheckChildTransformer(OneToManyChildXApiTransformerMixin, BaseProblemCheckTransformer): + """ + Transformer for subproblems of a multi-question problem_check event. + """ + def _get_submission(self): + """ + Return this child's submission data from the event data, if valid. + + Returns: + dict + """ + submissions = self.get_data('submission') or {} + return submissions.get(self.child_id) + + def get_object_id(self): + """ + Returns the child object.id, which it creates from the parent object.id + and the child_id. + + Returns: + str + """ + object_id = super().get_object_id() or "" + object_id = '@'.join([ + *object_id.split('@')[:-1], + self.child_id, + ]) + return object_id + + def get_result(self): + """ + Get result for the xAPI transformed child event. + + Returns: + `Result` + """ + result = super().get_result() + # Don't report the score on child events; only the parent knows the score. + result.score = None + submission = self._get_submission() or {} + result.response = submission.get('answer') + return result diff --git a/event_routing_backends/processors/xapi/statements.py b/event_routing_backends/processors/xapi/statements.py new file mode 100644 index 00000000..11951308 --- /dev/null +++ b/event_routing_backends/processors/xapi/statements.py @@ -0,0 +1,15 @@ +""" +xAPI statement classes +""" +from tincan import Activity + + +class GroupActivity(Activity): + """ + Subclass of tincan.Activity which reports object_type="GroupActivity" + + For use with Activites that contain one or more child Activities, like Problems that contain multiple Questions. + """ + @Activity.object_type.setter + def object_type(self, _): + self._object_type = 'GroupActivity' diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser).json index 2a7eef9a..54831e74 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser).json @@ -26,6 +26,7 @@ }, "object": { "definition": { + "interactionType": "other", "type": "http://adlnet.gov/expapi/activities/cmi.interaction" }, "id": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@3fc5461f86764ad7bdbdf6cbdde61e66", diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser)legacy.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser)legacy.json index 2d04afa4..98962f50 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser)legacy.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser)legacy.json @@ -26,6 +26,7 @@ }, "object": { "definition": { + "interactionType": "other", "type": "http://adlnet.gov/expapi/activities/cmi.interaction" }, "id": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@sequential+block@ef37eb3cf1724e38b7f88a9ce85a4842", diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server)_no_response_type.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server)_no_response_type.json index 81c84bc8..2fe58cab 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server)_no_response_type.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server)_no_response_type.json @@ -29,6 +29,7 @@ "extensions":{ "http://id.tincanapi.com/extension/attempt-id": 10 }, + "name": {"en-US": "Checkboxes"}, "interactionType": "other", "type": "http://adlnet.gov/expapi/activities/cmi.interaction" }, diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json index f3fe2a7b..f47b6ae9 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json @@ -1,6 +1,186 @@ +[ { "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", - "result": {"score": {"min": 0.0}, "success": false}, + "result": {"score": {"min": 0, "max": 3, "raw": 3, "scaled": 1}, "success": true}, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "objectType": "GroupActivity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "name": {"en-US": "Multiple Choice Questions"}, + "interactionType": "other" + } + }, + "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": "97662bef7c463c187b8fd91e0f580468" + } + } +}, +{ + "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "result": {"response": "blue", "success": true}, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "objectType": "Activity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4_2_1", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "description": { + "en-US": "What color is the open ocean on a sunny day?" + }, + "interactionType": "choice" + } + }, + "context": { + "statement": { + "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "objectType": "StatementRef" + }, + "contextActivities": { + "parent": [ + { + "objectType": "GroupActivity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "name": { + "en-US": "Multiple Choice Questions" + }, + "interactionType": "other" + } + } + ], + "grouping": [ + { + "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": "97662bef7c463c187b8fd91e0f580468" + } + } +}, +{ + "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "result": {"response": "['a piano', 'a guitar']", "success": true}, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "objectType": "Activity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4_4_1", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "description": { + "en-US": "Which of the following are musical instruments?" + }, + "interactionType": "choice" + } + }, + "context": { + "statement": { + "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "objectType": "StatementRef" + }, + "contextActivities": { + "parent": [ + { + "objectType": "GroupActivity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "name": { + "en-US": "Multiple Choice Questions" + }, + "interactionType": "other" + } + } + ], + "grouping": [ + { + "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": "97662bef7c463c187b8fd91e0f580468" + } + } +}, +{ + "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "result": {"response": "a chair", "success": true}, "version": "1.0.3", "actor": { "objectType": "Agent", @@ -17,14 +197,35 @@ }, "object": { "objectType": "Activity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4_3_1", "definition": { "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "description": { + "en-US": "Which piece of furniture is built for sitting?" + }, "interactionType": "choice" } }, "context": { + "statement": { + "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "objectType": "StatementRef" + }, "contextActivities": { "parent": [ + { + "objectType": "GroupActivity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "name": { + "en-US": "Multiple Choice Questions" + }, + "interactionType": "other" + } + } + ], + "grouping": [ { "id": "http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course", "objectType": "Activity", @@ -43,3 +244,4 @@ } } } +] diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json index f3fe2a7b..41541ab7 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json @@ -1,6 +1,188 @@ +[ { "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", - "result": {"score": {"min": 0.0}, "success": false}, + "result": {"score": {"min": 0, "max": 3, "raw": 0, "scaled": 0}, "success": false}, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "objectType": "GroupActivity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "name": { + "en-US": "Multiple Choice Questions" + }, + "interactionType": "other" + } + }, + "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": "97662bef7c463c187b8fd91e0f580468" + } + } +}, +{ + "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "result": {"response": "yellow", "success": false}, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "objectType": "Activity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4_2_1", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "description": { + "en-US": "What color is the open ocean on a sunny day?" + }, + "interactionType": "choice" + } + }, + "context": { + "statement": { + "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "objectType": "StatementRef" + }, + "contextActivities": { + "parent": [ + { + "objectType": "GroupActivity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "name": { + "en-US": "Multiple Choice Questions" + }, + "interactionType": "other" + } + } + ], + "grouping": [ + { + "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": "97662bef7c463c187b8fd91e0f580468" + } + } +}, +{ + "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "result": {"response": "['a window']", "success": false}, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "objectType": "Activity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4_4_1", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "description": { + "en-US": "Which of the following are musical instruments?" + }, + "interactionType": "choice" + } + }, + "context": { + "statement": { + "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "objectType": "StatementRef" + }, + "contextActivities": { + "parent": [ + { + "objectType": "GroupActivity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "name": { + "en-US": "Multiple Choice Questions" + }, + "interactionType": "other" + } + } + ], + "grouping": [ + { + "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": "97662bef7c463c187b8fd91e0f580468" + } + } +}, +{ + "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "result": {"response": "a bookshelf", "success": false}, "version": "1.0.3", "actor": { "objectType": "Agent", @@ -17,14 +199,35 @@ }, "object": { "objectType": "Activity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4_3_1", "definition": { "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "description": { + "en-US": "Which piece of furniture is built for sitting?" + }, "interactionType": "choice" } }, "context": { + "statement": { + "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "objectType": "StatementRef" + }, "contextActivities": { "parent": [ + { + "objectType": "GroupActivity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "name": { + "en-US": "Multiple Choice Questions" + }, + "interactionType": "other" + } + } + ], + "grouping": [ { "id": "http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course", "objectType": "Activity", @@ -43,3 +246,4 @@ } } } +] diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json index b6c3ad1c..d8014195 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json @@ -1,6 +1,186 @@ +[ { "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", - "result": {"score": {"min": 0.0}, "success": false}, + "result": {"score": {"min": 0, "max": 3, "raw": 2, "scaled": 0.6666666666666666}, "success": false}, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "objectType": "GroupActivity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "name": {"en-US": "Multiple Choice Questions"}, + "interactionType": "other" + } + }, + "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": "2c65c4e9e6a637feb42426fd35b5dac3" + } + } +}, +{ + "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "result": {"response": "blue", "success": true}, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "objectType": "Activity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4_2_1", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "description": { + "en-US": "What color is the open ocean on a sunny day?" + }, + "interactionType": "choice" + } + }, + "context": { + "statement": { + "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "objectType": "StatementRef" + }, + "contextActivities": { + "parent": [ + { + "objectType": "GroupActivity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "name": { + "en-US": "Multiple Choice Questions" + }, + "interactionType": "other" + } + } + ], + "grouping": [ + { + "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": "2c65c4e9e6a637feb42426fd35b5dac3" + } + } +}, +{ + "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "result": {"response": "['a piano', 'a guitar']", "success": true}, + "version": "1.0.3", + "actor": { + "objectType": "Agent", + "account": { + "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "homePage": "http://localhost:18000" + } + }, + "verb": { + "id": "https://w3id.org/xapi/acrossx/verbs/evaluated", + "display": { + "en": "evaluated" + } + }, + "object": { + "objectType": "Activity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4_4_1", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "description": { + "en-US": "Which of the following are musical instruments?" + }, + "interactionType": "choice" + } + }, + "context": { + "statement": { + "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "objectType": "StatementRef" + }, + "contextActivities": { + "parent": [ + { + "objectType": "GroupActivity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "name": { + "en-US": "Multiple Choice Questions" + }, + "interactionType": "other" + } + } + ], + "grouping": [ + { + "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": "2c65c4e9e6a637feb42426fd35b5dac3" + } + } +}, +{ + "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "result": {"response": "a bookshelf", "success": false}, "version": "1.0.3", "actor": { "objectType": "Agent", @@ -17,14 +197,35 @@ }, "object": { "objectType": "Activity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4_3_1", "definition": { "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "description": { + "en-US": "Which piece of furniture is built for sitting?" + }, "interactionType": "choice" } }, "context": { + "statement": { + "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "objectType": "StatementRef" + }, "contextActivities": { "parent": [ + { + "objectType": "GroupActivity", + "id" :"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@a0effb954cca4759994f1ac9e9434bf4", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "name": { + "en-US": "Multiple Choice Questions" + }, + "interactionType": "other" + } + } + ], + "grouping": [ { "id": "http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course", "objectType": "Activity", @@ -43,3 +244,4 @@ } } } +] diff --git a/event_routing_backends/processors/xapi/tests/test_transformers.py b/event_routing_backends/processors/xapi/tests/test_transformers.py index d40844a6..a3cc28ce 100644 --- a/event_routing_backends/processors/xapi/tests/test_transformers.py +++ b/event_routing_backends/processors/xapi/tests/test_transformers.py @@ -31,6 +31,28 @@ def compare_events(self, transformed_event, expected_event): """ Test that transformed_event and expected_event are identical. + Arguments: + transformed_event (dict or list) + expected_event (dict or list) + + Raises: + AssertionError: Raised if the two events are not same. + """ + # Compare lists of events + if isinstance(expected_event, list): + assert isinstance(transformed_event, list) + assert len(transformed_event) == len(expected_event) + for idx, e_event in enumerate(expected_event): + self._compare_events(transformed_event[idx], e_event) + + # Compare single events + else: + self._compare_events(transformed_event, expected_event) + + def _compare_events(self, transformed_event, expected_event): + """ + Test that transformed_event and expected_event are identical. + Arguments: transformed_event (dict) expected_event (dict) diff --git a/event_routing_backends/processors/xapi/transformer.py b/event_routing_backends/processors/xapi/transformer.py index ee2860ad..3be1843c 100644 --- a/event_routing_backends/processors/xapi/transformer.py +++ b/event_routing_backends/processors/xapi/transformer.py @@ -14,6 +14,7 @@ Extensions, LanguageMap, Statement, + StatementRef, Verb, ) @@ -178,3 +179,119 @@ def verb(self): id=verb['id'], display=LanguageMap({constants.EN: verb['display']}) ) + + +class OneToManyXApiTransformerMixin: + """ + Abstract mixin that helps transform a single input event into: + + * 1 parent xAPI event, plus + * N "child" xAPI events, where N>=0 + """ + @property + def child_transformer_class(self): + """ + Abstract property which returns the transformer class to use when transforming the child events. + + Should inherit from OneToManyChildXApiTransformerMixin. + + Returns: + Type + """ + raise NotImplementedError + + def get_child_ids(self): + """ + Abstract method which returns the list of "child" event IDs from the parent event data. + + Returns: + list of strings + """ + raise NotImplementedError + + def transform(self): + """ + Transform the edX event into a list of events, if there is child data. + + If transform_child_events() is Falsey, then only the parent event is returned. + Otherwise, returns a list containing the parent event, followed by any child events. + + Returns: + ANY, or list of ANY + """ + parent_event = super().transform() + child_events = self.transform_children(parent_event) + if child_events: + return [parent_event, *child_events] + return parent_event + + def transform_children(self, parent): + """ + Transform the children of the parent xAPI event. + + + Returns: + list of ANY + """ + child_ids = self.get_child_ids() + ChildTransformer = self.child_transformer_class + return [ + ChildTransformer( + child_id=child_id, + parent=parent, + event=self.event, + ).transform() for child_id in child_ids + ] + + +class OneToManyChildXApiTransformerMixin: + """ + Mixin for processing child xAPI events from a parent transformer. + + This class handles initialization, and adds methods for the expected stanzas in the transformed child event. + + 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, + for use when transforming the child data. + """ + super().__init__(*args, **kwargs) + self.parent = parent + self.child_id = child_id + + def get_context(self): + """ + Returns the context for the xAPI transformed child event. + + Returns: + `Context` + """ + return Context( + extensions=self.get_context_extensions(), + contextActivities=self.get_context_activities(), + statement=self.get_context_statement(), + ) + + def get_context_activities(self): + """ + Get context activities for xAPI transformed event. + + Returns: + `ContextActivities` + """ + return ContextActivities( + parent=ActivityList([self.parent.object]), + grouping=ActivityList(self.parent.context.context_activities.parent), + ) + + def get_context_statement(self): + """ + Returns a StatementRef that refers to the parent event. + Returns: + `StatementRef` + """ + return StatementRef( + id=self.parent.id, + ) From 40f7422940c5c603b1187d37e46483aceb2b3fb9 Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Tue, 8 Aug 2023 16:42:15 +0930 Subject: [PATCH 6/9] fix: XApiProcessor.transform_event needs to handle lists of events * fixes "AttributeError: 'list' object has no attribute 'to_json'" during event processing for one-to-many problem_check events * adds test for ^ * reverts unneeded change to test_caliper to reinstate 100% test coverage --- .../processors/caliper/tests/test_caliper.py | 8 ++++---- .../processors/xapi/tests/test_xapi.py | 15 +++++++++++++++ .../processors/xapi/transformer_processor.py | 14 ++++++++++---- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/event_routing_backends/processors/caliper/tests/test_caliper.py b/event_routing_backends/processors/caliper/tests/test_caliper.py index 0c6a4a9b..a18bcddf 100644 --- a/event_routing_backends/processors/caliper/tests/test_caliper.py +++ b/event_routing_backends/processors/caliper/tests/test_caliper.py @@ -66,9 +66,9 @@ def test_send_method_with_successfull_flow( mocked_logger, mocked_get_transformer ): - transformed_event = [{ + transformed_event = { 'transformed_key': 'transformed_value' - }] + } mocked_transformer = MagicMock() mocked_transformer.transform.return_value = transformed_event mocked_get_transformer.return_value = mocked_transformer @@ -102,9 +102,9 @@ def test_send_method_with_successfull_flow_logging_disabled( mocked_logger, mocked_get_transformer ): - transformed_event = [{ + transformed_event = { 'transformed_key': 'transformed_value' - }] + } mocked_transformer = MagicMock() mocked_transformer.transform.return_value = transformed_event mocked_get_transformer.return_value = mocked_transformer diff --git a/event_routing_backends/processors/xapi/tests/test_xapi.py b/event_routing_backends/processors/xapi/tests/test_xapi.py index 6e5e5f75..7510f2e0 100644 --- a/event_routing_backends/processors/xapi/tests/test_xapi.py +++ b/event_routing_backends/processors/xapi/tests/test_xapi.py @@ -66,6 +66,21 @@ def test_send_method_with_successfull_flow(self, mocked_logger, mocked_get_trans self.assertIn(call(transformed_event.to_json()), mocked_logger.mock_calls) + @patch( + 'event_routing_backends.processors.xapi.transformer_processor.XApiTransformersRegistry.get_transformer' + ) + @patch('event_routing_backends.processors.xapi.transformer_processor.xapi_logger') + def test_send_method_with_event_list_successfull_flow(self, mocked_logger, mocked_get_transformer): + transformed_event = Statement() + transformed_event.object = Activity(id=str(uuid.uuid4())) + mocked_transformer = MagicMock() + mocked_transformer.transform.return_value = [transformed_event] + mocked_get_transformer.return_value = mocked_transformer + + self.processor([self.sample_event]) + + self.assertIn(call(transformed_event.to_json()), mocked_logger.mock_calls) + @patch( 'event_routing_backends.processors.xapi.transformer_processor.XApiTransformersRegistry.get_transformer' ) diff --git a/event_routing_backends/processors/xapi/transformer_processor.py b/event_routing_backends/processors/xapi/transformer_processor.py index 418e204c..d09488e9 100644 --- a/event_routing_backends/processors/xapi/transformer_processor.py +++ b/event_routing_backends/processors/xapi/transformer_processor.py @@ -42,9 +42,15 @@ def transform_event(self, event): if not XAPI_EVENTS_ENABLED.is_enabled(): raise NoBackendEnabled - transformed_event = super().transform_event(event) + transformed_events = super().transform_event(event) + if not transformed_events: + return None - if transformed_event: + if not isinstance(transformed_events, list): + transformed_events = [transformed_events] + + returned_events = [] + for transformed_event in transformed_events: event_json = transformed_event.to_json() if not transformed_event.object or not transformed_event.object.id: @@ -55,6 +61,6 @@ def transform_event(self, event): xapi_logger.info(event_json) logger.debug('xAPI statement of edx event "{}" is: {}'.format(event["name"], event_json)) - return json.loads(event_json) + returned_events.append(json.loads(event_json)) - return transformed_event + return returned_events From ea3d27d3e4514bc8fe26721725c42bcd460f84b2 Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Tue, 8 Aug 2023 16:45:08 +0930 Subject: [PATCH 7/9] chore: bumps major version to 6.0.0 --- event_routing_backends/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/event_routing_backends/__init__.py b/event_routing_backends/__init__.py index 2fbe0f36..38bca024 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__ = '5.6.0' +__version__ = '6.0.0' From 885f476203ceebbf938616a84588622915f63dd6 Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Wed, 9 Aug 2023 16:36:07 +0930 Subject: [PATCH 8/9] test: ensure that xapi event UUIDs remain unchanged. * stops mocking uuid5 during tests uuid5 generates the same UUID when provided with the same namespace + name, and so we can rely on this remaining the same in the expected fixture files. * updates test data to use the actual uuid5s generated for the given input events. --- .../processors/tests/transformers_test_mixin.py | 10 ++++++---- .../tests/fixtures/expected/complete_video.json | 2 +- ...edx.course.enrollment.activated.enterprise.json | 2 +- .../expected/edx.course.enrollment.activated.json | 2 +- ...se.enrollment.activated.required.only.data.json | 2 +- .../edx.course.enrollment.deactivated.json | 2 +- .../edx.course.enrollment.mode_changed.json | 2 +- .../expected/edx.course.grade.now_failed.json | 2 +- .../expected/edx.course.grade.now_passed.json | 2 +- .../edx.course.grade.passed.first_time.json | 2 +- .../expected/edx.forum.response.created.json | 2 +- .../expected/edx.forum.response.deleted.json | 2 +- .../expected/edx.forum.response.edited.json | 2 +- .../expected/edx.forum.response.reported.json | 2 +- .../expected/edx.forum.response.unreported.json | 2 +- .../expected/edx.forum.response.voted.json | 2 +- .../expected/edx.forum.thread.created.json | 2 +- .../expected/edx.forum.thread.deleted.json | 2 +- .../fixtures/expected/edx.forum.thread.edited.json | 2 +- .../expected/edx.forum.thread.reported.json | 2 +- .../expected/edx.forum.thread.unreported.json | 2 +- .../fixtures/expected/edx.forum.thread.viewed.json | 2 +- .../fixtures/expected/edx.forum.thread.voted.json | 2 +- .../expected/edx.grades.problem.submitted.json | 2 +- ...ades.problem.submitted.weighted_possible_0.json | 2 +- .../expected/edx.problem.completed(proposed).json | 2 +- .../edx.problem.hint.demandhint_displayed.json | 2 +- .../edx.ui.lms.link_clicked(anonymous_user).json | 2 +- .../fixtures/expected/edx.ui.lms.link_clicked.json | 2 +- ...edx.ui.lms.link_clicked.required.only.data.json | 2 +- .../expected/edx.ui.lms.outline.selected.json | 2 +- .../edx.ui.lms.sequence.next_selected.json | 2 +- .../edx.ui.lms.sequence.previous_selected.json | 2 +- .../expected/edx.ui.lms.sequence.tab_selected.json | 2 +- .../expected/edx.video.closed_captions.hidden.json | 2 +- .../expected/edx.video.closed_captions.shown.json | 2 +- .../tests/fixtures/expected/hide_transcript.json | 2 +- .../xapi/tests/fixtures/expected/load_video.json | 2 +- .../expected/load_video.required.only.data.json | 2 +- .../xapi/tests/fixtures/expected/pause_video.json | 2 +- .../xapi/tests/fixtures/expected/play_video.json | 2 +- .../fixtures/expected/problem_check(browser).json | 2 +- .../problem_check(browser).required.only.data.json | 2 +- .../expected/problem_check(browser)legacy.json | 2 +- .../fixtures/expected/problem_check(server).json | 2 +- .../problem_check(server).max_grade_0.json | 2 +- .../problem_check(server)_no_response_type.json | 2 +- ...m_check(server,multiple_questions,correct).json | 14 +++++++------- ...check(server,multiple_questions,incorrect).json | 14 +++++++------- ...server,multiple_questions,partial_correct).json | 14 +++++++------- .../problem_check(server,string_anwers).json | 2 +- ...lem_check(server,unsupported_responsetype).json | 2 +- .../xapi/tests/fixtures/expected/seek_video.json | 2 +- .../tests/fixtures/expected/show_transcript.json | 2 +- .../xapi/tests/fixtures/expected/showanswer.json | 2 +- .../fixtures/expected/speed_change_video.json | 2 +- .../xapi/tests/fixtures/expected/stop_video.json | 2 +- .../fixtures/expected/video_hide_cc_menu.json | 2 +- .../fixtures/expected/video_show_cc_menu.json | 2 +- .../processors/xapi/tests/test_transformers.py | 4 ---- 60 files changed, 82 insertions(+), 84 deletions(-) diff --git a/event_routing_backends/processors/tests/transformers_test_mixin.py b/event_routing_backends/processors/tests/transformers_test_mixin.py index eeb0320a..3e1898b1 100644 --- a/event_routing_backends/processors/tests/transformers_test_mixin.py +++ b/event_routing_backends/processors/tests/transformers_test_mixin.py @@ -5,6 +5,7 @@ import os from abc import abstractmethod from unittest.mock import patch +from uuid import UUID import ddt from django.contrib.auth import get_user_model @@ -89,11 +90,12 @@ def compare_events(self, transformed_event, expected_event): Every transformer's test case will implement its own logic to test events transformation """ - @patch('event_routing_backends.helpers.uuid') + @patch('event_routing_backends.helpers.uuid.uuid4') @ddt.data(*EVENT_FIXTURE_FILENAMES) - def test_event_transformer(self, event_filename, mocked_uuid): - mocked_uuid.uuid4.return_value = '32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb' - mocked_uuid.uuid5.return_value = '32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb' + def test_event_transformer(self, event_filename, mocked_uuid4): + # Used to generate the anonymized actor.name, + # which in turn is used to generate the event UUID. + mocked_uuid4.return_value = UUID('32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb') # if an event's expected fixture doesn't exist, the test shouldn't fail. # evaluate transformation of only supported event fixtures. diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/complete_video.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/complete_video.json index 0fa34a0f..3e533bfb 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/complete_video.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/complete_video.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "f7c8b743-f18f-5016-8455-35ff8a2f8020", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.activated.enterprise.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.activated.enterprise.json index 315695a0..53ecd183 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.activated.enterprise.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.activated.enterprise.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "bb946682-b0b9-5dc2-a17c-605ef5833c98", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.activated.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.activated.json index 15a14a0a..de5b0fa9 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.activated.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.activated.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "bb946682-b0b9-5dc2-a17c-605ef5833c98", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.activated.required.only.data.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.activated.required.only.data.json index 6fc9a13d..ce15adff 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.activated.required.only.data.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.activated.required.only.data.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "bb946682-b0b9-5dc2-a17c-605ef5833c98", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.deactivated.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.deactivated.json index c800b746..77864cfc 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.deactivated.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.deactivated.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "074afb4a-90d6-57d6-8a6a-4867b4bfcc5f", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.mode_changed.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.mode_changed.json index 15a14a0a..de5b0fa9 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.mode_changed.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.enrollment.mode_changed.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "bb946682-b0b9-5dc2-a17c-605ef5833c98", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.now_failed.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.now_failed.json index 0996e118..32af2314 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.now_failed.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.now_failed.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "ecf2a41b-d1c8-50f7-bd6f-79b305159dae", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.now_passed.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.now_passed.json index faf9c666..1da4c9a5 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.now_passed.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.now_passed.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "ebfef69f-dd05-5bf0-bf90-bad645bc6992", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.passed.first_time.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.passed.first_time.json index faf9c666..1da4c9a5 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.passed.first_time.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.passed.first_time.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "ebfef69f-dd05-5bf0-bf90-bad645bc6992", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.created.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.created.json index ad1e325f..1377089d 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.created.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.created.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b57c965a-218a-5a17-aee8-9f3dc75c5bb3", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.deleted.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.deleted.json index 29818cd9..d73b9334 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.deleted.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.deleted.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "54acd352-b4f5-56fb-a328-d7fa43d90710", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.edited.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.edited.json index 64922c5b..5743425e 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.edited.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.edited.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "196cf44f-9d76-5d76-ac0d-683359d9b88b", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.reported.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.reported.json index f5a410ff..cef6fe26 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.reported.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.reported.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b52e9a1c-3ff6-5841-8cb3-d9edbed4fc4f", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.unreported.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.unreported.json index d1f39321..a5a035e1 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.unreported.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.unreported.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "23ae2117-975a-5669-9d1d-cc47928b1959", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.voted.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.voted.json index ddb4c5f0..3aea4a9f 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.voted.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.response.voted.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "806c7c48-7df6-56b9-bdb0-c379a968acf5", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.created.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.created.json index 4e9983c9..929ea31d 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.created.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.created.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "a4e3d431-0281-5b3e-bcf7-0ddbeb8543b2", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.deleted.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.deleted.json index df1d36c9..5a7a9655 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.deleted.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.deleted.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "e57ea5a8-ad20-5717-b648-824bede138aa", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.edited.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.edited.json index f9285491..26f92d11 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.edited.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.edited.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "ae1a9bfe-2d3b-5afd-87f4-ad8d4c596932", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.reported.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.reported.json index 600c16d2..486b8276 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.reported.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.reported.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "81c7f03f-e07e-5b54-9095-f5019e5bbef3", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.unreported.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.unreported.json index b83df2a9..d28b70d2 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.unreported.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.unreported.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "0ce637d7-e823-5447-8cdf-8da3afb4fd9b", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.viewed.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.viewed.json index c51212f8..a00c62e8 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.viewed.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.viewed.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "d1325940-fe1b-5042-90c1-e3365960c9a1", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.voted.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.voted.json index c7fe0a24..e1fdac8a 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.voted.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.thread.voted.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "883a9580-f6bc-55b6-b5c8-1d5a13a8bdd7", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.problem.submitted.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.problem.submitted.json index 32f81365..1b88f869 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.problem.submitted.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.problem.submitted.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "a10da8af-8e6a-5d1c-b7d5-cdabaacc0ba8", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.problem.submitted.weighted_possible_0.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.problem.submitted.weighted_possible_0.json index d110ad8f..c147e570 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.problem.submitted.weighted_possible_0.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.problem.submitted.weighted_possible_0.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "a10da8af-8e6a-5d1c-b7d5-cdabaacc0ba8", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.problem.completed(proposed).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.problem.completed(proposed).json index f8380c0d..ee707af4 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.problem.completed(proposed).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.problem.completed(proposed).json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "1940cdf9-6a89-5cb7-82f9-5b0955584db8", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.problem.hint.demandhint_displayed.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.problem.hint.demandhint_displayed.json index 5ec5acbb..d8b54e3e 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.problem.hint.demandhint_displayed.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.problem.hint.demandhint_displayed.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "e9b9ba0f-d976-5656-9a6c-d432b12335ea", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.link_clicked(anonymous_user).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.link_clicked(anonymous_user).json index bda507cd..31a0ac3a 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.link_clicked(anonymous_user).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.link_clicked(anonymous_user).json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "01b620b3-0cba-556e-96d5-d1e9ef63489d", "actor": { "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"}, "objectType": "Agent" diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.link_clicked.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.link_clicked.json index 62dcf609..c259f5b6 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.link_clicked.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.link_clicked.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "01b620b3-0cba-556e-96d5-d1e9ef63489d", "actor": { "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"}, "objectType": "Agent" diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.link_clicked.required.only.data.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.link_clicked.required.only.data.json index 155982c8..ce5aec8b 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.link_clicked.required.only.data.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.link_clicked.required.only.data.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "01b620b3-0cba-556e-96d5-d1e9ef63489d", "actor": { "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"}, "objectType": "Agent" diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.outline.selected.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.outline.selected.json index ef178ed7..c050b005 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.outline.selected.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.outline.selected.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "d30c30af-2eeb-5ae3-a765-4e2ca9bb3283", "actor": { "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"}, "objectType": "Agent" diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.sequence.next_selected.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.sequence.next_selected.json index c344e017..261f2611 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.sequence.next_selected.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.sequence.next_selected.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "2802561d-3e8e-56d4-8944-07a8fbfd02b2", "actor": { "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"}, "objectType": "Agent" diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.sequence.previous_selected.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.sequence.previous_selected.json index 9957a861..b12e3e2d 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.sequence.previous_selected.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.sequence.previous_selected.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "6e0efdce-2611-52f8-a213-dcd81b78cfbd", "actor": { "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"}, "objectType": "Agent" diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.sequence.tab_selected.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.sequence.tab_selected.json index 3cea2076..a7073946 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.sequence.tab_selected.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.ui.lms.sequence.tab_selected.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "0f73de3b-445e-5674-aab9-b0117c64671a", "actor": { "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"}, "objectType": "Agent" diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.video.closed_captions.hidden.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.video.closed_captions.hidden.json index 5753ce82..c4f5c103 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.video.closed_captions.hidden.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.video.closed_captions.hidden.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "9e08cf47-e86f-5b64-b48f-a4d524f9a7e4", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.video.closed_captions.shown.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.video.closed_captions.shown.json index c0b57780..468f5b9a 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.video.closed_captions.shown.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.video.closed_captions.shown.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "9e08cf47-e86f-5b64-b48f-a4d524f9a7e4", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/hide_transcript.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/hide_transcript.json index 1f5e17bd..32b8efec 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/hide_transcript.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/hide_transcript.json @@ -1,5 +1,5 @@ { - "id": "0b9e39477cf34507a7a48f74be381fdd", + "id": "8c936f51-267d-51ad-a850-9fffdb7595de", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/load_video.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/load_video.json index 27a16982..d0ffb4ac 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/load_video.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/load_video.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "3feca024-a2a2-5ee3-9307-50711dc48abd", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/load_video.required.only.data.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/load_video.required.only.data.json index 877da649..37065c1f 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/load_video.required.only.data.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/load_video.required.only.data.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "3feca024-a2a2-5ee3-9307-50711dc48abd", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/pause_video.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/pause_video.json index 007a1ab2..6b471695 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/pause_video.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/pause_video.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b2fff5f6-c77d-5aa4-a08b-bb547d6ee5a6", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/play_video.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/play_video.json index b74454b4..4e4d2e17 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/play_video.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/play_video.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "203e182c-6a2d-5458-9c61-a271297d459a", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser).json index 54831e74..ad39df5f 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser).json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "2a1f0f7c-6636-52df-913b-59e2f197a58e", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser).required.only.data.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser).required.only.data.json index 0628996d..1d466f6c 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser).required.only.data.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser).required.only.data.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "37e0953c-fa85-5435-8c53-723909ab81d9", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser)legacy.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser)legacy.json index 98962f50..398f4e06 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser)legacy.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(browser)legacy.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "2a1f0f7c-6636-52df-913b-59e2f197a58e", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).json index d15b6a37..3fb71d5a 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "c9c7da1a-0cf0-5161-9bf5-35c61091f826", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).max_grade_0.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).max_grade_0.json index c287bad1..5cf96cc3 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).max_grade_0.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server).max_grade_0.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "c9c7da1a-0cf0-5161-9bf5-35c61091f826", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server)_no_response_type.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server)_no_response_type.json index 2fe58cab..cde5281a 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server)_no_response_type.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server)_no_response_type.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "c9c7da1a-0cf0-5161-9bf5-35c61091f826", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json index f47b6ae9..0b5e3797 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json @@ -1,6 +1,6 @@ [ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "result": {"score": {"min": 0, "max": 3, "raw": 3, "scaled": 1}, "success": true}, "version": "1.0.3", "actor": { @@ -47,7 +47,7 @@ } }, { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "result": {"response": "blue", "success": true}, "version": "1.0.3", "actor": { @@ -76,7 +76,7 @@ }, "context": { "statement": { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "objectType": "StatementRef" }, "contextActivities": { @@ -113,7 +113,7 @@ } }, { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "result": {"response": "['a piano', 'a guitar']", "success": true}, "version": "1.0.3", "actor": { @@ -142,7 +142,7 @@ }, "context": { "statement": { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "objectType": "StatementRef" }, "contextActivities": { @@ -179,7 +179,7 @@ } }, { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "result": {"response": "a chair", "success": true}, "version": "1.0.3", "actor": { @@ -208,7 +208,7 @@ }, "context": { "statement": { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "objectType": "StatementRef" }, "contextActivities": { diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json index 41541ab7..db914659 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json @@ -1,6 +1,6 @@ [ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "result": {"score": {"min": 0, "max": 3, "raw": 0, "scaled": 0}, "success": false}, "version": "1.0.3", "actor": { @@ -49,7 +49,7 @@ } }, { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "result": {"response": "yellow", "success": false}, "version": "1.0.3", "actor": { @@ -78,7 +78,7 @@ }, "context": { "statement": { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "objectType": "StatementRef" }, "contextActivities": { @@ -115,7 +115,7 @@ } }, { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "result": {"response": "['a window']", "success": false}, "version": "1.0.3", "actor": { @@ -144,7 +144,7 @@ }, "context": { "statement": { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "objectType": "StatementRef" }, "contextActivities": { @@ -181,7 +181,7 @@ } }, { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "result": {"response": "a bookshelf", "success": false}, "version": "1.0.3", "actor": { @@ -210,7 +210,7 @@ }, "context": { "statement": { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "objectType": "StatementRef" }, "contextActivities": { diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json index d8014195..8a79bd86 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json @@ -1,6 +1,6 @@ [ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "result": {"score": {"min": 0, "max": 3, "raw": 2, "scaled": 0.6666666666666666}, "success": false}, "version": "1.0.3", "actor": { @@ -47,7 +47,7 @@ } }, { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "result": {"response": "blue", "success": true}, "version": "1.0.3", "actor": { @@ -76,7 +76,7 @@ }, "context": { "statement": { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "objectType": "StatementRef" }, "contextActivities": { @@ -113,7 +113,7 @@ } }, { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "result": {"response": "['a piano', 'a guitar']", "success": true}, "version": "1.0.3", "actor": { @@ -142,7 +142,7 @@ }, "context": { "statement": { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "objectType": "StatementRef" }, "contextActivities": { @@ -179,7 +179,7 @@ } }, { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "result": {"response": "a bookshelf", "success": false}, "version": "1.0.3", "actor": { @@ -208,7 +208,7 @@ }, "context": { "statement": { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", "objectType": "StatementRef" }, "contextActivities": { diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,string_anwers).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,string_anwers).json index 04ec3fe6..7b22833e 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,string_anwers).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,string_anwers).json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "c9c7da1a-0cf0-5161-9bf5-35c61091f826", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,unsupported_responsetype).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,unsupported_responsetype).json index 7e9e8c76..bf6204c3 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,unsupported_responsetype).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,unsupported_responsetype).json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "c9c7da1a-0cf0-5161-9bf5-35c61091f826", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/seek_video.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/seek_video.json index 86dc8def..dedd4022 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/seek_video.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/seek_video.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "50e46aad-346e-58da-ab55-e599abcd554c", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/show_transcript.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/show_transcript.json index 543cd097..52714c85 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/show_transcript.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/show_transcript.json @@ -1,5 +1,5 @@ { - "id": "0b9e39477cf34507a7a48f74be381fdd", + "id": "8c936f51-267d-51ad-a850-9fffdb7595de", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/showanswer.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/showanswer.json index f4b40e3e..64513665 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/showanswer.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/showanswer.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "c552e1bc-2a30-555c-99e8-79d72413eaf7", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/speed_change_video.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/speed_change_video.json index 9b49bc4f..7a2762c4 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/speed_change_video.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/speed_change_video.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "2f31f095-ee62-54d8-bcab-8e764a45b47f", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/stop_video.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/stop_video.json index 34acb8cf..52b61316 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/stop_video.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/stop_video.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "d079c151-11f1-5a91-8e49-9627a5c9832d", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/video_hide_cc_menu.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/video_hide_cc_menu.json index 5753ce82..c4f5c103 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/video_hide_cc_menu.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/video_hide_cc_menu.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "9e08cf47-e86f-5b64-b48f-a4d524f9a7e4", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/video_show_cc_menu.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/video_show_cc_menu.json index c0b57780..468f5b9a 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/video_show_cc_menu.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/video_show_cc_menu.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "9e08cf47-e86f-5b64-b48f-a4d524f9a7e4", "actor": { "objectType": "Agent", "account": {"homePage": "http://localhost:18000", "name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb"} diff --git a/event_routing_backends/processors/xapi/tests/test_transformers.py b/event_routing_backends/processors/xapi/tests/test_transformers.py index a3cc28ce..369d54f5 100644 --- a/event_routing_backends/processors/xapi/tests/test_transformers.py +++ b/event_routing_backends/processors/xapi/tests/test_transformers.py @@ -60,11 +60,7 @@ def _compare_events(self, transformed_event, expected_event): Raises: AssertionError: Raised if the two events are not same. """ - # id is a randomly generated UUID therefore not comparing that transformed_event_json = json.loads(transformed_event.to_json()) - self.assertIn('id', transformed_event_json) - expected_event.pop('id') - transformed_event_json.pop('id') self.assertDictEqual(expected_event, transformed_event_json) @override_settings(XAPI_AGENT_IFI_TYPE='mbox') From 9482075cfb9530747a2d03dd2071aacfc50d82dd Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Wed, 9 Aug 2023 18:25:35 +0930 Subject: [PATCH 9/9] fix: child events must use unique event IDs * pulls event ID generation into get_event_id(), taking care not to modify how parent event IDs are generated. * overrides get_event_id() for child events by using the child_id as part of the UUID namespace_key. * updates tests to check that child events and their parent event use different event IDs, and the affected problem_check multiple-question test data --- ...ck(server,multiple_questions,correct).json | 6 +-- ...(server,multiple_questions,incorrect).json | 6 +-- ...r,multiple_questions,partial_correct).json | 6 +-- .../xapi/tests/test_transformers.py | 5 ++ .../processors/xapi/transformer.py | 46 +++++++++++++++---- 5 files changed, 51 insertions(+), 18 deletions(-) diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json index 0b5e3797..218b0799 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json @@ -47,7 +47,7 @@ } }, { - "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", + "id": "2cb89608-ab9c-5129-ae6e-4d7d2f9cde68", "result": {"response": "blue", "success": true}, "version": "1.0.3", "actor": { @@ -113,7 +113,7 @@ } }, { - "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", + "id": "d0b94156-376d-5012-be91-c6235d3a0770", "result": {"response": "['a piano', 'a guitar']", "success": true}, "version": "1.0.3", "actor": { @@ -179,7 +179,7 @@ } }, { - "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", + "id": "a51f4090-8ebf-5644-a669-a18768523eab", "result": {"response": "a chair", "success": true}, "version": "1.0.3", "actor": { diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json index db914659..bc68cba1 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json @@ -49,7 +49,7 @@ } }, { - "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", + "id": "2cb89608-ab9c-5129-ae6e-4d7d2f9cde68", "result": {"response": "yellow", "success": false}, "version": "1.0.3", "actor": { @@ -115,7 +115,7 @@ } }, { - "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", + "id": "d0b94156-376d-5012-be91-c6235d3a0770", "result": {"response": "['a window']", "success": false}, "version": "1.0.3", "actor": { @@ -181,7 +181,7 @@ } }, { - "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", + "id": "a51f4090-8ebf-5644-a669-a18768523eab", "result": {"response": "a bookshelf", "success": false}, "version": "1.0.3", "actor": { diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json index 8a79bd86..5d5018b8 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json @@ -47,7 +47,7 @@ } }, { - "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", + "id": "2cb89608-ab9c-5129-ae6e-4d7d2f9cde68", "result": {"response": "blue", "success": true}, "version": "1.0.3", "actor": { @@ -113,7 +113,7 @@ } }, { - "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", + "id": "d0b94156-376d-5012-be91-c6235d3a0770", "result": {"response": "['a piano', 'a guitar']", "success": true}, "version": "1.0.3", "actor": { @@ -179,7 +179,7 @@ } }, { - "id": "b2f56b44-07f9-5044-85d1-c1dbc550d35f", + "id": "a51f4090-8ebf-5644-a669-a18768523eab", "result": {"response": "a bookshelf", "success": false}, "version": "1.0.3", "actor": { diff --git a/event_routing_backends/processors/xapi/tests/test_transformers.py b/event_routing_backends/processors/xapi/tests/test_transformers.py index 369d54f5..12a34649 100644 --- a/event_routing_backends/processors/xapi/tests/test_transformers.py +++ b/event_routing_backends/processors/xapi/tests/test_transformers.py @@ -42,9 +42,14 @@ def compare_events(self, transformed_event, expected_event): if isinstance(expected_event, list): assert isinstance(transformed_event, list) assert len(transformed_event) == len(expected_event) + event_ids = set() for idx, e_event in enumerate(expected_event): + event_ids.add(transformed_event[idx].id) self._compare_events(transformed_event[idx], e_event) + # Ensure a unique event ID was applied for the parent + child events + assert len(event_ids) == len(transformed_event) + # Compare single events else: self._compare_events(transformed_event, expected_event) diff --git a/event_routing_backends/processors/xapi/transformer.py b/event_routing_backends/processors/xapi/transformer.py index 3be1843c..d75761ff 100644 --- a/event_routing_backends/processors/xapi/transformer.py +++ b/event_routing_backends/processors/xapi/transformer.py @@ -52,23 +52,31 @@ def base_transform(self, transformed_event): Transform the fields that are common for all events. """ transformed_event = super().base_transform(transformed_event) - actor = self.get_actor() - event_timestamp = self.get_timestamp() transformed_event.update({ - 'actor': actor, + 'id': self.get_event_id(), + 'actor': self.get_actor(), 'context': self.get_context(), - 'timestamp': event_timestamp, + 'timestamp': self.get_timestamp(), }) - transformed_event['actor'] = self.get_actor() - transformed_event['context'] = self.get_context() - transformed_event['timestamp'] = self.get_timestamp() + return transformed_event + + def get_event_id(self): + """ + Generates the UUID for this event. + + Uses the actor, event timestamp, and verb to generate a UUID for this event + which will be the same even if this event is re-processed. + Returns: + UUID + """ # Warning! changing anything in these 2 lines or changing the "base_uuid" can invalidate # billions of rows in the database. Please have a community discussion first before introducing # any change in generation of UUID. + actor = self.get_actor() + event_timestamp = self.get_timestamp() uuid_str = f'{actor.to_json()}-{event_timestamp}' - transformed_event['id'] = get_uuid5(self.verb.to_json(), uuid_str) # pylint: disable=no-member - return transformed_event + return get_uuid5(self.verb.to_json(), uuid_str) # pylint: disable=no-member def get_actor(self): """ @@ -261,6 +269,26 @@ def __init__(self, parent, child_id, *args, **kwargs): self.parent = parent self.child_id = child_id + def get_event_id(self): + """ + Generates the UUID for this event. + + Uses the actor, event timestamp, verb, and child_id to generate a UUID for this child event + which ensures a unique event ID for each child event which also differs from the parent event, + which will be the same even if this event is re-processed. + + Returns: + UUID + """ + # Warning! changing anything in these 2 lines or changing the "base_uuid" can invalidate + # billions of rows in the database. Please have a community discussion first before introducing + # any change in generation of UUID. + 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}' + return get_uuid5(namespace_key, name) + def get_context(self): """ Returns the context for the xAPI transformed child event.