feat: add support for user feedback on autogenerated transcripts (#33518)

* feat: WIP transcript feedback

* feat: Add UI mock for Transcript Feedbacks (#33416)

* feat: Add UI mock for Transcript Feedbacks

* fix: Fix mongo tests

* feat: Get video_uuid, user_uuid and language for request (#33445)

* feat: make call to ai-translations to obtain feedback

* feat: Show widget if transcript was AI generated

* feat: bind all class methods

* fix: async calls

* feat: send request when choosing feedback

* feat: update showing condition (#33474)

* fix: ajax success lint

* fix: video caption specs errors fixed

* feat: add coverage to feedback widget

* chore: connect XT to LMS and CMS

* feat: use url

* chore: add vars to devstack

* chore: fix url name

* feat: update unit tests regarding env vars

* fix: fix test_video_mongo

* feat: add more tests

* feat: remove console log

Co-authored-by: Jesper Hodge <19345795+jesperhodge@users.noreply.github.com>

* fix: rename shouldShowWidget to loadAndSetVisibility

---------

Co-authored-by: María Guillermina Véscovo <mvescovo@2u.com>
Co-authored-by: Jesper Hodge <19345795+jesperhodge@users.noreply.github.com>
This commit is contained in:
Rodrigo Martin
2023-11-06 13:33:53 -03:00
committed by GitHub
parent 2cf4d73a98
commit e51c01bf4e
20 changed files with 888 additions and 156 deletions

View File

@@ -33,48 +33,49 @@
<div class="focus_grabber last"></div>
<h3 class="hd hd-4 downloads-heading sr" id="video-download-transcripts">Downloads and transcripts</h3>
<div class="wrapper-downloads" role="region" aria-labelledby="video-download-transcripts">
<div class="wrapper-download-video">
<h4 class="hd hd-5">Video</h4>
<div class="wrapper-social-share" data-url="video-share-url">
<span class="icon fa fa-share-alt" aria-hidden="true"></span>
Share on:
<a class="btn-link social-share-link" data-source="twitter" href="#">
<div class="wrapper-video-bottom-section">
<h3 class="hd hd-4 downloads-heading sr" id="video-download-transcripts">Downloads and transcripts</h3>
<div class="wrapper-downloads" role="region" aria-labelledby="video-download-transcripts">
<div class="wrapper-download-video">
<h4 class="hd hd-5">Video</h4>
<div class="wrapper-social-share" data-url="video-share-url">
<span class="icon fa fa-share-alt" aria-hidden="true"></span>
Share on:
<a class="btn-link social-share-link" data-source="twitter" href="#">
<span class="icon fa fa-linkedin-square" aria-hidden="true"></span>
<span class="sr">Share on twitter</span>
</a>
<a class="btn-link social-share-link" data-source="facebook" href="#">
<span class="icon fa fa-linkedin-square" aria-hidden="true"></span>
<span class="sr">Share on twitter</span>
</a>
<a class="btn-link social-share-link" data-source="facebook" href="#">
<span class="icon fa fa-linkedin-square" aria-hidden="true"></span>
<span class="sr">Share on facebook</span>
</a>
<a class="btn-link social-share-link" data-source="linkedin" href="#">
<span class="icon fa fa-linkedin-square" aria-hidden="true"></span>
<span class="sr">Share on linkedin</span>
<span class="sr">Share on facebook</span>
</a>
<a class="btn-link social-share-link" data-source="linkedin" href="#">
<span class="icon fa fa-linkedin-square" aria-hidden="true"></span>
<span class="sr">Share on linkedin</span>
</a>
</div>
<a class="btn-link video-sources video-download-button" href="#">
Download video file
</a>
</div>
<a class="btn-link video-sources video-download-button" href="#">
Download video file
</a>
</div>
<div class="wrapper-download-transcripts">
<h4 class="hd hd-5">Transcripts</h4>
<ul class="list-download-transcripts">
<li class="transcript-option">
<a href="#" class="btn btn-link" data-href="txt">Download Text (.txt) file</a>
</li>
<li class="transcript-option">
<a href="#" class="btn btn-link" data-href="srt">Download SubRip (.srt) file</a>
</li>
</ul>
<a href="#" class="external-track">Download transcript</a>
</div>
<div class="wrapper-handouts">
<h4 class="hd hd-5">Handouts</h4>
<a href="#">Download Handout</a>
<div class="wrapper-download-transcripts">
<h4 class="hd hd-5">Transcripts</h4>
<ul class="list-download-transcripts">
<li class="transcript-option">
<a href="#" class="btn btn-link" data-href="txt">Download Text (.txt) file</a>
</li>
<li class="transcript-option">
<a href="#" class="btn btn-link" data-href="srt">Download SubRip (.srt) file</a>
</li>
</ul>
<a href="#" class="external-track">Download transcript</a>
</div>
<div class="wrapper-handouts">
<h4 class="hd hd-5">Handouts</h4>
<a href="#">Download Handout</a>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,57 @@
<div class="course-content">
<div class="vert-mod">
<div id="video_example">
<div id="example">
<div
id="video_id"
class="video closed vert vert-video_id"
data-block-id="block-v1:coursekey+type@video+block@000000000000000000"
data-course-id="course-v1:someOrg+thisCOurse+runAway"
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": ["/base/fixtures/test.mp4","/base/fixtures/test.webm","/base/fixtures/test.ogv"], "speed": "1.5", "startTime": "", "streams": "", "sub": "Z5KLxerq05Y", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "/base/fixtures/youtube_iframe_api.js", "ytImageUrl": "", "ytTestTimeout": "1500", "ytMetadataUrl": "www.googleapis.com/youtube/v3/videos/"}'
>
<div class="focus_grabber first"></div>
<div class="tc-wrapper">
<article class="video-wrapper">
<span tabindex="0" class="spinner" aria-hidden="false" aria-label="Loading video player"></span>
<span tabindex="-1" class="btn-play fa fa-youtube-play fa-2x is-hidden" aria-hidden="true" aria-label="Play video"></span>
<div class="video-player-pre"></div>
<section class="video-player">
<div id="id"></div>
</section>
<div class="video-player-post"></div>
<div class="closed-captions"></div>
<section class="video-controls is-hidden">
<div class="slider"></div>
<div>
<div class="vcr"><div class="vidtime">0:00 / 0:00</div></div>
<div class="secondary-controls"></div>
</div>
</section>
</article>
</div>
<div class="focus_grabber last"></div>
<div class="wrapper-video-bottom-section">
<div class="wrapper-transcript-feedback" data-video-id='365b710a-6dd6-11ee-b962-0242ac120002' data-user-id='1'>
<h4 class="hd hd-5">How is the transcript quality ?</h4>
<div class="transcript-feedback-buttons">
<div class="transcript-feedback-btn-wrapper">
<button class="thumbs-up-btn">
<span class="icon fa fa-thumbs-o-up thumbs-up-icon"></span>
</button>
</div>
<div class="transcript-feedback-btn-wrapper">
<button class="thumbs-down-btn">
<span class="icon fa fa-thumbs-o-down thumbs-down-icon"></span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -46,6 +46,7 @@ import './spec/video/initialize_spec.js';
import './spec/video/iterator_spec.js';
import './spec/video/resizer_spec.js';
import './spec/video/sjson_spec.js';
import './spec/video/social_share_spec.js';
import './spec/video/video_autoadvance_spec.js';
import './spec/video/video_bumper_spec.js';
import './spec/video/video_caption_spec.js';
@@ -66,9 +67,9 @@ import './spec/video/video_save_state_plugin_spec.js';
import './spec/video/video_skip_control_spec.js';
import './spec/video/video_speed_control_spec.js';
import './spec/video/video_storage_spec.js';
import './spec/video/video_transcript_feedback_spec.js';
import './spec/video/video_volume_control_spec.js';
import './spec/time_spec.js';
import './spec/video/social_share_spec.js';
// overwrite the loaded method and manually start the karma after a delay
// Somehow the code initialized in jQuery's onready doesn't get called before karma auto starts

View File

@@ -180,6 +180,38 @@
return {};
} else if (settings.url === '/save_user_state') {
return {success: true};
} else if (settings.url.match(/.+video-transcript.+$/)) {
if (settings.url.match(/.+&video_uuid=notAIGenerated/)) {
return settings.success(null);
}
if (settings.url.match(/.+&video_uuid=inProgress/)) {
return settings.success({
status: 'In Progress'
});
}
if (settings.url.match(/.+&video_uuid=error/)) {
return settings.error();
}
return settings.success({
status: 'Completed'
});
} else if (settings.url.match(/.+transcript-feedback.+$/) && settings.type === 'GET') {
if (settings.url.match(/.+&video_uuid=error.+$/)) {
return settings.error();
}
if (settings.url.match(/.+&video_uuid=negative.+$/)) {
return settings.success({
value: false
});
}
if (settings.url.match(/.+&video_uuid=none.+$/)) {
return settings.success(null);
}
return settings.success({
value: true
});
} else if (settings.url.match(/.+transcript-feedback.+$/) && settings.type === 'POST') {
return settings.success(settings.data.value !== null ? { value: settings.data.value } : null);
} else if (settings.url.match(new RegExp(jasmine.getFixtures().fixturesPath + '.+', 'g'))) {
return origAjax(settings);
} else {

View File

@@ -833,7 +833,7 @@
});
});
msg = 'on succes: language menu is rendered if translations available';
msg = 'on success: language menu is rendered if translations available';
it(msg, function() {
state.config.transcriptLanguages = {
en: 'English',
@@ -853,7 +853,7 @@
});
});
msg = 'on succes: language menu isn\'t rendered if translations unavailable';
msg = 'on success: language menu isn\'t rendered if translations unavailable';
it(msg, function() {
state.config.transcriptLanguages = {
en: 'English',

View File

@@ -0,0 +1,271 @@
(function() {
// eslint-disable-next-line lines-around-directive
'use strict';
describe('VideoTranscriptFeedback', function() {
var state;
var videoId = "365b710a-6dd6-11ee-b962-0242ac120002";
var userId = 1;
var currentLanguage = "en";
var getAITranscriptUrl = '/video-transcript' + '?transcript_language=' + currentLanguage + '&video_uuid=' + videoId;
var getTranscriptFeedbackUrl = '/transcript-feedback' + '?transcript_language=' + currentLanguage + '&video_uuid=' + videoId + '&user_id=' + userId;
var sendTranscriptFeedbackUrl = '/transcript-feedback/';
beforeEach(function() {
state = jasmine.initializePlayer('video_transcript_feedback.html');
});
afterEach(function() {
$('source').remove();
state.storage.clear();
if (state.videoPlayer) {
state.videoPlayer.destroy();
}
});
describe('initialize', function() {
it('instantiates widget and handlers along with necessary data', function() {
spyOn(state.videoTranscriptFeedback, 'loadAndSetVisibility').and.callFake(function() {
return true;
});
spyOn(state.videoTranscriptFeedback, 'bindHandlers').and.callFake(function() {
return true;
});
state.videoTranscriptFeedback.initialize();
expect(state.videoTranscriptFeedback.videoId).toEqual(videoId);
expect(state.videoTranscriptFeedback.userId).toEqual(userId);
expect(state.videoTranscriptFeedback.currentTranscriptLanguage).toEqual(currentLanguage);
expect(state.videoTranscriptFeedback.loadAndSetVisibility).toHaveBeenCalled();
expect(state.videoTranscriptFeedback.bindHandlers).toHaveBeenCalled();
});
});
describe('should show widget', function() {
it('checks if transcript was AI generated', function() {
spyOn(state.videoTranscriptFeedback, 'loadAndSetVisibility').and.callThrough();
state.videoTranscriptFeedback.loadAndSetVisibility();
var getAITranscriptCall = $.ajax.calls.all().find(function(call) {
return call.args[0].url.match(/.+video-transcript.+$/);
});
expect(state.videoTranscriptFeedback.loadAndSetVisibility).toHaveBeenCalled();
expect(getAITranscriptCall.args[0].url).toEqual(state.videoTranscriptFeedback.aiTranslationsUrl + getAITranscriptUrl);
expect(getAITranscriptCall.args[0].type).toEqual('GET');
expect(getAITranscriptCall.args[0].async).toEqual(false);
expect(getAITranscriptCall.args[0].success).toEqual(jasmine.any(Function));
expect(getAITranscriptCall.args[0].error).toEqual(jasmine.any(Function));
});
it('shows widget if transcript is AI generated', function() {
state.videoTranscriptFeedback.loadAndSetVisibility();
expect($('.wrapper-transcript-feedback')[0]).toExist();
});
it('hides widget if transcript is not AI generated', function() {
state.videoTranscriptFeedback.videoId = 'notAIGenerated';
state.videoTranscriptFeedback.loadAndSetVisibility();
expect($('.wrapper-transcript-feedback')[0]).toExist();
expect($('.wrapper-transcript-feedback')[0].style.display).toEqual('none');
});
it('hides widget if transcript is AI generated but is still in progress', function() {
state.videoTranscriptFeedback.videoId = 'inProgress';
state.videoTranscriptFeedback.loadAndSetVisibility();
expect($('.wrapper-transcript-feedback')[0]).toExist();
expect($('.wrapper-transcript-feedback')[0].style.display).toEqual('none');
});
it('hides widget if query for transcript AI generated fails', function() {
state.videoTranscriptFeedback.videoId = 'error';
state.videoTranscriptFeedback.loadAndSetVisibility();
expect($('.wrapper-transcript-feedback')[0]).toExist();
expect($('.wrapper-transcript-feedback')[0].style.display).toEqual('none');
});
it('checks if feedback exists for AI generated transcript', function() {
spyOn(state.videoTranscriptFeedback, 'getFeedbackForCurrentTranscript').and.callThrough();
state.videoTranscriptFeedback.loadAndSetVisibility();
var getTranscriptFeedbackCall = $.ajax.calls.all().find(function(call) {
return call.args[0].url.match(/.+transcript-feedback.+$/);
});
expect(state.videoTranscriptFeedback.getFeedbackForCurrentTranscript).toHaveBeenCalled();
expect(getTranscriptFeedbackCall.args[0].url).toEqual(state.videoTranscriptFeedback.aiTranslationsUrl + getTranscriptFeedbackUrl);
expect(getTranscriptFeedbackCall.args[0].type).toEqual('GET');
expect(getTranscriptFeedbackCall.args[0].success).toEqual(jasmine.any(Function));
expect(getTranscriptFeedbackCall.args[0].error).toEqual(jasmine.any(Function));
});
});
describe('get feedback for current transcript', function() {
it('marks thumbs up button if feedback exists and it is positive', function() {
state.videoTranscriptFeedback.getFeedbackForCurrentTranscript();
var thumbsUpIcon = $('.thumbs-up-icon')[0];
var thumbsDownIcon = $('.thumbs-down-icon')[0];
expect(thumbsUpIcon.classList).toContain('fa-thumbs-up');
expect(thumbsDownIcon.classList).toContain('fa-thumbs-o-down');
expect(state.videoTranscriptFeedback.currentFeedback).toEqual(true);
});
it('marks thumbs down button if feedback exists and it is negative', function() {
state.videoTranscriptFeedback.videoId = 'negative';
state.videoTranscriptFeedback.getFeedbackForCurrentTranscript();
var thumbsUpIcon = $('.thumbs-up-icon')[0];
var thumbsDownIcon = $('.thumbs-down-icon')[0];
expect(thumbsUpIcon.classList).toContain('fa-thumbs-o-up');
expect(thumbsDownIcon.classList).toContain('fa-thumbs-down');
expect(state.videoTranscriptFeedback.currentFeedback).toEqual(false);
});
it('marks thumbs up buttons as empty if feedback does not exist', function() {
state.videoTranscriptFeedback.videoId = 'none';
state.videoTranscriptFeedback.getFeedbackForCurrentTranscript();
var thumbsUpIcon = $('.thumbs-up-icon')[0];
var thumbsDownIcon = $('.thumbs-down-icon')[0];
expect(thumbsUpIcon.classList).toContain('fa-thumbs-o-up');
expect(thumbsDownIcon.classList).toContain('fa-thumbs-o-down');
expect(state.videoTranscriptFeedback.currentFeedback).toEqual(null);
});
it('marks thumbs up buttons as empty if query fails', function() {
state.videoTranscriptFeedback.videoId = 'error';
state.videoTranscriptFeedback.getFeedbackForCurrentTranscript();
var thumbsUpIcon = $('.thumbs-up-icon')[0];
var thumbsDownIcon = $('.thumbs-down-icon')[0];
expect(thumbsUpIcon.classList).toContain('fa-thumbs-o-up');
expect(thumbsDownIcon.classList).toContain('fa-thumbs-o-down');
expect(state.videoTranscriptFeedback.currentFeedback).toEqual(null);
});
});
describe('onHideLanguageMenu', function() {
it('calls loadAndSetVisibility if language changed', function() {
state.videoTranscriptFeedback.currentTranscriptLanguage = 'es';
spyOn(state.videoTranscriptFeedback, 'loadAndSetVisibility').and.callThrough();
state.el.trigger('language_menu:hide', {
id: 'id',
code: 'code',
language: 'en',
duration: 10
});
expect(state.videoTranscriptFeedback.loadAndSetVisibility).toHaveBeenCalled();
});
it('does not call loadAndSetVisibility if language did not change', function() {
state.videoTranscriptFeedback.currentTranscriptLanguage = 'en';
spyOn(state.videoTranscriptFeedback, 'loadAndSetVisibility').and.callThrough();
state.el.trigger('language_menu:hide', {
id: 'id',
code: 'code',
language: 'en',
duration: 10
});
expect(state.videoTranscriptFeedback.loadAndSetVisibility).not.toHaveBeenCalled();
});
});
describe('clicking on thumbs up button', function() {
it('sends positive feedback if there is no current feedback', function() {
state.videoTranscriptFeedback.loadAndSetVisibility();
state.videoTranscriptFeedback.currentFeedback = undefined;
spyOn(state.videoTranscriptFeedback, 'sendFeedbackForCurrentTranscript').and.callFake(function() {
return true;
});
var thumbsUpButton = $('.thumbs-up-btn');
thumbsUpButton.trigger('click');
expect(state.videoTranscriptFeedback.sendFeedbackForCurrentTranscript).toHaveBeenCalledWith(true);
});
it('sends empty feedback if there is a current positive feedback', function() {
state.videoTranscriptFeedback.loadAndSetVisibility();
state.videoTranscriptFeedback.currentFeedback = true;
spyOn(state.videoTranscriptFeedback, 'sendFeedbackForCurrentTranscript').and.callFake(function() {
return true;
});
var thumbsUpButton = $('.thumbs-up-btn');
thumbsUpButton.trigger('click');
expect(state.videoTranscriptFeedback.sendFeedbackForCurrentTranscript).toHaveBeenCalledWith(null);
});
});
describe('clicking on thumbs down button', function() {
it('sends negative feedback if there is no current feedback', function() {
state.videoTranscriptFeedback.loadAndSetVisibility();
state.videoTranscriptFeedback.currentFeedback = undefined;
spyOn(state.videoTranscriptFeedback, 'sendFeedbackForCurrentTranscript').and.callFake(function() {
return true;
});
var thumbsDownButton = $('.thumbs-down-btn');
thumbsDownButton.trigger('click');
expect(state.videoTranscriptFeedback.sendFeedbackForCurrentTranscript).toHaveBeenCalledWith(false);
});
it('sends empty feedback if there is a current negative feedback', function() {
state.videoTranscriptFeedback.loadAndSetVisibility();
state.videoTranscriptFeedback.currentFeedback = false;
spyOn(state.videoTranscriptFeedback, 'sendFeedbackForCurrentTranscript').and.callFake(function() {
return true;
});
var thumbsDownButton = $('.thumbs-down-btn');
thumbsDownButton.trigger('click');
expect(state.videoTranscriptFeedback.sendFeedbackForCurrentTranscript).toHaveBeenCalledWith(null);
});
});
describe('calling send transcript feedback', function() {
it('sends proper request to ai translation service', function() {
state.videoTranscriptFeedback.loadAndSetVisibility();
state.videoTranscriptFeedback.currentFeedback = undefined;
state.videoTranscriptFeedback.sendFeedbackForCurrentTranscript(true);
var sendTranscriptFeedbackCall = $.ajax.calls.all().find(function(call) {
return call.args[0].url.match(/.+transcript-feedback.+$/) && call.args[0].type === 'POST';
});
expect(sendTranscriptFeedbackCall.args[0].url).toEqual(state.videoTranscriptFeedback.aiTranslationsUrl + sendTranscriptFeedbackUrl);
expect(sendTranscriptFeedbackCall.args[0].type).toEqual('POST');
expect(sendTranscriptFeedbackCall.args[0].dataType).toEqual('json');
expect(sendTranscriptFeedbackCall.args[0].data).toEqual({
transcript_language: currentLanguage,
video_uuid: videoId,
user_id: userId,
value: true,
});
expect(sendTranscriptFeedbackCall.args[0].success).toEqual(jasmine.any(Function));
expect(sendTranscriptFeedbackCall.args[0].error).toEqual(jasmine.any(Function));
});
it('marks thumbs up button as selected if response is positive', function() {
state.videoTranscriptFeedback.loadAndSetVisibility();
state.videoTranscriptFeedback.currentFeedback = undefined;
state.videoTranscriptFeedback.sendFeedbackForCurrentTranscript(true);
var thumbsUpIcon = $('.thumbs-up-icon')[0];
var thumbsDownIcon = $('.thumbs-down-icon')[0];
expect(thumbsUpIcon.classList).toContain('fa-thumbs-up');
expect(thumbsDownIcon.classList).toContain('fa-thumbs-o-down');
expect(state.videoTranscriptFeedback.currentFeedback).toEqual(true);
});
it('marks thumbs down button as selected if response is negative', function() {
state.videoTranscriptFeedback.loadAndSetVisibility();
state.videoTranscriptFeedback.currentFeedback = undefined;
state.videoTranscriptFeedback.sendFeedbackForCurrentTranscript(false);
var thumbsUpIcon = $('.thumbs-up-icon')[0];
var thumbsDownIcon = $('.thumbs-down-icon')[0];
expect(thumbsUpIcon.classList).toContain('fa-thumbs-o-up');
expect(thumbsDownIcon.classList).toContain('fa-thumbs-down');
expect(state.videoTranscriptFeedback.currentFeedback).toEqual(false);
});
it('unselects thumbs buttons if response is empty', function() {
state.videoTranscriptFeedback.loadAndSetVisibility();
state.videoTranscriptFeedback.currentFeedback = true;
state.videoTranscriptFeedback.sendFeedbackForCurrentTranscript(null);
var thumbsUpIcon = $('.thumbs-up-icon')[0];
var thumbsDownIcon = $('.thumbs-down-icon')[0];
expect(thumbsUpIcon.classList).toContain('fa-thumbs-o-up');
expect(thumbsDownIcon.classList).toContain('fa-thumbs-o-down');
expect(state.videoTranscriptFeedback.currentFeedback).toEqual(null);
});
});
});
}).call(this);

View File

@@ -0,0 +1,247 @@
(function(define) {
// VideoTranscriptFeedbackHandler module.
'use strict';
define('video/037_video_caption.js', ['underscore'],
function(_) {
/**
* @desc VideoTranscriptFeedbackHandler module exports a function.
*
* @type {function}
* @access public
*
* @param {object} state - The object containing the state of the video
* player. All other modules, their parameters, public variables, etc.
* are available via this object.
*
* @this {object} The global window object.
*
*/
var VideoTranscriptFeedbackHandler = function(state) {
if (!(this instanceof VideoTranscriptFeedbackHandler)) {
return new VideoTranscriptFeedbackHandler(state);
}
_.bindAll(this, 'destroy', 'getFeedbackForCurrentTranscript', 'markAsPositiveFeedback', 'markAsNegativeFeedback', 'markAsEmptyFeedback',
'selectThumbsUp', 'selectThumbsDown', 'unselectThumbsUp', 'unselectThumbsDown', 'thumbsUpClickHandler', 'thumbsDownClickHandler',
'sendFeedbackForCurrentTranscript', 'onHideLanguageMenu', 'getCurrentLanguage', 'loadAndSetVisibility', 'showWidget', 'hideWidget'
);
this.state = state;
this.state.videoTranscriptFeedback = this;
this.currentTranscriptLanguage = this.state.lang;
this.transcriptLanguages = this.state.config.transcriptLanguages;
if (this.state.el.find('.wrapper-transcript-feedback').length) {
this.initialize();
}
return false;
};
VideoTranscriptFeedbackHandler.prototype = {
destroy: function() {
this.state.el.off(this.events);
},
// Initializes the module.
initialize: function() {
this.el = this.state.el.find('.wrapper-transcript-feedback');
this.videoId = this.el.data('video-id');
this.userId = this.el.data('user-id');
this.aiTranslationsUrl = this.state.config.aiTranslationsUrl;
this.thumbsUpButton = this.el.find('.thumbs-up-btn');
this.thumbsDownButton = this.el.find('.thumbs-down-btn');
this.thumbsUpButton.on('click', this.thumbsUpClickHandler);
this.thumbsDownButton.on('click', this.thumbsDownClickHandler);
this.events = {
'language_menu:hide': this.onHideLanguageMenu,
destroy: this.destroy
};
this.loadAndSetVisibility();
this.bindHandlers();
},
bindHandlers: function() {
this.state.el.on(this.events);
},
getFeedbackForCurrentTranscript: function() {
var self = this;
var url = self.aiTranslationsUrl + '/transcript-feedback' + '?transcript_language=' + self.currentTranscriptLanguage + '&video_uuid=' + self.videoId + '&user_id=' + self.userId;
$.ajax({
url: url,
type: 'GET',
success: function(data) {
if (data && data.value === true) {
self.markAsPositiveFeedback();
self.currentFeedback = true;
} else {
if (data && data.value === false) {
self.markAsNegativeFeedback();
self.currentFeedback = false;
} else {
self.markAsEmptyFeedback();
self.currentFeedback = null;
}
}
},
error: function(error) {
self.markAsEmptyFeedback();
self.currentFeedback = null;
}
});
},
markAsPositiveFeedback: function() {
this.selectThumbsUp();
this.unselectThumbsDown();
},
markAsNegativeFeedback: function() {
this.selectThumbsDown();
this.unselectThumbsUp();
},
markAsEmptyFeedback: function() {
this.unselectThumbsUp();
this.unselectThumbsDown();
},
selectThumbsUp: function() {
var thumbsUpIcon = this.thumbsUpButton.find('.thumbs-up-icon');
if (thumbsUpIcon[0].classList.contains('fa-thumbs-o-up')) {
thumbsUpIcon[0].classList.remove("fa-thumbs-o-up");
thumbsUpIcon[0].classList.add("fa-thumbs-up");
}
},
selectThumbsDown: function() {
var thumbsDownIcon = this.thumbsDownButton.find('.thumbs-down-icon');
if (thumbsDownIcon[0].classList.contains('fa-thumbs-o-down')) {
thumbsDownIcon[0].classList.remove("fa-thumbs-o-down");
thumbsDownIcon[0].classList.add("fa-thumbs-down");
}
},
unselectThumbsUp: function() {
var thumbsUpIcon = this.thumbsUpButton.find('.thumbs-up-icon');
if (thumbsUpIcon[0].classList.contains('fa-thumbs-up')) {
thumbsUpIcon[0].classList.remove("fa-thumbs-up");
thumbsUpIcon[0].classList.add("fa-thumbs-o-up");
}
},
unselectThumbsDown: function() {
var thumbsDownIcon = this.thumbsDownButton.find('.thumbs-down-icon');
if (thumbsDownIcon[0].classList.contains('fa-thumbs-down')) {
thumbsDownIcon[0].classList.remove("fa-thumbs-down");
thumbsDownIcon[0].classList.add("fa-thumbs-o-down");
}
},
thumbsUpClickHandler: function() {
if (this.currentFeedback) {
this.sendFeedbackForCurrentTranscript(null);
} else {
this.sendFeedbackForCurrentTranscript(true);
}
},
thumbsDownClickHandler: function() {
if (this.currentFeedback === false) {
this.sendFeedbackForCurrentTranscript(null);
} else {
this.sendFeedbackForCurrentTranscript(false);
}
},
sendFeedbackForCurrentTranscript: function(feedbackValue) {
var self = this;
var url = self.aiTranslationsUrl + '/transcript-feedback/';
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
data: {
transcript_language: self.currentTranscriptLanguage,
video_uuid: self.videoId,
user_id: self.userId,
value: feedbackValue,
},
success: function(data) {
if (data && data.value === true) {
self.markAsPositiveFeedback();
self.currentFeedback = true;
} else {
if (data && data.value === false) {
self.markAsNegativeFeedback();
self.currentFeedback = false;
} else {
self.markAsEmptyFeedback();
self.currentFeedback = null;
}
}
},
error: function() {
self.markAsEmptyFeedback();
self.currentFeedback = null;
}
});
},
onHideLanguageMenu: function() {
var newLanguageSelected = this.getCurrentLanguage();
if (this.currentTranscriptLanguage !== newLanguageSelected) {
this.currentTranscriptLanguage = this.getCurrentLanguage();
this.loadAndSetVisibility();
}
},
getCurrentLanguage: function() {
var language = this.state.lang;
return language;
},
loadAndSetVisibility: function() {
var self = this;
var url = self.aiTranslationsUrl + '/video-transcript' + '?transcript_language=' + self.currentTranscriptLanguage + '&video_uuid=' + self.videoId;
$.ajax({
url: url,
type: 'GET',
async: false,
success: function(data) {
if (data && data.status === 'Completed') {
self.showWidget();
self.getFeedbackForCurrentTranscript();
} else {
self.hideWidget();
}
},
error: function(error) {
self.hideWidget();
}
});
},
showWidget: function() {
this.el.show();
},
hideWidget: function() {
this.el.hide();
},
};
return VideoTranscriptFeedbackHandler;
});
}(RequireJS.define));

View File

@@ -65,14 +65,15 @@
'video/09_completion.js',
'video/10_commands.js',
'video/095_video_context_menu.js',
'video/036_video_social_sharing.js'
'video/036_video_social_sharing.js',
'video/037_video_transcript_feedback.js'
],
function(
VideoStorage, initialize, FocusGrabber, VideoAccessibleMenu, VideoControl, VideoFullScreen,
VideoQualityControl, VideoProgressSlider, VideoVolumeControl, VideoSpeedControl, VideoAutoAdvanceControl,
VideoCaption, VideoPlayPlaceholder, VideoPlayPauseControl, VideoPlaySkipControl, VideoSkipControl,
VideoBumper, VideoSaveStatePlugin, VideoEventsPlugin, VideoEventsBumperPlugin, VideoPoster,
VideoCompletionHandler, VideoCommands, VideoContextMenu, VideoSocialSharing
VideoCompletionHandler, VideoCommands, VideoContextMenu, VideoSocialSharing, VideoTranscriptFeedback
) {
/* RequireJS */
var youtubeXhr = null,
@@ -92,10 +93,10 @@
FocusGrabber, VideoControl, VideoPlayPlaceholder,
VideoPlayPauseControl, VideoProgressSlider, VideoSpeedControl,
VideoVolumeControl, VideoQualityControl, VideoFullScreen, VideoCaption, VideoCommands,
VideoContextMenu, VideoSaveStatePlugin, VideoEventsPlugin, VideoCompletionHandler
VideoContextMenu, VideoSaveStatePlugin, VideoEventsPlugin, VideoCompletionHandler, VideoTranscriptFeedback
].concat(autoAdvanceEnabled ? [VideoAutoAdvanceControl] : []),
bumperVideoModules = [VideoControl, VideoPlaySkipControl, VideoSkipControl,
VideoVolumeControl, VideoCaption, VideoCommands, VideoSaveStatePlugin,
VideoVolumeControl, VideoCaption, VideoCommands, VideoSaveStatePlugin, VideoTranscriptFeedback,
VideoEventsBumperPlugin, VideoCompletionHandler],
state = {
el: el,