From c45a6019717877df46e2d9b1ece79b0094e451ef Mon Sep 17 00:00:00 2001 From: Jonathan Piacenti Date: Thu, 15 Oct 2015 16:50:52 -0500 Subject: [PATCH] Enable seamless use of steps that have no questions. --- problem_builder/answer.py | 1 + .../public/js/mentoring_with_steps.js | 59 +++++++++++++++---- problem_builder/public/js/step.js | 4 ++ problem_builder/questionnaire.py | 1 + problem_builder/step.py | 4 ++ problem_builder/templates/html/step.html | 2 +- .../tests/integration/test_step_builder.py | 27 +++++++++ .../xml_templates/step_builder.xml | 4 ++ .../xml_templates/step_builder_html_last.xml | 26 ++++++++ 9 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 problem_builder/tests/integration/xml_templates/step_builder_html_last.xml diff --git a/problem_builder/answer.py b/problem_builder/answer.py index 5b68bfc5..68ddf973 100644 --- a/problem_builder/answer.py +++ b/problem_builder/answer.py @@ -125,6 +125,7 @@ class AnswerBlock(SubmittingXBlockMixin, AnswerMixin, QuestionMixin, StudioEdita """ CATEGORY = 'pb-answer' STUDIO_LABEL = _(u"Long Answer") + answerable = True name = String( display_name=_("Question ID (name)"), diff --git a/problem_builder/public/js/mentoring_with_steps.js b/problem_builder/public/js/mentoring_with_steps.js index cc64b15d..c790ad9d 100644 --- a/problem_builder/public/js/mentoring_with_steps.js +++ b/problem_builder/public/js/mentoring_with_steps.js @@ -56,6 +56,15 @@ function MentoringWithStepsBlock(runtime, element) { } } + function postUpdateStep(response) { + activeStep = response.active_step; + if (activeStep === -1) { + updateNumAttempts(); + } else { + updateControls(); + } + } + function handleResults(response) { showFeedback(response); @@ -64,14 +73,7 @@ function MentoringWithStepsBlock(runtime, element) { // Otherwise, get UI ready for showing next step. var handlerUrl = runtime.handlerUrl(element, 'update_active_step'); $.post(handlerUrl, JSON.stringify(activeStep+1)) - .success(function(response) { - activeStep = response.active_step; - if (activeStep === -1) { - updateNumAttempts(); - } else { - updateControls(); - } - }); + .success(postUpdateStep); } function updateNumAttempts() { @@ -138,6 +140,14 @@ function MentoringWithStepsBlock(runtime, element) { step.submit(handleResults); } + function markRead() { + var handlerUrl = runtime.handlerUrl(element, 'update_active_step'); + $.post(handlerUrl, JSON.stringify(activeStep+1)).success(function (response) { + postUpdateStep(response); + updateDisplay(); + }); + } + function getResults() { var step = steps[activeStep]; step.getResults(handleReviewResults); @@ -195,9 +205,18 @@ function MentoringWithStepsBlock(runtime, element) { showActiveStep(); validateXBlock(); updateNextLabel(); - nextDOM.attr('disabled', 'disabled'); + var step = steps[activeStep]; + if (step.hasQuestion()) { + nextDOM.attr('disabled', 'disabled'); + } else { + nextDOM.removeAttr('disabled'); + } if (isLastStep() && reviewStep) { - reviewDOM.attr('disabled', 'disabled'); + if (step.hasQuestion()) { + reviewDOM.attr('disabled', 'disabled'); + } else { + reviewDOM.removeAttr('disabled') + } reviewDOM.show(); } } @@ -261,9 +280,14 @@ function MentoringWithStepsBlock(runtime, element) { nextDOM.show(); nextDOM.removeAttr('disabled'); } + var step = steps[activeStep]; tryAgainDOM.hide(); - submitDOM.show(); + if (step.hasQuestion()) { + submitDOM.show(); + } else { + submitDOM.hide(); + } submitDOM.attr('disabled', 'disabled'); reviewLinkDOM.show(); @@ -302,8 +326,19 @@ function MentoringWithStepsBlock(runtime, element) { } else { submitDOM.removeAttr('disabled'); } - if (isLastStep()) { + if (isLastStep() && step.hasQuestion()) { nextDOM.hide(); + } else if (isLastStep()) { + reviewDOM.one('click', markRead); + reviewDOM.removeAttr('disabled'); + nextDOM.hide() + } else if (!step.hasQuestion()) { + nextDOM.one('click', markRead); + } + if (step.hasQuestion()) { + submitDOM.show(); + } else { + submitDOM.hide(); } } diff --git a/problem_builder/public/js/step.js b/problem_builder/public/js/step.js index 3751393e..83438acd 100644 --- a/problem_builder/public/js/step.js +++ b/problem_builder/public/js/step.js @@ -85,6 +85,10 @@ function MentoringStepBlock(runtime, element) { getStepLabel: function() { return $('.sb-step', element).data('next-button-label'); + }, + + hasQuestion: function() { + return $('.sb-step', element).data('has-question') } }; diff --git a/problem_builder/questionnaire.py b/problem_builder/questionnaire.py index 40cf9240..7e846809 100644 --- a/problem_builder/questionnaire.py +++ b/problem_builder/questionnaire.py @@ -90,6 +90,7 @@ class QuestionnaireAbstractBlock( ) editable_fields = ('question', 'message', 'weight', 'display_name', 'show_title') has_children = True + answerable = True @lazy def html_id(self): diff --git a/problem_builder/step.py b/problem_builder/step.py index 77b1bd8c..43a351b3 100644 --- a/problem_builder/step.py +++ b/problem_builder/step.py @@ -133,6 +133,10 @@ def allowed_nested_blocks(self): AnswerRecapBlock, MentoringTableBlock, ] + @property + def has_question(self): + return any(getattr(child, 'answerable', False) for child in self.steps) + @XBlock.json_handler def submit(self, submissions, suffix=''): log.info(u'Received submissions: {}'.format(submissions)) diff --git a/problem_builder/templates/html/step.html b/problem_builder/templates/html/step.html index 8fd325d4..096dd0d7 100644 --- a/problem_builder/templates/html/step.html +++ b/problem_builder/templates/html/step.html @@ -1,4 +1,4 @@ -
+
{% if show_title %}

diff --git a/problem_builder/tests/integration/test_step_builder.py b/problem_builder/tests/integration/test_step_builder.py index 999d2678..989c3975 100644 --- a/problem_builder/tests/integration/test_step_builder.py +++ b/problem_builder/tests/integration/test_step_builder.py @@ -211,6 +211,7 @@ def extended_feedback_checks(self, step_builder, controls, expected_results): # It should be possible to visit the MRQ from here self.wait_until_clickable(controls.next_question) controls.next_question.click() + self.html_section(step_builder, controls) self.peek_at_multiple_response_question( None, step_builder, controls, extended_feedback=True, alternative_review=True ) @@ -230,6 +231,24 @@ def test_next_label(self): self.expect_question_visible(None, step_builder) self.assertEqual(controls.next_question.get_attribute('value'), "Next Challenge") + def html_section(self, step_builder, controls, last=False): + self.wait_until_hidden(controls.submit) + target_control = controls.review if last else controls.next_question + self.wait_until_clickable(target_control) + target_control.click() + + def test_html_last(self): + step_builder, controls = self.load_assessment_scenario("step_builder_html_last.xml") + # Step 1 + # Submit free-form answer, go to next step + self.freeform_answer(None, step_builder, controls, 'This is the answer', CORRECT) + + # Step 2 + # Submit MCQ, go to next step + self.single_choice_question(None, step_builder, controls, 'Maybe not', INCORRECT) + + self.html_section(step_builder, controls, last=True) + @data( {"max_attempts": 0, "extended_feedback": False}, # Unlimited attempts, no extended feedback {"max_attempts": 1, "extended_feedback": True}, # Limited attempts, extended feedback @@ -253,6 +272,9 @@ def test_step_builder(self, params): # Submit rating, go to next step self.rating_question(None, step_builder, controls, "5 - Extremely good", CORRECT) + # Step 4, html with no question. + self.html_section(step_builder, controls) + # Last step # Submit MRQ, go to review with patch.object(WorkbenchRuntime, 'publish') as patched_method: @@ -308,6 +330,8 @@ def test_step_builder(self, params): # Submit rating, go to next step self.rating_question(None, step_builder, controls, "1 - Not good at all", INCORRECT) + # Step 4, read only. Go to next step. + self.html_section(step_builder, controls) # Last step # Submit MRQ, go to review user_selection = ("Its elegance", "Its beauty", "Its gracefulness") @@ -350,6 +374,7 @@ def test_review_tips(self): self.freeform_answer(None, step_builder, controls, 'This is the answer', CORRECT) self.single_choice_question(None, step_builder, controls, 'Maybe not', INCORRECT) self.rating_question(None, step_builder, controls, "5 - Extremely good", CORRECT) + self.html_section(step_builder, controls) self.multiple_response_question(None, step_builder, controls, ("Its beauty",), PARTIAL, last=True) # The review tips for MCQ 2 and the MRQ should be shown: @@ -374,6 +399,7 @@ def test_review_tips(self): self.single_choice_question(None, step_builder, controls, 'Yes', CORRECT) self.rating_question(None, step_builder, controls, "5 - Extremely good", CORRECT) user_selection = ("Its elegance", "Its beauty", "Its gracefulness") + self.html_section(step_builder, controls) self.multiple_response_question(None, step_builder, controls, user_selection, CORRECT, last=True) # If attempts remain and student got all answers right, show "complete" message @@ -390,6 +416,7 @@ def test_review_tips(self): ) self.single_choice_question(None, step_builder, controls, 'Maybe not', INCORRECT) self.rating_question(None, step_builder, controls, "1 - Not good at all", INCORRECT) + self.html_section(step_builder, controls) self.multiple_response_question(None, step_builder, controls, ("Its beauty",), PARTIAL, last=True) # The review tips will not be shown because no attempts remain: diff --git a/problem_builder/tests/integration/xml_templates/step_builder.xml b/problem_builder/tests/integration/xml_templates/step_builder.xml index fa553e1c..a60ba87c 100644 --- a/problem_builder/tests/integration/xml_templates/step_builder.xml +++ b/problem_builder/tests/integration/xml_templates/step_builder.xml @@ -36,6 +36,10 @@ + + Test test test + + Its elegance diff --git a/problem_builder/tests/integration/xml_templates/step_builder_html_last.xml b/problem_builder/tests/integration/xml_templates/step_builder_html_last.xml new file mode 100644 index 00000000..f6643af3 --- /dev/null +++ b/problem_builder/tests/integration/xml_templates/step_builder_html_last.xml @@ -0,0 +1,26 @@ + + + + + + + + + Yes + Maybe not + I don't understand + + Great! + Ah, damn. +
Really?
+
+
+ + + Bla bla bla + + + + +