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/Makefile b/Makefile index 0e49b534..6655b8ad 100644 --- a/Makefile +++ b/Makefile @@ -12,14 +12,15 @@ help: ## display this help message @awk -F ':.*?## ' '/^[a-zA-Z]/ && NF==2 {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort clean: ## remove generated byte code, coverage reports, and build artifacts - find . -name '__pycache__' -exec rm -rf {} + - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + coverage erase rm -fr build/ rm -fr dist/ rm -fr *.egg-info + rm -rf test_output/*.json + find . -name '__pycache__' -exec rm -rf {} + + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + coverage: clean ## generate and view HTML coverage report pytest --cov-report html diff --git a/event_routing_backends/__init__.py b/event_routing_backends/__init__.py index 76a6b185..abb7ae37 100644 --- a/event_routing_backends/__init__.py +++ b/event_routing_backends/__init__.py @@ -2,4 +2,4 @@ Various backends for receiving edX LMS events.. """ -__version__ = '6.2.0' +__version__ = '7.0.0' 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 b4d8bfa6..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() @@ -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', 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/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/tests/test_caliper.py b/event_routing_backends/processors/caliper/tests/test_caliper.py index 3857da23..a18bcddf 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' @@ -73,7 +73,7 @@ def test_send_method_with_successfull_flow( 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( @@ -109,7 +109,7 @@ def test_send_method_with_successfull_flow_logging_disabled( 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/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/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/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/tests/transformers_test_mixin.py b/event_routing_backends/processors/tests/transformers_test_mixin.py index 083c1c62..a5db646e 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 import pytest @@ -90,11 +91,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. @@ -118,7 +120,16 @@ def test_event_transformer(self, event_filename, mocked_uuid): self.compare_events(actual_transformed_event, expected_event) except Exception as e: # pragma: no cover with open(f"test_output/generated.{event_filename}.json", "w") as actual_transformed_event_file: - actual_transformed_event_file.write(actual_transformed_event.to_json()) + try: + actual_transformed_event_file.write(actual_transformed_event.to_json()) + # Lists of events will trigger this exception and need to be transformed together + except AttributeError: + actual_transformed_event_file.write("[") + out_events = [] + for event in actual_transformed_event: + out_events.append(event.to_json()) + actual_transformed_event_file.write(",".join(out_events)) + actual_transformed_event_file.write("]") with open(f"test_output/expected.{event_filename}.json", "w") as expected_event_file: json.dump(expected_event, expected_event_file, indent=4) 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/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.completion.block_completion.changed.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.completion.block_completion.changed.json index 4ea719af..2aa7c6ea 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.completion.block_completion.changed.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.completion.block_completion.changed.json @@ -1,5 +1,5 @@ { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "88e13618-29ec-5b75-9de7-e2e220af1728", "result": { "completion": true, "extensions": { "https://w3id.org/xapi/cmi5/result/extensions/progress": 100.0 } 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.comment.created.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.created.json index 7f6e60f0..16127d3d 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.created.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.created.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "ca2a0abb-2dd6-5032-86c5-360a9fff4404", "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.comment.deleted.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.deleted.json index 5e98e8ac..4cf59452 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.deleted.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.deleted.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "b8f6d5e1-50a3-573c-adf6-72c5bfc46112", "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.comment.edited.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.edited.json index 78474a82..7807b92c 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.edited.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.edited.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "2553ef8a-5838-5ed1-9d40-0cb238a11c71", "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.comment.reported.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.reported.json index a8dd965c..e5e599cc 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.reported.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.reported.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "84683715-6e1b-54a9-af87-ee8c193af8f9", "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.comment.unreported.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.unreported.json index b7e61193..70252012 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.unreported.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.forum.comment.unreported.json @@ -1,5 +1,5 @@ { - "id": "6d1f033b-3f70-458c-b53a-e6bb63cbaef9", + "id": "995dcbbd-d6fd-5440-825f-7e5f2daf0588", "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.course.grade_calculated_letter_grade.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.course.grade_calculated_letter_grade.json index ea2b45ec..7501c232 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.course.grade_calculated_letter_grade.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.course.grade_calculated_letter_grade.json @@ -1,5 +1,5 @@ { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "375b9512-1f1c-541d-96da-0d4eeff13116", "result": { "score": { "scaled": 1.0, diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.course.grade_calculated_no_letter_grade.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.course.grade_calculated_no_letter_grade.json index 85135e05..328c7426 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.course.grade_calculated_no_letter_grade.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.course.grade_calculated_no_letter_grade.json @@ -1,5 +1,5 @@ { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "375b9512-1f1c-541d-96da-0d4eeff13116", "result": { "score": { "scaled": 0.02, 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.grades.subsection.grade_calculated.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.subsection.grade_calculated.json index 231ecb37..e09f487e 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.subsection.grade_calculated.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.subsection.grade_calculated.json @@ -1,5 +1,5 @@ { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "7e8d93b1-1fe9-53a1-a3d2-60a2a931f80a", "result": { "score": { "scaled": 1.0, "raw": 3.0, "min": 0.0, "max": 3.0 }, "success": true }, "version": "1.0.3", "actor": { diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.subsection.grade_calculated.weighted_possible_0.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.subsection.grade_calculated.weighted_possible_0.json index 3fb42447..dac3ee27 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.subsection.grade_calculated.weighted_possible_0.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.subsection.grade_calculated.weighted_possible_0.json @@ -1,5 +1,5 @@ { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "7e8d93b1-1fe9-53a1-a3d2-60a2a931f80a", "result": { "score": { "scaled": 0.0, "raw": 0.0, "min": 0.0, "max": 0.0 }, "success": true }, "version": "1.0.3", "actor": { 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.special_exam.practice.attempt.created.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.practice.attempt.created.json index e57d2db7..ac96237e 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.practice.attempt.created.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.practice.attempt.created.json @@ -1,5 +1,5 @@ { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "eb1aa991-fbf2-5ea4-a699-7533030f0c44", "version": "1.0.3", "actor": { "objectType": "Agent", diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.practice.attempt.submitted.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.practice.attempt.submitted.json index dc437b4b..02ead3d4 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.practice.attempt.submitted.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.practice.attempt.submitted.json @@ -1,5 +1,5 @@ { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "2399cb4b-a054-5d49-91cb-1fac2201da03", "version": "1.0.3", "actor": { "objectType": "Agent", diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.proctored.attempt.created.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.proctored.attempt.created.json index 795c5cd7..d0ebcb07 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.proctored.attempt.created.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.proctored.attempt.created.json @@ -1,5 +1,5 @@ { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "eb1aa991-fbf2-5ea4-a699-7533030f0c44", "version": "1.0.3", "actor": { "objectType": "Agent", diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.proctored.attempt.submitted.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.proctored.attempt.submitted.json index fef793a3..bf4acb8b 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.proctored.attempt.submitted.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.proctored.attempt.submitted.json @@ -1,5 +1,5 @@ { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "2399cb4b-a054-5d49-91cb-1fac2201da03", "version": "1.0.3", "actor": { "objectType": "Agent", diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.timed.attempt.created.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.timed.attempt.created.json index 1fc4f26d..0e466b14 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.timed.attempt.created.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.timed.attempt.created.json @@ -1,5 +1,5 @@ { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "eb1aa991-fbf2-5ea4-a699-7533030f0c44", "version": "1.0.3", "actor": { "objectType": "Agent", diff --git a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.timed.attempt.submitted.json b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.timed.attempt.submitted.json index 4c54f35b..84370c4d 100644 --- a/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.timed.attempt.submitted.json +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/edx.special_exam.timed.attempt.submitted.json @@ -1,5 +1,5 @@ { - "id": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb", + "id": "2399cb4b-a054-5d49-91cb-1fac2201da03", "version": "1.0.3", "actor": { "objectType": "Agent", 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 2a7eef9a..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"} @@ -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).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 2d04afa4..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"} @@ -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).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 81c84bc8..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"} @@ -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 new file mode 100644 index 00000000..265dd612 --- /dev/null +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,correct).json @@ -0,0 +1,251 @@ +[ +{ + "id": "8ac118c6-7964-5939-8b2b-fa3070881c66", + "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" + } + }, + "timestamp": "2023-08-03T06:32:31.431355+00:00", + "context": { + "contextActivities": { + "parent": [ + { + "id": "http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course", + "objectType": "Activity", + "definition": { + "name": { + "en-US": "Demonstration Course" + }, + "type": "http://adlnet.gov/expapi/activities/course" + } + } + ] + }, + "extensions": { + "https://w3id.org/xapi/openedx/extension/transformer-version": "event-routing-backends@1.1.1", + "https://w3id.org/xapi/openedx/extensions/session-id": "97662bef7c463c187b8fd91e0f580468" + } + } +}, +{ + "id": "7e4f87d3-911b-581b-b41e-7b4d938c4647", + "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" + } + }, + "timestamp": "2023-08-03T06:32:31.431355+00:00", + "context": { + "statement": { + "id": "8ac118c6-7964-5939-8b2b-fa3070881c66", + "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": "19ff71b6-3e82-5dda-886d-f98182d61b90", + "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" + } + }, + "timestamp": "2023-08-03T06:32:31.431355+00:00", + "context": { + "statement": { + "id": "8ac118c6-7964-5939-8b2b-fa3070881c66", + "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": "02d16f04-dde1-5d0f-8a6c-abe76128db76", + "result": {"response": "a chair", "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_3_1", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "description": { + "en-US": "Which piece of furniture is built for sitting?" + }, + "interactionType": "choice" + } + }, + "timestamp": "2023-08-03T06:32:31.431355+00:00", + "context": { + "statement": { + "id": "8ac118c6-7964-5939-8b2b-fa3070881c66", + "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" + } + } +} +] 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..c3d7c7be --- /dev/null +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,incorrect).json @@ -0,0 +1,253 @@ +[ +{ + "id": "980a041a-19a8-59c6-9e6c-72fd94d2577e", + "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" + } + }, + "timestamp": "2023-08-03T06:30:32.026903+00:00", + "context": { + "contextActivities": { + "parent": [ + { + "id": "http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course", + "objectType": "Activity", + "definition": { + "name": { + "en-US": "Demonstration Course" + }, + "type": "http://adlnet.gov/expapi/activities/course" + } + } + ] + }, + "extensions": { + "https://w3id.org/xapi/openedx/extension/transformer-version": "event-routing-backends@1.1.1", + "https://w3id.org/xapi/openedx/extensions/session-id": "97662bef7c463c187b8fd91e0f580468" + } + } +}, +{ + "id": "e734c4c4-0c9f-53c9-ae3a-056705d552a1", + "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" + } + }, + "timestamp": "2023-08-03T06:30:32.026903+00:00", + "context": { + "statement": { + "id": "980a041a-19a8-59c6-9e6c-72fd94d2577e", + "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": "fd898be3-1b7e-5273-a83f-02cb95cbb91d", + "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" + } + }, + "timestamp": "2023-08-03T06:30:32.026903+00:00", + "context": { + "statement": { + "id": "980a041a-19a8-59c6-9e6c-72fd94d2577e", + "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": "59ce6fe5-4ecc-5aa2-bc2d-00043b4a481d", + "result": {"response": "a bookshelf", "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_3_1", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "description": { + "en-US": "Which piece of furniture is built for sitting?" + }, + "interactionType": "choice" + } + }, + "timestamp": "2023-08-03T06:30:32.026903+00:00", + "context": { + "statement": { + "id": "980a041a-19a8-59c6-9e6c-72fd94d2577e", + "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" + } + } +} +] 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..0e9e6319 --- /dev/null +++ b/event_routing_backends/processors/xapi/tests/fixtures/expected/problem_check(server,multiple_questions,partial_correct).json @@ -0,0 +1,251 @@ +[ +{ + "id": "7d8be6d2-15b9-5f0d-bdfc-4e2daee2eee4", + "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" + } + }, + "timestamp": "2023-08-03T06:31:39.005451+00:00", + "context": { + "contextActivities": { + "parent": [ + { + "id": "http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course", + "objectType": "Activity", + "definition": { + "name": { + "en-US": "Demonstration Course" + }, + "type": "http://adlnet.gov/expapi/activities/course" + } + } + ] + }, + "extensions": { + "https://w3id.org/xapi/openedx/extension/transformer-version": "event-routing-backends@1.1.1", + "https://w3id.org/xapi/openedx/extensions/session-id": "2c65c4e9e6a637feb42426fd35b5dac3" + } + } +}, +{ + "id": "f3e25970-2c71-57a6-9ba9-8ba714e68f76", + "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" + } + }, + "timestamp": "2023-08-03T06:31:39.005451+00:00", + "context": { + "statement": { + "id": "7d8be6d2-15b9-5f0d-bdfc-4e2daee2eee4", + "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": "8a30b6a8-b9a3-5246-8a23-7bed05cfc031", + "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" + } + }, + "timestamp": "2023-08-03T06:31:39.005451+00:00", + "context": { + "statement": { + "id": "7d8be6d2-15b9-5f0d-bdfc-4e2daee2eee4", + "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": "b7d38689-d6fc-594e-9f8b-79cca5f7302c", + "result": {"response": "a bookshelf", "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_3_1", + "definition": { + "type": "http://adlnet.gov/expapi/activities/cmi.interaction", + "description": { + "en-US": "Which piece of furniture is built for sitting?" + }, + "interactionType": "choice" + } + }, + "timestamp": "2023-08-03T06:31:39.005451+00:00", + "context": { + "statement": { + "id": "7d8be6d2-15b9-5f0d-bdfc-4e2daee2eee4", + "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" + } + } +} +] 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 d40844a6..12a34649 100644 --- a/event_routing_backends/processors/xapi/tests/test_transformers.py +++ b/event_routing_backends/processors/xapi/tests/test_transformers.py @@ -31,6 +31,33 @@ 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) + 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) + + def _compare_events(self, transformed_event, expected_event): + """ + Test that transformed_event and expected_event are identical. + Arguments: transformed_event (dict) expected_event (dict) @@ -38,11 +65,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') diff --git a/event_routing_backends/processors/xapi/tests/test_xapi.py b/event_routing_backends/processors/xapi/tests/test_xapi.py index 9bb044ba..7510f2e0 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,22 @@ 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) + + @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) @@ -77,7 +92,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 +108,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 +117,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.py b/event_routing_backends/processors/xapi/transformer.py index 15586b6d..e946d602 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, ) @@ -46,22 +47,36 @@ 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. """ - actor = self.get_actor() - event_timestamp = self.get_timestamp() - self.transformed_event = { - 'actor': actor, + transformed_event = super().base_transform(transformed_event) + transformed_event.update({ + 'id': self.get_event_id(), + 'actor': self.get_actor(), 'context': self.get_context(), - 'timestamp': event_timestamp, - } + '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}' - self.transformed_event['id'] = get_uuid5(self.verb.to_json(), uuid_str) # pylint: disable=no-member + return get_uuid5(self.verb.to_json(), uuid_str) # pylint: disable=no-member def get_actor(self): """ @@ -172,3 +187,139 @@ 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_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. + + 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, + ) diff --git a/event_routing_backends/processors/xapi/transformer_processor.py b/event_routing_backends/processors/xapi/transformer_processor.py index 71ac712d..d09488e9 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 @@ -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