From d6052675da8e8f4d997b00f03d8f28b874ce57c3 Mon Sep 17 00:00:00 2001 From: Gabe Mulley Date: Thu, 27 Mar 2014 12:48:53 -0400 Subject: [PATCH] Support emitting analytical events from xblocks running in the LMS XBlocks can and should use the "publish" method provided by the runtime to emit analytical events. In theory we could now start replacing calls to track_function with calls to publish, however, that migration is not handled by this change. Fixes: AN-789 --- common/lib/xmodule/xmodule/capa_base.py | 2 +- common/lib/xmodule/xmodule/lti_module.py | 6 +++--- common/lib/xmodule/xmodule/x_module.py | 8 +++++--- docs/en_us/developers/source/xblocks.rst | 15 +++++++++++---- lms/djangoapps/courseware/module_render.py | 18 +++++++++--------- requirements/edx/github.txt | 2 +- 6 files changed, 30 insertions(+), 21 deletions(-) diff --git a/common/lib/xmodule/xmodule/capa_base.py b/common/lib/xmodule/xmodule/capa_base.py index edc0449af6..b88f83f552 100644 --- a/common/lib/xmodule/xmodule/capa_base.py +++ b/common/lib/xmodule/xmodule/capa_base.py @@ -844,8 +844,8 @@ class CapaMixin(CapaFields): score = self.lcp.get_score() self.runtime.publish( self, + 'grade', { - 'event_name': 'grade', 'value': score['score'], 'max_value': score['total'], } diff --git a/common/lib/xmodule/xmodule/lti_module.py b/common/lib/xmodule/xmodule/lti_module.py index dc2fcf06b7..5e2bf900a7 100644 --- a/common/lib/xmodule/xmodule/lti_module.py +++ b/common/lib/xmodule/xmodule/lti_module.py @@ -533,12 +533,12 @@ oauth_consumer_key="", oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D"'} if action == 'replaceResultRequest': self.system.publish( self, + 'grade', { - 'event_name': 'grade', 'value': score * self.max_score(), 'max_value': self.max_score(), - }, - custom_user=real_user + 'user_id': real_user.id, + } ) values = { diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 38bb854152..1e3bebcc96 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -1059,11 +1059,13 @@ class DescriptorSystem(ConfigurableFragmentWrapper, Runtime): # pylint: disable """ raise NotImplementedError("edX Platform doesn't currently implement XBlock resource urls") - def publish(self, block, event): + def publish(self, block, event_type, event): """ See :meth:`xblock.runtime.Runtime:publish` for documentation. """ - raise NotImplementedError("edX Platform doesn't currently implement XBlock publish") + xmodule_runtime = getattr(block, 'xmodule_runtime', None) + if xmodule_runtime is not None: + return xmodule_runtime.publish(block, event_type, event) def add_block_as_child_node(self, block, node): child = etree.SubElement(node, "unknown") @@ -1228,7 +1230,7 @@ class ModuleSystem(ConfigurableFragmentWrapper, Runtime): # pylint: disable=abs def resource_url(self, resource): raise NotImplementedError("edX Platform doesn't currently implement XBlock resource urls") - def publish(self, block, event): + def publish(self, block, event_type, event): pass diff --git a/docs/en_us/developers/source/xblocks.rst b/docs/en_us/developers/source/xblocks.rst index 7e7aee22f3..2a15ea53cb 100644 --- a/docs/en_us/developers/source/xblocks.rst +++ b/docs/en_us/developers/source/xblocks.rst @@ -21,8 +21,13 @@ These are properties and methods available on ``self.runtime`` when a view or ha that the block is being executed in. The same student in two different courses will have two different ids. -* publish(event): Emit events to the surrounding system. Events are dictionaries with - at least the key 'event_type', which identifies the other fields. +* publish(event): Emit events to the surrounding system. Events are dictionaries that can contain arbitrary data. + XBlocks can publish events by calling ``self.runtime.publish(self, event_type, event)``. The ``event_type`` parameter + enables downstream processing of the event since it uniquely identifies the schema. This call will cause the runtime + to save the event data in the application event stream. XBlocks should publish events whenever a significant state + change occurs. Post-hoc analysis of the event stream can yield insight about how the XBlock is used in the context of + the application. Ideally interesting state of the XBlock could be reconstructed at any point in history through + careful analysis of the event stream. TODO: Link to the authoritive list of event types. @@ -51,12 +56,14 @@ should ``publish`` a ``grade`` event whenever the grade changes. The ``grade`` e dictionary of the following form:: { - 'event_type': 'grade', 'value': , 'max_value': , + 'user_id': , } -The grade event represents a grade of ``value/max_value`` for the current user. +The grade event represents a grade of ``value/max_value`` for the current user. The +``user_id`` field is optional, the currently logged in user's ID will be used if it is +omitted. Restrictions ~~~~~~~~~~~~ diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 4954a9bf4b..9e6f246688 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -292,15 +292,8 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours position, wrap_xmodule_display, grade_bucket_type, static_asset_path) - def publish(block, event, custom_user=None): - """A function that allows XModules to publish events. This only supports grade changes right now.""" - if event.get('event_name') != 'grade': - return - - if custom_user: - user_id = custom_user.id - else: - user_id = user.id + def handle_grade_event(block, event_type, event): + user_id = event.get('user_id', user.id) # Construct the key for the module key = KeyValueStore.Key( @@ -333,6 +326,13 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours dog_stats_api.increment("lms.courseware.question_answered", tags=tags) + def publish(block, event_type, event): + """A function that allows XModules to publish events.""" + if event_type == 'grade': + handle_grade_event(block, event_type, event) + else: + track_function(event_type, event) + # Build a list of wrapping functions that will be applied in order # to the Fragment content coming out of the xblocks that are about to be rendered. block_wrappers = [] diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt index 9eda75f8a7..feca3fac52 100644 --- a/requirements/edx/github.txt +++ b/requirements/edx/github.txt @@ -17,7 +17,7 @@ -e git+https://github.com/appliedsec/pygeoip.git@95e69341cebf5a6a9fbf7c4f5439d458898bdc3b#egg=pygeoip # Our libraries: --e git+https://github.com/edx/XBlock.git@6ec7edd6c44c7d2f1583df077cbf3e0f621ae984#egg=XBlock +-e git+https://github.com/edx/XBlock.git@cfe5c37f98febd9a215d23cb206a25711056a142#egg=XBlock -e git+https://github.com/edx/codejail.git@e3d98f9455#egg=codejail -e git+https://github.com/edx/diff-cover.git@v0.2.9#egg=diff_cover -e git+https://github.com/edx/js-test-tool.git@v0.1.5#egg=js_test_tool