Merge pull request #17938 from edx/ammar/add-check-transcript-ajax-on-basic-tab

add check transcript ajax for edx_video_id
This commit is contained in:
Muhammad Ammar
2018-05-03 11:21:18 +05:00
committed by GitHub
17 changed files with 591 additions and 240 deletions

View File

@@ -195,7 +195,7 @@ class TestUploadTranscripts(BaseTranscripts):
self.bad_name_srt_file.close()
self.bom_srt_file.close()
def upload_transcript(self, locator, transcript_file):
def upload_transcript(self, locator, transcript_file, edx_video_id=None):
"""
Uploads a transcript for a video
"""
@@ -203,6 +203,9 @@ class TestUploadTranscripts(BaseTranscripts):
if locator:
payload.update({'locator': locator})
if edx_video_id is not None:
payload.update({'edx_video_id': edx_video_id})
if transcript_file:
payload.update({'transcript-file': transcript_file})
@@ -233,7 +236,7 @@ class TestUploadTranscripts(BaseTranscripts):
# Upload a transcript
transcript_file = self.bom_srt_file if include_bom else self.good_srt_file
response = self.upload_transcript(self.video_usage_key, transcript_file)
response = self.upload_transcript(self.video_usage_key, transcript_file, '')
# Verify the response
self.assert_response(response, expected_status_code=200, expected_message='Success')
@@ -258,7 +261,7 @@ class TestUploadTranscripts(BaseTranscripts):
"""
Test that transcript upload validation fails if the video locator is missing
"""
response = self.upload_transcript(locator=None, transcript_file=self.good_srt_file)
response = self.upload_transcript(locator=None, transcript_file=self.good_srt_file, edx_video_id='')
self.assert_response(
response,
expected_status_code=400,
@@ -269,7 +272,7 @@ class TestUploadTranscripts(BaseTranscripts):
"""
Test that transcript upload validation fails if transcript file is missing
"""
response = self.upload_transcript(locator=self.video_usage_key, transcript_file=None)
response = self.upload_transcript(locator=self.video_usage_key, transcript_file=None, edx_video_id='')
self.assert_response(
response,
expected_status_code=400,
@@ -280,7 +283,11 @@ class TestUploadTranscripts(BaseTranscripts):
"""
Test that transcript upload validation fails if transcript format is not SRT
"""
response = self.upload_transcript(locator=self.video_usage_key, transcript_file=self.bad_name_srt_file)
response = self.upload_transcript(
locator=self.video_usage_key,
transcript_file=self.bad_name_srt_file,
edx_video_id=''
)
self.assert_response(
response,
expected_status_code=400,
@@ -292,7 +299,11 @@ class TestUploadTranscripts(BaseTranscripts):
Test that transcript upload validation fails in case of bad transcript content.
"""
# Request to upload transcript for the video
response = self.upload_transcript(locator=self.video_usage_key, transcript_file=self.bad_data_srt_file)
response = self.upload_transcript(
locator=self.video_usage_key,
transcript_file=self.bad_data_srt_file,
edx_video_id=''
)
self.assert_response(
response,
expected_status_code=400,
@@ -306,7 +317,7 @@ class TestUploadTranscripts(BaseTranscripts):
# non_video module setup - i.e. an item whose category is not 'video'.
usage_key = self.create_non_video_module()
# Request to upload transcript for the item
response = self.upload_transcript(locator=usage_key, transcript_file=self.good_srt_file)
response = self.upload_transcript(locator=usage_key, transcript_file=self.good_srt_file, edx_video_id='')
self.assert_response(
response,
expected_status_code=400,
@@ -318,13 +329,47 @@ class TestUploadTranscripts(BaseTranscripts):
Test that transcript upload validation fails in case of invalid item's locator.
"""
# Request to upload transcript for the item
response = self.upload_transcript(locator='non_existent_locator', transcript_file=self.good_srt_file)
response = self.upload_transcript(
locator='non_existent_locator',
transcript_file=self.good_srt_file,
edx_video_id=''
)
self.assert_response(
response,
expected_status_code=400,
expected_message=u'Cannot find item by locator.'
)
def test_transcript_upload_without_edx_video_id(self):
"""
Test that transcript upload validation fails if the `edx_video_id` is missing
"""
response = self.upload_transcript(locator=self.video_usage_key, transcript_file=self.good_srt_file)
self.assert_response(
response,
expected_status_code=400,
expected_message=u'Video ID is required.'
)
def test_transcript_upload_with_non_existant_edx_video_id(self):
"""
Test that transcript upload works as expected if `edx_video_id` set on
video descriptor is different from `edx_video_id` received in POST request.
"""
non_existant_edx_video_id = '1111-2222-3333-4444'
# Upload with non-existant `edx_video_id`
response = self.upload_transcript(
locator=self.video_usage_key,
transcript_file=self.good_srt_file,
edx_video_id=non_existant_edx_video_id
)
# Verify the response
self.assert_response(response, expected_status_code=400, expected_message='Invalid Video ID')
# Verify transcript does not exist for non-existant `edx_video_id`
self.assertIsNone(get_video_transcript_content(non_existant_edx_video_id, language_code=u'en'))
@ddt.ddt
class TestChooseTranscripts(BaseTranscripts):

View File

@@ -169,19 +169,22 @@ def validate_transcript_upload_data(request):
error, validated_data = None, {}
data, files = request.POST, request.FILES
video_locator = data.get('locator')
edx_video_id = data.get('edx_video_id')
if not video_locator:
error = _(u'Video locator is required.')
elif 'transcript-file' not in files:
error = _(u'A transcript file is required.')
elif os.path.splitext(files['transcript-file'].name)[1][1:] != Transcript.SRT:
error = _(u'This transcript file type is not supported.')
elif 'edx_video_id' not in data:
error = _(u'Video ID is required.')
if not error:
error, video = validate_video_module(request, video_locator)
if not error:
validated_data.update({
'video': video,
'edx_video_id': clean_video_id(video.edx_video_id),
'edx_video_id': clean_video_id(edx_video_id) or clean_video_id(video.edx_video_id),
'transcript_file': files['transcript-file']
})
@@ -212,6 +215,8 @@ def upload_transcripts(request):
video.edx_video_id = edx_video_id
video.save_with_metadata(request.user)
response = JsonResponse({'edx_video_id': edx_video_id, 'status': 'Success'}, status=200)
try:
# Convert 'srt' transcript into the 'sjson' and upload it to
# configured transcript storage. For example, S3.
@@ -220,7 +225,7 @@ def upload_transcripts(request):
input_format=Transcript.SRT,
output_format=Transcript.SJSON
)
create_or_update_video_transcript(
transcript_created = create_or_update_video_transcript(
video_id=edx_video_id,
language_code=u'en',
metadata={
@@ -230,7 +235,9 @@ def upload_transcripts(request):
},
file_data=ContentFile(sjson_subs),
)
response = JsonResponse({'edx_video_id': edx_video_id, 'status': 'Success'}, status=200)
if transcript_created is None:
response = JsonResponse({'status': 'Invalid Video ID'}, status=400)
except (TranscriptsGenerationException, UnicodeDecodeError):
@@ -310,7 +317,8 @@ def check_transcripts(request):
transcripts_presence['status'] = 'Success'
try:
get_transcript_from_val(edx_video_id=item.edx_video_id, lang=u'en')
edx_video_id = clean_video_id(videos.get('edx_video_id'))
get_transcript_from_val(edx_video_id=edx_video_id, lang=u'en')
command = 'found'
except NotFoundError:
filename = 'subs_{0}.srt.sjson'.format(item.sub)
@@ -465,6 +473,9 @@ def _validate_transcripts_data(request):
for video_data in data.get('videos'):
if video_data['type'] == 'youtube':
videos['youtube'] = video_data['video']
elif video_data['type'] == 'edx_video_id':
if clean_video_id(video_data['video']):
videos['edx_video_id'] = video_data['video']
else: # do not add same html5 videos
if videos['html5'].get('video') != video_data['video']:
videos['html5'][video_data['video']] = video_data['mode']

View File

@@ -242,10 +242,10 @@
'js/spec/views/metadata_edit_spec',
'js/spec/views/textbook_spec',
'js/spec/views/upload_spec',
'js/spec/video/transcripts/message_manager_spec',
'js/spec/video/transcripts/utils_spec',
'js/spec/video/transcripts/editor_spec',
'js/spec/video/transcripts/videolist_spec',
'js/spec/video/transcripts/message_manager_spec',
'js/spec/video/transcripts/file_uploader_spec',
'js/spec/models/component_template_spec',
'js/spec/models/explicit_url_spec',

View File

@@ -28,6 +28,7 @@
'jquery.iframe-transport': 'xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport', // eslint-disable-line max-len
'jquery.inputnumber': 'xmodule_js/common_static/js/vendor/html5-input-polyfills/number-polyfill',
'jquery.immediateDescendents': 'xmodule_js/common_static/js/src/jquery.immediateDescendents',
'jquery.ajaxQueue': 'xmodule_js/common_static/js/vendor/jquery.ajaxQueue',
'datepair': 'xmodule_js/common_static/js/vendor/timepicker/datepair',
'date': 'xmodule_js/common_static/js/vendor/date',
'text': 'xmodule_js/common_static/js/vendor/requirejs/text',
@@ -108,6 +109,10 @@
deps: ['jquery', 'tinymce'],
exports: 'jQuery.fn.tinymce'
},
'jquery.ajaxQueue': {
deps: ['jquery'],
exports: 'jQuery.fn.ajaxQueue'
},
'datepair': {
deps: ['jquery.ui', 'jquery.timepicker']
},

View File

@@ -38,7 +38,7 @@ function($, Backbone, _, Utils, Editor, MetadataView, MetadataModel, MetadataCol
field_name: 'edx_video_id',
help: 'Specifies the video ID.',
options: [],
type: MetadataModel.GENERIC_TYPE,
type: 'VideoID',
value: 'basic tab video id'
},
models = [DisplayNameEntry, VideoListEntry, VideoIDEntry],
@@ -51,7 +51,8 @@ function($, Backbone, _, Utils, Editor, MetadataView, MetadataModel, MetadataCol
object: testData,
string: JSON.stringify(testData)
},
transcripts, $container;
component_locator = 'component_locator',
transcripts, $container, waitForEvent, editor;
var waitsForDisplayName = function(collection) {
return jasmine.waitUntil(function() {
@@ -76,15 +77,109 @@ function($, Backbone, _, Utils, Editor, MetadataView, MetadataModel, MetadataCol
Utils.Storage.remove('sub');
});
describe('Events', function() {
beforeEach(function() {
Utils.command.and.callThrough();
spyOn(Backbone, 'trigger').and.callThrough();
spyOn(Editor.prototype, 'destroy').and.callThrough();
spyOn(Editor.prototype, 'handleFieldChanged').and.callThrough();
spyOn(Editor.prototype, 'getLocator').and.returnValue(component_locator);
appendSetFixtures(
sandbox({ // eslint-disable-line no-undef
class: 'wrapper-comp-settings basic_metadata_edit',
'data-metadata': JSON.stringify({video_url: VideoListEntry, edx_video_id: VideoIDEntry})
})
);
appendSetFixtures(
$('<script>',
{
id: 'metadata-videolist-entry',
type: 'text/template'
}
).text(readFixtures('video/transcripts/metadata-videolist-entry.underscore'))
);
appendSetFixtures(
$('<script>',
{
id: 'metadata-string-entry',
type: 'text/template'
}
).text(readFixtures('metadata-string-entry.underscore'))
);
editor = new Editor({
el: $('.basic_metadata_edit')
});
// reset the already triggered events
Backbone.trigger.calls.reset();
// reset the manual call to `handleFieldChanged` we made in the `editor.js::initialize`
Editor.prototype.handleFieldChanged.calls.reset();
});
waitForEvent = function(eventName) {
var triggerCallArgs;
return jasmine.waitUntil(function() {
triggerCallArgs = Backbone.trigger.calls.mostRecent().args;
return Backbone.trigger.calls.count() === 1 && triggerCallArgs[0] === eventName;
});
};
afterEach(function() {
Backbone.trigger.calls.reset();
Editor.prototype.destroy.calls.reset();
Editor.prototype.handleFieldChanged.calls.reset();
});
it('handles transcripts:basicTabFieldChanged', function(done) {
var event = 'transcripts:basicTabFieldChanged';
Backbone.trigger(event);
waitForEvent(event)
.then(function() {
expect(Editor.prototype.handleFieldChanged).toHaveBeenCalled();
expect(Utils.command).toHaveBeenCalledWith(
'check',
component_locator,
[
{ mode: 'youtube', video: '12345678901', type: 'youtube' },
{ mode: 'html5', video: 'video', type: 'mp4' },
{ mode: 'html5', video: 'video', type: 'webm' },
{ mode: 'edx_video_id', type: 'edx_video_id', video: 'basic tab video id' }
]
);
}).always(done);
});
it('handles xblock:editorModalHidden', function(done) {
var event = 'xblock:editorModalHidden';
Backbone.trigger(event);
waitForEvent(event)
.then(function() {
expect(Editor.prototype.destroy).toHaveBeenCalled();
}).always(done);
});
});
describe('Test initialization', function() {
beforeEach(function() {
spyOn(MetadataView, 'Editor');
spyOn(Editor.prototype, 'handleFieldChanged');
transcripts = new Editor({
el: $container
});
});
afterEach(function() {
MetadataView.Editor.calls.reset();
Editor.prototype.handleFieldChanged.calls.reset();
});
$.each(metadataDict, function(index, val) {
it('toModels with argument as ' + index, function() {
expect(transcripts.toModels(val)).toEqual(models);
@@ -159,6 +254,7 @@ function($, Backbone, _, Utils, Editor, MetadataView, MetadataModel, MetadataCol
beforeEach(function() {
spyOn(MetadataView, 'Editor');
spyOn(Editor.prototype, 'handleFieldChanged');
transcripts = new Editor({
el: $container
@@ -182,6 +278,11 @@ function($, Backbone, _, Utils, Editor, MetadataView, MetadataModel, MetadataCol
);
});
afterEach(function() {
MetadataView.Editor.calls.reset();
Editor.prototype.handleFieldChanged.calls.reset();
});
describe('Test Advanced to Basic synchronization', function() {
it('Correct data', function(done) {
transcripts.syncBasicTab(metadataCollection, metadataView);

View File

@@ -4,7 +4,7 @@ define(
'js/views/video/transcripts/utils', 'js/views/video/transcripts/file_uploader',
'xmodule', 'jquery.form'
],
function($, _, Backbone, Utils, FileUploader) {
function($, _, Backbone, TranscriptUtils, FileUploader) {
'use strict';
describe('Transcripts.FileUploader', function() {
@@ -95,6 +95,12 @@ function($, _, Backbone, Utils, FileUploader) {
});
describe('Upload', function() {
var videoId = '123-456-789-0';
beforeEach(function() {
TranscriptUtils.Storage.set('edx_video_id', videoId);
});
it('File is not chosen', function() {
spyOn($.fn, 'ajaxSubmit');
view.upload();
@@ -109,6 +115,9 @@ function($, _, Backbone, Utils, FileUploader) {
view.upload();
expect(view.$form.ajaxSubmit).toHaveBeenCalled();
expect(view.$form.ajaxSubmit).toHaveBeenCalledWith(jasmine.objectContaining({
data: {'edx_video_id': videoId}
}));
});
});

View File

@@ -1,13 +1,15 @@
define(
[
'jquery', 'underscore',
'jquery', 'underscore', 'backbone',
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'js/views/video/transcripts/utils',
'js/views/video/transcripts/editor',
'js/views/video/transcripts/metadata_videolist', 'js/models/metadata',
'js/views/abstract_editor',
'js/views/video/transcripts/message_manager',
'xmodule'
],
function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
function($, _, Backbone, AjaxHelpers, Utils, Editor, VideoList, MetadataModel, AbstractEditor, MessageManager) {
'use strict';
describe('CMS.Views.Metadata.VideoList', function() {
var videoListEntryTemplate = readFixtures(
@@ -46,12 +48,23 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
'video.webm'
]
},
videoIDStub = {
default_value: 'test default value',
display_name: 'Video ID',
explicitly_set: true,
field_name: 'edx_video_id',
help: 'Specifies the video ID.',
options: [],
type: 'VideoID',
value: 'advanced tab video id'
},
response = JSON.stringify({
command: 'found',
status: 'Success',
subs: 'video_id'
}),
MessageManager, messenger;
waitForEvent,
createVideoListView;
var createMockAjaxServer = function() {
@@ -67,7 +80,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
};
beforeEach(function() {
var tpl = sandbox({
var tpl = sandbox({ // eslint-disable-line no-undef
class: 'component',
'data-locator': component_locator
});
@@ -86,24 +99,17 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
// create mock server
this.mockServer = createMockAjaxServer();
spyOn($.fn, 'on').and.callThrough();
spyOn(Backbone, 'trigger').and.callThrough();
spyOn(Utils, 'command').and.callThrough();
spyOn(abstractEditor, 'initialize').and.callThrough();
spyOn(abstractEditor, 'render').and.callThrough();
spyOn(console, 'error');
messenger = jasmine.createSpyObj('MessageManager', [
'initialize', 'render', 'showError', 'hideError'
]);
$.each(messenger, function(index, method) {
method.and.returnValue(messenger);
});
MessageManager = function() {
messenger.initialize();
return messenger;
};
spyOn(MessageManager.prototype, 'initialize').and.callThrough();
spyOn(MessageManager.prototype, 'render').and.callThrough();
spyOn(MessageManager.prototype, 'showError').and.callThrough();
spyOn(MessageManager.prototype, 'hideError').and.callThrough();
jasmine.addMatchers({
assertValueInView: function() {
@@ -154,13 +160,49 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
this.mockServer.restore();
});
var createVideoListView = function() {
var model = new MetadataModel(modelStub);
return new VideoList({
waitForEvent = function() {
var triggerCallArgs;
return jasmine.waitUntil(function() {
triggerCallArgs = Backbone.trigger.calls.mostRecent().args;
return Backbone.trigger.calls.count() === 1 &&
triggerCallArgs[0] === 'transcripts:basicTabFieldChanged';
});
};
createVideoListView = function(mockServer) {
var $container, editor, model, videoListView;
appendSetFixtures(
sandbox({ // eslint-disable-line no-undef
class: 'wrapper-comp-settings basic_metadata_edit',
'data-metadata': JSON.stringify({video_url: modelStub, edx_video_id: videoIDStub})
})
);
$container = $('.basic_metadata_edit');
editor = new Editor({
el: $container
});
spyOn(editor, 'getLocator').and.returnValue(component_locator);
// reset
Backbone.trigger.calls.reset();
mockServer.requests.length = 0;
model = new MetadataModel(modelStub);
videoListView = new VideoList({
el: $('.component'),
model: model,
MessageManager: MessageManager
});
waitForEvent()
.then(function() {
return true;
});
return videoListView;
};
var waitsForResponse = function(mockServer) {
@@ -174,36 +216,46 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
it('Initialize', function(done) {
var view = createVideoListView();
var view = createVideoListView(this.mockServer), callArgs;
waitsForResponse(this.mockServer)
.then(function() {
expect(abstractEditor.initialize).toHaveBeenCalled();
expect(messenger.initialize).toHaveBeenCalled();
expect(view.component_locator).toBe(component_locator);
expect(view.$el).toHandle('input');
}).always(done);
.then(function() {
expect(abstractEditor.initialize).toHaveBeenCalled();
expect(MessageManager.prototype.initialize).toHaveBeenCalled();
expect(view.component_locator).toBe(component_locator);
expect(view.$el).toHandle('input');
callArgs = view.$el.on.calls.mostRecent().args;
expect(callArgs[0]).toEqual('input');
expect(callArgs[1]).toEqual('.videolist-settings-item input');
}).always(done);
});
describe('Render', function() {
var assertToHaveBeenRendered = function(videoList) {
expect(abstractEditor.render).toHaveBeenCalled();
expect(Utils.command).toHaveBeenCalledWith(
'check',
component_locator,
videoList
);
var assertToHaveBeenRendered = function(expectedVideoList) {
var commandCallArgs = Utils.command.calls.mostRecent().args,
actualVideoList = commandCallArgs[2].slice(0, expectedVideoList.length);
expect(messenger.render).toHaveBeenCalled();
expect(commandCallArgs[0]).toEqual('check');
expect(commandCallArgs[1]).toEqual(component_locator);
_.each([0, 1, 2], function(index) {
expect(_.isEqual(expectedVideoList[index], actualVideoList[index])).toBeTruthy();
});
expect(abstractEditor.render).toHaveBeenCalled();
expect(MessageManager.prototype.render).toHaveBeenCalled();
},
resetSpies = function(mockServer) {
abstractEditor.render.calls.reset();
Utils.command.calls.reset();
messenger.render.calls.reset();
mockServer.requests.length = 0;
MessageManager.prototype.render.calls.reset();
mockServer.requests.length = 0; // eslint-disable-line no-param-reassign
};
afterEach(function() {
Backbone.trigger('xblock:editorModalHidden');
});
it('is rendered in correct way', function(done) {
createVideoListView();
var view = createVideoListView(this.mockServer);
waitsForResponse(this.mockServer)
.then(function() {
assertToHaveBeenRendered(videoList);
@@ -212,7 +264,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
});
it('is rendered with opened extra videos bar', function(done) {
var view = createVideoListView();
var view = createVideoListView(this.mockServer);
var videoListLength = [
{
mode: 'youtube',
@@ -233,8 +285,8 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
}
];
spyOn(view, 'getVideoObjectsList').and.returnValue(videoListLength);
spyOn(view, 'openExtraVideosBar');
spyOn(VideoList.prototype, 'getVideoObjectsList').and.returnValue(videoListLength);
spyOn(VideoList.prototype, 'openExtraVideosBar');
resetSpies(this.mockServer);
view.render();
@@ -260,7 +312,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
});
it('is rendered without opened extra videos bar', function(done) {
var view = createVideoListView(),
var view = createVideoListView(this.mockServer),
videoList = [
{
mode: 'youtube',
@@ -269,8 +321,8 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
}
];
spyOn(view, 'getVideoObjectsList').and.returnValue(videoList);
spyOn(view, 'closeExtraVideosBar');
spyOn(VideoList.prototype, 'getVideoObjectsList').and.returnValue(videoList);
spyOn(VideoList.prototype, 'closeExtraVideosBar');
resetSpies(this.mockServer);
view.render();
@@ -286,7 +338,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
describe('isUniqOtherVideos', function() {
it('Unique data - return true', function(done) {
var view = createVideoListView(),
var view = createVideoListView(this.mockServer),
data = videoList.concat([{
mode: 'html5',
type: 'other',
@@ -302,7 +354,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
});
it('Not Unique data - return false', function(done) {
var view = createVideoListView(),
var view = createVideoListView(this.mockServer),
data = [
{
mode: 'html5',
@@ -342,7 +394,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
describe('isUniqVideoTypes', function() {
it('Unique data - return true', function(done) {
var view = createVideoListView(),
var view = createVideoListView(this.mockServer),
data = videoList;
waitsForResponse(this.mockServer)
@@ -354,7 +406,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
});
it('Not Unique data - return false', function(done) {
var view = createVideoListView(),
var view = createVideoListView(this.mockServer),
data = [
{
mode: 'html5',
@@ -389,7 +441,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
describe('checkIsUniqVideoTypes', function() {
it('Error is shown', function(done) {
var view = createVideoListView(),
var view = createVideoListView(this.mockServer),
data = [
{
mode: 'html5',
@@ -417,14 +469,14 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
.then(function() {
var result = view.checkIsUniqVideoTypes(data);
expect(messenger.showError).toHaveBeenCalled();
expect(MessageManager.prototype.showError).toHaveBeenCalled();
expect(result).toBe(false);
})
.always(done);
});
it('All works okay if arguments are not passed', function(done) {
var view = createVideoListView();
var view = createVideoListView(this.mockServer);
spyOn(view, 'getVideoObjectsList').and.returnValue(videoList);
waitsForResponse(this.mockServer)
@@ -432,7 +484,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
var result = view.checkIsUniqVideoTypes();
expect(view.getVideoObjectsList).toHaveBeenCalled();
expect(messenger.showError).not.toHaveBeenCalled();
expect(MessageManager.prototype.showError).not.toHaveBeenCalled();
expect(result).toBe(true);
})
.always(done);
@@ -441,7 +493,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
describe('checkValidity', function() {
it('Error message is shown', function(done) {
var view = createVideoListView();
var view = createVideoListView(this.mockServer);
spyOn(view, 'checkIsUniqVideoTypes').and.returnValue(true);
waitsForResponse(this.mockServer)
@@ -449,7 +501,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
var data = {mode: 'incorrect'},
result = view.checkValidity(data, true);
expect(messenger.showError).toHaveBeenCalled();
expect(MessageManager.prototype.showError).toHaveBeenCalled();
expect(view.checkIsUniqVideoTypes).toHaveBeenCalled();
expect(result).toBe(false);
})
@@ -457,7 +509,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
});
it('Error message is shown when flag is not passed', function(done) {
var view = createVideoListView();
var view = createVideoListView(this.mockServer);
spyOn(view, 'checkIsUniqVideoTypes').and.returnValue(true);
waitsForResponse(this.mockServer)
@@ -465,14 +517,14 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
var data = {mode: 'incorrect'},
result = view.checkValidity(data);
expect(messenger.showError).not.toHaveBeenCalled();
expect(MessageManager.prototype.showError).not.toHaveBeenCalled();
expect(view.checkIsUniqVideoTypes).toHaveBeenCalled();
expect(result).toBe(true);
}).always(done);
});
it('All works okay if correct data is passed', function(done) {
var view = createVideoListView();
var view = createVideoListView(this.mockServer);
spyOn(view, 'checkIsUniqVideoTypes').and.returnValue(true);
waitsForResponse(this.mockServer)
@@ -480,7 +532,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
var data = videoList,
result = view.checkValidity(data);
expect(messenger.showError).not.toHaveBeenCalled();
expect(MessageManager.prototype.showError).not.toHaveBeenCalled();
expect(view.checkIsUniqVideoTypes).toHaveBeenCalled();
expect(result).toBe(true);
})
@@ -489,7 +541,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
});
it('openExtraVideosBar', function(done) {
var view = createVideoListView();
var view = createVideoListView(this.mockServer);
waitsForResponse(this.mockServer)
.then(function() {
view.$extraVideosBar.removeClass('is-visible');
@@ -500,7 +552,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
});
it('closeExtraVideosBar', function(done) {
var view = createVideoListView();
var view = createVideoListView(this.mockServer);
waitsForResponse(this.mockServer)
.then(function() {
view.$extraVideosBar.addClass('is-visible');
@@ -512,7 +564,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
});
it('toggleExtraVideosBar', function(done) {
var view = createVideoListView();
var view = createVideoListView(this.mockServer);
waitsForResponse(this.mockServer)
.then(function() {
view.$extraVideosBar.addClass('is-visible');
@@ -525,7 +577,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
});
it('getValueFromEditor', function(done) {
var view = createVideoListView();
var view = createVideoListView(this.mockServer);
waitsForResponse(this.mockServer)
.then(function() {
expect(view).assertValueInView(modelStub.value);
@@ -534,7 +586,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
});
it('setValueInEditor', function(done) {
var view = createVideoListView();
var view = createVideoListView(this.mockServer);
waitsForResponse(this.mockServer)
.then(function() {
expect(view).assertCanUpdateView(['abc.mp4']);
@@ -543,7 +595,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
});
it('getVideoObjectsList', function(done) {
var view = createVideoListView();
var view = createVideoListView(this.mockServer);
var value = [
{
mode: 'youtube',
@@ -577,7 +629,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
describe('getPlaceholders', function() {
it('All works okay if empty values are passed', function(done) {
var view = createVideoListView(),
var view = createVideoListView(this.mockServer),
defaultPlaceholders = view.placeholders;
waitsForResponse(this.mockServer)
@@ -593,7 +645,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
it('On filling less than 3 fields, remaining fields should have ' +
'placeholders for video types that were not filled yet',
function(done) {
var view = createVideoListView(),
var view = createVideoListView(this.mockServer),
defaultPlaceholders = view.placeholders;
var dataDict = {
youtube: {
@@ -640,7 +692,7 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
var eventObject;
var resetSpies = function(view) {
messenger.hideError.calls.reset();
MessageManager.prototype.hideError.calls.reset();
view.updateModel.calls.reset();
view.closeExtraVideosBar.calls.reset();
};
@@ -660,100 +712,77 @@ function($, _, AjaxHelpers, Utils, VideoList, MetadataModel, AbstractEditor) {
resetSpies(view);
};
it('Field has invalid value - nothing should happen',
function(done) {
var view = createVideoListView();
setUp(view);
$.fn.hasClass.and.returnValue(false);
view.checkValidity.and.returnValue(false);
var videoListView = function() {
return new VideoList({
el: $('.component'),
model: new MetadataModel(modelStub),
MessageManager: MessageManager
});
};
waitsForResponse(this.mockServer)
.then(function() {
view.inputHandler(eventObject);
expect(messenger.hideError).not.toHaveBeenCalled();
expect(view.updateModel).not.toHaveBeenCalled();
expect(view.closeExtraVideosBar).not.toHaveBeenCalled();
expect($.fn.prop).toHaveBeenCalledWith(
'disabled', true
);
expect($.fn.addClass).toHaveBeenCalledWith(
'is-disabled'
);
})
.always(done);
}
);
beforeEach(function() {
MessageManager.prototype.render.and.callFake(function() { return true; });
});
it('Main field has invalid value - extra Videos Bar is closed',
function(done) {
var view = createVideoListView();
setUp(view);
$.fn.hasClass.and.returnValue(true);
view.checkValidity.and.returnValue(false);
afterEach(function() {
MessageManager.prototype.render.and.callThrough();
});
waitsForResponse(this.mockServer)
.then(function() {
view.inputHandler(eventObject);
expect(messenger.hideError).not.toHaveBeenCalled();
expect(view.updateModel).not.toHaveBeenCalled();
expect(view.closeExtraVideosBar).toHaveBeenCalled();
expect($.fn.prop).toHaveBeenCalledWith(
'disabled', true
);
expect($.fn.addClass).toHaveBeenCalledWith(
'is-disabled'
);
})
.always(done);
}
);
it('Field has invalid value - nothing should happen', function() {
var view = videoListView();
setUp(view);
$.fn.hasClass.and.returnValue(false);
view.checkValidity.and.returnValue(false);
it('Model is updated if value is valid',
function(done) {
var view = createVideoListView();
setUp(view);
view.checkValidity.and.returnValue(true);
_.isEqual.and.returnValue(false);
view.inputHandler(eventObject);
expect(MessageManager.prototype.hideError).not.toHaveBeenCalled();
expect(view.updateModel).not.toHaveBeenCalled();
expect(view.closeExtraVideosBar).not.toHaveBeenCalled();
expect($.fn.prop).toHaveBeenCalledWith('disabled', true);
expect($.fn.addClass).toHaveBeenCalledWith('is-disabled');
});
waitsForResponse(this.mockServer)
.then(function() {
view.inputHandler(eventObject);
expect(messenger.hideError).not.toHaveBeenCalled();
expect(view.updateModel).toHaveBeenCalled();
expect(view.closeExtraVideosBar).not.toHaveBeenCalled();
expect($.fn.prop).toHaveBeenCalledWith(
'disabled', false
);
expect($.fn.removeClass).toHaveBeenCalledWith(
'is-disabled'
);
})
.always(done);
}
);
it('Main field has invalid value - extra Videos Bar is closed', function() {
var view = videoListView();
setUp(view);
$.fn.hasClass.and.returnValue(true);
view.checkValidity.and.returnValue(false);
it('Corner case: Error is hided',
function(done) {
var view = createVideoListView();
setUp(view);
view.checkValidity.and.returnValue(true);
_.isEqual.and.returnValue(true);
waitsForResponse(this.mockServer)
.then(function() {
view.inputHandler(eventObject);
expect(messenger.hideError).toHaveBeenCalled();
expect(view.updateModel).not.toHaveBeenCalled();
expect(view.closeExtraVideosBar).not.toHaveBeenCalled();
expect($.fn.prop).toHaveBeenCalledWith(
'disabled', false
);
expect($.fn.removeClass).toHaveBeenCalledWith(
'is-disabled'
);
})
.always(done);
}
);
view.inputHandler(eventObject);
expect(MessageManager.prototype.hideError).not.toHaveBeenCalled();
expect(view.updateModel).not.toHaveBeenCalled();
expect(view.closeExtraVideosBar).toHaveBeenCalled();
expect($.fn.prop).toHaveBeenCalledWith('disabled', true);
expect($.fn.addClass).toHaveBeenCalledWith('is-disabled');
});
it('Model is updated if value is valid', function() {
var view = videoListView();
setUp(view);
view.checkValidity.and.returnValue(true);
_.isEqual.and.returnValue(false);
view.inputHandler(eventObject);
expect(MessageManager.prototype.hideError).not.toHaveBeenCalled();
expect(view.updateModel).toHaveBeenCalled();
expect(view.closeExtraVideosBar).not.toHaveBeenCalled();
expect($.fn.prop).toHaveBeenCalledWith('disabled', false);
expect($.fn.removeClass).toHaveBeenCalledWith('is-disabled');
});
it('Corner case: Error is hided', function() {
var view = videoListView();
setUp(view);
view.checkValidity.and.returnValue(true);
_.isEqual.and.returnValue(true);
view.inputHandler(eventObject);
expect(MessageManager.prototype.hideError).toHaveBeenCalled();
expect(view.updateModel).not.toHaveBeenCalled();
expect(view.closeExtraVideosBar).not.toHaveBeenCalled();
expect($.fn.prop).toHaveBeenCalledWith('disabled', false);
expect($.fn.removeClass).toHaveBeenCalledWith('is-disabled');
});
});
});
});

View File

@@ -3,8 +3,9 @@
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(["js/models/metadata", "js/collections/metadata", "js/views/metadata", "cms/js/main"],
function(MetadataModel, MetadataCollection, MetadataView, main) {
define(["underscore", "js/models/metadata", "js/collections/metadata", "js/views/metadata", "cms/js/main",
"js/views/video/transcripts/utils", 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'],
function(_, MetadataModel, MetadataCollection, MetadataView, main, TranscriptUtils, AjaxHelpers) {
const verifyInputType = function(input, expectedType) {
// Some browsers (e.g. FireFox) do not support the "number"
// input type. We can accept a "text" input instead
@@ -43,6 +44,8 @@ function(MetadataModel, MetadataCollection, MetadataView, main) {
value: "Word cloud"
};
const videoIDEntry = _.extend({}, genericEntry, {field_name: "edx_video_id", type: "VideoID"});
const selectEntry = {
default_value: "answered",
display_name: "Show Answer",
@@ -271,6 +274,51 @@ function(MetadataModel, MetadataCollection, MetadataView, main) {
});
});
describe("MetadataView.VideoID", function() {
var waitForMock;
waitForMock = function(mock) {
return jasmine.waitUntil(function() {
return mock.calls.count() === 1;
});
};
beforeEach(function() {
const model = new MetadataModel(videoIDEntry);
spyOn(TranscriptUtils.Storage, 'set');
this.view = new MetadataView.VideoID({model});
spyOn(Backbone, 'trigger');
expect(TranscriptUtils.Storage.set).toHaveBeenCalledWith('edx_video_id', this.view.getValueFromEditor());
});
it("triggers correct event on input change", function(done) {
// change value and trigger input event
this.view.$el.find('input').val("1234-5678-90").trigger('input');
waitForMock(Backbone.trigger)
.then(function() {
expect(Backbone.trigger).toHaveBeenCalledWith('transcripts:basicTabFieldChanged');
})
.always(done);
});
it("triggers correct event on clear", function(done) {
this.view.clear();
waitForMock(Backbone.trigger)
.then(function() {
expect(Backbone.trigger).toHaveBeenCalledWith('transcripts:basicTabFieldChanged');
})
.always(done);
});
it("constructs correct data", function() {
expect(
this.view.getData()
).toEqual(
[{mode: 'edx_video_id', type: 'edx_video_id', video: this.view.getValueFromEditor()}]
);
});
});
describe("MetadataView.Option is an option input type with clear functionality", function() {
beforeEach(function() {
const model = new MetadataModel(selectEntry);

View File

@@ -1,6 +1,7 @@
define(['jquery', 'underscore', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'js/spec_helpers/edit_helpers',
'js/views/modals/edit_xblock', 'js/models/xblock_info'],
function($, _, AjaxHelpers, EditHelpers, EditXBlockModal, XBlockInfo) {
define(['jquery', 'underscore', 'backbone', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'js/spec_helpers/edit_helpers', 'js/views/modals/edit_xblock', 'js/models/xblock_info'],
function($, _, Backbone, AjaxHelpers, EditHelpers, EditXBlockModal, XBlockInfo) {
'use strict';
describe('EditXBlockModal', function() {
var model, modal, showModal;
@@ -30,6 +31,7 @@ define(['jquery', 'underscore', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpe
beforeEach(function() {
EditHelpers.installMockXBlock();
spyOn(Backbone, 'trigger').and.callThrough();
});
afterEach(function() {
@@ -74,6 +76,7 @@ define(['jquery', 'underscore', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpe
modal.editorView.notifyRuntime('save', {state: 'end'});
expect(EditHelpers.isShowingModal(modal)).toBeFalsy();
expect(refreshed).toBeTruthy();
expect(Backbone.trigger).toHaveBeenCalledWith('xblock:editorModalHidden');
});
it('hides itself and does not refresh after cancel notification', function() {
@@ -86,6 +89,7 @@ define(['jquery', 'underscore', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpe
modal.editorView.notifyRuntime('cancel');
expect(EditHelpers.isShowingModal(modal)).toBeFalsy();
expect(refreshed).toBeFalsy();
expect(Backbone.trigger).toHaveBeenCalledWith('xblock:editorModalHidden');
});
describe('Custom Buttons', function() {

View File

@@ -1,9 +1,14 @@
define(["sinon", "js/models/uploads", "js/views/uploads", "js/models/chapter",
define(["underscore", "sinon", "js/models/uploads", "js/views/uploads", "js/models/chapter",
"edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "js/spec_helpers/modal_helpers"],
(sinon, FileUpload, UploadDialog, Chapter, AjaxHelpers, modal_helpers) =>
(_, sinon, FileUpload, UploadDialog, Chapter, AjaxHelpers, modal_helpers) =>
describe("UploadDialog", function() {
const tpl = readFixtures("upload-dialog.underscore");
const tpl = readFixtures("upload-dialog.underscore"),
uploadData = {
edx_video_id: '123-456-789-0',
language_code: 'en',
new_language_code: 'ur'
};
beforeEach(function() {
let dialogResponse;
@@ -27,7 +32,8 @@ define(["sinon", "js/models/uploads", "js/views/uploads", "js/models/chapter",
url: CMS.URL.UPLOAD_ASSET,
onSuccess: response => {
return test.dialogResponse.push(response.response);
}
},
uploadData: uploadData
});
spyOn(view, 'remove').and.callThrough();
@@ -37,6 +43,7 @@ define(["sinon", "js/models/uploads", "js/views/uploads", "js/models/chapter",
const jqMockFileInput = jasmine.createSpyObj('jqMockFileInput', ['get', 'replaceWith']);
jqMockFileInput.get.and.returnValue(mockFileInput);
const originalView$ = view.$;
spyOn($.fn, 'ajaxSubmit').and.callThrough();
spyOn(view, "$").and.callFake(function(selector) {
if (selector === "input[type=file]") {
return jqMockFileInput;
@@ -126,6 +133,9 @@ define(["sinon", "js/models/uploads", "js/views/uploads", "js/models/chapter",
view.upload();
expect(this.model.get("uploading")).toBeTruthy();
AjaxHelpers.expectRequest(requests, "POST", "/upload");
expect($.fn.ajaxSubmit.calls.mostRecent().args[0].data).toEqual(
_.extend({}, uploadData, {notifyOnError: false})
);
AjaxHelpers.respondWithJson(requests, { response: "dummy_response"});
expect(this.model.get("uploading")).toBeFalsy();
expect(this.model.get("finished")).toBeTruthy();

View File

@@ -1,16 +1,21 @@
define(
[
'backbone',
'js/views/baseview', 'underscore', 'js/models/metadata', 'js/views/abstract_editor',
'js/models/uploads', 'js/views/uploads',
'js/models/license', 'js/views/license',
'js/views/video/transcripts/utils',
'js/views/video/transcripts/metadata_videolist',
'js/views/video/translations_editor'
],
function(BaseView, _, MetadataModel, AbstractEditor, FileUpload, UploadDialog,
LicenseModel, LicenseView, VideoList, VideoTranslations) {
function(Backbone, BaseView, _, MetadataModel, AbstractEditor, FileUpload, UploadDialog,
LicenseModel, LicenseView, TranscriptUtils, VideoList, VideoTranslations) {
'use strict';
var Metadata = {};
Metadata.Editor = BaseView.extend({
// Store rendered view references
views: {},
// Model is CMS.Models.MetadataCollection,
initialize: function() {
@@ -42,10 +47,10 @@ function(BaseView, _, MetadataModel, AbstractEditor, FileUpload, UploadDialog,
}
if (_.isFunction(Metadata[type])) {
new Metadata[type](data);
self.views[data.model.getFieldName()] = new Metadata[type](data);
} else {
// Everything else is treated as GENERIC_TYPE, which uses String editor.
new Metadata.String(data);
self.views[data.model.getFieldName()] = new Metadata.String(data);
}
});
},
@@ -120,6 +125,40 @@ function(BaseView, _, MetadataModel, AbstractEditor, FileUpload, UploadDialog,
}
});
Metadata.VideoID = Metadata.String.extend({
// Delay between check_transcript requests
requestDelay: 300,
initialize: function() {
Metadata.String.prototype.initialize.apply(this, arguments);
this.$el.on(
'input',
'input',
_.debounce(_.bind(this.inputChange, this), this.requestDelay)
);
},
render: function() {
Metadata.String.prototype.render.apply(this, arguments);
TranscriptUtils.Storage.set('edx_video_id', this.getValueFromEditor());
},
clear: function() {
this.model.setValue('');
this.inputChange();
},
getData: function() {
return [{mode: 'edx_video_id', type: 'edx_video_id', video: this.getValueFromEditor()}];
},
inputChange: function() {
TranscriptUtils.Storage.set('edx_video_id', this.getValueFromEditor());
Backbone.trigger('transcripts:basicTabFieldChanged');
}
});
Metadata.Number = AbstractEditor.extend({
events: {

View File

@@ -3,9 +3,9 @@
* It is invoked using the edit method which is passed an existing rendered xblock,
* and upon save an optional refresh function can be invoked to update the display.
*/
define(['jquery', 'underscore', 'gettext', 'js/views/modals/base_modal', 'common/js/components/utils/view_utils',
'js/views/utils/xblock_utils', 'js/views/xblock_editor'],
function($, _, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockEditorView) {
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/views/modals/base_modal',
'common/js/components/utils/view_utils', 'js/views/utils/xblock_utils', 'js/views/xblock_editor'],
function($, _, Backbone, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockEditorView) {
'use strict';
var EditXBlockModal = BaseModal.extend({
@@ -181,6 +181,9 @@ define(['jquery', 'underscore', 'gettext', 'js/views/modals/base_modal', 'common
},
hide: function() {
// Notify child views to stop listening events
Backbone.trigger('xblock:editorModalHidden');
BaseModal.prototype.hide.call(this);
// Notify the runtime that the modal has been hidden

View File

@@ -12,7 +12,6 @@ function($, Backbone, _, Utils, MetadataView, MetadataCollection) {
initialize: function() {
// prepare data for MetadataView.Editor
var metadata = this.$el.data('metadata'),
models = this.toModels(metadata);
@@ -26,6 +25,20 @@ function($, Backbone, _, Utils, MetadataView, MetadataCollection) {
// Listen to edx_video_id update
this.listenTo(Backbone, 'transcripts:basicTabUpdateEdxVideoId', this.handleUpdateEdxVideoId);
// Listen to `video_url` and `edx_video_id` updates
this.listenTo(Backbone, 'transcripts:basicTabFieldChanged', this.handleFieldChanged);
// Listen to modal hidden event
this.listenTo(Backbone, 'xblock:editorModalHidden', this.destroy);
// Now `video_url` and `edx_video_id` viwes are rendered so
// send a `check_transcript` request to get transctip status
// This is needed because we need to update the transcrript status
// when basic tabs renders. We trigger `basicTabFieldChanged` event
// in `video_url` field but that event triggers before event is
// actually binded
this.handleFieldChanged();
},
/**
@@ -196,8 +209,38 @@ function($, Backbone, _, Utils, MetadataView, MetadataCollection) {
handleUpdateEdxVideoId: function(edxVideoId) {
var edxVideoIdField = Utils.getField(this.collection, 'edx_video_id');
edxVideoIdField.setValue(edxVideoId);
}
},
getLocator: function() {
return this.$el.closest('[data-locator]').data('locator');
},
/**
* Event handler for `transcripts:basicTabFieldChanged` event.
*/
handleFieldChanged: function() {
var views = this.settingsView.views,
videoURLSView = views.video_url,
edxVideoIdView = views.edx_video_id,
edxVideoIdData = edxVideoIdView.getData(),
videoURLsData = videoURLSView.getVideoObjectsList(),
data = videoURLsData.concat(edxVideoIdData),
locator = this.getLocator();
Utils.command('check', locator, data)
.done(function(response) {
videoURLSView.updateOnCheckTranscriptSuccess(videoURLsData, response);
})
.fail(function(response) {
videoURLSView.showServerError(response);
});
},
destroy: function() {
this.stopListening();
this.undelegateEvents();
this.$el.empty();
}
});
return Editor;

View File

@@ -3,7 +3,7 @@ define(
'jquery', 'backbone', 'underscore',
'js/views/video/transcripts/utils'
],
function($, Backbone, _, Utils) {
function($, Backbone, _, TranscriptUtils) {
var FileUploader = Backbone.View.extend({
invisibleClass: 'is-invisible',
@@ -57,6 +57,10 @@ function($, Backbone, _, Utils) {
*
*/
upload: function() {
var data = {
'edx_video_id': TranscriptUtils.Storage.get('edx_video_id') || ''
};
if (!this.file) {
return;
}
@@ -64,7 +68,8 @@ function($, Backbone, _, Utils) {
this.$form.ajaxSubmit({
beforeSend: this.xhrResetProgressBar,
uploadProgress: this.xhrProgressHandler,
complete: this.xhrCompleteHandler
complete: this.xhrCompleteHandler,
data: data
});
},

View File

@@ -43,7 +43,7 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager) {
.apply(this, arguments);
this.$el.on(
'input', 'input',
'input', '.videolist-settings-item input',
_.debounce(_.bind(this.inputHandler, this), this.inputDelay)
);
@@ -56,55 +56,45 @@ function($, Backbone, _, AbstractEditor, Utils, MessageManager) {
AbstractEditor.prototype.render
.apply(this, arguments);
var self = this,
component_locator = this.$el.closest('[data-locator]')
.data('locator'),
videoList = this.getVideoObjectsList(),
showServerError = function(response) {
var errorMessage = response.status ||
gettext('Error: Connection with server failed.');
self.messenger
.render('not_found')
.showError(
errorMessage,
true // hide buttons
);
};
this.$extraVideosBar = this.$el.find('.videolist-extra-videos');
if (videoList.length === 0) {
this.messenger
.render('not_found')
.showError(
gettext('No sources'),
true // hide buttons
);
// Check current state of Timed Transcripts.
Backbone.trigger('transcripts:basicTabFieldChanged');
},
return void(0);
updateOnCheckTranscriptSuccess: function(videoList, response) {
var params = response,
len = videoList.length,
mode = (len === 1) ? videoList[0].mode : false;
// If there are more than 1 video or just html5 source is
// passed, video sources box should expand
if (len > 1 || mode === 'html5') {
this.openExtraVideosBar();
} else {
this.closeExtraVideosBar();
}
// Check current state of Timed Transcripts.
Utils.command('check', component_locator, videoList)
.done(function(resp) {
var params = resp,
len = videoList.length,
mode = (len === 1) ? videoList[0].mode : false;
this.messenger.render(response.command, params);
this.checkIsUniqVideoTypes();
},
// If there are more than 1 video or just html5 source is
// passed, video sources box should expand
if (len > 1 || mode === 'html5') {
self.openExtraVideosBar();
} else {
self.closeExtraVideosBar();
}
/**
* Updates the message with error.
*/
showServerError: function(response) {
var errorMessage = gettext('Error: Connection with server failed.');
self.messenger.render(resp.command, params);
self.checkIsUniqVideoTypes();
})
.fail(showServerError);
if (response.responseJSON !== undefined) {
errorMessage = response.responseJSON.status;
}
this.messenger
.render('not_found')
.showError(
errorMessage,
true // hide buttons
);
},
/**

View File

@@ -27,6 +27,8 @@ function($, _, HtmlUtils, TranscriptUtils, AbstractEditor, ViewUtils, FileUpload
templateName: 'metadata-translations-entry',
templateItemName: 'metadata-translations-item',
validFileFormats: ['srt'],
initialize: function() {
var templateName = _.result(this, 'templateItemName'),
tpl = document.getElementById(templateName).text,
@@ -38,11 +40,12 @@ function($, _, HtmlUtils, TranscriptUtils, AbstractEditor, ViewUtils, FileUpload
this.templateItem = _.template(tpl);
// Initialize language map. Keys in this map represent language codes present on
// server. Values will change if user changes the language from a dropdown.
// For example: Initially map will look like {'ar': 'ar', 'zh': 'zh'} and corresponding
// dropdowns will show language names `Arabic` and `Chinese`. If user changes `Chinese`
// to `Russian` then map will become {'ar': 'ar', 'zh': 'ru'}
// Initialize language map. This maps original language to the newly selected language.
// Keys in this map represent language codes present on server, they don't change when
// user selects a language while values represent currently selected language.
// Initially, the map will look like {'ar': 'ar', 'zh': 'zh'} i.e {'original_lang': 'original_lang'}
// and corresponding dropdowns will show language names Arabic and Chinese. If user changes
// Chinese to Russian then map will become {'ar': 'ar', 'zh': 'ru'} i.e {'original_lang': 'new_lang'}
_.each(this.model.getDisplayValue(), function(value, lang) {
languageMap[lang] = lang;
});
@@ -216,7 +219,7 @@ function($, _, HtmlUtils, TranscriptUtils, AbstractEditor, ViewUtils, FileUpload
fileUploadModel = new FileUpload({
title: gettext('Upload translation'),
fileFormats: ['srt']
fileFormats: this.validFileFormats
});
videoUploadDialog = new VideoUploadDialog({

View File

@@ -614,6 +614,12 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
editable_fields['transcripts']['languages'] = languages
editable_fields['transcripts']['type'] = 'VideoTranslations'
# We need to send ajax requests to show transcript status
# whenever edx_video_id changes on frontend. Thats why we
# are changing type to `VideoID` so that a specific
# Backbonjs view can handle it.
editable_fields['edx_video_id']['type'] = 'VideoID'
# construct transcripts info and also find if `en` subs exist
transcripts_info = self.get_transcripts_info()
possible_sub_ids = [self.sub, self.youtube_id_1_0] + get_html5_ids(self.html5_sources)