SOL-217 Adding basic analytics events logging for courseware search and index
This commit is contained in:
@@ -571,15 +571,13 @@ class TestCourseReIndex(CourseTestCase):
|
||||
self.assertEqual(response['results'], [])
|
||||
|
||||
# Start manual reindex
|
||||
errors = CoursewareSearchIndexer.do_course_reindex(modulestore(), self.course.id)
|
||||
self.assertEqual(errors, None)
|
||||
CoursewareSearchIndexer.do_course_reindex(modulestore(), self.course.id)
|
||||
|
||||
self.html.display_name = "My expanded HTML"
|
||||
modulestore().update_item(self.html, ModuleStoreEnum.UserID.test)
|
||||
|
||||
# Start manual reindex
|
||||
errors = CoursewareSearchIndexer.do_course_reindex(modulestore(), self.course.id)
|
||||
self.assertEqual(errors, None)
|
||||
CoursewareSearchIndexer.do_course_reindex(modulestore(), self.course.id)
|
||||
|
||||
# Check results indexed now
|
||||
response = perform_search(
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from search.search_engine_base import SearchEngine
|
||||
@@ -10,6 +11,7 @@ from search.search_engine_base import SearchEngine
|
||||
from . import ModuleStoreEnum
|
||||
from .exceptions import ItemNotFoundError
|
||||
|
||||
|
||||
# Use default index and document names for now
|
||||
INDEX_NAME = "courseware_index"
|
||||
DOCUMENT_TYPE = "courseware_content"
|
||||
@@ -36,6 +38,7 @@ class CoursewareSearchIndexer(object):
|
||||
Add to courseware search index from given location and its children
|
||||
"""
|
||||
error_list = []
|
||||
indexed_count = 0
|
||||
# TODO - inline for now, need to move this out to a celery task
|
||||
searcher = SearchEngine.get_search_engine(INDEX_NAME)
|
||||
if not searcher:
|
||||
@@ -115,6 +118,7 @@ class CoursewareSearchIndexer(object):
|
||||
remove_index_item_location(location)
|
||||
else:
|
||||
index_item_location(location, None)
|
||||
indexed_count += 1
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
# broad exception so that index operation does not prevent the rest of the application from working
|
||||
log.exception(
|
||||
@@ -127,9 +131,72 @@ class CoursewareSearchIndexer(object):
|
||||
if raise_on_error and error_list:
|
||||
raise SearchIndexingError(_('Error(s) present during indexing'), error_list)
|
||||
|
||||
return indexed_count
|
||||
|
||||
@classmethod
|
||||
def do_publish_index(cls, modulestore, location, delete=False, raise_on_error=False):
|
||||
"""
|
||||
Add to courseware search index published section and children
|
||||
"""
|
||||
indexed_count = cls.add_to_search_index(modulestore, location, delete, raise_on_error)
|
||||
CoursewareSearchIndexer._track_index_request('edx.course.index.published', indexed_count, str(location))
|
||||
return indexed_count
|
||||
|
||||
@classmethod
|
||||
def do_course_reindex(cls, modulestore, course_key):
|
||||
"""
|
||||
(Re)index all content within the given course
|
||||
"""
|
||||
return cls.add_to_search_index(modulestore, course_key, delete=False, raise_on_error=True)
|
||||
indexed_count = cls.add_to_search_index(modulestore, course_key, delete=False, raise_on_error=True)
|
||||
CoursewareSearchIndexer._track_index_request('edx.course.index.reindexed', indexed_count)
|
||||
return indexed_count
|
||||
|
||||
@staticmethod
|
||||
def _track_index_request(event_name, indexed_count, location=None):
|
||||
"""Track content index requests.
|
||||
|
||||
Arguments:
|
||||
location (str): The ID of content to be indexed.
|
||||
event_name (str): Name of the event to be logged.
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
|
||||
from eventtracking import tracker as track
|
||||
tracker = track.get_tracker()
|
||||
tracking_context = tracker.resolve_context() # pylint: disable=no-member
|
||||
data = {
|
||||
"indexed_count": indexed_count,
|
||||
'category': 'courseware_index',
|
||||
}
|
||||
|
||||
if location:
|
||||
data['location_id'] = location
|
||||
|
||||
tracker.emit(
|
||||
event_name,
|
||||
data
|
||||
)
|
||||
|
||||
try:
|
||||
if settings.FEATURES.get('SEGMENT_IO_LMS') and hasattr(settings, 'SEGMENT_IO_LMS_KEY'):
|
||||
#Google Analytics - log index content request
|
||||
import analytics
|
||||
|
||||
analytics.track(
|
||||
event_name,
|
||||
data,
|
||||
context={
|
||||
'Google Analytics': {
|
||||
'clientId': tracking_context.get('client_id')
|
||||
}
|
||||
}
|
||||
)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# Capturing all exceptions thrown while tracking analytics events. We do not want
|
||||
# an operation to fail because of an analytics event, so we will capture these
|
||||
# errors in the logs.
|
||||
log.exception(
|
||||
u'Unable to emit {0} event for content indexing.'.format(event_name)
|
||||
)
|
||||
|
||||
@@ -732,7 +732,7 @@ class DraftModuleStore(MongoModuleStore):
|
||||
self.signal_handler.send("course_published", course_key=course_key)
|
||||
|
||||
# Now it's been published, add the object to the courseware search index so that it appears in search results
|
||||
CoursewareSearchIndexer.add_to_search_index(self, location)
|
||||
CoursewareSearchIndexer.do_publish_index(self, location)
|
||||
|
||||
return self.get_item(as_published(location))
|
||||
|
||||
|
||||
@@ -357,7 +357,7 @@ class DraftVersioningModuleStore(SplitMongoModuleStore, ModuleStoreDraftAndPubli
|
||||
)
|
||||
|
||||
# Now it's been published, add the object to the courseware search index so that it appears in search results
|
||||
CoursewareSearchIndexer.add_to_search_index(self, location)
|
||||
CoursewareSearchIndexer.do_publish_index(self, location)
|
||||
|
||||
return self.get_item(location.for_branch(ModuleStoreEnum.BranchName.published), **kwargs)
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ from lazy import lazy
|
||||
from mock import Mock
|
||||
from operator import attrgetter
|
||||
from path import path
|
||||
from eventtracking import tracker
|
||||
from eventtracking.django import DjangoTracker
|
||||
|
||||
from xblock.field_data import DictFieldData
|
||||
from xblock.fields import ScopeIds, Scope, Reference, ReferenceList, ReferenceValueDict
|
||||
@@ -49,7 +51,6 @@ open_ended_grading_interface = {
|
||||
'grading_controller': 'grading_controller',
|
||||
}
|
||||
|
||||
|
||||
class TestModuleSystem(ModuleSystem): # pylint: disable=abstract-method
|
||||
"""
|
||||
ModuleSystem for testing
|
||||
@@ -59,6 +60,8 @@ class TestModuleSystem(ModuleSystem): # pylint: disable=abstract-method
|
||||
kwargs.setdefault('id_reader', id_manager)
|
||||
kwargs.setdefault('id_generator', id_manager)
|
||||
kwargs.setdefault('services', {}).setdefault('field-data', DictFieldData({}))
|
||||
self.tracker = DjangoTracker()
|
||||
tracker.register_tracker(self.tracker)
|
||||
super(TestModuleSystem, self).__init__(**kwargs)
|
||||
|
||||
def handler_url(self, block, handler, suffix='', query='', thirdparty=False):
|
||||
|
||||
@@ -62,7 +62,10 @@ define([
|
||||
},
|
||||
error: function (self, xhr) {
|
||||
self.trigger('error');
|
||||
}
|
||||
},
|
||||
add: true,
|
||||
reset: false,
|
||||
remove: false
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ define([
|
||||
'jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'gettext'
|
||||
], function ($, _, Backbone, gettext) {
|
||||
'gettext',
|
||||
'logger'
|
||||
], function ($, _, Backbone, gettext, Logger) {
|
||||
'use strict';
|
||||
|
||||
return Backbone.View.extend({
|
||||
@@ -17,6 +18,10 @@ define([
|
||||
'aria-label': 'search result'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click .search-results-item a': 'logSearchItem',
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
var template_name = (this.model.attributes.content_type === "Sequence") ? '#search_item_seq-tpl' : '#search_item-tpl';
|
||||
this.tpl = _.template($(template_name).html());
|
||||
@@ -25,9 +30,34 @@ define([
|
||||
render: function () {
|
||||
this.$el.html(this.tpl(this.model.attributes));
|
||||
return this;
|
||||
},
|
||||
|
||||
logSearchItem: function(event) {
|
||||
event.preventDefault();
|
||||
var target = this.model.id;
|
||||
var link = $(event.target).attr('href');
|
||||
var collection = this.model.collection;
|
||||
var page = collection.page;
|
||||
var pageSize = collection.pageSize;
|
||||
var searchTerm = collection.searchTerm;
|
||||
var index = collection.indexOf(this.model);
|
||||
Logger.log("edx.course.search.result_selected",
|
||||
{
|
||||
"search_term": searchTerm,
|
||||
"result_position": (page * pageSize + index),
|
||||
"result_link": target
|
||||
});
|
||||
window.analytics.track('"edx.course.search.result_selected"', {
|
||||
category: 'courseware_search',
|
||||
search_term: searchTerm,
|
||||
result_position: (page * pageSize + index),
|
||||
result_link: target
|
||||
});
|
||||
window.location.href = link;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})(define || RequireJS.define);
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ define([
|
||||
var item = new SearchItemView({ model: result });
|
||||
return item.render().el;
|
||||
});
|
||||
this.$el.find('.search-results').append(items);
|
||||
this.$el.find('.search-results').html(items);
|
||||
},
|
||||
|
||||
totalCountMsg: function () {
|
||||
|
||||
@@ -353,6 +353,7 @@ define([
|
||||
expect(this.listView.$el).toContainHtml('Search Results');
|
||||
expect(this.listView.$el).toContainHtml('this is a short excerpt');
|
||||
|
||||
searchResults[1] = searchResults[0]
|
||||
this.collection.set(searchResults);
|
||||
this.collection.totalCount = 2;
|
||||
this.listView.renderNext();
|
||||
|
||||
Reference in New Issue
Block a user