Commit 7d33b801 by Braden MacDonald

Implemented targeted feedback during assessment review

parent 9af13c34
......@@ -34,6 +34,7 @@ from xblock.validation import ValidationMessage
from .message import MentoringMessageBlock
from .step import StepParentMixin, StepMixin
from xblockutils.helpers import child_isinstance
from xblockutils.resources import ResourceLoader
from xblockutils.studio_editable import StudioEditableXBlockMixin, StudioContainerXBlockMixin
......@@ -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, '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_grade.html'), "text/html")
fragment.add_resource(loader.load_unicode('templates/html/mentoring_review_questions.html'), "text/html")
if self.is_assessment:
fragment.add_resource(
loader.load_unicode('templates/html/mentoring_assessment_templates.html'), "text/html"
)
self.include_theme_files(fragment)
# Workbench doesn't have font awesome, so add it:
......@@ -425,6 +428,28 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
else:
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):
return self.extended_feedback and self.max_attempts_reached
......@@ -604,6 +629,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
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
assessment_message = None
review_tips = []
for child in children:
if child.name and child.name in submissions:
......@@ -639,6 +665,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
})
event_data['final_grade'] = score.raw
assessment_message = self.assessment_message
review_tips = self.review_tips
self.num_attempts += 1
self.completed = True
......@@ -663,6 +690,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
'partial': self.partial_json(stringify=False),
'extended_feedback': self.show_extended_feedback() or '',
'assessment_message': assessment_message,
'assessment_review_tips': review_tips,
}
@XBlock.json_handler
......@@ -691,8 +719,9 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
def get_message_content(self, message_type):
for child_id in self.children:
if child_isinstance(self, child_id, MentoringMessageBlock):
child = self.runtime.get_block(child_id)
if isinstance(child, MentoringMessageBlock) and child.type == message_type:
if child.type == message_type:
return child.content
def validate(self):
......
......@@ -84,6 +84,20 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin):
"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(
......@@ -103,6 +117,10 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin):
{"value": "incomplete", "display_name": MESSAGE_TYPES["incomplete"]["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-question",
"display_name": MESSAGE_TYPES["on-assessment-review-question"]["display_name"]
},
),
)
editable_fields = ("content", )
......
......@@ -174,6 +174,17 @@
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 {
font-style: normal;
}
......
......@@ -4,3 +4,13 @@
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) {
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 submitXHR;
var checkmark;
......@@ -63,8 +64,20 @@ function MentoringAssessmentView(runtime, element, mentoring) {
}
mentoring.renderAttempts();
if (data.assessment_message && (data.max_attempts === 0 || data.num_attempts < data.max_attempts)) {
mentoring.setContent(messagesDOM, data.assessment_message);
if (data.max_attempts === 0 || data.num_attempts < data.max_attempts) {
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();
}
$('a.question-link', element).click(reviewJump);
......
......@@ -33,6 +33,7 @@ from xblockutils.studio_editable import StudioEditableXBlockMixin, StudioContain
from .choice import ChoiceBlock
from .mentoring import MentoringBlock
from .message import MentoringMessageBlock
from .step import StepMixin
from .tip import TipBlock
......@@ -226,3 +227,11 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
break
values_with_tips.update(values)
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 @@
data-num_attempts="{{ self.num_attempts }}"
data-extended_feedback="{%if self.extended_feedback %}True{% endif %}"
data-assessment_message="{{ self.assessment_message }}"
data-assessment_review_tips="{{ self.review_tips_json }}"
data-correct="{{ self.correct_json }}"
data-incorrect="{{ self.incorrect_json }}"
data-partial="{{ self.partial_json }}">
......
......@@ -56,3 +56,24 @@
<hr/>
</div>
</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 @@
<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-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>
</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