From 3973317aa90718f6c2f407bbf9cdb7b58135609c Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Fri, 9 Jan 2015 11:01:40 -0800 Subject: [PATCH] Report the original LibraryUsageLocator in LMS analytics --- .../xmodule/xmodule/library_content_module.py | 15 +++++--- common/lib/xmodule/xmodule/library_tools.py | 38 +++++++++++++++++++ lms/djangoapps/lms_xblock/runtime.py | 2 + 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/library_content_module.py b/common/lib/xmodule/xmodule/library_content_module.py index 78a8e0eecf..e3384efefc 100644 --- a/common/lib/xmodule/xmodule/library_content_module.py +++ b/common/lib/xmodule/xmodule/library_content_module.py @@ -220,8 +220,11 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule): if hasattr(self, "_selected_set"): # Already done: return self._selected_set # pylint: disable=access-member-before-definition + + lib_tools = self.runtime.service(self, 'library_tools') + format_block_keys = lambda block_keys: lib_tools.create_block_analytics_summary(self.location.course_key, block_keys) + # Determine which of our children we will show: - jsonify_block_keys = lambda keys: [unicode(self.location.course_key.make_usage_key(*key)) for key in keys] selected = set(tuple(k) for k in self.selected) # set of (block_type, block_id) tuples valid_block_keys = set([(c.block_type, c.block_id) for c in self.children]) # pylint: disable=no-member # Remove any selected blocks that are no longer valid: @@ -231,8 +234,9 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule): # Publish an event for analytics purposes: self.runtime.publish(self, "edx.librarycontentblock.content.removed", { "location": unicode(self.location), - "blocks": jsonify_block_keys(invalid_block_keys), + "removed": format_block_keys(invalid_block_keys), "reason": "invalid", # Deleted from library or library being used has changed + "result": format_block_keys(selected), }) # If max_count has been decreased, we may have to drop some previously selected blocks: overlimit_block_keys = set() @@ -242,8 +246,9 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule): # Publish an event for analytics purposes: self.runtime.publish(self, "edx.librarycontentblock.content.removed", { "location": unicode(self.location), - "blocks": jsonify_block_keys(overlimit_block_keys), + "removed": format_block_keys(overlimit_block_keys), "reason": "overlimit", + "result": format_block_keys(selected), }) # Do we have enough blocks now? num_to_add = self.max_count - len(selected) @@ -262,8 +267,8 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule): # Publish an event for analytics purposes: self.runtime.publish(self, "edx.librarycontentblock.content.assigned", { "location": unicode(self.location), - "added": jsonify_block_keys(added_block_keys), - "result": jsonify_block_keys(selected), + "added": format_block_keys(added_block_keys), + "result": format_block_keys(selected), }) # Save our selections to the user state, to ensure consistency: self.selected = list(selected) # TODO: this doesn't save from the LMS "Progress" page. diff --git a/common/lib/xmodule/xmodule/library_tools.py b/common/lib/xmodule/xmodule/library_tools.py index 8f3c6d0cee..aebe2aef98 100644 --- a/common/lib/xmodule/xmodule/library_tools.py +++ b/common/lib/xmodule/xmodule/library_tools.py @@ -44,6 +44,44 @@ class LibraryToolsService(object): return library.location.library_key.version_guid return None + def create_block_analytics_summary(self, course_key, block_keys): + """ + Given a CourseKey and a list of (block_type, block_id) pairs, + prepare the JSON-ready metadata needed for analytics logging. + + This is [ + {"usage_key": x, "original_usage_key": y, "original_usage_version": z, "descendants": [...]} + ] + where the main list contains all top-level blocks, and descendants contains a *flat* list of all + descendants of the top level blocks, if any. + """ + def summarize_block(usage_key): + """ Basic information about the given block """ + orig_key, orig_version = self.store.get_block_original_usage(usage_key) + return { + "usage_key": unicode(usage_key), + "original_usage_key": unicode(orig_key) if orig_key else None, + "original_usage_version": unicode(orig_version) if orig_version else None, + } + + result_json = [] + for block_key in block_keys: + key = course_key.make_usage_key(*block_key) + info = summarize_block(key) + info['descendants'] = [] + try: + block = self.store.get_item(key, depth=None) # Load the item and all descendants + children = list(getattr(block, "children", [])) + while children: + child_key = children.pop() + child = self.store.get_item(child_key) + info['descendants'].append(summarize_block(child_key)) + children.extend(getattr(child, "children", [])) + except ItemNotFoundError: + pass # The block has been deleted + result_json.append(info) + return result_json + def _filter_child(self, usage_key, capa_type): """ Filters children by CAPA problem type, if configured diff --git a/lms/djangoapps/lms_xblock/runtime.py b/lms/djangoapps/lms_xblock/runtime.py index 49d0abbf38..7b5c3fe7b9 100644 --- a/lms/djangoapps/lms_xblock/runtime.py +++ b/lms/djangoapps/lms_xblock/runtime.py @@ -10,6 +10,7 @@ from django.conf import settings from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig from openedx.core.djangoapps.user_api.api import course_tag as user_course_tag_api from xmodule.modulestore.django import modulestore +from xmodule.library_tools import LibraryToolsService from xmodule.x_module import ModuleSystem from xmodule.partitions.partitions_service import PartitionService @@ -199,6 +200,7 @@ class LmsModuleSystem(LmsHandlerUrls, ModuleSystem): # pylint: disable=abstract course_id=kwargs.get('course_id'), track_function=kwargs.get('track_function', None), ) + services['library_tools'] = LibraryToolsService(modulestore()) services['fs'] = xblock.reference.plugins.FSService() self.request_token = kwargs.pop('request_token', None) super(LmsModuleSystem, self).__init__(**kwargs)