CSC/ECE 517 Fall 2014/OSS E1450 cxm: Difference between revisions
(108 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
==E1450: UI change for assignment view== | ==E1450: UI change for assignment view== | ||
<b>What it does: </b>Change UI of Expertiza to support varying rubric feature (allow instructors to specify different review rubrics for different review rounds) | |||
== Background Information== | == Background Information== | ||
=== What is Expertiza? === | === What is Expertiza? === | ||
Expertiza is a web application where students can submit and peer-review learning objects (articles, code, web sites, etc). The Expertiza project is supported by the National Science Foundation<ref>https://github.com/expertiza/expertiza</ref>. The Expertiza project is software to create reusable learning objects through peer review. It also supports team projects, and the submission of almost any document type, including URLs and wiki pages<ref>http://wikis.lib.ncsu.edu/index.php/Expertiza</ref>. | |||
=== JavaScript === | === JavaScript === | ||
JavaScript is a dynamic programming language of the Web browsers. Despite some naming, syntactic, and standard library similarities, JavaScript and Java are otherwise unrelated and have very different [http://en.wikipedia.org/wiki/Semantics_(computer_science) semantics]. The syntax of JavaScript is actually derived from [http://en.wikipedia.org/wiki/C_(programming_language) C], while the semantics and design are influenced by [http://en.wikipedia.org/wiki/Self_(programming_language) Self] and [http://en.wikipedia.org/wiki/Scheme_(programming_language) Scheme] programming languages<ref>http://en.wikipedia.org/wiki/JavaScript</ref>. This mix of features makes it a multi-paradigm language, supporting [http://en.wikipedia.org/wiki/Object-oriented_programming object-oriented], [http://en.wikipedia.org/wiki/Imperative_programming imperative], and [http://en.wikipedia.org/wiki/Functional_programming functional] programming styles. | |||
==Project Description== | ==Project Description== | ||
<b>Classes involved:</b> | <b>Classes involved:</b>controllers/assignment_controller.rb (488 lines)<br> views/assignment/edit.html.erb (55 lines) in production branch (not in master branch) | ||
<b>What needs to be done:</b> | |||
This project requires JavaScript knowledge. | |||
*A checkbox “Review rubrics vary by round” should be added to the “Rubric” tab in the view of creating/editing assignment. No corresponding field in “assignments” table is necessary. We can tell if this checkbox should be checked by checking “assignments_questionnaires” table by current assignment_id. If there is no record with a non-null value in “used_in_round” field, this assignment is not using this feature and the checkbox should not be checked. (if one assignment has 2 rounds but they are using the same set of rubrics, for each type of rubric there should be only one entry with “used_in_round” field null)R | |||
* There should be a editable “deadline name” for each due date on “due date” panel if this type of review is specified to be “varying by rounds” in the “rubrics” tab (the input should be recorded in deadline_name field in due_dates table) | |||
* Another “description URL” text box should be editable when this type of review is specified to be “varying by rounds” in the “rubrics” tab (the input should be recorded in description_url field in due_dates table) | |||
* The "deadline_name" and "description URL" could be hidden when you change the status of the checkbox in Due_Date tab | |||
* A drop-down box which help instructor to select review rubric should be added for a review round when this type of review is specified to be “varying by rounds” in the “rubrics” tab (the input should be recorded in assignments_questionnaires table) | |||
* There are no tests for the code. Create appropriate functional and integration tests. | |||
== Environment Setup== | |||
Since we are developing on the Production branch, in stead of the Rails4 branch, the environment for us would be different from the majority of the class. Here is the list: | |||
<b>Ruby:</b> 1.8.7 | |||
<b>Rails:</b> 2.3.15 | |||
<b>Java:</b> 1.6 | |||
<b>Openjdk:</b> 6.0 | |||
<b>Database:</b> expertiza_scrubbed_2014_03_14.sql | |||
== Code Modifications == | == Code Modifications == | ||
=== Case 1: Add Checkbox on "Rubric" Tab=== | |||
==== Current Scenario ==== | |||
No checkbox for "Varying Rubric by Round" on the general tab for Instructors. | |||
==== After Changes ==== | |||
In the views / assignment / edit / _rubric.html.erb, we added a checkbox, which can read the value of used_in_round_flag. In Rubrics Tab, add a Review rubric varies by round checkbox, when checked, it displays corresponding number of rounds of Review round 1 to Review round n; when unchecked, it displays as usual. | |||
At the same time, it will store new data in the table into database, in assignment_questionnaires table, it will add 1,2,3...n to used_in_round column if the review rubric varies by round; if not, it will store NULL. | |||
Everytime webpage refreshes, the checkbox is checked or not according to the value of used_in_round column in the assignment_questionnaires table. | |||
==== Codes ==== | |||
In the assignment_controller.rb, we added a method to load the value from database to used_in_round_flag: | |||
def edit | |||
... | |||
@reviewvarycheck = nil | |||
@assignment_questionnaires.each do |aq| | |||
if(!(aq.used_in_round.nil?)) | |||
@reviewvarycheck = 1 | |||
@q_id = aq.questionnaire_id | |||
@qn = Questionnaire.find(@q_id) | |||
if(@qn.display_type.eql?"Review") | |||
@review_check = 1 | |||
elsif(@qn.display_type.eql?"Metareview") | |||
@metareview_check=1 | |||
elsif(@qn.display_type.eql?"Author Feedback") | |||
@author_check = 1 | |||
elsif(@qn.display_type.eql?"Teammate Review") | |||
@teammate_check = 1 | |||
end | |||
end | |||
end | |||
... | |||
end | |||
In the _rubrics.html.erb, we added a javascript function to handle the click action: | |||
function handleCheckReviewVary(checkvalue) { | |||
var state = checkvalue.checked; | |||
var round_count = <%= @assignment.rounds_of_reviews%>; | |||
if (state == true && round_count >1){ | |||
//Make it display by rounds | |||
var element_id; | |||
element_id = 'questionnaire_table_' + 'ReviewQuestionnaire'; | |||
jQuery('#' + element_id).remove(); | |||
element_id = 'questionnaire_table_' + 'MetareviewQuestionnaire'; | |||
jQuery('#' + element_id).remove(); | |||
element_id = 'questionnaire_table_' + 'AuthorFeedbackQuestionnaire'; | |||
jQuery('#' + element_id).remove(); | |||
element_id = 'questionnaire_table_' + 'TeammateReviewQuestionnaire'; | |||
jQuery('#' + element_id).remove(); | |||
for (i = 1; i <= round_count; i++) { | |||
addQuestionnaireTableRow('ReviewQuestionnaire', i, <%= questionnaire(@assignment, <br> 'ReviewQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment, 'ReviewQuestionnaire',nil).<br>to_json %>, <%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); | |||
} | |||
addQuestionnaireTableRow('MetareviewQuestionnaire', null, <%= questionnaire(@assignment, <br>'MetareviewQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment, <br>'MetareviewQuestionnaire',nil).to_json %>, <%= questionnaire_options(@assignment, <br>'MetareviewQuestionnaire').to_json %>); | |||
addQuestionnaireTableRow('AuthorFeedbackQuestionnaire', null,<%= questionnaire(@assignment, <br>'AuthorFeedbackQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment, <br>'AuthorFeedbackQuestionnaire',nil).to_json %>, <%= questionnaire_options(@assignment,<br> 'AuthorFeedbackQuestionnaire').to_json %>); | |||
addQuestionnaireTableRow('TeammateReviewQuestionnaire',null, <%= questionnaire(@assignment, <br>'TeammateReviewQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment, <br>'TeammateReviewQuestionnaire',nil).to_json %>, <%= questionnaire_options(@assignment, <br>'TeammateReviewQuestionnaire').to_json %>); | |||
<%@avoidrepeatsign=1%>; | |||
} | |||
if (state == false && round_count>1){ | |||
//Make it display as usual | |||
var element_id; | |||
for (i=1;i<=round_count+1;i++) { | |||
element_id = 'questionnaire_table_' + 'ReviewQuestionnaire'; | |||
jQuery('#' + element_id).remove(); | |||
} | |||
element_id = 'questionnaire_table_' + 'MetareviewQuestionnaire'; | |||
jQuery('#' + element_id).remove(); | |||
element_id = 'questionnaire_table_' + 'AuthorFeedbackQuestionnaire'; | |||
jQuery('#' + element_id).remove(); | |||
element_id = 'questionnaire_table_' + 'TeammateReviewQuestionnaire'; | |||
jQuery('#' + element_id).remove(); | |||
//And display original ones | |||
addQuestionnaireTableRow('ReviewQuestionnaire',null, <%= questionnaire(@assignment,<br> 'ReviewQuestionnaire',1).to_json %>, <%= assignment_questionnaire(@assignment, <br>'ReviewQuestionnaire', 1).to_json %>, <%= questionnaire_options(@assignment, 'ReviewQuestionnaire')<br>.to_json %>); | |||
addQuestionnaireTableRow('MetareviewQuestionnaire', null, <%= questionnaire(@assignment, <br>'MetareviewQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment, <br>'MetareviewQuestionnaire',nil).to_json %>, <%= questionnaire_options(@assignment, <br>'MetareviewQuestionnaire').to_json %>); | |||
addQuestionnaireTableRow('AuthorFeedbackQuestionnaire', null,<%= questionnaire(@assignment, <br>'AuthorFeedbackQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment, <br>'AuthorFeedbackQuestionnaire',nil).to_json %>, <%= questionnaire_options(@assignment, <br>'AuthorFeedbackQuestionnaire').to_json %>); | |||
addQuestionnaireTableRow('TeammateReviewQuestionnaire',null, <%= questionnaire(@assignment, <br>'TeammateReviewQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment, <br>'TeammateReviewQuestionnaire',nil).to_json %>, <%= questionnaire_options(@assignment, <br>'TeammateReviewQuestionnaire').to_json %>); | |||
} | |||
} | |||
<!--//Add a review varies by round checkbox. By: Xiaoqiang Shao--> | |||
<input name="assignment_questionnaire[used_in_round]" type="hidden" value="false" /> | |||
<%= check_box_tag('assignment_questionnaire[used_in_round]', 'true', <br> @reviewvarycheck,:onclick=>"handleCheckReviewVary(this)" ) %> | |||
<%= label_tag('assignment_questionnaire[used_in_round]', 'Review rubric varies by round?') %> | |||
=== Case 2: Add "Deadline_name" and "Description_url" on "Due_date" Tab=== | |||
==== Current Scenario ==== | |||
No test_box for "deadline_name" and "description_url" on due_date tab. | |||
==== After Changes ==== | |||
* Add Due Date Name and Description_url to the table, users can fill in blanks and store them in DB | |||
* Rewrite set button function calling, everytime the button is pressed, it calls submit_form() and update in assignment_controller, then change the @assignment.rounds_of_review value in order to make changes in the DB instantly, which will help Rubrics tab display correctly | |||
* Add a Change name and description url checkbox. If checked, the Due Date Name and Descripton_url will whow up; if unchecked, the two columns will hide. | |||
* Everytime when webpage refreshes, the checkbox value will be set according to whether there’s value that is not empty in the deadline_name or description_url columns in due_dates table in DB. | |||
==== Codes ==== | |||
In the assignment_controller.rb, we added a method to check if the name and url are NULL in database to make a decision in the initial display (or after webpage refreshes). If there are contents in the DB, which means someone has typed in some texts, it will display the two columns; on the contrary, it won't display to make the table simpler.: | |||
def edit | |||
... | |||
@due_date_nameurl_notempty = "null" | |||
@due_date_nameurl_notempty_checkbox = false | |||
... | |||
# Add by Xiaoqiang Shao, check if name and url in database is empty before webpage displays | |||
@due_date_all.each do |dd| | |||
if(!(dd.deadline_name.nil?)) | |||
if(dd.deadline_name.length>=1) | |||
@due_date_nameurl_notempty = 1 | |||
@due_date_nameurl_notempty_checkbox = true | |||
end | |||
end | |||
if(!(dd.description_url.nil?)) | |||
if(dd.description_url.length>=1) | |||
@due_date_nameurl_notempty = 1 | |||
@due_date_nameurl_notempty_checkbox = true | |||
end | |||
end | |||
end | |||
... | |||
end | |||
And at any time, users can hide/show the two columns by checking the checkbox. Here are the main changes in _due_dates.html.erb: | |||
function handleChangenameurl(checkvalue,due_date_name_column,due_date_url_column) { | |||
var state = checkvalue; | |||
var round_count = <%= @assignment.rounds_of_reviews%>; | |||
if (!document.getElementById) { | |||
return; | |||
} | |||
if (state == false && round_count >1){ | |||
//hide name and url colomns | |||
for (i =1; i <= round_count; i++) { | |||
removeDueDateTableElement('submission', i); | |||
removeDueDateTableElement('review', i); | |||
} | |||
removeDueDateTableElement('metareview', 0); | |||
for (i =1; i <= round_count; i++) { | |||
addDueDateTableElement(null,'submission',i, <%= due_date(@assignment, 'submission', 0).to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); // or use questionnaire_options(@assignment, nil).to_json | |||
addDueDateTableElement(null,'review', i, <%= due_date(@assignment, 'review', 0).to_json %>, <%= questionnaire(@assignment, 'ReviewQuestionnaire',nil).to_json %>,<%= assignment_questionnaire(@assignment, 'ReviewQuestionnaire',nil).to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); | |||
} | |||
addDueDateTableElement(null,'metareview',0,<%= due_date(@assignment,'metareview', 0).to_json%>); | |||
due_date_name_column.style.display="none"; | |||
due_date_url_column.style.display="none"; | |||
} | |||
if (state == true && round_count >1){ | |||
//display name and url colomns | |||
for (i =1; i <= round_count; i++) { | |||
removeDueDateTableElement('submission', i); | |||
removeDueDateTableElement('review', i); | |||
} | |||
removeDueDateTableElement('metareview', 0); | |||
for (i =1; i <= round_count; i++) { | |||
addDueDateTableElement(1,'submission', i, <%= due_date(@assignment, 'submission', 0).to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); // or use questionnaire_options(@assignment, nil).to_json | |||
addDueDateTableElement(1,'review',i, <%= due_date(@assignment, 'review', 0).to_json %>, <%= questionnaire(@assignment, 'ReviewQuestionnaire',nil).to_json %>,<%= assignment_questionnaire(@assignment, 'ReviewQuestionnaire',nil).to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); | |||
} | |||
addDueDateTableElement(1,'metareview',0,<%= due_date(@assignment,'metareview', 0).to_json%>); | |||
due_date_name_column.removeAttribute("style"); | |||
due_date_url_column.removeAttribute("style"); | |||
} | |||
else{ | |||
return; | |||
} | |||
} | |||
<!--Add a change name or url checkbox. By: Xiaoqiang Shao--> | |||
<input name="changenameurl" type="hidden" value="false" /> | |||
<%= check_box_tag('changenameurl', 'true', @due_date_nameurl_notempty_checkbox,:onclick=>"handleChangenameurl(this.checked,due_date_name_column,due_date_url_column)" ) %> | |||
<%= label_tag('changenameurl', 'Change name and description url?') %> | |||
In the end corresponding changes should be made in the end of _due_dates.html.erb, after which the webpage will display corresponding contents (hide or display name and url columns according to whether they are empty): | |||
<pre> | |||
<table class='exp' id='due_dates_table' style='padding:10px;min-width: 990px;overflow: hidden;'> | |||
<tr id='due_date_heading'> | |||
... | |||
</tr> | |||
... | |||
<script> | |||
jQuery(document).ready(function () { | |||
<% for i in 1..@assignment.rounds_of_reviews %> | |||
addDueDateTableElement(<%= @due_date_nameurl_notempty %>,'submission', <%=i%>, <%= due_date(@assignment, 'submission', i-1).to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); // or use questionnaire_options(@assignment, nil).to_json | |||
addDueDateTableElement(<%= @due_date_nameurl_notempty %>,'review', <%=i%>, <%= due_date(@assignment, 'review', i-1).to_json %>, <%= questionnaire(@assignment, 'ReviewQuestionnaire',nil).to_json %>,<%= assignment_questionnaire(@assignment, 'ReviewQuestionnaire',nil).to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); | |||
<% end %> | |||
addDueDateTableElement(<%= @due_date_nameurl_notempty %>,'metareview', 0, <%= due_date(@assignment, 'metareview').to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); | |||
//after webpage refreshes, hide or display name and url according to whether names and urls are empty in DB | |||
var due_date_name_column=document.getElementById("due_date_name_column"); | |||
var due_date_url_column=document.getElementById("due_date_url_column"); | |||
<% if @due_date_nameurl_notempty =="null" %> | |||
due_date_name_column.style.visibility="hidden"; | |||
due_date_url_column.style.visibility="hidden"; | |||
<% end %> | |||
}()); | |||
</script> | |||
</table> | |||
</pre> | |||
=== Case 3: Make slight changes to existing methods/codes === | |||
==== Current Scenario ==== | |||
Existing methods have no enough parameters we want, or they are not returning out results we want. | |||
==== After Changes ==== | |||
* Make small changes to them to fit the new methods we write without interfering the previous methods. | |||
==== Exampls ==== | |||
The slight changes of functions are small, trivial and less important to readers to understand what we have done. So only examples are shown to convey the ideas, and all the detailed modification can be found at our [https://github.com/shaoxq1205/expertiza Github] (on branch production). <ref>https://github.com/shaoxq1205/expertiza</ref> | |||
Here's the example: | |||
We need to add a round_number for the questionnaire() method in order to make it return different values (actually from different rows of the assignment_questionnaire table) because there're more than 1 rounds now and each round may have different values. | |||
previous method of questionnaire(): | |||
<pre> | |||
def questionnaire(assignment, type) | |||
questionnaire = assignment.questionnaires.find_by_type(type) | |||
if questionnaire.nil? | |||
questionnaire = Object.const_get(type).new | |||
questionnaire | |||
else | |||
questionnaire | |||
end | |||
end | |||
</pre> | |||
current method of questionnaire(): | |||
<pre> | |||
def questionnaire(assignment, type, round_number) | |||
if round_number.nil? | |||
questionnaire=assignment.questionnaires.find_by_type(type) | |||
else | |||
ass_ques=assignment.assignment_questionnaires.find_by_used_in_round(round_number) | |||
# make sure the assignment_questionnaire record is not empty | |||
if !ass_ques.nil? | |||
temp_num=ass_ques.questionnaire_id | |||
questionnaire = assignment.questionnaires.find_by_id(temp_num) | |||
end | |||
end | |||
if questionnaire.nil? | |||
questionnaire = Object.const_get(type).new | |||
questionnaire | |||
else | |||
questionnaire | |||
end | |||
end | |||
</pre> | |||
== Cucumber Tests == | |||
=== Design === | |||
A few cucumber tests are added to perform the functional and integrated test for our project, such as: | |||
Scenario 1: An instructor can set the "Review Rubric Varies by Round" for an assignment, and set due dates for all of submissions and reviews, as well as different rubrics would be used | |||
Scenario 2: A student should see the review rubric for 1st round in the other's work link | |||
Scenario 3: The instructor change the due date to start the 2nd round of review | |||
Scenario 4: A student should see the review rubric for 2nd round in the other's work link | |||
=== Codes === | |||
In app/features/instructor/Vary_Rubrics_by_Round_Test.feature: | |||
<pre> | |||
Scenario1: Instructor can set the review rubric varies by round | |||
Given an instructor named "user6" | |||
And I am logged in as "user6" | |||
@instructor | |||
@manage_assignments | |||
When I move to the "Assignments" page | |||
And I click the "edit" link for "Week 4 wiki" | |||
And I check "Review rubric varies by round?" | |||
And I fill in "2014,11,10" for "Round1 Submission" | |||
And I fill in "2014,11,30" for "Round1 Review" | |||
And I fill in "2014,12,20" for "Round2 Submission" | |||
And I fill in "2014,12,22" for "Round2:Review" | |||
And I use Review Round1 named "update_wiki" | |||
And I use Review Round2"named "rubric1" | |||
Then I press "Save" | |||
@wip | |||
Scenario2: Student should see the review rubric for 1st round | |||
Given I am logged in as a user13 | |||
And I move to the "Assignments" page | |||
And I click the "Week 4 wiki" link | |||
When I click the "Others' work" link | |||
Then I should see "update_wiki" | |||
Scenario3: Instructor then change the due date of Round 2, so that the student would see a different review rubric | |||
Given an instructor named "user6" | |||
And I am logged in as "user6" | |||
@instructor | |||
@manage_assignments | |||
When I move to the "Assignments" page | |||
And I click the "edit" link for "Week 4 wiki" | |||
And I fill in "2014,10,10" for "Round1 Submission" | |||
And I fill in "2014,10,22" for "Round1 Review" | |||
And I fill in "2014,11,10" for "Round2 Submission" | |||
And I fill in "2014,12,23" for "Round2:Review" | |||
Then I press "Save" | |||
@wip | |||
Scenario4: Student should see the review rubric for 2st round, which is different from the 1st one | |||
Given I am logged in as a user13 | |||
And I move to the "Assignments" page | |||
And I click the "Week 4 wiki" link | |||
When I click the "Others' work" link | |||
Then I should see "rubric1" | |||
</pre> | |||
In app/features/step_definitions/review_rubric_varies_by_round.rb: | |||
<pre> | |||
And /^I click the "edit" link for "Week 4 wiki"$/ do | |||
visit('/assignment/edit/18') | |||
end | |||
And /^I fill in "([^"]*)" for "([^"]*)" $/ do |due_date, deadline| | |||
step "I fill in \"#{due_date}\" for \"due_date\"" | |||
step "I fill in \"#{deadline}\" for \"deadline\"" | |||
end | |||
And /^I use Review Round1 named "update_wiki" $/ do | |||
step "I select \"update_wiki\" from \"questionnaires[review1]\"" | |||
end | |||
And /^I use Review Round1 named "rubric1" $/ do | |||
step "I select \"rubric1\" from \"questionnaires[review2]\"" | |||
end | |||
</pre> | |||
== Final Layout == | |||
Here are the screen shots of the final layouts. | |||
=== Rubrics Tab === | |||
[[File:Unchecked.png]] | |||
[[File:Checked.png]] | |||
As you can see, a Review rubric varies by round checkbox will make a difference, which makes sense. | |||
=== Due Dates Tab === | |||
[[File:Due_unchecked.jpg]] | |||
[[File:Due_checked.png]] | |||
As you can see, a Change name and description url checkbox will make a difference, which makes sense. | |||
== References == | == References == | ||
<references/> |
Latest revision as of 23:04, 12 November 2014
E1450: UI change for assignment view
What it does: Change UI of Expertiza to support varying rubric feature (allow instructors to specify different review rubrics for different review rounds)
Background Information
What is Expertiza?
Expertiza is a web application where students can submit and peer-review learning objects (articles, code, web sites, etc). The Expertiza project is supported by the National Science Foundation<ref>https://github.com/expertiza/expertiza</ref>. The Expertiza project is software to create reusable learning objects through peer review. It also supports team projects, and the submission of almost any document type, including URLs and wiki pages<ref>http://wikis.lib.ncsu.edu/index.php/Expertiza</ref>.
JavaScript
JavaScript is a dynamic programming language of the Web browsers. Despite some naming, syntactic, and standard library similarities, JavaScript and Java are otherwise unrelated and have very different semantics. The syntax of JavaScript is actually derived from C, while the semantics and design are influenced by Self and Scheme programming languages<ref>http://en.wikipedia.org/wiki/JavaScript</ref>. This mix of features makes it a multi-paradigm language, supporting object-oriented, imperative, and functional programming styles.
Project Description
Classes involved:controllers/assignment_controller.rb (488 lines)
views/assignment/edit.html.erb (55 lines) in production branch (not in master branch)
What needs to be done:
This project requires JavaScript knowledge.
- A checkbox “Review rubrics vary by round” should be added to the “Rubric” tab in the view of creating/editing assignment. No corresponding field in “assignments” table is necessary. We can tell if this checkbox should be checked by checking “assignments_questionnaires” table by current assignment_id. If there is no record with a non-null value in “used_in_round” field, this assignment is not using this feature and the checkbox should not be checked. (if one assignment has 2 rounds but they are using the same set of rubrics, for each type of rubric there should be only one entry with “used_in_round” field null)R
- There should be a editable “deadline name” for each due date on “due date” panel if this type of review is specified to be “varying by rounds” in the “rubrics” tab (the input should be recorded in deadline_name field in due_dates table)
- Another “description URL” text box should be editable when this type of review is specified to be “varying by rounds” in the “rubrics” tab (the input should be recorded in description_url field in due_dates table)
- The "deadline_name" and "description URL" could be hidden when you change the status of the checkbox in Due_Date tab
- A drop-down box which help instructor to select review rubric should be added for a review round when this type of review is specified to be “varying by rounds” in the “rubrics” tab (the input should be recorded in assignments_questionnaires table)
- There are no tests for the code. Create appropriate functional and integration tests.
Environment Setup
Since we are developing on the Production branch, in stead of the Rails4 branch, the environment for us would be different from the majority of the class. Here is the list:
Ruby: 1.8.7
Rails: 2.3.15
Java: 1.6
Openjdk: 6.0
Database: expertiza_scrubbed_2014_03_14.sql
Code Modifications
Case 1: Add Checkbox on "Rubric" Tab
Current Scenario
No checkbox for "Varying Rubric by Round" on the general tab for Instructors.
After Changes
In the views / assignment / edit / _rubric.html.erb, we added a checkbox, which can read the value of used_in_round_flag. In Rubrics Tab, add a Review rubric varies by round checkbox, when checked, it displays corresponding number of rounds of Review round 1 to Review round n; when unchecked, it displays as usual. At the same time, it will store new data in the table into database, in assignment_questionnaires table, it will add 1,2,3...n to used_in_round column if the review rubric varies by round; if not, it will store NULL. Everytime webpage refreshes, the checkbox is checked or not according to the value of used_in_round column in the assignment_questionnaires table.
Codes
In the assignment_controller.rb, we added a method to load the value from database to used_in_round_flag:
def edit ... @reviewvarycheck = nil @assignment_questionnaires.each do |aq| if(!(aq.used_in_round.nil?)) @reviewvarycheck = 1 @q_id = aq.questionnaire_id @qn = Questionnaire.find(@q_id) if(@qn.display_type.eql?"Review") @review_check = 1 elsif(@qn.display_type.eql?"Metareview") @metareview_check=1 elsif(@qn.display_type.eql?"Author Feedback") @author_check = 1 elsif(@qn.display_type.eql?"Teammate Review") @teammate_check = 1 end end end ... end
In the _rubrics.html.erb, we added a javascript function to handle the click action:
function handleCheckReviewVary(checkvalue) { var state = checkvalue.checked; var round_count = <%= @assignment.rounds_of_reviews%>; if (state == true && round_count >1){ //Make it display by rounds var element_id; element_id = 'questionnaire_table_' + 'ReviewQuestionnaire'; jQuery('#' + element_id).remove(); element_id = 'questionnaire_table_' + 'MetareviewQuestionnaire'; jQuery('#' + element_id).remove(); element_id = 'questionnaire_table_' + 'AuthorFeedbackQuestionnaire'; jQuery('#' + element_id).remove(); element_id = 'questionnaire_table_' + 'TeammateReviewQuestionnaire'; jQuery('#' + element_id).remove(); for (i = 1; i <= round_count; i++) { addQuestionnaireTableRow('ReviewQuestionnaire', i, <%= questionnaire(@assignment,
'ReviewQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment, 'ReviewQuestionnaire',nil).
to_json %>, <%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); } addQuestionnaireTableRow('MetareviewQuestionnaire', null, <%= questionnaire(@assignment,
'MetareviewQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment,
'MetareviewQuestionnaire',nil).to_json %>, <%= questionnaire_options(@assignment,
'MetareviewQuestionnaire').to_json %>); addQuestionnaireTableRow('AuthorFeedbackQuestionnaire', null,<%= questionnaire(@assignment,
'AuthorFeedbackQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment,
'AuthorFeedbackQuestionnaire',nil).to_json %>, <%= questionnaire_options(@assignment,
'AuthorFeedbackQuestionnaire').to_json %>); addQuestionnaireTableRow('TeammateReviewQuestionnaire',null, <%= questionnaire(@assignment,
'TeammateReviewQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment,
'TeammateReviewQuestionnaire',nil).to_json %>, <%= questionnaire_options(@assignment,
'TeammateReviewQuestionnaire').to_json %>); <%@avoidrepeatsign=1%>; } if (state == false && round_count>1){ //Make it display as usual var element_id; for (i=1;i<=round_count+1;i++) { element_id = 'questionnaire_table_' + 'ReviewQuestionnaire'; jQuery('#' + element_id).remove(); } element_id = 'questionnaire_table_' + 'MetareviewQuestionnaire'; jQuery('#' + element_id).remove(); element_id = 'questionnaire_table_' + 'AuthorFeedbackQuestionnaire'; jQuery('#' + element_id).remove(); element_id = 'questionnaire_table_' + 'TeammateReviewQuestionnaire'; jQuery('#' + element_id).remove(); //And display original ones addQuestionnaireTableRow('ReviewQuestionnaire',null, <%= questionnaire(@assignment,
'ReviewQuestionnaire',1).to_json %>, <%= assignment_questionnaire(@assignment,
'ReviewQuestionnaire', 1).to_json %>, <%= questionnaire_options(@assignment, 'ReviewQuestionnaire')
.to_json %>); addQuestionnaireTableRow('MetareviewQuestionnaire', null, <%= questionnaire(@assignment,
'MetareviewQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment,
'MetareviewQuestionnaire',nil).to_json %>, <%= questionnaire_options(@assignment,
'MetareviewQuestionnaire').to_json %>); addQuestionnaireTableRow('AuthorFeedbackQuestionnaire', null,<%= questionnaire(@assignment,
'AuthorFeedbackQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment,
'AuthorFeedbackQuestionnaire',nil).to_json %>, <%= questionnaire_options(@assignment,
'AuthorFeedbackQuestionnaire').to_json %>); addQuestionnaireTableRow('TeammateReviewQuestionnaire',null, <%= questionnaire(@assignment,
'TeammateReviewQuestionnaire',nil).to_json %>, <%= assignment_questionnaire(@assignment,
'TeammateReviewQuestionnaire',nil).to_json %>, <%= questionnaire_options(@assignment,
'TeammateReviewQuestionnaire').to_json %>); } } <input name="assignment_questionnaire[used_in_round]" type="hidden" value="false" /> <%= check_box_tag('assignment_questionnaire[used_in_round]', 'true',
@reviewvarycheck,:onclick=>"handleCheckReviewVary(this)" ) %> <%= label_tag('assignment_questionnaire[used_in_round]', 'Review rubric varies by round?') %>
Case 2: Add "Deadline_name" and "Description_url" on "Due_date" Tab
Current Scenario
No test_box for "deadline_name" and "description_url" on due_date tab.
After Changes
- Add Due Date Name and Description_url to the table, users can fill in blanks and store them in DB
- Rewrite set button function calling, everytime the button is pressed, it calls submit_form() and update in assignment_controller, then change the @assignment.rounds_of_review value in order to make changes in the DB instantly, which will help Rubrics tab display correctly
- Add a Change name and description url checkbox. If checked, the Due Date Name and Descripton_url will whow up; if unchecked, the two columns will hide.
- Everytime when webpage refreshes, the checkbox value will be set according to whether there’s value that is not empty in the deadline_name or description_url columns in due_dates table in DB.
Codes
In the assignment_controller.rb, we added a method to check if the name and url are NULL in database to make a decision in the initial display (or after webpage refreshes). If there are contents in the DB, which means someone has typed in some texts, it will display the two columns; on the contrary, it won't display to make the table simpler.:
def edit ... @due_date_nameurl_notempty = "null" @due_date_nameurl_notempty_checkbox = false ... # Add by Xiaoqiang Shao, check if name and url in database is empty before webpage displays @due_date_all.each do |dd| if(!(dd.deadline_name.nil?)) if(dd.deadline_name.length>=1) @due_date_nameurl_notempty = 1 @due_date_nameurl_notempty_checkbox = true end end if(!(dd.description_url.nil?)) if(dd.description_url.length>=1) @due_date_nameurl_notempty = 1 @due_date_nameurl_notempty_checkbox = true end end end ... end
And at any time, users can hide/show the two columns by checking the checkbox. Here are the main changes in _due_dates.html.erb:
function handleChangenameurl(checkvalue,due_date_name_column,due_date_url_column) { var state = checkvalue; var round_count = <%= @assignment.rounds_of_reviews%>; if (!document.getElementById) { return; } if (state == false && round_count >1){ //hide name and url colomns for (i =1; i <= round_count; i++) { removeDueDateTableElement('submission', i); removeDueDateTableElement('review', i); } removeDueDateTableElement('metareview', 0); for (i =1; i <= round_count; i++) { addDueDateTableElement(null,'submission',i, <%= due_date(@assignment, 'submission', 0).to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); // or use questionnaire_options(@assignment, nil).to_json addDueDateTableElement(null,'review', i, <%= due_date(@assignment, 'review', 0).to_json %>, <%= questionnaire(@assignment, 'ReviewQuestionnaire',nil).to_json %>,<%= assignment_questionnaire(@assignment, 'ReviewQuestionnaire',nil).to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); } addDueDateTableElement(null,'metareview',0,<%= due_date(@assignment,'metareview', 0).to_json%>); due_date_name_column.style.display="none"; due_date_url_column.style.display="none"; } if (state == true && round_count >1){ //display name and url colomns for (i =1; i <= round_count; i++) { removeDueDateTableElement('submission', i); removeDueDateTableElement('review', i); } removeDueDateTableElement('metareview', 0); for (i =1; i <= round_count; i++) { addDueDateTableElement(1,'submission', i, <%= due_date(@assignment, 'submission', 0).to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); // or use questionnaire_options(@assignment, nil).to_json addDueDateTableElement(1,'review',i, <%= due_date(@assignment, 'review', 0).to_json %>, <%= questionnaire(@assignment, 'ReviewQuestionnaire',nil).to_json %>,<%= assignment_questionnaire(@assignment, 'ReviewQuestionnaire',nil).to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); } addDueDateTableElement(1,'metareview',0,<%= due_date(@assignment,'metareview', 0).to_json%>); due_date_name_column.removeAttribute("style"); due_date_url_column.removeAttribute("style"); } else{ return; } } <input name="changenameurl" type="hidden" value="false" /> <%= check_box_tag('changenameurl', 'true', @due_date_nameurl_notempty_checkbox,:onclick=>"handleChangenameurl(this.checked,due_date_name_column,due_date_url_column)" ) %> <%= label_tag('changenameurl', 'Change name and description url?') %>
In the end corresponding changes should be made in the end of _due_dates.html.erb, after which the webpage will display corresponding contents (hide or display name and url columns according to whether they are empty):
<table class='exp' id='due_dates_table' style='padding:10px;min-width: 990px;overflow: hidden;'> <tr id='due_date_heading'> ... </tr> ... <script> jQuery(document).ready(function () { <% for i in 1..@assignment.rounds_of_reviews %> addDueDateTableElement(<%= @due_date_nameurl_notempty %>,'submission', <%=i%>, <%= due_date(@assignment, 'submission', i-1).to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); // or use questionnaire_options(@assignment, nil).to_json addDueDateTableElement(<%= @due_date_nameurl_notempty %>,'review', <%=i%>, <%= due_date(@assignment, 'review', i-1).to_json %>, <%= questionnaire(@assignment, 'ReviewQuestionnaire',nil).to_json %>,<%= assignment_questionnaire(@assignment, 'ReviewQuestionnaire',nil).to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); <% end %> addDueDateTableElement(<%= @due_date_nameurl_notempty %>,'metareview', 0, <%= due_date(@assignment, 'metareview').to_json %>,<%= questionnaire_options(@assignment, 'ReviewQuestionnaire').to_json %>); //after webpage refreshes, hide or display name and url according to whether names and urls are empty in DB var due_date_name_column=document.getElementById("due_date_name_column"); var due_date_url_column=document.getElementById("due_date_url_column"); <% if @due_date_nameurl_notempty =="null" %> due_date_name_column.style.visibility="hidden"; due_date_url_column.style.visibility="hidden"; <% end %> }()); </script> </table>
Case 3: Make slight changes to existing methods/codes
Current Scenario
Existing methods have no enough parameters we want, or they are not returning out results we want.
After Changes
- Make small changes to them to fit the new methods we write without interfering the previous methods.
Exampls
The slight changes of functions are small, trivial and less important to readers to understand what we have done. So only examples are shown to convey the ideas, and all the detailed modification can be found at our Github (on branch production). <ref>https://github.com/shaoxq1205/expertiza</ref>
Here's the example: We need to add a round_number for the questionnaire() method in order to make it return different values (actually from different rows of the assignment_questionnaire table) because there're more than 1 rounds now and each round may have different values. previous method of questionnaire():
def questionnaire(assignment, type) questionnaire = assignment.questionnaires.find_by_type(type) if questionnaire.nil? questionnaire = Object.const_get(type).new questionnaire else questionnaire end end
current method of questionnaire():
def questionnaire(assignment, type, round_number) if round_number.nil? questionnaire=assignment.questionnaires.find_by_type(type) else ass_ques=assignment.assignment_questionnaires.find_by_used_in_round(round_number) # make sure the assignment_questionnaire record is not empty if !ass_ques.nil? temp_num=ass_ques.questionnaire_id questionnaire = assignment.questionnaires.find_by_id(temp_num) end end if questionnaire.nil? questionnaire = Object.const_get(type).new questionnaire else questionnaire end end
Cucumber Tests
Design
A few cucumber tests are added to perform the functional and integrated test for our project, such as:
Scenario 1: An instructor can set the "Review Rubric Varies by Round" for an assignment, and set due dates for all of submissions and reviews, as well as different rubrics would be used
Scenario 2: A student should see the review rubric for 1st round in the other's work link
Scenario 3: The instructor change the due date to start the 2nd round of review
Scenario 4: A student should see the review rubric for 2nd round in the other's work link
Codes
In app/features/instructor/Vary_Rubrics_by_Round_Test.feature:
Scenario1: Instructor can set the review rubric varies by round Given an instructor named "user6" And I am logged in as "user6" @instructor @manage_assignments When I move to the "Assignments" page And I click the "edit" link for "Week 4 wiki" And I check "Review rubric varies by round?" And I fill in "2014,11,10" for "Round1 Submission" And I fill in "2014,11,30" for "Round1 Review" And I fill in "2014,12,20" for "Round2 Submission" And I fill in "2014,12,22" for "Round2:Review" And I use Review Round1 named "update_wiki" And I use Review Round2"named "rubric1" Then I press "Save" @wip Scenario2: Student should see the review rubric for 1st round Given I am logged in as a user13 And I move to the "Assignments" page And I click the "Week 4 wiki" link When I click the "Others' work" link Then I should see "update_wiki" Scenario3: Instructor then change the due date of Round 2, so that the student would see a different review rubric Given an instructor named "user6" And I am logged in as "user6" @instructor @manage_assignments When I move to the "Assignments" page And I click the "edit" link for "Week 4 wiki" And I fill in "2014,10,10" for "Round1 Submission" And I fill in "2014,10,22" for "Round1 Review" And I fill in "2014,11,10" for "Round2 Submission" And I fill in "2014,12,23" for "Round2:Review" Then I press "Save" @wip Scenario4: Student should see the review rubric for 2st round, which is different from the 1st one Given I am logged in as a user13 And I move to the "Assignments" page And I click the "Week 4 wiki" link When I click the "Others' work" link Then I should see "rubric1"
In app/features/step_definitions/review_rubric_varies_by_round.rb:
And /^I click the "edit" link for "Week 4 wiki"$/ do visit('/assignment/edit/18') end And /^I fill in "([^"]*)" for "([^"]*)" $/ do |due_date, deadline| step "I fill in \"#{due_date}\" for \"due_date\"" step "I fill in \"#{deadline}\" for \"deadline\"" end And /^I use Review Round1 named "update_wiki" $/ do step "I select \"update_wiki\" from \"questionnaires[review1]\"" end And /^I use Review Round1 named "rubric1" $/ do step "I select \"rubric1\" from \"questionnaires[review2]\"" end
Final Layout
Here are the screen shots of the final layouts.
Rubrics Tab
As you can see, a Review rubric varies by round checkbox will make a difference, which makes sense.
Due Dates Tab
As you can see, a Change name and description url checkbox will make a difference, which makes sense.
References
<references/>