Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ import * as Time from 'time.js';
});

function itSpec(value) {
state.config.saveStateEnabled = true;
var asyncVal = value.asyncVal,
speedVal = value.speedVal,
positionVal = value.positionVal,
Expand Down Expand Up @@ -162,6 +163,11 @@ import * as Time from 'time.js';
});

it('can save state on speed change', function() {
state.el.trigger('speedchange', ['2.0']);
expect($.ajax).not.toHaveBeenCalledWith({
url: state.config.saveStateUrl
});
state.config.saveStateEnabled = true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For each of these tests can you add the check for what happens if saveStateEnabled is not true as well? For example, in this test if we put the following as the first two lines it should pass:

state.el.trigger('speedchange', ['2.0']);
expect($.ajax).not.toHaveBeenCalled();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the saveStateEnabled set to false, it makes an ajax call to /event. So I've added checks to verify that it doesn't call savestate when saveStateEnabled is false.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thanks!

state.el.trigger('speedchange', ['2.0']);
expect($.ajax).toHaveBeenCalledWith({
url: state.config.saveStateUrl,
Expand All @@ -173,6 +179,12 @@ import * as Time from 'time.js';
});

it('can save state on page unload', function() {
$.ajax.calls.reset();
state.videoSaveStatePlugin.onUnload();
expect($.ajax).not.toHaveBeenCalledWith({
url: state.config.saveStateUrl
})
state.config.saveStateEnabled = true;
$.ajax.calls.reset();
state.videoSaveStatePlugin.onUnload();
expect($.ajax).toHaveBeenCalledWith({
Expand All @@ -185,6 +197,11 @@ import * as Time from 'time.js';
});

it('can save state on pause', function() {
state.el.trigger('pause');
expect($.ajax).not.toHaveBeenCalledWith({
url: state.config.saveStateUrl
})
state.config.saveStateEnabled = true;
state.el.trigger('pause');
expect($.ajax).toHaveBeenCalledWith({
url: state.config.saveStateUrl,
Expand Down Expand Up @@ -213,6 +230,11 @@ import * as Time from 'time.js';
expect($.ajax).not.toHaveBeenCalled();

// Test that we can go from unavailable -> available
state.config.saveStateEnabled = false;
state.config.recordedYoutubeIsAvailable = false;
state.el.trigger('youtube_availability', [true]);
expect($.ajax).not.toHaveBeenCalled()
state.config.saveStateEnabled = true;
state.config.recordedYoutubeIsAvailable = false;
state.el.trigger('youtube_availability', [true]);
expect($.ajax).toHaveBeenCalledWith({
Expand Down
44 changes: 23 additions & 21 deletions common/lib/xmodule/xmodule/js/src/video/09_save_state_plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,28 +99,30 @@
},

saveState: function(async, data) {
if (!($.isPlainObject(data))) {
data = {
saved_video_position: this.state.videoPlayer.currentTime
};
if (this.state.config.saveStateEnabled) {
if (!($.isPlainObject(data))) {
data = {
saved_video_position: this.state.videoPlayer.currentTime
};
}

if (data.speed) {
this.state.storage.setItem('speed', data.speed, true);
}

if (_.has(data, 'saved_video_position')) {
this.state.storage.setItem('savedVideoPosition', data.saved_video_position, true);
data.saved_video_position = Time.formatFull(data.saved_video_position);
}

$.ajax({
url: this.state.config.saveStateUrl,
type: 'POST',
async: !!async,
dataType: 'json',
data: data
});
}

if (data.speed) {
this.state.storage.setItem('speed', data.speed, true);
}

if (_.has(data, 'saved_video_position')) {
this.state.storage.setItem('savedVideoPosition', data.saved_video_position, true);
data.saved_video_position = Time.formatFull(data.saved_video_position);
}

$.ajax({
url: this.state.config.saveStateUrl,
type: 'POST',
async: !!async,
dataType: 'json',
data: data
});
}
};

Expand Down
3 changes: 2 additions & 1 deletion common/lib/xmodule/xmodule/seq_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,8 @@ def _render_student_view_for_items(self, context, display_items, fragment, view=

if is_user_authenticated:
if item.location.block_type == 'vertical':
iteminfo['complete'] = completion_service.vertical_is_complete(item)
if completion_service:
iteminfo['complete'] = completion_service.vertical_is_complete(item)

contents.append(iteminfo)

Expand Down
12 changes: 10 additions & 2 deletions common/lib/xmodule/xmodule/video_module/video_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from xmodule.raw_module import EmptyDataRawDescriptor
from xmodule.validation import StudioValidation, StudioValidationMessage
from xmodule.video_module import manage_video_subtitles_save
from xmodule.x_module import XModule, module_attr
from xmodule.x_module import XModule, module_attr, PUBLIC_VIEW, STUDENT_VIEW
from xmodule.xml_module import deserialize_field, is_pointer_tag, name_to_pathname

from .bumper_utils import bumperize
Expand All @@ -54,6 +54,7 @@
from .video_handlers import VideoStudentViewHandlers, VideoStudioViewHandlers
from .video_utils import create_youtube_string, format_xml_exception_message, get_poster, rewrite_video_url
from .video_xfields import VideoFields
from web_fragments.fragment import Fragment

# The following import/except block for edxval is temporary measure until
# edxval is a proper XBlock Runtime Service.
Expand Down Expand Up @@ -208,7 +209,13 @@ def prioritize_hls(self, youtube_streams, html5_sources):

return False

def get_html(self):
def public_view(self, context):
"""
Returns a fragment that contains the html for the public view
"""
return Fragment(self.get_html(view=PUBLIC_VIEW))

def get_html(self, view=STUDENT_VIEW):

track_status = (self.download_track and self.track)
transcript_download_format = self.transcript_download_format if not track_status else None
Expand Down Expand Up @@ -338,6 +345,7 @@ def get_html(self):
autoadvance_this_video = self.auto_advance and autoadvance_enabled

metadata = {
'saveStateEnabled': view != PUBLIC_VIEW,
'saveStateUrl': self.system.ajax_url + '/save_user_state',
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
'streams': self.youtube_streams,
Expand Down
23 changes: 22 additions & 1 deletion lms/djangoapps/courseware/tests/test_video_mongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
EXPORT_IMPORT_COURSE_DIR,
EXPORT_IMPORT_STATIC_DIR,
)
from xmodule.x_module import STUDENT_VIEW
from xmodule.x_module import STUDENT_VIEW, PUBLIC_VIEW

from .helpers import BaseTestXmodule
from .test_video_handlers import TestVideo
Expand Down Expand Up @@ -97,6 +97,7 @@ def test_video_constructor(self):
'id': self.item_descriptor.location.html_id(),
'metadata': json.dumps(OrderedDict({
'autoAdvance': False,
'saveStateEnabled': True,
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
'autoplay': False,
'streams': '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg',
Expand Down Expand Up @@ -179,6 +180,7 @@ def test_video_constructor(self):
'id': self.item_descriptor.location.html_id(),
'metadata': json.dumps(OrderedDict({
'autoAdvance': False,
'saveStateEnabled': True,
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
'autoplay': False,
'streams': '1.00:3_yD_cEKoCk',
Expand Down Expand Up @@ -238,6 +240,7 @@ def setUp(self):
self.setup_course()
self.default_metadata_dict = OrderedDict({
'autoAdvance': False,
'saveStateEnabled': True,
'saveStateUrl': '',
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True),
'streams': '1.00:3_yD_cEKoCk',
Expand Down Expand Up @@ -971,6 +974,22 @@ def test_get_html_hls_no_video_id(self):
context = self.item_descriptor.render(STUDENT_VIEW).content
self.assertIn("'download_video_link': None", context)

def test_html_student_public_view(self):
"""
Test the student and public views
"""
video_xml = """
<video display_name="Video" download_video="true" source="https://hls.com/hls.m3u8">
["https://hls.com/hls2.m3u8", "https://hls.com/hls3.m3u8"]
</video>
"""

self.initialize_module(data=video_xml)
context = self.item_descriptor.render(STUDENT_VIEW).content
self.assertIn('"saveStateEnabled": true', context)
context = self.item_descriptor.render(PUBLIC_VIEW).content
self.assertIn('"saveStateEnabled": false', context)

@patch('xmodule.video_module.video_module.edxval_api.get_course_video_image_url')
def test_poster_image(self, get_course_video_image_url):
"""
Expand Down Expand Up @@ -2135,6 +2154,7 @@ def test_bumper_metadata(self, get_url_for_profiles, get_bumper_settings, is_bum
'id': self.item_descriptor.location.html_id(),
'metadata': json.dumps(OrderedDict({
'autoAdvance': False,
'saveStateEnabled': True,
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
'autoplay': False,
'streams': '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg',
Expand Down Expand Up @@ -2208,6 +2228,7 @@ def prepare_expected_context(self, autoadvanceenabled_flag, autoadvance_flag):
'bumper_metadata': 'null',
'metadata': json.dumps(OrderedDict({
'autoAdvance': autoadvance_flag,
'saveStateEnabled': True,
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
'autoplay': False,
'streams': '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg',
Expand Down
4 changes: 3 additions & 1 deletion lms/djangoapps/lms_xblock/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ def __init__(self, **kwargs):
store = modulestore()

services = kwargs.setdefault('services', {})
services['completion'] = CompletionService(user=kwargs.get('user'), course_key=kwargs.get('course_id'))
user = kwargs.get('user')
if user and user.is_authenticated:
services['completion'] = CompletionService(user=user, course_key=kwargs.get('course_id'))
services['fs'] = xblock.reference.plugins.FSService()
services['i18n'] = ModuleI18nService
services['library_tools'] = LibraryToolsService(store)
Expand Down