Commit 7d33b801 by Braden MacDonald

Implemented targeted feedback during assessment review

parent 9af13c34
...@@ -34,6 +34,7 @@ from xblock.validation import ValidationMessage ...@@ -34,6 +34,7 @@ from xblock.validation import ValidationMessage
from .message import MentoringMessageBlock from .message import MentoringMessageBlock
from .step import StepParentMixin, StepMixin from .step import StepParentMixin, StepMixin
from xblockutils.helpers import child_isinstance
from xblockutils.resources import ResourceLoader from xblockutils.resources import ResourceLoader
from xblockutils.studio_editable import StudioEditableXBlockMixin, StudioContainerXBlockMixin from xblockutils.studio_editable import StudioEditableXBlockMixin, StudioContainerXBlockMixin
...@@ -323,8 +324,10 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -323,8 +324,10 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
fragment.add_javascript_url(self.runtime.local_resource_url(self, js_file)) fragment.add_javascript_url(self.runtime.local_resource_url(self, js_file))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring.js')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring.js'))
fragment.add_resource(loader.load_unicode('templates/html/mentoring_attempts.html'), "text/html") fragment.add_resource(loader.load_unicode('templates/html/mentoring_attempts.html'), "text/html")
fragment.add_resource(loader.load_unicode('templates/html/mentoring_grade.html'), "text/html") if self.is_assessment:
fragment.add_resource(loader.load_unicode('templates/html/mentoring_review_questions.html'), "text/html") fragment.add_resource(
loader.load_unicode('templates/html/mentoring_assessment_templates.html'), "text/html"
)
self.include_theme_files(fragment) self.include_theme_files(fragment)
# Workbench doesn't have font awesome, so add it: # Workbench doesn't have font awesome, so add it:
...@@ -425,6 +428,28 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -425,6 +428,28 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
else: else:
return None return None
@property
def review_tips(self):
""" Get review tips, shown for wrong answers in assessment mode. """
if not self.is_assessment or self.step != len(self.steps):
return [] # Review tips are only used in assessment mode, and only on the last step.
review_tips = []
status_cache = dict(self.student_results)
for child_id in self.steps:
child = self.runtime.get_block(child_id)
if child.name:
result = status_cache.get(child.name)
if result and result.get('status') != 'correct':
# The student got this wrong. Check if there is a review tip to show.
tip_html = child.get_review_tip()
if tip_html:
review_tips.append(tip_html)
return review_tips
@property
def review_tips_json(self):
return json.dumps(self.review_tips)
def show_extended_feedback(self): def show_extended_feedback(self):
return self.extended_feedback and self.max_attempts_reached return self.extended_feedback and self.max_attempts_reached
...@@ -604,6 +629,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -604,6 +629,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
children = [child for child in children if not isinstance(child, MentoringMessageBlock)] children = [child for child in children if not isinstance(child, MentoringMessageBlock)]
steps = [child for child in children if isinstance(child, StepMixin)] # Faster than the self.steps property steps = [child for child in children if isinstance(child, StepMixin)] # Faster than the self.steps property
assessment_message = None assessment_message = None
review_tips = []
for child in children: for child in children:
if child.name and child.name in submissions: if child.name and child.name in submissions:
...@@ -639,6 +665,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -639,6 +665,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
}) })
event_data['final_grade'] = score.raw event_data['final_grade'] = score.raw
assessment_message = self.assessment_message assessment_message = self.assessment_message
review_tips = self.review_tips
self.num_attempts += 1 self.num_attempts += 1
self.completed = True self.completed = True
...@@ -663,6 +690,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -663,6 +690,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
'partial': self.partial_json(stringify=False), 'partial': self.partial_json(stringify=False),
'extended_feedback': self.show_extended_feedback() or '', 'extended_feedback': self.show_extended_feedback() or '',
'assessment_message': assessment_message, 'assessment_message': assessment_message,
'assessment_review_tips': review_tips,
} }
@XBlock.json_handler @XBlock.json_handler
...@@ -691,9 +719,10 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -691,9 +719,10 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
def get_message_content(self, message_type): def get_message_content(self, message_type):
for child_id in self.children: for child_id in self.children:
child = self.runtime.get_block(child_id) if child_isinstance(self, child_id, MentoringMessageBlock):
if isinstance(child, MentoringMessageBlock) and child.type == message_type: child = self.runtime.get_block(child_id)
return child.content if child.type == message_type:
return child.content
def validate(self): def validate(self):
""" """
......
...@@ -84,6 +84,20 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin): ...@@ -84,6 +84,20 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin):
"used up all of their allowed attempts." "used up all of their allowed attempts."
), ),
}, },
"on-assessment-review-question": {
"display_name": _(u"Study tips if this question was wrong"),
"long_display_name": _(u"Study tips shown during assessment review if wrong"),
"default": _(
u"Review ____."
),
"description": _(
u"In assessment mode, this message will be shown when the student is reviewing "
"their answers to the assessment, if the student got this specific question "
"wrong and is allowed to try again. "
"This message is ignored in standard mode and is not shown if the student has "
"used up all of their allowed attempts."
),
},
} }
content = String( content = String(
...@@ -103,6 +117,10 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin): ...@@ -103,6 +117,10 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin):
{"value": "incomplete", "display_name": MESSAGE_TYPES["incomplete"]["display_name"]}, {"value": "incomplete", "display_name": MESSAGE_TYPES["incomplete"]["display_name"]},
{"value": "max_attempts_reached", "display_name": MESSAGE_TYPES["max_attempts_reached"]["display_name"]}, {"value": "max_attempts_reached", "display_name": MESSAGE_TYPES["max_attempts_reached"]["display_name"]},
{"value": "on-assessment-review", "display_name": MESSAGE_TYPES["on-assessment-review"]["display_name"]}, {"value": "on-assessment-review", "display_name": MESSAGE_TYPES["on-assessment-review"]["display_name"]},
{
"value": "on-assessment-review-question",
"display_name": MESSAGE_TYPES["on-assessment-review-question"]["display_name"]
},
), ),
) )
editable_fields = ("content", ) editable_fields = ("content", )
......
...@@ -174,6 +174,17 @@ ...@@ -174,6 +174,17 @@
display: none; display: none;
} }
.mentoring .assessment-messages p.review-tips-intro {
margin-top: 1em;
margin-bottom: 0;
font-weight: bold;
}
.mentoring .assessment-messages .review-tips-list {
margin-top: 0;
padding-top: 0;
}
.pb-clarification span.clarification i { .pb-clarification span.clarification i {
font-style: normal; font-style: normal;
} }
......
...@@ -4,3 +4,13 @@ ...@@ -4,3 +4,13 @@
height: 30px; height: 30px;
line-height: 30px; line-height: 30px;
} }
.xblock[data-block-type=pb-mcq] .submission-message-help p,
.xblock[data-block-type=pb-mrq] .submission-message-help p,
.xblock[data-block-type=pb-rating] .submission-message-help p {
border-top: 1px solid #ddd;
font-size: 0.85em;
font-style: italic;
margin-top: 1em;
padding-top: 0.3em;
}
function MentoringAssessmentView(runtime, element, mentoring) { function MentoringAssessmentView(runtime, element, mentoring) {
var gradeTemplate = _.template($('#xblock-grade-template').html()); var gradeTemplate = _.template($('#xblock-grade-template').html());
var reviewQuestionsTemplate = _.template($('#xblock-review-questions-template').html()); var reviewQuestionsTemplate = _.template($('#xblock-review-questions-template').html()); // Detailed list of which questions the user got wrong
var reviewTipsTemplate = _.template($('#xblock-review-tips-template').html()); // Tips about specific questions the user got wrong
var submitDOM, nextDOM, reviewDOM, tryAgainDOM, messagesDOM, reviewLinkDOM; var submitDOM, nextDOM, reviewDOM, tryAgainDOM, messagesDOM, reviewLinkDOM;
var submitXHR; var submitXHR;
var checkmark; var checkmark;
...@@ -63,8 +64,20 @@ function MentoringAssessmentView(runtime, element, mentoring) { ...@@ -63,8 +64,20 @@ function MentoringAssessmentView(runtime, element, mentoring) {
} }
mentoring.renderAttempts(); mentoring.renderAttempts();
if (data.assessment_message && (data.max_attempts === 0 || data.num_attempts < data.max_attempts)) { if (data.max_attempts === 0 || data.num_attempts < data.max_attempts) {
mentoring.setContent(messagesDOM, data.assessment_message); var messageHTML = '';
if (data.assessment_message) {
messageHTML += data.assessment_message; // Overall on-assessment-review message
}
if (data.assessment_review_tips.length > 0) {
messageHTML += reviewTipsTemplate({
// on-assessment-review-question messages specific to questions the student got wrong:
tips: data.assessment_review_tips
});
}
if (messageHTML.length > 0) {
mentoring.setContent(messagesDOM, messageHTML);
}
messagesDOM.show(); messagesDOM.show();
} }
$('a.question-link', element).click(reviewJump); $('a.question-link', element).click(reviewJump);
......
...@@ -33,6 +33,7 @@ from xblockutils.studio_editable import StudioEditableXBlockMixin, StudioContain ...@@ -33,6 +33,7 @@ from xblockutils.studio_editable import StudioEditableXBlockMixin, StudioContain
from .choice import ChoiceBlock from .choice import ChoiceBlock
from .mentoring import MentoringBlock from .mentoring import MentoringBlock
from .message import MentoringMessageBlock
from .step import StepMixin from .step import StepMixin
from .tip import TipBlock from .tip import TipBlock
...@@ -226,3 +227,11 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc ...@@ -226,3 +227,11 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
break break
values_with_tips.update(values) values_with_tips.update(values)
return validation return validation
def get_review_tip(self):
""" Get the text to show on the assessment review when the student gets this question wrong """
for child_id in self.children:
if child_isinstance(self, child_id, MentoringMessageBlock):
child = self.runtime.get_block(child_id)
if child.type == "on-assessment-review-question":
return child.content
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
data-num_attempts="{{ self.num_attempts }}" data-num_attempts="{{ self.num_attempts }}"
data-extended_feedback="{%if self.extended_feedback %}True{% endif %}" data-extended_feedback="{%if self.extended_feedback %}True{% endif %}"
data-assessment_message="{{ self.assessment_message }}" data-assessment_message="{{ self.assessment_message }}"
data-assessment_review_tips="{{ self.review_tips_json }}"
data-correct="{{ self.correct_json }}" data-correct="{{ self.correct_json }}"
data-incorrect="{{ self.incorrect_json }}" data-incorrect="{{ self.incorrect_json }}"
data-partial="{{ self.partial_json }}"> data-partial="{{ self.partial_json }}">
......
...@@ -56,3 +56,24 @@ ...@@ -56,3 +56,24 @@
<hr/> <hr/>
</div> </div>
</script> </script>
<!-- Template for extended feedback: Show extended feedback details when all attempts are used up. -->
<script type="text/template" id="xblock-review-questions-template">
<% var q, last_question; %>
<ul class="review-list <%= label %>-list">
<% for (var question in questions) {{ q = questions[question]; last_question = question == questions.length - 1; %>
<li><a href="#" class="question-link" data-step="<%= q.number %>"><%= _.template(gettext("Question {number}"), {number: q.number}, {interpolate: /\{(.+?)\}/g}) %></a></li>
<% }} %>
</ul>
</script>
<!-- Tips about specific questions the student got wrong. From pb-message[type=on-assessment-review-question] blocks -->
<script type="text/template" id="xblock-review-tips-template">
<p class="review-tips-intro"><%= gettext("You might consider reviewing the following items before your next assessment attempt:") %></p>
<ul class="review-tips-list">
<% for (var tip_idx in tips) {{ %>
<li><%= tips[tip_idx] %></li>
<% }} %>
</ul>
</script>
<script type="text/template" id="xblock-review-questions-template">
<% var q, last_question; %>
<ul class="review-list <%= label %>-list">
<% for (var question in questions) {{ q = questions[question]; last_question = question == questions.length - 1; %>
<li><a href="#" class="question-link" data-step="<%= q.number %>"><%= _.template(gettext("Question {number}"), {number: q.number}, {interpolate: /\{(.+?)\}/g}) %></a></li>
<% }} %>
</ul>
</script>
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
<ul class="new-component-type"> <ul class="new-component-type">
<li><a href="#" class="single-template add-xblock-component-button" data-category="pb-choice" data-boilerplate="studio_default">{% trans "Add Custom Choice" %}</a></li> <li><a href="#" class="single-template add-xblock-component-button" data-category="pb-choice" data-boilerplate="studio_default">{% trans "Add Custom Choice" %}</a></li>
<li><a href="#" class="single-template add-xblock-component-button" data-category="pb-tip">{% trans "Add Tip" %}</a></li> <li><a href="#" class="single-template add-xblock-component-button" data-category="pb-tip">{% trans "Add Tip" %}</a></li>
<li><a href="#" class="single-template add-xblock-component-button" data-category="pb-message" data-boilerplate="on-assessment-review-question">{% trans "Message (Assessment Review)" %}</a></li>
</ul> </ul>
</div> </div>
</div> </div>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment