Index: assignment-api/api/src/java/org/sakaiproject/assignment/api/model/PeerAssessmentItem.java =================================================================== --- assignment-api/api/src/java/org/sakaiproject/assignment/api/model/PeerAssessmentItem.java (revision 0) +++ assignment-api/api/src/java/org/sakaiproject/assignment/api/model/PeerAssessmentItem.java (revision 0) @@ -0,0 +1,74 @@ +package org.sakaiproject.assignment.api.model; + +import java.io.Serializable; + +public class PeerAssessmentItem implements Serializable{ + + private static final long serialVersionUID = -8376570648172966170L; + private String assignmentId; + private String submissionId; + private String assessorUserId; + private Integer score; + private String comment; + private boolean removed; + //submitted is only a flag to help with the UI show/hide reviews + //that the user still needs to complete (more of a hide flag than a submit) + private boolean submitted; + //transient variables for displaying information in the UI + private String assessorDisplayName; + + public String getSubmissionId() { + return submissionId; + } + public void setSubmissionId(String submissionId) { + this.submissionId = submissionId; + } + public Integer getScore() { + return score; + } + public void setScore(Integer score) { + this.score = score; + } + public String getComment() { + return comment; + } + public void setComment(String comment) { + this.comment = comment; + } + public boolean isRemoved() { + return removed; + } + public void setRemoved(boolean removed) { + this.removed = removed; + } + public String getAssessorUserId() { + return assessorUserId; + } + public void setAssessorUserId(String assessorUserId) { + this.assessorUserId = assessorUserId; + } + public String getAssignmentId() { + return assignmentId; + } + public void setAssignmentId(String assignmentId) { + this.assignmentId = assignmentId; + } + //score is stored as a integer value in the DB, but is really a decimal value (divide by 10) + public String getScoreDisplay(){ + return getScore() == null ? "" : "" + score/10.0; + } + //transient variable that is only set for UI + public String getAssessorDisplayName(){ + return assessorDisplayName; + } + //transient variable that is only set for UI + public void setAssessorDisplayName(String assessorDisplayName) { + this.assessorDisplayName = assessorDisplayName; + } + public boolean isSubmitted() { + return submitted; + } + public void setSubmitted(boolean submitted) { + this.submitted = submitted; + } +} Index: assignment-api/api/src/java/org/sakaiproject/assignment/api/model/PeerAssessmentItem.hbm.xml =================================================================== --- assignment-api/api/src/java/org/sakaiproject/assignment/api/model/PeerAssessmentItem.hbm.xml (revision 0) +++ assignment-api/api/src/java/org/sakaiproject/assignment/api/model/PeerAssessmentItem.hbm.xml (revision 0) @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + create index PEER_ASSESSOR_I on ASN_PEER_ASSESSMENT_ITEM_T (SUBMISSION_ID, ASSESSOR_USER_ID); + + + + + + create index PEER_ASSESSOR2_I on ASN_PEER_ASSESSMENT_ITEM_T (ASSIGNMENT_ID, ASSESSOR_USER_ID); + + + + + Index: assignment-api/api/src/java/org/sakaiproject/assignment/api/AssignmentSubmission.java =================================================================== --- assignment-api/api/src/java/org/sakaiproject/assignment/api/AssignmentSubmission.java (revision 310244) +++ assignment-api/api/src/java/org/sakaiproject/assignment/api/AssignmentSubmission.java (working copy) @@ -63,6 +63,8 @@ * @return String - the Assignment id */ public String getAssignmentId(); + + public String getSubmitterId(); /** * Access the list of Users who submitted this response to the Assignment. Index: assignment-api/api/src/java/org/sakaiproject/assignment/api/Assignment.java =================================================================== --- assignment-api/api/src/java/org/sakaiproject/assignment/api/Assignment.java (revision 310244) +++ assignment-api/api/src/java/org/sakaiproject/assignment/api/Assignment.java (working copy) @@ -278,8 +278,45 @@ * @return The AssignmentAccess access mode for the Assignment. */ AssignmentAccess getAccess(); + + public Time getPeerAssessmentPeriod(); + + public boolean getPeerAssessmentAnonEval(); + public boolean getPeerAssessmentStudentViewReviews(); + + public int getPeerAssessmentNumReviews(); + + public String getPeerAssessmentInstructions(); /** + * Access whether this AssignmentContent allows peer assessment. + * + * @return true if the AssignmentContent allows peer assessment, false otherwise. + */ + public boolean getAllowPeerAssessment(); + + /** + * peer assessment is set for this assignment and the current time + * falls between the assignment close time and the peer asseessment period time + * @return + */ + public boolean isPeerAssessmentOpen(); + + /** + * peer assessment is set for this assignment but the close time hasn't passed + * @return + */ + public boolean isPeerAssessmentPending(); + + /** + * peer assessment is set for this assignment but the current time is passed + * the peer assessment period + * @return + */ + public boolean isPeerAssessmentClosed(); + + + /** *

* AssignmentAccess enumerates different access modes for the assignment: site-wide or grouped. *

Index: assignment-api/api/src/java/org/sakaiproject/assignment/api/AssignmentPeerAssessmentService.java =================================================================== --- assignment-api/api/src/java/org/sakaiproject/assignment/api/AssignmentPeerAssessmentService.java (revision 0) +++ assignment-api/api/src/java/org/sakaiproject/assignment/api/AssignmentPeerAssessmentService.java (revision 0) @@ -0,0 +1,28 @@ +package org.sakaiproject.assignment.api; + +import java.util.Collection; +import java.util.List; + +import org.sakaiproject.api.app.scheduler.ScheduledInvocationCommand; +import org.sakaiproject.assignment.api.model.PeerAssessmentItem; + +public interface AssignmentPeerAssessmentService extends ScheduledInvocationCommand{ + + public void schedulePeerReview(String assignmentId); + + public void removeScheduledPeerReview(String assignmentId); + + public List getPeerAssessmentItems(String assignmentId, String assessorUserId); + + public PeerAssessmentItem getPeerAssessmentItem(String submissionId, String assessorUserId); + + public void savePeerAssessmentItem(PeerAssessmentItem item); + + public List getPeerAssessmentItems(String submissionId); + + public List getPeerAssessmentItemsByAssignmentId(String assignmentId); + + public List getPeerAssessmentItems(Collection submissionsIds); + + public void updateScore(String submissionId); +} Index: assignment-api/api/src/java/org/sakaiproject/assignment/api/AssignmentEdit.java =================================================================== --- assignment-api/api/src/java/org/sakaiproject/assignment/api/AssignmentEdit.java (revision 310244) +++ assignment-api/api/src/java/org/sakaiproject/assignment/api/AssignmentEdit.java (working copy) @@ -165,4 +165,23 @@ * The Assignment's order. */ public void setPosition_order(int position_order); + + /** + * Does this Assignment allow using the peer assessment? + * + * @param allow - + * true if the Assignment allows peer assessment, false otherwise? + */ + public void setAllowPeerAssessment(boolean allow); + + public void setPeerAssessmentPeriod(Time time); + + public void setPeerAssessmentAnonEval(boolean anonEval); + + public void setPeerAssessmentStudentViewReviews(boolean studentViewReviews); + + public void setPeerAssessmentNumReviews(int numReviews); + + public void setPeerAssessmentInstructions(String instructions); + } Index: assignment-api/api/pom.xml =================================================================== --- assignment-api/api/pom.xml (revision 310244) +++ assignment-api/api/pom.xml (working copy) @@ -40,5 +40,9 @@ org.sakaiproject.taggable sakai-taggable-api + + org.sakaiproject.scheduler + scheduler-api + Index: assignment-bundles/resources/assignment.properties =================================================================== --- assignment-bundles/resources/assignment.properties (revision 310244) +++ assignment-bundles/resources/assignment.properties (working copy) @@ -106,6 +106,7 @@ gen.grading = Grading gen.checked = Checked gen.commented = Commented +gen.comments = Comments gen.regra = Re-grade gen.regrading = Re-grading gen.grarep = Grade Report @@ -143,6 +144,7 @@ gen.orisub2 = Original submission text with the instructor's comments inserted if applicable gen.orisub3 = Last submission text with instructor comments inserted if applicable gen.outof = out of +gen.peerReviews = Peer Reviews gen.pf = P/F gen.pos = Post gen.position = Position @@ -152,6 +154,9 @@ gen.retu = Return gen.backtolist = Back to list gen.reorder = Reorder +gen.review = Review +gen.peerreview = Peer Review +gen.reviewer = Reviewer gen.reordertitle = Organize Default Assignment List View gen.retustud = Save and Release to Student gen.retustudtit = Save and return comments and grade to student @@ -162,6 +167,7 @@ gen.savdragra = Save and Don't Release to Student gen.savdratit = Save comments and grade, but do not return to student yet gen.sca = Scale +gen.score = Score gen.sect = Section gen.settfor = Settings for gen.sorasc = Sort ascending @@ -803,3 +809,41 @@ send.submission.releasereturn.email.options=Released Resubmission Notification Email Options: send.submission.releasereturn.email.none=Do not send notification email to student when the grade is released and resubmission is available send.submission.releasereturn.email=Send notification email to student when the grade is released and resubmission is available + +# Peer Review +peerAssessmentSavedGrading=Comments and/or grade have been saved but not submitted. If you don't submit your review before the due date, it will be submitted for you. +peerAssessmentSavedSubmission=Comments and/or grade have been saved and submitted. +peerAssessmentName=Peer Assessment +peerAssessmentUse=Use peer assessment +peerassessment.note=Peer assessment requires a points grading scale and do not allow group assignments. +peerassessment.periodfinishes=Evaluation Period Finishes: +peerassessment.anonymousEvaluation=Anonymous evaluation +peerassessment.studentViewReviews=Allow students to see reviews of their submissions +peerassessment.numSubmissions=Number of submissions students must review +peerassessment.reviewInstructions=Instructions for reviewers: +peerassessment.specifyNumReview=Please enter a value for the number of submissions a student must review for peer assessment. +peerassessment.invalidNumReview=You must enter a valid number greater than 0 for the number of submissions a student must review for peer assessment. +peerassessment.invliadPeriodTime=You must enter a peer review period date that is at least 10 minutes after the accept until date. +peerassessment.reviewStarts=Review starts on +peerassessment.notavailable=Peer assessment is not available for the assignment you selected. +peerassessment.allSubmitted=You have submitted all of your peer reviews. +peerassessment.instructionsForReviewer=Instructions for the Reviewer +peerassessment.reviewerComments=Reviewer Comments +peerassessment.alert.saveerrorunkown=An error has occurred while saving, please refresh and try again. +peerassessment.alert.saveinvalidscore=You must enter a valid number greater than 0 for the score. +peerassessment.alert.savenoscorecomment=You must enter a grade or a comment in order to submit your peer assessment. +peerassessment.instructions.reviewer=Instructions for the Reviewer +peerassessment.invliadGroupAssignment=Peer assessments are not available for group assignments. +peerassessment.invliadSubmissionTypeAssignment=Peer assessments are not available for non-electronic assignments. +peerassessment.invliadGradeTypeAssignment=Peer assessments are only available when Grade Scale is set to Points. +peerassessment.reviewedby=Reviewed By +peerassessment.reviewergrade=Reviewer Grade +peerassessment.noscore=No score +peerassessment.nocomments=No comments +peerassessment.removereview=Remove Review +peerassessment.restorereview=Restore Review +peerassessment.cantgrade=Instructor grading is disabled until after the review period: +peerassessment.removed=Successfully removed review for student's submission +peerassessment.restored=Successfully restored review for student's submission +peerassessment.peerReviewDueDate=Peer review due date: +peerassessment.peerGradeInfo=You are able to accept or override the averaged peer review grade in this section. Once this grade is released, this is the grade that will appear in the gradebook. \ No newline at end of file Index: assignment-tool/tool/src/java/org/sakaiproject/assignment/tool/AssignmentAction.java =================================================================== --- assignment-tool/tool/src/java/org/sakaiproject/assignment/tool/AssignmentAction.java (revision 310244) +++ assignment-tool/tool/src/java/org/sakaiproject/assignment/tool/AssignmentAction.java (working copy) @@ -69,6 +69,7 @@ import org.sakaiproject.assignment.api.AssignmentContent; import org.sakaiproject.assignment.api.AssignmentContentEdit; import org.sakaiproject.assignment.api.AssignmentEdit; +import org.sakaiproject.assignment.api.AssignmentPeerAssessmentService; import org.sakaiproject.assignment.api.AssignmentSubmission; import org.sakaiproject.assignment.api.AssignmentSubmissionEdit; import org.sakaiproject.assignment.api.model.AssignmentAllPurposeItem; @@ -78,6 +79,7 @@ import org.sakaiproject.assignment.api.model.AssignmentSupplementItemAttachment; import org.sakaiproject.assignment.api.model.AssignmentSupplementItemService; import org.sakaiproject.assignment.api.model.AssignmentSupplementItemWithAttachment; +import org.sakaiproject.assignment.api.model.PeerAssessmentItem; import org.sakaiproject.assignment.cover.AssignmentService; import org.sakaiproject.assignment.taggable.api.AssignmentActivityProducer; import org.sakaiproject.assignment.taggable.tool.DecoratedTaggingProvider; @@ -171,10 +173,23 @@ private static final String ASSIGNMENT_TOOL_ID = "sakai.assignment.grades"; private static final Boolean allowReviewService = ServerConfigurationService.getBoolean("assignment.useContentReview", false); + private static final Boolean allowPeerAssessment = ServerConfigurationService.getBoolean("assignment.usePeerAssessment", true); /** Is the review service available? */ private static final String ALLOW_REVIEW_SERVICE = "allow_review_service"; - + //Peer Assessment + private static final String NEW_ASSIGNMENT_USE_PEER_ASSESSMENT= "new_assignment_use_peer_assessment"; + private static final String NEW_ASSIGNMENT_PEERPERIODMONTH = "new_assignment_peerperiodmonth"; + private static final String NEW_ASSIGNMENT_PEERPERIODDAY = "new_assignment_peerperiodday"; + private static final String NEW_ASSIGNMENT_PEERPERIODYEAR = "new_assignment_peerperiodyear"; + private static final String NEW_ASSIGNMENT_PEERPERIODHOUR = "new_assignment_peerperiodhour"; + private static final String NEW_ASSIGNMENT_PEERPERIODMIN = "new_assignment_peerperiodmin"; + private static final String NEW_ASSIGNMENT_PEERPERIODAMPM = "new_assignment_peerperiodampm"; + private static final String NEW_ASSIGNMENT_PEER_ASSESSMENT_ANON_EVAL= "new_assignment_peer_assessment_anon_eval"; + private static final String NEW_ASSIGNMENT_PEER_ASSESSMENT_STUDENT_VIEW_REVIEWS= "new_assignment_peer_assessment_student_view_review"; + private static final String NEW_ASSIGNMENT_PEER_ASSESSMENT_NUM_REVIEWS= "new_assignment_peer_assessment_num_reviews"; + private static final String NEW_ASSIGNMENT_PEER_ASSESSMENT_INSTRUCTIONS = "new_assignment_peer_assessment_instructions"; + private static final String NEW_ASSIGNMENT_USE_REVIEW_SERVICE = "new_assignment_use_review_service"; private static final String NEW_ASSIGNMENT_ALLOW_STUDENT_VIEW = "new_assignment_allow_student_view"; @@ -420,6 +435,7 @@ private static final String GRADE_SUBMISSION_ALLOW_RESUBMIT = "grade_submission_allow_resubmit"; private static final String GRADE_SUBMISSION_DONE = "grade_submission_done"; + private static final String GRADE_SUBMISSION_SUBMIT = "grade_submission_submit"; /** ******************* instructor's export assignment ***************************** */ private static final String EXPORT_ASSIGNMENT_REF = "export_assignment_ref"; @@ -606,6 +622,10 @@ /** The option view */ private static final String MODE_OPTIONS= "options"; // set in velocity template + + /** Review Edit page for students */ + private static final String MODE_STUDENT_REVIEW_EDIT= "Assignment.mode_student_review_edit"; // set in velocity template + /** ************************* vm names ************************** */ /** The list view of assignments */ private static final String TEMPLATE_LIST_ASSIGNMENTS = "_list_assignments"; @@ -658,6 +678,9 @@ /** The instructor view to upload all information from archive file */ private static final String TEMPLATE_INSTRUCTOR_UPLOAD_ALL = "_instructor_uploadAll"; + /** The student view to edit reviews **/ + private static final String TEMPLATE_STUDENT_REVIEW_EDIT = "_student_review_edit"; + /** The options page */ private static final String TEMPLATE_OPTIONS = "_options"; @@ -691,6 +714,12 @@ /** the user and submission list for list of submissions page */ private static final String USER_SUBMISSIONS = "user_submissions"; + /** the items for storing the comments and grades for peer assessment **/ + private static final String PEER_ASSESSMENT_ITEMS = "peer_assessment_items"; + + private static final String PEER_ASSESSMENT_ASSESSOR_ID = "peer_assessment_assesor_id"; + private static final String PEER_ASSESSMENT_REMOVED_STATUS = "peer_assessment_removed_status"; + /** ************************* Taggable constants ************************** */ /** identifier of tagging provider that will provide the appropriate helper */ private static final String PROVIDER_ID = "providerId"; @@ -793,6 +822,11 @@ /** To know if grade_submission go from view_students_assignment view or not **/ private static final String FROM_VIEW = "from_view"; + private AssignmentPeerAssessmentService assignmentPeerAssessmentService; + public void setAssignmentPeerAssessmentService(AssignmentPeerAssessmentService assignmentPeerAssessmentService){ + this.assignmentPeerAssessmentService = assignmentPeerAssessmentService; + } + /** * central place for dispatching the build routines based on the state name */ @@ -847,6 +881,13 @@ context.put("reviewServiceUse", reviewServiceUse); } + //Peer Assessment + context.put("allowPeerAssessment", allowPeerAssessment); + if(allowPeerAssessment){ + context.put("peerAssessmentName", rb.getString("peerAssessmentName")); + context.put("peerAssessmentUse", rb.getString("peerAssessmentUse")); + } + // grading option context.put("withGrade", state.getAttribute(WITH_GRADES)); @@ -1021,8 +1062,12 @@ template = build_options_context(portlet, context, data, state); } } + + else if (mode.equals(MODE_STUDENT_REVIEW_EDIT)) + { + template = build_student_review_edit_context(portlet, context, data, state); + } - if (template == null) { // default to student list view @@ -1592,8 +1636,44 @@ context.put("submission", submission); // can the student view model answer or not - canViewAssignmentIntoContext(context, assignment, submission); - } + canViewAssignmentIntoContext(context, assignment, submission); + + + //peer review + if(assignment.getAllowPeerAssessment() + && assignment.getPeerAssessmentStudentViewReviews() + && assignment.isPeerAssessmentClosed()){ + List reviews = assignmentPeerAssessmentService.getPeerAssessmentItems(submission.getId()); + if(reviews != null){ + List completedReviews = new ArrayList(); + for(PeerAssessmentItem review : reviews){ + if(!review.isRemoved() && (review.getScore() != null || (review.getComment() != null && !"".equals(review.getComment().trim())))){ + //only show peer reviews that have either a score or a comment saved + if(assignment.getPeerAssessmentAnonEval()){ + //annonymous eval + review.setAssessorDisplayName(rb.get("gen.reviewer").toString() + " " + (completedReviews.size() + 1)); + }else{ + //need to set the assessor's display name + try { + review.setAssessorDisplayName(UserDirectoryService.getUser(review.getAssessorUserId()).getDisplayName()); + } catch (UserNotDefinedException e) { + //reviewer doesn't exist or userId is wrong + M_log.error(e.getMessage(), e); + //set a default one: + review.setAssessorDisplayName(rb.get("gen.reviewer").toString() + " " + (completedReviews.size() + 1)); + } + } + completedReviews.add(review); + + } + } + if(completedReviews.size() > 0){ + context.put("peerReviews", completedReviews); + } + } + } + + } TaggingManager taggingManager = (TaggingManager) ComponentManager .get("org.sakaiproject.taggable.api.TaggingManager"); @@ -1680,7 +1760,33 @@ List assignments = prepPage(state); context.put("assignments", assignments.iterator()); - + // allow add assignment? + Map peerAssessmentItemsMap = new HashMap(); + boolean allowAddAssignment = AssignmentService.allowAddAssignment(contextString); + if(!allowAddAssignment){ + //this is the same requirement for displaying the assignment link for students + //now lets create a map for peer reviews for each eligible assignment + for(Assignment assignment : (List) assignments){ + if(assignment.getAllowPeerAssessment() && assignment.isPeerAssessmentOpen()){ + List items = assignmentPeerAssessmentService.getPeerAssessmentItems(assignment.getId(), UserDirectoryService.getCurrentUser().getId()); + if(items == null || items.size() == 0){ + //something is wrong here, there are no peer assessments for this user, set to -1 for UI to know + peerAssessmentItemsMap.put(assignment.getId(), -1); + }else{ + Integer count = 0; + //only count peer review items that the student hasn't submitted + for(PeerAssessmentItem item : items){ + if(!item.isSubmitted()){ + count++; + } + } + peerAssessmentItemsMap.put(assignment.getId(), count); + } + } + } + } + context.put("peerAssessmentItemsMap", peerAssessmentItemsMap); + // allow get assignment context.put("allowGetAssignment", Boolean.valueOf(AssignmentService.allowGetAssignment(contextString))); @@ -1817,7 +1923,11 @@ { // put the names and values into vm file - + context.put("name_UsePeerAssessment", NEW_ASSIGNMENT_USE_PEER_ASSESSMENT); + context.put("name_PeerAssessmentAnonEval", NEW_ASSIGNMENT_PEER_ASSESSMENT_ANON_EVAL); + context.put("name_PeerAssessmentStudentViewReviews", NEW_ASSIGNMENT_PEER_ASSESSMENT_STUDENT_VIEW_REVIEWS); + context.put("name_PeerAssessmentNumReviews", NEW_ASSIGNMENT_PEER_ASSESSMENT_NUM_REVIEWS); + context.put("name_PeerAssessmentInstructions", NEW_ASSIGNMENT_PEER_ASSESSMENT_INSTRUCTIONS); context.put("name_UseReviewService", NEW_ASSIGNMENT_USE_REVIEW_SERVICE); context.put("name_AllowStudentView", NEW_ASSIGNMENT_ALLOW_STUDENT_VIEW); context.put("name_NEW_ASSIGNMENT_REVIEW_SERVICE_SUBMIT_RADIO", NEW_ASSIGNMENT_REVIEW_SERVICE_SUBMIT_RADIO); @@ -1891,7 +2001,14 @@ context.put("value_GradePoints", displayGrade(state, maxGrade)); context.put("value_Description", state.getAttribute(NEW_ASSIGNMENT_DESCRIPTION)); - + //Peer Assessment + context.put("value_UsePeerAssessment", state.getAttribute(NEW_ASSIGNMENT_USE_PEER_ASSESSMENT)); + context.put("value_PeerAssessmentAnonEval", state.getAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_ANON_EVAL)); + context.put("value_PeerAssessmentStudentViewReviews", state.getAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_STUDENT_VIEW_REVIEWS)); + context.put("value_PeerAssessmentNumReviews", state.getAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_NUM_REVIEWS)); + context.put("value_PeerAssessmentInstructions", state.getAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_INSTRUCTIONS)); + putTimePropertiesInContext(context, state, "PeerPeriod", NEW_ASSIGNMENT_PEERPERIODMONTH, NEW_ASSIGNMENT_PEERPERIODDAY, NEW_ASSIGNMENT_PEERPERIODYEAR, NEW_ASSIGNMENT_PEERPERIODHOUR, NEW_ASSIGNMENT_PEERPERIODMIN, NEW_ASSIGNMENT_PEERPERIODAMPM); + // Keep the use review service setting context.put("value_UseReviewService", state.getAttribute(NEW_ASSIGNMENT_USE_REVIEW_SERVICE)); context.put("value_AllowStudentView", state.getAttribute(NEW_ASSIGNMENT_ALLOW_STUDENT_VIEW) == null ? Boolean.toString(ServerConfigurationService.getBoolean("turnitin.allowStudentView.default", false)) : state.getAttribute(NEW_ASSIGNMENT_ALLOW_STUDENT_VIEW)); @@ -2581,6 +2698,13 @@ state.removeAttribute(GRADE_SUBMISSION_DONE); } + // put the grade confirmation message if applicable + if (state.getAttribute(GRADE_SUBMISSION_SUBMIT) != null) + { + context.put("gradingSubmit", Boolean.TRUE); + state.removeAttribute(GRADE_SUBMISSION_SUBMIT); + } + // letter grading letterGradeOptionsIntoContext(context); @@ -2608,6 +2732,54 @@ return time; } + public void doPrev_back_next_submission_review(RunData rundata, String option, boolean submit) + { + SessionState state = ((JetspeedRunData) rundata).getPortletSessionState(((JetspeedRunData) rundata).getJs_peid()); + // save the instructor input + boolean hasChange = saveReviewGradeForm(rundata, state, submit ? "submit" : "save"); + + if (state.getAttribute(STATE_MESSAGE) == null) + { + ParameterParser params = rundata.getParameters(); + List submissionIds = new ArrayList(); + if(state.getAttribute(USER_SUBMISSIONS) != null){ + submissionIds = (List) state.getAttribute(USER_SUBMISSIONS); + } + + String submissionId = null; + String assessorId = null; + if ("next".equals(option)) + { + submissionId = params.get("nextSubmissionId"); + assessorId = params.get("nextAssessorId"); + } + else if ("prev".equals(option)) + { + submissionId = params.get("prevSubmissionId"); + assessorId = params.get("prevAssessorId"); + } + else if ("back".equals(option)) + { + String assignmentId = (String) state.getAttribute(VIEW_ASSIGNMENT_ID); + List userSubmissionsState = state.getAttribute(STATE_PAGEING_TOTAL_ITEMS) != null ? (List) state.getAttribute(STATE_PAGEING_TOTAL_ITEMS):null; + if(userSubmissionsState != null && userSubmissionsState.size() > 0 && userSubmissionsState.get(0) instanceof UserSubmission + && AssignmentService.allowGradeSubmission(assignmentId)){ + //coming from instructor view submissions page + state.setAttribute(STATE_MODE, MODE_INSTRUCTOR_GRADE_ASSIGNMENT); + }else{ + state.setAttribute(STATE_MODE, MODE_LIST_ASSIGNMENTS); + } + } + if(submissionId != null && submissionIds.contains(submissionId)){ + state.setAttribute(GRADE_SUBMISSION_SUBMISSION_ID, submissionId); + } + if(assessorId != null){ + state.setAttribute(PEER_ASSESSMENT_ASSESSOR_ID, assessorId); + } + } + } + + /** * Responding to the request of submission navigation * @param rundata @@ -2872,6 +3044,52 @@ state.setAttribute(USER_SUBMISSIONS, userSubmissions); context.put("userSubmissions", state.getAttribute(USER_SUBMISSIONS)); + + //find peer assessment grades if exist + if(assignment.getAllowPeerAssessment()){ + List submissionIds = new ArrayList(); + //get list of submission ids to look up reviews in db + for(UserSubmission s : userSubmissions){ + submissionIds.add(s.getSubmission().getId()); + } + //look up reviews for these submissions + List items = assignmentPeerAssessmentService.getPeerAssessmentItems(submissionIds); + //create a map for velocity to use in displaying the submission reviews + Map> itemsMap = new HashMap>(); + Map reviewersMap = new HashMap(); + if(items != null){ + for(PeerAssessmentItem item : items){ + //update items map + List sItems = itemsMap.get(item.getSubmissionId()); + if(sItems == null){ + sItems = new ArrayList(); + } + sItems.add(item); + itemsMap.put(item.getSubmissionId(), sItems); + //update users map: + User u = reviewersMap.get(item.getAssessorUserId()); + if(u == null){ + try { + u = UserDirectoryService.getUser(item.getAssessorUserId()); + reviewersMap.put(item.getAssessorUserId(), u); + } catch (UserNotDefinedException e) { + M_log.warn(e.getMessage(), e); + } + } + } + } + //go through all the submissions and make sure there aren't any nulls + for(String id : submissionIds){ + List sItems = itemsMap.get(id); + if(sItems == null){ + sItems = new ArrayList(); + itemsMap.put(id, sItems); + } + } + context.put("peerAssessmentItems", itemsMap); + context.put("reviewersMap", reviewersMap); + } + // whether to show the resubmission choice if (state.getAttribute(SHOW_ALLOW_RESUBMISSION) != null) { @@ -3111,9 +3329,323 @@ String template = (String) getContext(data).get("template"); return template + TEMPLATE_INSTRUCTOR_REORDER_ASSIGNMENT; - + } // build_instructor_reorder_assignment_context + protected String build_student_review_edit_context(VelocityPortlet portlet, Context context, RunData data, SessionState state){ + int gradeType = -1; + context.put("context", state.getAttribute(STATE_CONTEXT_STRING)); + List peerAssessmentItems = (List) state.getAttribute(PEER_ASSESSMENT_ITEMS); + String assignmentId = (String) state.getAttribute(VIEW_ASSIGNMENT_ID); + User sessionUser = (User) state.getAttribute(STATE_USER); + String assessorId = sessionUser.getId(); + if(state.getAttribute(PEER_ASSESSMENT_ASSESSOR_ID) != null){ + assessorId = (String) state.getAttribute(PEER_ASSESSMENT_ASSESSOR_ID); + } + Assignment assignment = getAssignment(assignmentId, "build_student_review_edit_context", state); + if (assignment != null){ + context.put("assignment", assignment); + if (assignment.getContent() != null) + { + gradeType = assignment.getContent().getTypeOfGrade(); + } + context.put("peerAssessmentInstructions", assignment.getPeerAssessmentInstructions() == null ? "" : assignment.getPeerAssessmentInstructions()); + } + String submissionId = ""; + SecurityAdvisor secAdv = new SecurityAdvisor(){ + @Override + public SecurityAdvice isAllowed(String userId, String function, + String reference) { + if("asn.submit".equals(function) || "asn.submit".equals(function) || "asn.grade".equals(function)){ + return SecurityAdvice.ALLOWED; + } + return null; + } + }; + AssignmentSubmission s = null; + try{ + //surround with a try/catch/finally for the security advisor + m_securityService.pushAdvisor(secAdv); + s = getSubmission((String) state.getAttribute(GRADE_SUBMISSION_SUBMISSION_ID), "build_student_review_edit_context", state); + m_securityService.popAdvisor(secAdv); + }catch(Exception e){ + M_log.error(e.getMessage(), e); + }finally{ + if(secAdv != null){ + m_securityService.popAdvisor(secAdv); + } + } + if (s != null) + { + submissionId = s.getId(); + context.put("submission", s); + + ResourceProperties p = s.getProperties(); + if (p.getProperty(ResourceProperties.PROP_SUBMISSION_PREVIOUS_FEEDBACK_TEXT) != null) + { + context.put("prevFeedbackText", p.getProperty(ResourceProperties.PROP_SUBMISSION_PREVIOUS_FEEDBACK_TEXT)); + } + + if (p.getProperty(ResourceProperties.PROP_SUBMISSION_PREVIOUS_FEEDBACK_COMMENT) != null) + { + context.put("prevFeedbackComment", p.getProperty(ResourceProperties.PROP_SUBMISSION_PREVIOUS_FEEDBACK_COMMENT)); + } + + if (p.getProperty(PROP_SUBMISSION_PREVIOUS_FEEDBACK_ATTACHMENTS) != null) + { + context.put("prevFeedbackAttachments", getPrevFeedbackAttachments(p)); + } + if ((s.getFeedbackText() == null) || (s.getFeedbackText().length() == 0)) + { + context.put("value_feedback_text", s.getSubmittedText()); + } + else + { + context.put("value_feedback_text", s.getFeedbackFormattedText()); + } + List v = EntityManager.newReferenceList(); + Iterator attachments = s.getFeedbackAttachments().iterator(); + while (attachments.hasNext()) + { + v.add(attachments.next()); + } + context.put("value_feedback_attachment", v); + state.setAttribute(ATTACHMENTS, v); + } + if(peerAssessmentItems != null && submissionId != null){ + //find the peerAssessmentItem for this submission: + PeerAssessmentItem peerAssessmentItem = null; + for(PeerAssessmentItem item : peerAssessmentItems){ + if(submissionId.equals(item.getSubmissionId()) + && assessorId.equals(item.getAssessorUserId())){ + peerAssessmentItem = item; + break; + } + } + if(peerAssessmentItem != null){ + //check if current user is the peer assessor, if not, only display data (no editing) + if(!sessionUser.getId().equals(peerAssessmentItem.getAssessorUserId())){ + context.put("view_only", true); + try { + User reviewer = UserDirectoryService.getUser(peerAssessmentItem.getAssessorUserId()); + context.put("reviewer", reviewer); + } catch (UserNotDefinedException e) { + M_log.warn(e.getMessage(), e); + } + }else{ + context.put("view_only", false); + } + + //scores are saved as whole values + //so a score of 1.3 would be stored as 13 + //so a DB score of 13 needs to be 1.3: + if(peerAssessmentItem.getScore() != null){ + double score = peerAssessmentItem.getScore()/10.0; + context.put("value_grade", score); + }else{ + context.put("value_grade", null); + } + context.put("display_grade", peerAssessmentItem.getScoreDisplay()); + context.put("item_removed", peerAssessmentItem.isRemoved()); + context.put("value_feedback_comment", peerAssessmentItem.getComment()); + + //set previous/next values + List userSubmissionsState = state.getAttribute(STATE_PAGEING_TOTAL_ITEMS) != null ? (List) state.getAttribute(STATE_PAGEING_TOTAL_ITEMS):null; + List userSubmissions = new ArrayList(); + boolean instructorView = false; + if(userSubmissionsState != null && userSubmissionsState.size() > 0 && userSubmissionsState.get(0) instanceof UserSubmission){ + //from instructor view/ + //dd + for(UserSubmission userSubmission : (List) userSubmissionsState){ + if(!userSubmissions.contains(userSubmission.getSubmission().getId()) + && userSubmission.getSubmission().getSubmitted()){ + userSubmissions.add(userSubmission.getSubmission().getId()); + } + } + }else{ + //student view + for(PeerAssessmentItem item : peerAssessmentItems){ + if(!userSubmissions.contains(item.getSubmissionId()) && !item.isSubmitted()){ + userSubmissions.add(item.getSubmissionId()); + } + } + } + if(userSubmissions != null){ + context.put("totalReviews", userSubmissions.size()); + //first setup map to make the navigation logic easier: + Map> itemMap = new HashMap>(); + for(String userSubmissionId : userSubmissions){ + for (PeerAssessmentItem item : peerAssessmentItems){ + if(userSubmissionId.equals(item.getSubmissionId())){ + List items = itemMap.get(userSubmissionId); + if(items == null){ + items = new ArrayList(); + } + items.add(item); + itemMap.put(item.getSubmissionId(), items); + } + } + } + for(int i = 0; i < userSubmissions.size(); i++){ + String userSubmissionId = userSubmissions.get(i); + if(userSubmissionId.equals(submissionId)){ + //we found the right submission, now find the items + context.put("reviewNumber", (i + 1)); + List submissionItems = itemMap.get(submissionId); + if(submissionItems != null){ + for (int j = 0; j < submissionItems.size(); j++){ + PeerAssessmentItem item = submissionItems.get(j); + if(item.getAssessorUserId().equals(assessorId)){ + context.put("anonNumber", i + 1); + boolean goPT = false; + boolean goNT = false; + if ((i - 1) >= 0 || (j - 1) >= 0) + { + goPT = true; + } + if ((i + 1) < userSubmissions.size() || (j + 1) < submissionItems.size()) + { + goNT = true; + } + context.put("goPTButton", Boolean.valueOf(goPT)); + context.put("goNTButton", Boolean.valueOf(goNT)); + + if (j>0) + { + // retrieve the previous submission id + context.put("prevSubmissionId", (submissionItems.get(j-1).getSubmissionId())); + context.put("prevAssessorId", (submissionItems.get(j-1).getAssessorUserId())); + }else if(i > 0){ + //go to previous submission and grab the last item in that list + int k = i - 1; + while(k >= 0 && !itemMap.containsKey(userSubmissions.get(k))){ + k--; + } + if(k >= 0 && itemMap.get(userSubmissions.get(k)).size() > 0){ + List pItems = itemMap.get(userSubmissions.get(k)); + PeerAssessmentItem pItem = pItems.get(pItems.size() - 1); + context.put("prevSubmissionId", (pItem.getSubmissionId())); + context.put("prevAssessorId", (pItem.getAssessorUserId())); + }else{ + //no previous option, set to false + context.put("goPTButton", Boolean.valueOf(false)); + } + } + + if (j < submissionItems.size() - 1) + { + // retrieve the next submission id + context.put("nextSubmissionId", (submissionItems.get(j+1).getSubmissionId())); + context.put("nextAssessorId", (submissionItems.get(j+1).getAssessorUserId())); + }else if (i < userSubmissions.size() - 1){ + //go to previous submission and grab the last item in that list + int k = i + 1; + while(k < userSubmissions.size() && !itemMap.containsKey(userSubmissions.get(k))){ + k++; + } + if(k < userSubmissions.size() && itemMap.get(userSubmissions.get(k)).size() > 0){ + List pItems = itemMap.get(userSubmissions.get(k)); + PeerAssessmentItem pItem = pItems.get(0); + context.put("nextSubmissionId", (pItem.getSubmissionId())); + context.put("nextAssessorId", (pItem.getAssessorUserId())); + }else{ + //no next option, set to false + context.put("goNTButton", Boolean.valueOf(false)); + } + } + } + } + } + } + } + } + /*else{ + //not an instructor, so no need to worry about submission order + for (int i = 0; i < peerAssessmentItems.size(); i++) + { + if (peerAssessmentItems.get(i).getSubmissionId().equals(submissionId) + && assessorId.equals(peerAssessmentItems.get(i).getAssessorUserId())) + { + context.put("anonNumber", i + 1); + boolean goPT = false; + boolean goNT = false; + if ((i - 1) >= 0) + { + goPT = true; + } + if ((i + 1) < peerAssessmentItems.size()) + { + goNT = true; + } + context.put("goPTButton", Boolean.valueOf(goPT)); + context.put("goNTButton", Boolean.valueOf(goNT)); + + if (i>0) + { + // retrieve the previous submission id + context.put("prevSubmissionId", (peerAssessmentItems.get(i-1).getSubmissionId())); + context.put("prevAssessorId", (peerAssessmentItems.get(i-1).getAssessorUserId())); + } + + if (i < peerAssessmentItems.size() - 1) + { + // retrieve the next submission id + context.put("nextSubmissionId", (peerAssessmentItems.get(i+1).getSubmissionId())); + context.put("nextAssessorId", (peerAssessmentItems.get(i+1).getAssessorUserId())); + } + } + } + }*/ + } + + } + + context.put("assignment_expand_flag", state.getAttribute(GRADE_SUBMISSION_ASSIGNMENT_EXPAND_FLAG)); + context.put("user", sessionUser); + context.put("submissionTypeTable", submissionTypeTable()); + context.put("instructorAttachments", state.getAttribute(ATTACHMENTS)); + context.put("contentTypeImageService", state.getAttribute(STATE_CONTENT_TYPE_IMAGE_SERVICE)); + context.put("service", AssignmentService.getInstance()); + // names + context.put("name_grade_assignment_id", GRADE_SUBMISSION_ASSIGNMENT_ID); + context.put("name_feedback_comment", GRADE_SUBMISSION_FEEDBACK_COMMENT); + context.put("name_feedback_text", GRADE_SUBMISSION_FEEDBACK_TEXT); + context.put("name_feedback_attachment", GRADE_SUBMISSION_FEEDBACK_ATTACHMENT); + context.put("name_grade", GRADE_SUBMISSION_GRADE); + context.put("name_allowResubmitNumber", AssignmentSubmission.ALLOW_RESUBMIT_NUMBER); + // put supplement item into context + try{ + //surround with a try/catch/finally for the security advisor + m_securityService.pushAdvisor(secAdv); + supplementItemIntoContext(state, context, assignment, null); + }catch(Exception e){ + M_log.error(e.getMessage(), e); + }finally{ + if(secAdv != null){ + m_securityService.popAdvisor(secAdv); + } + } + // put the grade confirmation message if applicable + if (state.getAttribute(GRADE_SUBMISSION_DONE) != null) + { + context.put("gradingDone", Boolean.TRUE); + state.removeAttribute(GRADE_SUBMISSION_DONE); + if(state.getAttribute(PEER_ASSESSMENT_REMOVED_STATUS) != null){ + context.put("itemRemoved", state.getAttribute(PEER_ASSESSMENT_REMOVED_STATUS)); + state.removeAttribute(PEER_ASSESSMENT_REMOVED_STATUS); + } + } + // put the grade confirmation message if applicable + if (state.getAttribute(GRADE_SUBMISSION_SUBMIT) != null) + { + context.put("gradingSubmit", Boolean.TRUE); + state.removeAttribute(GRADE_SUBMISSION_SUBMIT); + } + + String template = (String) getContext(data).get("template"); + return template + TEMPLATE_STUDENT_REVIEW_EDIT; + } + /** * build the instructor view to view the list of students for an assignment */ @@ -3904,6 +4436,7 @@ state.removeAttribute(GRADE_SUBMISSION_SUBMISSION_ID); state.removeAttribute(GRADE_GREATER_THAN_MAX_ALERT); state.removeAttribute(GRADE_SUBMISSION_DONE); + state.removeAttribute(GRADE_SUBMISSION_SUBMIT); state.removeAttribute(AssignmentSubmission.ALLOW_RESUBMIT_NUMBER); resetAllowResubmitParams(state); } @@ -3984,6 +4517,60 @@ } // doSave_grade_submission + public void doSave_grade_submission_review(RunData data) + { + SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); + saveReviewGradeForm(data, state, "save"); + } + + public void doSave_toggle_remove_review(RunData data) + { + SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); + if(state.getAttribute(PEER_ASSESSMENT_ASSESSOR_ID) != null){ + String peerAssessor = (String) state.getAttribute(PEER_ASSESSMENT_ASSESSOR_ID); + ParameterParser params = data.getParameters(); + String submissionRef = params.getString("submissionId"); + String submissionId = null; + if(submissionRef != null){ + int i = submissionRef.lastIndexOf(Entity.SEPARATOR); + if (i == -1){ + submissionId = submissionRef; + }else{ + submissionId = submissionRef.substring(i + 1); + } + } + if(submissionId != null){ + //call the DB to make sure this user can edit this assessment, otherwise it wouldn't exist + PeerAssessmentItem item = assignmentPeerAssessmentService.getPeerAssessmentItem(submissionId, peerAssessor); + if(item != null){ + item.setRemoved(!item.isRemoved()); + assignmentPeerAssessmentService.savePeerAssessmentItem(item); + if(item.getScore() != null){ + //item was part of the calculation, re-calculate + assignmentPeerAssessmentService.updateScore(submissionId); + } + state.setAttribute(GRADE_SUBMISSION_DONE, Boolean.TRUE); + state.setAttribute(PEER_ASSESSMENT_REMOVED_STATUS, item.isRemoved()); + //update session state: + List peerAssessmentItems = (List) state.getAttribute(PEER_ASSESSMENT_ITEMS); + if(peerAssessmentItems != null){ + for(int i = 0; i < peerAssessmentItems.size(); i++) { + PeerAssessmentItem sItem = peerAssessmentItems.get(i); + if(sItem.getSubmissionId().equals(item.getSubmissionId()) + && sItem.getAssessorUserId().equals(item.getAssessorUserId())){ + //found it, just update it + peerAssessmentItems.set(i, item); + state.setAttribute(PEER_ASSESSMENT_ITEMS, peerAssessmentItems); + break; + } + } + } + } + } + } + } + + /** * Action is to release the grade to submission */ @@ -4740,6 +5327,8 @@ state.setAttribute(NEW_ASSIGNMENT_SECTION, sections_string); state.setAttribute(NEW_ASSIGNMENT_SUBMISSION_TYPE, Integer.valueOf(params.getString(NEW_ASSIGNMENT_SUBMISSION_TYPE))); + Integer submissionType = Integer.valueOf(params.getString(NEW_ASSIGNMENT_SUBMISSION_TYPE)); + state.setAttribute(NEW_ASSIGNMENT_SUBMISSION_TYPE, submissionType); // Skip category if it was never set. Long catInt = Long.valueOf(-1); @@ -4756,9 +5345,72 @@ state.setAttribute(NEW_ASSIGNMENT_GRADE_TYPE, Integer.valueOf(gradeType)); } + //Peer Assessment + boolean peerAssessment = false; + String r = params.getString(NEW_ASSIGNMENT_USE_PEER_ASSESSMENT); + String b; + if (r == null){ + b = Boolean.FALSE.toString(); + }else{ + b = Boolean.TRUE.toString(); + peerAssessment = true; + } + state.setAttribute(NEW_ASSIGNMENT_USE_PEER_ASSESSMENT, b); + if(peerAssessment){ + //do not allow non-electronic assignments + if(Assignment.NON_ELECTRONIC_ASSIGNMENT_SUBMISSION == submissionType){ + addAlert(state, rb.getString("peerassessment.invliadSubmissionTypeAssignment")); + } + if (gradeType != Assignment.SCORE_GRADE_TYPE){ + addAlert(state, rb.getString("peerassessment.invliadGradeTypeAssignment")); + } - String r = params.getString(NEW_ASSIGNMENT_USE_REVIEW_SERVICE); - String b; + + Time peerPeriodTime = putTimeInputInState(params, state, NEW_ASSIGNMENT_PEERPERIODMONTH, NEW_ASSIGNMENT_PEERPERIODDAY, NEW_ASSIGNMENT_PEERPERIODYEAR, NEW_ASSIGNMENT_PEERPERIODHOUR, NEW_ASSIGNMENT_PEERPERIODMIN, NEW_ASSIGNMENT_PEERPERIODAMPM, "newassig.opedat"); + GregorianCalendar peerPeriodMinTimeCal = new GregorianCalendar(); + peerPeriodMinTimeCal.setTimeInMillis(closeTime.getTime()); + peerPeriodMinTimeCal.add(GregorianCalendar.MINUTE, 10); + GregorianCalendar peerPeriodTimeCal = new GregorianCalendar(); + peerPeriodTimeCal.setTimeInMillis(peerPeriodTime.getTime()); + //peer assessment must complete at a minimum of 10 mins after close time + if(peerPeriodTimeCal.before(peerPeriodMinTimeCal)){ + addAlert(state, rb.getString("peerassessment.invliadPeriodTime")); + } + } + + + r = params.getString(NEW_ASSIGNMENT_PEER_ASSESSMENT_ANON_EVAL); + if (r == null) b = Boolean.FALSE.toString(); + else b = Boolean.TRUE.toString(); + state.setAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_ANON_EVAL, b); + + r = params.getString(NEW_ASSIGNMENT_PEER_ASSESSMENT_STUDENT_VIEW_REVIEWS); + if (r == null) b = Boolean.FALSE.toString(); + else b = Boolean.TRUE.toString(); + state.setAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_STUDENT_VIEW_REVIEWS, b); + if(peerAssessment){ + if(params.get(NEW_ASSIGNMENT_PEER_ASSESSMENT_NUM_REVIEWS) != null && !"".equals(params.get(NEW_ASSIGNMENT_PEER_ASSESSMENT_NUM_REVIEWS))){ + try{ + int peerAssessmentNumOfReviews = Integer.parseInt(params.getString(NEW_ASSIGNMENT_PEER_ASSESSMENT_NUM_REVIEWS)); + if(peerAssessmentNumOfReviews > 0){ + state.setAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_NUM_REVIEWS, Integer.valueOf(peerAssessmentNumOfReviews)); + }else{ + addAlert(state, rb.getString("peerassessment.invalidNumReview")); + } + }catch(Exception e){ + addAlert(state, rb.getString("peerassessment.invalidNumReview")); + } + }else{ + addAlert(state, rb.getString("peerassessment.specifyNumReview")); + } + } + + String peerAssessmentInstructions = processFormattedTextFromBrowser(state, params.getString(NEW_ASSIGNMENT_PEER_ASSESSMENT_INSTRUCTIONS), true); + state.setAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_INSTRUCTIONS, peerAssessmentInstructions); + + //REVIEW SERVICE + r = params.getString(NEW_ASSIGNMENT_USE_REVIEW_SERVICE); + // set whether we use the review service or not if (r == null) b = Boolean.FALSE.toString(); else b = Boolean.TRUE.toString(); @@ -5290,6 +5942,28 @@ } // doHide_preview_assignment_student_view /** + *Action is to hide the preview assignment student view + */ + public void doHide_submission_assignment_instruction_review(RunData data) + { + SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); + state.setAttribute(GRADE_SUBMISSION_ASSIGNMENT_EXPAND_FLAG, Boolean.valueOf(false)); + + // save user input + saveReviewGradeForm(data, state, "read"); + + } + + public void doShow_submission_assignment_instruction_review(RunData data) + { + SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); + state.setAttribute(GRADE_SUBMISSION_ASSIGNMENT_EXPAND_FLAG, Boolean.valueOf(true)); + + // save user input + saveReviewGradeForm(data, state, "read"); + } + + /** * Action is to show the preview assignment student view */ public void doShow_submission_assignment_instruction(RunData data) @@ -5588,7 +6262,18 @@ // resubmit option is not allowed for non-electronic type allowResubmitNumber = null; } - + //Peer Assessment + boolean usePeerAssessment = "true".equalsIgnoreCase((String) state.getAttribute(NEW_ASSIGNMENT_USE_PEER_ASSESSMENT)); + Time peerPeriodTime = getTimeFromState(state, NEW_ASSIGNMENT_PEERPERIODMONTH, NEW_ASSIGNMENT_PEERPERIODDAY, NEW_ASSIGNMENT_PEERPERIODYEAR, NEW_ASSIGNMENT_PEERPERIODHOUR, NEW_ASSIGNMENT_PEERPERIODMIN, NEW_ASSIGNMENT_PEERPERIODAMPM); + boolean peerAssessmentAnonEval = "true".equalsIgnoreCase((String) state.getAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_ANON_EVAL)); + boolean peerAssessmentStudentViewReviews = "true".equalsIgnoreCase((String) state.getAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_STUDENT_VIEW_REVIEWS)); + int peerAssessmentNumReviews = 0; + if(state.getAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_NUM_REVIEWS) != null){ + peerAssessmentNumReviews = ((Integer) state.getAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_NUM_REVIEWS)).intValue(); + } + String peerAssessmentInstructions = (String) state.getAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_INSTRUCTIONS); + + //Review Service boolean useReviewService = "true".equalsIgnoreCase((String) state.getAttribute(NEW_ASSIGNMENT_USE_REVIEW_SERVICE)); boolean allowStudentViewReport = "true".equalsIgnoreCase((String) state.getAttribute(NEW_ASSIGNMENT_ALLOW_STUDENT_VIEW)); @@ -5667,8 +6352,8 @@ } // comment the changes to Assignment object - commitAssignmentEdit(state, post, ac, a, title, openTime, dueTime, closeTime, enableCloseDate, section, range, groups); - + commitAssignmentEdit(state, post, ac, a, title, openTime, dueTime, closeTime, enableCloseDate, section, range, groups, + usePeerAssessment,peerPeriodTime, peerAssessmentAnonEval, peerAssessmentStudentViewReviews, peerAssessmentNumReviews, peerAssessmentInstructions); if (post) { // we need to update the submission @@ -6555,7 +7240,8 @@ } } - private void commitAssignmentEdit(SessionState state, boolean post, AssignmentContentEdit ac, AssignmentEdit a, String title, Time openTime, Time dueTime, Time closeTime, boolean enableCloseDate, String s, String range, Collection groups) + private void commitAssignmentEdit(SessionState state, boolean post, AssignmentContentEdit ac, AssignmentEdit a, String title, Time openTime, Time dueTime, Time closeTime, boolean enableCloseDate, String s, String range, Collection groups, + boolean usePeerAssessment, Time peerPeriodTime, boolean peerAssessmentAnonEval, boolean peerAssessmentStudentViewReviews, int peerAssessmentNumReviews, String peerAssessmentInstructions) { a.setTitle(title); a.setContent(ac); @@ -6577,7 +7263,14 @@ a.setCloseTime(null); } } - + + a.setAllowPeerAssessment(usePeerAssessment); + a.setPeerAssessmentPeriod(peerPeriodTime); + a.setPeerAssessmentAnonEval(peerAssessmentAnonEval); + a.setPeerAssessmentStudentViewReviews(peerAssessmentStudentViewReviews); + a.setPeerAssessmentNumReviews(peerAssessmentNumReviews); + a.setPeerAssessmentInstructions(peerAssessmentInstructions); + // post the assignment a.setDraft(!post); @@ -6971,6 +7664,79 @@ } // doView_assignment_as_student + public void doView_submissionReviews(RunData data){ + String submissionId = data.getParameters().getString("submissionId"); + SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); + String assessorId = data.getParameters().getString("assessorId"); + String assignmentId = StringUtils.trimToNull(data.getParameters().getString("assignmentId")); + Assignment a = getAssignment(assignmentId, "doEdit_assignment", state); + if (submissionId != null && !"".equals(submissionId) && a != null){ + //set the page to go to + state.setAttribute(VIEW_ASSIGNMENT_ID, assignmentId); + List peerAssessmentItems = assignmentPeerAssessmentService.getPeerAssessmentItemsByAssignmentId(a.getId()); + state.setAttribute(PEER_ASSESSMENT_ITEMS, peerAssessmentItems); + List submissionIds = new ArrayList(); + if(peerAssessmentItems != null){ + for(PeerAssessmentItem item : peerAssessmentItems){ + submissionIds.add(item.getSubmissionId()); + } + } + state.setAttribute(USER_SUBMISSIONS, submissionIds); + state.setAttribute(GRADE_SUBMISSION_SUBMISSION_ID, submissionId); + state.setAttribute(PEER_ASSESSMENT_ASSESSOR_ID, assessorId); + state.setAttribute(STATE_MODE, MODE_STUDENT_REVIEW_EDIT); + }else{ + addAlert(state, rb.getString("peerassessment.notavailable")); + } + } + + public void doEdit_review(RunData data){ + SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); + ParameterParser params = data.getParameters(); + + String assignmentId = StringUtils.trimToNull(params.getString("assignmentId")); + Assignment a = getAssignment(assignmentId, "doEdit_assignment", state); + if (a != null && a.isPeerAssessmentOpen()){ + //set the page to go to + state.setAttribute(VIEW_ASSIGNMENT_ID, assignmentId); + String submissionId = null; + List peerAssessmentItems = assignmentPeerAssessmentService.getPeerAssessmentItems(a.getId(), UserDirectoryService.getCurrentUser().getId()); + state.setAttribute(PEER_ASSESSMENT_ITEMS, peerAssessmentItems); + List submissionIds = new ArrayList(); + if(peerAssessmentItems != null){ + for(PeerAssessmentItem item : peerAssessmentItems){ + if(!item.isSubmitted()){ + submissionIds.add(item.getSubmissionId()); + } + } + } + if(params.getString("submissionId") != null && submissionIds.contains(params.getString("submissionId"))){ + submissionId = StringUtils.trimToNull(params.getString("submissionId")); + }else if(submissionIds.size() > 0){ + //submission Id wasn't passed in, let's find one for this user + //grab the first one: + submissionId = submissionIds.get(0); + } + + if(submissionId != null){ + state.setAttribute(USER_SUBMISSIONS, submissionIds); + state.setAttribute(GRADE_SUBMISSION_SUBMISSION_ID, submissionId); + state.setAttribute(STATE_MODE, MODE_STUDENT_REVIEW_EDIT); + }else{ + if(peerAssessmentItems != null && peerAssessmentItems.size() > 0){ + //student has submitted all their peer reviews, nothing left to review + //(student really shouldn't get to this warning) + addAlert(state, rb.getString("peerassessment.allSubmitted")); + }else{ + //wasn't able to find a submission id, throw error + addAlert(state, rb.getString("peerassessment.notavailable")); + } + } + }else{ + addAlert(state, rb.getString("peerassessment.notavailable")); + } + } + /** * Action is to show the edit assignment screen */ @@ -7114,6 +7880,17 @@ // put the resubmission option into state assignment_resubmission_option_into_state(a, null, state); + // set whether we use peer assessment or not + if(a.getPeerAssessmentPeriod() != null){ + + state.setAttribute(NEW_ASSIGNMENT_USE_PEER_ASSESSMENT, Boolean.valueOf(a.getAllowPeerAssessment()).toString()); + putTimePropertiesInState(state, a.getPeerAssessmentPeriod(), NEW_ASSIGNMENT_PEERPERIODMONTH, NEW_ASSIGNMENT_PEERPERIODDAY, NEW_ASSIGNMENT_PEERPERIODYEAR, NEW_ASSIGNMENT_PEERPERIODHOUR, NEW_ASSIGNMENT_PEERPERIODMIN, NEW_ASSIGNMENT_PEERPERIODAMPM); + state.setAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_ANON_EVAL, Boolean.valueOf(a.getPeerAssessmentAnonEval()).toString()); + state.setAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_STUDENT_VIEW_REVIEWS, Boolean.valueOf(a.getPeerAssessmentStudentViewReviews()).toString()); + state.setAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_NUM_REVIEWS, a.getPeerAssessmentNumReviews()); + state.setAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_INSTRUCTIONS, a.getPeerAssessmentInstructions()); + } + // set whether we use the review service or not state.setAttribute(NEW_ASSIGNMENT_USE_REVIEW_SERVICE, Boolean.valueOf(a.getContent().getAllowReviewService()).toString()); @@ -8075,6 +8852,28 @@ // save grading doSave_grade_submission(data); } + else if ("savegrade_review".equals(option)) + { + // save review grading + doSave_grade_submission_review(data); + }else if("submitgrade_review".equals(option)){ + //we basically need to submit, save, and move the user to the next review (if available) + if(data.getParameters().get("nextSubmissionId") != null){ + //go next + doPrev_back_next_submission_review(data, "next", true); + }else if(data.getParameters().get("prevSubmissionId") != null){ + //go previous + doPrev_back_next_submission_review(data, "prev", true); + }else{ + //go back to the list + doPrev_back_next_submission_review(data, "back", true); + } + } + else if ("toggleremove_review".equals(option)) + { + // save review grading + doSave_toggle_remove_review(data); + } else if ("previewgrade".equals(option)) { // preview grading @@ -8085,6 +8884,11 @@ // cancel grading doCancel_grade_submission(data); } + else if ("cancelgrade_review".equals(option)) + { + // cancel grade review + // no need to do anything, session will have original values and refresh + } else if ("cancelreorder".equals(option)) { // cancel reordering @@ -8111,11 +8915,21 @@ // hide the assignment instruction doHide_submission_assignment_instruction(data); } + else if ("hide_instruction_review".equals(option)) + { + // hide the assignment instruction + doHide_submission_assignment_instruction_review(data); + } else if ("show_instruction".equals(option)) { // show the assignment instruction doShow_submission_assignment_instruction(data); } + else if ("show_instruction_review".equals(option)) + { + // show the assignment instruction + doShow_submission_assignment_instruction_review(data); + } else if ("sortbygroupdescription".equals(option)) { // show the assignment instruction @@ -8136,6 +8950,16 @@ // save and navigate to previous submission doPrev_back_next_submission(data, "next"); } + else if ("prevsubmission_review".equals(option)) + { + // save and navigate to previous submission + doPrev_back_next_submission_review(data, "prev", false); + } + else if ("nextsubmission_review".equals(option)) + { + // save and navigate to previous submission + doPrev_back_next_submission_review(data, "next", false); + } else if ("cancelgradesubmission".equals(option)) { if (MODE_INSTRUCTOR_VIEW_STUDENTS_ASSIGNMENT.equals(fromView)) { @@ -8146,6 +8970,11 @@ doPrev_back_next_submission(data, "back"); } } + else if ("cancelgradesubmission_review".equals(option)) + { + // save and navigate to previous submission + doPrev_back_next_submission_review(data, "back", false); + } else if ("reorderNavigation".equals(option)) { // save and do reorder @@ -8343,7 +9172,146 @@ state.setAttribute(VIEW_SUBMISSION_HONOR_PLEDGE_YES, "true"); } } + + /** + * read review grade information form and see if any grading information has been changed + * @param data + * @param state + * @param gradeOption + * @return + */ +public boolean saveReviewGradeForm(RunData data, SessionState state, String gradeOption){ + String assessorUserId = UserDirectoryService.getCurrentUser().getId(); + if(state.getAttribute(PEER_ASSESSMENT_ASSESSOR_ID) != null && !assessorUserId.equals(state.getAttribute(PEER_ASSESSMENT_ASSESSOR_ID))){ + //this is only set during the read only view, so just return + return false; + } + ParameterParser params = data.getParameters(); + String submissionRef = params.getString("submissionId"); + String submissionId = null; + if(submissionRef != null){ + int i = submissionRef.lastIndexOf(Entity.SEPARATOR); + if (i == -1){ + submissionId = submissionRef; + }else{ + submissionId = submissionRef.substring(i + 1); + } + } + if(submissionId != null){ + //call the DB to make sure this user can edit this assessment, otherwise it wouldn't exist + PeerAssessmentItem item = assignmentPeerAssessmentService.getPeerAssessmentItem(submissionId, assessorUserId); + if(item != null){ + //find the original assessment item and compare to see if it has changed + //if so, save it + boolean changed = false; + + if(submissionId.equals(item.getSubmissionId()) + && assessorUserId.equals(item.getAssessorUserId())){ + //Grade + String g = StringUtils.trimToNull(params.getCleanString(GRADE_SUBMISSION_GRADE)); + Integer score = item.getScore(); + if(g != null && !"".equals(g)){ + try{ + Double dScore = Double.parseDouble(g); + if(dScore < 0){ + addAlert(state, rb.getString("peerassessment.alert.saveinvalidscore")); + }else{ + String assignmentId = (String) state.getAttribute(VIEW_ASSIGNMENT_ID); + if(assignmentId != null){ + Assignment a = getAssignment(assignmentId, "saveReviewGradeForm", state); + if(a != null){ + if(dScore <= a.getContent().getMaxGradePoint()/10.0){ + //scores are saved as whole values + //so a score of 1.3 would be stored as 13 + score = (int) Math.round(dScore * 10); + }else{ + addAlert(state, rb.getFormattedMessage("plesuse4", new Object[]{g, a.getContent().getMaxGradePoint()/10.0})); + } + }else{ + addAlert(state, rb.getString("peerassessment.alert.saveerrorunkown")); + } + }else{ + addAlert(state, rb.getString("peerassessment.alert.saveerrorunkown")); + } + } + }catch(Exception e){ + addAlert(state, rb.getString("peerassessment.alert.saveinvalidscore")); + } + } + boolean scoreChanged = false; + if(score != null && item.getScore() == null + || score == null && item.getScore() != null + || (score != null && item.getScore() != null && !score.equals(item.getScore()))){ + //Score changed + changed = true; + scoreChanged = true; + item.setScore(score); + } + + //Comment: + boolean checkForFormattingErrors = true; + String feedbackComment = processFormattedTextFromBrowser(state, params.getCleanString(GRADE_SUBMISSION_FEEDBACK_COMMENT), + checkForFormattingErrors); + if(feedbackComment != null && item.getComment() == null + || feedbackComment == null && item.getComment() != null + || (feedbackComment != null && item.getComment() != null && !feedbackComment.equals(item.getComment()))){ + //comment changed + changed = true; + item.setComment(feedbackComment); + } + //Submitted + if("submit".equals(gradeOption)){ + if(item.getScore() != null || (item.getComment() != null && !"".equals(item.getComment().trim()))){ + item.setSubmitted(true); + changed = true; + }else{ + addAlert(state, rb.getString("peerassessment.alert.savenoscorecomment")); + } + } + if(("submit".equals(gradeOption) || "save".equals(gradeOption)) && state.getAttribute(STATE_MESSAGE) == null){ + if(changed){ + //save this in the DB + assignmentPeerAssessmentService.savePeerAssessmentItem(item); + if(scoreChanged){ + //need to re-calcuate the overall score: + assignmentPeerAssessmentService.updateScore(submissionId); + } + state.setAttribute(GRADE_SUBMISSION_DONE, Boolean.TRUE); + if("submit".equals(gradeOption)){ + state.setAttribute(GRADE_SUBMISSION_SUBMIT, Boolean.TRUE); + } + } + } + + //update session state: + List peerAssessmentItems = (List) state.getAttribute(PEER_ASSESSMENT_ITEMS); + if(peerAssessmentItems != null){ + for(int i = 0; i < peerAssessmentItems.size(); i++) { + PeerAssessmentItem sItem = peerAssessmentItems.get(i); + if(sItem.getSubmissionId().equals(item.getSubmissionId()) + && sItem.getAssessorUserId().equals(item.getAssessorUserId())){ + //found it, just update it + peerAssessmentItems.set(i, item); + state.setAttribute(PEER_ASSESSMENT_ITEMS, peerAssessmentItems); + break; + } + } + } + + } + + return changed; + }else{ + addAlert(state, rb.getString("peerassessment.alert.saveerrorunkown")); + } + }else{ + addAlert(state, rb.getString("peerassessment.alert.saveerrorunkown")); + + } + return false; + } + /** * read grade information form and see if any grading information has been changed * @param data @@ -8682,6 +9650,9 @@ m_securityService = (SecurityService) ComponentManager.get("org.sakaiproject.authz.api.SecurityService"); } + if(assignmentPeerAssessmentService == null){ + assignmentPeerAssessmentService = (AssignmentPeerAssessmentService) ComponentManager.get("org.sakaiproject.assignment.api.AssignmentPeerAssessmentService"); + } String siteId = ToolManager.getCurrentPlacement().getContext(); @@ -9030,7 +10001,17 @@ state.setAttribute(ALLPURPOSE_RETRACT_HOUR, Integer.valueOf(5)); state.setAttribute(ALLPURPOSE_RETRACT_MIN, Integer.valueOf(0)); state.setAttribute(ALLPURPOSE_RETRACT_AMPM, "PM"); - + + // set the peer period time to be 10 mins after accept until date + state.setAttribute(NEW_ASSIGNMENT_PEERPERIODMONTH, Integer.valueOf(month)); + state.setAttribute(NEW_ASSIGNMENT_PEERPERIODDAY, Integer.valueOf(day)); + state.setAttribute(NEW_ASSIGNMENT_PEERPERIODYEAR, Integer.valueOf(year)); + state.setAttribute(NEW_ASSIGNMENT_PEERPERIODHOUR, Integer.valueOf(5)); + state.setAttribute(NEW_ASSIGNMENT_PEERPERIODMIN, Integer.valueOf(10)); + state.setAttribute(NEW_ASSIGNMENT_PEERPERIODAMPM, "PM"); + state.setAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_ANON_EVAL, Boolean.TRUE.toString()); + state.setAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_STUDENT_VIEW_REVIEWS, Boolean.TRUE.toString()); + state.setAttribute(NEW_ASSIGNMENT_SECTION, "001"); state.setAttribute(NEW_ASSIGNMENT_SUBMISSION_TYPE, Integer.valueOf(Assignment.TEXT_AND_ATTACHMENT_ASSIGNMENT_SUBMISSION)); state.setAttribute(NEW_ASSIGNMENT_GRADE_TYPE, Integer.valueOf(Assignment.UNGRADED_GRADE_TYPE)); @@ -9175,11 +10156,37 @@ state.removeAttribute(ALLPURPOSE_ACCESS); state.removeAttribute(ALLPURPOSE_ATTACHMENTS); + //revmoew peer assessment settings + state.removeAttribute(NEW_ASSIGNMENT_USE_PEER_ASSESSMENT); + state.removeAttribute(NEW_ASSIGNMENT_PEERPERIODMONTH); + state.removeAttribute(NEW_ASSIGNMENT_PEERPERIODDAY); + state.removeAttribute(NEW_ASSIGNMENT_PEERPERIODYEAR); + state.removeAttribute(NEW_ASSIGNMENT_PEERPERIODHOUR); + state.removeAttribute(NEW_ASSIGNMENT_PEERPERIODMIN); + state.removeAttribute(NEW_ASSIGNMENT_PEERPERIODAMPM); + state.removeAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_ANON_EVAL); + state.removeAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_STUDENT_VIEW_REVIEWS); + state.removeAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_NUM_REVIEWS); + state.removeAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_INSTRUCTIONS); + // remove content-review setting state.removeAttribute(NEW_ASSIGNMENT_USE_REVIEW_SERVICE); state.removeAttribute(AssignmentService.PROP_ASSIGNMENT_ASSOCIATE_GRADEBOOK_ASSIGNMENT); + //remove peer assessment state: + state.removeAttribute(NEW_ASSIGNMENT_USE_PEER_ASSESSMENT); + state.removeAttribute(NEW_ASSIGNMENT_PEERPERIODMONTH); + state.removeAttribute(NEW_ASSIGNMENT_PEERPERIODDAY); + state.removeAttribute(NEW_ASSIGNMENT_PEERPERIODYEAR); + state.removeAttribute(NEW_ASSIGNMENT_PEERPERIODHOUR); + state.removeAttribute(NEW_ASSIGNMENT_PEERPERIODMIN); + state.removeAttribute(NEW_ASSIGNMENT_PEERPERIODAMPM); + state.removeAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_ANON_EVAL); + state.removeAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_STUDENT_VIEW_REVIEWS); + state.removeAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_NUM_REVIEWS); + state.removeAttribute(NEW_ASSIGNMENT_PEER_ASSESSMENT_INSTRUCTIONS); + } // resetNewAssignment /** Index: assignment-tool/tool/src/webapp/vm/assignment/chef_assignments_instructor_list_submissions.vm =================================================================== --- assignment-tool/tool/src/webapp/vm/assignment/chef_assignments_instructor_list_submissions.vm (revision 310244) +++ assignment-tool/tool/src/webapp/vm/assignment/chef_assignments_instructor_list_submissions.vm (working copy) @@ -149,7 +149,12 @@ #end - #set ($submissionType = $assignment.getContent().getTypeOfSubmission()) + + #set($disableGrade=false) + #if($assignment.getAllowPeerAssessment() == true && $assignment.isPeerAssessmentClosed() == false) + #set($disableGrade=true) + #end + #set ($submissionType = $assignment.getContent().getTypeOfSubmission()) #set ($showMsg = false) #set ($showMsg = $!allMsgNumber) #if (!$!showMsg || $showMsg ==0) @@ -161,14 +166,16 @@
## download all $!tlang.getString('downall') - ## upload all + #if(!$disableGrade) + ## upload all | $!tlang.getString('uploadall.title') | $!tlang.getString('relgrad') - #else - title="$!tlang.getString('relcommented')">$!tlang.getString('relcommented') - #end + #if ($withGrade) + title="$!tlang.getString('relgrad')">$!tlang.getString('relgrad') + #else + title="$!tlang.getString('relcommented')">$!tlang.getString('relcommented') + #end + #end #if ($taggable) #foreach ($provider in $providers) #set ($helperInfo = false) @@ -190,9 +197,9 @@ #else