Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FC-73 create pr for edx platform repository (Creating waffle flag for integration towards created_submission in the edx-submission repo) #36235

Closed
2 changes: 1 addition & 1 deletion cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,7 @@
CSRF_TRUSTED_ORIGINS_WITH_SCHEME = []

#################### CAPA External Code Evaluation #############################
XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds
XQUEUE_WAITTIME_BETWEEN_REQUESTS = 120 # seconds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this changing?

XQUEUE_INTERFACE = {
'url': 'http://localhost:18040',
'basic_auth': ['edx', 'edx'],
Expand Down
3 changes: 2 additions & 1 deletion lms/djangoapps/courseware/tests/test_submitting_problems.py
Original file line number Diff line number Diff line change
Expand Up @@ -782,7 +782,7 @@ def problem_setup(self, name, files):

# re-fetch the course from the database so the object is up to date
self.refresh_course()

@patch.object(XQueueInterface, '_http_post')
def test_three_files(self, mock_xqueue_post):
# Open the test files, and arrange to close them later.
Expand All @@ -809,6 +809,7 @@ def test_three_files(self, mock_xqueue_post):
assert args[0].endswith('/submit/')
self.assertEqual(list(kwargs['files'].keys()), filenames.split())



class TestPythonGradedResponse(TestSubmittingProblems):
"""
Expand Down
4 changes: 2 additions & 2 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1094,8 +1094,8 @@
'url': 'http://localhost:18040',
'basic_auth': ['edx', 'edx'],
'django_auth': {
'username': 'lms',
'password': 'password'
'username': 'gabriel_admin',
'password': 'carvajal1407'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this changing?

}
}

Expand Down
7 changes: 7 additions & 0 deletions lms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.user_authn.views.login import redirect_to_lms_login
from openedx.features.enterprise_support.api import enterprise_enabled
from submissions import urls as submissions_urls

RESET_COURSE_DEADLINES_NAME = 'reset_course_deadlines'
RENDER_XBLOCK_NAME = 'render_xblock'
Expand Down Expand Up @@ -1047,3 +1048,9 @@
urlpatterns += [
path('api/notifications/', include('openedx.core.djangoapps.notifications.urls')),
]


urlpatterns += [
path('xqueue/', include((submissions_urls, 'submissions'), namespace='submissions')),
]

2 changes: 1 addition & 1 deletion xmodule/capa/capa_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1255,4 +1255,4 @@ def response_a11y_data(self, response, inputfields, responsetype_id, problem_dat
problem_data[inputfields[0].get('id')] = {
'label': HTML(label.strip()) if label else '',
'descriptions': descriptions
}
}
2 changes: 1 addition & 1 deletion xmodule/capa/inputtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1809,4 +1809,4 @@ def extract_choices(element, i18n):

# Add the tuple for the current choice to the list of choices
choices.append((choice.get("name"), components))
return choices
return choices
2 changes: 1 addition & 1 deletion xmodule/capa/responsetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3917,4 +3917,4 @@ def _check_student_inputs(self, numtolerance_inputs):
AnnotationResponse,
ChoiceTextResponse,
]
# pylint: enable=invalid-all-object
# pylint: enable=invalid-all-object
2 changes: 1 addition & 1 deletion xmodule/capa/tests/test_inputtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1678,4 +1678,4 @@ def test_translated_names(self):
statobj = inputtypes.Status('test', func)
assert statobj.display_name == 'test'
assert str(statobj) == 'test'
assert statobj.classname == 'test'
assert statobj.classname == 'test'
2 changes: 1 addition & 1 deletion xmodule/capa/tests/test_responsetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2831,4 +2831,4 @@ def test_checkbox_grades(self):
submission,
correctness,
msg="{0} should be {1}".format(name, correctness)
)
)
58 changes: 57 additions & 1 deletion xmodule/capa/tests/test_xqueue_interface.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
"""Test the XQueue service and interface."""

from unittest import TestCase
from unittest.mock import Mock
from unittest.mock import Mock, patch

from django.conf import settings
from django.test.utils import override_settings
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from xblock.fields import ScopeIds
from waffle.testutils import override_switch
import json

from openedx.core.djangolib.testing.utils import skip_unless_lms
from xmodule.capa.xqueue_interface import XQueueInterface, XQueueService
import pytest


@skip_unless_lms
Expand Down Expand Up @@ -52,3 +55,56 @@ def test_waittime(self):

with override_settings(XQUEUE_WAITTIME_BETWEEN_REQUESTS=15):
assert self.service.waittime == 15


@pytest.mark.django_db
@override_switch('xqueue_submission.enabled', active=True)
@patch('xmodule.capa.xqueue_submission.XQueueInterfaceSubmission.send_to_submission')
def test_send_to_queue_with_waffle_enabled(mock_send_to_submission):
url = "http://example.com/xqueue"
django_auth = {"username": "user", "password": "pass"}
requests_auth = None
xqueue_interface = XQueueInterface(url, django_auth, requests_auth)

header = json.dumps({
'lms_callback_url': 'http://example.com/courses/course-v1:test_org+test_course+test_run/xqueue/block@item_id/type@problem',
})
body = json.dumps({
'student_info': json.dumps({'anonymous_student_id': 'student_id'}),
'student_response': 'student_answer'
})
files_to_upload = None

mock_send_to_submission.return_value = {'submission': 'mock_submission'}
error, msg = xqueue_interface.send_to_queue(header, body, files_to_upload)

mock_send_to_submission.assert_called_once_with(header, body, {})


@pytest.mark.django_db
@override_switch('xqueue_submission.enabled', active=False)
@patch('xmodule.capa.xqueue_interface.XQueueInterface._http_post')
def test_send_to_queue_with_waffle_disabled(mock_http_post):

url = "http://example.com/xqueue"
django_auth = {"username": "user", "password": "pass"}
requests_auth = None
xqueue_interface = XQueueInterface(url, django_auth, requests_auth)

header = json.dumps({
'lms_callback_url': 'http://example.com/courses/course-v1:test_org+test_course+test_run/xqueue/block@item_id/type@problem',
})
body = json.dumps({
'student_info': json.dumps({'anonymous_student_id': 'student_id'}),
'student_response': 'student_answer'
})
files_to_upload = None

mock_http_post.return_value = (0, "Submission sent successfully")
error, msg = xqueue_interface.send_to_queue(header, body, files_to_upload)

mock_http_post.assert_called_once_with(
'http://example.com/xqueue/xqueue/submit/',
{'xqueue_header': header, 'xqueue_body': body},
files={}
)
76 changes: 76 additions & 0 deletions xmodule/capa/tests/test_xqueue_submission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import json
import pytest
from unittest.mock import Mock, patch
from django.conf import settings
from xmodule.capa.xqueue_submission import XQueueInterfaceSubmission
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from xblock.fields import ScopeIds


@pytest.fixture
def xqueue_service():
location = BlockUsageLocator(CourseLocator("test_org", "test_course", "test_run"), "problem", "ExampleProblem")
block = Mock(scope_ids=ScopeIds('user1', 'mock_problem', location, location))
return XQueueInterfaceSubmission()


def test_extract_item_data():
header = json.dumps({
'lms_callback_url': 'http://example.com/courses/course-v1:org+course+run/xqueue/5/block-v1:org+course+run+type@problem+block@item_id/score_update',
})
payload = json.dumps({
'student_info': json.dumps({'anonymous_student_id': 'student_id'}),
'student_response': 'student_answer',
'grader_payload': json.dumps({'grader': 'test.py'})
})
with patch('lms.djangoapps.courseware.models.StudentModule.objects.filter') as mock_filter:
mock_filter.return_value.first.return_value = Mock(grade=0.85)

student_item, student_answer, queue_name,grader, score = XQueueInterfaceSubmission().extract_item_data(header, payload)

assert student_item == {
'item_id': 'block-v1:org+course+run+type@problem+block@item_id',
'item_type': 'problem',
'course_id': 'course-v1:org+course+run',
'student_id': 'student_id'
}
assert student_answer == 'student_answer'
assert queue_name == 'default'
assert grader == 'test.py'
assert score == 0.85


@patch('submissions.api.create_submission')
def test_send_to_submission(mock_create_submission, xqueue_service):
header = json.dumps({
'lms_callback_url': 'http://example.com/courses/course-v1:test_org+test_course+test_run/xqueue/5/block-v1:test_org+test_course+test_run+type@problem+block@item_id/score_update',
})
body = json.dumps({
'student_info': json.dumps({'anonymous_student_id': 'student_id'}),
'student_response': 'student_answer',
'grader_payload': json.dumps({'grader': 'test.py'})
})

with patch('lms.djangoapps.courseware.models.StudentModule.objects.filter') as mock_filter:
mock_filter.return_value.first.return_value = Mock(grade=0.85)

mock_create_submission.return_value = {'submission': 'mock_submission'}

# Llamada a send_to_submission
result = xqueue_service.send_to_submission(header, body)

# Afirmaciones
assert 'submission' in result
assert result['submission'] == 'mock_submission'
mock_create_submission.assert_called_once_with(
{
'item_id': 'block-v1:test_org+test_course+test_run+type@problem+block@item_id',
'item_type': 'problem',
'course_id': 'course-v1:test_org+test_course+test_run',
'student_id': 'student_id'
},
'student_answer',
queue_name='default',
grader='test.py',
score=0.85
)
21 changes: 21 additions & 0 deletions xmodule/capa/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import nh3
from calc import evaluator
from lxml import etree
from django.conf import settings
from django.urls import reverse

from openedx.core.djangolib.markup import HTML

Expand Down Expand Up @@ -249,3 +251,22 @@ def get_course_id_from_capa_block(capa_block):
# raise a type error when we try to serialize them into a course
# run key. This is tolerable because such course runs are deprecated.
return None


def construct_callback(block, dispatch: str = 'score_update') -> str:
"""
Return a fully qualified callback URL for external queueing system.
"""
relative_xqueue_callback_url = reverse(
'xqueue_callback',
kwargs=dict(
course_id=str(block.scope_ids.usage_id.context_key),
userid=str(block.scope_ids.user_id),
mod_id=str(block.scope_ids.usage_id),
dispatch=dispatch,
),
)
xqueue_callback_url_prefix = settings.XQUEUE_INTERFACE.get('url', settings.LMS_ROOT_URL)
print('xqueue_callback_url_prefix -------', xqueue_callback_url_prefix)
print('relative_xqueue_callback_url --------------------', relative_xqueue_callback_url)
return xqueue_callback_url_prefix + relative_xqueue_callback_url
13 changes: 11 additions & 2 deletions xmodule/capa/xqueue_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
import logging

import requests
from waffle import switch_is_active
from django.conf import settings
from django.urls import reverse
from requests.auth import HTTPBasicAuth
from xmodule.capa.xqueue_submission import XQueueInterfaceSubmission

if TYPE_CHECKING:
from xmodule.capa_block import ProblemBlock

log = logging.getLogger(__name__)
dateformat = '%Y%m%d%H%M%S'
dateformat = '%Y-%m-%dT%H:%M:%S'

XQUEUE_METRIC_NAME = 'edxapp.xqueue'

Expand Down Expand Up @@ -130,11 +132,18 @@ def _send_to_queue(self, header, body, files_to_upload): # lint-amnesty, pylint
'xqueue_header': header,
'xqueue_body': body
}

files = {}
if files_to_upload is not None:
for f in files_to_upload:
files.update({f.name: f})


if switch_is_active('xqueue_submission.enabled'):
# Use the new edx-submissions workflow
submission = XQueueInterfaceSubmission().send_to_submission(header, body, files)
log.error(submission)


return self._http_post(self.url + '/xqueue/submit/', payload, files=files)

def _http_post(self, url, data, files=None): # lint-amnesty, pylint: disable=missing-function-docstring
Expand Down
Loading
Loading