Commit 5f9d73ac by Kelketek

Merge pull request #47 from open-craft/kelketek/analytics-refinement

Update and refine analytics events and their documentation.
parents ae0832c4 b1f39a55
language: python language: python
sudo: false
python: python:
- "2.7" - "2.7"
before_install: before_install:
......
...@@ -287,9 +287,7 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -287,9 +287,7 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
# so we have to figure that we're running in Studio for now # so we have to figure that we're running in Studio for now
pass pass
self.runtime.publish(self, 'xblock.drag-and-drop-v2.item.dropped', { self.runtime.publish(self, 'edx.drag_and_drop_v2.item.dropped', {
'user_id': self.scope_ids.user_id,
'component_id': self._get_unique_id(),
'item_id': item['id'], 'item_id': item['id'],
'location': attempt.get('zone'), 'location': attempt.get('zone'),
'input': attempt.get('input'), 'input': attempt.get('input'),
...@@ -444,9 +442,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -444,9 +442,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
except KeyError: except KeyError:
return {'result': 'error', 'message': 'Missing event_type in JSON data'} return {'result': 'error', 'message': 'Missing event_type in JSON data'}
data['user_id'] = self.scope_ids.user_id
data['component_id'] = self._get_unique_id()
self.runtime.publish(self, event_type, data) self.runtime.publish(self, event_type, data)
return {'result': 'success'} return {'result': 'success'}
......
...@@ -12,6 +12,9 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -12,6 +12,9 @@ function DragAndDropBlock(runtime, element, configuration) {
var state = undefined; var state = undefined;
var __vdom = virtualDom.h(); // blank virtual DOM var __vdom = virtualDom.h(); // blank virtual DOM
// Event string size limit.
var MAX_LENGTH = 255;
// Keyboard accessibility // Keyboard accessibility
var ESC = 27; var ESC = 27;
var RET = 13; var RET = 13;
...@@ -57,7 +60,7 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -57,7 +60,7 @@ function DragAndDropBlock(runtime, element, configuration) {
$element.on('click', '.submit-input', submitInput); $element.on('click', '.submit-input', submitInput);
// Indicate that exercise is done loading // Indicate that exercise is done loading
publishEvent({event_type: 'xblock.drag-and-drop-v2.loaded'}); publishEvent({event_type: 'edx.drag_and_drop_v2.loaded'});
}).fail(function() { }).fail(function() {
$root.text(gettext("An error occurred. Unable to load drag and drop exercise.")); $root.text(gettext("An error occurred. Unable to load drag and drop exercise."));
}); });
...@@ -84,6 +87,15 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -84,6 +87,15 @@ function DragAndDropBlock(runtime, element, configuration) {
} }
}; };
var truncateField = function(data, fieldName){
if (data[fieldName].length > MAX_LENGTH) {
data[fieldName] = data[fieldName].substring(0, MAX_LENGTH);
data['truncated'] = true;
} else {
data['truncated'] = false;
}
};
var focusModalButton = function() { var focusModalButton = function() {
$root.find('.keyboard-help-dialog .modal-dismiss-button ').focus(); $root.find('.keyboard-help-dialog .modal-dismiss-button ').focus();
}; };
...@@ -160,28 +172,26 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -160,28 +172,26 @@ function DragAndDropBlock(runtime, element, configuration) {
* Update the DOM to reflect 'state'. * Update the DOM to reflect 'state'.
*/ */
var applyState = function() { var applyState = function() {
// Is there a change to the feedback popup? // Has the feedback popup been closed?
if (state.feedback !== previousFeedback) { if (state.closing) {
if (state.feedback) { var data = {
if (previousFeedback) { event_type: 'edx.drag_and_drop_v2.feedback.closed',
publishEvent({ content: previousFeedback || state.feedback,
event_type: 'xblock.drag-and-drop-v2.feedback.closed', manually: state.manually_closed,
content: previousFeedback, };
manually: false, truncateField(data, 'content');
}); publishEvent(data);
} delete state.feedback;
publishEvent({ delete state.closing;
event_type: 'xblock.drag-and-drop-v2.feedback.opened', }
content: state.feedback, // Has feedback been set?
}); if (state.feedback) {
} else { var data = {
publishEvent({ event_type: 'edx.drag_and_drop_v2.feedback.opened',
event_type: 'xblock.drag-and-drop-v2.feedback.closed', content: state.feedback,
content: state.feedback, };
manually: true, truncateField(data, 'content');
}); publishEvent(data);
}
previousFeedback = state.feedback;
} }
updateDOM(); updateDOM();
...@@ -336,7 +346,7 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -336,7 +346,7 @@ function DragAndDropBlock(runtime, element, configuration) {
var $item = $(this); var $item = $(this);
grabItem($item); grabItem($item);
publishEvent({ publishEvent({
event_type: 'xblock.drag-and-drop-v2.item.picked-up', event_type: 'edx.drag_and_drop_v2.item.picked_up',
item_id: $item.data('value'), item_id: $item.data('value'),
}); });
}, },
...@@ -471,7 +481,14 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -471,7 +481,14 @@ function DragAndDropBlock(runtime, element, configuration) {
return; return;
} }
delete state.feedback; state.closing = true;
previousFeedback = state.feedback;
if (target.is(close_button)) {
state.manually_closed = true;
} else {
state.manually_closed = false;
}
applyState(); applyState();
}; };
......
...@@ -29,7 +29,8 @@ setup( ...@@ -29,7 +29,8 @@ setup(
install_requires=[ install_requires=[
'XBlock', 'XBlock',
'xblock-utils', 'xblock-utils',
'ddt' 'ddt',
'mock',
], ],
entry_points={ entry_points={
'xblock.v1': 'drag-and-drop-v2 = drag_and_drop_v2:DragAndDropBlock', 'xblock.v1': 'drag-and-drop-v2 = drag_and_drop_v2:DragAndDropBlock',
......
# Imports ########################################################### # Imports ###########################################################
from ddt import ddt, data from ddt import ddt, data, unpack
from mock import Mock, patch
from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver import ActionChains from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from workbench.runtime import WorkbenchRuntime
from xblockutils.resources import ResourceLoader from xblockutils.resources import ResourceLoader
from drag_and_drop_v2.default_data import ( from drag_and_drop_v2.default_data import (
...@@ -310,9 +312,9 @@ class InteractionTestBase(object): ...@@ -310,9 +312,9 @@ class InteractionTestBase(object):
self.assertFalse(dialog_modal.is_displayed()) self.assertFalse(dialog_modal.is_displayed())
class BasicInteractionTest(InteractionTestBase): class DefaultDataTestMixin(object):
""" """
Testing interactions with Drag and Drop XBlock against default data. If default data changes this will break. Provides a test scenario with default options.
""" """
PAGE_TITLE = 'Drag and Drop v2' PAGE_TITLE = 'Drag and Drop v2'
PAGE_ID = 'drag_and_drop_v2' PAGE_ID = 'drag_and_drop_v2'
...@@ -340,6 +342,11 @@ class BasicInteractionTest(InteractionTestBase): ...@@ -340,6 +342,11 @@ class BasicInteractionTest(InteractionTestBase):
def _get_scenario_xml(self): # pylint: disable=no-self-use def _get_scenario_xml(self): # pylint: disable=no-self-use
return "<vertical_demo><drag-and-drop-v2/></vertical_demo>" return "<vertical_demo><drag-and-drop-v2/></vertical_demo>"
class BasicInteractionTest(DefaultDataTestMixin, InteractionTestBase):
"""
Testing interactions with Drag and Drop XBlock against default data. If default data changes this will break.
"""
def test_item_positive_feedback_on_good_move(self): def test_item_positive_feedback_on_good_move(self):
self.parameterized_item_positive_feedback_on_good_move(self.items_map) self.parameterized_item_positive_feedback_on_good_move(self.items_map)
...@@ -360,6 +367,74 @@ class BasicInteractionTest(InteractionTestBase): ...@@ -360,6 +367,74 @@ class BasicInteractionTest(InteractionTestBase):
@ddt @ddt
class EventsFiredTest(DefaultDataTestMixin, InteractionTestBase, BaseIntegrationTest):
"""
Tests that the analytics events are fired and in the proper order.
"""
# These events must be fired in this order.
scenarios = (
{
'name': 'edx.drag_and_drop_v2.loaded',
'data': {},
},
{
'name': 'edx.drag_and_drop_v2.item.picked_up',
'data': {'item_id': 0},
},
{
'name': 'grade',
'data': {'max_value': 1, 'value': (1.0 / 3)},
},
{
'name': 'edx.drag_and_drop_v2.item.dropped',
'data': {
'input': None,
'is_correct': True,
'is_correct_location': True,
'item_id': 0,
'location': u'The Top Zone',
},
},
{
'name': 'edx.drag_and_drop_v2.feedback.opened',
'data': {
'content': u'Correct! This one belongs to The Top Zone.',
'truncated': False,
},
},
{
'name': 'edx.drag_and_drop_v2.feedback.closed',
'data': {
'manually': False,
'content': u'Correct! This one belongs to The Top Zone.',
'truncated': False,
},
},
)
def setUp(self):
mock = Mock()
context = patch.object(WorkbenchRuntime, 'publish', mock)
context.start()
self.addCleanup(context.stop)
self.publish = mock
super(EventsFiredTest, self).setUp()
def _get_scenario_xml(self): # pylint: disable=no-self-use
return "<vertical_demo><drag-and-drop-v2/></vertical_demo>"
@data(*enumerate(scenarios)) # pylint: disable=star-args
@unpack
def test_event(self, index, event):
self.parameterized_item_positive_feedback_on_good_move(self.items_map)
dummy, name, published_data = self.publish.call_args_list[index][0]
self.assertEqual(name, event['name'])
self.assertEqual(
published_data, event['data']
)
@ddt
class KeyboardInteractionTest(BasicInteractionTest, BaseIntegrationTest): class KeyboardInteractionTest(BasicInteractionTest, BaseIntegrationTest):
@data(Keys.RETURN, Keys.SPACE, Keys.CONTROL+'m', Keys.COMMAND+'m') @data(Keys.RETURN, Keys.SPACE, Keys.CONTROL+'m', Keys.COMMAND+'m')
def test_item_positive_feedback_on_good_move_with_keyboard(self, action_key): def test_item_positive_feedback_on_good_move_with_keyboard(self, action_key):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment