Transcript file upload - EDUCATOR-2029

This commit is contained in:
Mushtaq Ali
2017-12-29 18:02:33 +05:00
parent 4f422e37d9
commit 00a86ccef8
5 changed files with 385 additions and 30 deletions

View File

@@ -1,12 +1,15 @@
define(
['jquery', 'underscore', 'backbone', 'js/views/video_transcripts', 'js/views/previous_video_upload_list',
'common/js/spec_helpers/template_helpers'],
function($, _, Backbone, VideoTranscriptsView, PreviousVideoUploadListView, TemplateHelpers) {
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/js/spec_helpers/template_helpers'],
function($, _, Backbone, VideoTranscriptsView, PreviousVideoUploadListView, AjaxHelpers, TemplateHelpers) {
'use strict';
describe('VideoTranscriptsView', function() {
var videoTranscriptsView,
renderView,
verifyTranscriptActions,
verifyTranscriptStateInfo,
verifyMessage,
verifyDetailedErrorMessage,
createFakeTranscriptFile,
transcripts = {
en: 'English',
es: 'Spanish',
@@ -23,17 +26,28 @@ define(
},
TRANSCRIPT_DOWNLOAD_FILE_FORMAT = 'srt',
TRANSCRIPT_DOWNLOAD_URL = 'abc.com/transcript_download/course_id',
TRANSCRIPT_UPLOAD_URL = 'abc.com/transcript_upload/course_id',
videoSupportedFileFormats = ['.mov', '.mp4'],
videoTranscriptSettings = {
trancript_download_file_format: TRANSCRIPT_DOWNLOAD_FILE_FORMAT,
transcript_download_handler_url: TRANSCRIPT_DOWNLOAD_URL
transcript_download_handler_url: TRANSCRIPT_DOWNLOAD_URL,
transcript_upload_handler_url: TRANSCRIPT_UPLOAD_URL
},
videoListView;
verifyTranscriptActions = function($transcriptActionsEl, transcriptLanguage) {
var downloadTranscriptActionEl = $transcriptActionsEl.find('.download-transcript-button'),
verifyTranscriptStateInfo = function($transcriptEl, transcriptLanguage) {
var $transcriptActionsEl = $transcriptEl.find('.transcript-actions'),
downloadTranscriptActionEl = $transcriptActionsEl.find('.download-transcript-button'),
uploadTranscriptActionEl = $transcriptActionsEl.find('.upload-transcript-button');
// Verify transcript data attributes
expect($transcriptEl.data('edx-video-id')).toEqual(edxVideoID);
expect($transcriptEl.data('language-code')).toEqual(transcriptLanguage);
// Verify transcript language dropdown has correct value set.
expect($transcriptEl.find('.transcript-language-menu').val(), transcriptLanguage);
// Verify transcript actions
expect(downloadTranscriptActionEl.html().trim(), 'Download');
expect(
downloadTranscriptActionEl.attr('href'),
@@ -41,8 +55,33 @@ define(
);
expect(uploadTranscriptActionEl.html().trim(), 'Upload');
expect(uploadTranscriptActionEl.data('edx-video-id'), edxVideoID);
expect(uploadTranscriptActionEl.data('language-code'), transcriptLanguage);
};
verifyMessage = function($transcriptEl, status) {
var $transcriptStatusEl = $transcriptEl.find('.transcript-upload-status-container'),
statusData = videoTranscriptsView.transcriptUploadStatuses[status];
expect($transcriptStatusEl.hasClass(statusData.statusClass)).toEqual(true);
expect($transcriptStatusEl.find('span.fa').hasClass(statusData.iconClasses)).toEqual(true);
expect(
$transcriptStatusEl.find('.more-details-action').hasClass('hidden')
).toEqual(statusData.hiddenClass === 'hidden');
expect(
$transcriptStatusEl.find('.transcript-detail-status').html().trim()
).toEqual(statusData.shortMessage);
};
verifyDetailedErrorMessage = function($transcriptEl, expectedTitle, expectedMessage) {
$transcriptEl.find('.more-details-action').click();
expect($('#prompt-warning-title').text().trim()).toEqual(expectedTitle);
expect($('#prompt-warning-description').text().trim()).toEqual(expectedMessage);
};
createFakeTranscriptFile = function(transcriptFileName) {
var transcriptFileName = transcriptFileName || 'test-transcript.srt', // eslint-disable-line no-redeclare, max-len
size = 100,
type = '';
return new File([new Blob([Array(size).join('i')], {type: type})], transcriptFileName);
};
renderView = function(availableTranscripts, isVideoTranscriptEnabled) {
@@ -71,7 +110,10 @@ define(
};
beforeEach(function() {
setFixtures('<section class="wrapper-assets"></section>');
setFixtures(
'<div id="page-prompt"></div>' +
'<section class="wrapper-assets"></section>'
);
TemplateHelpers.installTemplate('previous-video-upload-list');
renderView(transcripts);
});
@@ -177,18 +219,133 @@ define(
);
_.each(transcripts, function(langaugeText, languageCode) {
$transcriptEl = $(videoTranscriptsView.$el.find('#show-video-transcript-content-' + languageCode));
$transcriptEl = videoTranscriptsView.$el.find('.show-video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
// Verify correct transcript title is set.
expect($transcriptEl.find('.transcript-title').html()).toEqual(
'Video client title n_' + languageCode + '.' + TRANSCRIPT_DOWNLOAD_FILE_FORMAT
);
// Verify transcript language dropdown has correct value set.
expect($transcriptEl.find('.transcript-language-menu').val(), languageCode);
// Verify transcript actions are rendered correctly.
verifyTranscriptActions($transcriptEl.find('.transcript-actions'), languageCode);
// Verify transcript is rendered with correct info.
verifyTranscriptStateInfo($transcriptEl, languageCode);
});
});
it('can upload transcript', function() {
var languageCode = 'en',
newLanguageCode = 'ar',
requests = AjaxHelpers.requests(this),
$transcriptEl = videoTranscriptsView.$el.find('.show-video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
// Verify correct transcript title is set.
expect($transcriptEl.find('.transcript-title').html()).toEqual(
'Video client title n_' + languageCode + '.' + TRANSCRIPT_DOWNLOAD_FILE_FORMAT
);
// Select a language
$transcriptEl.find('.transcript-language-menu').val(newLanguageCode);
$transcriptEl.find('.upload-transcript-button').click();
// Add transcript to upload queue and send POST request to upload transcript.
$transcriptEl.find('.upload-transcript-input').fileupload('add', {files: [createFakeTranscriptFile()]});
// Verify if POST request received for image upload
AjaxHelpers.expectRequest(
requests,
'POST',
TRANSCRIPT_UPLOAD_URL
);
// Send successful upload response
AjaxHelpers.respondWithJson(requests, {});
// Verify correct transcript title is set.
expect($transcriptEl.find('.transcript-title').html()).toEqual(
'Video client title n_' + newLanguageCode + '.' + TRANSCRIPT_DOWNLOAD_FILE_FORMAT
);
verifyMessage($transcriptEl, 'uploaded');
// Verify transcript is rendered with correct info.
verifyTranscriptStateInfo($transcriptEl, newLanguageCode);
});
it('shows error state correctly', function() {
var languageCode = 'en',
requests = AjaxHelpers.requests(this),
errorMessage = 'Transcript failed error message',
$transcriptEl = videoTranscriptsView.$el.find('.show-video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
$transcriptEl.find('.upload-transcript-button').click();
// Add transcript to upload queue and send POST request to upload transcript.
$transcriptEl.find('.upload-transcript-input').fileupload('add', {files: [createFakeTranscriptFile()]});
// Server response with bad request.
AjaxHelpers.respondWithError(requests, 400, {error: errorMessage});
verifyMessage($transcriptEl, 'failed');
// verify detailed error message
verifyDetailedErrorMessage(
$transcriptEl,
videoTranscriptsView.defaultFailureTitle,
errorMessage
);
// Verify transcript is rendered with correct info.
verifyTranscriptStateInfo($transcriptEl, languageCode);
});
it('should show error message in case of server error', function() {
var languageCode = 'en',
requests = AjaxHelpers.requests(this),
$transcriptEl = videoTranscriptsView.$el.find('.show-video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
$transcriptEl.find('.upload-transcript-button').click();
// Add transcript to upload queue and send POST request to upload transcript.
$transcriptEl.find('.upload-transcript-input').fileupload('add', {files: [createFakeTranscriptFile()]});
AjaxHelpers.respondWithError(requests, 500);
verifyMessage($transcriptEl, 'failed');
// verify detailed error message
verifyDetailedErrorMessage(
$transcriptEl,
videoTranscriptsView.defaultFailureTitle,
videoTranscriptsView.defaultFailureMessage
);
// Verify transcript is rendered with correct info.
verifyTranscriptStateInfo($transcriptEl, languageCode);
});
it('should show error message in case of unsupported transcript file format', function() {
var languageCode = 'en',
transcriptFileName = 'unsupported-transcript-file-format.txt',
errorMessage = transcriptFileName + ' is not in a supported file format. Supported file format is ' + TRANSCRIPT_DOWNLOAD_FILE_FORMAT + '.', // eslint-disable-line max-len
$transcriptEl = videoTranscriptsView.$el.find('.show-video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
$transcriptEl.find('.upload-transcript-button').click();
// Add transcript to upload queue and send POST request to upload transcript.
$transcriptEl.find('.upload-transcript-input').fileupload('add', {
files: [createFakeTranscriptFile(transcriptFileName)]
});
verifyMessage($transcriptEl, 'validationFailed');
// verify detailed error message
verifyDetailedErrorMessage(
$transcriptEl,
videoTranscriptsView.defaultFailureTitle,
errorMessage
);
// Verify transcript is rendered with correct info.
verifyTranscriptStateInfo($transcriptEl, languageCode);
});
});
}
);

View File

@@ -1,14 +1,18 @@
define(
['underscore', 'gettext', 'js/views/baseview', 'edx-ui-toolkit/js/utils/html-utils',
'edx-ui-toolkit/js/utils/string-utils', 'text!templates/video-transcripts.underscore'],
function(_, gettext, BaseView, HtmlUtils, StringUtils, videoTranscriptsTemplate) {
['underscore', 'gettext', 'js/views/baseview', 'common/js/components/views/feedback_prompt',
'edx-ui-toolkit/js/utils/html-utils', 'edx-ui-toolkit/js/utils/string-utils',
'text!templates/video-transcripts.underscore', 'text!templates/video-transcript-upload-status.underscore'],
function(_, gettext, BaseView, PromptView, HtmlUtils, StringUtils, videoTranscriptsTemplate,
videoTranscriptUploadStatusTemplate) {
'use strict';
var VideoTranscriptsView = BaseView.extend({
tagName: 'div',
events: {
'click .toggle-show-transcripts-button': 'toggleShowTranscripts'
'click .toggle-show-transcripts-button': 'toggleShowTranscripts',
'click .upload-transcript-button': 'chooseFile',
'click .more-details-action': 'showUploadFailureMessage'
},
initialize: function(options) {
@@ -19,6 +23,40 @@ define(
this.videoSupportedFileFormats = options.videoSupportedFileFormats;
this.videoTranscriptSettings = options.videoTranscriptSettings;
this.template = HtmlUtils.template(videoTranscriptsTemplate);
this.transcriptUploadStatusTemplate = HtmlUtils.template(videoTranscriptUploadStatusTemplate);
this.defaultFailureTitle = gettext('Your file could not be uploaded');
this.defaultFailureMessage = gettext('This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.'); // eslint-disable-line max-len
this.transcriptUploadStatuses = {
uploaded: {
statusClass: 'success',
iconClasses: 'fa-check',
shortMessage: 'Transcript uploaded.',
hiddenClass: 'hidden'
},
uploading: {
statusClass: '',
iconClasses: 'fa-spinner fa-pulse',
shortMessage: 'Uploading transcript',
hiddenClass: 'hidden'
},
failed: {
statusClass: 'error',
iconClasses: 'fa-warning',
shortMessage: 'Upload failed.',
hiddenClass: ''
},
validationFailed: {
statusClass: 'error',
iconClasses: 'fa-warning',
shortMessage: 'Validation failed.',
hiddenClass: ''
}
};
// This is needed to attach transcript methods to this object while uploading.
_.bindAll(
this, 'render', 'chooseFile', 'transcriptSelected', 'transcriptUploadSucceeded',
'transcriptUploadFailed'
);
},
/*
@@ -40,7 +78,7 @@ define(
var clientTitle = this.clientVideoID;
// Remove video file extension for transcript title.
_.each(this.videoSupportedFileFormats, function(videoFormat) {
clientTitle.replace(videoFormat, '');
clientTitle = clientTitle.replace(videoFormat, '');
});
return clientTitle.substring(0, 20);
},
@@ -74,6 +112,140 @@ define(
}
},
validateTranscriptUpload: function(file) {
var errorMessage = '',
fileName = file.name,
fileType = fileName.substr(fileName.lastIndexOf('.') + 1);
if (fileType !== this.videoTranscriptSettings.trancript_download_file_format) {
errorMessage = gettext(
'{filename} is not in a supported file format. ' +
'Supported file format is {supportedFileFormat}.'
)
.replace('{filename}', fileName)
.replace('{supportedFileFormat}', this.videoTranscriptSettings.trancript_download_file_format);
}
return errorMessage;
},
chooseFile: function(event) {
var $transcriptContainer = $(event.target).parents('.show-video-transcript-content'),
$transcriptUploadEl = $transcriptContainer.find('.upload-transcript-input');
$transcriptUploadEl.fileupload({
url: this.videoTranscriptSettings.transcript_upload_handler_url,
add: this.transcriptSelected,
done: this.transcriptUploadSucceeded,
fail: this.transcriptUploadFailed,
formData: {
edx_video_id: this.edxVideoID,
language_code: $transcriptContainer.attr('data-language-code'),
new_language_code: $transcriptContainer.find('.transcript-language-menu').val()
}
});
$transcriptUploadEl.click();
},
transcriptSelected: function(event, data) {
var errorMessage,
$transcriptContainer = $(event.target).parents('.show-video-transcript-content');
errorMessage = this.validateTranscriptUpload(data.files[0]);
if (!errorMessage) {
// Do not trigger global AJAX error handler
data.global = false; // eslint-disable-line no-param-reassign
data.submit();
this.renderMessage($transcriptContainer, 'uploading');
} else {
// Reset transcript language back to original.
$transcriptContainer.find('.transcript-language-menu').val($transcriptContainer.attr('data-language-code')); // eslint-disable-line max-len
this.renderMessage($transcriptContainer, 'validationFailed', errorMessage);
}
},
transcriptUploadSucceeded: function(event, data) {
var languageCode = data.formData.language_code,
newLanguageCode = data.formData.new_language_code,
$transcriptContainer = this.$el.find('.show-video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
$transcriptContainer.attr('data-language-code', newLanguageCode);
HtmlUtils.setHtml(
$transcriptContainer.find('.transcript-title'),
StringUtils.interpolate(gettext('{transcriptClientTitle}_{transcriptLanguageCode}.{fileExtension}'),
{
transcriptClientTitle: this.getTranscriptClientTitle(),
transcriptLanguageCode: newLanguageCode,
fileExtension: this.videoTranscriptSettings.trancript_download_file_format
}
)
);
this.renderMessage($transcriptContainer, 'uploaded');
},
transcriptUploadFailed: function(event, data) {
var errorMessage,
languageCode = data.formData.language_code,
$transcriptContainer = this.$el.find('.show-video-transcript-content[data-language-code="' + languageCode + '"]'); // eslint-disable-line max-len
try {
errorMessage = JSON.parse(data.jqXHR.responseText).error;
errorMessage = errorMessage || this.defaultFailureMessage;
} catch (error) {
errorMessage = this.defaultFailureMessage;
}
// Reset transcript language back to original.
$transcriptContainer.find('.transcript-language-menu').val(languageCode);
this.renderMessage($transcriptContainer, 'failed', errorMessage);
},
clearMessage: function() {
var $transcriptStatusesEl = this.$el.find('.transcript-upload-status-container');
// Clear all message containers
HtmlUtils.setHtml($transcriptStatusesEl, '');
$transcriptStatusesEl.removeClass('success error');
},
renderMessage: function($transcriptContainer, status, errorMessage) {
var statusData = this.transcriptUploadStatuses[status],
$transcriptStatusEl = $transcriptContainer.find('.transcript-upload-status-container');
// If a messge is already present above the video transcript element, remove it.
this.clearMessage();
HtmlUtils.setHtml(
$transcriptStatusEl,
this.transcriptUploadStatusTemplate({
status: statusData.statusClass,
iconClasses: statusData.iconClasses,
shortMessage: gettext(statusData.shortMessage),
errorMessage: errorMessage || '',
hiddenClass: statusData.hiddenClass
})
);
$transcriptStatusEl.addClass(statusData.statusClass);
},
showUploadFailureMessage: function(event) {
var errorMessage = $(event.target).data('error-message');
return new PromptView.Warning({
title: this.defaultFailureTitle,
message: errorMessage,
actions: {
primary: {
text: gettext('Close'),
click: function(prompt) {
return prompt.hide();
}
}
}
}).show();
},
/*
Renders transcripts view.
*/
@@ -85,7 +257,7 @@ define(
transcriptAvailableLanguages: this.sortByValue(this.transcriptAvailableLanguages),
edxVideoID: this.edxVideoID,
transcriptClientTitle: this.getTranscriptClientTitle(),
transcriptDownloadFileFormat: this.videoTranscriptSettings.trancript_download_file_format,
transcriptFileFormat: this.videoTranscriptSettings.trancript_download_file_format,
transcriptDownloadHandlerUrl: this.videoTranscriptSettings.transcript_download_handler_url
})
);

View File

@@ -8,7 +8,7 @@
@extend %t-copy;
vertical-align: bottom;
margin-right: ($baseline/5);
@include margin-right($baseline/45);
}
}
@@ -33,12 +33,30 @@
}
}
.show-video-transcripts-wrapper.hidden {
.hidden {
display: none;
}
.show-video-transcript-content {
margin-top: ($baseline/2);
.transcript-upload-status-container {
.video-transcript-detail-status, .more-details-action {
@include font-size(12);
@include line-height(12);
@include margin-left($baseline/4);
}
}
.transcript-upload-status-container.error {
color: $color-error;
}
.transcript-upload-status-container.success {
color: $color-ready;
}
.transcript-language-menu {
display: block;
width: 200px;

View File

@@ -0,0 +1,5 @@
<span class="fa <%- iconClasses %>" aria-hidden="true"></span>
<span class='transcript-detail-status'><%- shortMessage %></span>
<button class='button-link more-details-action <%- hiddenClass %>' data-error-message='<%- errorMessage %>'>
<%- gettext('Read More') %>
</button>

View File

@@ -13,14 +13,19 @@
<% }%>
<div class='show-video-transcripts-wrapper hidden'>
<% _.each(transcripts, function(transcriptLanguageText, transcriptLanguageCode){ %>
<div id='show-video-transcript-content-<%- transcriptLanguageCode %>' class='show-video-transcript-content'>
<strong class='transcript-title'><%- StringUtils.interpolate(gettext('{transcriptClientTitle}_{transcriptLanguageCode}.{fileExtension}'), {transcriptClientTitle: transcriptClientTitle, transcriptLanguageCode: transcriptLanguageCode, fileExtension: transcriptDownloadFileFormat}) %></strong>
<select id='transcript-language-<%- transcriptLanguageCode %>' class='transcript-language-menu'>
<% selectedLanguageCodes = _.keys(_.omit(transcripts, transcriptLanguageCode)); %>
<div class='show-video-transcript-content' data-edx-video-id="<%- edxVideoID %>" data-language-code="<%- transcriptLanguageCode %>">
<div class='transcript-upload-status-container'></div>
<strong class='transcript-title'><%- StringUtils.interpolate(gettext('{transcriptClientTitle}_{transcriptLanguageCode}.{fileExtension}'), {transcriptClientTitle: transcriptClientTitle, transcriptLanguageCode: transcriptLanguageCode, fileExtension: transcriptFileFormat}) %></strong>
<select class='transcript-language-menu'>
<option value=''>Select Language</option>
<% _.each(transcriptAvailableLanguages, function(availableLanguage){ %>
<option value='<%- availableLanguage[1] %>' <%- transcriptLanguageCode === availableLanguage[1] ? 'selected': '' %>><%- availableLanguage[0] %></option>
<% if (!_.contains(selectedLanguageCodes, availableLanguage[1])) { %>
<option value='<%- availableLanguage[1] %>' <%- transcriptLanguageCode === availableLanguage[1] ? 'selected': '' %>><%- availableLanguage[0] %></option>
<% } %>
<% }) %>
</select>
<input class="upload-transcript-input hidden" type="file" name="file" accept=".<%- transcriptFileFormat %>"/>
<div class='transcript-actions'>
<a
class="button-link download-transcript-button"
@@ -36,9 +41,7 @@
<%- gettext('Download') %>
</a>
<span class='transcript-actions-separator'> | </span>
<button class="button-link upload-transcript-button" data-edx-video-id="<%- edxVideoID %>" data-language-code="<%- transcriptLanguageCode %>">
<%- gettext('Upload') %>
</button>
<button class="button-link upload-transcript-button"><%- gettext('Upload') %></button>
</div>
</div>
<% }) %>