diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 5092e5c378..ea60b3b7bd 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -307,7 +307,18 @@ def filesubmission(element, value, status, render_template, msg=''): Upload a single file (e.g. for programming assignments) ''' eid = element.get('id') - context = { 'id': eid, 'state': status, 'msg': msg, 'value': value, } + + # Check if problem has been queued + queued = '' + queue_len = 0 + if status == 'incomplete': # Flag indicating that the problem has been queued, 'msg' is length of queue + queued = 'true' + queue_len = msg + msg = 'Submitted to grader. (Queue length: %s)' % queue_len + + context = { 'id': eid, 'state': status, 'msg': msg, 'value': value, + 'queued': queued, 'queue_len': queue_len + } html = render_template("filesubmission.html", context) return etree.XML(html) @@ -329,10 +340,18 @@ def textbox(element, value, status, render_template, msg=''): hidden = element.get('hidden', '') # if specified, then textline is hidden and id is stored in div of name given by hidden if not value: value = element.text # if no student input yet, then use the default input given by the problem + + # Check if problem has been queued + queued = '' + queue_len = 0 + if status == 'incomplete': # Flag indicating that the problem has been queued, 'msg' is length of queue + queued = 'true' + queue_len = msg + msg = 'Submitted to grader. (Queue length: %s)' % queue_len # For CodeMirror - mode = element.get('mode') or 'python' # mode, eg "python" or "xml" - linenumbers = element.get('linenumbers','true') # for CodeMirror + mode = element.get('mode','python') + linenumbers = element.get('linenumbers','true') tabsize = element.get('tabsize','4') tabsize = int(tabsize) @@ -340,6 +359,7 @@ def textbox(element, value, status, render_template, msg=''): 'mode': mode, 'linenumbers': linenumbers, 'rows': rows, 'cols': cols, 'hidden': hidden, 'tabsize': tabsize, + 'queued': queued, 'queue_len': queue_len, } html = render_template("textbox.html", context) try: diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 66212f1e87..25b99fc00a 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -898,7 +898,7 @@ class CodeResponse(LoncapaResponse): 'processor': self.code, } - # Submit request + # Submit request. When successful, 'msg' is the prior length of the queue if is_file(submission): contents.update({'edX_student_response': submission.name}) (error, msg) = qinterface.send_to_queue(header=xheader, @@ -914,8 +914,11 @@ class CodeResponse(LoncapaResponse): cmap.set(self.answer_id, queuekey=None, msg='Unable to deliver your submission to grader. (Reason: %s.) Please try again later.' % msg) else: - # Non-null CorrectMap['queuekey'] indicates that the problem has been queued - cmap.set(self.answer_id, queuekey=queuekey, msg='Submitted to grader. (Queue length: %s)' % msg) + # Queueing mechanism flags: + # 1) Backend: Non-null CorrectMap['queuekey'] indicates that the problem has been queued + # 2) Frontend: correctness='incomplete' eventually trickles down through inputtypes.textbox + # and .filesubmission to inform the browser to poll the LMS + cmap.set(self.answer_id, queuekey=queuekey, correctness='incomplete', msg=msg) return cmap diff --git a/common/lib/capa/capa/templates/filesubmission.html b/common/lib/capa/capa/templates/filesubmission.html index ff9fc992fd..09bc287923 100644 --- a/common/lib/capa/capa/templates/filesubmission.html +++ b/common/lib/capa/capa/templates/filesubmission.html @@ -9,6 +9,9 @@ % elif state == 'incomplete': % endif + % if queued: + ${queue_len} + % endif (${state})
${msg|n} diff --git a/common/lib/capa/capa/templates/textbox.html b/common/lib/capa/capa/templates/textbox.html index f201bd6947..f31b98b580 100644 --- a/common/lib/capa/capa/templates/textbox.html +++ b/common/lib/capa/capa/templates/textbox.html @@ -19,6 +19,9 @@ % if hidden:
% endif + % if queued: + ${queue_len} + % endif
(${state})
diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index 2088e8baa3..6b1c32ae65 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -49,6 +49,8 @@ padding-left: flex-gutter(9); } } + + div { p.status { text-indent: -9999px; @@ -64,6 +66,16 @@ div { } } + &.processing { + p.status { + @include inline-block(); + background: url('../images/spinner.gif') center center no-repeat; + height: 20px; + width: 20px; + text-indent: -9999px; + } + } + &.correct, &.ui-icon-check { p.status { @include inline-block(); @@ -134,6 +146,15 @@ div { width: 14px; } + &.processing, &.ui-icon-check { + @include inline-block(); + background: url('../images/spinner.gif') center center no-repeat; + height: 20px; + position: relative; + top: 6px; + width: 25px; + } + &.correct, &.ui-icon-check { @include inline-block(); background: url('../images/correct-icon.png') center center no-repeat; diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.coffee b/common/lib/xmodule/xmodule/js/src/capa/display.coffee index 18bec8a7d1..ae589b8b04 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/capa/display.coffee @@ -12,7 +12,10 @@ class @Problem bind: => MathJax.Hub.Queue ["Typeset", MathJax.Hub] window.update_schematics() - @inputs = @$("[id^=input_#{@element_id.replace(/problem_/, '')}_]") + + problem_prefix = @element_id.replace(/problem_/,'') + @inputs = @$("[id^=input_#{problem_prefix}_]") + @$('section.action input:button').click @refreshAnswers @$('section.action input.check').click @check_fd #@$('section.action input.check').click @check @@ -26,15 +29,37 @@ class @Problem @el.attr progress: response.progress_status @el.trigger('progressChanged') + queueing: => + @queued_items = @$(".xqueue") + if @queued_items.length > 0 + if window.queuePollerID # Only one poller 'thread' per Problem + window.clearTimeout(window.queuePollerID) + window.queuePollerID = window.setTimeout(@poll, 100) + + poll: => + $.postWithPrefix "#{@url}/problem_get", (response) => + @el.html(response.html) + @executeProblemScripts() + @bind() + + @queued_items = @$(".xqueue") + if @queued_items.length == 0 + delete window.queuePollerID + else + # TODO: Dynamically adjust timeout interval based on @queued_items.value + window.queuePollerID = window.setTimeout(@poll, 1000) + render: (content) -> if content @el.html(content) @bind() + @queueing() else $.postWithPrefix "#{@url}/problem_get", (response) => @el.html(response.html) @executeProblemScripts() @bind() + @queueing() executeProblemScripts: -> @el.find(".script_placeholder").each (index, placeholder) -> diff --git a/common/lib/xmodule/xmodule/js/src/sequence/display.coffee b/common/lib/xmodule/xmodule/js/src/sequence/display.coffee index 0b17111d81..832a5ec7eb 100644 --- a/common/lib/xmodule/xmodule/js/src/sequence/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/sequence/display.coffee @@ -91,6 +91,13 @@ class @Sequence event.preventDefault() new_position = $(event.target).data('element') Logger.log "seq_goto", old: @position, new: new_position, id: @id + + # On Sequence chage, destroy any existing polling thread + # for queued submissions, see ../capa/display.coffee + if window.queuePollerID + window.clearTimeout(window.queuePollerID) + delete window.queuePollerID + @render new_position next: (event) => diff --git a/common/static/images/spinner.gif b/common/static/images/spinner.gif new file mode 100644 index 0000000000..b2f94cd12c Binary files /dev/null and b/common/static/images/spinner.gif differ