Merge pull request #19366 from edx/dahlia/proctoring-master

Upgrade edx-proctoring to version 1.5
This commit is contained in:
Dave St.Germain
2019-01-02 11:37:55 -05:00
committed by GitHub
42 changed files with 1139 additions and 677 deletions

View File

@@ -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
)

View File

@@ -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:

View File

@@ -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):
"""

View File

@@ -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,

View File

@@ -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,

View File

@@ -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)

View File

@@ -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', {})

View File

@@ -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 #####################

View File

@@ -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', {})

View File

@@ -23,6 +23,7 @@ DATABASES = {
}
######################### PIPELINE ####################################
# Use RequireJS optimized storage

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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: {

View File

@@ -266,7 +266,6 @@ class AdvancedSettingsPage(CoursePage):
'enable_subsection_gating',
'learning_info',
'instructor_info',
'create_zendesk_tickets',
'ccx_connector',
'enable_ccx',
]

View File

@@ -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)

View File

@@ -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)

View File

@@ -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', ''))

View File

@@ -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.

View File

@@ -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', ''))

View File

@@ -35,6 +35,11 @@ XQUEUE_INTERFACE = {
"basic_auth": ('anant', 'agarwal'),
}
PROCTORING_BACKENDS = {
'DEFAULT': 'mock',
'mock': {},
'mock_proctoring_without_rules': {},
}
######################### PIPELINE ####################################

View File

@@ -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')
}
]);
}

View File

@@ -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
}
);

View File

@@ -14,5 +14,9 @@ import pytz
<h3 class="hd hd-3">${_('Student Special Exam Attempts')}</h3>
<div class="student-proctored-exam-container" data-course-id="${ section_data['course_id'] }"></div>
</div>
<div class="wrap">
<h3 class="hd hd-3">${_('Review Dashboard')}</h3>
<div class="student-review-dashboard-container" data-course-id="${ section_data['course_id'] }"></div>
</div>
</div>
</div>

View File

@@ -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(

View File

@@ -51,6 +51,12 @@ DATABASES = {
}
}
PROCTORING_BACKENDS = {
'DEFAULT': 'mock',
'mock': {},
'mock_proctoring_without_rules': {},
}
FEATURES = {}
INSTALLED_APPS = (

173
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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/]
}
}
});
}));

View File

@@ -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)];