diff --git a/cms/djangoapps/contentstore/config/waffle.py b/cms/djangoapps/contentstore/config/waffle.py
index f2770c42d1..c473728d78 100644
--- a/cms/djangoapps/contentstore/config/waffle.py
+++ b/cms/djangoapps/contentstore/config/waffle.py
@@ -2,7 +2,7 @@
This module contains various configuration settings via
waffle switches for the contentstore app.
"""
-from openedx.core.djangoapps.waffle_utils import WaffleFlagNamespace, WaffleSwitchNamespace
+from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlagNamespace, WaffleSwitchNamespace
# Namespace
WAFFLE_NAMESPACE = u'studio'
@@ -23,3 +23,10 @@ def waffle_flags():
Returns the namespaced, cached, audited Waffle Flag class for Studio pages.
"""
return WaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix=u'Studio: ')
+
+# Flags
+ENABLE_PROCTORING_PROVIDER_OVERRIDES = CourseWaffleFlag(
+ waffle_namespace=waffle_flags(),
+ flag_name=u'enable_proctoring_provider_overrides',
+ flag_undefined_default=False
+)
diff --git a/cms/djangoapps/contentstore/proctoring.py b/cms/djangoapps/contentstore/proctoring.py
index 70d0cbd46e..7659063f7b 100644
--- a/cms/djangoapps/contentstore/proctoring.py
+++ b/cms/djangoapps/contentstore/proctoring.py
@@ -30,7 +30,6 @@ def register_special_exams(course_key):
subsystem. Likewise, if formerly registered exams are unmarked, then those
registered exams are marked as inactive
"""
-
if not settings.FEATURES.get('ENABLE_SPECIAL_EXAMS'):
# if feature is not enabled then do a quick exit
return
@@ -72,52 +71,47 @@ def register_special_exams(course_key):
)
log.info(msg)
+ exam_metadata = {
+ 'exam_name': timed_exam.display_name,
+ 'time_limit_mins': timed_exam.default_time_limit_minutes,
+ 'due_date': timed_exam.due,
+ 'is_proctored': timed_exam.is_proctored_exam,
+ 'is_practice_exam': timed_exam.is_practice_exam,
+ 'is_active': True,
+ 'hide_after_due': timed_exam.hide_after_due,
+ 'backend': course.proctoring_provider,
+ }
+
try:
exam = get_exam_by_content_id(unicode(course_key), unicode(timed_exam.location))
# update case, make sure everything is synced
- exam_id = update_exam(
- exam_id=exam['id'],
- exam_name=timed_exam.display_name,
- time_limit_mins=timed_exam.default_time_limit_minutes,
- due_date=timed_exam.due,
- is_proctored=timed_exam.is_proctored_exam,
- is_practice_exam=timed_exam.is_practice_exam,
- is_active=True,
- hide_after_due=timed_exam.hide_after_due,
- )
+ exam_metadata['exam_id'] = exam['id']
+
+ exam_id = update_exam(**exam_metadata)
msg = 'Updated timed exam {exam_id}'.format(exam_id=exam['id'])
log.info(msg)
except ProctoredExamNotFoundException:
- exam_id = create_exam(
- course_id=unicode(course_key),
- content_id=unicode(timed_exam.location),
- exam_name=timed_exam.display_name,
- time_limit_mins=timed_exam.default_time_limit_minutes,
- due_date=timed_exam.due,
- is_proctored=timed_exam.is_proctored_exam,
- is_practice_exam=timed_exam.is_practice_exam,
- is_active=True,
- hide_after_due=timed_exam.hide_after_due,
- )
+ exam_metadata['course_id'] = unicode(course_key)
+ exam_metadata['content_id'] = unicode(timed_exam.location)
+
+ exam_id = create_exam(**exam_metadata)
msg = 'Created new timed exam {exam_id}'.format(exam_id=exam_id)
log.info(msg)
+ exam_review_policy_metadata = {
+ 'exam_id': exam_id,
+ 'set_by_user_id': timed_exam.edited_by,
+ 'review_policy': timed_exam.exam_review_rules,
+ }
+
# only create/update exam policy for the proctored exams
if timed_exam.is_proctored_exam and not timed_exam.is_practice_exam:
try:
- update_review_policy(
- exam_id=exam_id,
- set_by_user_id=timed_exam.edited_by,
- review_policy=timed_exam.exam_review_rules
- )
+ update_review_policy(**exam_review_policy_metadata)
except ProctoredExamReviewPolicyNotFoundException:
if timed_exam.exam_review_rules: # won't save an empty rule.
- create_exam_review_policy(
- exam_id=exam_id,
- set_by_user_id=timed_exam.edited_by,
- review_policy=timed_exam.exam_review_rules
- )
+ create_exam_review_policy(**exam_review_policy_metadata)
msg = 'Created new exam review policy with exam_id {exam_id}'.format(exam_id=exam_id)
log.info(msg)
else:
diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py
index 18fc69174e..19fd2ce5ae 100644
--- a/cms/djangoapps/contentstore/tests/test_course_settings.py
+++ b/cms/djangoapps/contentstore/tests/test_course_settings.py
@@ -7,19 +7,24 @@ import json
import unittest
import ddt
-import mock
from django.conf import settings
+from django.test import RequestFactory
from django.test.utils import override_settings
from pytz import UTC
-from milestones.tests.utils import MilestonesTestCaseMixin
+import mock
from mock import Mock, patch
+from crum import set_current_request
+from milestones.tests.utils import MilestonesTestCaseMixin
+
from contentstore.utils import reverse_course_url, reverse_usage_url
+from contentstore.config.waffle import ENABLE_PROCTORING_PROVIDER_OVERRIDES
from milestones.models import MilestoneRelationshipType
from models.settings.course_grading import CourseGradingModel, GRADING_POLICY_CHANGED_EVENT_TYPE, hash_grading_policy
from models.settings.course_metadata import CourseMetadata
from models.settings.encoder import CourseSettingsEncoder
from openedx.core.djangoapps.models.course_details import CourseDetails
+from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
from student.roles import CourseInstructorRole, CourseStaffRole
from student.tests.factories import UserFactory
from util import milestones_helpers
@@ -790,12 +795,18 @@ class CourseMetadataEditingTest(CourseTestCase):
shard = 1
def setUp(self):
- CourseTestCase.setUp(self)
+ super(CourseMetadataEditingTest, self).setUp()
self.fullcourse = CourseFactory.create()
self.course_setting_url = get_url(self.course.id, 'advanced_settings_handler')
self.fullcourse_setting_url = get_url(self.fullcourse.id, 'advanced_settings_handler')
self.notes_tab = {"type": "notes", "name": "My Notes"}
+ self.request = RequestFactory().request()
+ self.user = UserFactory()
+ self.request.user = self.user
+ set_current_request(self.request)
+ self.addCleanup(set_current_request, None)
+
def test_fetch_initial_fields(self):
test_model = CourseMetadata.fetch(self.course)
self.assertIn('display_name', test_model, 'Missing editable metadata field')
@@ -1189,6 +1200,193 @@ class CourseMetadataEditingTest(CourseTestCase):
})
self.assertEqual(response.status_code, 200)
+ @override_waffle_flag(ENABLE_PROCTORING_PROVIDER_OVERRIDES, True)
+ def test_proctoring_provider_present_when_waffle_flag_enabled(self):
+ """
+ Tests that proctoring provider field is not filtered out when the waffle flag is enabled.
+ """
+ test_model = CourseMetadata.fetch(self.fullcourse)
+ self.assertIn('proctoring_provider', test_model)
+
+ @override_settings(
+ PROCTORING_BACKENDS={
+ 'DEFAULT': 'test_proctoring_provider',
+ 'test_proctoring_provider': {}
+ }
+ )
+ @override_waffle_flag(ENABLE_PROCTORING_PROVIDER_OVERRIDES, True)
+ def test_validate_update_does_not_filter_out_proctoring_provider_when_waffle_flag_enabled(self):
+ """
+ Tests that proctoring provider field is returned by validate_and_update_from_json method when
+ waffle flag is enabled.
+ """
+ field_name = "proctoring_provider"
+
+ _, _, test_model = CourseMetadata.validate_and_update_from_json(
+ self.course,
+ {
+ field_name: {"value": 'test_proctoring_provider'},
+ },
+ user=self.user
+ )
+ self.assertIn(field_name, test_model)
+
+ @override_settings(
+ PROCTORING_BACKENDS={
+ 'DEFAULT': 'test_proctoring_provider',
+ 'test_proctoring_provider': {}
+ }
+ )
+ @override_waffle_flag(ENABLE_PROCTORING_PROVIDER_OVERRIDES, True)
+ def test_update_from_json_does_not_filter_out_proctoring_provider_when_waffle_flag_enabled(self):
+ """
+ Tests that proctoring provider field is returned by update_from_json method when
+ waffle flag is enabled.
+ """
+ field_name = "proctoring_provider"
+ test_model = CourseMetadata.update_from_json(
+ self.course,
+ {
+ field_name: {"value": 'test_proctoring_provider'},
+ },
+ user=self.user
+ )
+ self.assertIn(field_name, test_model)
+
+ @override_waffle_flag(ENABLE_PROCTORING_PROVIDER_OVERRIDES, False)
+ def test_proctoring_provider_not_present_when_waffle_flag_not_enabled(self):
+ """
+ Tests that proctoring provider field is filtered out when the waffle flag is not enabled.
+ """
+ test_model = CourseMetadata.fetch(self.fullcourse)
+ self.assertNotIn('proctoring_provider', test_model)
+
+ @override_waffle_flag(ENABLE_PROCTORING_PROVIDER_OVERRIDES, False)
+ def test_validate_update_does_filter_out_proctoring_provider_when_waffle_flag_not_enabled(self):
+ """
+ Tests that proctoring provider field is not returned by validate_and_update_from_json method when
+ waffle flag is not enabled.
+ """
+ field_name = "proctoring_provider"
+
+ _, _, test_model = CourseMetadata.validate_and_update_from_json(
+ self.course,
+ {
+ field_name: {"value": 'test_proctoring_provider'},
+ },
+ user=self.user
+ )
+ self.assertNotIn(field_name, test_model)
+
+ @override_waffle_flag(ENABLE_PROCTORING_PROVIDER_OVERRIDES, False)
+ def test_update_from_json_does_filter_out_proctoring_provider_when_waffle_flag_not_enabled(self):
+ """
+ Tests that proctoring provider field is not returned by update_from_json method when
+ waffle flag is not enabled.
+ """
+ field_name = "proctoring_provider"
+
+ test_model = CourseMetadata.update_from_json(
+ self.course,
+ {
+ field_name: {"value": 'test_proctoring_provider'},
+ },
+ user=self.user
+ )
+ self.assertNotIn(field_name, test_model)
+
+ def test_create_zendesk_tickets_present_for_edx_staff(self):
+ """
+ Tests that create zendesk tickets field is not filtered out when the user is an edX staff member.
+ """
+ self._set_request_user_to_staff()
+
+ test_model = CourseMetadata.fetch(self.fullcourse)
+ self.assertIn('create_zendesk_tickets', test_model)
+
+ def test_validate_update_does_not_filter_out_create_zendesk_tickets_for_edx_staff(self):
+ """
+ Tests that create zendesk tickets field is returned by validate_and_update_from_json method when
+ the user is an edX staff member.
+ """
+ self._set_request_user_to_staff()
+
+ field_name = "create_zendesk_tickets"
+
+ _, _, test_model = CourseMetadata.validate_and_update_from_json(
+ self.course,
+ {
+ field_name: {"value": True},
+ },
+ user=self.user
+ )
+ self.assertIn(field_name, test_model)
+
+ def test_update_from_json_does_not_filter_out_create_zendesk_tickets_for_edx_staff(self):
+ """
+ Tests that create zendesk tickets field is returned by update_from_json method when
+ the user is an edX staff member.
+ """
+ self._set_request_user_to_staff()
+
+ field_name = "create_zendesk_tickets"
+
+ test_model = CourseMetadata.update_from_json(
+ self.course,
+ {
+ field_name: {"value": True},
+ },
+ user=self.user
+ )
+ self.assertIn(field_name, test_model)
+
+ def test_create_zendesk_tickets_not_present_for_course_staff(self):
+ """
+ Tests that create zendesk tickets field is filtered out when the user is not an edX staff member.
+ """
+ test_model = CourseMetadata.fetch(self.fullcourse)
+ self.assertNotIn('create_zendesk_tickets', test_model)
+
+ def test_validate_update_does_filter_out_create_zendesk_tickets_for_course_staff(self):
+ """
+ Tests that create zendesk tickets field is not returned by validate_and_update_from_json method when
+ the user is not an edX staff member.
+ """
+ field_name = "create_zendesk_tickets"
+
+ _, _, test_model = CourseMetadata.validate_and_update_from_json(
+ self.course,
+ {
+ field_name: {"value": True},
+ },
+ user=self.user
+ )
+ self.assertNotIn(field_name, test_model)
+
+ def test_update_from_json_does_filter_out_create_zendesk_tickets_for_course_staff(self):
+ """
+ Tests that create zendesk tickets field is not returned by update_from_json method when
+ the user is not an edX staff member.
+ """
+ field_name = "create_zendesk_tickets"
+
+ test_model = CourseMetadata.update_from_json(
+ self.course,
+ {
+ field_name: {"value": True},
+ },
+ user=self.user
+ )
+ self.assertNotIn(field_name, test_model)
+
+ def _set_request_user_to_staff(self):
+ """
+ Update the current request's user to be an edX staff member.
+ """
+ self.user.is_staff = True
+ self.request.user = self.user
+ set_current_request(self.request)
+
class CourseGraderUpdatesTest(CourseTestCase):
"""
diff --git a/cms/djangoapps/contentstore/tests/test_proctoring.py b/cms/djangoapps/contentstore/tests/test_proctoring.py
index b497874459..f273517c45 100644
--- a/cms/djangoapps/contentstore/tests/test_proctoring.py
+++ b/cms/djangoapps/contentstore/tests/test_proctoring.py
@@ -10,6 +10,7 @@ from mock import patch
from pytz import UTC
from contentstore.signals.handlers import listen_for_course_publish
+from django.conf import settings
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
@@ -31,7 +32,8 @@ class TestProctoredExams(ModuleStoreTestCase):
org='edX',
course='900',
run='test_run',
- enable_proctored_exams=True
+ enable_proctored_exams=True,
+ proctoring_provider=settings.PROCTORING_BACKENDS['DEFAULT'],
)
def _verify_exam_data(self, sequence, expected_active):
@@ -61,20 +63,22 @@ class TestProctoredExams(ModuleStoreTestCase):
self.assertEqual(exam['is_proctored'], sequence.is_proctored_exam)
self.assertEqual(exam['is_practice_exam'], sequence.is_practice_exam)
self.assertEqual(exam['is_active'], expected_active)
+ self.assertEqual(exam['backend'], self.course.proctoring_provider)
@ddt.data(
- (True, 10, True, False, True, False, False),
- (True, 10, False, False, True, False, False),
- (True, 10, False, False, True, False, True),
- (True, 10, True, True, True, True, False),
+ (True, False, True, False, False),
+ (False, False, True, False, False),
+ (False, False, True, False, True),
+ (True, True, True, True, False),
)
@ddt.unpack
- def test_publishing_exam(self, is_time_limited, default_time_limit_minutes, is_proctored_exam,
+ def test_publishing_exam(self, is_proctored_exam,
is_practice_exam, expected_active, republish, hide_after_due):
"""
Happy path testing to see that when a course is published which contains
a proctored exam, it will also put an entry into the exam tables
"""
+ default_time_limit_minutes = 10
chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
sequence = ItemFactory.create(
@@ -82,7 +86,7 @@ class TestProctoredExams(ModuleStoreTestCase):
category='sequential',
display_name='Test Proctored Exam',
graded=True,
- is_time_limited=is_time_limited,
+ is_time_limited=True,
default_time_limit_minutes=default_time_limit_minutes,
is_proctored_exam=is_proctored_exam,
is_practice_exam=is_practice_exam,
@@ -104,14 +108,13 @@ class TestProctoredExams(ModuleStoreTestCase):
listen_for_course_publish(self, self.course.id)
# reverify
- self._verify_exam_data(sequence, expected_active)
+ self._verify_exam_data(sequence, expected_active,)
def test_unpublishing_proctored_exam(self):
"""
Make sure that if we publish and then unpublish a proctored exam,
the exam record stays, but is marked as is_active=False
"""
-
chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
sequence = ItemFactory.create(
parent=chapter,
@@ -178,7 +181,6 @@ class TestProctoredExams(ModuleStoreTestCase):
"""
Make sure the feature flag is honored
"""
-
chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
ItemFactory.create(
parent=chapter,
diff --git a/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py b/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py
index a20122aade..0f66cbc37b 100644
--- a/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py
+++ b/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py
@@ -2,7 +2,6 @@
Test module for Entrance Exams AJAX callback handler workflows
"""
import json
-
from django.conf import settings
from django.contrib.auth.models import User
from django.test.client import RequestFactory
@@ -229,13 +228,14 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
{'entrance_exam_minimum_score_pct': '50'},
http_accept='application/json'
)
+
self.assertEqual(resp.status_code, 201)
resp = self.client.get(self.exam_url)
self.assertEqual(resp.status_code, 200)
self.course = modulestore().get_course(self.course.id)
-
# Should raise an ItemNotFoundError and return a 404
updated_metadata = {'entrance_exam_id': 'i4x://org.4/course_4/chapter/ed7c4c6a4d68409998e2c8554c4629d1'}
+
CourseMetadata.update_from_dict(
updated_metadata,
self.course,
@@ -247,6 +247,7 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
# Should raise an InvalidKeyError and return a 404
updated_metadata = {'entrance_exam_id': '123afsdfsad90f87'}
+
CourseMetadata.update_from_dict(
updated_metadata,
self.course,
diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py
index d9981e5740..20fcf29bf3 100644
--- a/cms/djangoapps/models/settings/course_metadata.py
+++ b/cms/djangoapps/models/settings/course_metadata.py
@@ -5,11 +5,14 @@ from django.conf import settings
from django.utils.translation import ugettext as _
from six import text_type
from xblock.fields import Scope
+from crum import get_current_user
from xblock_django.models import XBlockStudioConfigurationFlag
from xmodule.modulestore.django import modulestore
+from student.roles import GlobalStaff
from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG
+from cms.djangoapps.contentstore.config.waffle import ENABLE_PROCTORING_PROVIDER_OVERRIDES
class CourseMetadata(object):
@@ -20,9 +23,9 @@ class CourseMetadata(object):
editable metadata.
'''
# The list of fields that wouldn't be shown in Advanced Settings.
- # Should not be used directly. Instead the filtered_list method should
+ # Should not be used directly. Instead the get_blacklist_of_fields method should
# be used if the field needs to be filtered depending on the feature flag.
- FILTERED_LIST = [
+ FIELDS_BLACK_LIST = [
'cohort_config',
'xml_attributes',
'start',
@@ -65,65 +68,77 @@ class CourseMetadata(object):
]
@classmethod
- def filtered_list(cls, course_key=None):
+ def get_blacklist_of_fields(cls, course_key):
"""
- Filter fields based on feature flag, i.e. enabled, disabled.
+ Returns a list of fields to not include in Studio Advanced settings based on a
+ feature flag (i.e. enabled or disabled).
"""
# Copy the filtered list to avoid permanently changing the class attribute.
- filtered_list = list(cls.FILTERED_LIST)
+ black_list = list(cls.FIELDS_BLACK_LIST)
# Do not show giturl if feature is not enabled.
if not settings.FEATURES.get('ENABLE_EXPORT_GIT'):
- filtered_list.append('giturl')
+ black_list.append('giturl')
# Do not show edxnotes if the feature is disabled.
if not settings.FEATURES.get('ENABLE_EDXNOTES'):
- filtered_list.append('edxnotes')
+ black_list.append('edxnotes')
# Do not show video auto advance if the feature is disabled
if not settings.FEATURES.get('ENABLE_OTHER_COURSE_SETTINGS'):
- filtered_list.append('other_course_settings')
+ black_list.append('other_course_settings')
# Do not show video_upload_pipeline if the feature is disabled.
if not settings.FEATURES.get('ENABLE_VIDEO_UPLOAD_PIPELINE'):
- filtered_list.append('video_upload_pipeline')
+ black_list.append('video_upload_pipeline')
# Do not show video auto advance if the feature is disabled
if not settings.FEATURES.get('ENABLE_AUTOADVANCE_VIDEOS'):
- filtered_list.append('video_auto_advance')
+ black_list.append('video_auto_advance')
# Do not show social sharing url field if the feature is disabled.
if (not hasattr(settings, 'SOCIAL_SHARING_SETTINGS') or
not getattr(settings, 'SOCIAL_SHARING_SETTINGS', {}).get("CUSTOM_COURSE_URLS")):
- filtered_list.append('social_sharing_url')
+ black_list.append('social_sharing_url')
# Do not show teams configuration if feature is disabled.
if not settings.FEATURES.get('ENABLE_TEAMS'):
- filtered_list.append('teams_configuration')
+ black_list.append('teams_configuration')
if not settings.FEATURES.get('ENABLE_VIDEO_BUMPER'):
- filtered_list.append('video_bumper')
+ black_list.append('video_bumper')
# Do not show enable_ccx if feature is not enabled.
if not settings.FEATURES.get('CUSTOM_COURSES_EDX'):
- filtered_list.append('enable_ccx')
- filtered_list.append('ccx_connector')
+ black_list.append('enable_ccx')
+ black_list.append('ccx_connector')
# Do not show "Issue Open Badges" in Studio Advanced Settings
# if the feature is disabled.
if not settings.FEATURES.get('ENABLE_OPENBADGES'):
- filtered_list.append('issue_badges')
+ black_list.append('issue_badges')
# If the XBlockStudioConfiguration table is not being used, there is no need to
# display the "Allow Unsupported XBlocks" setting.
if not XBlockStudioConfigurationFlag.is_enabled():
- filtered_list.append('allow_unsupported_xblocks')
+ black_list.append('allow_unsupported_xblocks')
+
+ # If the ENABLE_PROCTORING_PROVIDER_OVERRIDES waffle flag is not enabled,
+ # do not show "Proctoring Configuration" in Studio Advanced Settings.
+ if not ENABLE_PROCTORING_PROVIDER_OVERRIDES.is_enabled(course_key):
+ black_list.append('proctoring_provider')
# Do not show "Course Visibility For Unenrolled Learners" in Studio Advanced Settings
# if the enable_anonymous_access flag is not enabled
if not COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course_key=course_key):
- filtered_list.append('course_visibility')
- return filtered_list
+ black_list.append('course_visibility')
+
+ # Do not show "Create Zendesk Tickets For Suspicious Proctored Exam Attempts" in
+ # Studio Advanced Settings if the user is not edX staff.
+ if not GlobalStaff().has_user(get_current_user()):
+ black_list.append('create_zendesk_tickets')
+
+ return black_list
@classmethod
def fetch(cls, descriptor):
@@ -133,8 +148,10 @@ class CourseMetadata(object):
"""
result = {}
metadata = cls.fetch_all(descriptor)
+ black_list_of_fields = cls.get_blacklist_of_fields(descriptor.id)
+
for key, value in metadata.iteritems():
- if key in cls.filtered_list(descriptor.id):
+ if key in black_list_of_fields:
continue
result[key] = value
return result
@@ -169,17 +186,17 @@ class CourseMetadata(object):
Ensures none of the fields are in the blacklist.
"""
- filtered_list = cls.filtered_list(descriptor.id)
+ blacklist_of_fields = cls.get_blacklist_of_fields(descriptor.id)
# Don't filter on the tab attribute if filter_tabs is False.
if not filter_tabs:
- filtered_list.remove("tabs")
+ blacklist_of_fields.remove("tabs")
# Validate the values before actually setting them.
key_values = {}
for key, model in jsondict.iteritems():
# should it be an error if one of the filtered list items is in the payload?
- if key in filtered_list:
+ if key in blacklist_of_fields:
continue
try:
val = model['value']
@@ -205,11 +222,12 @@ class CourseMetadata(object):
errors: list of error objects
result: the updated course metadata or None if error
"""
- filtered_list = cls.filtered_list(descriptor.id)
- if not filter_tabs:
- filtered_list.remove("tabs")
+ blacklist_of_fields = cls.get_blacklist_of_fields(descriptor.id)
- filtered_dict = dict((k, v) for k, v in jsondict.iteritems() if k not in filtered_list)
+ if not filter_tabs:
+ blacklist_of_fields.remove("tabs")
+
+ filtered_dict = dict((k, v) for k, v in jsondict.iteritems() if k not in blacklist_of_fields)
did_validate = True
errors = []
key_values = {}
@@ -238,7 +256,7 @@ class CourseMetadata(object):
for key, value in key_values.iteritems():
setattr(descriptor, key, value)
- if save and len(key_values):
+ if save and key_values:
modulestore().update_item(descriptor, user.id)
return cls.fetch(descriptor)
diff --git a/cms/envs/aws.py b/cms/envs/aws.py
index 14dae321bb..7fad015159 100644
--- a/cms/envs/aws.py
+++ b/cms/envs/aws.py
@@ -490,11 +490,6 @@ XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
XBLOCK_SETTINGS.setdefault("VideoDescriptor", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
XBLOCK_SETTINGS.setdefault("VideoModule", {})['YOUTUBE_API_KEY'] = AUTH_TOKENS.get('YOUTUBE_API_KEY', YOUTUBE_API_KEY)
-################# PROCTORING CONFIGURATION ##################
-
-PROCTORING_BACKEND_PROVIDER = AUTH_TOKENS.get("PROCTORING_BACKEND_PROVIDER", PROCTORING_BACKEND_PROVIDER)
-PROCTORING_SETTINGS = ENV_TOKENS.get("PROCTORING_SETTINGS", PROCTORING_SETTINGS)
-
################# MICROSITE ####################
# microsite specific configurations.
MICROSITE_CONFIGURATION = ENV_TOKENS.get('MICROSITE_CONFIGURATION', {})
diff --git a/cms/envs/common.py b/cms/envs/common.py
index bf1baa22a2..8c79bebfe8 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -881,6 +881,10 @@ WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'bundles/',
'STATS_FILE': os.path.join(STATIC_ROOT, 'webpack-stats.json')
+ },
+ 'WORKERS': {
+ 'BUNDLE_DIR_NAME': 'bundles/',
+ 'STATS_FILE': os.path.join(STATIC_ROOT, 'webpack-worker-stats.json')
}
}
WEBPACK_CONFIG_PATH = 'webpack.prod.config.js'
@@ -1093,9 +1097,6 @@ INSTALLED_APPS = [
'xblock_django',
- # edX Proctoring
- 'edx_proctoring',
-
# Catalog integration
'openedx.core.djangoapps.catalog',
@@ -1445,12 +1446,6 @@ MICROSITE_TEMPLATE_BACKEND = 'microsite_configuration.backends.filebased.Filebas
# TTL for microsite database template cache
MICROSITE_DATABASE_TEMPLATE_CACHE_TTL = 5 * 60
-############################### PROCTORING CONFIGURATION DEFAULTS ##############
-PROCTORING_BACKEND_PROVIDER = {
- 'class': 'edx_proctoring.backends.null.NullBackendProvider',
- 'options': {},
-}
-PROCTORING_SETTINGS = {}
############################ Global Database Configuration #####################
diff --git a/cms/envs/production.py b/cms/envs/production.py
index 2fc776a3b7..c528fc3cf9 100644
--- a/cms/envs/production.py
+++ b/cms/envs/production.py
@@ -128,6 +128,7 @@ STATIC_ROOT_BASE = ENV_TOKENS.get('STATIC_ROOT_BASE', None)
if STATIC_ROOT_BASE:
STATIC_ROOT = path(STATIC_ROOT_BASE) / 'studio'
WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = STATIC_ROOT / "webpack-stats.json"
+ WEBPACK_LOADER['WORKERS']['STATS_FILE'] = STATIC_ROOT / "webpack-worker-stats.json"
EMAIL_BACKEND = ENV_TOKENS.get('EMAIL_BACKEND', EMAIL_BACKEND)
EMAIL_FILE_PATH = ENV_TOKENS.get('EMAIL_FILE_PATH', None)
@@ -493,11 +494,6 @@ XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
XBLOCK_SETTINGS.setdefault("VideoDescriptor", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
XBLOCK_SETTINGS.setdefault("VideoModule", {})['YOUTUBE_API_KEY'] = AUTH_TOKENS.get('YOUTUBE_API_KEY', YOUTUBE_API_KEY)
-################# PROCTORING CONFIGURATION ##################
-
-PROCTORING_BACKEND_PROVIDER = AUTH_TOKENS.get("PROCTORING_BACKEND_PROVIDER", PROCTORING_BACKEND_PROVIDER)
-PROCTORING_SETTINGS = ENV_TOKENS.get("PROCTORING_SETTINGS", PROCTORING_SETTINGS)
-
################# MICROSITE ####################
# microsite specific configurations.
MICROSITE_CONFIGURATION = ENV_TOKENS.get('MICROSITE_CONFIGURATION', {})
diff --git a/cms/envs/test_static_optimized.py b/cms/envs/test_static_optimized.py
index 2874ee494d..64a94efa31 100644
--- a/cms/envs/test_static_optimized.py
+++ b/cms/envs/test_static_optimized.py
@@ -23,6 +23,7 @@ DATABASES = {
}
+
######################### PIPELINE ####################################
# Use RequireJS optimized storage
diff --git a/common/djangoapps/student/roles.py b/common/djangoapps/student/roles.py
index 19f4425cc0..da31eb57be 100644
--- a/common/djangoapps/student/roles.py
+++ b/common/djangoapps/student/roles.py
@@ -120,7 +120,7 @@ class GlobalStaff(AccessRole):
The global staff role
"""
def has_user(self, user):
- return user.is_staff
+ return bool(user and user.is_staff)
def add_users(self, *users):
for user in users:
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index 1471489977..c6d39f15a0 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -183,6 +183,89 @@ class TextbookList(List):
return json_data
+class ProctoringProvider(String):
+ """
+ ProctoringProvider field, which includes validation of the provider
+ and default that pulls from edx platform settings.
+ """
+ def from_json(self, value):
+ """
+ Return ProctoringProvider as full featured Python type. Perform validation on the provider
+ and include any inherited values from the platform default.
+ """
+ errors = []
+ value = super(ProctoringProvider, self).from_json(value)
+
+ provider_errors = self._validate_proctoring_provider(value)
+ errors.extend(provider_errors)
+
+ if errors:
+ raise ValueError(errors)
+
+ value = self._get_proctoring_value(value)
+
+ return value
+
+ def _get_proctoring_value(self, value):
+ """
+ Return a proctoring value that includes any inherited attributes from the platform defaults
+ for the provider.
+ """
+ # if provider is missing from the value, return the default
+ if value is None:
+ return self.default
+
+ return value
+
+ def _validate_proctoring_provider(self, value):
+ """
+ Validate the value for the proctoring provider. If the proctoring provider value is
+ specified, and it is not one of the providers configured at the platform level, return
+ a list of error messages to the caller.
+ """
+ errors = []
+
+ available_providers = get_available_providers()
+
+ if value and value not in available_providers:
+ errors.append(
+ _('The selected proctoring provider, {proctoring_provider}, is not a valid provider. '
+ 'Please select from one of {available_providers}.')
+ .format(
+ proctoring_provider=value,
+ available_providers=available_providers
+ )
+ )
+
+ return errors
+
+ @property
+ def default(self):
+ """
+ Return default value for ProctoringProvider.
+ """
+ default = super(ProctoringProvider, self).default
+
+ proctoring_backend_settings = getattr(settings, 'PROCTORING_BACKENDS', None)
+
+ if proctoring_backend_settings:
+ return proctoring_backend_settings.get('DEFAULT', None)
+
+ return default
+
+
+def get_available_providers():
+ proctoring_backend_settings = getattr(
+ settings,
+ 'PROCTORING_BACKENDS',
+ {}
+ )
+
+ available_providers = [provider for provider in proctoring_backend_settings if provider != 'DEFAULT']
+ available_providers.sort()
+ return available_providers
+
+
class CourseFields(object):
lti_passports = List(
display_name=_("LTI Passports"),
@@ -738,6 +821,21 @@ class CourseFields(object):
scope=Scope.settings
)
+ proctoring_provider = ProctoringProvider(
+ display_name=_("Proctoring Provider"),
+ help=_(
+ "Enter the proctoring provider you want to use for this course run. "
+ "Choose from the following options: {available_providers}."),
+ help_format_args=dict(
+ # Put the available providers into a format variable so that translators
+ # don't translate them.
+ available_providers=(
+ ', '.join(get_available_providers())
+ ),
+ ),
+ scope=Scope.settings,
+ )
+
allow_proctoring_opt_out = Boolean(
display_name=_("Allow Opting Out of Proctored Exams"),
help=_(
@@ -1049,7 +1147,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
def definition_to_xml(self, resource_fs):
xml_object = super(CourseDescriptor, self).definition_to_xml(resource_fs)
- if len(self.textbooks) > 0:
+ if self.textbooks:
textbook_xml_object = etree.Element('textbook')
for textbook in self.textbooks:
textbook_xml_object.set('title', textbook.title)
@@ -1103,7 +1201,8 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
@raw_grader.setter
def raw_grader(self, value):
- # NOTE WELL: this change will not update the processed graders. If we need that, this needs to call grader_from_conf
+ # NOTE WELL: this change will not update the processed graders.
+ # If we need that, this needs to call grader_from_conf.
self._grading_policy['RAW_GRADER'] = value
self.grading_policy['GRADER'] = value
diff --git a/common/lib/xmodule/xmodule/tests/test_course_module.py b/common/lib/xmodule/xmodule/tests/test_course_module.py
index 16f53729d9..eb00aa57e5 100644
--- a/common/lib/xmodule/xmodule/tests/test_course_module.py
+++ b/common/lib/xmodule/xmodule/tests/test_course_module.py
@@ -9,6 +9,8 @@ from fs.memoryfs import MemoryFS
from mock import Mock, patch
from pytz import utc
from xblock.runtime import KvsFieldData, DictKeyValueStore
+from django.conf import settings
+from django.test import override_settings
import xmodule.course_module
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore
@@ -419,3 +421,73 @@ class CourseDescriptorTestCase(unittest.TestCase):
"""
expected_certificate_available_date = self.course.end + timedelta(days=2)
self.assertEqual(expected_certificate_available_date, self.course.certificate_available_date)
+
+
+class ProctoringProviderTestCase(unittest.TestCase):
+ """
+ Tests for ProctoringProvider, including the default value, validation, and inheritance behavior.
+ """
+ shard = 1
+
+ def setUp(self):
+ """
+ Initialize dummy testing course.
+ """
+ super(ProctoringProviderTestCase, self).setUp()
+ self.proctoring_provider = xmodule.course_module.ProctoringProvider()
+
+ def test_from_json_with_platform_default(self):
+ """
+ Test that a proctoring provider value equivalent to the platform
+ default will pass validation.
+ """
+ default_provider = settings.PROCTORING_BACKENDS.get('DEFAULT')
+
+ # we expect the validated value to be equivalent to the value passed in,
+ # since there are no validation errors or missing data
+ self.assertEqual(self.proctoring_provider.from_json(default_provider), default_provider)
+
+ def test_from_json_with_invalid_provider(self):
+ """
+ Test that an invalid provider (i.e. not one configured at the platform level)
+ throws a ValueError with the correct error message.
+ """
+ provider = 'invalid-provider'
+ proctoring_provider_whitelist = [u'mock', u'mock_proctoring_without_rules']
+
+ with self.assertRaises(ValueError) as context_manager:
+ self.proctoring_provider.from_json(provider)
+ self.assertEqual(
+ context_manager.exception.args[0],
+ ['The selected proctoring provider, {}, is not a valid provider. Please select from one of {}.'
+ .format(provider, proctoring_provider_whitelist)]
+ )
+
+ def test_from_json_adds_platform_default_for_missing_provider(self):
+ """
+ Test that a value with no provider will inherit the default provider
+ from the platform defaults.
+ """
+ default_provider = 'mock'
+
+ self.assertEqual(self.proctoring_provider.from_json(None), default_provider)
+
+ @override_settings(
+ PROCTORING_BACKENDS={
+ 'mock': {},
+ 'mock_proctoring_without_rules': {}
+ }
+ )
+ def test_default_with_no_platform_default(self):
+ """
+ Test that, when the platform defaults are not set, the default is correct.
+ """
+ self. assertEqual(self.proctoring_provider.default, None)
+
+ @override_settings(PROCTORING_BACKENDS=None)
+ def test_default_with_no_platform_configuration(self):
+ """
+ Test that, when the platform default is not specified, the default is correct.
+ """
+ default = self.proctoring_provider.default
+ self.assertEqual(default, None)
diff --git a/common/static/common/js/karma.common.conf.js b/common/static/common/js/karma.common.conf.js
index 5cff42b208..c0cb71dfd2 100644
--- a/common/static/common/js/karma.common.conf.js
+++ b/common/static/common/js/karma.common.conf.js
@@ -54,7 +54,7 @@ var webpackConfig = require(path.join(appRoot, 'webpack.dev.config.js'));
// https://github.com/webpack-contrib/karma-webpack/issues/24#issuecomment-257613167
//
// This should be fixed in v3 of karma-webpack
-var commonsChunkPluginIndex = webpackConfig.plugins.findIndex(function(plugin) { return plugin.chunkNames; });
+var commonsChunkPluginIndex = webpackConfig[0].plugins.findIndex(function(plugin) { return plugin.chunkNames; });
// Files which are needed by all lms/cms suites.
var commonFiles = {
@@ -80,9 +80,9 @@ var commonFiles = {
]
};
-webpackConfig.plugins.splice(commonsChunkPluginIndex, 1);
+webpackConfig[0].plugins.splice(commonsChunkPluginIndex, 1);
-delete webpackConfig.entry;
+delete webpackConfig[0].entry;
/**
* Customize the name attribute in xml testcase element
@@ -410,7 +410,7 @@ function getBaseConfig(config, useRequireJs) {
captureConsole: false
},
- webpack: webpackConfig,
+ webpack: webpackConfig[0],
webpackMiddleware: {
watchOptions: {
diff --git a/common/test/acceptance/pages/studio/settings_advanced.py b/common/test/acceptance/pages/studio/settings_advanced.py
index 2fd3cf858d..69372af6de 100644
--- a/common/test/acceptance/pages/studio/settings_advanced.py
+++ b/common/test/acceptance/pages/studio/settings_advanced.py
@@ -266,7 +266,6 @@ class AdvancedSettingsPage(CoursePage):
'enable_subsection_gating',
'learning_info',
'instructor_info',
- 'create_zendesk_tickets',
'ccx_connector',
'enable_ccx',
]
diff --git a/lms/djangoapps/instructor/services.py b/lms/djangoapps/instructor/services.py
index d644bfb6f9..8947cef23a 100644
--- a/lms/djangoapps/instructor/services.py
+++ b/lms/djangoapps/instructor/services.py
@@ -88,7 +88,7 @@ class InstructorService(object):
"""
return auth.user_has_role(user, CourseStaffRole(CourseKey.from_string(course_id)))
- def send_support_notification(self, course_id, exam_name, student_username, review_status):
+ def send_support_notification(self, course_id, exam_name, student_username, review_status, review_url=None):
"""
Creates a Zendesk ticket for an exam attempt review from the proctoring system.
Currently, it sends notifications for 'Suspicious" status, but additional statuses can be supported
@@ -103,15 +103,17 @@ class InstructorService(object):
if course.create_zendesk_tickets:
requester_name = "edx-proctoring"
email = "edx-proctoring@edx.org"
- subject = _("Proctored Exam Review: {review_status}").format(review_status=review_status)
+ subject = _(u"Proctored Exam Review: {review_status}").format(review_status=review_status)
body = _(
- "A proctored exam attempt for {exam_name} in {course_name} by username: {student_username} "
- "was reviewed as {review_status} by the proctored exam review provider."
+ u"A proctored exam attempt for {exam_name} in {course_name} by username: {student_username} "
+ "was reviewed as {review_status} by the proctored exam review provider.\n"
+ "Review link: {review_url}"
).format(
exam_name=exam_name,
course_name=course.display_name,
student_username=student_username,
- review_status=review_status
+ review_status=review_status,
+ review_url=review_url or u'not available',
)
tags = ["proctoring"]
create_zendesk_ticket(requester_name, email, subject, body, tags)
diff --git a/lms/djangoapps/instructor/tests/test_services.py b/lms/djangoapps/instructor/tests/test_services.py
index f52d1da434..84856eac9f 100644
--- a/lms/djangoapps/instructor/tests/test_services.py
+++ b/lms/djangoapps/instructor/tests/test_services.py
@@ -147,21 +147,40 @@ class InstructorServiceTests(SharedModuleStoreTestCase):
"""
requester_name = "edx-proctoring"
email = "edx-proctoring@edx.org"
- subject = "Proctored Exam Review: {review_status}".format(review_status="Suspicious")
+ subject = u"Proctored Exam Review: {review_status}".format(review_status="Suspicious")
+
body = "A proctored exam attempt for {exam_name} in {course_name} by username: {student_username} was " \
- "reviewed as {review_status} by the proctored exam review provider."
- body = body.format(
- exam_name="test_exam", course_name=self.course.display_name, student_username="test_student",
- review_status="Suspicious"
- )
+ "reviewed as {review_status} by the proctored exam review provider.\n" \
+ "Review link: {url}"
+ args = {
+ 'exam_name': 'test_exam',
+ 'student_username': 'test_student',
+ 'url': 'not available',
+ 'course_name': self.course.display_name,
+ 'review_status': 'Suspicious',
+ }
+ expected_body = body.format(**args)
tags = ["proctoring"]
with mock.patch("lms.djangoapps.instructor.services.create_zendesk_ticket") as mock_create_zendesk_ticket:
self.service.send_support_notification(
course_id=unicode(self.course.id),
- exam_name="test_exam",
- student_username="test_student",
- review_status="Suspicious"
+ exam_name=args['exam_name'],
+ student_username=args["student_username"],
+ review_status="Suspicious",
+ review_url=None,
)
- mock_create_zendesk_ticket.assert_called_with(requester_name, email, subject, body, tags)
+ mock_create_zendesk_ticket.assert_called_with(requester_name, email, subject, expected_body, tags)
+ # Now check sending a notification with a review link
+ args['url'] = 'http://review/url'
+ with mock.patch("lms.djangoapps.instructor.services.create_zendesk_ticket") as mock_create_zendesk_ticket:
+ self.service.send_support_notification(
+ course_id=unicode(self.course.id),
+ exam_name=args['exam_name'],
+ student_username=args["student_username"],
+ review_status="Suspicious",
+ review_url=args['url'],
+ )
+ expected_body = body.format(**args)
+ mock_create_zendesk_ticket.assert_called_with(requester_name, email, subject, expected_body, tags)
diff --git a/lms/envs/aws.py b/lms/envs/aws.py
index 439597f85e..3ef99d2e03 100644
--- a/lms/envs/aws.py
+++ b/lms/envs/aws.py
@@ -892,11 +892,6 @@ CREDIT_HELP_LINK_URL = ENV_TOKENS.get('CREDIT_HELP_LINK_URL', CREDIT_HELP_LINK_U
JWT_AUTH.update(ENV_TOKENS.get('JWT_AUTH', {}))
JWT_AUTH.update(AUTH_TOKENS.get('JWT_AUTH', {}))
-################# PROCTORING CONFIGURATION ##################
-
-PROCTORING_BACKEND_PROVIDER = AUTH_TOKENS.get("PROCTORING_BACKEND_PROVIDER", PROCTORING_BACKEND_PROVIDER)
-PROCTORING_SETTINGS = ENV_TOKENS.get("PROCTORING_SETTINGS", PROCTORING_SETTINGS)
-
################# MICROSITE ####################
MICROSITE_CONFIGURATION = ENV_TOKENS.get('MICROSITE_CONFIGURATION', {})
MICROSITE_ROOT_DIR = path(ENV_TOKENS.get('MICROSITE_ROOT_DIR', ''))
diff --git a/lms/envs/common.py b/lms/envs/common.py
index b6d939af3e..a64cfd2a41 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -1345,28 +1345,6 @@ courseware_js = [
'js/modules/tab.js',
]
-proctoring_js = (
- [
- 'proctoring/js/models/proctored_exam_allowance_model.js',
- 'proctoring/js/models/proctored_exam_attempt_model.js',
- 'proctoring/js/models/proctored_exam_model.js'
- ] +
- [
- 'proctoring/js/collections/proctored_exam_allowance_collection.js',
- 'proctoring/js/collections/proctored_exam_attempt_collection.js',
- 'proctoring/js/collections/proctored_exam_collection.js'
- ] +
- [
- 'proctoring/js/views/Backbone.ModalDialog.js',
- 'proctoring/js/views/proctored_exam_add_allowance_view.js',
- 'proctoring/js/views/proctored_exam_allowance_view.js',
- 'proctoring/js/views/proctored_exam_attempt_view.js',
- 'proctoring/js/views/proctored_exam_view.js'
- ] +
- [
- 'proctoring/js/proctored_app.js'
- ]
-)
# Before a student accesses courseware, we do not
# need many of the JS dependencies. This includes
@@ -1693,10 +1671,6 @@ PIPELINE_JS = {
),
'output_filename': 'js/lms-application.js',
},
- 'proctoring': {
- 'source_filenames': proctoring_js,
- 'output_filename': 'js/lms-proctoring.js',
- },
'courseware': {
'source_filenames': courseware_js,
'output_filename': 'js/lms-courseware.js',
@@ -1841,6 +1815,10 @@ WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'bundles/',
'STATS_FILE': os.path.join(STATIC_ROOT, 'webpack-stats.json')
+ },
+ 'WORKERS': {
+ 'BUNDLE_DIR_NAME': 'bundles/',
+ 'STATS_FILE': os.path.join(STATIC_ROOT, 'webpack-worker-stats.json')
}
}
WEBPACK_CONFIG_PATH = 'webpack.prod.config.js'
@@ -2881,9 +2859,6 @@ OPTIONAL_APPS = [
# edxval
('edxval', 'openedx.core.djangoapps.content.course_overviews.apps.CourseOverviewsConfig'),
- # edX Proctoring
- ('edx_proctoring', None),
-
# Organizations App (http://github.com/edx/edx-organizations)
('organizations', None),
@@ -3215,14 +3190,6 @@ MICROSITE_DATABASE_TEMPLATE_CACHE_TTL = 5 * 60
RSS_PROXY_CACHE_TIMEOUT = 3600 # The length of time we cache RSS retrieved from remote URLs in seconds
-#### PROCTORING CONFIGURATION DEFAULTS
-
-PROCTORING_BACKEND_PROVIDER = {
- 'class': 'edx_proctoring.backends.null.NullBackendProvider',
- 'options': {},
-}
-PROCTORING_SETTINGS = {}
-
#### Custom Courses for EDX (CCX) configuration
# This is an arbitrary hard limit.
diff --git a/lms/envs/production.py b/lms/envs/production.py
index a98925a535..3cd0ec018e 100644
--- a/lms/envs/production.py
+++ b/lms/envs/production.py
@@ -112,6 +112,7 @@ STATIC_ROOT_BASE = ENV_TOKENS.get('STATIC_ROOT_BASE', None)
if STATIC_ROOT_BASE:
STATIC_ROOT = path(STATIC_ROOT_BASE)
WEBPACK_LOADER['DEFAULT']['STATS_FILE'] = STATIC_ROOT / "webpack-stats.json"
+ WEBPACK_LOADER['WORKERS']['STATS_FILE'] = STATIC_ROOT / "webpack-worker-stats.json"
# STATIC_URL_BASE specifies the base url to use for static files
@@ -888,11 +889,6 @@ CREDIT_HELP_LINK_URL = ENV_TOKENS.get('CREDIT_HELP_LINK_URL', CREDIT_HELP_LINK_U
JWT_AUTH.update(ENV_TOKENS.get('JWT_AUTH', {}))
JWT_AUTH.update(AUTH_TOKENS.get('JWT_AUTH', {}))
-################# PROCTORING CONFIGURATION ##################
-
-PROCTORING_BACKEND_PROVIDER = AUTH_TOKENS.get("PROCTORING_BACKEND_PROVIDER", PROCTORING_BACKEND_PROVIDER)
-PROCTORING_SETTINGS = ENV_TOKENS.get("PROCTORING_SETTINGS", PROCTORING_SETTINGS)
-
################# MICROSITE ####################
MICROSITE_CONFIGURATION = ENV_TOKENS.get('MICROSITE_CONFIGURATION', {})
MICROSITE_ROOT_DIR = path(ENV_TOKENS.get('MICROSITE_ROOT_DIR', ''))
diff --git a/lms/envs/test_static_optimized.py b/lms/envs/test_static_optimized.py
index 25a44eb9e4..bf5bf7f6da 100644
--- a/lms/envs/test_static_optimized.py
+++ b/lms/envs/test_static_optimized.py
@@ -35,6 +35,11 @@ XQUEUE_INTERFACE = {
"basic_auth": ('anant', 'agarwal'),
}
+PROCTORING_BACKENDS = {
+ 'DEFAULT': 'mock',
+ 'mock': {},
+ 'mock_proctoring_without_rules': {},
+}
######################### PIPELINE ####################################
diff --git a/lms/static/js/instructor_dashboard/instructor_dashboard.js b/lms/static/js/instructor_dashboard/instructor_dashboard.js
index 58c65d2c5d..c58e7610e9 100644
--- a/lms/static/js/instructor_dashboard/instructor_dashboard.js
+++ b/lms/static/js/instructor_dashboard/instructor_dashboard.js
@@ -206,6 +206,9 @@ such that the value can be defined later than this assignment (file load order).
}, {
constructor: edx.instructor_dashboard.proctoring.ProctoredExamAttemptView,
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#special_exams')
+ }, {
+ constructor: edx.instructor_dashboard.proctoring.ProctoredExamDashboardView,
+ $element: idashContent.find('.' + CSS_IDASH_SECTION + '#special_exams')
}
]);
}
diff --git a/lms/static/js/instructor_dashboard/proctoring.js b/lms/static/js/instructor_dashboard/proctoring.js
index 22a44b5500..9ef274f9de 100644
--- a/lms/static/js/instructor_dashboard/proctoring.js
+++ b/lms/static/js/instructor_dashboard/proctoring.js
@@ -7,15 +7,9 @@ $(function() {
$proctoringAccordionPane.accordion(
{
heightStyle: 'content',
- activate: function(event, ui) {
- var active = $proctoringAccordionPane.accordion('option', 'active');
- $.cookie('saved_index', null);
- $.cookie('saved_index', active);
- },
animate: 400,
header: '> .wrap > .hd',
icons: icons,
- active: isNaN(parseInt($.cookie('saved_index'))) ? 0 : parseInt($.cookie('saved_index')),
collapsible: true
}
);
diff --git a/lms/templates/instructor/instructor_dashboard_2/special_exams.html b/lms/templates/instructor/instructor_dashboard_2/special_exams.html
index a878a94207..f823ea9628 100644
--- a/lms/templates/instructor/instructor_dashboard_2/special_exams.html
+++ b/lms/templates/instructor/instructor_dashboard_2/special_exams.html
@@ -14,5 +14,9 @@ import pytz
${_('Student Special Exam Attempts')}
+
+
${_('Review Dashboard')}
+
+
diff --git a/lms/urls.py b/lms/urls.py
index 762cf863c5..ba2be98a95 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -1012,11 +1012,6 @@ if 'debug_toolbar' in settings.INSTALLED_APPS:
url(r'^__debug__/', include(debug_toolbar.urls)),
]
-# include into our URL patterns the HTTP REST API that comes with edx-proctoring.
-urlpatterns += [
- url(r'^api/', include('edx_proctoring.urls')),
-]
-
if settings.FEATURES.get('ENABLE_FINANCIAL_ASSISTANCE_FORM'):
urlpatterns += [
url(
diff --git a/openedx/tests/settings.py b/openedx/tests/settings.py
index 22da32f525..7403435b4f 100644
--- a/openedx/tests/settings.py
+++ b/openedx/tests/settings.py
@@ -51,6 +51,12 @@ DATABASES = {
}
}
+PROCTORING_BACKENDS = {
+ 'DEFAULT': 'mock',
+ 'mock': {},
+ 'mock_proctoring_without_rules': {},
+}
+
FEATURES = {}
INSTALLED_APPS = (
diff --git a/package-lock.json b/package-lock.json
index 0cab901273..77241e89fa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -85,6 +85,18 @@
}
}
},
+ "@edx/edx-proctoring": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@edx/edx-proctoring/-/edx-proctoring-1.5.0.tgz",
+ "integrity": "sha512-RiNjAgh8ZMX0D5gfN2R09a0RBs/R/Blfs/DiqhLmvCSvyCoeMDGANrDDQXv1w5blxxSJbz8a2awSZkwpv6gWNQ=="
+ },
+ "@edx/mockprock": {
+ "version": "git+https://git@github.com/edx/mockprock.git#c9e4814ace9afad7a778e2af372b3125b3e56588",
+ "dev": true,
+ "requires": {
+ "@edx/edx-proctoring": "1.5.0"
+ }
+ },
"@edx/paragon": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-2.6.4.tgz",
@@ -247,7 +259,7 @@
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
- "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+ "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg="
},
"accepts": {
"version": "1.3.3",
@@ -509,7 +521,7 @@
"arr-flatten": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
- "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg=="
+ "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE="
},
"arr-union": {
"version": "3.1.0",
@@ -1523,7 +1535,7 @@
"babylon": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
- "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ=="
+ "integrity": "sha1-ry87iPpvXB5MY00aD46sT1WzleM="
},
"backbone": {
"version": "1.3.3",
@@ -1728,7 +1740,7 @@
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
- "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
+ "integrity": "sha1-LN4J617jQfSEdGuwMJsyU7GxRC8="
},
"body-parser": {
"version": "1.18.2",
@@ -1891,7 +1903,7 @@
"browserify-zlib": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
- "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "integrity": "sha1-KGlFnZqjviRf6P4sofRuLn9U1z8=",
"requires": {
"pako": "1.0.6"
}
@@ -2182,7 +2194,7 @@
"cipher-base": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
- "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+ "integrity": "sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=",
"requires": {
"inherits": "2.0.3",
"safe-buffer": "5.1.1"
@@ -2203,7 +2215,7 @@
"clap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz",
- "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==",
+ "integrity": "sha1-TzZ0WzIAhJJVf0ZBLWbVDLmbzlE=",
"requires": {
"chalk": "1.1.3"
},
@@ -2401,7 +2413,7 @@
"color-convert": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
- "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
+ "integrity": "sha1-wSYRB66y8pTr/+ye2eytUppgl+0=",
"requires": {
"color-name": "1.1.3"
}
@@ -2568,7 +2580,7 @@
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
- "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+ "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=",
"dev": true
},
"convert-source-map": {
@@ -2608,7 +2620,7 @@
"cosmiconfig": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-3.1.0.tgz",
- "integrity": "sha512-zedsBhLSbPBms+kE7AH4vHg6JsKDz6epSv2/+5XHs8ILHlgDciSJfSWf8sX9aQ52Jb7KI7VswUTsLpR/G0cr2Q==",
+ "integrity": "sha1-ZAqUv5hH8yGABAPNJzr2BmXHM5c=",
"dev": true,
"requires": {
"is-directory": "0.3.1",
@@ -2620,7 +2632,7 @@
"esprima": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
- "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
+ "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=",
"dev": true
},
"js-yaml": {
@@ -2707,7 +2719,7 @@
"crypto-browserify": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
- "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+ "integrity": "sha1-OWz58xN/A+S45TLFj2mCVOAPgOw=",
"requires": {
"browserify-cipher": "1.0.0",
"browserify-sign": "4.0.4",
@@ -2945,6 +2957,31 @@
"whatwg-url": "6.5.0"
}
},
+ "datatables": {
+ "version": "1.10.18",
+ "resolved": "https://registry.npmjs.org/datatables/-/datatables-1.10.18.tgz",
+ "integrity": "sha512-ntatMgS9NN6UMpwbmO+QkYJuKlVeMA2Mi0Gu/QxyIh+dW7ZjLSDhPT2tWlzjpIWEkDYgieDzS9Nu7bdQCW0sbQ==",
+ "requires": {
+ "jquery": "2.2.4"
+ }
+ },
+ "datatables.net": {
+ "version": "1.10.19",
+ "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.10.19.tgz",
+ "integrity": "sha512-+ljXcI6Pj3PTGy5pesp3E5Dr3x3AV45EZe0o1r0gKENN2gafBKXodVnk2ypKwl2tTmivjxbkiqoWnipTefyBTA==",
+ "requires": {
+ "jquery": "2.2.4"
+ }
+ },
+ "datatables.net-fixedcolumns": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/datatables.net-fixedcolumns/-/datatables.net-fixedcolumns-3.2.6.tgz",
+ "integrity": "sha512-PtEs2tllcHRVZj7fwmAQBWGJ5URRQZpDG2pJsh5jusvnRje3w1+KueMZm60iCtfOkIlUn+/j2+MghxLx/8yfKQ==",
+ "requires": {
+ "datatables.net": "1.10.19",
+ "jquery": "2.2.4"
+ }
+ },
"date-now": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
@@ -2958,7 +2995,7 @@
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
"requires": {
"ms": "2.0.0"
}
@@ -3338,6 +3375,12 @@
}
}
},
+ "edx-proctoring-proctortrack": {
+ "version": "git+https://git@github.com/joshivj/edx-proctoring-proctortrack.git#66650ed6cd39bf489a86723d5ad3593c2ec8992f",
+ "requires": {
+ "@edx/edx-proctoring": "1.5.0"
+ }
+ },
"edx-ui-toolkit": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/edx-ui-toolkit/-/edx-ui-toolkit-1.5.2.tgz",
@@ -3431,7 +3474,7 @@
"emoji-regex": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",
- "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==",
+ "integrity": "sha1-m66pKbFVVlwR6kHGYm6qZc75ksI=",
"dev": true
},
"emojis-list": {
@@ -4017,7 +4060,7 @@
"eslint-config-airbnb-base": {
"version": "11.3.2",
"resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-11.3.2.tgz",
- "integrity": "sha512-/fhjt/VqzBA2SRsx7ErDtv6Ayf+XLw9LIOqmpBuHFCVwyJo2EtzGWMB9fYRFBoWWQLxmNmCpenNiH0RxyeS41w==",
+ "integrity": "sha1-hwOxGr48iKx+wrdFt/31LgCuaAo=",
"dev": true,
"requires": {
"eslint-restricted-globals": "0.1.1"
@@ -4315,7 +4358,7 @@
"evp_bytestokey": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
- "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+ "integrity": "sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI=",
"requires": {
"md5.js": "1.3.4",
"safe-buffer": "5.1.1"
@@ -4522,7 +4565,7 @@
"async": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz",
- "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==",
+ "integrity": "sha1-YaKau2/MAm/qd+VtHG7FOnlZUfQ=",
"requires": {
"lodash": "4.17.5"
}
@@ -4899,7 +4942,7 @@
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0="
},
"function.prototype.name": {
"version": "1.1.0",
@@ -4987,7 +5030,7 @@
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
- "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+ "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=",
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
@@ -5017,7 +5060,7 @@
"globals": {
"version": "9.18.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
- "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ=="
+ "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo="
},
"globby": {
"version": "7.1.1",
@@ -5570,7 +5613,7 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM="
}
}
},
@@ -5582,7 +5625,7 @@
"ignore": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz",
- "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==",
+ "integrity": "sha1-YSKJv7PCIOGGpYEYYY1b6MG6sCE=",
"dev": true
},
"import-local": {
@@ -5851,7 +5894,7 @@
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
- "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4="
},
"is-builtin-module": {
"version": "1.0.0",
@@ -6048,7 +6091,7 @@
"is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
- "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=",
"requires": {
"isobject": "3.0.1"
}
@@ -8231,7 +8274,7 @@
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
- "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+ "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=",
"requires": {
"safe-buffer": "5.1.1"
}
@@ -8376,7 +8419,7 @@
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
"requires": {
"brace-expansion": "1.1.11"
}
@@ -8562,7 +8605,7 @@
"node-fetch": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
- "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
+ "integrity": "sha1-mA9vcthSEaU0fGsrwYxbhMPrR+8=",
"requires": {
"encoding": "0.1.12",
"is-stream": "1.1.0"
@@ -8604,7 +8647,7 @@
"node-libs-browser": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz",
- "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==",
+ "integrity": "sha1-X5QmPUBPbkR2fXJpAf/wVHjWAN8=",
"requires": {
"assert": "1.4.1",
"browserify-zlib": "0.2.0",
@@ -8775,7 +8818,7 @@
"normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
- "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
+ "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=",
"requires": {
"hosted-git-info": "2.5.0",
"is-builtin-module": "1.0.0",
@@ -9203,7 +9246,7 @@
"pako": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz",
- "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg=="
+ "integrity": "sha1-AQEhG6pwxLykoPY/Igbpe3368lg="
},
"parse-asn1": {
"version": "5.1.0",
@@ -9356,7 +9399,7 @@
"pbkdf2": {
"version": "3.0.14",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz",
- "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==",
+ "integrity": "sha1-o14TxkeZsGzhUyD0WcIw5o5zut4=",
"requires": {
"create-hash": "1.1.3",
"create-hmac": "1.1.6",
@@ -9790,7 +9833,7 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM="
}
}
},
@@ -9878,7 +9921,7 @@
"postcss-reporter": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-5.0.0.tgz",
- "integrity": "sha512-rBkDbaHAu5uywbCR2XE8a25tats3xSOsGNx6mppK6Q9kSFGKc/FyAzfci+fWM2l+K402p1D0pNcfDGxeje5IKg==",
+ "integrity": "sha1-oUF3/RNCgp0pFlPyeG79ZxEDMsM=",
"dev": true,
"requires": {
"chalk": "2.3.1",
@@ -9901,7 +9944,7 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=",
"dev": true
}
}
@@ -9964,7 +10007,7 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=",
"dev": true
}
}
@@ -10080,7 +10123,7 @@
"private": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
- "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg=="
+ "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8="
},
"process": {
"version": "0.11.10",
@@ -10101,7 +10144,7 @@
"promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
- "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+ "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=",
"requires": {
"asap": "2.0.6"
}
@@ -10236,7 +10279,7 @@
"randomatic": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
- "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==",
+ "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=",
"requires": {
"is-number": "3.0.0",
"kind-of": "4.0.0"
@@ -10590,7 +10633,7 @@
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
- "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+ "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=",
"requires": {
"safe-buffer": "5.1.1"
}
@@ -10670,7 +10713,7 @@
"redux": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
- "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
+ "integrity": "sha1-BrcxIyFZAdJdBlvjQusCa8HIU3s=",
"requires": {
"lodash": "4.17.5",
"lodash-es": "4.17.6",
@@ -10706,7 +10749,7 @@
"regenerator-transform": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz",
- "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==",
+ "integrity": "sha1-HkmWg3Ix2ot/PPQRTXG1aRoGgN0=",
"requires": {
"babel-runtime": "6.26.0",
"babel-types": "6.26.0",
@@ -11050,7 +11093,7 @@
"rtlcss": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-2.2.1.tgz",
- "integrity": "sha512-JjQ5DlrmwiItAjlmhoxrJq5ihgZcE0wMFxt7S17bIrt4Lw0WwKKFk+viRhvodB/0falyG/5fiO043ZDh6/aqTw==",
+ "integrity": "sha1-+FN+QVUggWawXhiYAhMZNvzv0p4=",
"requires": {
"chalk": "2.3.1",
"findup": "0.1.5",
@@ -11072,7 +11115,7 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM="
}
}
},
@@ -11094,7 +11137,7 @@
"safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
- "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
+ "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM="
},
"safe-regex": {
"version": "1.1.0",
@@ -11488,7 +11531,7 @@
"sass-loader": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-6.0.6.tgz",
- "integrity": "sha512-c3/Zc+iW+qqDip6kXPYLEgsAu2lf4xz0EZDplB7EmSUMda12U1sGJPetH55B/j9eu0bTtKzKlNPWWyYC7wFNyQ==",
+ "integrity": "sha1-6dXmwfFV+qMqSybXqbcQfCJeQPk=",
"requires": {
"async": "2.6.0",
"clone-deep": "0.3.0",
@@ -11500,7 +11543,7 @@
"async": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz",
- "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==",
+ "integrity": "sha1-YaKau2/MAm/qd+VtHG7FOnlZUfQ=",
"requires": {
"lodash": "4.17.5"
}
@@ -11770,7 +11813,7 @@
"slice-ansi": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz",
- "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==",
+ "integrity": "sha1-BE8aSdiEL/MHqta1Be0Xi9lQE00=",
"dev": true,
"requires": {
"is-fullwidth-code-point": "2.0.0"
@@ -12046,7 +12089,7 @@
"source-list-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz",
- "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A=="
+ "integrity": "sha1-qqR0A/eyRakvvJfqCPJQ1gh+0IU="
},
"source-map": {
"version": "0.5.7",
@@ -12116,7 +12159,7 @@
"specificity": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/specificity/-/specificity-0.3.2.tgz",
- "integrity": "sha512-Nc/QN/A425Qog7j9aHmwOrlwX2e7pNI47ciwxwy4jOlvbbMHkNNJchit+FX+UjF3IAdiaaV5BKeWuDUnws6G1A==",
+ "integrity": "sha1-meZRHs7vD42bV5JJN6rCyxPRPEI=",
"dev": true
},
"split-string": {
@@ -12283,7 +12326,7 @@
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
- "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+ "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=",
"requires": {
"safe-buffer": "5.1.1"
}
@@ -12506,7 +12549,7 @@
"style-loader": {
"version": "0.18.2",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.18.2.tgz",
- "integrity": "sha512-WPpJPZGUxWYHWIUMNNOYqql7zh85zGmr84FdTVWq52WTIkqlW9xSxD3QYWi/T31cqn9UNSsietVEgGn2aaSCzw==",
+ "integrity": "sha1-zDFFmvvNbYC3Ig7lSykan9Zv9es=",
"requires": {
"loader-utils": "1.1.0",
"schema-utils": "0.3.0"
@@ -12617,7 +12660,7 @@
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
- "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
"dev": true,
"requires": {
"ms": "2.0.0"
@@ -12772,13 +12815,13 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=",
"dev": true
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
- "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=",
"dev": true,
"requires": {
"is-fullwidth-code-point": "2.0.0",
@@ -12823,7 +12866,7 @@
"stylelint-config-recommended-scss": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-2.0.0.tgz",
- "integrity": "sha512-DUIW3daRl5EAyU4ZR6xfPa+bqV5wDccS7X1je6Enes9edpbmWUBR/5XLfDPnjMJgqOe2QwqwaE/qnG4lXID9rg==",
+ "integrity": "sha1-P0SzOK+zv1tr2e663UaO7ydxOSI=",
"dev": true,
"requires": {
"stylelint-config-recommended": "1.0.0"
@@ -12832,7 +12875,7 @@
"stylelint-config-standard": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-17.0.0.tgz",
- "integrity": "sha512-G8jMZ0KsaVH7leur9XLZVhwOBHZ2vdbuJV8Bgy0ta7/PpBhEHo6fjVDaNchyCGXB5sRcWVq6O9rEU/MvY9cQDQ==",
+ "integrity": "sha1-QhA6CQBU7io93p7K7VXl1NnQWfw=",
"dev": true,
"requires": {
"stylelint-config-recommended": "1.0.0"
@@ -12841,7 +12884,7 @@
"stylelint-formatter-pretty": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/stylelint-formatter-pretty/-/stylelint-formatter-pretty-1.0.3.tgz",
- "integrity": "sha512-Jg39kL6kkjUrdKIiHwwz/fbElcF5dOS48ZhvGrEJeWijUbmY1yudclfXv9H61eBqKKu0E33nfez2r0G4EvPtFA==",
+ "integrity": "sha1-prQ8PzoTIGvft3fQ2ozvxsdsNsM=",
"dev": true,
"requires": {
"ansi-escapes": "2.0.0",
@@ -12900,7 +12943,7 @@
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
- "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=",
"dev": true,
"requires": {
"is-fullwidth-code-point": "2.0.0",
@@ -12955,7 +12998,7 @@
"sugarss": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sugarss/-/sugarss-1.0.1.tgz",
- "integrity": "sha512-3qgLZytikQQEVn1/FrhY7B68gPUUGY3R1Q1vTiD5xT+Ti1DP/8iZuwFet9ONs5+bmL8pZoDQ6JrQHVgrNlK6mA==",
+ "integrity": "sha1-voJtkAPg8kdzX5I2XcP9fxuunkQ=",
"dev": true,
"requires": {
"postcss": "6.0.19"
@@ -12975,7 +13018,7 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=",
"dev": true
}
}
@@ -13446,7 +13489,7 @@
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
- "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+ "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=",
"requires": {
"safe-buffer": "5.1.1"
}
@@ -13469,7 +13512,7 @@
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
- "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=",
"dev": true,
"requires": {
"os-tmpdir": "1.0.2"
@@ -14403,7 +14446,7 @@
"webpack-merge": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.1.tgz",
- "integrity": "sha512-geQsZ86YkXOVOjvPC5yv3JSNnL6/X3Kzh935AQ/gJNEYXEfJDQFu/sdFuktS9OW2JcH/SJec8TGfRdrpHshH7A==",
+ "integrity": "sha1-8Rl6Cpc+acb77rbWWCGaqMDBNVU=",
"requires": {
"lodash": "4.17.5"
}
@@ -14420,7 +14463,7 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM="
}
}
},
@@ -14571,7 +14614,7 @@
"xml2js": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
- "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
+ "integrity": "sha1-aGwg8hMgnpSr8NG88e+qKRx4J6c=",
"dev": true,
"requires": {
"sax": "1.2.4",
diff --git a/package.json b/package.json
index 53933f92b5..d9da99f345 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"dependencies": {
"@edx/cookie-policy-banner": "1.1.10",
"@edx/edx-bootstrap": "1.0.3",
+ "@edx/edx-proctoring": "^1.5.0",
"@edx/paragon": "2.6.4",
"@edx/studio-frontend": "1.16.12",
"babel-core": "6.26.0",
@@ -21,7 +22,10 @@
"camelize": "1.0.0",
"classnames": "2.2.5",
"css-loader": "0.28.8",
+ "datatables": "1.10.18",
+ "datatables.net-fixedcolumns": "3.2.6",
"edx-pattern-library": "0.18.1",
+ "edx-proctoring-proctortrack": "git+https://git@github.com/joshivj/edx-proctoring-proctortrack.git",
"edx-ui-toolkit": "1.5.2",
"exports-loader": "0.6.4",
"extract-text-webpack-plugin": "2.1.2",
@@ -63,6 +67,7 @@
"which-country": "1.0.0"
},
"devDependencies": {
+ "@edx/mockprock": "^1.0.1",
"@edx/stylelint-config-edx": "1.1.0",
"babel-jest": "23.0.1",
"edx-custom-a11y-rules": "1.0.5",
diff --git a/requirements/constraints.txt b/requirements/constraints.txt
index a77a74eb9d..36a494baa8 100644
--- a/requirements/constraints.txt
+++ b/requirements/constraints.txt
@@ -12,8 +12,6 @@
# https://github.com/transifex/transifex-client/issues/252
six==1.11.0
-
-
# Convert text markup to HTML; used in capa problems, forums, and course wikis; pin Markdown version as tests failed for its upgrade to latest release
Markdown==2.6.11
@@ -21,9 +19,6 @@ Markdown==2.6.11
# Can be removed when we get to Python 3.
pylint-plugin-utils==0.3
-# Testing framework # Pinned due to https://github.com/pytest-dev/pytest/issues/3749
-pytest==3.6.3
-
# pytest plugin for measuring code coverage. # Pinned due to https://openedx.atlassian.net/browse/TE-2731
pytest-cov<2.6
diff --git a/requirements/edx/base.in b/requirements/edx/base.in
index e36a17747f..869932ca8a 100644
--- a/requirements/edx/base.in
+++ b/requirements/edx/base.in
@@ -80,7 +80,7 @@ edx-enterprise
edx-milestones
edx-oauth2-provider
edx-organizations
-edx-proctoring
+edx-proctoring>=1.5.0
edx-rest-api-client
edx-search
edx-submissions
@@ -133,7 +133,7 @@ rfc6266-parser # Used to generate Content-Disposition heade
social-auth-app-django<3.0.0
social-auth-core<2.0.0
pysrt==0.4.7 # Support for SubRip subtitle files, used in the video XModule
-pytz==2016.10 # Time zone information database
+pytz # Time zone information database
PyYAML # Used to parse XModule resource templates
redis==2.10.6 # celery task broker
requests-oauthlib # Simplifies use of OAuth via the requests library, used for CCX and LTI
diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt
index 2c4047545d..e670657dfe 100644
--- a/requirements/edx/base.txt
+++ b/requirements/edx/base.txt
@@ -118,13 +118,13 @@ edx-django-release-util==0.3.1
edx-django-sites-extensions==2.3.1
edx-django-utils==1.0.3
edx-drf-extensions==2.0.1
-edx-enterprise==1.2.0
+edx-enterprise==1.2.1
edx-i18n-tools==0.4.6
edx-milestones==0.1.13
edx-oauth2-provider==1.2.2
edx-opaque-keys[django]==0.4.4
edx-organizations==1.0.0
-edx-proctoring==1.4.0
+edx-proctoring==1.5.0
edx-rest-api-client==1.9.2
edx-search==1.2.1
edx-submissions==2.0.12
@@ -204,7 +204,7 @@ python-memcached==1.48
python-openid==2.2.5
python-saml==2.4.0
python-swiftclient==3.6.0
-pytz==2016.10
+pytz==2018.7
pyuca==1.1
pyyaml==3.13
redis==2.10.6
diff --git a/requirements/edx/coverage.in b/requirements/edx/coverage.in
index 26a1299140..a0c036431a 100644
--- a/requirements/edx/coverage.in
+++ b/requirements/edx/coverage.in
@@ -14,3 +14,4 @@
coverage==4.4 # Code coverage testing for Python
diff-cover==0.9.8 # Automatically find diff lines that need test coverage
+six==1.11.0 # Pinned because diff-cover needs it, but later transifex-client says ==1.11.0
diff --git a/requirements/edx/coverage.txt b/requirements/edx/coverage.txt
index a632cc4a62..0be086ff78 100644
--- a/requirements/edx/coverage.txt
+++ b/requirements/edx/coverage.txt
@@ -11,4 +11,4 @@ jinja2-pluralize==0.3.0 # via diff-cover
jinja2==2.10 # via diff-cover, jinja2-pluralize
markupsafe==1.1.0 # via jinja2
pygments==2.3.1 # via diff-cover
-six==1.11.0 # via diff-cover
+six==1.11.0
diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt
index 3468db47fc..cfaf668b26 100644
--- a/requirements/edx/development.txt
+++ b/requirements/edx/development.txt
@@ -137,14 +137,14 @@ edx-django-release-util==0.3.1
edx-django-sites-extensions==2.3.1
edx-django-utils==1.0.3
edx-drf-extensions==2.0.1
-edx-enterprise==1.2.0
+edx-enterprise==1.2.1
edx-i18n-tools==0.4.6
edx-lint==1.0.0
edx-milestones==0.1.13
edx-oauth2-provider==1.2.2
edx-opaque-keys[django]==0.4.4
edx-organizations==1.0.0
-edx-proctoring==1.4.0
+edx-proctoring==1.5.0
edx-rest-api-client==1.9.2
edx-search==1.2.1
edx-sphinx-theme==1.4.0
@@ -228,6 +228,7 @@ pa11ycrawler==1.6.2
packaging==18.0 # via sphinx
parsel==1.5.1
path.py==8.2.1
+pathlib2==2.3.3
pathtools==0.1.2
paver==1.3.4
pbr==5.1.1
@@ -235,7 +236,7 @@ pdfminer==20140328
piexif==1.0.2
pillow==5.3.0
pip-tools==3.2.0
-pluggy==0.6.0
+pluggy==0.8.0
polib==1.1.0
psutil==1.2.1
py2neo==3.1.2
@@ -271,7 +272,7 @@ pytest-django==3.1.2
pytest-forked==0.2
pytest-randomly==1.2.3
pytest-xdist==1.25.0
-pytest==3.6.3
+pytest==4.0.2
python-dateutil==2.4.0
python-levenshtein==0.12.0
python-memcached==1.48
@@ -281,7 +282,7 @@ python-saml==2.4.0
python-slugify==1.2.6
python-subunit==1.3.0
python-swiftclient==3.6.0
-pytz==2016.10
+pytz==2018.7
pyuca==1.1
pyyaml==3.13
queuelib==1.5.0
@@ -295,6 +296,7 @@ rfc6266-parser==0.0.5.post2
rules==2.0.1
s3transfer==0.1.13
sailthru-client==2.2.3
+scandir==1.9.0
scipy==0.14.0
scrapy==1.1.2
selenium==3.141.0
@@ -312,20 +314,20 @@ social-auth-app-django==2.1.0
social-auth-core==1.7.0
sorl-thumbnail==12.3
sortedcontainers==0.9.2
-sphinx==1.8.2
+sphinx==1.8.3
sphinxcontrib-websupport==1.1.0 # via sphinx
splinter==0.9.0
sqlparse==0.2.4
stevedore==1.10.0
sure==1.4.11
sympy==0.7.1
-testfixtures==6.4.0
+testfixtures==6.4.1
testtools==2.3.0
text-unidecode==1.2
tincan==0.0.5
toml==0.10.0
tox-battery==0.5.1
-tox==3.6.0
+tox==3.6.1
traceback2==1.4.0
transifex-client==0.13.5
twisted==16.6.0
diff --git a/requirements/edx/paver.in b/requirements/edx/paver.in
index 85b0656b3c..36b1cb4054 100644
--- a/requirements/edx/paver.in
+++ b/requirements/edx/paver.in
@@ -24,3 +24,5 @@ requests # Simple interface for making HTTP requests
stevedore==1.10.0 # via edx-opaque-keys
watchdog # Used in paver watch_assets
wrapt==1.10.5 # Decorator utilities used in the @timed paver task decorator
+
+six==1.11.0 # Pinned because a few things here need it, but later transifex-client says ==1.11.0
diff --git a/requirements/edx/paver.txt b/requirements/edx/paver.txt
index f0d2c0689c..979d82023a 100644
--- a/requirements/edx/paver.txt
+++ b/requirements/edx/paver.txt
@@ -23,7 +23,7 @@ pymongo==2.9.1
python-memcached==1.48
pyyaml==3.13 # via watchdog
requests==2.21.0
-six==1.11.0 # via edx-opaque-keys, libsass, paver, stevedore
+six==1.11.0
stevedore==1.10.0
urllib3==1.23 # via requests
watchdog==0.9.0
diff --git a/requirements/edx/pip-tools.in b/requirements/edx/pip-tools.in
index 50f3de3e19..098843a697 100644
--- a/requirements/edx/pip-tools.in
+++ b/requirements/edx/pip-tools.in
@@ -10,3 +10,4 @@
-c ../constraints.txt
pip-tools # Contains pip-compile, used to generate pip requirements files
+six==1.11.0 # Pinned because pip-tools needs it, but later transifex-client says ==1.11.0
diff --git a/requirements/edx/pip-tools.txt b/requirements/edx/pip-tools.txt
index 546f9d2811..feb69c6ac1 100644
--- a/requirements/edx/pip-tools.txt
+++ b/requirements/edx/pip-tools.txt
@@ -6,4 +6,4 @@
#
click==7.0 # via pip-tools
pip-tools==3.2.0
-six==1.11.0 # via pip-tools
+six==1.11.0
diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt
index ce1bd72028..fb9c730de4 100644
--- a/requirements/edx/testing.txt
+++ b/requirements/edx/testing.txt
@@ -132,14 +132,14 @@ edx-django-release-util==0.3.1
edx-django-sites-extensions==2.3.1
edx-django-utils==1.0.3
edx-drf-extensions==2.0.1
-edx-enterprise==1.2.0
+edx-enterprise==1.2.1
edx-i18n-tools==0.4.6
edx-lint==1.0.0
edx-milestones==0.1.13
edx-oauth2-provider==1.2.2
edx-opaque-keys[django]==0.4.4
edx-organizations==1.0.0
-edx-proctoring==1.4.0
+edx-proctoring==1.5.0
edx-rest-api-client==1.9.2
edx-search==1.2.1
edx-submissions==2.0.12
@@ -219,13 +219,14 @@ openapi-codec==1.3.2
pa11ycrawler==1.6.2
parsel==1.5.1 # via scrapy
path.py==8.2.1
+pathlib2==2.3.3 # via pytest
pathtools==0.1.2
paver==1.3.4
pbr==5.1.1
pdfminer==20140328
piexif==1.0.2
pillow==5.3.0
-pluggy==0.6.0 # via pytest, tox
+pluggy==0.8.0 # via pytest, tox
polib==1.1.0
psutil==1.2.1
py2neo==3.1.2
@@ -246,7 +247,7 @@ pyjwt==1.5.2
pylint-celery==0.3 # via edx-lint
pylint-django==0.7.2 # via edx-lint
pylint-plugin-utils==0.3 # via pylint-celery, pylint-django
-pylint==1.7.6 # via edx-lint, pylint-celery, pylint-django, pylint-plugin-utils
+pylint==1.7.6 # via edx-lint, pylint-celery, pylint-django
pymongo==2.9.1
pynliner==0.8.0
pyopenssl==18.0.0 # via scrapy
@@ -260,7 +261,7 @@ pytest-django==3.1.2
pytest-forked==0.2 # via pytest-xdist
pytest-randomly==1.2.3
pytest-xdist==1.25.0
-pytest==3.6.3
+pytest==4.0.2
python-dateutil==2.4.0
python-levenshtein==0.12.0
python-memcached==1.48
@@ -270,7 +271,7 @@ python-saml==2.4.0
python-slugify==1.2.6 # via transifex-client
python-subunit==1.3.0
python-swiftclient==3.6.0
-pytz==2016.10
+pytz==2018.7
pyuca==1.1
pyyaml==3.13
queuelib==1.5.0 # via scrapy
@@ -284,6 +285,7 @@ rfc6266-parser==0.0.5.post2
rules==2.0.1
s3transfer==0.1.13
sailthru-client==2.2.3
+scandir==1.9.0 # via pathlib2
scipy==0.14.0
scrapy==1.1.2 # via pa11ycrawler
selenium==3.141.0
@@ -304,13 +306,13 @@ sqlparse==0.2.4
stevedore==1.10.0
sure==1.4.11
sympy==0.7.1
-testfixtures==6.4.0
+testfixtures==6.4.1
testtools==2.3.0 # via fixtures, python-subunit
text-unidecode==1.2 # via faker
tincan==0.0.5
toml==0.10.0 # via tox
tox-battery==0.5.1
-tox==3.6.0
+tox==3.6.1
traceback2==1.4.0 # via testtools, unittest2
transifex-client==0.13.5
twisted==16.6.0 # via pa11ycrawler, scrapy
diff --git a/webpack.common.config.js b/webpack.common.config.js
index 0cc4624926..93c59deb75 100644
--- a/webpack.common.config.js
+++ b/webpack.common.config.js
@@ -26,362 +26,405 @@ var defineFooter = new RegExp('(' + defineCallFooter.source + ')|('
+ defineDirectFooter.source + ')|('
+ defineFancyFooter.source + ')', 'm');
-module.exports = Merge.smart({
- context: __dirname,
-
- entry: {
- // Studio
- Import: './cms/static/js/features/import/factories/import.js',
- CourseOrLibraryListing: './cms/static/js/features_jsx/studio/CourseOrLibraryListing.jsx',
- 'js/factories/login': './cms/static/js/factories/login.js',
- 'js/factories/textbooks': './cms/static/js/factories/textbooks.js',
- 'js/factories/container': './cms/static/js/factories/container.js',
- 'js/factories/context_course': './cms/static/js/factories/context_course.js',
- 'js/factories/library': './cms/static/js/factories/library.js',
- 'js/factories/xblock_validation': './cms/static/js/factories/xblock_validation.js',
- 'js/factories/edit_tabs': './cms/static/js/factories/edit_tabs.js',
- 'js/sock': './cms/static/js/sock.js',
-
- // LMS
- SingleSupportForm: './lms/static/support/jsx/single_support_form.jsx',
- AlertStatusBar: './lms/static/js/accessible_components/StatusBarAlert.jsx',
- LearnerAnalyticsDashboard: './lms/static/js/learner_analytics_dashboard/LearnerAnalyticsDashboard.jsx',
- UpsellExperimentModal: './lms/static/common/js/components/UpsellExperimentModal.jsx',
- PortfolioExperimentUpsellModal: './lms/static/common/js/components/PortfolioExperimentUpsellModal.jsx',
- EntitlementSupportPage: './lms/djangoapps/support/static/support/jsx/entitlements/index.jsx',
- PasswordResetConfirmation: './lms/static/js/student_account/components/PasswordResetConfirmation.jsx',
- StudentAccountDeletion: './lms/static/js/student_account/components/StudentAccountDeletion.jsx',
- StudentAccountDeletionInitializer: './lms/static/js/student_account/StudentAccountDeletionInitializer.js',
- ProblemBrowser: './lms/djangoapps/instructor/static/instructor/ProblemBrowser/index.jsx',
-
- // Learner Dashboard
- EntitlementFactory: './lms/static/js/learner_dashboard/course_entitlement_factory.js',
- EntitlementUnenrollmentFactory: './lms/static/js/learner_dashboard/entitlement_unenrollment_factory.js',
- ProgramDetailsFactory: './lms/static/js/learner_dashboard/program_details_factory.js',
- ProgramListFactory: './lms/static/js/learner_dashboard/program_list_factory.js',
- UnenrollmentFactory: './lms/static/js/learner_dashboard/unenrollment_factory.js',
- CompletionOnViewService: './lms/static/completion/js/CompletionOnViewService.js',
-
- // Features
- CourseGoals: './openedx/features/course_experience/static/course_experience/js/CourseGoals.js',
- CourseHome: './openedx/features/course_experience/static/course_experience/js/CourseHome.js',
- CourseOutline: './openedx/features/course_experience/static/course_experience/js/CourseOutline.js',
- CourseSock: './openedx/features/course_experience/static/course_experience/js/CourseSock.js',
- CourseTalkReviews: './openedx/features/course_experience/static/course_experience/js/CourseTalkReviews.js',
- Currency: './openedx/features/course_experience/static/course_experience/js/currency.js',
- Enrollment: './openedx/features/course_experience/static/course_experience/js/Enrollment.js',
- LatestUpdate: './openedx/features/course_experience/static/course_experience/js/LatestUpdate.js',
- WelcomeMessage: './openedx/features/course_experience/static/course_experience/js/WelcomeMessage.js',
-
- CookiePolicyBanner: './common/static/js/src/CookiePolicyBanner.jsx',
-
- // Common
- ReactRenderer: './common/static/js/src/ReactRenderer.jsx',
- XModuleShim: 'xmodule/js/src/xmodule.js',
-
- VerticalStudentView: './common/lib/xmodule/xmodule/assets/vertical/public/js/vertical_student_view.js',
- commons: 'babel-polyfill'
- },
-
- output: {
- path: path.resolve(__dirname, 'common/static/bundles'),
- libraryTarget: 'window'
- },
-
- plugins: [
- new webpack.NoEmitOnErrorsPlugin(),
- new webpack.NamedModulesPlugin(),
- new BundleTracker({
- path: process.env.STATIC_ROOT_CMS,
- filename: 'webpack-stats.json'
- }),
- new BundleTracker({
- path: process.env.STATIC_ROOT_LMS,
- filename: 'webpack-stats.json'
- }),
- new webpack.ProvidePlugin({
- _: 'underscore',
- $: 'jquery',
- jQuery: 'jquery',
- 'window.jQuery': 'jquery',
- Popper: 'popper.js', // used by bootstrap
- CodeMirror: 'codemirror',
- 'edx.HtmlUtils': 'edx-ui-toolkit/js/utils/html-utils',
- AjaxPrefix: 'ajax_prefix',
- // This is used by some XModules/XBlocks, which don't have
- // any other way to declare that dependency.
- $script: 'scriptjs'
- }),
-
- // Note: Until karma-webpack releases v3, it doesn't play well with
- // the CommonsChunkPlugin. We have a kludge in karma.common.conf.js
- // that dynamically removes this plugin from webpack config when
- // running those tests (the details are in that file). This is a
- // recommended workaround, as this plugin is just an optimization. But
- // because of this, we really don't want to get too fancy with how we
- // invoke this plugin until we can upgrade karma-webpack.
- new webpack.optimize.CommonsChunkPlugin({
- // If the value below changes, update the render_bundle call in
- // common/djangoapps/pipeline_mako/templates/static_content.html
- name: 'commons',
- filename: 'commons.js',
- minChunks: 3
- })
- ],
-
- module: {
- noParse: [
- // See sinon/webpack interaction weirdness:
- // https://github.com/webpack/webpack/issues/304#issuecomment-272150177
- // (I've tried every other suggestion solution on that page, this
- // was the only one that worked.)
- /\/sinon\.js|codemirror-compressed\.js|hls\.js|tinymce\.full\.min\.js/
- ],
- rules: [
- {
- test: files.namespacedRequire.concat(files.textBangUnderscore, filesWithRequireJSBlocks),
- loader: StringReplace.replace(
- ['babel-loader'],
- {
- replacements: [
- {
- pattern: defineHeader,
- replacement: function() { return ''; }
- },
- {
- pattern: defineFooter,
- replacement: function() { return ''; }
- },
- {
- pattern: /(\/\* RequireJS) \*\//g,
- replacement: function(match, p1) { return p1; }
- },
- {
- pattern: /\/\* Webpack/g,
- replacement: function(match) { return match + ' */'; }
- },
- {
- pattern: /text!(.*?\.underscore)/g,
- replacement: function(match, p1) { return p1; }
- },
- {
- pattern: /RequireJS.require/g,
- replacement: function() {
- return 'require';
- }
- }
- ]
- }
- )
- },
- {
- test: /\.(js|jsx)$/,
- exclude: [
- /node_modules/,
- files.namespacedRequire,
- files.textBangUnderscore,
- filesWithRequireJSBlocks
+var workerConfig = function() {
+ try {
+ return {
+ webworker: {
+ target: 'webworker',
+ context: __dirname,
+ entry: require('../workers.json'),
+ output: {
+ filename: '[name].js',
+ path: path.resolve(__dirname, 'common/static/bundles')
+ },
+ plugins: [
+ new BundleTracker({
+ path: process.env.STATIC_ROOT_LMS,
+ filename: 'webpack-worker-stats.json'
+ })
],
- use: 'babel-loader'
- },
- {
- test: /\.(js|jsx)$/,
- include: [
- /paragon/
- ],
- use: 'babel-loader'
- },
- {
- test: path.resolve(__dirname, 'common/static/js/src/ajax_prefix.js'),
- use: [
- 'babel-loader',
- {
- loader: 'exports-loader',
- options: {
- 'this.AjaxPrefix': true
+ module: {
+ rules: [
+ {
+ test: /\.(js|jsx)$/,
+ include: [
+ /node_modules\//
+ ],
+ use: 'babel-loader'
}
- }
- ]
- },
- {
- test: /\.underscore$/,
- use: 'raw-loader'
- },
- {
- // This file is used by both RequireJS and Webpack and depends on window globals
- // This is a dirty hack and shouldn't be replicated for other files.
- test: path.resolve(__dirname, 'cms/static/cms/js/main.js'),
- loader: StringReplace.replace(
- ['babel-loader'],
- {
- replacements: [
- {
- pattern: /\(function\(AjaxPrefix\) {/,
- replacement: function() { return ''; }
- },
- {
- pattern: /], function\(domReady, \$, str, Backbone, gettext, NotificationView\) {/,
- replacement: function() {
- // eslint-disable-next-line
- return '], function(domReady, $, str, Backbone, gettext, NotificationView, AjaxPrefix) {';
- }
- },
- {
- pattern: /'..\/..\/common\/js\/components\/views\/feedback_notification',/,
- replacement: function() {
- return "'../../common/js/components/views/feedback_notification', 'AjaxPrefix',";
- }
- },
- {
- pattern: /}\).call\(this, AjaxPrefix\);/,
- replacement: function() { return ''; }
- },
- {
- pattern: /'..\/..\/common\/js\/components\/views\/feedback_notification',/,
- replacement: function() {
- return "'../../common/js/components/views/feedback_notification', 'AjaxPrefix',";
- }
- }
- ]
- }
- )
- },
- {
- test: /\.(woff2?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
- loader: 'file-loader'
- },
- {
- test: /\.svg$/,
- loader: 'svg-inline-loader'
- },
- {
- test: /xblock\/core/,
- loader: 'exports-loader?window.XBlock!imports-loader?jquery,jquery.immediateDescendents,this=>window'
- },
- {
- test: /xblock\/runtime.v1/,
- loader: 'exports-loader?window.XBlock!imports-loader?XBlock=xblock/core,this=>window'
- },
- {
- test: /descriptors\/js/,
- loader: 'imports-loader?this=>window'
- },
- {
- test: /modules\/js/,
- loader: 'imports-loader?this=>window'
- },
- {
- test: /codemirror/,
- loader: 'exports-loader?window.CodeMirror'
- },
- {
- test: /tinymce/,
- loader: 'imports-loader?this=>window'
- },
- {
- test: /xmodule\/js\/src\/xmodule/,
- loader: 'exports-loader?window.XModule!imports-loader?this=>window'
- },
- {
- test: /mock-ajax/,
- loader: 'imports-loader?exports=>false'
- },
- {
- test: /d3.min/,
- use: [
- 'babel-loader',
- {
- loader: 'exports-loader',
- options: {
- d3: true
- }
- }
- ]
- },
- {
- test: /logger/,
- loader: 'imports-loader?this=>window'
+ ]
+ },
+ resolve: {
+ extensions: ['.js']
+ }
}
- ]
- },
-
- resolve: {
- extensions: ['.js', '.jsx', '.json'],
- alias: {
- AjaxPrefix: 'ajax_prefix',
- accessibility: 'accessibility_tools',
- codemirror: 'codemirror-compressed',
- datepair: 'timepicker/datepair',
- 'edx-ui-toolkit': 'edx-ui-toolkit/src/', // @TODO: some paths in toolkit are not valid relative paths
- ieshim: 'ie_shim',
- jquery: 'jquery/src/jquery', // Use the non-diqst form of jQuery for better debugging + optimization
- 'jquery.flot': 'flot/jquery.flot.min',
- 'jquery.ui': 'jquery-ui.min',
- 'jquery.tinymce': 'jquery.tinymce.min',
- 'jquery.inputnumber': 'html5-input-polyfills/number-polyfill',
- 'jquery.qtip': 'jquery.qtip.min',
- 'jquery.smoothScroll': 'jquery.smooth-scroll.min',
- 'jquery.timepicker': 'timepicker/jquery.timepicker',
- 'backbone.associations': 'backbone-associations/backbone-associations-min',
- squire: 'Squire',
- tinymce: 'tinymce.full.min',
-
- // See sinon/webpack interaction weirdness:
- // https://github.com/webpack/webpack/issues/304#issuecomment-272150177
- // (I've tried every other suggestion solution on that page, this
- // was the only one that worked.)
- sinon: __dirname + '/node_modules/sinon/pkg/sinon.js',
- hls: 'hls.js/dist/hls.js'
- },
- modules: [
- 'cms/djangoapps/pipeline_js/js',
- 'cms/static',
- 'cms/static/cms/js',
- 'cms/templates/js',
- 'lms/static',
- 'common/lib/xmodule',
- 'common/lib/xmodule/xmodule/js/src',
- 'common/lib/xmodule/xmodule/assets/word_cloud/src/js',
- 'common/static',
- 'common/static/coffee/src',
- 'common/static/common/js',
- 'common/static/common/js/vendor/',
- 'common/static/common/js/components',
- 'common/static/js/src',
- 'common/static/js/vendor/',
- 'common/static/js/vendor/jQuery-File-Upload/js/',
- 'common/static/js/vendor/tinymce/js/tinymce',
- 'node_modules',
- 'common/static/xmodule'
- ]
- },
-
- resolveLoader: {
- alias: {
- text: 'raw-loader' // Compatibility with RequireJSText's text! loader, uses raw-loader under the hood
}
- },
-
- externals: {
- $: 'jQuery',
- backbone: 'Backbone',
- canvas: 'canvas',
- coursetalk: 'CourseTalk',
- gettext: 'gettext',
- jquery: 'jQuery',
- logger: 'Logger',
- underscore: '_',
- URI: 'URI',
- XBlockToXModuleShim: 'XBlockToXModuleShim',
- XModule: 'XModule'
- },
-
- watchOptions: {
- poll: true
- },
-
- node: {
- fs: 'empty'
+ } catch (err) {
+ return null;
}
+};
+
+module.exports = Merge.smart({
+ web: {
+ context: __dirname,
+
+ entry: {
+ // Studio
+ Import: './cms/static/js/features/import/factories/import.js',
+ CourseOrLibraryListing: './cms/static/js/features_jsx/studio/CourseOrLibraryListing.jsx',
+ 'js/factories/login': './cms/static/js/factories/login.js',
+ 'js/factories/textbooks': './cms/static/js/factories/textbooks.js',
+ 'js/factories/container': './cms/static/js/factories/container.js',
+ 'js/factories/context_course': './cms/static/js/factories/context_course.js',
+ 'js/factories/library': './cms/static/js/factories/library.js',
+ 'js/factories/xblock_validation': './cms/static/js/factories/xblock_validation.js',
+ 'js/factories/edit_tabs': './cms/static/js/factories/edit_tabs.js',
+ 'js/sock': './cms/static/js/sock.js',
+
+ // LMS
+ SingleSupportForm: './lms/static/support/jsx/single_support_form.jsx',
+ AlertStatusBar: './lms/static/js/accessible_components/StatusBarAlert.jsx',
+ LearnerAnalyticsDashboard: './lms/static/js/learner_analytics_dashboard/LearnerAnalyticsDashboard.jsx',
+ UpsellExperimentModal: './lms/static/common/js/components/UpsellExperimentModal.jsx',
+ PortfolioExperimentUpsellModal: './lms/static/common/js/components/PortfolioExperimentUpsellModal.jsx',
+ EntitlementSupportPage: './lms/djangoapps/support/static/support/jsx/entitlements/index.jsx',
+ PasswordResetConfirmation: './lms/static/js/student_account/components/PasswordResetConfirmation.jsx',
+ StudentAccountDeletion: './lms/static/js/student_account/components/StudentAccountDeletion.jsx',
+ StudentAccountDeletionInitializer: './lms/static/js/student_account/StudentAccountDeletionInitializer.js',
+ ProblemBrowser: './lms/djangoapps/instructor/static/instructor/ProblemBrowser/index.jsx',
+
+ // Learner Dashboard
+ EntitlementFactory: './lms/static/js/learner_dashboard/course_entitlement_factory.js',
+ EntitlementUnenrollmentFactory: './lms/static/js/learner_dashboard/entitlement_unenrollment_factory.js',
+ ProgramDetailsFactory: './lms/static/js/learner_dashboard/program_details_factory.js',
+ ProgramListFactory: './lms/static/js/learner_dashboard/program_list_factory.js',
+ UnenrollmentFactory: './lms/static/js/learner_dashboard/unenrollment_factory.js',
+ CompletionOnViewService: './lms/static/completion/js/CompletionOnViewService.js',
+
+ // Features
+ CourseGoals: './openedx/features/course_experience/static/course_experience/js/CourseGoals.js',
+ CourseHome: './openedx/features/course_experience/static/course_experience/js/CourseHome.js',
+ CourseOutline: './openedx/features/course_experience/static/course_experience/js/CourseOutline.js',
+ CourseSock: './openedx/features/course_experience/static/course_experience/js/CourseSock.js',
+ CourseTalkReviews: './openedx/features/course_experience/static/course_experience/js/CourseTalkReviews.js',
+ Currency: './openedx/features/course_experience/static/course_experience/js/currency.js',
+ Enrollment: './openedx/features/course_experience/static/course_experience/js/Enrollment.js',
+ LatestUpdate: './openedx/features/course_experience/static/course_experience/js/LatestUpdate.js',
+ WelcomeMessage: './openedx/features/course_experience/static/course_experience/js/WelcomeMessage.js',
+
+ CookiePolicyBanner: './common/static/js/src/CookiePolicyBanner.jsx',
+
+ // Common
+ ReactRenderer: './common/static/js/src/ReactRenderer.jsx',
+ XModuleShim: 'xmodule/js/src/xmodule.js',
+ VerticalStudentView: './common/lib/xmodule/xmodule/assets/vertical/public/js/vertical_student_view.js',
+ commons: 'babel-polyfill'
+ },
+
+ output: {
+ path: path.resolve(__dirname, 'common/static/bundles'),
+ libraryTarget: 'window'
+ },
+
+ plugins: [
+ new webpack.NoEmitOnErrorsPlugin(),
+ new webpack.NamedModulesPlugin(),
+ new BundleTracker({
+ path: process.env.STATIC_ROOT_CMS,
+ filename: 'webpack-stats.json'
+ }),
+ new BundleTracker({
+ path: process.env.STATIC_ROOT_LMS,
+ filename: 'webpack-stats.json'
+ }),
+ new webpack.ProvidePlugin({
+ _: 'underscore',
+ $: 'jquery',
+ jQuery: 'jquery',
+ 'window.jQuery': 'jquery',
+ Popper: 'popper.js', // used by bootstrap
+ CodeMirror: 'codemirror',
+ 'edx.HtmlUtils': 'edx-ui-toolkit/js/utils/html-utils',
+ AjaxPrefix: 'ajax_prefix',
+ // This is used by some XModules/XBlocks, which don't have
+ // any other way to declare that dependency.
+ $script: 'scriptjs'
+ }),
+
+ // Note: Until karma-webpack releases v3, it doesn't play well with
+ // the CommonsChunkPlugin. We have a kludge in karma.common.conf.js
+ // that dynamically removes this plugin from webpack config when
+ // running those tests (the details are in that file). This is a
+ // recommended workaround, as this plugin is just an optimization. But
+ // because of this, we really don't want to get too fancy with how we
+ // invoke this plugin until we can upgrade karma-webpack.
+ new webpack.optimize.CommonsChunkPlugin({
+ // If the value below changes, update the render_bundle call in
+ // common/djangoapps/pipeline_mako/templates/static_content.html
+ name: 'commons',
+ filename: 'commons.js',
+ minChunks: 3
+ })
+ ],
+
+ module: {
+ noParse: [
+ // See sinon/webpack interaction weirdness:
+ // https://github.com/webpack/webpack/issues/304#issuecomment-272150177
+ // (I've tried every other suggestion solution on that page, this
+ // was the only one that worked.)
+ /\/sinon\.js|codemirror-compressed\.js|hls\.js|tinymce\.full\.min\.js/
+ ],
+ rules: [
+ {
+ test: files.namespacedRequire.concat(files.textBangUnderscore, filesWithRequireJSBlocks),
+ loader: StringReplace.replace(
+ ['babel-loader'],
+ {
+ replacements: [
+ {
+ pattern: defineHeader,
+ replacement: function() { return ''; }
+ },
+ {
+ pattern: defineFooter,
+ replacement: function() { return ''; }
+ },
+ {
+ pattern: /(\/\* RequireJS) \*\//g,
+ replacement: function(match, p1) { return p1; }
+ },
+ {
+ pattern: /\/\* Webpack/g,
+ replacement: function(match) { return match + ' */'; }
+ },
+ {
+ pattern: /text!(.*?\.underscore)/g,
+ replacement: function(match, p1) { return p1; }
+ },
+ {
+ pattern: /RequireJS.require/g,
+ replacement: function() {
+ return 'require';
+ }
+ }
+ ]
+ }
+ )
+ },
+ {
+ test: /\.(js|jsx)$/,
+ exclude: [
+ /node_modules/,
+ files.namespacedRequire,
+ files.textBangUnderscore,
+ filesWithRequireJSBlocks
+ ],
+ use: 'babel-loader'
+ },
+ {
+ test: /\.(js|jsx)$/,
+ include: [
+ /paragon/
+ ],
+ use: 'babel-loader'
+ },
+ {
+ test: path.resolve(__dirname, 'common/static/js/src/ajax_prefix.js'),
+ use: [
+ 'babel-loader',
+ {
+ loader: 'exports-loader',
+ options: {
+ 'this.AjaxPrefix': true
+ }
+ }
+ ]
+ },
+ {
+ test: /\.underscore$/,
+ use: 'raw-loader'
+ },
+ {
+ // This file is used by both RequireJS and Webpack and depends on window globals
+ // This is a dirty hack and shouldn't be replicated for other files.
+ test: path.resolve(__dirname, 'cms/static/cms/js/main.js'),
+ loader: StringReplace.replace(
+ ['babel-loader'],
+ {
+ replacements: [
+ {
+ pattern: /\(function\(AjaxPrefix\) {/,
+ replacement: function() { return ''; }
+ },
+ {
+ pattern: /], function\(domReady, \$, str, Backbone, gettext, NotificationView\) {/,
+ replacement: function() {
+ // eslint-disable-next-line
+ return '], function(domReady, $, str, Backbone, gettext, NotificationView, AjaxPrefix) {';
+ }
+ },
+ {
+ pattern: /'..\/..\/common\/js\/components\/views\/feedback_notification',/,
+ replacement: function() {
+ return "'../../common/js/components/views/feedback_notification'," +
+ "'AjaxPrefix',";
+ }
+ },
+ {
+ pattern: /}\).call\(this, AjaxPrefix\);/,
+ replacement: function() { return ''; }
+ },
+ {
+ pattern: /'..\/..\/common\/js\/components\/views\/feedback_notification',/,
+ replacement: function() {
+ return "'../../common/js/components/views/feedback_notification'," +
+ "'AjaxPrefix',";
+ }
+ }
+ ]
+ }
+ )
+ },
+ {
+ test: /\.(woff2?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
+ loader: 'file-loader'
+ },
+ {
+ test: /\.svg$/,
+ loader: 'svg-inline-loader'
+ },
+ {
+ test: /xblock\/core/,
+ loader: 'exports-loader?window.XBlock!' +
+ 'imports-loader?jquery,jquery.immediateDescendents,this=>window'
+ },
+ {
+ test: /xblock\/runtime.v1/,
+ loader: 'exports-loader?window.XBlock!imports-loader?XBlock=xblock/core,this=>window'
+ },
+ {
+ test: /descriptors\/js/,
+ loader: 'imports-loader?this=>window'
+ },
+ {
+ test: /modules\/js/,
+ loader: 'imports-loader?this=>window'
+ },
+ {
+ test: /codemirror/,
+ loader: 'exports-loader?window.CodeMirror'
+ },
+ {
+ test: /tinymce/,
+ loader: 'imports-loader?this=>window'
+ },
+ {
+ test: /xmodule\/js\/src\/xmodule/,
+ loader: 'exports-loader?window.XModule!imports-loader?this=>window'
+ },
+ {
+ test: /mock-ajax/,
+ loader: 'imports-loader?exports=>false'
+ },
+ {
+ test: /d3.min/,
+ use: [
+ 'babel-loader',
+ {
+ loader: 'exports-loader',
+ options: {
+ d3: true
+ }
+ }
+ ]
+ },
+ {
+ test: /logger/,
+ loader: 'imports-loader?this=>window'
+ }
+ ]
+ },
+
+ resolve: {
+ extensions: ['.js', '.jsx', '.json'],
+ alias: {
+ AjaxPrefix: 'ajax_prefix',
+ accessibility: 'accessibility_tools',
+ codemirror: 'codemirror-compressed',
+ datepair: 'timepicker/datepair',
+ 'edx-ui-toolkit': 'edx-ui-toolkit/src/', // @TODO: some paths in toolkit are not valid relative paths
+ ieshim: 'ie_shim',
+ jquery: 'jquery/src/jquery', // Use the non-diqst form of jQuery for better debugging + optimization
+ 'jquery.flot': 'flot/jquery.flot.min',
+ 'jquery.ui': 'jquery-ui.min',
+ 'jquery.tinymce': 'jquery.tinymce.min',
+ 'jquery.inputnumber': 'html5-input-polyfills/number-polyfill',
+ 'jquery.qtip': 'jquery.qtip.min',
+ 'jquery.smoothScroll': 'jquery.smooth-scroll.min',
+ 'jquery.timepicker': 'timepicker/jquery.timepicker',
+ 'backbone.associations': 'backbone-associations/backbone-associations-min',
+ squire: 'Squire',
+ tinymce: 'tinymce.full.min',
+
+ // See sinon/webpack interaction weirdness:
+ // https://github.com/webpack/webpack/issues/304#issuecomment-272150177
+ // (I've tried every other suggestion solution on that page, this
+ // was the only one that worked.)
+ sinon: __dirname + '/node_modules/sinon/pkg/sinon.js',
+ hls: 'hls.js/dist/hls.js'
+ },
+ modules: [
+ 'cms/djangoapps/pipeline_js/js',
+ 'cms/static',
+ 'cms/static/cms/js',
+ 'cms/templates/js',
+ 'lms/static',
+ 'common/lib/xmodule',
+ 'common/lib/xmodule/xmodule/js/src',
+ 'common/lib/xmodule/xmodule/assets/word_cloud/src/js',
+ 'common/static',
+ 'common/static/coffee/src',
+ 'common/static/common/js',
+ 'common/static/common/js/vendor/',
+ 'common/static/common/js/components',
+ 'common/static/js/src',
+ 'common/static/js/vendor/',
+ 'common/static/js/vendor/jQuery-File-Upload/js/',
+ 'common/static/js/vendor/tinymce/js/tinymce',
+ 'node_modules',
+ 'common/static/xmodule'
+ ]
+ },
+
+ resolveLoader: {
+ alias: {
+ text: 'raw-loader' // Compatibility with RequireJSText's text! loader, uses raw-loader under the hood
+ }
+ },
+
+ externals: {
+ $: 'jQuery',
+ backbone: 'Backbone',
+ canvas: 'canvas',
+ coursetalk: 'CourseTalk',
+ gettext: 'gettext',
+ jquery: 'jQuery',
+ logger: 'Logger',
+ underscore: '_',
+ URI: 'URI',
+ XBlockToXModuleShim: 'XBlockToXModuleShim',
+ XModule: 'XModule'
+ },
+
+ watchOptions: {
+ poll: true
+ },
+
+ node: {
+ fs: 'empty'
+ }
+
+ }
+}, {web: xmoduleJS}, workerConfig());
-}, xmoduleJS);
diff --git a/webpack.dev.config.js b/webpack.dev.config.js
index ae22b2bc00..913d773cac 100644
--- a/webpack.dev.config.js
+++ b/webpack.dev.config.js
@@ -5,56 +5,59 @@
var Merge = require('webpack-merge');
var path = require('path');
var webpack = require('webpack');
+var _ = require('underscore');
var commonConfig = require('./webpack.common.config.js');
-module.exports = Merge.smart(commonConfig, {
- output: {
- filename: '[name].js'
- },
- devtool: 'source-map',
- plugins: [
- new webpack.LoaderOptionsPlugin({
- debug: true
- }),
- new webpack.DefinePlugin({
- 'process.env.NODE_ENV': JSON.stringify('development')
- })
- ],
- module: {
- rules: [
- {
- test: /(.scss|.css)$/,
- include: [
- /paragon/,
- /font-awesome/
- ],
- use: [
- 'style-loader',
- {
- loader: 'css-loader',
- options: {
- sourceMap: true,
- modules: true,
- localIdentName: '[name]__[local]'
+module.exports = _.values(Merge.smart(commonConfig, {
+ web: {
+ output: {
+ filename: '[name].js'
+ },
+ devtool: 'source-map',
+ plugins: [
+ new webpack.LoaderOptionsPlugin({
+ debug: true
+ }),
+ new webpack.DefinePlugin({
+ 'process.env.NODE_ENV': JSON.stringify('development')
+ })
+ ],
+ module: {
+ rules: [
+ {
+ test: /(.scss|.css)$/,
+ include: [
+ /paragon/,
+ /font-awesome/
+ ],
+ use: [
+ 'style-loader',
+ {
+ loader: 'css-loader',
+ options: {
+ sourceMap: true,
+ modules: true,
+ localIdentName: '[name]__[local]'
+ }
+ },
+ {
+ loader: 'sass-loader',
+ options: {
+ data: '$base-rem-size: 0.625; @import "paragon-reset";',
+ includePaths: [
+ path.join(__dirname, './node_modules/@edx/paragon/src/utils'),
+ path.join(__dirname, './node_modules/')
+ ],
+ sourceMap: true
+ }
}
- },
- {
- loader: 'sass-loader',
- options: {
- data: '$base-rem-size: 0.625; @import "paragon-reset";',
- includePaths: [
- path.join(__dirname, './node_modules/@edx/paragon/src/utils'),
- path.join(__dirname, './node_modules/')
- ],
- sourceMap: true
- }
- }
- ]
- }
- ]
- },
- watchOptions: {
- ignored: [/node_modules/, /\.git/]
+ ]
+ }
+ ]
+ },
+ watchOptions: {
+ ignored: [/node_modules/, /\.git/]
+ }
}
-});
+}));
diff --git a/webpack.prod.config.js b/webpack.prod.config.js
index 97570626b2..360ab56d4d 100644
--- a/webpack.prod.config.js
+++ b/webpack.prod.config.js
@@ -4,10 +4,13 @@
var Merge = require('webpack-merge');
var webpack = require('webpack');
+var BundleTracker = require('webpack-bundle-tracker');
+var _ = require('underscore');
var commonConfig = require('./webpack.common.config.js');
var optimizedConfig = Merge.smart(commonConfig, {
+ web: {
output: {
filename: '[name].[chunkhash].js'
},
@@ -28,7 +31,7 @@ var optimizedConfig = Merge.smart(commonConfig, {
minChunks: 3
})
]
-});
+}});
// requireCompatConfig only exists so that you can use RequireJS to require a
// Webpack bundle (but try not to do that if you can help it). RequireJS knows
@@ -44,6 +47,7 @@ var optimizedConfig = Merge.smart(commonConfig, {
// Step 1: Alter the bundle output names to omit the chunkhash.
var requireCompatConfig = Merge.smart(optimizedConfig, {
+ web: {
output: {
filename: '[name].js'
},
@@ -56,14 +60,12 @@ var requireCompatConfig = Merge.smart(optimizedConfig, {
minChunks: 3
})
]
-});
+}});
// Step 2: Remove the plugin entries that generate the webpack-stats.json files
// that Django needs to look up resources. We never want to accidentally
// overwrite those because it means that we'll be serving assets with shorter
// cache times. RequireJS never looks at the webpack-stats.json file.
-requireCompatConfig.plugins = requireCompatConfig.plugins.filter(
- function(plugin) { return !plugin.options || (plugin.options && plugin.options.filename !== 'webpack-stats.json'); }
-);
+requireCompatConfig.web.plugins = requireCompatConfig.web.plugins.filter((plugin) => !(plugin instanceof BundleTracker));
-module.exports = [optimizedConfig, requireCompatConfig];
+module.exports = [..._.values(optimizedConfig), ..._.values(requireCompatConfig)];