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
dd04ee8c
Commit
dd04ee8c
authored
Oct 15, 2015
by
Tim Krones
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update plots when navigating to next step to make sure they are always up-to-date.
parent
fe2b7258
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
164 additions
and
54 deletions
+164
-54
problem_builder/mentoring.py
+5
-0
problem_builder/plot.py
+52
-14
problem_builder/public/js/mentoring_with_steps.js
+1
-0
problem_builder/public/js/plot.js
+72
-30
problem_builder/public/js/step.js
+18
-0
problem_builder/step.py
+14
-8
problem_builder/templates/html/plot.html
+2
-2
No files found.
problem_builder/mentoring.py
View file @
dd04ee8c
...
@@ -1062,6 +1062,11 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
...
@@ -1062,6 +1062,11 @@ class MentoringWithExplicitStepsBlock(BaseMentoringBlock, StudioContainerWithNes
'active_step'
:
self
.
active_step
'active_step'
:
self
.
active_step
}
}
def
author_preview_view
(
self
,
context
):
context
=
context
.
copy
()
if
context
else
{}
context
[
'author_preview_view'
]
=
True
return
super
(
MentoringWithExplicitStepsBlock
,
self
)
.
author_preview_view
(
context
)
def
author_edit_view
(
self
,
context
):
def
author_edit_view
(
self
,
context
):
"""
"""
Add some HTML to the author view that allows authors to add child blocks.
Add some HTML to the author view that allows authors to add child blocks.
...
...
problem_builder/plot.py
View file @
dd04ee8c
...
@@ -44,6 +44,7 @@ def _(text):
...
@@ -44,6 +44,7 @@ def _(text):
@XBlock.needs
(
'i18n'
)
@XBlock.needs
(
'i18n'
)
@XBlock.wants
(
'user'
)
class
PlotBlock
(
StudioEditableXBlockMixin
,
StudioContainerWithNestedXBlocksMixin
,
XBlockWithPreviewMixin
,
XBlock
):
class
PlotBlock
(
StudioEditableXBlockMixin
,
StudioContainerWithNestedXBlocksMixin
,
XBlockWithPreviewMixin
,
XBlock
):
"""
"""
XBlock that displays plot that summarizes answers to scale questions.
XBlock that displays plot that summarizes answers to scale questions.
...
@@ -141,32 +142,56 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
...
@@ -141,32 +142,56 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
@property
@property
def
default_claims
(
self
):
def
default_claims
(
self
):
if
not
self
.
claims
:
if
not
self
.
claims
:
return
json
.
dumps
([])
return
[]
course_id
=
unicode
(
getattr
(
self
.
runtime
,
'course_id'
,
'course_id'
))
course_key
=
CourseKey
.
from_string
(
course_id
)
course_key_str
=
unicode
(
course_key
)
user_service
=
self
.
runtime
.
service
(
self
,
'user'
)
user
=
user_service
.
get_current_user
()
username
=
user
.
opt_attrs
.
get
(
'edx-platform.username'
)
anonymous_user_id
=
user_service
.
get_anonymous_user_id
(
username
,
course_id
)
mentoring_block
=
self
.
get_parent
()
.
get_parent
()
mentoring_block
=
self
.
get_parent
()
.
get_parent
()
question_ids
,
questions
=
mentoring_block
.
question_ids
,
mentoring_block
.
questions
claims
=
[]
claims
=
[]
for
line
in
self
.
claims
.
split
(
'
\n
'
):
for
line
in
self
.
claims
.
split
(
'
\n
'
):
claim
,
q1
,
q2
=
line
.
split
(
', '
)
claim
,
q1
,
q2
=
line
.
split
(
', '
)
r1
,
r2
=
None
,
None
r1
,
r2
=
None
,
None
for
step
in
mentoring_block
.
steps
:
for
question_id
,
question
in
zip
(
question_ids
,
questions
):
for
student_result
in
step
.
student_results
:
if
question
.
name
==
q1
:
child_name
,
child_result
=
student_result
r1
=
self
.
_default_response
(
course_key_str
,
question
,
question_id
,
anonymous_user_id
)
if
child_name
==
q1
:
if
question
.
name
==
q2
:
r1
=
child_result
[
'submission'
]
r2
=
self
.
_default_response
(
course_key_str
,
question
,
question_id
,
anonymous_user_id
)
if
child_name
==
q2
:
# Don't use "elif" here (would break cases in which q1 == q2)
r2
=
child_result
[
'submission'
]
if
r1
is
not
None
and
r2
is
not
None
:
break
if
r1
is
not
None
and
r2
is
not
None
:
if
r1
is
not
None
and
r2
is
not
None
:
break
break
claims
.
append
([
claim
,
r1
,
r2
])
claims
.
append
([
claim
,
r1
,
r2
])
return
json
.
dumps
(
claims
)
return
claims
def
_default_response
(
self
,
course_key_str
,
question
,
question_id
,
user_id
):
from
.tasks
import
_get_answer
# Import here to avoid circular dependency
# 1. Obtain block_type for question
question_type
=
question
.
scope_ids
.
block_type
# 2. Obtain submissions for question using course_key_str, block_id, block_type, user_id
student_dict
=
{
'student_id'
:
user_id
,
'course_id'
:
course_key_str
,
'item_id'
:
question_id
,
'item_type'
:
question_type
,
}
submissions
=
sub_api
.
get_submissions
(
student_dict
,
limit
=
1
)
# Gets latest submission
# 3. Extract response from latest submission for question
answer_cache
=
{}
for
submission
in
submissions
:
answer
=
_get_answer
(
question
,
submission
,
answer_cache
)
return
int
(
answer
)
@property
@property
def
average_claims
(
self
):
def
average_claims
(
self
):
if
not
self
.
claims
:
if
not
self
.
claims
:
return
json
.
dumps
([])
return
[]
course_id
=
unicode
(
getattr
(
self
.
runtime
,
'course_id'
,
'course_id'
))
course_id
=
unicode
(
getattr
(
self
.
runtime
,
'course_id'
,
'course_id'
))
course_key
=
CourseKey
.
from_string
(
course_id
)
course_key
=
CourseKey
.
from_string
(
course_id
)
...
@@ -187,14 +212,14 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
...
@@ -187,14 +212,14 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
break
break
claims
.
append
([
claim
,
r1
,
r2
])
claims
.
append
([
claim
,
r1
,
r2
])
return
json
.
dumps
(
claims
)
return
claims
def
_average_response
(
self
,
course_key_str
,
question
,
question_id
):
def
_average_response
(
self
,
course_key_str
,
question
,
question_id
):
from
.tasks
import
_get_answer
# Import here to avoid circular dependency
from
.tasks
import
_get_answer
# Import here to avoid circular dependency
# 1. Obtain block_type for question
# 1. Obtain block_type for question
question_type
=
question
.
scope_ids
.
block_type
question_type
=
question
.
scope_ids
.
block_type
# 2. Obtain submissions for question using course_key_str, block_id, block_type
# 2. Obtain submissions for question using course_key_str, block_id, block_type
submissions
=
sub_api
.
get_all_submissions
(
course_key_str
,
question_id
,
question_type
)
submissions
=
sub_api
.
get_all_submissions
(
course_key_str
,
question_id
,
question_type
)
# Gets latest submissions
# 3. Extract responses from submissions for question and sum them up
# 3. Extract responses from submissions for question and sum them up
answer_cache
=
{}
answer_cache
=
{}
response_total
=
0
response_total
=
0
...
@@ -207,6 +232,19 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
...
@@ -207,6 +232,19 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
if
num_submissions
:
if
num_submissions
:
return
response_total
/
float
(
num_submissions
)
return
response_total
/
float
(
num_submissions
)
def
default_claims_json
(
self
):
return
json
.
dumps
(
self
.
default_claims
)
def
average_claims_json
(
self
):
return
json
.
dumps
(
self
.
average_claims
)
@XBlock.json_handler
def
get_data
(
self
,
data
,
suffix
):
return
{
'default_claims'
:
self
.
default_claims
,
'average_claims'
:
self
.
average_claims
,
}
def
clean_studio_edits
(
self
,
data
):
def
clean_studio_edits
(
self
,
data
):
# FIXME: Use this to clean data.claims (remove leading/trailing whitespace, etc.)
# FIXME: Use this to clean data.claims (remove leading/trailing whitespace, etc.)
pass
pass
...
...
problem_builder/public/js/mentoring_with_steps.js
View file @
dd04ee8c
...
@@ -303,6 +303,7 @@ function MentoringWithStepsBlock(runtime, element) {
...
@@ -303,6 +303,7 @@ function MentoringWithStepsBlock(runtime, element) {
function
showActiveStep
()
{
function
showActiveStep
()
{
var
step
=
steps
[
activeStep
];
var
step
=
steps
[
activeStep
];
step
.
updatePlots
();
$
(
step
.
element
).
show
();
$
(
step
.
element
).
show
();
}
}
...
...
problem_builder/public/js/plot.js
View file @
dd04ee8c
...
@@ -57,7 +57,6 @@ function PlotBlock(runtime, element) {
...
@@ -57,7 +57,6 @@ function PlotBlock(runtime, element) {
var
defaultClaims
=
$
(
'.plot-default'
,
element
).
data
(
'claims'
);
var
defaultClaims
=
$
(
'.plot-default'
,
element
).
data
(
'claims'
);
var
averageClaims
=
$
(
'.plot-average'
,
element
).
data
(
'claims'
);
var
averageClaims
=
$
(
'.plot-average'
,
element
).
data
(
'claims'
);
// Colors
// Colors
var
defaultColor
=
$
(
'.plot-default'
,
element
).
data
(
'point-color'
);
var
defaultColor
=
$
(
'.plot-default'
,
element
).
data
(
'point-color'
);
...
@@ -75,48 +74,60 @@ function PlotBlock(runtime, element) {
...
@@ -75,48 +74,60 @@ function PlotBlock(runtime, element) {
var
defaultButton
=
$
(
'.plot-default'
,
element
);
var
defaultButton
=
$
(
'.plot-default'
,
element
);
var
averageButton
=
$
(
'.plot-average'
,
element
);
var
averageButton
=
$
(
'.plot-average'
,
element
);
function
toggleOverlay
(
claims
,
color
,
klass
)
{
function
toggleOverlay
(
claims
,
color
,
klass
,
refresh
)
{
var
selector
=
"."
+
klass
;
var
selector
=
"."
+
klass
;
var
selection
=
svgContainer
.
selectAll
(
selector
);
var
selection
=
svgContainer
.
selectAll
(
selector
);
if
(
selection
.
empty
())
{
if
(
selection
.
empty
())
{
svgContainer
.
selectAll
(
selector
)
showOverlay
(
selection
,
claims
,
color
,
klass
);
.
data
(
claims
)
.
enter
()
.
append
(
"circle"
)
.
attr
(
"class"
,
klass
)
.
attr
(
"title"
,
function
(
d
)
{
return
d
[
0
]
+
": "
+
d
[
1
]
+
", "
+
d
[
2
];
})
.
attr
(
"cx"
,
function
(
d
)
{
return
xScale
(
d
[
1
]);
})
.
attr
(
"cy"
,
function
(
d
)
{
return
yScale
(
d
[
2
]);
})
.
attr
(
"r"
,
5
)
.
style
(
"fill"
,
color
);
}
else
{
}
else
{
selection
.
remove
();
hideOverlay
(
selection
);
if
(
refresh
)
{
toggleOverlay
(
claims
,
color
,
klass
);
}
}
}
}
}
function
toggleBorderColor
(
button
,
color
)
{
function
showOverlay
(
selection
,
claims
,
color
,
klass
)
{
selection
.
data
(
claims
)
.
enter
()
.
append
(
"circle"
)
.
attr
(
"class"
,
klass
)
.
attr
(
"title"
,
function
(
d
)
{
return
d
[
0
]
+
": "
+
d
[
1
]
+
", "
+
d
[
2
];
})
.
attr
(
"cx"
,
function
(
d
)
{
return
xScale
(
d
[
1
]);
})
.
attr
(
"cy"
,
function
(
d
)
{
return
yScale
(
d
[
2
]);
})
.
attr
(
"r"
,
5
)
.
style
(
"fill"
,
color
);
}
function
hideOverlay
(
selection
)
{
selection
.
remove
();
}
function
toggleBorderColor
(
button
,
color
,
refresh
)
{
var
$button
=
$
(
button
);
var
$button
=
$
(
button
);
var
overlayOn
=
$button
.
data
(
"overlay-on"
);
var
overlayOn
=
$button
.
data
(
"overlay-on"
);
if
(
overlayOn
)
{
if
(
overlayOn
&&
!
refresh
)
{
$button
.
css
(
"border-color"
,
"rgb(237, 237, 237)"
);
// Default color: grey
$button
.
css
(
"border-color"
,
"rgb(237, 237, 237)"
);
// Default color: grey
$button
.
data
(
"overlay-on"
,
false
);
}
else
{
}
else
{
$button
.
css
(
"border-color"
,
color
);
$button
.
css
(
"border-color"
,
color
);
$button
.
data
(
"overlay-on"
,
true
);
}
}
$button
.
data
(
"overlay-on"
,
!
overlayOn
);
}
}
defaultButton
.
on
(
'click'
,
function
()
{
defaultButton
.
on
(
'click'
,
function
(
event
,
refresh
)
{
toggleOverlay
(
defaultClaims
,
defaultColor
,
'claim-default'
);
toggleOverlay
(
defaultClaims
,
defaultColor
,
'claim-default'
,
refresh
);
toggleBorderColor
(
this
,
defaultColor
);
toggleBorderColor
(
this
,
defaultColor
,
refresh
);
});
});
averageButton
.
on
(
'click'
,
function
()
{
averageButton
.
on
(
'click'
,
function
(
event
)
{
toggleOverlay
(
averageClaims
,
averageColor
,
'claim-average'
);
toggleOverlay
(
averageClaims
,
averageColor
,
'claim-average'
);
toggleBorderColor
(
this
,
averageColor
);
toggleBorderColor
(
this
,
averageColor
);
});
});
...
@@ -163,12 +174,43 @@ function PlotBlock(runtime, element) {
...
@@ -163,12 +174,43 @@ function PlotBlock(runtime, element) {
toggleQuadrantLabels
();
toggleQuadrantLabels
();
});
});
// Show default overlay initially
defaultButton
.
trigger
(
'click'
);
// Quadrant labels are off initially; color of button for toggling them should reflect this
// Quadrant labels are off initially; color of button for toggling them should reflect this
quadrantsButton
.
css
(
"border-color"
,
"red"
);
quadrantsButton
.
css
(
"border-color"
,
"red"
);
// Functions that can be called from the outside
var
dataXHR
;
return
{
update
:
function
()
{
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'get_data'
);
if
(
dataXHR
)
{
dataXHR
.
abort
();
}
dataXHR
=
$
.
post
(
handlerUrl
,
JSON
.
stringify
({}))
.
success
(
function
(
response
)
{
defaultClaims
=
response
.
default_claims
;
averageClaims
=
response
.
average_claims
;
// Default overlay should be visible initially.
// Might still be visible from a previous attempt;
// in that case, we refresh it:
defaultButton
.
trigger
(
'click'
,
'refresh'
);
// Average overlay should be hidden initially.
// This is the default when (re-)loading the page from scratch.
// However, the overlay might still be visible from a previous attempt;
// in that case, we hide it:
var
selection
=
svgContainer
.
selectAll
(
'.claim-average'
);
if
(
!
selection
.
empty
())
{
hideOverlay
(
selection
);
toggleBorderColor
(
averageButton
,
averageColor
);
}
});
}
};
}
}
problem_builder/public/js/step.js
View file @
dd04ee8c
function
MentoringStepBlock
(
runtime
,
element
)
{
function
MentoringStepBlock
(
runtime
,
element
)
{
var
children
=
runtime
.
children
(
element
);
var
children
=
runtime
.
children
(
element
);
var
plots
=
[];
for
(
var
i
=
0
;
i
<
children
.
length
;
i
++
)
{
var
child
=
children
[
i
];
var
blockType
=
$
(
child
.
element
).
data
(
'block-type'
);
if
(
blockType
===
'sb-plot'
)
{
plots
.
push
(
child
);
}
}
var
submitXHR
,
resultsXHR
;
var
submitXHR
,
resultsXHR
;
function
callIfExists
(
obj
,
fn
)
{
function
callIfExists
(
obj
,
fn
)
{
...
@@ -89,6 +98,15 @@ function MentoringStepBlock(runtime, element) {
...
@@ -89,6 +98,15 @@ function MentoringStepBlock(runtime, element) {
hasQuestion
:
function
()
{
hasQuestion
:
function
()
{
return
$
(
'.sb-step'
,
element
).
data
(
'has-question'
)
return
$
(
'.sb-step'
,
element
).
data
(
'has-question'
)
},
updatePlots
:
function
()
{
if
(
plots
)
{
for
(
var
i
=
0
;
i
<
plots
.
length
;
i
++
)
{
var
plot
=
plots
[
i
];
plot
.
update
();
}
}
}
}
};
};
...
...
problem_builder/step.py
View file @
dd04ee8c
...
@@ -225,16 +225,14 @@ class MentoringStepBlock(
...
@@ -225,16 +225,14 @@ class MentoringStepBlock(
fragment
.
initialize_js
(
'StepEdit'
)
fragment
.
initialize_js
(
'StepEdit'
)
return
fragment
return
fragment
def
student_view
(
self
,
context
=
None
):
""" Student View """
return
self
.
_render_view
(
context
,
'student_view'
)
def
mentoring_view
(
self
,
context
=
None
):
def
mentoring_view
(
self
,
context
=
None
):
""" Mentoring View """
""" Mentoring View """
return
self
.
_render_view
(
context
,
'mentoring_view'
)
return
self
.
_render_view
(
context
,
'mentoring_view'
)
def
_render_view
(
self
,
context
,
view
):
def
_render_view
(
self
,
context
,
view
):
""" Actually renders a view """
""" Actually renders a view """
rendering_for_studio
=
context
.
get
(
'author_preview_view'
)
fragment
=
Fragment
()
fragment
=
Fragment
()
child_contents
=
[]
child_contents
=
[]
...
@@ -243,10 +241,18 @@ class MentoringStepBlock(
...
@@ -243,10 +241,18 @@ class MentoringStepBlock(
if
child
is
None
:
# child should not be None but it can happen due to bugs or permission issues
if
child
is
None
:
# child should not be None but it can happen due to bugs or permission issues
child_contents
.
append
(
u"<p>[{}]</p>"
.
format
(
self
.
_
(
u"Error: Unable to load child component."
)))
child_contents
.
append
(
u"<p>[{}]</p>"
.
format
(
self
.
_
(
u"Error: Unable to load child component."
)))
else
:
else
:
child_fragment
=
self
.
_render_child_fragment
(
child
,
context
,
view
)
if
rendering_for_studio
and
isinstance
(
child
,
PlotBlock
):
# Don't use view to render plot blocks in Studio.
fragment
.
add_frag_resources
(
child_fragment
)
# This is necessary because:
child_contents
.
append
(
child_fragment
.
content
)
# - student_view of plot block uses submissions API to retrieve results,
# which causes "SubmissionRequestError" in Studio.
# - author_preview_view does not supply JS code for plot that JS code for step depends on
# (step calls "update" on plot to get latest data during rendering).
child_contents
.
append
(
u"<p>{}</p>"
.
format
(
child
.
display_name
))
else
:
child_fragment
=
self
.
_render_child_fragment
(
child
,
context
,
view
)
fragment
.
add_frag_resources
(
child_fragment
)
child_contents
.
append
(
child_fragment
.
content
)
fragment
.
add_content
(
loader
.
render_template
(
'templates/html/step.html'
,
{
fragment
.
add_content
(
loader
.
render_template
(
'templates/html/step.html'
,
{
'self'
:
self
,
'self'
:
self
,
...
...
problem_builder/templates/html/plot.html
View file @
dd04ee8c
...
@@ -18,14 +18,14 @@
...
@@ -18,14 +18,14 @@
<input
type=
"button"
<input
type=
"button"
class=
"plot-default"
class=
"plot-default"
data-claims=
"{{ self.default_claims }}"
data-claims=
"{{ self.default_claims
_json
}}"
data-point-color=
"{{ self.point_color_default }}"
data-point-color=
"{{ self.point_color_default }}"
data-overlay-on=
"false"
data-overlay-on=
"false"
value=
"{{ self.plot_label }}"
value=
"{{ self.plot_label }}"
/>
/>
<input
type=
"button"
<input
type=
"button"
class=
"plot-average"
class=
"plot-average"
data-claims=
"{{ self.average_claims }}"
data-claims=
"{{ self.average_claims
_json
}}"
data-point-color=
"{{ self.point_color_average }}"
data-point-color=
"{{ self.point_color_average }}"
data-overlay-on=
"false"
data-overlay-on=
"false"
value=
"Average"
value=
"Average"
...
...
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