Unverified Commit 9a35a543 by Feanil Patel Committed by GitHub

Revert "Transcript secure credentials"

parent c2daef61
......@@ -19,7 +19,6 @@ 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 *
......
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)
"""
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
"""
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
......@@ -17,38 +18,33 @@ 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_http_methods, require_POST
from django.views.decorators.http import require_GET, require_POST, require_http_methods
from edxval.api import (
SortDirection,
VideoSortField,
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_status,
update_video_image,
update_video_status
get_3rd_party_transcription_plans,
get_transcript_preferences,
create_or_update_transcript_preferences,
remove_transcript_preferences,
)
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__)
......@@ -593,8 +589,7 @@ def videos_index_html(course):
},
'is_video_transcript_enabled': is_video_transcript_enabled,
'video_transcript_settings': None,
'active_transcript_preferences': None,
'transcript_credentials': None
'active_transcript_preferences': None
}
if is_video_transcript_enabled:
......@@ -603,15 +598,9 @@ 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,9 +947,6 @@ 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,7 +15,6 @@ define([
videoSupportedFileFormats,
videoUploadMaxFileSizeInGB,
activeTranscriptPreferences,
transcriptOrganizationCredentials,
videoTranscriptSettings,
isVideoTranscriptEnabled,
videoImageSettings
......@@ -28,7 +27,6 @@ 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: null
transcription_plans: {}
},
isVideoTranscriptEnabled: isVideoTranscriptEnabled
});
......
......@@ -8,23 +8,10 @@ define(
courseVideoSettingsView,
renderCourseVideoSettingsView,
destroyCourseVideoSettingsView,
verifyTranscriptPreferences,
verifyTranscriptPreferencesView,
verifyOrganizationCredentialsView,
verifyCredentialFieldsPresent,
verifyOrganizationCredentialField,
verifyMessage,
verifyPreferanceErrorState,
selectPreference,
verifyProviderList,
verifyProviderSelectedView,
verifyCredentialsSaved,
resetProvider,
changeProvider,
submitOrganizationCredentials,
chooseProvider,
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',
......@@ -34,10 +21,6 @@ define(
preferred_languages: ['fr', 'en'],
modified: '2017-08-27T12:28:17.421260Z'
},
transcriptOrganizationCredentials = {
Cielo24: true,
'3PlayMedia': true
},
transcriptionPlans = {
'3PlayMedia': {
languages: {
......@@ -89,37 +72,15 @@ 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, transcriptOrganizationCredentialsData) { // eslint-disable-line max-len
// First destroy old referance to the view if present.
destroyCourseVideoSettingsView();
renderCourseVideoSettingsView = function(activeTranscriptPreferencesData, transcriptionPlansData) {
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;
};
......@@ -147,167 +108,10 @@ define(
$preference.change();
};
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();
}
chooseProvider = function(selectedProvider) {
$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>' +
......@@ -344,10 +148,17 @@ 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();
// Checking turnaround is sufficient to check preferences are are shown or not.
expect($courseVideoSettingsEl.find('.transcript-turnaround-wrapper')).not.toExist();
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('');
});
it('populates transcription plans correctly', function() {
......@@ -358,51 +169,39 @@ define(
it('populates active preferances correctly', function() {
// First check preferance are selected correctly in HTML.
verifyTranscriptPreferences();
});
it('resets to active preferences when clicked on cancel', function() {
var selectedProvider = '3PlayMedia';
renderCourseVideoSettingsView(
activeTranscriptPreferences,
transcriptionPlans,
transcriptOrganizationCredentials
expect($courseVideoSettingsEl.find('.transcript-provider-group input:checked').val()).toEqual(
activeTranscriptPreferences.provider
);
// 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($courseVideoSettingsEl.find('.transcript-turnaround').val()).toEqual(
activeTranscriptPreferences.cielo24_turnaround
);
expect(
courseVideoSettingsView.selectedTurnaroundPlan,
transcriptionPlans[selectedProvider].turnaround.default
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
);
// Now click cancel button and verify active preferences are shown.
$courseVideoSettingsEl.find('.action-cancel-course-video-settings').click();
verifyTranscriptPreferences();
expect(courseVideoSettingsView.selectedProvider, providers.Cielo24);
// 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
);
expect(courseVideoSettingsView.selectedLanguages, activeTranscriptPreferences.preferred_languages);
});
it('shows video source language directly in case of 3Play provider', function() {
var sourceLanguages,
selectedProvider = '3PlayMedia';
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
// Select provider
changeProvider(selectedProvider);
// Select CIELIO24 provider
chooseProvider(selectedProvider);
expect(courseVideoSettingsView.selectedProvider).toEqual(selectedProvider);
// Verify source langauges menu is shown.
......@@ -420,10 +219,10 @@ define(
selectedProvider = 'Cielo24',
selectedFidelity = 'PROFESSIONAL';
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
renderCourseVideoSettingsView(null, transcriptionPlans);
// Select provider
changeProvider(selectedProvider);
// Select CIELIO24 provider
chooseProvider(selectedProvider);
expect(courseVideoSettingsView.selectedProvider).toEqual(selectedProvider);
// Verify source language is not shown.
......@@ -455,10 +254,8 @@ define(
selectedProvider = 'Cielo24',
selectedFidelity = 'PROFESSIONAL';
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
// Select provider
changeProvider(selectedProvider);
// Select CIELIO24 provider
chooseProvider(selectedProvider);
expect(courseVideoSettingsView.selectedProvider).toEqual(selectedProvider);
// Select fidelity
......@@ -486,10 +283,8 @@ define(
selectedProvider = 'Cielo24',
selectedFidelity = 'MECHANICAL';
renderCourseVideoSettingsView(null, transcriptionPlans, transcriptOrganizationCredentials);
// Select provider
changeProvider(selectedProvider);
// Select CIELIO24 provider
chooseProvider(selectedProvider);
expect(courseVideoSettingsView.selectedProvider).toEqual(selectedProvider);
// Select fidelity
......@@ -537,16 +332,37 @@ define(
});
// Verify that success message is shown.
verifyMessage('success', 'Settings updated');
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>'
);
});
it('removes transcript settings on update settings button click when no provider is selected', function() {
// Reset to None provider
resetProvider();
verifyProviderList(providers.none);
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, {});
// Verify that success message is shown.
verifyMessage('success', 'Automatic transcripts are disabled.');
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>'
);
});
it('shows error message if server sends error', function() {
......@@ -574,7 +390,12 @@ define(
});
// Verify that error message is shown.
verifyMessage('error', 'Error message');
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>'
);
});
it('implies preferences are required if not selected when saving preferances', function() {
......@@ -602,203 +423,8 @@ 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
// for specific preferance selected tests etc. - See EDUCATOR-1478
// like N/A selected, specific provider selected tests, specific preferance selected tests etc.
});
}
);
......@@ -42,7 +42,6 @@ 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;
......@@ -86,7 +85,6 @@ define([
if (this.isVideoTranscriptEnabled) {
this.courseVideoSettingsView = new CourseVideoSettingsView({
activeTranscriptPreferences: this.activeTranscriptPreferences,
transcriptOrganizationCredentials: this.transcriptOrganizationCredentials,
videoTranscriptSettings: this.videoTranscriptSettings
});
this.courseVideoSettingsView.render();
......
......@@ -3,26 +3,16 @@
*/
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-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'
'text!templates/course-video-settings.underscore'
],
function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, TranscriptSettingsTemplate,
TranscriptPreferencesTemplate, TranscriptProviderEmptyStateTemplate, TranscriptProviderSelectedStateTemplate,
OrganizationCredentialsTemplate, UpdateSettingsFooterTemplate, OrganizationCredentialsFooterTemplate) {
function($, Backbone, _, gettext, moment, HtmlUtils, StringUtils, TranscriptSettingsTemplate) {
'use strict';
var CourseVideoSettingsView,
CIELO24 = 'Cielo24',
THREE_PLAY_MEDIA = '3PlayMedia',
INTERNAL_SERVER_ERROR_MESSAGE = gettext('An error has occurred. Wait a few minutes, and then try again.');
THREE_PLAY_MEDIA = '3PlayMedia';
CourseVideoSettingsView = Backbone.View.extend({
el: 'div.video-transcript-settings-wrapper',
......@@ -34,27 +24,16 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
'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 = [];
},
......@@ -70,7 +49,7 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
// 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();
}
......@@ -181,35 +160,7 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
providerSelected: function(event) {
this.resetPlanData();
this.selectedProvider = event.target.value;
// 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();
}
this.renderPreferences();
},
languageSelected: function(event) {
......@@ -236,56 +187,43 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
$(event.target.parentElement).parent().remove();
// Remove language from selected languages.
this.selectedLanguages = this.activeLanguages = _.without(this.selectedLanguages, selectedLanguage);
this.selectedLanguages = _.without(this.selectedLanguages, selectedLanguage);
// Populate menu again to reflect latest changes.
this.populateLanguageMenu();
},
renderProviders: function(state) {
var $transcriptProviderWrapperEl = this.$el.find('.transcript-provider-wrapper');
if (!state) {
state = this.selectedProvider ? 'selected' : 'empty'; // eslint-disable-line no-param-reassign
}
renderProviders: function() {
var self = this,
providerPlan = self.availableTranscriptionPlans,
$providerEl = self.$el.find('.transcript-provider-group');
// If no transcription plans are sentm return.
if (!this.availableTranscriptionPlans) {
return;
}
if (state === 'empty') {
if (providerPlan) {
HtmlUtils.setHtml(
$transcriptProviderWrapperEl,
this.transcriptProviderEmptyStateTemplate({
providers: [
{
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' : ''
},
$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
{
key: THREE_PLAY_MEDIA,
value: THREE_PLAY_MEDIA,
name: this.availableTranscriptionPlans[THREE_PLAY_MEDIA].display_name,
checked: this.selectedProvider === THREE_PLAY_MEDIA ? 'checked' : ''
text: providerObject.display_name,
value: key,
checked: checked
}
]
})
);
} else {
HtmlUtils.setHtml(
$transcriptProviderWrapperEl,
this.transcriptProviderSelectedStateTemplate({
selectedProvider: this.availableTranscriptionPlans[this.selectedProvider].display_name
})
);
this.renderTranscriptPreferences();
)
);
});
}
},
......@@ -346,10 +284,6 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
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');
......@@ -358,9 +292,9 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
$languagesContainer.empty();
// Show language container if source language is selected.
// Show language container if source language is selected .
if (self.selectedVideoSourceLanguage) {
_.each(selectedLanguages, function(language) {
_.each(self.activeLanguages, function(language) {
// Only add if not in the list already.
if (_.indexOf(self.selectedLanguages, language) === -1) {
self.selectedLanguages.push(language);
......@@ -452,6 +386,14 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
}
},
renderPreferences: function() {
this.renderProviders();
this.renderTurnaround();
this.renderFidelity();
this.renderSourceLanguages();
this.renderTargetLanguages();
},
addLanguage: function(language) {
var $languagesContainer = this.$el.find('.languages-container');
HtmlUtils.append(
......@@ -477,9 +419,8 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
);
},
updateSuccessResponseStatus: function(data, successMessage) {
updateSuccessResponseStatus: function(data) {
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) {
......@@ -495,10 +436,7 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
);
}
// Now re-render providers state.
this.renderProviders();
this.renderResponseStatus(successMessage, 'success');
this.renderResponseStatus(gettext('Settings updated'), 'success');
// Sync ActiveUploadListView with latest active plan.
this.activeTranscriptionPlan = data;
Backbone.trigger('coursevideosettings:syncActiveTranscriptPreferences', this.activeTranscriptionPlan);
......@@ -510,8 +448,9 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
// show some error to user
try {
errorMessage = $.parseJSON(data).error;
} catch (e) {} // eslint-disable-line no-empty
this.renderResponseStatus(errorMessage || INTERNAL_SERVER_ERROR_MESSAGE, 'error');
} catch (e) {} // eslint-disable-line no-empty
errorMessage = errorMessage || gettext('Error saving data');
this.renderResponseStatus(errorMessage, 'error');
},
renderResponseStatus: function(responseText, type) {
......@@ -609,46 +548,6 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
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;
......@@ -668,69 +567,24 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
responseTranscriptPreferences = data ? data.transcript_preferences : null;
self.updateSuccessResponseStatus(responseTranscriptPreferences);
}).fail(function(jqXHR) {
self.updateFailResponseStatus(jqXHR.responseText);
if (jqXHR.responseText) {
self.updateFailResponseStatus(jqXHR.responseText);
}
});
} else {
$.ajax({
type: 'DELETE',
url: self.transcriptHandlerUrl
}).done(function() {
responseTranscriptPreferences = null;
self.updateSuccessResponseStatus(
responseTranscriptPreferences,
gettext('Automatic transcripts are disabled.')
);
self.updateSuccessResponseStatus(null);
}).fail(function(jqXHR) {
self.updateFailResponseStatus(jqXHR.responseText);
if (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()) {
......@@ -740,81 +594,17 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
}
},
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.find('.course-video-settings-footer'),
this.updateSettingsFooterTemplate({
this.$el,
this.template({
dateModified: dateModified
})
);
this.renderProviders();
this.renderPreferences();
this.registerCloseClickHandler();
this.setFixedCourseVideoSettingsPane();
......@@ -850,7 +640,7 @@ function($, Backbone, _, gettext, moment, ViewUtils, HtmlUtils, StringUtils, Tra
},
closeCourseVideoSettings: function() {
// TODO: Slide out when closing settings pane. See EDUCATOR-1477
// TODO: Slide out when closing settings pane. We may need to hide the view instead of destroying it.
// Trigger destroy transcript event.
Backbone.trigger('coursevideosettings:destroyCourseVideoSettingsView');
......
......@@ -83,15 +83,6 @@
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 {
......@@ -108,7 +99,6 @@
.error-info {
@include font-size(16);
@include margin-left($baseline/2);
}
.transcript-preferance-label {
......@@ -118,15 +108,10 @@
display: block;
}
.transcript-provider-group, .transcript-turnaround, .transcript-fidelity, .video-source-language, .selected-transcript-provider {
.transcript-provider-group, .transcript-turnaround, .transcript-fidelity, .video-source-language {
margin-top: ($baseline*0.8);
}
.selected-transcript-provider {
.action-change-provider {
@include margin-left($baseline/2);
}
}
.transcript-provider-group {
input[type=radio] {
......@@ -143,10 +128,6 @@
display: none;
}
.transcript-languages-wrapper .transcript-preferance-label {
display: inline-block;
}
.transcript-languages-container .languages-container {
margin-top: ($baseline*0.8);
.transcript-language-container {
......@@ -167,9 +148,6 @@
.action-add-language {
@include margin-left($baseline/4);
}
.error-info {
display: inline-block;
}
}
}
.transcript-language-menu, .video-source-language {
......@@ -177,28 +155,11 @@
}
}
.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);
display: block;
margin-top: ($baseline/2);
@include margin-left($baseline/4);
}
}
......
<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>
<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,8 +11,49 @@
<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'></div>
<div class='course-video-settings-content'></div>
<div class='course-video-settings-footer'></div>
<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>
</div>
<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>
<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>
<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>
<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,7 +39,6 @@
${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,8 +141,6 @@ 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,9 +2078,6 @@ 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',
......
"""
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)
"""
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
# -*- 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,
},
),
]
"""
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)
"""
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)
"""
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
)
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.3
edxval==0.1.2
event-tracking==0.2.4
feedparser==5.1.3
firebase-token-generator==1.3.2
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment