From 5c1f838dcbe72449f1df512df9057e3b7a3b51f6 Mon Sep 17 00:00:00 2001
From: Jesse Zoldak
Date: Thu, 22 Dec 2016 16:20:19 -0500
Subject: [PATCH 01/12] Give the bok choy server more time to start up
---
pavelib/utils/test/bokchoy_utils.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pavelib/utils/test/bokchoy_utils.py b/pavelib/utils/test/bokchoy_utils.py
index a760395550..ea9e7a49d2 100644
--- a/pavelib/utils/test/bokchoy_utils.py
+++ b/pavelib/utils/test/bokchoy_utils.py
@@ -83,7 +83,7 @@ def wait_for_server(server, port):
attempts = 0
server_ok = False
- while attempts < 20:
+ while attempts < 30:
try:
connection = httplib.HTTPConnection(server, port, timeout=10)
connection.request('GET', '/')
From 0fd91f07398717d5bb252d4284709ae6eec71a6e Mon Sep 17 00:00:00 2001
From: Anthony Mangano
Date: Wed, 21 Dec 2016 15:37:34 -0500
Subject: [PATCH 02/12] add course_name to enrollment api reponse
ECOM-5936
---
common/djangoapps/enrollment/api.py | 16 +++++++++++-----
common/djangoapps/enrollment/serializers.py | 1 +
common/djangoapps/enrollment/tests/test_data.py | 1 +
common/djangoapps/enrollment/tests/test_views.py | 13 ++++++++++---
common/djangoapps/enrollment/views.py | 4 ++++
.../source/enrollment/enrollment.rst | 4 ++++
6 files changed, 31 insertions(+), 8 deletions(-)
diff --git a/common/djangoapps/enrollment/api.py b/common/djangoapps/enrollment/api.py
index 46ea7562e8..d97e31111f 100644
--- a/common/djangoapps/enrollment/api.py
+++ b/common/djangoapps/enrollment/api.py
@@ -37,8 +37,9 @@ def get_enrollments(user_id):
"mode": "honor",
"is_active": True,
"user": "Bob",
- "course": {
+ "course_details": {
"course_id": "edX/DemoX/2014T2",
+ "course_name": "edX Demonstration Course",
"enrollment_end": "2014-12-20T20:18:00Z",
"enrollment_start": "2014-10-15T20:18:00Z",
"course_start": "2015-02-03T00:00:00Z",
@@ -64,8 +65,9 @@ def get_enrollments(user_id):
"mode": "verified",
"is_active": True,
"user": "Bob",
- "course": {
+ "course_details": {
"course_id": "edX/edX-Insider/2014T2",
+ "course_name": "edX Insider Course",
"enrollment_end": "2014-12-20T20:18:00Z",
"enrollment_start": "2014-10-15T20:18:00Z",
"course_start": "2015-02-03T00:00:00Z",
@@ -111,8 +113,9 @@ def get_enrollment(user_id, course_id):
"mode": "honor",
"is_active": True,
"user": "Bob",
- "course": {
+ "course_details": {
"course_id": "edX/DemoX/2014T2",
+ "course_name": "edX Demonstration Course",
"enrollment_end": "2014-12-20T20:18:00Z",
"enrollment_start": "2014-10-15T20:18:00Z",
"course_start": "2015-02-03T00:00:00Z",
@@ -163,8 +166,9 @@ def add_enrollment(user_id, course_id, mode=None, is_active=True):
"mode": "audit",
"is_active": True,
"user": "Bob",
- "course": {
+ "course_details": {
"course_id": "edX/DemoX/2014T2",
+ "course_name": "edX Demonstration Course",
"enrollment_end": "2014-12-20T20:18:00Z",
"enrollment_start": "2014-10-15T20:18:00Z",
"course_start": "2015-02-03T00:00:00Z",
@@ -217,8 +221,9 @@ def update_enrollment(user_id, course_id, mode=None, is_active=None, enrollment_
"mode": "honor",
"is_active": True,
"user": "Bob",
- "course": {
+ "course_details": {
"course_id": "edX/DemoX/2014T2",
+ "course_name": "edX Demonstration Course",
"enrollment_end": "2014-12-20T20:18:00Z",
"enrollment_start": "2014-10-15T20:18:00Z",
"course_start": "2015-02-03T00:00:00Z",
@@ -282,6 +287,7 @@ def get_course_enrollment_details(course_id, include_expired=False):
>>> get_course_enrollment_details("edX/DemoX/2014T2")
{
"course_id": "edX/DemoX/2014T2",
+ "course_name": "edX Demonstration Course",
"enrollment_end": "2014-12-20T20:18:00Z",
"enrollment_start": "2014-10-15T20:18:00Z",
"course_start": "2015-02-03T00:00:00Z",
diff --git a/common/djangoapps/enrollment/serializers.py b/common/djangoapps/enrollment/serializers.py
index 1e894f210d..8f4d5357c9 100644
--- a/common/djangoapps/enrollment/serializers.py
+++ b/common/djangoapps/enrollment/serializers.py
@@ -36,6 +36,7 @@ class CourseSerializer(serializers.Serializer): # pylint: disable=abstract-meth
"""
course_id = serializers.CharField(source="id")
+ course_name = serializers.CharField(source="display_name_with_default")
enrollment_start = serializers.DateTimeField(format=None)
enrollment_end = serializers.DateTimeField(format=None)
course_start = serializers.DateTimeField(source="start", format=None)
diff --git a/common/djangoapps/enrollment/tests/test_data.py b/common/djangoapps/enrollment/tests/test_data.py
index a8143488f4..36185cbb6d 100644
--- a/common/djangoapps/enrollment/tests/test_data.py
+++ b/common/djangoapps/enrollment/tests/test_data.py
@@ -71,6 +71,7 @@ class EnrollmentDataTest(ModuleStoreTestCase):
# Confirm the returned enrollment and the data match up.
self.assertEqual(course_mode, enrollment['mode'])
self.assertEqual(is_active, enrollment['is_active'])
+ self.assertEqual(self.course.display_name_with_default, enrollment['course_details']['course_name'])
def test_unenroll(self):
# Enroll the user in the course
diff --git a/common/djangoapps/enrollment/tests/test_views.py b/common/djangoapps/enrollment/tests/test_views.py
index 8639480c1a..88bc30c398 100644
--- a/common/djangoapps/enrollment/tests/test_views.py
+++ b/common/djangoapps/enrollment/tests/test_views.py
@@ -191,8 +191,13 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase):
)
# Create an enrollment
- self.assert_enrollment_status()
+ resp = self.assert_enrollment_status()
+ # Verify that the response contains the correct course_name
+ data = json.loads(resp.content)
+ self.assertEqual(self.course.display_name_with_default, data['course_details']['course_name'])
+
+ # Verify that the enrollment was created correctly
self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id))
course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
self.assertTrue(is_active)
@@ -212,6 +217,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase):
self.assertEqual(resp.status_code, status.HTTP_200_OK)
data = json.loads(resp.content)
self.assertEqual(unicode(self.course.id), data['course_details']['course_id'])
+ self.assertEqual(self.course.display_name_with_default, data['course_details']['course_name'])
self.assertEqual(CourseMode.DEFAULT_MODE_SLUG, data['mode'])
self.assertTrue(data['is_active'])
@@ -329,8 +335,8 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = json.loads(response.content)
self.assertItemsEqual(
- [enrollment['course_details']['course_id'] for enrollment in data],
- [unicode(course.id) for course in courses]
+ [(datum['course_details']['course_id'], datum['course_details']['course_name']) for datum in data],
+ [(unicode(course.id), course.display_name_with_default) for course in courses]
)
def test_enrollment_list_permissions(self):
@@ -411,6 +417,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase):
data = json.loads(resp.content)
self.assertEqual(unicode(self.course.id), data['course_id'])
+ self.assertEqual(self.course.display_name_with_default, data['course_name'])
mode = data['course_modes'][0]
self.assertEqual(mode['slug'], CourseMode.HONOR)
self.assertEqual(mode['sku'], '123')
diff --git a/common/djangoapps/enrollment/views.py b/common/djangoapps/enrollment/views.py
index 776a5b8bbe..9f61346e4d 100644
--- a/common/djangoapps/enrollment/views.py
+++ b/common/djangoapps/enrollment/views.py
@@ -99,6 +99,7 @@ class EnrollmentView(APIView, ApiKeyPermissionMixIn):
* course_end: The date and time when the course closes. If
null, the course never ends.
* course_id: The unique identifier for the course.
+ * course_name: The name of the course.
* course_modes: An array of data about the enrollment modes
supported for the course. If the request uses the parameter
include_expired=1, the array also includes expired
@@ -216,6 +217,7 @@ class EnrollmentCourseDetailView(APIView):
* course_end: The date and time when the course closes. If
null, the course never ends.
* course_id: The unique identifier for the course.
+ * course_name: The name of the course.
* course_modes: An array of data about the enrollment modes
supported for the course. If the request uses the parameter
include_expired=1, the array also includes expired
@@ -400,6 +402,8 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
* course_id: The unique identifier for the course.
+ * course_name: The name of the course.
+
* course_modes: An array of data about the enrollment modes
supported for the course. If the request uses the parameter
include_expired=1, the array also includes expired
diff --git a/docs/en_us/platform_api/source/enrollment/enrollment.rst b/docs/en_us/platform_api/source/enrollment/enrollment.rst
index 271f1979cd..f4dc8bfba3 100644
--- a/docs/en_us/platform_api/source/enrollment/enrollment.rst
+++ b/docs/en_us/platform_api/source/enrollment/enrollment.rst
@@ -33,6 +33,7 @@ Get the User's Enrollment Status in a Course
"is_active": true,
"course_details": {
"course_id": "edX/DemoX/Demo_Course",
+ "course_name": "edX Demonstration Course",
"enrollment_end": null,
"course_modes": [
{
@@ -70,6 +71,7 @@ Get the User's Enrollment Information for a Course
{
"course_id": "edX/DemoX/Demo_Course",
+ "course_name": "edX Demonstration Course",
"enrollment_end": null,
"course_modes": [
{
@@ -112,6 +114,7 @@ View a User's Enrollments or Enroll a User in a Course
"is_active": true,
"course_details": {
"course_id": "edX/DemoX/Demo_Course",
+ "course_name": "edX Demonstration Course",
"enrollment_end": null,
"course_modes": [
{
@@ -135,6 +138,7 @@ View a User's Enrollments or Enroll a User in a Course
"is_active": true,
"course_details": {
"course_id": "ArbisoftX/BulkyEmail101/2014-15",
+ "course_name": "Course Name Here",
"enrollment_end": null,
"course_modes": [
{
From bf8b34558694f60b997437271fe1869a49802f1e Mon Sep 17 00:00:00 2001
From: cahrens
Date: Thu, 5 Jan 2017 09:49:22 -0500
Subject: [PATCH 03/12] Remove flaky decorator.
Increased number of retries, as it is expected that sometimes
a seed will be reused. TNL-6041
---
common/lib/xmodule/xmodule/tests/test_capa_module.py | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py
index abeb12b143..e7653ef81c 100644
--- a/common/lib/xmodule/xmodule/tests/test_capa_module.py
+++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py
@@ -13,7 +13,6 @@ import textwrap
import unittest
import ddt
-import flaky
from lxml import etree
from mock import Mock, patch, DEFAULT
import webob
@@ -1412,7 +1411,6 @@ class CapaModuleTest(unittest.TestCase):
RANDOMIZATION.ALWAYS,
RANDOMIZATION.ONRESET
)
- @flaky.flaky # TNL-6041
def test_random_seed_with_reset(self, rerandomize):
"""
Run the test for each possible rerandomize value
@@ -1470,13 +1468,13 @@ class CapaModuleTest(unittest.TestCase):
# to another valid seed
else:
- # Since there's a small chance we might get the
- # same seed again, give it 5 chances
+ # Since there's a small chance (expected) we might get the
+ # same seed again, give it 10 chances
# to generate a different seed
- success = _retry_and_check(5, lambda: _reset_and_get_seed(module) != seed)
+ success = _retry_and_check(10, lambda: _reset_and_get_seed(module) != seed)
self.assertIsNotNone(module.seed)
- msg = 'Could not get a new seed from reset after 5 tries'
+ msg = 'Could not get a new seed from reset after 10 tries'
self.assertTrue(success, msg)
@ddt.data(
From aa8d22267851f0d5ab25cf86d63074143ff98266 Mon Sep 17 00:00:00 2001
From: cahrens
Date: Tue, 6 Dec 2016 15:38:34 -0500
Subject: [PATCH 04/12] New example jsinput problem.
TNL-5893
---
.../templates/problem/jsinput_response.yaml | 55 ++++++------
.../js/capa/jsinput/jsinput_example.css | 9 ++
.../js/capa/jsinput/jsinput_example.html | 15 ++++
.../static/js/capa/jsinput/jsinput_example.js | 86 +++++++++++++++++++
4 files changed, 140 insertions(+), 25 deletions(-)
create mode 100644 common/static/js/capa/jsinput/jsinput_example.css
create mode 100644 common/static/js/capa/jsinput/jsinput_example.html
create mode 100644 common/static/js/capa/jsinput/jsinput_example.js
diff --git a/common/lib/xmodule/xmodule/templates/problem/jsinput_response.yaml b/common/lib/xmodule/xmodule/templates/problem/jsinput_response.yaml
index 5a37b6ede3..20e7874695 100644
--- a/common/lib/xmodule/xmodule/templates/problem/jsinput_response.yaml
+++ b/common/lib/xmodule/xmodule/templates/problem/jsinput_response.yaml
@@ -1,6 +1,6 @@
---
metadata:
- display_name: Custom Javascript Display and Grading
+ display_name: Custom JavaScript Display and Grading
markdown: !!null
showanswer: never
data: |
@@ -8,8 +8,8 @@ data: |
In these problems (also called custom JavaScript problems or JS Input
problems), you add a problem or tool that uses JavaScript in Studio.
- Studio embeds the problem in an IFrame so that your students can
- interact with it in the LMS. You can grade your students' work using
+ Studio embeds the problem in an IFrame so that your learners can
+ interact with it in the LMS. You can grade your learners' work using
JavaScript and some basic Python, and the grading is integrated into the
edX grading system.
@@ -31,42 +31,47 @@ data: |
When you add the problem, be sure to select Settings
to specify a Display Name and other values that apply.
+ Also, be sure to specify a title attribute on the jsinput tag;
+ this title is used for the title attribute on the generated IFrame. Generally,
+ the title attribute on the IFrame should match the title tag of the HTML hosted
+ within the IFrame, which is specified by the html_file attribute.
You can use the following example problem as a model.
-
+
-
In the following image, click the objects until the cone is yellow and the cube is blue.
- This is paragraph text displayed before the IFrame.
+
diff --git a/common/static/js/capa/jsinput/jsinput_example.css b/common/static/js/capa/jsinput/jsinput_example.css
new file mode 100644
index 0000000000..8f1144ac40
--- /dev/null
+++ b/common/static/js/capa/jsinput/jsinput_example.css
@@ -0,0 +1,9 @@
+.directions {
+ font-size: large
+}
+
+.feedback {
+ font-size: medium;
+ border: 2px solid cornflowerblue;
+ padding: 5px;
+}
diff --git a/common/static/js/capa/jsinput/jsinput_example.html b/common/static/js/capa/jsinput/jsinput_example.html
new file mode 100644
index 0000000000..caa034fff9
--- /dev/null
+++ b/common/static/js/capa/jsinput/jsinput_example.html
@@ -0,0 +1,15 @@
+
+
+
+ Dropdown with Dynamic Text
+
+
+
+
+
+
+
+
+
diff --git a/common/static/js/capa/jsinput/jsinput_example.js b/common/static/js/capa/jsinput/jsinput_example.js
new file mode 100644
index 0000000000..6aa125fd69
--- /dev/null
+++ b/common/static/js/capa/jsinput/jsinput_example.js
@@ -0,0 +1,86 @@
+/* globals Channel */
+
+(function() {
+ 'use strict';
+
+ // state will be populated via initial_state via the `setState` method. Defining dummy values here
+ // to make the expected structure clear.
+ var state = {
+ availableChoices: [],
+ selectedChoice: ''
+ },
+ channel,
+ select = document.getElementsByClassName('choices')[0],
+ feedback = document.getElementsByClassName('feedback')[0];
+
+ function populateSelect() {
+ // Populate the select from `state.availableChoices`.
+ var i, option;
+
+ // Clear out any pre-existing options.
+ while (select.firstChild) {
+ select.removeChild(select.firstChild);
+ }
+
+ // Populate the select with the available choices.
+ for (i = 0; i < state.availableChoices.length; i++) {
+ option = document.createElement('option');
+ option.value = i;
+ option.innerHTML = state.availableChoices[i];
+ if (state.availableChoices[i] === state.selectedChoice) {
+ option.selected = true;
+ }
+ select.appendChild(option);
+ }
+ feedback.innerText = "The currently selected answer is '" + state.selectedChoice + "'.";
+ }
+
+ function getGrade() {
+ // The following return value may or may not be used to grade server-side.
+ // If getState and setState are used, then the Python grader also gets access
+ // to the return value of getState and can choose it instead to grade.
+ return JSON.stringify(state.selectedChoice);
+ }
+
+ function getState() {
+ // Returns the current state (which can be used for grading).
+ return JSON.stringify(state);
+ }
+
+ // This function will be called with 1 argument when JSChannel is not used,
+ // 2 otherwise. In the latter case, the first argument is a transaction
+ // object that will not be used here
+ // (see http://mozilla.github.io/jschannel/docs/)
+ function setState() {
+ var stateString = arguments.length === 1 ? arguments[0] : arguments[1];
+ state = JSON.parse(stateString);
+ populateSelect();
+ }
+
+ // Establish a channel only if this application is embedded in an iframe.
+ // This will let the parent window communicate with this application using
+ // RPC and bypass SOP restrictions.
+ if (window.parent !== window) {
+ channel = Channel.build({
+ window: window.parent,
+ origin: '*',
+ scope: 'JSInput'
+ });
+
+ channel.bind('getGrade', getGrade);
+ channel.bind('getState', getState);
+ channel.bind('setState', setState);
+ }
+
+ select.addEventListener('change', function() {
+ state.selectedChoice = select.options[select.selectedIndex].text;
+ feedback.innerText = "You have selected '" + state.selectedChoice +
+ "'. Click Submit to grade your answer.";
+ });
+
+ return {
+ getState: getState,
+ setState: setState,
+ getGrade: getGrade
+ };
+}());
From 77be91baf9558d09baf167ddeda0c2a961934a3e Mon Sep 17 00:00:00 2001
From: cahrens
Date: Mon, 12 Dec 2016 11:05:18 -0500
Subject: [PATCH 05/12] Related to javascriptresponse, which was already
deleted.
I missed these files the first time around.
---
.../capa/capa/javascript_problem_generator.js | 28 -------------------
.../capa/capa/javascript_problem_grader.js | 26 -----------------
2 files changed, 54 deletions(-)
delete mode 100644 common/lib/capa/capa/javascript_problem_generator.js
delete mode 100644 common/lib/capa/capa/javascript_problem_grader.js
diff --git a/common/lib/capa/capa/javascript_problem_generator.js b/common/lib/capa/capa/javascript_problem_generator.js
deleted file mode 100644
index 473a5d8e8c..0000000000
--- a/common/lib/capa/capa/javascript_problem_generator.js
+++ /dev/null
@@ -1,28 +0,0 @@
-require('coffee-script');
-var importAll = function(modulePath) {
- module = require(modulePath);
- for (key in module) {
- global[key] = module[key];
- }
-};
-
-importAll('mersenne-twister-min');
-importAll('xproblem');
-
-generatorModulePath = process.argv[2];
-dependencies = JSON.parse(process.argv[3]);
-seed = JSON.parse(process.argv[4]);
-params = JSON.parse(process.argv[5]);
-
-if (seed == null) {
- seed = 4;
-}
-
-for (var i = 0; i < dependencies.length; i++) {
- importAll(dependencies[i]);
-}
-
-generatorModule = require(generatorModulePath);
-generatorClass = generatorModule.generatorClass;
-generator = new generatorClass(seed, params);
-console.log(JSON.stringify(generator.generate()));
diff --git a/common/lib/capa/capa/javascript_problem_grader.js b/common/lib/capa/capa/javascript_problem_grader.js
deleted file mode 100644
index 02cb7da7f2..0000000000
--- a/common/lib/capa/capa/javascript_problem_grader.js
+++ /dev/null
@@ -1,26 +0,0 @@
-require('coffee-script');
-var importAll = function(modulePath) {
- module = require(modulePath);
- for (key in module) {
- global[key] = module[key];
- }
-};
-
-importAll('xproblem');
-
-graderModulePath = process.argv[2];
-dependencies = JSON.parse(process.argv[3]);
-submission = JSON.parse(process.argv[4]);
-problemState = JSON.parse(process.argv[5]);
-params = JSON.parse(process.argv[6]);
-
-for (var i = 0; i < dependencies.length; i++) {
- importAll(dependencies[i]);
-}
-
-graderModule = require(graderModulePath);
-graderClass = graderModule.graderClass;
-grader = new graderClass(submission, problemState, params);
-console.log(JSON.stringify(grader.grade()));
-console.log(JSON.stringify(grader.evaluation));
-console.log(JSON.stringify(grader.solution));
From ed614448e487f99d689609d6e11e404952c038be Mon Sep 17 00:00:00 2001
From: cahrens
Date: Tue, 20 Dec 2016 12:33:28 -0500
Subject: [PATCH 06/12] Cleanup of status.
Also remove unused empty jsinput_css.css.
---
.../capa/templates/chemicalequationinput.html | 2 +-
.../capa/templates/drag_and_drop_input.html | 2 +-
.../capa/capa/templates/editageneinput.html | 2 +-
.../capa/capa/templates/editamolecule.html | 2 +-
common/lib/capa/capa/templates/jsinput.html | 6 ++--
.../lib/xmodule/xmodule/css/capa/display.scss | 33 -------------------
common/static/css/capa/jsinput_css.css | 0
7 files changed, 7 insertions(+), 40 deletions(-)
delete mode 100644 common/static/css/capa/jsinput_css.css
diff --git a/common/lib/capa/capa/templates/chemicalequationinput.html b/common/lib/capa/capa/templates/chemicalequationinput.html
index b61172951a..3f18a699e6 100644
--- a/common/lib/capa/capa/templates/chemicalequationinput.html
+++ b/common/lib/capa/capa/templates/chemicalequationinput.html
@@ -11,7 +11,7 @@
% endif
/>
-