Revert "Revert "Transcript secure credentials""
This commit is contained in:
committed by
muzaffaryousaf
parent
e503ed86ae
commit
7157ec2e2f
@@ -19,6 +19,7 @@ from .export_git import *
|
||||
from .user import *
|
||||
from .tabs import *
|
||||
from .videos import *
|
||||
from .transcript_settings import *
|
||||
from .transcripts_ajax import *
|
||||
try:
|
||||
from .dev import *
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import ddt
|
||||
import json
|
||||
from mock import Mock, patch
|
||||
|
||||
from django.test.testcases import TestCase
|
||||
|
||||
from contentstore.tests.utils import CourseTestCase
|
||||
from contentstore.utils import reverse_course_url
|
||||
from contentstore.views.transcript_settings import TranscriptionProviderErrorType, validate_transcript_credentials
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@patch(
|
||||
'openedx.core.djangoapps.video_config.models.VideoTranscriptEnabledFlag.feature_enabled',
|
||||
Mock(return_value=True)
|
||||
)
|
||||
class TranscriptCredentialsTest(CourseTestCase):
|
||||
"""
|
||||
Tests for transcript credentials handler.
|
||||
"""
|
||||
VIEW_NAME = 'transcript_credentials_handler'
|
||||
|
||||
def get_url_for_course_key(self, course_id):
|
||||
return reverse_course_url(self.VIEW_NAME, course_id)
|
||||
|
||||
def test_302_with_anonymous_user(self):
|
||||
"""
|
||||
Verify that redirection happens in case of unauthorized request.
|
||||
"""
|
||||
self.client.logout()
|
||||
transcript_credentials_url = self.get_url_for_course_key(self.course.id)
|
||||
response = self.client.post(transcript_credentials_url, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_405_with_not_allowed_request_method(self):
|
||||
"""
|
||||
Verify that 405 is returned in case of not-allowed request methods.
|
||||
Allowed request methods include POST.
|
||||
"""
|
||||
transcript_credentials_url = self.get_url_for_course_key(self.course.id)
|
||||
response = self.client.get(transcript_credentials_url, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 405)
|
||||
|
||||
def test_404_with_feature_disabled(self):
|
||||
"""
|
||||
Verify that 404 is returned if the corresponding feature is disabled.
|
||||
"""
|
||||
transcript_credentials_url = self.get_url_for_course_key(self.course.id)
|
||||
with patch('openedx.core.djangoapps.video_config.models.VideoTranscriptEnabledFlag.feature_enabled') as feature:
|
||||
feature.return_value = False
|
||||
response = self.client.post(transcript_credentials_url, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@ddt.data(
|
||||
(
|
||||
{
|
||||
'provider': 'abc_provider',
|
||||
'api_key': '1234'
|
||||
},
|
||||
({}, None),
|
||||
400,
|
||||
'{\n "error": "Invalid Provider abc_provider."\n}'
|
||||
),
|
||||
(
|
||||
{
|
||||
'provider': '3PlayMedia',
|
||||
'api_key': '11111',
|
||||
'api_secret_key': '44444'
|
||||
},
|
||||
({'error_type': TranscriptionProviderErrorType.INVALID_CREDENTIALS}, False),
|
||||
400,
|
||||
'{\n "error": "The information you entered is incorrect."\n}'
|
||||
),
|
||||
(
|
||||
{
|
||||
'provider': 'Cielo24',
|
||||
'api_key': '12345',
|
||||
'username': 'test_user'
|
||||
},
|
||||
({}, True),
|
||||
200,
|
||||
''
|
||||
)
|
||||
)
|
||||
@ddt.unpack
|
||||
@patch('contentstore.views.transcript_settings.update_3rd_party_transcription_service_credentials')
|
||||
def test_transcript_credentials_handler(self, request_payload, update_credentials_response, expected_status_code,
|
||||
expected_response, mock_update_credentials):
|
||||
"""
|
||||
Tests that transcript credentials handler works as expected.
|
||||
"""
|
||||
mock_update_credentials.return_value = update_credentials_response
|
||||
transcript_credentials_url = self.get_url_for_course_key(self.course.id)
|
||||
response = self.client.post(
|
||||
transcript_credentials_url,
|
||||
data=json.dumps(request_payload),
|
||||
content_type='application/json'
|
||||
)
|
||||
self.assertEqual(response.status_code, expected_status_code)
|
||||
self.assertEqual(response.content, expected_response)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TranscriptCredentialsValidationTest(TestCase):
|
||||
"""
|
||||
Tests for credentials validations.
|
||||
"""
|
||||
|
||||
@ddt.data(
|
||||
(
|
||||
'ABC',
|
||||
{
|
||||
'username': 'test_user',
|
||||
'password': 'test_pass'
|
||||
},
|
||||
'Invalid Provider ABC.',
|
||||
{}
|
||||
),
|
||||
(
|
||||
'Cielo24',
|
||||
{
|
||||
'username': 'test_user'
|
||||
},
|
||||
'api_key must be specified.',
|
||||
{}
|
||||
),
|
||||
(
|
||||
'Cielo24',
|
||||
{
|
||||
'username': 'test_user',
|
||||
'api_key': 'test_api_key',
|
||||
'extra_param': 'extra_value'
|
||||
},
|
||||
'',
|
||||
{
|
||||
'username': 'test_user',
|
||||
'api_key': 'test_api_key'
|
||||
}
|
||||
),
|
||||
(
|
||||
'3PlayMedia',
|
||||
{
|
||||
'username': 'test_user'
|
||||
},
|
||||
'api_key and api_secret_key must be specified.',
|
||||
{}
|
||||
),
|
||||
(
|
||||
'3PlayMedia',
|
||||
{
|
||||
'api_key': 'test_key',
|
||||
'api_secret_key': 'test_secret',
|
||||
'extra_param': 'extra_value'
|
||||
},
|
||||
'',
|
||||
{
|
||||
'api_key': 'test_key',
|
||||
'api_secret_key': 'test_secret'
|
||||
}
|
||||
),
|
||||
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_invalid_credentials(self, provider, credentials, expected_error_message, expected_validated_credentials):
|
||||
"""
|
||||
Test validation with invalid transcript credentials.
|
||||
"""
|
||||
error_message, validated_credentials = validate_transcript_credentials(provider, **credentials)
|
||||
# Assert the results.
|
||||
self.assertEqual(error_message, expected_error_message)
|
||||
self.assertDictEqual(validated_credentials, expected_validated_credentials)
|
||||
110
cms/djangoapps/contentstore/views/transcript_settings.py
Normal file
110
cms/djangoapps/contentstore/views/transcript_settings.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""
|
||||
Views related to the transcript preferences feature
|
||||
"""
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpResponseNotFound
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.http import require_POST
|
||||
from edxval.api import (
|
||||
get_3rd_party_transcription_plans,
|
||||
update_transcript_credentials_state_for_org,
|
||||
)
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag
|
||||
from openedx.core.djangoapps.video_pipeline.api import update_3rd_party_transcription_service_credentials
|
||||
from util.json_request import JsonResponse, expect_json
|
||||
|
||||
from contentstore.views.videos import TranscriptProvider
|
||||
|
||||
__all__ = ['transcript_credentials_handler']
|
||||
|
||||
|
||||
class TranscriptionProviderErrorType:
|
||||
"""
|
||||
Transcription provider's error types enumeration.
|
||||
"""
|
||||
INVALID_CREDENTIALS = 1
|
||||
|
||||
|
||||
def validate_transcript_credentials(provider, **credentials):
|
||||
"""
|
||||
Validates transcript credentials.
|
||||
|
||||
Validations:
|
||||
Providers must be either 3PlayMedia or Cielo24.
|
||||
In case of:
|
||||
3PlayMedia - 'api_key' and 'api_secret_key' are required.
|
||||
Cielo24 - 'api_key' and 'username' are required.
|
||||
|
||||
It ignores any extra/unrelated parameters passed in credentials and
|
||||
only returns the validated ones.
|
||||
"""
|
||||
error_message, validated_credentials = '', {}
|
||||
valid_providers = get_3rd_party_transcription_plans().keys()
|
||||
if provider in valid_providers:
|
||||
must_have_props = []
|
||||
if provider == TranscriptProvider.THREE_PLAY_MEDIA:
|
||||
must_have_props = ['api_key', 'api_secret_key']
|
||||
elif provider == TranscriptProvider.CIELO24:
|
||||
must_have_props = ['api_key', 'username']
|
||||
|
||||
missing = [must_have_prop for must_have_prop in must_have_props if must_have_prop not in credentials.keys()]
|
||||
if missing:
|
||||
error_message = u'{missing} must be specified.'.format(missing=' and '.join(missing))
|
||||
return error_message, validated_credentials
|
||||
|
||||
validated_credentials.update({
|
||||
prop: credentials[prop] for prop in must_have_props
|
||||
})
|
||||
else:
|
||||
error_message = u'Invalid Provider {provider}.'.format(provider=provider)
|
||||
|
||||
return error_message, validated_credentials
|
||||
|
||||
|
||||
@expect_json
|
||||
@login_required
|
||||
@require_POST
|
||||
def transcript_credentials_handler(request, course_key_string):
|
||||
"""
|
||||
JSON view handler to update the transcript organization credentials.
|
||||
|
||||
Arguments:
|
||||
request: WSGI request object
|
||||
course_key_string: A course identifier to extract the org.
|
||||
|
||||
Returns:
|
||||
- A 200 response if credentials are valid and successfully updated in edx-video-pipeline.
|
||||
- A 404 response if transcript feature is not enabled for this course.
|
||||
- A 400 if credentials do not pass validations, hence not updated in edx-video-pipeline.
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_key_string)
|
||||
if not VideoTranscriptEnabledFlag.feature_enabled(course_key):
|
||||
return HttpResponseNotFound()
|
||||
|
||||
provider = request.json.pop('provider')
|
||||
error_message, validated_credentials = validate_transcript_credentials(provider=provider, **request.json)
|
||||
if error_message:
|
||||
response = JsonResponse({'error': error_message}, status=400)
|
||||
else:
|
||||
# Send the validated credentials to edx-video-pipeline.
|
||||
credentials_payload = dict(validated_credentials, org=course_key.org, provider=provider)
|
||||
error_response, is_updated = update_3rd_party_transcription_service_credentials(**credentials_payload)
|
||||
# Send appropriate response based on whether credentials were updated or not.
|
||||
if is_updated:
|
||||
# Cache credentials state in edx-val.
|
||||
update_transcript_credentials_state_for_org(org=course_key.org, provider=provider, exists=is_updated)
|
||||
response = JsonResponse(status=200)
|
||||
else:
|
||||
# Error response would contain error types and the following
|
||||
# error type is received from edx-video-pipeline whenever we've
|
||||
# got invalid credentials for a provider. Its kept this way because
|
||||
# edx-video-pipeline doesn't support i18n translations yet.
|
||||
error_type = error_response.get('error_type')
|
||||
if error_type == TranscriptionProviderErrorType.INVALID_CREDENTIALS:
|
||||
error_message = _('The information you entered is incorrect.')
|
||||
|
||||
response = JsonResponse({'error': error_message}, status=400)
|
||||
|
||||
return response
|
||||
@@ -1,11 +1,10 @@
|
||||
"""
|
||||
Views related to the video upload feature
|
||||
"""
|
||||
from contextlib import closing
|
||||
|
||||
import csv
|
||||
import json
|
||||
import logging
|
||||
from contextlib import closing
|
||||
from datetime import datetime, timedelta
|
||||
from uuid import uuid4
|
||||
|
||||
@@ -18,33 +17,38 @@ from django.core.files.images import get_image_dimensions
|
||||
from django.http import HttpResponse, HttpResponseNotFound
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_noop
|
||||
from django.views.decorators.http import require_GET, require_POST, require_http_methods
|
||||
from django.views.decorators.http import require_GET, require_http_methods, require_POST
|
||||
from edxval.api import (
|
||||
SortDirection,
|
||||
VideoSortField,
|
||||
create_video,
|
||||
get_videos_for_course,
|
||||
remove_video_for_course,
|
||||
update_video_status,
|
||||
update_video_image,
|
||||
get_3rd_party_transcription_plans,
|
||||
get_transcript_preferences,
|
||||
create_or_update_transcript_preferences,
|
||||
create_video,
|
||||
get_3rd_party_transcription_plans,
|
||||
get_transcript_credentials_state_for_org,
|
||||
get_transcript_preferences,
|
||||
get_videos_for_course,
|
||||
remove_transcript_preferences,
|
||||
remove_video_for_course,
|
||||
update_video_image,
|
||||
update_video_status
|
||||
)
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag
|
||||
from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace
|
||||
|
||||
from contentstore.models import VideoUploadConfig
|
||||
from contentstore.utils import reverse_course_url
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag
|
||||
from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace
|
||||
from util.json_request import JsonResponse, expect_json
|
||||
|
||||
from .course import get_course_and_check_access
|
||||
|
||||
|
||||
__all__ = ['videos_handler', 'video_encodings_download', 'video_images_handler', 'transcript_preferences_handler']
|
||||
__all__ = [
|
||||
'videos_handler',
|
||||
'video_encodings_download',
|
||||
'video_images_handler',
|
||||
'transcript_preferences_handler',
|
||||
]
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -589,7 +593,8 @@ def videos_index_html(course):
|
||||
},
|
||||
'is_video_transcript_enabled': is_video_transcript_enabled,
|
||||
'video_transcript_settings': None,
|
||||
'active_transcript_preferences': None
|
||||
'active_transcript_preferences': None,
|
||||
'transcript_credentials': None
|
||||
}
|
||||
|
||||
if is_video_transcript_enabled:
|
||||
@@ -598,9 +603,15 @@ def videos_index_html(course):
|
||||
'transcript_preferences_handler',
|
||||
unicode(course.id)
|
||||
),
|
||||
'transcript_credentials_handler_url': reverse_course_url(
|
||||
'transcript_credentials_handler',
|
||||
unicode(course.id)
|
||||
),
|
||||
'transcription_plans': get_3rd_party_transcription_plans(),
|
||||
}
|
||||
context['active_transcript_preferences'] = get_transcript_preferences(unicode(course.id))
|
||||
# Cached state for transcript providers' credentials (org-specific)
|
||||
context['transcript_credentials'] = get_transcript_credentials_state_for_org(course.id.org)
|
||||
|
||||
return render_to_response('videos_index.html', context)
|
||||
|
||||
|
||||
@@ -947,6 +947,9 @@ INSTALLED_APPS = [
|
||||
# Video module configs (This will be moved to Video once it becomes an XBlock)
|
||||
'openedx.core.djangoapps.video_config',
|
||||
|
||||
# edX Video Pipeline integration
|
||||
'openedx.core.djangoapps.video_pipeline',
|
||||
|
||||
# For CMS
|
||||
'contentstore.apps.ContentstoreConfig',
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ define([
|
||||
videoSupportedFileFormats,
|
||||
videoUploadMaxFileSizeInGB,
|
||||
activeTranscriptPreferences,
|
||||
transcriptOrganizationCredentials,
|
||||
videoTranscriptSettings,
|
||||
isVideoTranscriptEnabled,
|
||||
videoImageSettings
|
||||
@@ -27,6 +28,7 @@ define([
|
||||
videoUploadMaxFileSizeInGB: videoUploadMaxFileSizeInGB,
|
||||
videoImageSettings: videoImageSettings,
|
||||
activeTranscriptPreferences: activeTranscriptPreferences,
|
||||
transcriptOrganizationCredentials: transcriptOrganizationCredentials,
|
||||
videoTranscriptSettings: videoTranscriptSettings,
|
||||
isVideoTranscriptEnabled: isVideoTranscriptEnabled,
|
||||
onFileUploadDone: function(activeVideos) {
|
||||
|
||||
@@ -43,7 +43,7 @@ define(
|
||||
activeTranscriptPreferences: {},
|
||||
videoTranscriptSettings: {
|
||||
transcript_preferences_handler_url: '',
|
||||
transcription_plans: {}
|
||||
transcription_plans: null
|
||||
},
|
||||
isVideoTranscriptEnabled: isVideoTranscriptEnabled
|
||||
});
|
||||
|
||||
@@ -8,10 +8,23 @@ define(
|
||||
courseVideoSettingsView,
|
||||
renderCourseVideoSettingsView,
|
||||
destroyCourseVideoSettingsView,
|
||||
verifyTranscriptPreferences,
|
||||
verifyTranscriptPreferencesView,
|
||||
verifyOrganizationCredentialsView,
|
||||
verifyCredentialFieldsPresent,
|
||||
verifyOrganizationCredentialField,
|
||||
verifyMessage,
|
||||
verifyPreferanceErrorState,
|
||||
selectPreference,
|
||||
chooseProvider,
|
||||
verifyProviderList,
|
||||
verifyProviderSelectedView,
|
||||
verifyCredentialsSaved,
|
||||
resetProvider,
|
||||
changeProvider,
|
||||
submitOrganizationCredentials,
|
||||
transcriptPreferencesUrl = '/transcript_preferences/course-v1:edX+DemoX+Demo_Course',
|
||||
transcriptCredentialsHandlerUrl = '/transcript_credentials/course-v1:edX+DemoX+Demo_Course',
|
||||
INTERNAL_SERVER_ERROR = 'An error has occurred. Wait a few minutes, and then try again.',
|
||||
activeTranscriptPreferences = {
|
||||
provider: 'Cielo24',
|
||||
cielo24_fidelity: 'PROFESSIONAL',
|
||||
@@ -21,6 +34,10 @@ define(
|
||||
preferred_languages: ['fr', 'en'],
|
||||
modified: '2017-08-27T12:28:17.421260Z'
|
||||
},
|
||||
transcriptOrganizationCredentials = {
|
||||
Cielo24: true,
|
||||
'3PlayMedia': true
|
||||
},
|
||||
transcriptionPlans = {
|
||||
'3PlayMedia': {
|
||||
languages: {
|
||||
@@ -72,15 +89,37 @@ define(
|
||||
},
|
||||
display_name: 'Cielo24'
|
||||
}
|
||||
},
|
||||
providers = {
|
||||
none: {
|
||||
key: 'none',
|
||||
value: '',
|
||||
displayName: 'None'
|
||||
},
|
||||
Cielo24: {
|
||||
key: 'Cielo24',
|
||||
value: 'Cielo24',
|
||||
displayName: 'Cielo24'
|
||||
},
|
||||
'3PlayMedia': {
|
||||
key: '3PlayMedia',
|
||||
value: '3PlayMedia',
|
||||
displayName: '3Play Media'
|
||||
}
|
||||
};
|
||||
|
||||
renderCourseVideoSettingsView = function(activeTranscriptPreferencesData, transcriptionPlansData) {
|
||||
renderCourseVideoSettingsView = function(activeTranscriptPreferencesData, transcriptionPlansData, transcriptOrganizationCredentialsData) { // eslint-disable-line max-len
|
||||
// First destroy old referance to the view if present.
|
||||
destroyCourseVideoSettingsView();
|
||||
|
||||
courseVideoSettingsView = new CourseVideoSettingsView({
|
||||
activeTranscriptPreferences: activeTranscriptPreferencesData || null,
|
||||
videoTranscriptSettings: {
|
||||
transcript_preferences_handler_url: transcriptPreferencesUrl,
|
||||
transcript_credentials_handler_url: transcriptCredentialsHandlerUrl,
|
||||
transcription_plans: transcriptionPlansData || null
|
||||
}
|
||||
},
|
||||
transcriptOrganizationCredentials: transcriptOrganizationCredentialsData || null
|
||||
});
|
||||
$courseVideoSettingsEl = courseVideoSettingsView.render().$el;
|
||||
};
|
||||
@@ -108,10 +147,167 @@ define(
|
||||
$preference.change();
|
||||
};
|
||||
|
||||
chooseProvider = function(selectedProvider) {
|
||||
verifyMessage = function(state, message) {
|
||||
var icon = state === 'error' ? 'fa-info-circle' : 'fa-check-circle';
|
||||
expect($courseVideoSettingsEl.find('.course-video-settings-message-wrapper.' + state).html()).toEqual(
|
||||
'<div class="course-video-settings-message">' +
|
||||
'<span class="icon fa ' + icon + '" aria-hidden="true"></span>' +
|
||||
'<span>' + message + '</span>' +
|
||||
'</div>'
|
||||
);
|
||||
};
|
||||
|
||||
verifyProviderList = function(selectedProvider) {
|
||||
var $transcriptProvidersListEl = $courseVideoSettingsEl.find('.transcript-provider-wrapper .transcript-provider-group'); // eslint-disable-line max-len
|
||||
// Check None provider is selected.
|
||||
expect($transcriptProvidersListEl.find('input[type=radio]:checked').val()).toEqual(selectedProvider.value); // eslint-disable-line max-len
|
||||
_.each(providers, function(provider, key) {
|
||||
$transcriptProvidersListEl.find('label[for=transcript-provider-' + key + ']').val(provider.displayName); // eslint-disable-line max-len
|
||||
});
|
||||
};
|
||||
|
||||
verifyTranscriptPreferences = function() {
|
||||
expect($courseVideoSettingsEl.find('#transcript-turnaround').val()).toEqual(
|
||||
activeTranscriptPreferences.cielo24_turnaround
|
||||
);
|
||||
expect($courseVideoSettingsEl.find('#transcript-fidelity').val()).toEqual(
|
||||
activeTranscriptPreferences.cielo24_fidelity
|
||||
);
|
||||
expect($courseVideoSettingsEl.find('.transcript-language-container').length).toEqual(
|
||||
activeTranscriptPreferences.preferred_languages.length
|
||||
);
|
||||
// Now check values are assigned correctly.
|
||||
expect(courseVideoSettingsView.selectedTurnaroundPlan, activeTranscriptPreferences.cielo24_turnaround);
|
||||
expect(courseVideoSettingsView.selectedFidelityPlan, activeTranscriptPreferences.cielo24_fidelity);
|
||||
expect(courseVideoSettingsView.selectedLanguages, activeTranscriptPreferences.preferred_languages);
|
||||
};
|
||||
|
||||
verifyProviderSelectedView = function() {
|
||||
// Verify provider
|
||||
expect(
|
||||
$courseVideoSettingsEl.find('.selected-transcript-provider .title').html()
|
||||
).toEqual(courseVideoSettingsView.selectedProvider);
|
||||
|
||||
expect($courseVideoSettingsEl.find('.selected-transcript-provider .action-change-provider')).toExist();
|
||||
expect(
|
||||
$courseVideoSettingsEl.find('.selected-transcript-provider .action-change-provider .sr').html()
|
||||
).toEqual('Press change to change selected transcript provider.');
|
||||
};
|
||||
|
||||
verifyTranscriptPreferencesView = function() {
|
||||
expect($courseVideoSettingsEl.find('.course-video-transcript-preferances-wrapper')).toExist();
|
||||
};
|
||||
|
||||
verifyOrganizationCredentialsView = function() {
|
||||
expect($courseVideoSettingsEl.find('.organization-credentials-content')).toExist();
|
||||
};
|
||||
|
||||
verifyCredentialFieldsPresent = function(fields) {
|
||||
// Verify correct number of input fields are shown.
|
||||
expect(
|
||||
$courseVideoSettingsEl.find(
|
||||
'.organization-credentials-wrapper .transcript-preferance-wrapper input'
|
||||
).length
|
||||
).toEqual(_.keys(fields).length
|
||||
);
|
||||
|
||||
// Verify individual field has correct label and key.
|
||||
_.each(fields, function(label, fieldName) {
|
||||
verifyOrganizationCredentialField(fieldName, label);
|
||||
});
|
||||
};
|
||||
|
||||
verifyOrganizationCredentialField = function(fieldName, label) {
|
||||
var elementSelector = courseVideoSettingsView.selectedProvider + '-' + fieldName;
|
||||
// Verify that correct label is shown.
|
||||
expect(
|
||||
$courseVideoSettingsEl.find('.' + elementSelector + '-wrapper label .title').html()
|
||||
).toEqual(label);
|
||||
|
||||
// Verify that credential field is shown.
|
||||
expect(
|
||||
$courseVideoSettingsEl.find('.' + elementSelector + '-wrapper .' + elementSelector)
|
||||
).toExist();
|
||||
};
|
||||
|
||||
verifyCredentialsSaved = function() {
|
||||
// Verify that success message is shown.
|
||||
verifyMessage(
|
||||
'success',
|
||||
transcriptionPlans[courseVideoSettingsView.selectedProvider].display_name + ' credentials saved'
|
||||
);
|
||||
|
||||
// Also verify that transcript credential state is updated.
|
||||
expect(
|
||||
courseVideoSettingsView.transcriptOrganizationCredentials[courseVideoSettingsView.selectedProvider]
|
||||
).toBeTruthy();
|
||||
|
||||
// Verify that selected provider view after credentials are saved.
|
||||
verifyProviderSelectedView();
|
||||
};
|
||||
|
||||
changeProvider = function(selectedProvider) {
|
||||
// If Provider Selected view is show, first click on "Change Provider" button to
|
||||
// show all list of providers.
|
||||
if ($courseVideoSettingsEl.find('.selected-transcript-provider').length) {
|
||||
$courseVideoSettingsEl.find('.selected-transcript-provider .action-change-provider').click();
|
||||
}
|
||||
$courseVideoSettingsEl.find('#transcript-provider-' + selectedProvider).click();
|
||||
};
|
||||
|
||||
resetProvider = function() {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
// Set no provider selected
|
||||
changeProvider('none');
|
||||
$courseVideoSettingsEl.find('.action-update-course-video-settings').click();
|
||||
|
||||
AjaxHelpers.expectRequest(
|
||||
requests,
|
||||
'DELETE',
|
||||
transcriptPreferencesUrl
|
||||
);
|
||||
|
||||
// Send successful empty content response.
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
};
|
||||
|
||||
submitOrganizationCredentials = function(fieldValues, statusCode, errorMessage) {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
// Click change button to render organization credentials view.
|
||||
$courseVideoSettingsEl.find('.action-change-provider').click();
|
||||
|
||||
// Provide organization credentials.
|
||||
_.each(fieldValues, function(key) {
|
||||
$courseVideoSettingsEl.find('.' + courseVideoSettingsView.selectedProvider + '-' + key).val(key);
|
||||
});
|
||||
// Click save organization credentials button to save credentials.
|
||||
$courseVideoSettingsEl.find('.action-update-org-credentials').click();
|
||||
|
||||
AjaxHelpers.expectRequest(
|
||||
requests,
|
||||
'POST',
|
||||
transcriptCredentialsHandlerUrl,
|
||||
JSON.stringify(
|
||||
_.extend(
|
||||
{provider: courseVideoSettingsView.selectedProvider},
|
||||
fieldValues,
|
||||
{global: false}
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if (statusCode === 400) {
|
||||
// Send bad request error response.
|
||||
AjaxHelpers.respondWithError(requests, statusCode, {error: errorMessage});
|
||||
} else if (statusCode === 500) {
|
||||
// Send internal server error response.
|
||||
AjaxHelpers.respondWithError(requests, statusCode);
|
||||
} else {
|
||||
// Send empty response.
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
setFixtures(
|
||||
'<div class="video-transcript-settings-wrapper"></div>' +
|
||||
@@ -148,17 +344,10 @@ define(
|
||||
});
|
||||
|
||||
it('does not populate transcription plans if transcription plans are not provided', function() {
|
||||
// First detroy old referance to the view.
|
||||
destroyCourseVideoSettingsView();
|
||||
|
||||
// Create view with empty data.
|
||||
renderCourseVideoSettingsView(null, null);
|
||||
|
||||
expect($courseVideoSettingsEl.find('.transcript-provider-group').html()).toEqual('');
|
||||
expect($courseVideoSettingsEl.find('.transcript-turnaround').html()).toEqual('');
|
||||
expect($courseVideoSettingsEl.find('.transcript-fidelity').html()).toEqual('');
|
||||
expect($courseVideoSettingsEl.find('.video-source-language').html()).toEqual('');
|
||||
expect($courseVideoSettingsEl.find('.transcript-language-menu').html()).toEqual('');
|
||||
renderCourseVideoSettingsView();
|
||||
// Checking turnaround is sufficient to check preferences are are shown or not.
|
||||
expect($courseVideoSettingsEl.find('.transcript-turnaround-wrapper')).not.toExist();
|
||||
});
|
||||
|
||||
it('populates transcription plans correctly', function() {
|
||||
@@ -169,39 +358,51 @@ define(
|
||||
|
||||
it('populates active preferances correctly', function() {
|
||||
// First check preferance are selected correctly in HTML.
|
||||
expect($courseVideoSettingsEl.find('.transcript-provider-group input:checked').val()).toEqual(
|
||||
activeTranscriptPreferences.provider
|
||||
);
|
||||
expect($courseVideoSettingsEl.find('.transcript-turnaround').val()).toEqual(
|
||||
activeTranscriptPreferences.cielo24_turnaround
|
||||
);
|
||||
expect($courseVideoSettingsEl.find('.transcript-fidelity').val()).toEqual(
|
||||
activeTranscriptPreferences.cielo24_fidelity
|
||||
);
|
||||
expect($courseVideoSettingsEl.find('.video-source-language').val()).toEqual(
|
||||
activeTranscriptPreferences.video_source_language
|
||||
);
|
||||
expect($courseVideoSettingsEl.find('.transcript-language-container').length).toEqual(
|
||||
activeTranscriptPreferences.preferred_languages.length
|
||||
verifyTranscriptPreferences();
|
||||
});
|
||||
|
||||
it('resets to active preferences when clicked on cancel', function() {
|
||||
var selectedProvider = '3PlayMedia';
|
||||
|
||||
renderCourseVideoSettingsView(
|
||||
activeTranscriptPreferences,
|
||||
transcriptionPlans,
|
||||
transcriptOrganizationCredentials
|
||||
);
|
||||
|
||||
// Now check values are assigned correctly.
|
||||
expect(courseVideoSettingsView.selectedProvider, activeTranscriptPreferences.provider);
|
||||
expect(courseVideoSettingsView.selectedTurnaroundPlan, activeTranscriptPreferences.cielo24_turnaround);
|
||||
expect(courseVideoSettingsView.selectedFidelityPlan, activeTranscriptPreferences.cielo24_fidelity);
|
||||
expect(
|
||||
courseVideoSettingsView.selectedSourceLanguage,
|
||||
activeTranscriptPreferences.video_source_language
|
||||
// First check preferance are selected correctly in HTML.
|
||||
verifyTranscriptPreferences();
|
||||
expect(courseVideoSettingsView.selectedProvider, providers.Cielo24);
|
||||
|
||||
// Now change preferences.
|
||||
// Select provider.
|
||||
changeProvider(selectedProvider);
|
||||
expect(courseVideoSettingsView.selectedProvider, selectedProvider);
|
||||
|
||||
// Select turnaround.
|
||||
selectPreference(
|
||||
'.transcript-turnaround',
|
||||
transcriptionPlans[selectedProvider].turnaround.default
|
||||
);
|
||||
expect(courseVideoSettingsView.selectedLanguages, activeTranscriptPreferences.preferred_languages);
|
||||
expect(
|
||||
courseVideoSettingsView.selectedTurnaroundPlan,
|
||||
transcriptionPlans[selectedProvider].turnaround.default
|
||||
);
|
||||
|
||||
// Now click cancel button and verify active preferences are shown.
|
||||
$courseVideoSettingsEl.find('.action-cancel-course-video-settings').click();
|
||||
verifyTranscriptPreferences();
|
||||
expect(courseVideoSettingsView.selectedProvider, providers.Cielo24);
|
||||
});
|
||||
|
||||
it('shows video source language directly in case of 3Play provider', function() {
|
||||
var sourceLanguages,
|
||||
selectedProvider = '3PlayMedia';
|
||||
|
||||
// Select CIELIO24 provider
|
||||
chooseProvider(selectedProvider);
|
||||
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
|
||||
|
||||
// Select provider
|
||||
changeProvider(selectedProvider);
|
||||
expect(courseVideoSettingsView.selectedProvider).toEqual(selectedProvider);
|
||||
|
||||
// Verify source langauges menu is shown.
|
||||
@@ -219,10 +420,10 @@ define(
|
||||
selectedProvider = 'Cielo24',
|
||||
selectedFidelity = 'PROFESSIONAL';
|
||||
|
||||
renderCourseVideoSettingsView(null, transcriptionPlans);
|
||||
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
|
||||
|
||||
// Select CIELIO24 provider
|
||||
chooseProvider(selectedProvider);
|
||||
// Select provider
|
||||
changeProvider(selectedProvider);
|
||||
expect(courseVideoSettingsView.selectedProvider).toEqual(selectedProvider);
|
||||
|
||||
// Verify source language is not shown.
|
||||
@@ -254,8 +455,10 @@ define(
|
||||
selectedProvider = 'Cielo24',
|
||||
selectedFidelity = 'PROFESSIONAL';
|
||||
|
||||
// Select CIELIO24 provider
|
||||
chooseProvider(selectedProvider);
|
||||
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
|
||||
|
||||
// Select provider
|
||||
changeProvider(selectedProvider);
|
||||
expect(courseVideoSettingsView.selectedProvider).toEqual(selectedProvider);
|
||||
|
||||
// Select fidelity
|
||||
@@ -283,8 +486,10 @@ define(
|
||||
selectedProvider = 'Cielo24',
|
||||
selectedFidelity = 'MECHANICAL';
|
||||
|
||||
// Select CIELIO24 provider
|
||||
chooseProvider(selectedProvider);
|
||||
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
|
||||
|
||||
// Select provider
|
||||
changeProvider(selectedProvider);
|
||||
expect(courseVideoSettingsView.selectedProvider).toEqual(selectedProvider);
|
||||
|
||||
// Select fidelity
|
||||
@@ -332,37 +537,16 @@ define(
|
||||
});
|
||||
|
||||
// Verify that success message is shown.
|
||||
expect($courseVideoSettingsEl.find('.course-video-settings-message-wrapper.success').html()).toEqual(
|
||||
'<div class="course-video-settings-message">' +
|
||||
'<span class="icon fa fa-check-circle" aria-hidden="true"></span>' +
|
||||
'<span>Settings updated</span>' +
|
||||
'</div>'
|
||||
);
|
||||
verifyMessage('success', 'Settings updated');
|
||||
});
|
||||
|
||||
it('removes transcript settings on update settings button click when no provider is selected', function() {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
|
||||
// Set no provider selected
|
||||
courseVideoSettingsView.selectedProvider = null;
|
||||
$courseVideoSettingsEl.find('.action-update-course-video-settings').click();
|
||||
|
||||
AjaxHelpers.expectRequest(
|
||||
requests,
|
||||
'DELETE',
|
||||
transcriptPreferencesUrl
|
||||
);
|
||||
|
||||
// Send successful empty content response.
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
// Reset to None provider
|
||||
resetProvider();
|
||||
verifyProviderList(providers.none);
|
||||
|
||||
// Verify that success message is shown.
|
||||
expect($courseVideoSettingsEl.find('.course-video-settings-message-wrapper.success').html()).toEqual(
|
||||
'<div class="course-video-settings-message">' +
|
||||
'<span class="icon fa fa-check-circle" aria-hidden="true"></span>' +
|
||||
'<span>Settings updated</span>' +
|
||||
'</div>'
|
||||
);
|
||||
verifyMessage('success', 'Automatic transcripts are disabled.');
|
||||
});
|
||||
|
||||
it('shows error message if server sends error', function() {
|
||||
@@ -390,12 +574,7 @@ define(
|
||||
});
|
||||
|
||||
// Verify that error message is shown.
|
||||
expect($courseVideoSettingsEl.find('.course-video-settings-message-wrapper.error').html()).toEqual(
|
||||
'<div class="course-video-settings-message">' +
|
||||
'<span class="icon fa fa-info-circle" aria-hidden="true"></span>' +
|
||||
'<span>Error message</span>' +
|
||||
'</div>'
|
||||
);
|
||||
verifyMessage('error', 'Error message');
|
||||
});
|
||||
|
||||
it('implies preferences are required if not selected when saving preferances', function() {
|
||||
@@ -423,8 +602,203 @@ define(
|
||||
verifyPreferanceErrorState($courseVideoSettingsEl.find('.transcript-languages-wrapper'), false);
|
||||
});
|
||||
|
||||
it('shows provider selected view if active provider is present', function() {
|
||||
var $selectedProviderContainerEl = $courseVideoSettingsEl.find('.transcript-provider-wrapper .selected-transcript-provider'); // eslint-disable-line max-len
|
||||
expect($selectedProviderContainerEl.find('span').html()).toEqual(courseVideoSettingsView.selectedProvider); // eslint-disable-line max-len
|
||||
expect($selectedProviderContainerEl.find('button.action-change-provider')).toExist();
|
||||
// Verify provider list view is not shown.
|
||||
expect($courseVideoSettingsEl.find('.transcript-provider-wrapper .transcript-provider-group')).not.toExist(); // eslint-disable-line max-len
|
||||
});
|
||||
|
||||
it('does not show transcript preferences or organization credentials if None provider is saved', function() { // eslint-disable-line max-len
|
||||
renderCourseVideoSettingsView(null, transcriptionPlans);
|
||||
|
||||
// Check None provider
|
||||
resetProvider();
|
||||
verifyProviderList(providers.none);
|
||||
|
||||
// Verify selected provider view is not shown.
|
||||
expect($courseVideoSettingsEl.find('.transcript-provider-wrapper .selected-transcript-provider')).not.toExist(); // eslint-disable-line max-len
|
||||
});
|
||||
|
||||
it('does not show transcript preferences or organization credentials if None provider is checked', function() { // eslint-disable-line max-len
|
||||
renderCourseVideoSettingsView(null, transcriptionPlans);
|
||||
|
||||
// Check None provider
|
||||
resetProvider();
|
||||
verifyProviderList(providers.none);
|
||||
|
||||
// Verify selected provider view is not shown.
|
||||
expect($courseVideoSettingsEl.find('.transcript-provider-wrapper .selected-transcript-provider')).not.toExist(); // eslint-disable-line max-len
|
||||
// Verify transcript preferences are not shown.
|
||||
expect($courseVideoSettingsEl.find('.course-video-transcript-preferances-wrapper')).not.toExist();
|
||||
// Verify org credentials are not shown.
|
||||
expect($courseVideoSettingsEl.find('.organization-credentials-content')).not.toExist();
|
||||
});
|
||||
|
||||
it('shows organization credentials when organization credentials for selected provider are not present', function() { // eslint-disable-line max-len
|
||||
renderCourseVideoSettingsView(null, transcriptionPlans);
|
||||
|
||||
// Check Cielo24 provider
|
||||
changeProvider(providers.Cielo24.key);
|
||||
verifyProviderList(providers.Cielo24);
|
||||
|
||||
// Verify organization credentials are shown.
|
||||
verifyOrganizationCredentialsView();
|
||||
|
||||
// Verify transcript preferences are not shown.
|
||||
expect($courseVideoSettingsEl.find('.course-video-transcript-preferances-wrapper')).not.toExist();
|
||||
});
|
||||
|
||||
it('shows transcript preferences when organization credentials for selected provider are present', function() { // eslint-disable-line max-len
|
||||
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
|
||||
|
||||
// Check Cielo24 provider
|
||||
changeProvider('Cielo24');
|
||||
verifyProviderList(providers.Cielo24);
|
||||
|
||||
// Verify organization credentials are not shown.
|
||||
expect($courseVideoSettingsEl.find('.organization-credentials-content')).not.toExist();
|
||||
|
||||
// Verify transcript preferences are shown.
|
||||
verifyTranscriptPreferencesView();
|
||||
});
|
||||
|
||||
it('shows organization credentials view if clicked on change provider button', function() {
|
||||
// Verify organization credentials view is not shown initially.
|
||||
expect($courseVideoSettingsEl.find('.organization-credentials-content')).not.toExist();
|
||||
|
||||
verifyProviderSelectedView();
|
||||
// Click change button to render organization credentials view.
|
||||
$courseVideoSettingsEl.find('.action-change-provider').click();
|
||||
|
||||
// Verify organization credentials is now shown.
|
||||
verifyOrganizationCredentialsView();
|
||||
});
|
||||
|
||||
it('shows cielo specific organization credentials fields only', function() {
|
||||
verifyProviderSelectedView();
|
||||
// Click change button to render organization credentials view.
|
||||
$courseVideoSettingsEl.find('.action-change-provider').click();
|
||||
|
||||
// Verify api key is present.
|
||||
verifyCredentialFieldsPresent({
|
||||
'api-key': 'API Key',
|
||||
username: 'Username'
|
||||
});
|
||||
});
|
||||
|
||||
it('shows 3play specific organization credentials fields only', function() {
|
||||
// Set selected provider to 3Play Media
|
||||
changeProvider('3PlayMedia');
|
||||
|
||||
// Verify api key and api secret input fields are present.
|
||||
verifyCredentialFieldsPresent({
|
||||
'api-key': 'API Key',
|
||||
'api-secret': 'API Secret'
|
||||
});
|
||||
});
|
||||
|
||||
it('shows warning message when changing organization credentials if present already', function() {
|
||||
// Set selectedProvider organization credentials.
|
||||
courseVideoSettingsView.transcriptOrganizationCredentials[courseVideoSettingsView.selectedProvider] = true; // eslint-disable-line max-len
|
||||
|
||||
verifyProviderSelectedView();
|
||||
// Click change button to render organization credentials view.
|
||||
$courseVideoSettingsEl.find('.action-change-provider').click();
|
||||
|
||||
// Verify credentials are shown
|
||||
verifyOrganizationCredentialsView();
|
||||
// Verify warning message is shown.
|
||||
expect($courseVideoSettingsEl.find('.transcription-account-details.warning')).toExist();
|
||||
// Verify message
|
||||
expect($courseVideoSettingsEl.find('.transcription-account-details').html()).toEqual(
|
||||
'<span>This action updates the ' + courseVideoSettingsView.selectedProvider +
|
||||
' information for your entire organization.</span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not show warning message when changing organization credentials if not present already', function() { // eslint-disable-line max-len
|
||||
verifyProviderSelectedView();
|
||||
// Click change button to render organization credentials view.
|
||||
$courseVideoSettingsEl.find('.action-change-provider').click();
|
||||
|
||||
// Verify warning message is not shown.
|
||||
expect($courseVideoSettingsEl.find('.transcription-account-details.warning')).not.toExist();
|
||||
// Initial detail message is shown instead.
|
||||
expect($courseVideoSettingsEl.find('.transcription-account-details').html()).toEqual(
|
||||
'<span>Enter the account information for your organization.</span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('shows validation errors if no organization credentials are provided when saving credentials', function() { // eslint-disable-line max-len
|
||||
// Set selected provider to 3Play Media
|
||||
changeProvider('3PlayMedia');
|
||||
|
||||
// Click save organization credentials button to save credentials.
|
||||
$courseVideoSettingsEl.find('.action-update-org-credentials').click();
|
||||
|
||||
verifyPreferanceErrorState(
|
||||
$courseVideoSettingsEl.find('.' + courseVideoSettingsView.selectedProvider + '-api-key-wrapper'),
|
||||
true
|
||||
);
|
||||
|
||||
verifyPreferanceErrorState(
|
||||
$courseVideoSettingsEl.find('.' + courseVideoSettingsView.selectedProvider + '-api-secret-wrapper'),
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('saves cielo organization credentials on clicking save credentials button', function() {
|
||||
verifyProviderSelectedView();
|
||||
submitOrganizationCredentials({
|
||||
api_key: 'api-key',
|
||||
username: 'username'
|
||||
});
|
||||
|
||||
verifyCredentialsSaved();
|
||||
});
|
||||
|
||||
it('saves 3Play organization credentials on clicking save credentials button', function() {
|
||||
verifyProviderSelectedView();
|
||||
|
||||
// Set selected provider to 3Play Media
|
||||
changeProvider('3PlayMedia');
|
||||
|
||||
submitOrganizationCredentials({
|
||||
api_key: 'api-key',
|
||||
api_secret_key: 'api-secret'
|
||||
});
|
||||
|
||||
verifyCredentialsSaved();
|
||||
});
|
||||
|
||||
it('shows error message on saving organization credentials if server sends bad request error', function() {
|
||||
verifyProviderSelectedView();
|
||||
|
||||
submitOrganizationCredentials({
|
||||
api_key: 'api-key',
|
||||
username: 'username'
|
||||
}, 400, 'Error saving credentials.');
|
||||
|
||||
// Verify that bad request error message is shown.
|
||||
verifyMessage('error', 'Error saving credentials.');
|
||||
});
|
||||
|
||||
it('shows error message on saving organization credentials if server sends error', function() {
|
||||
verifyProviderSelectedView();
|
||||
|
||||
submitOrganizationCredentials({
|
||||
api_key: 'api-key',
|
||||
username: 'username'
|
||||
}, 500);
|
||||
|
||||
// Verify that server error message is shown.
|
||||
verifyMessage('error', INTERNAL_SERVER_ERROR);
|
||||
});
|
||||
|
||||
// TODO: Add more tests like clicking on add language, remove and their scenarios and some other tests
|
||||
// like N/A selected, specific provider selected tests, specific preferance selected tests etc.
|
||||
// for specific preferance selected tests etc. - See EDUCATOR-1478
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -42,6 +42,7 @@ define([
|
||||
this.concurrentUploadLimit = options.concurrentUploadLimit || 0;
|
||||
this.postUrl = options.postUrl;
|
||||
this.activeTranscriptPreferences = options.activeTranscriptPreferences;
|
||||
this.transcriptOrganizationCredentials = options.transcriptOrganizationCredentials;
|
||||
this.videoTranscriptSettings = options.videoTranscriptSettings;
|
||||
this.isVideoTranscriptEnabled = options.isVideoTranscriptEnabled;
|
||||
this.videoSupportedFileFormats = options.videoSupportedFileFormats;
|
||||
@@ -85,6 +86,7 @@ define([
|
||||
if (this.isVideoTranscriptEnabled) {
|
||||
this.courseVideoSettingsView = new CourseVideoSettingsView({
|
||||
activeTranscriptPreferences: this.activeTranscriptPreferences,
|
||||
transcriptOrganizationCredentials: this.transcriptOrganizationCredentials,
|
||||
videoTranscriptSettings: this.videoTranscriptSettings
|
||||
});
|
||||
this.courseVideoSettingsView.render();
|
||||
|
||||
@@ -3,16 +3,26 @@
|
||||
*/
|
||||
define([
|
||||
'jquery', 'backbone', 'underscore', 'gettext', 'moment',
|
||||
'common/js/components/utils/view_utils',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'edx-ui-toolkit/js/utils/string-utils',
|
||||
'text!templates/course-video-settings.underscore'
|
||||
'text!templates/course-video-settings.underscore',
|
||||
'text!templates/course-video-transcript-preferences.underscore',
|
||||
'text!templates/course-video-transcript-provider-empty.underscore',
|
||||
'text!templates/course-video-transcript-provider-selected.underscore',
|
||||
'text!templates/transcript-organization-credentials.underscore',
|
||||
'text!templates/course-video-settings-update-settings-footer.underscore',
|
||||
'text!templates/course-video-settings-update-org-credentials-footer.underscore'
|
||||
],
|
||||
function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSettingsTemplate) {
|
||||
function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, TranscriptSettingsTemplate,
|
||||
TranscriptPreferencesTemplate, TranscriptProviderEmptyStateTemplate, TranscriptProviderSelectedStateTemplate,
|
||||
OrganizationCredentialsTemplate, UpdateSettingsFooterTemplate, OrganizationCredentialsFooterTemplate) {
|
||||
'use strict';
|
||||
|
||||
var CourseVideoSettingsView,
|
||||
CIELO24 = 'Cielo24',
|
||||
THREE_PLAY_MEDIA = '3PlayMedia';
|
||||
THREE_PLAY_MEDIA = '3PlayMedia',
|
||||
INTERNAL_SERVER_ERROR_MESSAGE = gettext('An error has occurred. Wait a few minutes, and then try again.');
|
||||
|
||||
CourseVideoSettingsView = Backbone.View.extend({
|
||||
el: 'div.video-transcript-settings-wrapper',
|
||||
@@ -24,16 +34,27 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
'change #video-source-language': 'videoSourceLanguageSelected',
|
||||
'click .action-add-language': 'languageSelected',
|
||||
'click .action-remove-language': 'languageRemoved',
|
||||
'click .action-change-provider': 'renderOrganizationCredentials',
|
||||
'click .action-update-org-credentials': 'updateOrganizationCredentials',
|
||||
'click .action-update-course-video-settings': 'updateCourseVideoSettings',
|
||||
'click .action-cancel-course-video-settings': 'discardChanges',
|
||||
'click .action-close-course-video-settings': 'closeCourseVideoSettings'
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
var videoTranscriptSettings = options.videoTranscriptSettings;
|
||||
this.activeTranscriptionPlan = options.activeTranscriptPreferences;
|
||||
this.transcriptOrganizationCredentials = _.extend({}, options.transcriptOrganizationCredentials);
|
||||
this.availableTranscriptionPlans = videoTranscriptSettings.transcription_plans;
|
||||
this.transcriptHandlerUrl = videoTranscriptSettings.transcript_preferences_handler_url;
|
||||
this.transcriptCredentialsHandlerUrl = videoTranscriptSettings.transcript_credentials_handler_url;
|
||||
this.template = HtmlUtils.template(TranscriptSettingsTemplate);
|
||||
this.transcriptPreferencesTemplate = HtmlUtils.template(TranscriptPreferencesTemplate);
|
||||
this.organizationCredentialsTemplate = HtmlUtils.template(OrganizationCredentialsTemplate);
|
||||
this.organizationCredentialsFooterTemplate = HtmlUtils.template(OrganizationCredentialsFooterTemplate);
|
||||
this.updateSettingsFooterTemplate = HtmlUtils.template(UpdateSettingsFooterTemplate);
|
||||
this.transcriptProviderEmptyStateTemplate = HtmlUtils.template(TranscriptProviderEmptyStateTemplate);
|
||||
this.transcriptProviderSelectedStateTemplate = HtmlUtils.template(TranscriptProviderSelectedStateTemplate);
|
||||
this.setActiveTranscriptPlanData();
|
||||
this.selectedLanguages = [];
|
||||
},
|
||||
@@ -49,7 +70,7 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
|
||||
// Click anywhere outside the course video settings pane would close the pane.
|
||||
$(document).click(function(event) {
|
||||
// if the target of the click isn't the container nor a descendant of the contain
|
||||
// If the target of the click isn't the container nor a descendant of the contain
|
||||
if (!self.$el.is(event.target) && self.$el.has(event.target).length === 0) {
|
||||
self.closeCourseVideoSettings();
|
||||
}
|
||||
@@ -160,7 +181,35 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
providerSelected: function(event) {
|
||||
this.resetPlanData();
|
||||
this.selectedProvider = event.target.value;
|
||||
this.renderPreferences();
|
||||
// Re-render view
|
||||
this.reRenderView();
|
||||
},
|
||||
|
||||
reRenderView: function() {
|
||||
var $courseVideoSettingsContentEl = this.$el.find('.course-video-settings-content'),
|
||||
dateModified = this.activeTranscriptionPlan ?
|
||||
moment.utc(this.activeTranscriptionPlan.modified).format('ll') : '';
|
||||
|
||||
if (!this.selectedProvider) {
|
||||
// Hide organization credentials and transcript preferences views
|
||||
$courseVideoSettingsContentEl.hide();
|
||||
|
||||
// Render footer
|
||||
HtmlUtils.setHtml(
|
||||
this.$el.find('.course-video-settings-footer'),
|
||||
this.updateSettingsFooterTemplate({
|
||||
dateModified: dateModified
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
$courseVideoSettingsContentEl.show();
|
||||
// If org provider specific credentials are present
|
||||
if (this.transcriptOrganizationCredentials[this.selectedProvider]) {
|
||||
this.renderTranscriptPreferences();
|
||||
} else {
|
||||
this.renderOrganizationCredentials();
|
||||
}
|
||||
},
|
||||
|
||||
languageSelected: function(event) {
|
||||
@@ -187,43 +236,56 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
$(event.target.parentElement).parent().remove();
|
||||
|
||||
// Remove language from selected languages.
|
||||
this.selectedLanguages = _.without(this.selectedLanguages, selectedLanguage);
|
||||
this.selectedLanguages = this.activeLanguages = _.without(this.selectedLanguages, selectedLanguage);
|
||||
|
||||
// Populate menu again to reflect latest changes.
|
||||
this.populateLanguageMenu();
|
||||
},
|
||||
|
||||
renderProviders: function() {
|
||||
var self = this,
|
||||
providerPlan = self.availableTranscriptionPlans,
|
||||
$providerEl = self.$el.find('.transcript-provider-group');
|
||||
renderProviders: function(state) {
|
||||
var $transcriptProviderWrapperEl = this.$el.find('.transcript-provider-wrapper');
|
||||
if (!state) {
|
||||
state = this.selectedProvider ? 'selected' : 'empty'; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
if (providerPlan) {
|
||||
// If no transcription plans are sentm return.
|
||||
if (!this.availableTranscriptionPlans) {
|
||||
return;
|
||||
}
|
||||
if (state === 'empty') {
|
||||
HtmlUtils.setHtml(
|
||||
$providerEl,
|
||||
HtmlUtils.interpolateHtml(
|
||||
HtmlUtils.HTML('<input type="radio" id="transcript-provider-none" name="transcript-provider" value="" {checked}/><label for="transcript-provider-none">{text}</label>'), // eslint-disable-line max-len
|
||||
{
|
||||
text: gettext('N/A'),
|
||||
checked: self.selectedProvider === '' ? 'checked' : ''
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
_.each(providerPlan, function(providerObject, key) {
|
||||
var checked = self.selectedProvider === key ? 'checked' : '';
|
||||
HtmlUtils.append(
|
||||
$providerEl,
|
||||
HtmlUtils.interpolateHtml(
|
||||
HtmlUtils.HTML('<input type="radio" id="transcript-provider-{value}" name="transcript-provider" value="{value}" {checked}/><label for="transcript-provider-{value}">{text}'), // eslint-disable-line max-len
|
||||
$transcriptProviderWrapperEl,
|
||||
this.transcriptProviderEmptyStateTemplate({
|
||||
providers: [
|
||||
{
|
||||
text: providerObject.display_name,
|
||||
value: key,
|
||||
checked: checked
|
||||
key: 'none',
|
||||
value: '',
|
||||
name: gettext('None'),
|
||||
checked: this.selectedProvider === '' ? 'checked' : ''
|
||||
},
|
||||
{
|
||||
key: CIELO24,
|
||||
value: CIELO24,
|
||||
name: this.availableTranscriptionPlans[CIELO24].display_name,
|
||||
checked: this.selectedProvider === CIELO24 ? 'checked' : ''
|
||||
},
|
||||
{
|
||||
key: THREE_PLAY_MEDIA,
|
||||
value: THREE_PLAY_MEDIA,
|
||||
name: this.availableTranscriptionPlans[THREE_PLAY_MEDIA].display_name,
|
||||
checked: this.selectedProvider === THREE_PLAY_MEDIA ? 'checked' : ''
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
]
|
||||
})
|
||||
);
|
||||
} else {
|
||||
HtmlUtils.setHtml(
|
||||
$transcriptProviderWrapperEl,
|
||||
this.transcriptProviderSelectedStateTemplate({
|
||||
selectedProvider: this.availableTranscriptionPlans[this.selectedProvider].display_name
|
||||
})
|
||||
);
|
||||
this.renderTranscriptPreferences();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -284,6 +346,10 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
|
||||
renderTargetLanguages: function() {
|
||||
var self = this,
|
||||
// Merge active and selected languages, this handles the case when active languages are present and
|
||||
// user also has selected some languages but not saved, user changes organization credentials,
|
||||
// both active and selected languages should be rendered.
|
||||
selectedLanguages = _.union(self.activeLanguages, self.selectedLanguages),
|
||||
$languagesPreferenceContainer = self.$el.find('.transcript-languages-wrapper'),
|
||||
$languagesContainer = self.$el.find('.languages-container');
|
||||
|
||||
@@ -292,9 +358,9 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
|
||||
$languagesContainer.empty();
|
||||
|
||||
// Show language container if source language is selected .
|
||||
// Show language container if source language is selected.
|
||||
if (self.selectedVideoSourceLanguage) {
|
||||
_.each(self.activeLanguages, function(language) {
|
||||
_.each(selectedLanguages, function(language) {
|
||||
// Only add if not in the list already.
|
||||
if (_.indexOf(self.selectedLanguages, language) === -1) {
|
||||
self.selectedLanguages.push(language);
|
||||
@@ -386,14 +452,6 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
}
|
||||
},
|
||||
|
||||
renderPreferences: function() {
|
||||
this.renderProviders();
|
||||
this.renderTurnaround();
|
||||
this.renderFidelity();
|
||||
this.renderSourceLanguages();
|
||||
this.renderTargetLanguages();
|
||||
},
|
||||
|
||||
addLanguage: function(language) {
|
||||
var $languagesContainer = this.$el.find('.languages-container');
|
||||
HtmlUtils.append(
|
||||
@@ -419,8 +477,9 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
);
|
||||
},
|
||||
|
||||
updateSuccessResponseStatus: function(data) {
|
||||
updateSuccessResponseStatus: function(data, successMessage) {
|
||||
var dateModified = data ? moment.utc(data.modified).format('ll') : '';
|
||||
successMessage = successMessage ? successMessage : gettext('Settings updated'); // eslint-disable-line no-param-reassign, no-unneeded-ternary, max-len
|
||||
|
||||
// Update last modified date
|
||||
if (dateModified) {
|
||||
@@ -436,7 +495,10 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
);
|
||||
}
|
||||
|
||||
this.renderResponseStatus(gettext('Settings updated'), 'success');
|
||||
// Now re-render providers state.
|
||||
this.renderProviders();
|
||||
|
||||
this.renderResponseStatus(successMessage, 'success');
|
||||
// Sync ActiveUploadListView with latest active plan.
|
||||
this.activeTranscriptionPlan = data;
|
||||
Backbone.trigger('coursevideosettings:syncActiveTranscriptPreferences', this.activeTranscriptionPlan);
|
||||
@@ -448,9 +510,8 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
// show some error to user
|
||||
try {
|
||||
errorMessage = $.parseJSON(data).error;
|
||||
} catch (e) {} // eslint-disable-line no-empty
|
||||
errorMessage = errorMessage || gettext('Error saving data');
|
||||
this.renderResponseStatus(errorMessage, 'error');
|
||||
} catch (e) {} // eslint-disable-line no-empty
|
||||
this.renderResponseStatus(errorMessage || INTERNAL_SERVER_ERROR_MESSAGE, 'error');
|
||||
},
|
||||
|
||||
renderResponseStatus: function(responseText, type) {
|
||||
@@ -548,6 +609,46 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
return isValid;
|
||||
},
|
||||
|
||||
validateOrganizationCredentials: function() {
|
||||
var $OrganizationApiSecretWrapperEl,
|
||||
$OrganizationUsernameWrapperEl,
|
||||
isValid = true,
|
||||
$OrganizationApiKeyWrapperEl = this.$el.find('.' + this.selectedProvider + '-api-key-wrapper');
|
||||
|
||||
|
||||
// Explicit None selected case.
|
||||
if (this.selectedProvider === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($OrganizationApiKeyWrapperEl.find('input').val() === '') {
|
||||
isValid = false;
|
||||
this.addErrorState($OrganizationApiKeyWrapperEl);
|
||||
} else {
|
||||
this.clearPreferenceErrorState($OrganizationApiKeyWrapperEl);
|
||||
}
|
||||
|
||||
if (this.selectedProvider === THREE_PLAY_MEDIA) {
|
||||
$OrganizationApiSecretWrapperEl = this.$el.find('.' + this.selectedProvider + '-api-secret-wrapper');
|
||||
if ($OrganizationApiSecretWrapperEl.find('input').val() === '') {
|
||||
isValid = false;
|
||||
this.addErrorState($OrganizationApiSecretWrapperEl);
|
||||
} else {
|
||||
this.clearPreferenceErrorState($OrganizationApiSecretWrapperEl);
|
||||
}
|
||||
} else {
|
||||
$OrganizationUsernameWrapperEl = this.$el.find('.' + this.selectedProvider + '-username-wrapper');
|
||||
if ($OrganizationUsernameWrapperEl.find('input').val() === '') {
|
||||
isValid = false;
|
||||
this.addErrorState($OrganizationUsernameWrapperEl);
|
||||
} else {
|
||||
this.clearPreferenceErrorState($OrganizationUsernameWrapperEl);
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
},
|
||||
|
||||
saveTranscriptPreferences: function() {
|
||||
var self = this,
|
||||
responseTranscriptPreferences;
|
||||
@@ -567,24 +668,69 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
responseTranscriptPreferences = data ? data.transcript_preferences : null;
|
||||
self.updateSuccessResponseStatus(responseTranscriptPreferences);
|
||||
}).fail(function(jqXHR) {
|
||||
if (jqXHR.responseText) {
|
||||
self.updateFailResponseStatus(jqXHR.responseText);
|
||||
}
|
||||
self.updateFailResponseStatus(jqXHR.responseText);
|
||||
});
|
||||
} else {
|
||||
$.ajax({
|
||||
type: 'DELETE',
|
||||
url: self.transcriptHandlerUrl
|
||||
}).done(function() {
|
||||
self.updateSuccessResponseStatus(null);
|
||||
responseTranscriptPreferences = null;
|
||||
self.updateSuccessResponseStatus(
|
||||
responseTranscriptPreferences,
|
||||
gettext('Automatic transcripts are disabled.')
|
||||
);
|
||||
}).fail(function(jqXHR) {
|
||||
if (jqXHR.responseText) {
|
||||
self.updateFailResponseStatus(jqXHR.responseText);
|
||||
}
|
||||
self.updateFailResponseStatus(jqXHR.responseText);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
saveOrganizationCredentials: function() {
|
||||
var self = this,
|
||||
username,
|
||||
apiSecret,
|
||||
apiKey = this.$el.find('.' + this.selectedProvider + '-api-key').val();
|
||||
|
||||
// First clear response status if present already
|
||||
this.clearResponseStatus();
|
||||
|
||||
if (this.selectedProvider === THREE_PLAY_MEDIA) {
|
||||
apiSecret = this.$el.find('.' + this.selectedProvider + '-api-secret').val();
|
||||
} else {
|
||||
username = this.$el.find('.' + this.selectedProvider + '-username').val();
|
||||
}
|
||||
|
||||
$.postJSON(self.transcriptCredentialsHandlerUrl, {
|
||||
provider: self.selectedProvider,
|
||||
api_key: apiKey,
|
||||
api_secret_key: apiSecret,
|
||||
username: username,
|
||||
global: false // Do not trigger global AJAX error handler
|
||||
}, function() {
|
||||
self.$el.find('.organization-credentials-wrapper').hide();
|
||||
|
||||
// Update org credentials for selected provider
|
||||
self.transcriptOrganizationCredentials[self.selectedProvider] = true;
|
||||
|
||||
self.updateSuccessResponseStatus(
|
||||
self.activeTranscriptionPlan,
|
||||
gettext('{selectedProvider} credentials saved').replace(
|
||||
'{selectedProvider}',
|
||||
self.availableTranscriptionPlans[self.selectedProvider].display_name
|
||||
)
|
||||
);
|
||||
}).fail(function(jqXHR) {
|
||||
self.updateFailResponseStatus(jqXHR.responseText);
|
||||
});
|
||||
},
|
||||
|
||||
updateOrganizationCredentials: function() {
|
||||
if (this.validateOrganizationCredentials()) {
|
||||
this.saveOrganizationCredentials();
|
||||
}
|
||||
},
|
||||
|
||||
updateCourseVideoSettings: function() {
|
||||
var $messageWrapperEl = this.$el.find('.course-video-settings-message-wrapper');
|
||||
if (this.validateCourseVideoSettings()) {
|
||||
@@ -594,17 +740,81 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
}
|
||||
},
|
||||
|
||||
discardChanges: function() {
|
||||
this.setActiveTranscriptPlanData();
|
||||
// Re-render views
|
||||
this.renderProviders();
|
||||
this.reRenderView();
|
||||
},
|
||||
|
||||
renderOrganizationCredentials: function() {
|
||||
var $courseVideoSettingsContentEl = this.$el.find('.course-video-settings-content');
|
||||
|
||||
// Render empty state providers view.
|
||||
this.renderProviders('empty');
|
||||
|
||||
HtmlUtils.setHtml(
|
||||
$courseVideoSettingsContentEl,
|
||||
this.organizationCredentialsTemplate({
|
||||
selectedProvider: {
|
||||
key: this.selectedProvider,
|
||||
name: this.availableTranscriptionPlans[this.selectedProvider].display_name
|
||||
},
|
||||
organizationCredentialsExists: this.transcriptOrganizationCredentials[this.selectedProvider],
|
||||
CIELO24: CIELO24,
|
||||
THREE_PLAY_MEDIA: THREE_PLAY_MEDIA
|
||||
})
|
||||
);
|
||||
// Render footer
|
||||
HtmlUtils.setHtml(
|
||||
this.$el.find('.course-video-settings-footer'),
|
||||
this.organizationCredentialsFooterTemplate({})
|
||||
);
|
||||
},
|
||||
|
||||
renderTranscriptPreferences: function() {
|
||||
var $courseVideoSettingsContentEl = this.$el.find('.course-video-settings-content'),
|
||||
dateModified = this.activeTranscriptionPlan ?
|
||||
moment.utc(this.activeTranscriptionPlan.modified).format('ll') : '';
|
||||
|
||||
HtmlUtils.setHtml(
|
||||
$courseVideoSettingsContentEl,
|
||||
this.transcriptPreferencesTemplate({
|
||||
selectedProvider: this.selectedProvider,
|
||||
THREE_PLAY_MEDIA: THREE_PLAY_MEDIA
|
||||
})
|
||||
);
|
||||
|
||||
// Render transcript preferences.
|
||||
this.renderTurnaround();
|
||||
this.renderFidelity();
|
||||
this.renderSourceLanguages();
|
||||
this.renderTargetLanguages();
|
||||
|
||||
// Render footer
|
||||
HtmlUtils.setHtml(
|
||||
this.$el.find('.course-video-settings-footer'),
|
||||
this.updateSettingsFooterTemplate({
|
||||
dateModified: dateModified
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var dateModified = this.activeTranscriptionPlan ?
|
||||
moment.utc(this.activeTranscriptionPlan.modified).format('ll') : '';
|
||||
|
||||
HtmlUtils.setHtml(this.$el, this.template({}));
|
||||
|
||||
// Render footer
|
||||
HtmlUtils.setHtml(
|
||||
this.$el,
|
||||
this.template({
|
||||
this.$el.find('.course-video-settings-footer'),
|
||||
this.updateSettingsFooterTemplate({
|
||||
dateModified: dateModified
|
||||
})
|
||||
);
|
||||
|
||||
this.renderPreferences();
|
||||
this.renderProviders();
|
||||
|
||||
this.registerCloseClickHandler();
|
||||
this.setFixedCourseVideoSettingsPane();
|
||||
@@ -640,7 +850,7 @@ function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSett
|
||||
},
|
||||
|
||||
closeCourseVideoSettings: function() {
|
||||
// TODO: Slide out when closing settings pane. We may need to hide the view instead of destroying it.
|
||||
// TODO: Slide out when closing settings pane. See EDUCATOR-1477
|
||||
|
||||
// Trigger destroy transcript event.
|
||||
Backbone.trigger('coursevideosettings:destroyCourseVideoSettingsView');
|
||||
|
||||
@@ -83,6 +83,15 @@
|
||||
border: solid 1px $state-danger-border;
|
||||
}
|
||||
|
||||
.organization-credentials-content {
|
||||
margin-top: ($baseline*1.6);
|
||||
.org-credentials-wrapper input {
|
||||
width: 65%;
|
||||
margin-top: ($baseline*0.8);
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.transcript-preferance-wrapper {
|
||||
margin-top: ($baseline*1.6);
|
||||
.icon.fa-info-circle {
|
||||
@@ -99,6 +108,7 @@
|
||||
|
||||
.error-info {
|
||||
@include font-size(16);
|
||||
@include margin-left($baseline/2);
|
||||
}
|
||||
|
||||
.transcript-preferance-label {
|
||||
@@ -108,10 +118,15 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.transcript-provider-group, .transcript-turnaround, .transcript-fidelity, .video-source-language {
|
||||
.transcript-provider-group, .transcript-turnaround, .transcript-fidelity, .video-source-language, .selected-transcript-provider {
|
||||
margin-top: ($baseline*0.8);
|
||||
}
|
||||
|
||||
.selected-transcript-provider {
|
||||
.action-change-provider {
|
||||
@include margin-left($baseline/2);
|
||||
}
|
||||
}
|
||||
|
||||
.transcript-provider-group {
|
||||
input[type=radio] {
|
||||
@@ -128,6 +143,10 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.transcript-languages-wrapper .transcript-preferance-label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.transcript-languages-container .languages-container {
|
||||
margin-top: ($baseline*0.8);
|
||||
.transcript-language-container {
|
||||
@@ -148,6 +167,9 @@
|
||||
.action-add-language {
|
||||
@include margin-left($baseline/4);
|
||||
}
|
||||
.error-info {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.transcript-language-menu, .video-source-language {
|
||||
@@ -155,11 +177,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
.transcription-account-details {
|
||||
margin-top: ($baseline*0.8);
|
||||
span {
|
||||
@include font-size(15);
|
||||
}
|
||||
}
|
||||
|
||||
.transcription-account-details.warning {
|
||||
background-color: $state-warning-bg;
|
||||
padding: ($baseline/2);
|
||||
}
|
||||
|
||||
.action-cancel-course-video-settings {
|
||||
@include margin-right($baseline/2);
|
||||
}
|
||||
|
||||
.course-video-settings-footer {
|
||||
margin-top: ($baseline*1.6);
|
||||
.last-updated-text {
|
||||
@include font-size(12);
|
||||
@include margin-left($baseline/4);
|
||||
display: block;
|
||||
margin-top: ($baseline/2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<button class='button-link action-cancel-course-video-settings' aria-describedby='cancel-button-text'>
|
||||
<%- gettext('Discard Changes') %>
|
||||
<span id='cancel-button-text' class='sr'><%-gettext('Press discard changes to discard your changes.') %></span>
|
||||
</button>
|
||||
<button class='button action-update-org-credentials' aria-describedby='update-org-credentials-button-text'>
|
||||
<%- gettext('Update Settings') %>
|
||||
<span id='update-org-credentials-button-text' class='sr'><%-gettext('Press update settings to update the information for your organization.') %></span>
|
||||
</button>
|
||||
@@ -0,0 +1,13 @@
|
||||
<button class='button-link action-cancel-course-video-settings' aria-describedby='cancel-button-text'>
|
||||
<%- gettext('Discard Changes') %>
|
||||
<span id='cancel-button-text' class='sr'><%-gettext('Press discard changes to discard changes.') %></span>
|
||||
</button>
|
||||
<button class="button action-update-course-video-settings" aria-describedby='update-button-text'>
|
||||
<%- gettext('Update Settings') %>
|
||||
<span id='update-button-text' class='sr'><%-gettext('Press update settings to update course video settings') %></span>
|
||||
</button>
|
||||
<span class='last-updated-text'>
|
||||
<%if (dateModified) { %>
|
||||
<%- gettext('Last updated')%> <%- dateModified %>
|
||||
<% } %>
|
||||
</span>
|
||||
@@ -11,49 +11,8 @@
|
||||
<div class='course-video-settings-wrapper'>
|
||||
<div class='course-video-settings-message-wrapper'></div>
|
||||
<span class="course-video-settings-title"><%- gettext('Course Video Settings') %></span>
|
||||
<div class='transcript-preferance-wrapper transcript-provider-wrapper'>
|
||||
<label class='transcript-preferance-label' for='transcript-provider'><%- gettext('Transcript Provider') %><span class='error-icon' aria-hidden="true"></span></label>
|
||||
<div class='transcript-provider-group' id='transcript-provider'></div>
|
||||
<span class='error-info' aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class='transcript-preferance-wrapper transcript-turnaround-wrapper'>
|
||||
<label class='transcript-preferance-label' for='transcript-turnaround'><%- gettext('Transcript Turnaround') %><span class='error-icon' aria-hidden="true"></span></label>
|
||||
<select id='transcript-turnaround' class='transcript-turnaround'></select>
|
||||
<span class='error-info' aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class='transcript-preferance-wrapper transcript-fidelity-wrapper'>
|
||||
<label class='transcript-preferance-label' for='transcript-fidelity'><%- gettext('Transcript Fidelity') %><span class='error-icon' aria-hidden="true"></span></label>
|
||||
<select id='transcript-fidelity' class='transcript-fidelity'></select>
|
||||
<span class='error-info' aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class='transcript-preferance-wrapper video-source-language-wrapper'>
|
||||
<label class='transcript-preferance-label' for='video-source-language'><%- gettext('Video Source Language') %><span class='error-icon' aria-hidden="true"></span></label>
|
||||
<select id='video-source-language' class='video-source-language' aria-labelledby="video-source-language-none"></select>
|
||||
<span class='error-info' aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class='transcript-preferance-wrapper transcript-languages-wrapper'>
|
||||
<span class='transcript-preferance-label'><%- gettext('Transcript Languages') %><span class='error-icon' aria-hidden="true"></span></span>
|
||||
<div class='transcript-languages-container'>
|
||||
<div class='languages-container'></div>
|
||||
<div class="transcript-language-menu-container">
|
||||
<select class="transcript-language-menu" id="transcript-language" aria-labelledby="transcript-language-none"></select>
|
||||
<div class="add-language-action">
|
||||
<button class="button-link action-add-language"><%- gettext('Add') %><span class="sr"><%- gettext('Press Add to language') %></span></button>
|
||||
<span class="error-info" aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='course-video-settings-footer'>
|
||||
<button class="button button action-update-course-video-settings" aria-describedby='update-button-text'>
|
||||
<%- gettext('Update Settings') %>
|
||||
<span id='update-button-text' class='sr'><%-gettext('Press update settings to update course video settings') %></span>
|
||||
</button>
|
||||
<span class='last-updated-text'>
|
||||
<%if (dateModified) { %>
|
||||
<%- gettext('Last updated')%> <%- dateModified %>
|
||||
<% } %>
|
||||
</span>
|
||||
</div>
|
||||
<div class='transcript-preferance-wrapper transcript-provider-wrapper'></div>
|
||||
<div class='course-video-settings-content'></div>
|
||||
<div class='course-video-settings-footer'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<div class='course-video-transcript-preferances-wrapper'>
|
||||
<div class='transcript-preferance-wrapper transcript-turnaround-wrapper'>
|
||||
<label class='transcript-preferance-label' for='transcript-turnaround'><%- gettext('Transcript Turnaround') %><span class='error-icon' aria-hidden="true"></span></label>
|
||||
<select id='transcript-turnaround' class='transcript-turnaround'></select>
|
||||
<span class='error-info' aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class='transcript-preferance-wrapper transcript-fidelity-wrapper'>
|
||||
<label class='transcript-preferance-label' for='transcript-fidelity'><%- gettext('Transcript Fidelity') %><span class='error-icon' aria-hidden="true"></span></label>
|
||||
<select id='transcript-fidelity' class='transcript-fidelity'></select>
|
||||
<span class='error-info' aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class='transcript-preferance-wrapper video-source-language-wrapper'>
|
||||
<label class='transcript-preferance-label' for='video-source-language'><%- gettext('Video Source Language') %><span class='error-icon' aria-hidden="true"></span></label>
|
||||
<select id='video-source-language' class='video-source-language' aria-labelledby="video-source-language-none"></select>
|
||||
<span class='error-info' aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class='transcript-preferance-wrapper transcript-languages-wrapper'>
|
||||
<span class='transcript-preferance-label'><%- gettext('Transcript Languages') %></span>
|
||||
<span class='error-icon' aria-hidden="true"></span>
|
||||
<div class='transcript-languages-container'>
|
||||
<div class='languages-container'></div>
|
||||
<div class="transcript-language-menu-container">
|
||||
<select class="transcript-language-menu" id="transcript-language" aria-labelledby="transcript-language-none"></select>
|
||||
<div class="add-language-action">
|
||||
<button class="button-link action-add-language"><%- gettext('Add') %><span class="sr"><%- gettext('Press Add to language') %></span></button>
|
||||
</div>
|
||||
<span class="error-info" aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<label class='transcript-preferance-label' for='transcript-provider'><%- gettext('Automated Transcripts') %></label>
|
||||
<div class="transcript-provider-group" id="transcript-provider">
|
||||
<% for (var i = 0; i < providers.length; i++) { %>
|
||||
<input type='radio' id='transcript-provider-<%- providers[i].key %>' name='transcript-provider' value='<%- providers[i].value %>' <%- providers[i].checked %>>
|
||||
<label for='transcript-provider-<%- providers[i].key %>'><%- providers[i].name %></label>
|
||||
<% } %>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
<label class='transcript-preferance-label' for='transcript-provider'><%- gettext('Transcript Provider') %></label>
|
||||
<div class='selected-transcript-provider'>
|
||||
<span class='title'><%- selectedProvider %></span>
|
||||
<button class='button-link action-change-provider' aria-describedby='change-provider-button-text'>
|
||||
<%- gettext('Change') %>
|
||||
<span id='change-provider-button-text' class='sr'><%-gettext('Press change to change selected transcript provider.') %></span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,43 @@
|
||||
<div class='organization-credentials-wrapper'>
|
||||
<div class='organization-credentials-content'>
|
||||
<label class='transcript-preferance-label selected-provider-account'><%- selectedProvider.name %> <%- gettext('Account') %></label>
|
||||
<% if (organizationCredentialsExists) { %>
|
||||
<div class='transcription-account-details warning'><span><%- gettext("This action updates the {provider} information for your entire organization.").replace('{provider}', selectedProvider.name) %></span></div>
|
||||
<% } else { %>
|
||||
<div class='transcription-account-details'><span><%- gettext("Enter the account information for your organization.") %></span></div>
|
||||
<% } %>
|
||||
<div class='transcript-preferance-wrapper org-credentials-wrapper <%- selectedProvider.key %>-api-key-wrapper'>
|
||||
<label class='transcript-preferance-label' for='<%- selectedProvider.key %>-api-key'>
|
||||
<span class='title'><%- gettext('API Key') %></span>
|
||||
<span class='error-icon' aria-hidden="true"></span>
|
||||
</label>
|
||||
<div>
|
||||
<input type='text' class='<%- selectedProvider.key %>-api-key'>
|
||||
<span class='error-info' aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
<% if (selectedProvider.key === THREE_PLAY_MEDIA) { %>
|
||||
<div class='transcript-preferance-wrapper org-credentials-wrapper <%- selectedProvider.key %>-api-secret-wrapper'>
|
||||
<label class='transcript-preferance-label' for='<%- selectedProvider.key %>-api-secret'>
|
||||
<span class='title'><%- gettext('API Secret') %></span>
|
||||
<span class='error-icon' aria-hidden="true"></span>
|
||||
</label>
|
||||
<div>
|
||||
<input type='text' class='<%- selectedProvider.key %>-api-secret'>
|
||||
<span class='error-info' aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class='transcript-preferance-wrapper org-credentials-wrapper <%- selectedProvider.key %>-username-wrapper'>
|
||||
<label class='transcript-preferance-label' for='<%- selectedProvider.key %>-username'>
|
||||
<span class='title'><%- gettext('Username') %></span>
|
||||
<span class='error-icon' aria-hidden="true"></span>
|
||||
</label>
|
||||
<div>
|
||||
<input type='text' class='<%- selectedProvider.key %>-username'>
|
||||
<span class='error-info' aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,6 +39,7 @@
|
||||
${video_supported_file_formats | n, dump_js_escaped_json},
|
||||
${video_upload_max_file_size | n, dump_js_escaped_json},
|
||||
${active_transcript_preferences | n, dump_js_escaped_json},
|
||||
${transcript_credentials | n, dump_js_escaped_json},
|
||||
${video_transcript_settings | n, dump_js_escaped_json},
|
||||
${is_video_transcript_enabled | n, dump_js_escaped_json},
|
||||
${video_image_settings | n, dump_js_escaped_json}
|
||||
|
||||
@@ -141,6 +141,8 @@ urlpatterns = [
|
||||
contentstore.views.video_images_handler, name='video_images_handler'),
|
||||
url(r'^transcript_preferences/{}$'.format(settings.COURSE_KEY_PATTERN),
|
||||
contentstore.views.transcript_preferences_handler, name='transcript_preferences_handler'),
|
||||
url(r'^transcript_credentials/{}$'.format(settings.COURSE_KEY_PATTERN),
|
||||
contentstore.views.transcript_credentials_handler, name='transcript_credentials_handler'),
|
||||
url(r'^video_encodings_download/{}$'.format(settings.COURSE_KEY_PATTERN),
|
||||
contentstore.views.video_encodings_download, name='video_encodings_download'),
|
||||
url(r'^group_configurations/{}$'.format(settings.COURSE_KEY_PATTERN),
|
||||
|
||||
@@ -2078,6 +2078,9 @@ INSTALLED_APPS = [
|
||||
# Video module configs (This will be moved to Video once it becomes an XBlock)
|
||||
'openedx.core.djangoapps.video_config',
|
||||
|
||||
# edX Video Pipeline integration
|
||||
'openedx.core.djangoapps.video_pipeline',
|
||||
|
||||
# Bookmarks
|
||||
'openedx.core.djangoapps.bookmarks.apps.BookmarksConfig',
|
||||
|
||||
|
||||
0
openedx/core/djangoapps/video_pipeline/__init__.py
Normal file
0
openedx/core/djangoapps/video_pipeline/__init__.py
Normal file
9
openedx/core/djangoapps/video_pipeline/admin.py
Normal file
9
openedx/core/djangoapps/video_pipeline/admin.py
Normal file
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
Django admin for Video Pipeline models.
|
||||
"""
|
||||
from config_models.admin import ConfigurationModelAdmin
|
||||
from django.contrib import admin
|
||||
|
||||
from openedx.core.djangoapps.video_pipeline.models import VideoPipelineIntegration
|
||||
|
||||
admin.site.register(VideoPipelineIntegration, ConfigurationModelAdmin)
|
||||
51
openedx/core/djangoapps/video_pipeline/api.py
Normal file
51
openedx/core/djangoapps/video_pipeline/api.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
API utils in order to communicate to edx-video-pipeline.
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from slumber.exceptions import HttpClientError
|
||||
|
||||
from openedx.core.djangoapps.video_pipeline.models import VideoPipelineIntegration
|
||||
from openedx.core.djangoapps.video_pipeline.utils import create_video_pipeline_api_client
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def update_3rd_party_transcription_service_credentials(**credentials_payload):
|
||||
"""
|
||||
Updates the 3rd party transcription service's credentials.
|
||||
|
||||
Arguments:
|
||||
credentials_payload(dict): A payload containing org, provider and its credentials.
|
||||
|
||||
Returns:
|
||||
A Boolean specifying whether the credentials were updated or not
|
||||
and an error response received from pipeline.
|
||||
"""
|
||||
error_response, is_updated = {}, False
|
||||
pipeline_integration = VideoPipelineIntegration.current()
|
||||
if pipeline_integration.enabled:
|
||||
try:
|
||||
video_pipeline_user = pipeline_integration.get_service_user()
|
||||
except ObjectDoesNotExist:
|
||||
return error_response, is_updated
|
||||
|
||||
client = create_video_pipeline_api_client(user=video_pipeline_user, api_url=pipeline_integration.api_url)
|
||||
|
||||
try:
|
||||
client.transcript_credentials.post(credentials_payload)
|
||||
is_updated = True
|
||||
except HttpClientError as ex:
|
||||
is_updated = False
|
||||
log.exception(
|
||||
('[video-pipeline-service] Unable to update transcript credentials '
|
||||
'-- org=%s -- provider=%s -- response=%s.'),
|
||||
credentials_payload.get('org'),
|
||||
credentials_payload.get('provider'),
|
||||
ex.content,
|
||||
)
|
||||
error_response = json.loads(ex.content)
|
||||
|
||||
return error_response, is_updated
|
||||
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VideoPipelineIntegration',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
|
||||
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
|
||||
('api_url', models.URLField(help_text='edx-video-pipeline API URL.', verbose_name='Internal API URL')),
|
||||
('service_username', models.CharField(default=b'video_pipeline_service_user', help_text='Username created for Video Pipeline Integration, e.g. video_pipeline_service_user.', max_length=100)),
|
||||
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-change_date',),
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
31
openedx/core/djangoapps/video_pipeline/models.py
Normal file
31
openedx/core/djangoapps/video_pipeline/models.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Model to hold edx-video-pipeline configurations.
|
||||
"""
|
||||
from config_models.models import ConfigurationModel
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class VideoPipelineIntegration(ConfigurationModel):
|
||||
"""
|
||||
Manages configuration for connecting to the edx-video-pipeline service and using its API.
|
||||
"""
|
||||
api_url = models.URLField(
|
||||
verbose_name=_('Internal API URL'),
|
||||
help_text=_('edx-video-pipeline API URL.')
|
||||
)
|
||||
|
||||
service_username = models.CharField(
|
||||
max_length=100,
|
||||
default='video_pipeline_service_user',
|
||||
null=False,
|
||||
blank=False,
|
||||
help_text=_('Username created for Video Pipeline Integration, e.g. video_pipeline_service_user.')
|
||||
)
|
||||
|
||||
def get_service_user(self):
|
||||
# NOTE: We load the user model here to avoid issues at startup time that result from the hacks
|
||||
# in lms/startup.py.
|
||||
User = get_user_model() # pylint: disable=invalid-name
|
||||
return User.objects.get(username=self.service_username)
|
||||
23
openedx/core/djangoapps/video_pipeline/tests/mixins.py
Normal file
23
openedx/core/djangoapps/video_pipeline/tests/mixins.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Mixins to test video pipeline integration.
|
||||
"""
|
||||
from openedx.core.djangoapps.video_pipeline.models import VideoPipelineIntegration
|
||||
|
||||
|
||||
class VideoPipelineIntegrationMixin(object):
|
||||
"""
|
||||
Utility for working with the video pipeline service during testing.
|
||||
"""
|
||||
video_pipeline_integration_defaults = {
|
||||
'enabled': True,
|
||||
'api_url': 'https://video-pipeline.example.com/api/v1/',
|
||||
'service_username': 'cms_video_pipeline_service_user',
|
||||
}
|
||||
|
||||
def create_video_pipeline_integration(self, **kwargs):
|
||||
"""
|
||||
Creates a new `VideoPipelineIntegration` record with `video_pipeline_integration_defaults`,
|
||||
and it can be updated with any provided overrides.
|
||||
"""
|
||||
fields = dict(self.video_pipeline_integration_defaults, **kwargs)
|
||||
return VideoPipelineIntegration.objects.create(**fields)
|
||||
99
openedx/core/djangoapps/video_pipeline/tests/test_api.py
Normal file
99
openedx/core/djangoapps/video_pipeline/tests/test_api.py
Normal file
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
Tests for Video Pipeline api utils.
|
||||
"""
|
||||
import ddt
|
||||
import json
|
||||
from mock import Mock, patch
|
||||
|
||||
from django.test.testcases import TestCase
|
||||
from slumber.exceptions import HttpClientError
|
||||
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
from openedx.core.djangoapps.video_pipeline.api import update_3rd_party_transcription_service_credentials
|
||||
from openedx.core.djangoapps.video_pipeline.tests.mixins import VideoPipelineIntegrationMixin
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestAPIUtils(VideoPipelineIntegrationMixin, TestCase):
|
||||
"""
|
||||
Tests for API Utils.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.pipeline_integration = self.create_video_pipeline_integration()
|
||||
self.user = UserFactory(username=self.pipeline_integration.service_username)
|
||||
|
||||
def test_update_transcription_service_credentials_with_integration_disabled(self):
|
||||
"""
|
||||
Test updating the credentials when service integration is disabled.
|
||||
"""
|
||||
self.pipeline_integration.enabled = False
|
||||
self.pipeline_integration.save()
|
||||
__, is_updated = update_3rd_party_transcription_service_credentials()
|
||||
self.assertFalse(is_updated)
|
||||
|
||||
def test_update_transcription_service_credentials_with_unknown_user(self):
|
||||
"""
|
||||
Test updating the credentials when expected service user is not registered.
|
||||
"""
|
||||
self.pipeline_integration.service_username = 'non_existent_user'
|
||||
self.pipeline_integration.save()
|
||||
__, is_updated = update_3rd_party_transcription_service_credentials()
|
||||
self.assertFalse(is_updated)
|
||||
|
||||
@ddt.data(
|
||||
{
|
||||
'username': 'Jason_cielo_24',
|
||||
'api_key': '12345678',
|
||||
},
|
||||
{
|
||||
'api_key': '12345678',
|
||||
'api_secret': '11111111',
|
||||
}
|
||||
)
|
||||
@patch('openedx.core.djangoapps.video_pipeline.api.log')
|
||||
@patch('openedx.core.djangoapps.video_pipeline.utils.EdxRestApiClient')
|
||||
def test_update_transcription_service_credentials(self, credentials_payload, mock_client, mock_logger):
|
||||
"""
|
||||
Tests that the update transcription service credentials api util works as expected.
|
||||
"""
|
||||
# Mock the post request
|
||||
mock_credentials_endpoint = mock_client.return_value.transcript_credentials
|
||||
# Try updating the transcription service credentials
|
||||
error_response, is_updated = update_3rd_party_transcription_service_credentials(**credentials_payload)
|
||||
|
||||
mock_credentials_endpoint.post.assert_called_with(credentials_payload)
|
||||
# Making sure log.exception is not called.
|
||||
self.assertDictEqual(error_response, {})
|
||||
self.assertFalse(mock_logger.exception.called)
|
||||
self.assertTrue(is_updated)
|
||||
|
||||
@patch('openedx.core.djangoapps.video_pipeline.api.log')
|
||||
@patch('openedx.core.djangoapps.video_pipeline.utils.EdxRestApiClient')
|
||||
def test_update_transcription_service_credentials_exceptions(self, mock_client, mock_logger):
|
||||
"""
|
||||
Tests that the update transcription service credentials logs the exception occurring
|
||||
during communication with edx-video-pipeline.
|
||||
"""
|
||||
error_content = '{"error_type": "1"}'
|
||||
# Mock the post request
|
||||
mock_credentials_endpoint = mock_client.return_value.transcript_credentials
|
||||
mock_credentials_endpoint.post = Mock(side_effect=HttpClientError(content=error_content))
|
||||
# try updating the transcription service credentials
|
||||
credentials_payload = {
|
||||
'org': 'mit',
|
||||
'provider': 'ABC Provider',
|
||||
'api_key': '61c56a8d0'
|
||||
}
|
||||
error_response, is_updated = update_3rd_party_transcription_service_credentials(**credentials_payload)
|
||||
|
||||
mock_credentials_endpoint.post.assert_called_with(credentials_payload)
|
||||
# Assert the results.
|
||||
self.assertFalse(is_updated)
|
||||
self.assertDictEqual(error_response, json.loads(error_content))
|
||||
mock_logger.exception.assert_called_with(
|
||||
'[video-pipeline-service] Unable to update transcript credentials -- org=%s -- provider=%s -- response=%s.',
|
||||
credentials_payload['org'],
|
||||
credentials_payload['provider'],
|
||||
error_content
|
||||
)
|
||||
19
openedx/core/djangoapps/video_pipeline/utils.py
Normal file
19
openedx/core/djangoapps/video_pipeline/utils.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from django.conf import settings
|
||||
from edx_rest_api_client.client import EdxRestApiClient
|
||||
|
||||
from openedx.core.lib.token_utils import JwtBuilder
|
||||
|
||||
|
||||
def create_video_pipeline_api_client(user, api_url):
|
||||
"""
|
||||
Returns an API client which can be used to make Video Pipeline API requests.
|
||||
|
||||
Arguments:
|
||||
user(User): A requesting user.
|
||||
api_url(unicode): It is video pipeline's API URL.
|
||||
"""
|
||||
jwt_token = JwtBuilder(user).build_token(
|
||||
scopes=[],
|
||||
expires_in=settings.OAUTH_ID_TOKEN_EXPIRATION
|
||||
)
|
||||
return EdxRestApiClient(api_url, jwt=jwt_token)
|
||||
@@ -54,7 +54,7 @@ edx-organizations==0.4.7
|
||||
edx-rest-api-client==1.7.1
|
||||
edx-search==1.1.0
|
||||
edx-submissions==2.0.12
|
||||
edxval==0.1.2
|
||||
edxval==0.1.3
|
||||
event-tracking==0.2.4
|
||||
feedparser==5.1.3
|
||||
firebase-token-generator==1.3.2
|
||||
|
||||
Reference in New Issue
Block a user