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
cc0c234e
Commit
cc0c234e
authored
Nov 13, 2015
by
Matjaz Gregoric
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #91 from open-craft/competing-evidence-qa
Competing Evidence: Fix issues from client QA
parents
37d5d426
6157e302
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
253 additions
and
43 deletions
+253
-43
problem_builder/public/css/problem-builder.css
+11
-0
problem_builder/public/js/mentoring_with_steps.js
+60
-24
problem_builder/public/js/plot.js
+4
-2
problem_builder/public/js/step.js
+19
-14
problem_builder/public/themes/apros.css
+4
-0
problem_builder/templates/html/mentoring.html
+5
-0
problem_builder/templates/html/mentoring_with_steps.html
+5
-0
problem_builder/tests/integration/test_step_builder.py
+117
-3
problem_builder/tests/integration/xml_templates/step_builder_long_steps.xml
+28
-0
No files found.
problem_builder/public/css/problem-builder.css
View file @
cc0c234e
...
@@ -249,3 +249,14 @@
...
@@ -249,3 +249,14 @@
background-color
:
white
;
background-color
:
white
;
box-shadow
:
0
10px
20px
#5C5C5C
;
box-shadow
:
0
10px
20px
#5C5C5C
;
}
}
.mentoring
.copyright
{
color
:
#AAA
;
clear
:
both
;
font-size
:
10px
;
margin
:
3em
0
;
}
.mentoring
.copyright
a
{
color
:
#69C0E8
;
}
problem_builder/public/js/mentoring_with_steps.js
View file @
cc0c234e
...
@@ -158,23 +158,54 @@ function MentoringWithStepsBlock(runtime, element) {
...
@@ -158,23 +158,54 @@ function MentoringWithStepsBlock(runtime, element) {
showActiveStep
();
showActiveStep
();
validateXBlock
();
validateXBlock
();
updateNextLabel
();
updateNextLabel
();
// Reinstate default event handlers
nextDOM
.
on
(
'click'
,
updateDisplay
);
reviewButtonDOM
.
on
(
'click'
,
showGrade
);
var
step
=
steps
[
activeStep
];
var
step
=
steps
[
activeStep
];
if
(
step
.
hasQuestion
())
{
if
(
step
.
hasQuestion
())
{
// Step includes one or more questions
nextDOM
.
attr
(
'disabled'
,
'disabled'
);
nextDOM
.
attr
(
'disabled'
,
'disabled'
);
}
else
{
submitDOM
.
show
();
nextDOM
.
removeAttr
(
'disabled'
);
if
(
isLastStep
())
{
// Step is last step
}
nextDOM
.
hide
();
if
(
isLastStep
()
&&
hasAReviewStep
)
{
if
(
hasAReviewStep
)
{
// Step Builder includes review step
if
(
step
.
hasQuestion
())
{
reviewButtonDOM
.
attr
(
'disabled'
,
'disabled'
);
reviewButtonDOM
.
attr
(
'disabled'
,
'disabled'
);
}
else
{
reviewButtonDOM
.
show
();
reviewButtonDOM
.
removeAttr
(
'disabled'
)
}
}
}
}
else
{
// Step does not include any questions
nextDOM
.
removeAttr
(
'disabled'
);
submitDOM
.
hide
();
if
(
isLastStep
())
{
// Step is last step
// Remove default event handler from button that displays review.
// This is necessary to make sure updateDisplay is not called twice
// when user clicks this button next;
// "submit" already does the right thing with respect to updating the display,
// and calling updateDisplay twice causes issues with scrolling behavior:
reviewButtonDOM
.
off
();
reviewButtonDOM
.
one
(
'click'
,
submit
);
reviewButtonDOM
.
removeAttr
(
'disabled'
);
nextDOM
.
hide
();
if
(
hasAReviewStep
)
{
// Step Builder includes review step
reviewButtonDOM
.
show
();
reviewButtonDOM
.
show
();
}
}
}
else
{
// Step is not last step
// Remove default event handler from button that displays next step.
// This is necessary to make sure updateDisplay is not called twice
// when user clicks this button next;
// "submit" already does the right thing with respect to updating the display,
// and calling updateDisplay twice causes issues with scrolling behavior:
nextDOM
.
off
();
nextDOM
.
one
(
'click'
,
submit
);
}
}
}
}
}
// Scroll to top of this block
scrollIntoView
();
}
function
showReviewStep
()
{
function
showReviewStep
()
{
if
(
someAttemptsLeft
())
{
if
(
someAttemptsLeft
())
{
tryAgainDOM
.
removeAttr
(
'disabled'
);
tryAgainDOM
.
removeAttr
(
'disabled'
);
...
@@ -189,7 +220,7 @@ function MentoringWithStepsBlock(runtime, element) {
...
@@ -189,7 +220,7 @@ function MentoringWithStepsBlock(runtime, element) {
}
}
function
hideReviewStep
()
{
function
hideReviewStep
()
{
reviewStepDOM
.
hide
()
reviewStepDOM
.
hide
()
;
}
}
function
getStepToReview
(
event
)
{
function
getStepToReview
(
event
)
{
...
@@ -225,6 +256,9 @@ function MentoringWithStepsBlock(runtime, element) {
...
@@ -225,6 +256,9 @@ function MentoringWithStepsBlock(runtime, element) {
reviewLinkDOM
.
show
();
reviewLinkDOM
.
show
();
getResults
();
getResults
();
// Scroll to top of this block
scrollIntoView
();
}
}
function
showAttempts
()
{
function
showAttempts
()
{
...
@@ -236,8 +270,8 @@ function MentoringWithStepsBlock(runtime, element) {
...
@@ -236,8 +270,8 @@ function MentoringWithStepsBlock(runtime, element) {
function
showActiveStep
()
{
function
showActiveStep
()
{
var
step
=
steps
[
activeStep
];
var
step
=
steps
[
activeStep
];
step
.
updatePlots
();
$
(
step
.
element
).
show
();
$
(
step
.
element
).
show
();
step
.
updateChildren
();
}
}
function
onChange
()
{
function
onChange
()
{
...
@@ -260,20 +294,6 @@ function MentoringWithStepsBlock(runtime, element) {
...
@@ -260,20 +294,6 @@ function MentoringWithStepsBlock(runtime, element) {
}
else
{
}
else
{
submitDOM
.
removeAttr
(
'disabled'
);
submitDOM
.
removeAttr
(
'disabled'
);
}
}
if
(
isLastStep
()
&&
step
.
hasQuestion
())
{
nextDOM
.
hide
();
}
else
if
(
isLastStep
())
{
reviewButtonDOM
.
one
(
'click'
,
submit
);
reviewButtonDOM
.
removeAttr
(
'disabled'
);
nextDOM
.
hide
()
}
else
if
(
!
step
.
hasQuestion
())
{
nextDOM
.
one
(
'click'
,
submit
);
}
if
(
step
.
hasQuestion
())
{
submitDOM
.
show
();
}
else
{
submitDOM
.
hide
();
}
}
}
function
initSteps
(
options
)
{
function
initSteps
(
options
)
{
...
@@ -323,6 +343,9 @@ function MentoringWithStepsBlock(runtime, element) {
...
@@ -323,6 +343,9 @@ function MentoringWithStepsBlock(runtime, element) {
nextDOM
.
off
();
nextDOM
.
off
();
nextDOM
.
on
(
'click'
,
reviewNextStep
);
nextDOM
.
on
(
'click'
,
reviewNextStep
);
reviewLinkDOM
.
hide
();
reviewLinkDOM
.
hide
();
// Scroll to top of this block
scrollIntoView
();
}
}
function
reviewNextStep
()
{
function
reviewNextStep
()
{
...
@@ -363,6 +386,19 @@ function MentoringWithStepsBlock(runtime, element) {
...
@@ -363,6 +386,19 @@ function MentoringWithStepsBlock(runtime, element) {
}
}
}
}
function
scrollIntoView
()
{
// This function can be called multiple times per step initialization.
// We must make sure that only one animation is queued or running at any given time,
// that's why we use a special animation queue and make sure to .stop() any running/queued
// animations before enqueueing a new one.
var
rootBlock
=
$
(
element
),
rootBlockOffset
=
rootBlock
.
offset
().
top
,
queue
=
'sb-scroll'
,
props
=
{
scrollTop
:
rootBlockOffset
},
opts
=
{
duration
:
500
,
queue
:
queue
};
$
(
'html, body'
).
stop
(
queue
,
true
).
animate
(
props
,
opts
).
dequeue
(
queue
);
}
function
initClickHandlers
()
{
function
initClickHandlers
()
{
$
(
document
).
on
(
"click"
,
function
(
event
,
ui
)
{
$
(
document
).
on
(
"click"
,
function
(
event
,
ui
)
{
var
target
=
$
(
event
.
target
);
var
target
=
$
(
event
.
target
);
...
...
problem_builder/public/js/plot.js
View file @
cc0c234e
...
@@ -57,11 +57,13 @@ function PlotBlock(runtime, element) {
...
@@ -57,11 +57,13 @@ function PlotBlock(runtime, element) {
// Create axes
// Create axes
var
xAxis
=
d3
.
svg
.
axis
()
var
xAxis
=
d3
.
svg
.
axis
()
.
scale
(
xScale
)
.
scale
(
xScale
)
.
orient
(
"bottom"
);
.
orient
(
"bottom"
)
.
tickValues
([]);
var
yAxis
=
d3
.
svg
.
axis
()
var
yAxis
=
d3
.
svg
.
axis
()
.
scale
(
yScale
)
.
scale
(
yScale
)
.
orient
(
"left"
);
.
orient
(
"left"
)
.
tickValues
([]);
// Create SVG group elements for axes and call the xAxis and yAxis functions
// Create SVG group elements for axes and call the xAxis and yAxis functions
var
xAxisGroup
=
svgContainer
.
append
(
"g"
)
var
xAxisGroup
=
svgContainer
.
append
(
"g"
)
...
...
problem_builder/public/js/step.js
View file @
cc0c234e
function
MentoringStepBlock
(
runtime
,
element
)
{
function
MentoringStepBlock
(
runtime
,
element
)
{
var
children
=
runtime
.
children
(
element
);
var
children
=
runtime
.
children
(
element
);
var
plots
=
[];
for
(
var
i
in
children
)
{
var
child
=
children
[
i
];
var
blockType
=
$
(
child
.
element
).
data
(
'block-type'
);
if
(
blockType
===
'sb-plot'
)
{
plots
.
push
(
child
);
}
}
var
submitXHR
,
resultsXHR
,
var
submitXHR
,
resultsXHR
,
message
=
$
(
element
).
find
(
'.sb-step-message'
);
message
=
$
(
element
).
find
(
'.sb-step-message'
);
...
@@ -21,6 +13,14 @@ function MentoringStepBlock(runtime, element) {
...
@@ -21,6 +13,14 @@ function MentoringStepBlock(runtime, element) {
}
}
}
}
function
updateVideo
(
video
)
{
video
.
resizer
.
align
();
}
function
updatePlot
(
plot
)
{
plot
.
update
();
}
return
{
return
{
initChildren
:
function
(
options
)
{
initChildren
:
function
(
options
)
{
...
@@ -103,13 +103,18 @@ function MentoringStepBlock(runtime, element) {
...
@@ -103,13 +103,18 @@ function MentoringStepBlock(runtime, element) {
return
$
(
'.sb-step'
,
element
).
data
(
'has-question'
)
return
$
(
'.sb-step'
,
element
).
data
(
'has-question'
)
},
},
updatePlots
:
function
()
{
updateChildren
:
function
()
{
if
(
plots
)
{
children
.
forEach
(
function
(
child
)
{
for
(
var
i
in
plots
)
{
var
type
=
$
(
child
.
element
).
data
(
'block-type'
);
var
plot
=
plots
[
i
];
switch
(
type
)
{
plot
.
update
();
case
'video'
:
}
updateVideo
(
child
);
break
;
case
'sb-plot'
:
updatePlot
(
child
);
break
;
}
}
});
}
}
};
};
...
...
problem_builder/public/themes/apros.css
View file @
cc0c234e
...
@@ -26,3 +26,7 @@
...
@@ -26,3 +26,7 @@
margin-left
:
1.8em
;
margin-left
:
1.8em
;
padding-left
:
0
;
padding-left
:
0
;
}
}
.themed-xblock.mentoring
.copyright
{
display
:
none
;
}
problem_builder/templates/html/mentoring.html
View file @
cc0c234e
...
@@ -56,4 +56,9 @@
...
@@ -56,4 +56,9 @@
<div
class=
"assessment-review-tips"
></div>
<div
class=
"assessment-review-tips"
></div>
</div>
</div>
<div
class=
"review-link"
><a
href=
"#"
>
Review final grade
</a></div>
<div
class=
"review-link"
><a
href=
"#"
>
Review final grade
</a></div>
<p
class=
"copyright"
>
Copyright
©
2013
–
2015 OpenCraft, Harvard, edX, McKinsey, and The People's Science, released under the
<a
target=
"_blank"
href=
"https://github.com/open-craft/problem-builder/blob/master/LICENSE"
>
APGLv3 license
</a>
</p>
</div>
</div>
problem_builder/templates/html/mentoring_with_steps.html
View file @
cc0c234e
...
@@ -28,4 +28,9 @@
...
@@ -28,4 +28,9 @@
<div
class=
"review-link"
><a
href=
"#"
>
Review final grade
</a></div>
<div
class=
"review-link"
><a
href=
"#"
>
Review final grade
</a></div>
<p
class=
"copyright"
>
Copyright
©
2013
–
2015 OpenCraft, Harvard, edX, McKinsey, and The People's Science, released under the
<a
target=
"_blank"
href=
"https://github.com/open-craft/problem-builder/blob/master/LICENSE"
>
APGLv3 license
</a>
</p>
</div>
</div>
problem_builder/tests/integration/test_step_builder.py
View file @
cc0c234e
import
time
from
mock
import
patch
from
mock
import
patch
from
ddt
import
ddt
,
data
from
ddt
import
ddt
,
data
from
selenium.webdriver.support.ui
import
WebDriverWait
from
workbench.runtime
import
WorkbenchRuntime
from
workbench.runtime
import
WorkbenchRuntime
from
.base_test
import
CORRECT
,
INCORRECT
,
PARTIAL
,
MentoringAssessmentBaseTest
,
GetChoices
from
.base_test
import
CORRECT
,
INCORRECT
,
PARTIAL
,
MentoringAssessmentBaseTest
,
GetChoices
...
@@ -657,13 +660,16 @@ class StepBuilderTest(MentoringAssessmentBaseTest, MultipleSliderBlocksTestMixin
...
@@ -657,13 +660,16 @@ class StepBuilderTest(MentoringAssessmentBaseTest, MultipleSliderBlocksTestMixin
)
)
choices
.
select
(
choice_name
)
choices
.
select
(
choice_name
)
def
submit_and_go_to_next_step
(
self
,
controls
,
last
=
False
):
def
submit_and_go_to_next_step
(
self
,
controls
,
last
=
False
,
no_questions
=
False
):
controls
.
submit
.
click
()
controls
.
submit
.
click
()
self
.
wait_until_clickable
(
controls
.
next_question
)
self
.
wait_until_clickable
(
controls
.
next_question
)
controls
.
next_question
.
click
()
controls
.
next_question
.
click
()
if
last
:
if
last
:
self
.
wait_until_hidden
(
controls
.
next_question
)
self
.
wait_until_hidden
(
controls
.
next_question
)
else
:
else
:
if
no_questions
:
self
.
wait_until_hidden
(
controls
.
submit
)
else
:
self
.
wait_until_disabled
(
controls
.
next_question
)
self
.
wait_until_disabled
(
controls
.
next_question
)
def
plot_controls
(
self
,
step_builder
):
def
plot_controls
(
self
,
step_builder
):
...
@@ -777,9 +783,16 @@ class StepBuilderTest(MentoringAssessmentBaseTest, MultipleSliderBlocksTestMixin
...
@@ -777,9 +783,16 @@ class StepBuilderTest(MentoringAssessmentBaseTest, MultipleSliderBlocksTestMixin
plot_controls
.
quadrants_button
.
click
()
plot_controls
.
quadrants_button
.
click
()
self
.
check_quadrant_labels
(
step_builder
,
plot_controls
,
hidden
=
False
)
self
.
check_quadrant_labels
(
step_builder
,
plot_controls
,
hidden
=
False
)
def
wait_for_multiple_elements
(
self
,
step_builder
,
selector
,
expected_number_of_elements
):
def
wait_for_elements
(
container
):
elements
=
container
.
find_elements_by_css_selector
(
selector
)
return
len
(
elements
)
==
expected_number_of_elements
wait
=
WebDriverWait
(
step_builder
,
self
.
timeout
)
wait
.
until
(
wait_for_elements
)
def
check_overlays
(
self
,
step_builder
,
total_num_points
,
overlays
):
def
check_overlays
(
self
,
step_builder
,
total_num_points
,
overlays
):
points
=
step_builder
.
find_elements_by_css_selector
(
"circle"
)
self
.
wait_for_multiple_elements
(
step_builder
,
"circle"
,
total_num_points
)
self
.
assertEquals
(
len
(
points
),
total_num_points
)
for
overlay
in
overlays
:
for
overlay
in
overlays
:
# Check if correct number of points is present
# Check if correct number of points is present
...
@@ -1288,3 +1301,104 @@ class StepBuilderTest(MentoringAssessmentBaseTest, MultipleSliderBlocksTestMixin
...
@@ -1288,3 +1301,104 @@ class StepBuilderTest(MentoringAssessmentBaseTest, MultipleSliderBlocksTestMixin
# In that case, submitting an answer and will fail,
# In that case, submitting an answer and will fail,
# as it requires the corresponding question to be visible:
# as it requires the corresponding question to be visible:
self
.
freeform_answer
(
None
,
step_builder
,
controls
,
'This is the answer'
,
CORRECT
)
self
.
freeform_answer
(
None
,
step_builder
,
controls
,
'This is the answer'
,
CORRECT
)
def
provide_freeform_answer
(
self
,
step_number
,
question_number
,
step_builder
,
text_input
):
steps
=
step_builder
.
find_elements_by_css_selector
(
'div[data-block-type="sb-step"]'
)
current_step
=
steps
[
step_number
-
1
]
freeform_questions
=
current_step
.
find_elements_by_css_selector
(
'div[data-block-type="pb-answer"]'
)
current_question
=
freeform_questions
[
question_number
-
1
]
question_text
=
self
.
question_text
(
question_number
)
self
.
wait_until_text_in
(
question_text
,
current_question
)
self
.
assertIn
(
"What is your goal?"
,
current_question
.
text
)
textarea
=
current_question
.
find_element_by_css_selector
(
"textarea"
)
textarea
.
clear
()
textarea
.
send_keys
(
text_input
)
self
.
assertEquals
(
textarea
.
get_attribute
(
"value"
),
text_input
)
def
submit_and_go_to_review_step
(
self
,
step_builder
,
controls
,
result
):
controls
.
submit
.
click
()
self
.
do_submit_wait
(
controls
,
last
=
True
)
self
.
_assert_checkmark
(
step_builder
,
result
)
self
.
do_post
(
controls
,
last
=
True
)
def
check_viewport
(
self
):
step_builder_offset
=
int
(
self
.
browser
.
execute_script
(
"return $('div[data-block-type=
\"
step-builder
\"
]').offset().top"
)
)
def
is_scrolled_to_top
(
driver
):
scroll_top
=
int
(
driver
.
execute_script
(
"return $(window).scrollTop()"
))
return
abs
(
scroll_top
-
step_builder_offset
)
<
1
wait
=
WebDriverWait
(
self
.
browser
,
5
)
wait
.
until
(
is_scrolled_to_top
)
def
scroll_down
(
self
):
self
.
browser
.
execute_script
(
"$(window).scrollTop(50)"
)
def
test_scroll_into_view
(
self
):
# Make window small, so that we have to scroll.
self
.
browser
.
set_window_size
(
400
,
400
)
step_builder
,
controls
=
self
.
load_assessment_scenario
(
"step_builder_long_steps.xml"
,
{})
# First step
self
.
check_viewport
()
# - Answer questions
self
.
provide_freeform_answer
(
1
,
1
,
step_builder
,
"This is the answer"
)
self
.
provide_freeform_answer
(
1
,
2
,
step_builder
,
"This is the answer"
)
self
.
provide_freeform_answer
(
1
,
3
,
step_builder
,
"This is the answer"
)
self
.
provide_freeform_answer
(
1
,
4
,
step_builder
,
"This is the answer"
)
self
.
provide_freeform_answer
(
1
,
5
,
step_builder
,
"This is the answer"
)
# - Submit and go to next step
self
.
submit_and_go_to_next_step
(
controls
,
no_questions
=
True
)
# Second step
self
.
check_viewport
()
self
.
scroll_down
()
self
.
html_section
(
step_builder
,
controls
)
# Last step
self
.
check_viewport
()
# - Answer questions
self
.
provide_freeform_answer
(
3
,
1
,
step_builder
,
"This is the answer"
)
self
.
provide_freeform_answer
(
3
,
2
,
step_builder
,
"This is the answer"
)
self
.
provide_freeform_answer
(
3
,
3
,
step_builder
,
"This is the answer"
)
self
.
provide_freeform_answer
(
3
,
4
,
step_builder
,
"This is the answer"
)
self
.
provide_freeform_answer
(
3
,
5
,
step_builder
,
"This is the answer"
)
# - Submit and go to review step
self
.
submit_and_go_to_review_step
(
step_builder
,
controls
,
result
=
CORRECT
)
# Review step
self
.
check_viewport
()
question_links
=
step_builder
.
find_elements_by_css_selector
(
'.correct-list li a'
)
# - Review questions belonging to first step
question_links
[
2
]
.
click
()
self
.
check_viewport
()
self
.
scroll_down
()
# - Jump to review step
controls
.
review_link
.
click
()
self
.
check_viewport
()
self
.
scroll_down
()
# - Review questions belonging to last step
question_links
[
7
]
.
click
()
self
.
check_viewport
()
self
.
scroll_down
()
# - Jump to review step
controls
.
review_link
.
click
()
self
.
check_viewport
()
self
.
scroll_down
()
# - Review questions belonging to first step
question_links
[
2
]
.
click
()
self
.
check_viewport
()
self
.
scroll_down
()
# - Navigate to second step
controls
.
next_question
.
click
()
self
.
check_viewport
()
self
.
scroll_down
()
# - Review questions belonging to last step
controls
.
next_question
.
click
()
self
.
check_viewport
()
self
.
scroll_down
()
# - Navigate to review step
controls
.
review
.
click
()
self
.
check_viewport
()
problem_builder/tests/integration/xml_templates/step_builder_long_steps.xml
0 → 100644
View file @
cc0c234e
<step-builder
url_name=
"step-builder"
display_name=
"Step Builder"
max_attempts=
"1"
extended_feedback=
"true"
>
<sb-step
display_name=
"First step"
>
<pb-answer
name=
"goal"
question=
"What is your goal?"
/>
<pb-answer
name=
"goal"
question=
"What is your goal?"
/>
<pb-answer
name=
"goal"
question=
"What is your goal?"
/>
<pb-answer
name=
"goal"
question=
"What is your goal?"
/>
<pb-answer
name=
"goal"
question=
"What is your goal?"
/>
</sb-step>
<sb-step
display_name=
"Second Step"
>
<html_demo>
Test test test
</html_demo>
</sb-step>
<sb-step
display_name=
"Last step"
>
<pb-answer
name=
"goal"
question=
"What is your goal?"
/>
<pb-answer
name=
"goal"
question=
"What is your goal?"
/>
<pb-answer
name=
"goal"
question=
"What is your goal?"
/>
<pb-answer
name=
"goal"
question=
"What is your goal?"
/>
<pb-answer
name=
"goal"
question=
"What is your goal?"
/>
</sb-step>
<sb-review-step>
<sb-review-score/>
</sb-review-step>
</step-builder>
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