Commit 1e9907a8 by Muhammad Ammar Committed by muzaffaryousaf

Bookmark Events

TNL-1944
parent 3cbbb8f3
...@@ -44,6 +44,17 @@ class BookmarksTestMixin(EventsTestMixin, UniqueCourseTest): ...@@ -44,6 +44,17 @@ class BookmarksTestMixin(EventsTestMixin, UniqueCourseTest):
] ]
self.course_fixture.add_children(*xblocks).install() self.course_fixture.add_children(*xblocks).install()
def verify_event_data(self, event_type, event_data):
"""
Verify emitted event data.
Arguments:
event_type: expected event type
event_data: expected event data
"""
actual_events = self.wait_for_events(event_filter={'event_type': event_type}, number_of_matches=1)
self.assert_events_match(event_data, actual_events)
class BookmarksTest(BookmarksTestMixin): class BookmarksTest(BookmarksTestMixin):
""" """
...@@ -213,13 +224,6 @@ class BookmarksTest(BookmarksTestMixin): ...@@ -213,13 +224,6 @@ class BookmarksTest(BookmarksTestMixin):
When I click on bookmarked link When I click on bookmarked link
Then I can navigate to correct bookmarked unit Then I can navigate to correct bookmarked unit
""" """
# NOTE: We are checking the order of bookmarked units at API
# We are unable to check the order here because we are bookmarking
# the units by sending POSTs to API, And the time(created) between
# the bookmarked units is in milliseconds. These milliseconds are
# discarded by the current version of MySQL we are using due to the
# lack of support. Due to which order of bookmarked units will be
# incorrect.
self._test_setup() self._test_setup()
self._bookmark_units(2) self._bookmark_units(2)
...@@ -230,9 +234,10 @@ class BookmarksTest(BookmarksTestMixin): ...@@ -230,9 +234,10 @@ class BookmarksTest(BookmarksTestMixin):
bookmarked_breadcrumbs = self.bookmarks_page.breadcrumbs() bookmarked_breadcrumbs = self.bookmarks_page.breadcrumbs()
# Verify bookmarked breadcrumbs # Verify bookmarked breadcrumbs and bookmarks order (most recently bookmarked unit should come first)
breadcrumbs = self._breadcrumb(2) breadcrumbs = self._breadcrumb(2)
self.assertItemsEqual(bookmarked_breadcrumbs, breadcrumbs) breadcrumbs.reverse()
self.assertEqual(bookmarked_breadcrumbs, breadcrumbs)
# get usage ids for units # get usage ids for units
xblocks = self.course_fixture.get_nested_xblocks(category="vertical") xblocks = self.course_fixture.get_nested_xblocks(category="vertical")
...@@ -289,3 +294,32 @@ class BookmarksTest(BookmarksTestMixin): ...@@ -289,3 +294,32 @@ class BookmarksTest(BookmarksTestMixin):
self.bookmarks_page.click_bookmarks_button() self.bookmarks_page.click_bookmarks_button()
self.assertTrue(self.bookmarks_page.results_present()) self.assertTrue(self.bookmarks_page.results_present())
self.assertEqual(self.bookmarks_page.count(), 11) self.assertEqual(self.bookmarks_page.count(), 11)
def test_bookmarked_unit_accessed_event(self):
"""
Scenario: Bookmark events are emitted with correct data when we access/visit a bookmarked unit.
Given that I am a registered user
And I visit my courseware page
And I have bookmarked a unit
When I click on bookmarked unit
Then `edx.course.bookmark.accessed` event is emitted
"""
self._test_setup(num_chapters=1)
self.reset_event_tracking()
# create expected event data
xblocks = self.course_fixture.get_nested_xblocks(category="vertical")
event_data = [
{
'event': {
'bookmark_id': '{},{}'.format(self.USERNAME, xblocks[0].locator),
'component_type': xblocks[0].category,
'component_usage_id': xblocks[0].locator,
}
}
]
self._bookmark_unit(0)
self.bookmarks_page.click_bookmarks_button()
self.bookmarks_page.click_bookmarked_block(0)
self.verify_event_data('edx.bookmark.accessed', event_data)
""" """
Bookmarks Python API. Bookmarks Python API.
""" """
from eventtracking import tracker
from . import DEFAULT_FIELDS, OPTIONAL_FIELDS from . import DEFAULT_FIELDS, OPTIONAL_FIELDS
from .models import Bookmark from .models import Bookmark
from .serializers import BookmarkSerializer from .serializers import BookmarkSerializer
...@@ -68,10 +68,12 @@ def create_bookmark(user, usage_key): ...@@ -68,10 +68,12 @@ def create_bookmark(user, usage_key):
Raises: Raises:
ItemNotFoundError: If no block exists for the usage_key. ItemNotFoundError: If no block exists for the usage_key.
""" """
bookmark = Bookmark.create({ bookmark, created = Bookmark.create({
'user': user, 'user': user,
'usage_key': usage_key 'usage_key': usage_key
}) })
if created:
_track_event('edx.bookmark.added', bookmark)
return BookmarkSerializer(bookmark, context={'fields': DEFAULT_FIELDS + OPTIONAL_FIELDS}).data return BookmarkSerializer(bookmark, context={'fields': DEFAULT_FIELDS + OPTIONAL_FIELDS}).data
...@@ -91,3 +93,23 @@ def delete_bookmark(user, usage_key): ...@@ -91,3 +93,23 @@ def delete_bookmark(user, usage_key):
""" """
bookmark = Bookmark.objects.get(user=user, usage_key=usage_key) bookmark = Bookmark.objects.get(user=user, usage_key=usage_key)
bookmark.delete() bookmark.delete()
_track_event('edx.bookmark.removed', bookmark)
def _track_event(event_name, bookmark):
"""
Emit events for a bookmark.
Arguments:
event_name: name of event to track
bookmark: Bookmark object
"""
tracker.emit(
event_name,
{
'course_id': unicode(bookmark.course_key),
'bookmark_id': bookmark.resource_id,
'component_type': bookmark.usage_key.block_type,
'component_usage_id': unicode(bookmark.usage_key),
}
)
...@@ -46,8 +46,7 @@ class Bookmark(TimeStampedModel): ...@@ -46,8 +46,7 @@ class Bookmark(TimeStampedModel):
bookmark_data['path'] = cls.get_path(block) bookmark_data['path'] = cls.get_path(block)
user = bookmark_data.pop('user') user = bookmark_data.pop('user')
bookmark, __ = cls.objects.get_or_create(usage_key=usage_key, user=user, defaults=bookmark_data) return cls.objects.get_or_create(usage_key=usage_key, user=user, defaults=bookmark_data)
return bookmark
@staticmethod @staticmethod
def get_path(block): def get_path(block):
...@@ -69,3 +68,10 @@ class Bookmark(TimeStampedModel): ...@@ -69,3 +68,10 @@ class Bookmark(TimeStampedModel):
parents_data.reverse() parents_data.reverse()
return parents_data return parents_data
@property
def resource_id(self):
"""
Return the resource id: {username,usage_id}.
"""
return "{0},{1}".format(self.user.username, self.usage_key) # pylint: disable=no-member
...@@ -11,7 +11,7 @@ class BookmarkSerializer(serializers.ModelSerializer): ...@@ -11,7 +11,7 @@ class BookmarkSerializer(serializers.ModelSerializer):
""" """
Serializer for the Bookmark model. Serializer for the Bookmark model.
""" """
id = serializers.SerializerMethodField('resource_id') # pylint: disable=invalid-name id = serializers.Field(source='resource_id') # pylint: disable=invalid-name
course_id = serializers.Field(source='course_key') course_id = serializers.Field(source='course_key')
usage_id = serializers.Field(source='usage_key') usage_id = serializers.Field(source='usage_key')
block_type = serializers.Field(source='usage_key.block_type') block_type = serializers.Field(source='usage_key.block_type')
...@@ -44,9 +44,3 @@ class BookmarkSerializer(serializers.ModelSerializer): ...@@ -44,9 +44,3 @@ class BookmarkSerializer(serializers.ModelSerializer):
'path', 'path',
'created', 'created',
) )
def resource_id(self, bookmark):
"""
Return the REST resource id: {username,usage_id}.
"""
return "{0},{1}".format(bookmark.user.username, bookmark.usage_key)
...@@ -8,6 +8,8 @@ from opaque_keys.edx.keys import UsageKey ...@@ -8,6 +8,8 @@ from opaque_keys.edx.keys import UsageKey
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from util.testing import EventTestMixin
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
...@@ -17,7 +19,23 @@ from .. import api, DEFAULT_FIELDS, OPTIONAL_FIELDS ...@@ -17,7 +19,23 @@ from .. import api, DEFAULT_FIELDS, OPTIONAL_FIELDS
from ..models import Bookmark from ..models import Bookmark
class BookmarksAPITests(ModuleStoreTestCase): class BookmarkApiEventTestMixin(EventTestMixin):
""" Mixin for verifying that bookmark api events were emitted during a test. """
def setUp(self): # pylint: disable=arguments-differ
super(BookmarkApiEventTestMixin, self).setUp('lms.djangoapps.bookmarks.api.tracker')
def assert_bookmark_event_emitted(self, event_name, course_id, bookmark_id, usage_key):
""" Assert that an event has been emitted. """
self.assert_event_emitted(
event_name,
course_id=course_id,
bookmark_id=bookmark_id,
component_type=usage_key.category,
component_usage_id=unicode(usage_key),
)
class BookmarksAPITests(BookmarkApiEventTestMixin, ModuleStoreTestCase):
""" """
These tests cover the parts of the API methods. These tests cover the parts of the API methods.
""" """
...@@ -68,6 +86,8 @@ class BookmarksAPITests(ModuleStoreTestCase): ...@@ -68,6 +86,8 @@ class BookmarksAPITests(ModuleStoreTestCase):
) )
self.all_fields = DEFAULT_FIELDS + OPTIONAL_FIELDS self.all_fields = DEFAULT_FIELDS + OPTIONAL_FIELDS
self.reset_tracker()
def assert_bookmark_response(self, response_data, bookmark, optional_fields=False): def assert_bookmark_response(self, response_data, bookmark, optional_fields=False):
""" """
Determines if the given response data (dict) matches the given bookmark. Determines if the given response data (dict) matches the given bookmark.
...@@ -137,7 +157,14 @@ class BookmarksAPITests(ModuleStoreTestCase): ...@@ -137,7 +157,14 @@ class BookmarksAPITests(ModuleStoreTestCase):
""" """
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 1) self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 1)
api.create_bookmark(user=self.user, usage_key=self.vertical_1.location) bookmark_data = api.create_bookmark(user=self.user, usage_key=self.vertical_1.location)
self.assert_bookmark_event_emitted(
'edx.bookmark.added',
self.course_id,
bookmark_data['id'],
self.vertical_1.location
)
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 2) self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 2)
...@@ -148,12 +175,23 @@ class BookmarksAPITests(ModuleStoreTestCase): ...@@ -148,12 +175,23 @@ class BookmarksAPITests(ModuleStoreTestCase):
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 1) self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 1)
bookmark_data = api.create_bookmark(user=self.user, usage_key=self.vertical_1.location) bookmark_data = api.create_bookmark(user=self.user, usage_key=self.vertical_1.location)
self.assert_bookmark_event_emitted(
'edx.bookmark.added',
self.course_id,
bookmark_data['id'],
self.vertical_1.location
)
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 2) self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 2)
self.reset_tracker()
bookmark_data_2 = api.create_bookmark(user=self.user, usage_key=self.vertical_1.location) bookmark_data_2 = api.create_bookmark(user=self.user, usage_key=self.vertical_1.location)
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 2) self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 2)
self.assertEqual(bookmark_data, bookmark_data_2) self.assertEqual(bookmark_data, bookmark_data_2)
self.assert_no_events_were_emitted()
def test_create_bookmark_raises_error(self): def test_create_bookmark_raises_error(self):
""" """
Verifies that create_bookmark raises error as expected. Verifies that create_bookmark raises error as expected.
...@@ -161,6 +199,8 @@ class BookmarksAPITests(ModuleStoreTestCase): ...@@ -161,6 +199,8 @@ class BookmarksAPITests(ModuleStoreTestCase):
with self.assertRaises(ItemNotFoundError): with self.assertRaises(ItemNotFoundError):
api.create_bookmark(user=self.user, usage_key=UsageKey.from_string('i4x://brb/100/html/340ef1771a0940')) api.create_bookmark(user=self.user, usage_key=UsageKey.from_string('i4x://brb/100/html/340ef1771a0940'))
self.assert_no_events_were_emitted()
def test_delete_bookmark(self): def test_delete_bookmark(self):
""" """
Verifies that delete_bookmark removes bookmark as expected. Verifies that delete_bookmark removes bookmark as expected.
...@@ -169,6 +209,13 @@ class BookmarksAPITests(ModuleStoreTestCase): ...@@ -169,6 +209,13 @@ class BookmarksAPITests(ModuleStoreTestCase):
api.delete_bookmark(user=self.user, usage_key=self.vertical.location) api.delete_bookmark(user=self.user, usage_key=self.vertical.location)
self.assert_bookmark_event_emitted(
'edx.bookmark.removed',
self.course_id,
self.bookmark.resource_id,
self.vertical.location
)
bookmarks_data = api.get_bookmarks(user=self.user) bookmarks_data = api.get_bookmarks(user=self.user)
self.assertEqual(len(bookmarks_data), 1) self.assertEqual(len(bookmarks_data), 1)
self.assertNotEqual(unicode(self.vertical.location), bookmarks_data[0]['usage_id']) self.assertNotEqual(unicode(self.vertical.location), bookmarks_data[0]['usage_id'])
...@@ -179,3 +226,5 @@ class BookmarksAPITests(ModuleStoreTestCase): ...@@ -179,3 +226,5 @@ class BookmarksAPITests(ModuleStoreTestCase):
""" """
with self.assertRaises(ObjectDoesNotExist): with self.assertRaises(ObjectDoesNotExist):
api.delete_bookmark(user=self.other_user, usage_key=self.vertical.location) api.delete_bookmark(user=self.other_user, usage_key=self.vertical.location)
self.assert_no_events_were_emitted()
...@@ -66,7 +66,7 @@ class BookmarkModelTest(ModuleStoreTestCase): ...@@ -66,7 +66,7 @@ class BookmarkModelTest(ModuleStoreTestCase):
Tests creation of bookmark. Tests creation of bookmark.
""" """
bookmark_data = self.get_bookmark_data(self.vertical) bookmark_data = self.get_bookmark_data(self.vertical)
bookmark_object = Bookmark.create(bookmark_data) bookmark_object, __ = Bookmark.create(bookmark_data)
self.assert_valid_bookmark(bookmark_object, bookmark_data) self.assert_valid_bookmark(bookmark_object, bookmark_data)
def test_get_path(self): def test_get_path(self):
......
...@@ -6,10 +6,12 @@ import ddt ...@@ -6,10 +6,12 @@ import ddt
import json import json
import urllib import urllib
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from mock import patch
from rest_framework.test import APIClient from rest_framework.test import APIClient
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
...@@ -148,12 +150,20 @@ class BookmarksListViewTests(BookmarksViewTestsMixin): ...@@ -148,12 +150,20 @@ class BookmarksListViewTests(BookmarksViewTestsMixin):
GET /api/bookmarks/v1/bookmarks/?course_id={course_id1} GET /api/bookmarks/v1/bookmarks/?course_id={course_id1}
POST /api/bookmarks/v1/bookmarks POST /api/bookmarks/v1/bookmarks
""" """
def assert_bookmark_listed_event_emitted(self, mock_tracker, **kwargs):
""" Assert that edx.course.bookmark.listed event is emitted with correct data. """
mock_tracker.assert_any_call(
'edx.bookmark.listed',
kwargs
)
@ddt.data( @ddt.data(
('course_id={}', False), ('course_id={}', False),
('course_id={}&fields=path,display_name', True), ('course_id={}&fields=path,display_name', True),
) )
@ddt.unpack @ddt.unpack
def test_get_bookmarks_successfully(self, query_params, check_optionals): @patch('lms.djangoapps.bookmarks.views.tracker.emit')
def test_get_bookmarks_successfully(self, query_params, check_optionals, mock_tracker):
""" """
Test that requesting bookmarks for a course returns records successfully in Test that requesting bookmarks for a course returns records successfully in
expected order without optional fields. expected order without optional fields.
...@@ -174,7 +184,17 @@ class BookmarksListViewTests(BookmarksViewTestsMixin): ...@@ -174,7 +184,17 @@ class BookmarksListViewTests(BookmarksViewTestsMixin):
self.assert_valid_bookmark_response(bookmarks[0], self.bookmark_2, optional_fields=check_optionals) self.assert_valid_bookmark_response(bookmarks[0], self.bookmark_2, optional_fields=check_optionals)
self.assert_valid_bookmark_response(bookmarks[1], self.bookmark_1, optional_fields=check_optionals) self.assert_valid_bookmark_response(bookmarks[1], self.bookmark_1, optional_fields=check_optionals)
def test_get_bookmarks_with_pagination(self): self.assert_bookmark_listed_event_emitted(
mock_tracker,
course_id=self.course_id,
list_type='per_course',
bookmarks_count=2,
page_size=10,
page_number=1
)
@patch('lms.djangoapps.bookmarks.views.tracker.emit')
def test_get_bookmarks_with_pagination(self, mock_tracker):
""" """
Test that requesting bookmarks for a course return results with pagination 200 code. Test that requesting bookmarks for a course return results with pagination 200 code.
""" """
...@@ -191,7 +211,17 @@ class BookmarksListViewTests(BookmarksViewTestsMixin): ...@@ -191,7 +211,17 @@ class BookmarksListViewTests(BookmarksViewTestsMixin):
self.assertEqual(len(bookmarks), 1) self.assertEqual(len(bookmarks), 1)
self.assert_valid_bookmark_response(bookmarks[0], self.bookmark_2) self.assert_valid_bookmark_response(bookmarks[0], self.bookmark_2)
def test_get_bookmarks_with_invalid_data(self): self.assert_bookmark_listed_event_emitted(
mock_tracker,
course_id=self.course_id,
list_type='per_course',
bookmarks_count=2,
page_size=1,
page_number=1
)
@patch('lms.djangoapps.bookmarks.views.tracker.emit')
def test_get_bookmarks_with_invalid_data(self, mock_tracker):
""" """
Test that requesting bookmarks with invalid data returns 0 records. Test that requesting bookmarks with invalid data returns 0 records.
""" """
...@@ -200,7 +230,10 @@ class BookmarksListViewTests(BookmarksViewTestsMixin): ...@@ -200,7 +230,10 @@ class BookmarksListViewTests(BookmarksViewTestsMixin):
bookmarks = response.data['results'] bookmarks = response.data['results']
self.assertEqual(len(bookmarks), 0) self.assertEqual(len(bookmarks), 0)
def test_get_all_bookmarks_when_course_id_not_given(self): self.assertFalse(mock_tracker.emit.called) # pylint: disable=maybe-no-member
@patch('lms.djangoapps.bookmarks.views.tracker.emit')
def test_get_all_bookmarks_when_course_id_not_given(self, mock_tracker):
""" """
Test that requesting bookmarks returns all records for that user. Test that requesting bookmarks returns all records for that user.
""" """
...@@ -212,6 +245,14 @@ class BookmarksListViewTests(BookmarksViewTestsMixin): ...@@ -212,6 +245,14 @@ class BookmarksListViewTests(BookmarksViewTestsMixin):
self.assert_valid_bookmark_response(bookmarks[1], self.bookmark_2) self.assert_valid_bookmark_response(bookmarks[1], self.bookmark_2)
self.assert_valid_bookmark_response(bookmarks[2], self.bookmark_1) self.assert_valid_bookmark_response(bookmarks[2], self.bookmark_1)
self.assert_bookmark_listed_event_emitted(
mock_tracker,
list_type='all_courses',
bookmarks_count=3,
page_size=10,
page_number=1
)
def test_anonymous_access(self): def test_anonymous_access(self):
""" """
Test that an anonymous client (not logged in) cannot call GET or POST. Test that an anonymous client (not logged in) cannot call GET or POST.
...@@ -312,6 +353,42 @@ class BookmarksListViewTests(BookmarksViewTestsMixin): ...@@ -312,6 +353,42 @@ class BookmarksListViewTests(BookmarksViewTestsMixin):
self.assertEqual(405, self.client.put(reverse('bookmarks')).status_code) self.assertEqual(405, self.client.put(reverse('bookmarks')).status_code)
self.assertEqual(405, self.client.delete(reverse('bookmarks')).status_code) self.assertEqual(405, self.client.delete(reverse('bookmarks')).status_code)
@patch('lms.djangoapps.bookmarks.views.tracker.emit')
@ddt.unpack
@ddt.data(
{'page_size': -1, 'expected_bookmarks_count': 2, 'expected_page_size': 10, 'expected_page_number': 1},
{'page_size': 0, 'expected_bookmarks_count': 2, 'expected_page_size': 10, 'expected_page_number': 1},
{'page_size': 999, 'expected_bookmarks_count': 2, 'expected_page_size': 500, 'expected_page_number': 1}
)
def test_listed_event_for_different_page_size_values(self, mock_tracker, page_size, expected_bookmarks_count,
expected_page_size, expected_page_number):
""" Test that edx.course.bookmark.listed event values are as expected for different page size values """
query_parameters = 'course_id={}&page_size={}'.format(urllib.quote(self.course_id), page_size)
self.send_get(client=self.client, url=reverse('bookmarks'), query_parameters=query_parameters)
self.assert_bookmark_listed_event_emitted(
mock_tracker,
course_id=self.course_id,
list_type='per_course',
bookmarks_count=expected_bookmarks_count,
page_size=expected_page_size,
page_number=expected_page_number
)
@patch('lms.djangoapps.bookmarks.views.tracker.emit')
def test_listed_event_for_page_number(self, mock_tracker):
""" Test that edx.course.bookmark.listed event values are as expected when we request a specific page number """
self.send_get(client=self.client, url=reverse('bookmarks'), query_parameters='page_size=2&page=2')
self.assert_bookmark_listed_event_emitted(
mock_tracker,
list_type='all_courses',
bookmarks_count=3,
page_size=2,
page_number=2
)
@ddt.ddt @ddt.ddt
class BookmarksDetailViewTests(BookmarksViewTestsMixin): class BookmarksDetailViewTests(BookmarksViewTestsMixin):
......
...@@ -4,6 +4,7 @@ HTTP end-points for the Bookmarks API. ...@@ -4,6 +4,7 @@ HTTP end-points for the Bookmarks API.
For more information, see: For more information, see:
https://openedx.atlassian.net/wiki/display/TNL/Bookmarks+API https://openedx.atlassian.net/wiki/display/TNL/Bookmarks+API
""" """
from eventtracking import tracker
import logging import logging
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
...@@ -166,6 +167,31 @@ class BookmarksListView(ListCreateAPIView, BookmarksViewMixin): ...@@ -166,6 +167,31 @@ class BookmarksListView(ListCreateAPIView, BookmarksViewMixin):
return api.get_bookmarks(user=self.request.user, course_key=course_key, serialized=False) return api.get_bookmarks(user=self.request.user, course_key=course_key, serialized=False)
def paginate_queryset(self, queryset, page_size=None):
""" Override GenericAPIView.paginate_queryset for the purpose of eventing """
page = super(BookmarksListView, self).paginate_queryset(queryset, page_size)
course_id = self.request.QUERY_PARAMS.get('course_id')
if course_id:
try:
CourseKey.from_string(course_id)
except InvalidKeyError:
return page
event_data = {
'list_type': 'all_courses',
'bookmarks_count': page.paginator.count,
'page_size': self.get_paginate_by(),
'page_number': page.number,
}
if course_id is not None:
event_data['list_type'] = 'per_course'
event_data['course_id'] = course_id
tracker.emit('edx.bookmark.listed', event_data)
return page
def post(self, request): def post(self, request):
""" """
POST /api/bookmarks/v1/bookmarks/ POST /api/bookmarks/v1/bookmarks/
......
;(function (define, undefined) { ;(function (define, undefined) {
'use strict'; 'use strict';
define(['gettext', 'jquery', 'underscore', 'backbone', 'moment'], define(['gettext', 'jquery', 'underscore', 'backbone', 'logger', 'moment'],
function (gettext, $, _, Backbone, _moment) { function (gettext, $, _, Backbone, Logger, _moment) {
var moment = _moment || window.moment; var moment = _moment || window.moment;
...@@ -62,7 +62,20 @@ ...@@ -62,7 +62,20 @@
}, },
visitBookmark: function (event) { visitBookmark: function (event) {
window.location = event.target.pathname; var bookmarkedComponent = $(event.currentTarget);
var bookmark_id = bookmarkedComponent.data('bookmarkId');
var component_usage_id = bookmarkedComponent.data('usageId');
var component_type = bookmarkedComponent.data('componentType');
Logger.log(
'edx.bookmark.accessed',
{
bookmark_id: bookmark_id,
component_type: component_type,
component_usage_id: component_usage_id
}
).always(function () {
window.location.href = event.currentTarget.pathname;
});
}, },
/** /**
......
define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'js/common_helpers/template_helpers', define(['backbone', 'jquery', 'underscore', 'logger', 'js/common_helpers/ajax_helpers',
'js/bookmarks/views/bookmarks_list_button' 'js/common_helpers/template_helpers', 'js/bookmarks/views/bookmarks_list_button'
], ],
function (Backbone, $, _, AjaxHelpers, TemplateHelpers, BookmarksListButtonView) { function (Backbone, $, _, Logger, AjaxHelpers, TemplateHelpers, BookmarksListButtonView) {
'use strict'; 'use strict';
describe("lms.courseware.bookmarks", function () { describe("lms.courseware.bookmarks", function () {
...@@ -17,14 +17,14 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j ...@@ -17,14 +17,14 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
'templates/bookmarks/bookmarks_list' 'templates/bookmarks/bookmarks_list'
] ]
); );
spyOn(Logger, 'log').andReturn($.Deferred().resolve());
bookmarksButtonView = new BookmarksListButtonView();
this.addMatchers({ this.addMatchers({
toHaveBeenCalledWithUrl: function (expectedUrl) { toHaveBeenCalledWithUrl: function (expectedUrl) {
return expectedUrl === this.actual.argsForCall[0][0].target.pathname; return expectedUrl === this.actual.argsForCall[0][0].target.pathname;
} }
}); });
bookmarksButtonView = new BookmarksListButtonView();
}); });
var createBookmarksData = function (count) { var createBookmarksData = function (count) {
...@@ -39,6 +39,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j ...@@ -39,6 +39,7 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
created: new Date().toISOString(), created: new Date().toISOString(),
course_id: 'COURSE_ID', course_id: 'COURSE_ID',
usage_id: 'UNIT_USAGE_ID_' + i, usage_id: 'UNIT_USAGE_ID_' + i,
block_type: 'vertical',
path: [ path: [
{display_name: 'SECTION_DISAPLAY_NAME', usage_id: 'SECTION_USAGE_ID'}, {display_name: 'SECTION_DISAPLAY_NAME', usage_id: 'SECTION_USAGE_ID'},
{display_name: 'SUBSECTION_DISAPLAY_NAME', usage_id: 'SUBSECTION_USAGE_ID'} {display_name: 'SUBSECTION_DISAPLAY_NAME', usage_id: 'SUBSECTION_USAGE_ID'}
...@@ -68,17 +69,21 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j ...@@ -68,17 +69,21 @@ define(['backbone', 'jquery', 'underscore', 'js/common_helpers/ajax_helpers', 'j
expect(bookmarks.length, results.length); expect(bookmarks.length, results.length);
for(var b = 0; b < results.length; b++) { for(var bookmark_index = 0; bookmark_index < results.length; bookmark_index++) {
courseId = results[b].course_id; courseId = results[bookmark_index].course_id;
usageId = results[b].usage_id; usageId = results[bookmark_index].usage_id;
expect(bookmarks[bookmark_index]).toHaveAttr('href', createBookmarkUrl(courseId, usageId));
expect(bookmarks[b]).toHaveAttr('href', createBookmarkUrl(courseId, usageId)); expect($(bookmarks[bookmark_index]).data('bookmarkId')).toBe(bookmark_index);
expect($(bookmarks[bookmark_index]).data('componentType')).toBe('vertical');
expect($(bookmarks[bookmark_index]).data('usageId')).toBe(usageId);
expect($(bookmarks[b]).find('.list-item-breadcrumbtrail').html().trim()). expect($(bookmarks[bookmark_index]).find('.list-item-breadcrumbtrail').html().trim()).
toBe(breadcrumbTrail(results[b].path, results[b].display_name)); toBe(breadcrumbTrail(results[bookmark_index].path, results[bookmark_index].display_name));
expect($(bookmarks[b]).find('.list-item-date').text().trim()). expect($(bookmarks[bookmark_index]).find('.list-item-date').text().trim()).
toBe('Bookmarked on ' + view.humanFriendlyDate(results[b].created)); toBe('Bookmarked on ' + view.humanFriendlyDate(results[bookmark_index].created));
} }
}; };
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<div class='bookmarks-results-list'> <div class='bookmarks-results-list'>
<% _.each(bookmarks, function(bookmark, index) { %> <% _.each(bookmarks, function(bookmark, index) { %>
<a class="bookmarks-results-list-item" href="<%= bookmark.blockUrl() %>" aria-labelledby="bookmark-link-<%= index %>" aria-describedby="bookmark-type-<%= index %> bookmark-date-<%= index %>"> <a class="bookmarks-results-list-item" href="<%= bookmark.blockUrl() %>" aria-labelledby="bookmark-link-<%= index %>" data-bookmark-id="<%= bookmark.get('id') %>" data-component-type="<%= bookmark.get('block_type') %>" data-usage-id="<%= bookmark.get('usage_id') %>" aria-describedby="bookmark-type-<%= index %> bookmark-date-<%= index %>">
<div class="list-item-content"> <div class="list-item-content">
<div class="list-item-left-section"> <div class="list-item-left-section">
<h3 id="bookmark-link-<%= index %>" class="list-item-breadcrumbtrail"> <%= _.pluck(bookmark.get('path'), 'display_name').concat([bookmark.get('display_name')]).join(' <i class="icon fa fa-caret-right" aria-hidden="true"></i><span class="sr">-</span> ') %> </h3> <h3 id="bookmark-link-<%= index %>" class="list-item-breadcrumbtrail"> <%= _.pluck(bookmark.get('path'), 'display_name').concat([bookmark.get('display_name')]).join(' <i class="icon fa fa-caret-right" aria-hidden="true"></i><span class="sr">-</span> ') %> </h3>
......
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