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
'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
):
"""
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):
@XBlock.needs
(
'i18n'
)
@XBlock.wants
(
'user'
)
class
PlotBlock
(
StudioEditableXBlockMixin
,
StudioContainerWithNestedXBlocksMixin
,
XBlockWithPreviewMixin
,
XBlock
):
"""
XBlock that displays plot that summarizes answers to scale questions.
...
...
@@ -141,32 +142,56 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
@property
def
default_claims
(
self
):
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
()
question_ids
,
questions
=
mentoring_block
.
question_ids
,
mentoring_block
.
questions
claims
=
[]
for
line
in
self
.
claims
.
split
(
'
\n
'
):
claim
,
q1
,
q2
=
line
.
split
(
', '
)
r1
,
r2
=
None
,
None
for
step
in
mentoring_block
.
steps
:
for
student_result
in
step
.
student_results
:
child_name
,
child_result
=
student_result
if
child_name
==
q1
:
r1
=
child_result
[
'submission'
]
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
for
question_id
,
question
in
zip
(
question_ids
,
questions
):
if
question
.
name
==
q1
:
r1
=
self
.
_default_response
(
course_key_str
,
question
,
question_id
,
anonymous_user_id
)
if
question
.
name
==
q2
:
r2
=
self
.
_default_response
(
course_key_str
,
question
,
question_id
,
anonymous_user_id
)
if
r1
is
not
None
and
r2
is
not
None
:
break
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
def
average_claims
(
self
):
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
)
...
...
@@ -187,14 +212,14 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
break
claims
.
append
([
claim
,
r1
,
r2
])
return
json
.
dumps
(
claims
)
return
claims
def
_average_response
(
self
,
course_key_str
,
question
,
question_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
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
answer_cache
=
{}
response_total
=
0
...
...
@@ -207,6 +232,19 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
if
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
):
# FIXME: Use this to clean data.claims (remove leading/trailing whitespace, etc.)
pass
...
...
problem_builder/public/js/mentoring_with_steps.js
View file @
dd04ee8c
...
...
@@ -303,6 +303,7 @@ function MentoringWithStepsBlock(runtime, element) {
function
showActiveStep
()
{
var
step
=
steps
[
activeStep
];
step
.
updatePlots
();
$
(
step
.
element
).
show
();
}
...
...
problem_builder/public/js/plot.js
View file @
dd04ee8c
...
...
@@ -57,7 +57,6 @@ function PlotBlock(runtime, element) {
var
defaultClaims
=
$
(
'.plot-default'
,
element
).
data
(
'claims'
);
var
averageClaims
=
$
(
'.plot-average'
,
element
).
data
(
'claims'
);
// Colors
var
defaultColor
=
$
(
'.plot-default'
,
element
).
data
(
'point-color'
);
...
...
@@ -75,48 +74,60 @@ function PlotBlock(runtime, element) {
var
defaultButton
=
$
(
'.plot-default'
,
element
);
var
averageButton
=
$
(
'.plot-average'
,
element
);
function
toggleOverlay
(
claims
,
color
,
klass
)
{
function
toggleOverlay
(
claims
,
color
,
klass
,
refresh
)
{
var
selector
=
"."
+
klass
;
var
selection
=
svgContainer
.
selectAll
(
selector
);
if
(
selection
.
empty
())
{
svgContainer
.
selectAll
(
selector
)
.
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
);
showOverlay
(
selection
,
claims
,
color
,
klass
);
}
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
overlayOn
=
$button
.
data
(
"overlay-on"
);
if
(
overlayOn
)
{
if
(
overlayOn
&&
!
refresh
)
{
$button
.
css
(
"border-color"
,
"rgb(237, 237, 237)"
);
// Default color: grey
$button
.
data
(
"overlay-on"
,
false
);
}
else
{
$button
.
css
(
"border-color"
,
color
);
$button
.
data
(
"overlay-on"
,
true
);
}
$button
.
data
(
"overlay-on"
,
!
overlayOn
);
}
defaultButton
.
on
(
'click'
,
function
()
{
toggleOverlay
(
defaultClaims
,
defaultColor
,
'claim-default'
);
toggleBorderColor
(
this
,
defaultColor
);
defaultButton
.
on
(
'click'
,
function
(
event
,
refresh
)
{
toggleOverlay
(
defaultClaims
,
defaultColor
,
'claim-default'
,
refresh
);
toggleBorderColor
(
this
,
defaultColor
,
refresh
);
});
averageButton
.
on
(
'click'
,
function
()
{
averageButton
.
on
(
'click'
,
function
(
event
)
{
toggleOverlay
(
averageClaims
,
averageColor
,
'claim-average'
);
toggleBorderColor
(
this
,
averageColor
);
});
...
...
@@ -163,12 +174,43 @@ function PlotBlock(runtime, element) {
toggleQuadrantLabels
();
});
// Show default overlay initially
defaultButton
.
trigger
(
'click'
);
// Quadrant labels are off initially; color of button for toggling them should reflect this
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
)
{
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
;
function
callIfExists
(
obj
,
fn
)
{
...
...
@@ -89,6 +98,15 @@ function MentoringStepBlock(runtime, element) {
hasQuestion
:
function
()
{
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(
fragment
.
initialize_js
(
'StepEdit'
)
return
fragment
def
student_view
(
self
,
context
=
None
):
""" Student View """
return
self
.
_render_view
(
context
,
'student_view'
)
def
mentoring_view
(
self
,
context
=
None
):
""" Mentoring View """
return
self
.
_render_view
(
context
,
'mentoring_view'
)
def
_render_view
(
self
,
context
,
view
):
""" Actually renders a view """
rendering_for_studio
=
context
.
get
(
'author_preview_view'
)
fragment
=
Fragment
()
child_contents
=
[]
...
...
@@ -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
child_contents
.
append
(
u"<p>[{}]</p>"
.
format
(
self
.
_
(
u"Error: Unable to load child component."
)))
else
:
child_fragment
=
self
.
_render_child_fragment
(
child
,
context
,
view
)
fragment
.
add_frag_resources
(
child_fragment
)
child_contents
.
append
(
child_fragment
.
content
)
if
rendering_for_studio
and
isinstance
(
child
,
PlotBlock
):
# Don't use view to render plot blocks in Studio.
# This is necessary because:
# - 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'
,
{
'self'
:
self
,
...
...
problem_builder/templates/html/plot.html
View file @
dd04ee8c
...
...
@@ -18,14 +18,14 @@
<input
type=
"button"
class=
"plot-default"
data-claims=
"{{ self.default_claims }}"
data-claims=
"{{ self.default_claims
_json
}}"
data-point-color=
"{{ self.point_color_default }}"
data-overlay-on=
"false"
value=
"{{ self.plot_label }}"
/>
<input
type=
"button"
class=
"plot-average"
data-claims=
"{{ self.average_claims }}"
data-claims=
"{{ self.average_claims
_json
}}"
data-point-color=
"{{ self.point_color_average }}"
data-overlay-on=
"false"
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