Commit 22e12f6d by Jonathan Piacenti

Implemented extended feedback.

parent 29003051
...@@ -179,7 +179,8 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock): ...@@ -179,7 +179,8 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
""" Normal view of this XBlock, identical to mentoring_view """ """ Normal view of this XBlock, identical to mentoring_view """
return self.mentoring_view(context) return self.mentoring_view(context)
def get_results(self): def get_results(self, previous_response=None):
# Previous result is actually stored in database table-- ignore.
return { return {
'student_input': self.student_input, 'student_input': self.student_input,
'status': self.status, 'status': self.status,
......
...@@ -81,10 +81,12 @@ class MCQBlock(SubmittingXBlockMixin, QuestionnaireAbstractBlock): ...@@ -81,10 +81,12 @@ class MCQBlock(SubmittingXBlockMixin, QuestionnaireAbstractBlock):
if submission in tip.values: if submission in tip.values:
tips_html.append(tip.render('mentoring_view').content) tips_html.append(tip.render('mentoring_view').content)
formatted_tips = None
if tips_html: if tips_html:
formatted_tips = loader.render_template('templates/html/tip_choice_group.html', { formatted_tips = loader.render_template('templates/html/tip_choice_group.html', {
'tips_html': tips_html, 'tips_html': tips_html,
}) })
self.student_choice = submission self.student_choice = submission
...@@ -95,13 +97,13 @@ class MCQBlock(SubmittingXBlockMixin, QuestionnaireAbstractBlock): ...@@ -95,13 +97,13 @@ class MCQBlock(SubmittingXBlockMixin, QuestionnaireAbstractBlock):
return { return {
'submission': submission, 'submission': submission,
'status': 'correct' if correct else 'incorrect', 'status': 'correct' if correct else 'incorrect',
'tips': formatted_tips if tips_html else None, 'tips': formatted_tips,
'weight': self.weight, 'weight': self.weight,
'score': 1 if correct else 0, 'score': 1 if correct else 0,
} }
def get_results(self): def get_results(self, previous_result):
return self.calculate_results(self.student_choice) return self.calculate_results(previous_result['submission'])
def submit(self, submission): def submit(self, submission):
log.debug(u'Received MCQ submission: "%s"', submission) log.debug(u'Received MCQ submission: "%s"', submission)
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
# Imports ########################################################### # Imports ###########################################################
import logging import logging
import json
from collections import namedtuple from collections import namedtuple
...@@ -432,7 +433,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -432,7 +433,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
# of the whole mentoring block being completed. This is because in standard mode, all children # of the whole mentoring block being completed. This is because in standard mode, all children
# must be correct to complete the block. In assessment mode with extended feedback, completion # must be correct to complete the block. In assessment mode with extended feedback, completion
# happens when you're out of attempts, no matter how you did. # happens when you're out of attempts, no matter how you did.
completed = choices[child.name]['status'] == 'correct' completed = choices[child.name]['status']
break break
# The 'completed' message should always be shown in this case, since no more attempts are available. # The 'completed' message should always be shown in this case, since no more attempts are available.
...@@ -510,7 +511,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -510,7 +511,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
}) })
return { return {
'submitResults': submit_results, 'results': submit_results,
'completed': self.completed, 'completed': self.completed,
'attempted': self.attempted, 'attempted': self.attempted,
'message': message, 'message': message,
...@@ -526,6 +527,8 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -526,6 +527,8 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
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
print children
print submissions
for child in children: for child in children:
if child.name and child.name in submissions: if child.name and child.name in submissions:
submission = submissions[child.name] submission = submissions[child.name]
...@@ -565,6 +568,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -565,6 +568,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
self.num_attempts += 1 self.num_attempts += 1
self.completed = True self.completed = True
print current_child
event_data['exercise_id'] = current_child.name event_data['exercise_id'] = current_child.name
event_data['num_attempts'] = self.num_attempts event_data['num_attempts'] = self.num_attempts
event_data['submitted_answer'] = submissions event_data['submitted_answer'] = submissions
...@@ -581,6 +585,10 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC ...@@ -581,6 +585,10 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
'correct_answer': len(score.correct), 'correct_answer': len(score.correct),
'incorrect_answer': len(score.incorrect), 'incorrect_answer': len(score.incorrect),
'partially_correct_answer': len(score.partially_correct), 'partially_correct_answer': len(score.partially_correct),
'correct': self.correct_json(stringify=False),
'incorrect': self.incorrect_json(stringify=False),
'partial': self.partial_json(stringify=False),
'extended_feedback': self.show_extended_feedback() or '',
'assessment_message': assessment_message, 'assessment_message': assessment_message,
} }
......
...@@ -81,9 +81,25 @@ class MRQBlock(QuestionnaireAbstractBlock): ...@@ -81,9 +81,25 @@ class MRQBlock(QuestionnaireAbstractBlock):
return self._(u"Ignored") return self._(u"Ignored")
return self._(u"Not Acceptable") return self._(u"Not Acceptable")
def get_results(self, previous_result):
"""
Get the results a student has already submitted.
"""
result = self.calculate_results(previous_result['submissions'])
result['completed'] = True
return result
def submit(self, submissions):
log.debug(u'Received MRQ submissions: "%s"', submissions)
result = self.calculate_results(submissions)
self.student_choices = submissions
log.debug(u'MRQ submissions result: %s', result)
return result
def calculate_results(self, submissions): def calculate_results(self, submissions):
score = 0 score = 0
results = [] results = []
for choice in self.custom_choices: for choice in self.custom_choices:
choice_completed = True choice_completed = True
...@@ -126,18 +142,6 @@ class MRQBlock(QuestionnaireAbstractBlock): ...@@ -126,18 +142,6 @@ class MRQBlock(QuestionnaireAbstractBlock):
'score': (float(score) / len(results)) if results else 0, 'score': (float(score) / len(results)) if results else 0,
} }
def get_results(self):
return self.calculate_results(self.student_choices)
def submit(self, submissions):
log.debug(u'Received MRQ submissions: "%s"', submissions)
result = self.calculate_results(submissions)
self.student_choices = submissions
log.debug(u'MRQ submissions result: %s', result)
return result
def validate_field_data(self, validation, data): def validate_field_data(self, validation, data):
""" """
Validate this block's field data. Validate this block's field data.
......
...@@ -136,3 +136,28 @@ ...@@ -136,3 +136,28 @@
.mentoring input[type="radio"] { .mentoring input[type="radio"] {
margin: 0; margin: 0;
} }
.mentoring .review-list {
list-style: none;
padding-left: 0 !important;
margin-left: 0;
}
.mentoring .review-list li {
display: inline;
}
.mentoring .review-list li a{
font-weight: bold;
}
.mentoring .results-section {
float: left;
}
.mentoring .clear {
display: block;
clear: both;
}
.mentoring .review-link {
float: right;
}
\ No newline at end of file
...@@ -17,15 +17,22 @@ function AnswerBlock(runtime, element) { ...@@ -17,15 +17,22 @@ function AnswerBlock(runtime, element) {
return $(':input', element).serializeArray(); return $(':input', element).serializeArray();
}, },
handleReview: function(result) {
$('textarea', element).prop('disabled', true);
},
handleSubmit: function(result) { handleSubmit: function(result) {
if (this.mode === 'assessment')
return;
var checkmark = $('.answer-checkmark', element); var checkmark = $('.answer-checkmark', element);
$(element).find('.message').text((result || {}).error || ''); $(element).find('.message').text((result || {}).error || '');
this.clearResult(); this.clearResult();
if (this.mode === 'assessment') {
// Display of checkmark would be redundant.
return
}
if (result.status === "correct") { if (result.status === "correct") {
checkmark.addClass('checkmark-correct icon-ok fa-check'); checkmark.addClass('checkmark-correct icon-ok fa-check');
} }
......
...@@ -60,7 +60,7 @@ function MentoringBlock(runtime, element) { ...@@ -60,7 +60,7 @@ function MentoringBlock(runtime, element) {
if (typeof obj !== 'undefined' && typeof obj[fn] == 'function') { if (typeof obj !== 'undefined' && typeof obj[fn] == 'function') {
return obj[fn].apply(obj, Array.prototype.slice.call(arguments, 2)); return obj[fn].apply(obj, Array.prototype.slice.call(arguments, 2));
} else { } else {
return undefined; return null;
} }
} }
......
...@@ -4,26 +4,26 @@ function MentoringStandardView(runtime, element, mentoring) { ...@@ -4,26 +4,26 @@ function MentoringStandardView(runtime, element, mentoring) {
var callIfExists = mentoring.callIfExists; var callIfExists = mentoring.callIfExists;
function handleSubmitResults(results) { function handleSubmitResults(response) {
messagesDOM.empty().hide(); messagesDOM.empty().hide();
$.each(results.submitResults || [], function(index, submitResult) { $.each(response.results || [], function(index, result_spec) {
var input = submitResult[0]; var input = result_spec[0];
var result = submitResult[1]; var result = result_spec[1];
var child = mentoring.getChildByName(input); var child = mentoring.getChildByName(input);
var options = { var options = {
max_attempts: results.max_attempts, max_attempts: response.max_attempts,
num_attempts: results.num_attempts num_attempts: response.num_attempts
}; };
callIfExists(child, 'handleSubmit', result, options); callIfExists(child, 'handleSubmit', result, options);
}); });
$('.attempts', element).data('max_attempts', results.max_attempts); $('.attempts', element).data('max_attempts', response.max_attempts);
$('.attempts', element).data('num_attempts', results.num_attempts); $('.attempts', element).data('num_attempts', response.num_attempts);
mentoring.renderAttempts(); mentoring.renderAttempts();
// Messages should only be displayed upon hitting 'submit', not on page reload // Messages should only be displayed upon hitting 'submit', not on page reload
mentoring.setContent(messagesDOM, results.message); mentoring.setContent(messagesDOM, response.message);
if (messagesDOM.html().trim()) { if (messagesDOM.html().trim()) {
messagesDOM.prepend('<div class="title1">' + gettext('Feedback') + '</div>'); messagesDOM.prepend('<div class="title1">' + gettext('Feedback') + '</div>');
messagesDOM.show(); messagesDOM.show();
...@@ -32,23 +32,30 @@ function MentoringStandardView(runtime, element, mentoring) { ...@@ -32,23 +32,30 @@ function MentoringStandardView(runtime, element, mentoring) {
submitDOM.attr('disabled', 'disabled'); submitDOM.attr('disabled', 'disabled');
} }
function submit() { function calculate_results(handler_name) {
var success = true;
var data = {}; var data = {};
var children = mentoring.children; var children = mentoring.children;
for (var i = 0; i < children.length; i++) { for (var i = 0; i < children.length; i++) {
var child = children[i]; var child = children[i];
if (child && child.name !== undefined && typeof(child.submit) !== "undefined") { if (child && child.name !== undefined && typeof(child[handler_name]) !== "undefined") {
data[child.name] = child.submit(); data[child.name] = child[handler_name]();
} }
} }
var handlerUrl = runtime.handlerUrl(element, 'submit'); var handlerUrl = runtime.handlerUrl(element, handler_name);
if (submitXHR) { if (submitXHR) {
submitXHR.abort(); submitXHR.abort();
} }
submitXHR = $.post(handlerUrl, JSON.stringify(data)).success(handleSubmitResults); submitXHR = $.post(handlerUrl, JSON.stringify(data)).success(handleSubmitResults);
} }
function get_results() {
calculate_results('get_results');
}
function submit() {
calculate_results('submit')
}
function clearResults() { function clearResults() {
messagesDOM.empty().hide(); messagesDOM.empty().hide();
...@@ -68,6 +75,8 @@ function MentoringStandardView(runtime, element, mentoring) { ...@@ -68,6 +75,8 @@ function MentoringStandardView(runtime, element, mentoring) {
submitDOM = $(element).find('.submit .input-main'); submitDOM = $(element).find('.submit .input-main');
submitDOM.bind('click', submit); submitDOM.bind('click', submit);
submitDOM.show(); submitDOM.show();
// Not used in standard mode.
$(element).find('.review-link').hide();
var options = { var options = {
onChange: onChange onChange: onChange
......
...@@ -97,23 +97,24 @@ function MCQBlock(runtime, element) { ...@@ -97,23 +97,24 @@ function MCQBlock(runtime, element) {
} }
}, },
handleSubmit: function(result) { handleReview: function(result){
if (this.mode === 'assessment') $('.choice input[value="' + result.submission + '"]', element).prop('checked', true);
return; $('.choice input', element).prop('disabled', true);
},
handleSubmit: function(result) {
mentoring = this.mentoring; mentoring = this.mentoring;
var messageView = MessageView(element, mentoring); var messageView = MessageView(element, mentoring);
messageView.clearResult(); messageView.clearResult();
var choiceInputs = $('.choice input', element); var choiceInputs = $('.choice-selector input', element);
$.each(choiceInputs, function(index, choiceInput) { $.each(choiceInputs, function(index, choiceInput) {
var choiceInputDOM = $(choiceInput); var choiceInputDOM = $(choiceInput);
var choiceDOM = choiceInputDOM.closest('.choice'); var choiceDOM = choiceInputDOM.closest('.choice');
var choiceResultDOM = $('.choice-result', choiceDOM); var choiceResultDOM = $('.choice-result', choiceDOM);
var choiceTipsDOM = $('.choice-tips', choiceDOM); var choiceTipsDOM = $('.choice-tips', choiceDOM);
var choiceTipsCloseDOM;
if (result.status === "correct" && choiceInputDOM.val() === result.submission) { if (result.status === "correct" && choiceInputDOM.val() === result.submission) {
choiceDOM.addClass('correct'); choiceDOM.addClass('correct');
...@@ -129,7 +130,6 @@ function MCQBlock(runtime, element) { ...@@ -129,7 +130,6 @@ function MCQBlock(runtime, element) {
messageView.showMessage(choiceTipsDOM); messageView.showMessage(choiceTipsDOM);
} }
choiceTipsCloseDOM = $('.close', choiceTipsDOM);
choiceResultDOM.off('click').on('click', function() { choiceResultDOM.off('click').on('click', function() {
if (choiceTipsDOM.html() !== '') { if (choiceTipsDOM.html() !== '') {
messageView.showMessage(choiceTipsDOM); messageView.showMessage(choiceTipsDOM);
...@@ -178,9 +178,14 @@ function MRQBlock(runtime, element) { ...@@ -178,9 +178,14 @@ function MRQBlock(runtime, element) {
return checkedValues; return checkedValues;
}, },
handleReview: function(result) {
$.each(result.submissions, function (index, value) {
$('input[type="checkbox"][value="' + value + '"]').prop('checked', true)
});
$('input', element).prop('disabled', true);
},
handleSubmit: function(result, options) { handleSubmit: function(result, options) {
if (this.mode === 'assessment')
return;
mentoring = this.mentoring; mentoring = this.mentoring;
...@@ -193,14 +198,13 @@ function MRQBlock(runtime, element) { ...@@ -193,14 +198,13 @@ function MRQBlock(runtime, element) {
var questionnaireDOM = $('fieldset.questionnaire', element); var questionnaireDOM = $('fieldset.questionnaire', element);
var data = questionnaireDOM.data(); var data = questionnaireDOM.data();
var hide_results = (data.hide_results === 'True') ? true : false; var hide_results = (data.hide_results === 'True');
$.each(result.choices, function(index, choice) { $.each(result.choices, function(index, choice) {
var choiceInputDOM = $('.choice input[value='+choice.value+']', element); var choiceInputDOM = $('.choice input[value='+choice.value+']', element);
var choiceDOM = choiceInputDOM.closest('.choice'); var choiceDOM = choiceInputDOM.closest('.choice');
var choiceResultDOM = $('.choice-result', choiceDOM); var choiceResultDOM = $('.choice-result', choiceDOM);
var choiceTipsDOM = $('.choice-tips', choiceDOM); var choiceTipsDOM = $('.choice-tips', choiceDOM);
var choiceTipsCloseDOM;
/* show hint if checked or max_attempts is disabled */ /* show hint if checked or max_attempts is disabled */
if (!hide_results && if (!hide_results &&
...@@ -215,7 +219,6 @@ function MRQBlock(runtime, element) { ...@@ -215,7 +219,6 @@ function MRQBlock(runtime, element) {
mentoring.setContent(choiceTipsDOM, choice.tips); mentoring.setContent(choiceTipsDOM, choice.tips);
choiceTipsCloseDOM = $('.close', choiceTipsDOM);
choiceResultDOM.off('click').on('click', function() { choiceResultDOM.off('click').on('click', function() {
messageView.showMessage(choiceTipsDOM); messageView.showMessage(choiceTipsDOM);
}); });
......
...@@ -26,7 +26,11 @@ ...@@ -26,7 +26,11 @@
data-partially_correct_answer="{{ self.score.4|length }}" data-partially_correct_answer="{{ self.score.4|length }}"
data-max_attempts="{{ self.max_attempts }}" data-max_attempts="{{ self.max_attempts }}"
data-num_attempts="{{ self.num_attempts }}" data-num_attempts="{{ self.num_attempts }}"
data-assessment_message="{{ self.assessment_message }}"> data-extended_feedback="{%if self.extended_feedback %}True{% endif %}"
data-assessment_message="{{ self.assessment_message }}"
data-correct="{{ self.correct_json }}"
data-incorrect="{{ self.incorrect_json }}"
data-partial="{{ self.partial_json }}">
</div> </div>
<div class="assessment-messages"></div> <div class="assessment-messages"></div>
...@@ -49,4 +53,5 @@ ...@@ -49,4 +53,5 @@
{% endif %} {% endif %}
<div class="messages"></div> <div class="messages"></div>
</div> </div>
<div class="review-link"><a href="#">Review final grade</a></div>
</div> </div>
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
</p> </p>
<%= runDetails('correct') %> <%= runDetails('correct') %>
</div> </div>
<div class="clear"></div>
<span class="assessment-checkmark icon-2x checkmark-partially-correct icon-ok fa fa-check"></span> <span class="assessment-checkmark icon-2x checkmark-partially-correct icon-ok fa fa-check"></span>
<div class="results-section"> <div class="results-section">
<p> <p>
...@@ -35,6 +36,7 @@ ...@@ -35,6 +36,7 @@
</p> </p>
<%= runDetails('partial') %> <%= runDetails('partial') %>
</div> </div>
<div class="clear"></div>
<span class="assessment-checkmark icon-2x checkmark-incorrect icon-exclamation fa fa-exclamation"></span> <span class="assessment-checkmark icon-2x checkmark-incorrect icon-exclamation fa fa-exclamation"></span>
<div class="results-section"> <div class="results-section">
<p> <p>
...@@ -48,4 +50,5 @@ ...@@ -48,4 +50,5 @@
</p> </p>
<%= runDetails('incorrect') %> <%= runDetails('incorrect') %>
</div> </div>
<div class="clear"></div>
</script> </script>
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<% var q, last_question; %> <% var q, last_question; %>
<ul class="review-list <%= label %>-list"> <ul class="review-list <%= label %>-list">
<% for (var question in questions) {{ q = questions[question]; last_question = question == questions.length - 1; %> <% for (var question in questions) {{ q = questions[question]; last_question = question == questions.length - 1; %>
<li><a href="#" class="question-link" data-name="<%= q.id %>"><= gettext("Question {number}", {number: q.number}) =></a></li> <li><a href="#" class="question-link" data-step="<%= q.number %>"><%= _.template(gettext("Question {number}"), {number: q.number}, {interpolate: /\{(.+?)\}/g}) %></a></li>
<% }} %> <% }} %>
</ul> </ul>
</script> </script>
<problem-builder display_name="Mentoring Assessment Example" weight="1" mode="assessment" max_attempts="10"> <problem-builder display_name="Mentoring Assessment Example" weight="1" mode="assessment" max_attempts="2" extended_feedback="true">
<html_demo> <html_demo>
<p>This paragraph is shared between <strong>all</strong> questions.</p> <p>This paragraph is shared between <strong>all</strong> questions.</p>
<p>Please answer the questions below.</p> <p>Please answer the questions below.</p>
</html_demo> </html_demo>
<pb-answer name="goal" question="What is your goal?"> <pb-answer name="goal" question="What is your goal?" />
</pb-answer>
<pb-mcq name="mcq_1_1" question="Do you like this MCQ?" correct_choices="yes"> <pb-mcq name="mcq_1_1" question="Do you like this MCQ?" correct_choices='["yes"]'>
<pb-choice value="yes">Yes</pb-choice> <pb-choice value="yes">Yes</pb-choice>
<pb-choice value="maybenot">Maybe not</pb-choice> <pb-choice value="maybenot">Maybe not</pb-choice>
<pb-choice value="understand">I don't understand</pb-choice> <pb-choice value="understand">I don't understand</pb-choice>
<pb-tip values="yes">Great!</pb-tip> <pb-tip values='["yes"]'>Great!</pb-tip>
<pb-tip values="maybenot">Ah, damn.</pb-tip> <pb-tip values='["maybenot"]'>Ah, damn.</pb-tip>
<pb-tip values="understand"><div id="test-custom-html">Really?</div></pb-tip> <pb-tip values='["understand"]'><div id="test-custom-html">Really?</div></pb-tip>
</pb-mcq> </pb-mcq>
<pb-rating name="mcq_1_2" low="Not good at all" high="Extremely good" question="How much do you rate this MCQ?" correct_choices="4,5"> <pb-rating name="mcq_1_2" low="Not good at all" high="Extremely good" question="How much do you rate this MCQ?" correct_choices='["4","5"]'>
<pb-choice value="notwant">I don't want to rate it</pb-choice> <pb-choice value="notwant">I don't want to rate it</pb-choice>
<pb-tip values="4,5">I love good grades.</pb-tip> <pb-tip values='["4","5"]'>I love good grades.</pb-tip>
<pb-tip values="1,2,3">Will do better next time...</pb-tip> <pb-tip values='["1","2", "3"]'>Will do better next time...</pb-tip>
<pb-tip values="notwant">Your loss!</pb-tip> <pb-tip values='["notwant"]'>Your loss!</pb-tip>
</pb-rating> </pb-rating>
<pb-mrq name="mrq_1_1" question="What do you like in this MRQ?" required_choices="gracefulness,elegance,beauty"> <pb-mrq name="mrq_1_1" question="What do you like in this MRQ?" required_choices='["gracefulness","elegance","beauty"]'>
<pb-choice value="elegance">Its elegance</pb-choice> <pb-choice value="elegance">Its elegance</pb-choice>
<pb-choice value="beauty">Its beauty</pb-choice> <pb-choice value="beauty">Its beauty</pb-choice>
<pb-choice value="gracefulness">Its gracefulness</pb-choice> <pb-choice value="gracefulness">Its gracefulness</pb-choice>
<pb-choice value="bugs">Its bugs</pb-choice> <pb-choice value="bugs">Its bugs</pb-choice>
<pb-tip values="gracefulness">This MRQ is indeed very graceful</pb-tip> <pb-tip values='["gracefulness"]'>This MRQ is indeed very graceful</pb-tip>
<pb-tip values="elegance,beauty">This is something everyone has to like about this MRQ</pb-tip> <pb-tip values='["elegance","beauty"]'>This is something everyone has to like about this MRQ</pb-tip>
<pb-tip values="bugs">Nah, there aren't any!</pb-tip> <pb-tip values='["bugs"]'>Nah, there aren't any!</pb-tip>
</pb-mrq> </pb-mrq>
<pb-message type="on-assessment-review"> <pb-message type="on-assessment-review">
<html>Assessment additional feedback message text</html> <html>Assessment additional feedback message text</html>
</pb-message> </pb-message>
......
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