Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
problem-builder
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
problem-builder
Commits
b6927140
Commit
b6927140
authored
Oct 23, 2015
by
Braden MacDonald
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix: delay between submitting answer and next step being clickable
parent
28e6b62c
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
106 additions
and
150 deletions
+106
-150
problem_builder/mentoring.py
+53
-23
problem_builder/public/js/mentoring_with_steps.js
+36
-63
problem_builder/public/js/step.js
+12
-16
problem_builder/step.py
+3
-2
problem_builder/templates/html/mentoring_with_steps.html
+1
-1
problem_builder/tests/unit/test_step_builder.py
+0
-33
problem_builder/tests/unit/utils.py
+1
-12
No files found.
problem_builder/mentoring.py
View file @
b6927140
...
@@ -866,6 +866,27 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
...
@@ -866,6 +866,27 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
"""
"""
return
[
self
.
runtime
.
get_block
(
question_id
)
for
question_id
in
self
.
question_ids
]
return
[
self
.
runtime
.
get_block
(
question_id
)
for
question_id
in
self
.
question_ids
]
@property
def
active_step_safe
(
self
):
"""
Get self.active_step and double-check that it is a valid value.
The stored value could be invalid if this block has been edited and new steps were
added/deleted.
"""
active_step
=
self
.
active_step
if
active_step
>=
0
and
active_step
<
len
(
self
.
step_ids
):
return
active_step
if
active_step
==
-
1
and
self
.
has_review_step
:
return
active_step
# -1 indicates the review step
return
0
def
get_active_step
(
self
):
""" Get the active step as an instantiated XBlock """
block
=
self
.
runtime
.
get_block
(
self
.
step_ids
[
self
.
active_step_safe
])
if
block
is
None
:
log
.
error
(
"Unable to load step builder step child
%
s"
,
self
.
step_ids
[
self
.
active_step_safe
])
return
block
@lazy
@lazy
def
step_ids
(
self
):
def
step_ids
(
self
):
"""
"""
...
@@ -1007,36 +1028,45 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
...
@@ -1007,36 +1028,45 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
]
]
@XBlock.json_handler
@XBlock.json_handler
def
update_active_step
(
self
,
new_value
,
suffix
=
''
):
def
submit
(
self
,
data
,
suffix
=
None
):
"""
Called this when the user has submitted the answer[s] for the current step.
"""
# First verify that active_step is correct:
if
data
.
get
(
"active_step"
)
!=
self
.
active_step_safe
:
raise
JsonHandlerError
(
400
,
"Invalid Step. Refresh the page and try again."
)
# The step child will process the data:
step_block
=
self
.
get_active_step
()
if
not
step_block
:
raise
JsonHandlerError
(
500
,
"Unable to load the current step block."
)
response_data
=
step_block
.
submit
(
data
)
# Update the active step:
new_value
=
self
.
active_step_safe
+
1
if
new_value
<
len
(
self
.
step_ids
):
if
new_value
<
len
(
self
.
step_ids
):
self
.
active_step
=
new_value
self
.
active_step
=
new_value
elif
new_value
==
len
(
self
.
step_ids
):
elif
new_value
==
len
(
self
.
step_ids
):
# The user just completed the final step.
if
self
.
has_review_step
:
if
self
.
has_review_step
:
self
.
active_step
=
-
1
self
.
active_step
=
-
1
return
{
# Update the number of attempts, if necessary:
'active_step'
:
self
.
active_step
if
self
.
num_attempts
<
self
.
max_attempts
:
}
self
.
num_attempts
+=
1
response_data
[
'num_attempts'
]
=
self
.
num_attempts
@XBlock.json_handler
# And publish the score:
def
update_num_attempts
(
self
,
data
,
suffix
=
''
):
score
=
self
.
score
if
self
.
num_attempts
<
self
.
max_attempts
:
grade_data
=
{
self
.
num_attempts
+=
1
'value'
:
score
.
raw
,
return
{
'max_value'
:
1
,
'num_attempts'
:
self
.
num_attempts
}
}
self
.
runtime
.
publish
(
self
,
'grade'
,
grade_data
)
response_data
[
'grade_data'
]
=
self
.
get_grade
()
@XBlock.json_handler
response_data
[
'active_step'
]
=
self
.
active_step
def
publish_attempt
(
self
,
data
,
suffix
):
return
response_data
score
=
self
.
score
grade_data
=
{
'value'
:
score
.
raw
,
'max_value'
:
1
,
}
self
.
runtime
.
publish
(
self
,
'grade'
,
grade_data
)
return
{}
@XBlock.json_handler
def
get_grade
(
self
,
data
=
None
,
suffix
=
None
):
def
get_grade
(
self
,
data
,
suffix
):
score
=
self
.
score
score
=
self
.
score
return
{
return
{
'score'
:
score
.
percentage
,
'score'
:
score
.
percentage
,
...
...
problem_builder/public/js/mentoring_with_steps.js
View file @
b6927140
...
@@ -55,60 +55,22 @@ function MentoringWithStepsBlock(runtime, element) {
...
@@ -55,60 +55,22 @@ function MentoringWithStepsBlock(runtime, element) {
}
else
{
}
else
{
checkmark
.
addClass
(
'checkmark-incorrect icon-exclamation fa-exclamation'
);
checkmark
.
addClass
(
'checkmark-incorrect icon-exclamation fa-exclamation'
);
}
}
}
var
step
=
steps
[
activeStep
];
if
(
typeof
step
.
showFeedback
==
'function'
)
{
function
postUpdateStep
(
response
)
{
step
.
showFeedback
(
response
);
activeStep
=
response
.
active_step
;
if
(
activeStep
===
-
1
)
{
updateNumAttempts
();
}
else
{
updateControls
();
}
}
}
}
function
handleResults
(
response
)
{
function
updateGrade
(
grade_data
)
{
showFeedback
(
response
);
gradeDOM
.
data
(
'score'
,
grade_data
.
score
);
gradeDOM
.
data
(
'correct_answer'
,
grade_data
.
correct_answers
);
// Update active step:
gradeDOM
.
data
(
'incorrect_answer'
,
grade_data
.
incorrect_answers
);
// If we end up at the review step, proceed with updating the number of attempts used.
gradeDOM
.
data
(
'partially_correct_answer'
,
grade_data
.
partially_correct_answers
);
// Otherwise, get UI ready for showing next step.
gradeDOM
.
data
(
'correct'
,
grade_data
.
correct
);
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'update_active_step'
);
gradeDOM
.
data
(
'incorrect'
,
grade_data
.
incorrect
);
$
.
post
(
handlerUrl
,
JSON
.
stringify
(
activeStep
+
1
))
gradeDOM
.
data
(
'partial'
,
grade_data
.
partial
);
.
success
(
postUpdateStep
);
gradeDOM
.
data
(
'assessment_review_tips'
,
grade_data
.
assessment_review_tips
);
}
updateReviewStep
(
grade_data
);
function
updateNumAttempts
()
{
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'update_num_attempts'
);
$
.
post
(
handlerUrl
,
JSON
.
stringify
({}))
.
success
(
function
(
response
)
{
attemptsDOM
.
data
(
'num_attempts'
,
response
.
num_attempts
);
publishAttempt
();
});
}
function
publishAttempt
()
{
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'publish_attempt'
);
$
.
post
(
handlerUrl
,
JSON
.
stringify
({}))
.
success
(
function
(
response
)
{
// Now that relevant info is up-to-date and attempt has been published, get the latest grade
updateGrade
();
});
}
function
updateGrade
()
{
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'get_grade'
);
$
.
post
(
handlerUrl
,
JSON
.
stringify
({}))
.
success
(
function
(
response
)
{
gradeDOM
.
data
(
'score'
,
response
.
score
);
gradeDOM
.
data
(
'correct_answer'
,
response
.
correct_answers
);
gradeDOM
.
data
(
'incorrect_answer'
,
response
.
incorrect_answers
);
gradeDOM
.
data
(
'partially_correct_answer'
,
response
.
partially_correct_answers
);
gradeDOM
.
data
(
'correct'
,
response
.
correct
);
gradeDOM
.
data
(
'incorrect'
,
response
.
incorrect
);
gradeDOM
.
data
(
'partial'
,
response
.
partial
);
gradeDOM
.
data
(
'assessment_review_tips'
,
response
.
assessment_review_tips
);
updateReviewStep
(
response
);
});
}
}
function
updateReviewStep
(
response
)
{
function
updateReviewStep
(
response
)
{
...
@@ -136,16 +98,27 @@ function MentoringWithStepsBlock(runtime, element) {
...
@@ -136,16 +98,27 @@ function MentoringWithStepsBlock(runtime, element) {
}
}
function
submit
()
{
function
submit
()
{
// We do not handle submissions at this level, so just forward to "submit" method of active step
submitDOM
.
attr
(
'disabled'
,
'disabled'
);
// Disable the button until the results load.
var
step
=
steps
[
activeStep
];
var
submitUrl
=
runtime
.
handlerUrl
(
element
,
'submit'
);
step
.
submit
(
handleResults
);
}
var
hasQuestion
=
steps
[
activeStep
].
hasQuestion
();
var
data
=
steps
[
activeStep
].
getSubmitData
();
function
markRead
()
{
data
[
"active_step"
]
=
activeStep
;
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'update_active_step'
);
$
.
post
(
submitUrl
,
JSON
.
stringify
(
data
)).
success
(
function
(
response
)
{
$
.
post
(
handlerUrl
,
JSON
.
stringify
(
activeStep
+
1
)).
success
(
function
(
response
)
{
showFeedback
(
response
);
postUpdateStep
(
response
);
activeStep
=
response
.
active_step
;
updateDisplay
();
if
(
activeStep
===
-
1
)
{
// We are now showing the review step / end
// Update the number of attempts.
attemptsDOM
.
data
(
'num_attempts'
,
response
.
num_attempts
);
updateGrade
(
response
.
grade_data
);
}
else
if
(
!
hasQuestion
)
{
// This was a step with no questions, so proceed to the next step / review:
updateDisplay
();
}
else
{
// Enable the Next button so users can proceed.
updateControls
();
}
});
});
}
}
...
@@ -332,11 +305,11 @@ function MentoringWithStepsBlock(runtime, element) {
...
@@ -332,11 +305,11 @@ function MentoringWithStepsBlock(runtime, element) {
if
(
isLastStep
()
&&
step
.
hasQuestion
())
{
if
(
isLastStep
()
&&
step
.
hasQuestion
())
{
nextDOM
.
hide
();
nextDOM
.
hide
();
}
else
if
(
isLastStep
())
{
}
else
if
(
isLastStep
())
{
reviewDOM
.
one
(
'click'
,
markRead
);
reviewDOM
.
one
(
'click'
,
submit
);
reviewDOM
.
removeAttr
(
'disabled'
);
reviewDOM
.
removeAttr
(
'disabled'
);
nextDOM
.
hide
()
nextDOM
.
hide
()
}
else
if
(
!
step
.
hasQuestion
())
{
}
else
if
(
!
step
.
hasQuestion
())
{
nextDOM
.
one
(
'click'
,
markRead
);
nextDOM
.
one
(
'click'
,
submit
);
}
}
if
(
step
.
hasQuestion
())
{
if
(
step
.
hasQuestion
())
{
submitDOM
.
show
();
submitDOM
.
show
();
...
...
problem_builder/public/js/step.js
View file @
b6927140
...
@@ -44,29 +44,25 @@ function MentoringStepBlock(runtime, element) {
...
@@ -44,29 +44,25 @@ function MentoringStepBlock(runtime, element) {
return
is_valid
;
return
is_valid
;
},
},
submit
:
function
(
resultHandler
)
{
getSubmitData
:
function
()
{
var
handler_name
=
'submit'
;
var
data
=
{};
var
data
=
{};
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
)
{
if
(
child
&&
child
.
name
!==
undefined
)
{
data
[
child
.
name
.
toString
()]
=
callIfExists
(
child
,
handler_name
);
data
[
child
.
name
.
toString
()]
=
callIfExists
(
child
,
"submit"
);
}
}
}
}
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
handler_name
);
return
data
;
if
(
submitXHR
)
{
},
submitXHR
.
abort
();
}
showFeedback
:
function
(
response
)
{
submitXHR
=
$
.
post
(
handlerUrl
,
JSON
.
stringify
(
data
))
// Called when user has just submitted an answer or is reviewing their answer durign extended feedback.
.
success
(
function
(
response
)
{
if
(
message
.
length
)
{
resultHandler
(
response
);
message
.
fadeIn
();
if
(
message
.
length
)
{
$
(
document
).
click
(
function
()
{
message
.
fadeIn
();
message
.
fadeOut
();
$
(
document
).
click
(
function
()
{
message
.
fadeOut
();
});
}
});
});
}
},
},
getResults
:
function
(
resultHandler
)
{
getResults
:
function
(
resultHandler
)
{
...
...
problem_builder/step.py
View file @
b6927140
...
@@ -160,8 +160,8 @@ class MentoringStepBlock(
...
@@ -160,8 +160,8 @@ class MentoringStepBlock(
def
has_question
(
self
):
def
has_question
(
self
):
return
any
(
getattr
(
child
,
'answerable'
,
False
)
for
child
in
self
.
steps
)
return
any
(
getattr
(
child
,
'answerable'
,
False
)
for
child
in
self
.
steps
)
@XBlock.json_handler
def
submit
(
self
,
submissions
):
def
submit
(
self
,
submissions
,
suffix
=
''
):
""" Handle a student submission. This is called by the parent XBlock. """
log
.
info
(
u'Received submissions: {}'
.
format
(
submissions
))
log
.
info
(
u'Received submissions: {}'
.
format
(
submissions
))
# Submit child blocks (questions) and gather results
# Submit child blocks (questions) and gather results
...
@@ -177,6 +177,7 @@ class MentoringStepBlock(
...
@@ -177,6 +177,7 @@ class MentoringStepBlock(
self
.
reset
()
self
.
reset
()
for
result
in
submit_results
:
for
result
in
submit_results
:
self
.
student_results
.
append
(
result
)
self
.
student_results
.
append
(
result
)
self
.
save
()
return
{
return
{
'message'
:
'Success!'
,
'message'
:
'Success!'
,
...
...
problem_builder/templates/html/mentoring_with_steps.html
View file @
b6927140
{% load i18n %}
{% load i18n %}
<div
class=
"mentoring themed-xblock"
data-active-step=
"{{ self.active_step }}"
>
<div
class=
"mentoring themed-xblock"
data-active-step=
"{{ self.active_step
_safe
}}"
>
{% if show_title and title %}
{% if show_title and title %}
<div
class=
"title"
>
<div
class=
"title"
>
...
...
problem_builder/tests/unit/test_step_builder.py
deleted
100644 → 0
View file @
28e6b62c
import
unittest
import
ddt
from
mock
import
Mock
from
problem_builder.mentoring
import
MentoringWithExplicitStepsBlock
from
.utils
import
ScoresTestMixin
,
instantiate_block
@ddt.ddt
class
TestStepBuilder
(
ScoresTestMixin
,
unittest
.
TestCase
):
""" Unit tests for Step Builder (MentoringWithExplicitStepsBlock) """
def
test_scores
(
self
):
"""
Test that scores are emitted correctly.
"""
# Submit an empty block - score should be 0:
block
=
instantiate_block
(
MentoringWithExplicitStepsBlock
)
with
self
.
expect_score_event
(
block
,
score
=
0.0
,
max_score
=
1.0
):
request
=
Mock
(
method
=
"POST"
,
body
=
"{}"
)
block
.
publish_attempt
(
request
,
suffix
=
None
)
# Mock a block to contain an MCQ question, then submit it. Score should be 1:
block
=
instantiate_block
(
MentoringWithExplicitStepsBlock
)
block
.
questions
=
[
Mock
(
weight
=
1.0
)]
block
.
questions
[
0
]
.
name
=
'mcq1'
block
.
steps
=
[
Mock
(
student_results
=
[(
'mcq1'
,
{
'score'
:
1
,
'status'
:
'correct'
})]
)]
block
.
answer_mapper
=
lambda
_status
:
None
with
self
.
expect_score_event
(
block
,
score
=
1.0
,
max_score
=
1.0
):
request
=
Mock
(
method
=
"POST"
,
body
=
"{}"
)
block
.
publish_attempt
(
request
,
suffix
=
None
)
problem_builder/tests/unit/utils.py
View file @
b6927140
"""
"""
Helper methods for testing Problem Builder / Step Builder blocks
Helper methods for testing Problem Builder / Step Builder blocks
"""
"""
from
contextlib
import
contextmanager
from
mock
import
MagicMock
,
Mock
from
mock
import
MagicMock
,
Mock
,
patch
from
xblock.field_data
import
DictFieldData
from
xblock.field_data
import
DictFieldData
...
@@ -20,16 +19,6 @@ class ScoresTestMixin(object):
...
@@ -20,16 +19,6 @@ class ScoresTestMixin(object):
self
.
assertEqual
(
block
.
weight
,
1.0
)
# Default weight should be 1
self
.
assertEqual
(
block
.
weight
,
1.0
)
# Default weight should be 1
self
.
assertIsInstance
(
block
.
max_score
(),
(
int
,
float
))
self
.
assertIsInstance
(
block
.
max_score
(),
(
int
,
float
))
@contextmanager
def
expect_score_event
(
self
,
block
,
score
,
max_score
):
"""
Context manager. Expect that the given block instance will publish the given score.
"""
with
patch
.
object
(
block
.
runtime
,
'publish'
)
as
mocked_publish
:
yield
mocked_publish
.
assert_called_once_with
(
block
,
'grade'
,
{
'value'
:
score
,
'max_value'
:
max_score
})
def
instantiate_block
(
cls
,
fields
=
None
):
def
instantiate_block
(
cls
,
fields
=
None
):
"""
"""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment