github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/public/src/overmind/namespaces/review/actions.ts (about) 1 import { Context } from '../..' 2 import { GradingBenchmark, GradingCriterion, GradingCriterion_Grade, Review, Submission } from '../../../../proto/qf/types_pb' 3 import { Color, isAuthor } from '../../../Helpers' 4 import { SubmissionOwner } from '../../state' 5 6 7 /* Set the index of the selected review */ 8 export const setSelectedReview = ({ state }: Context, index: number): void => { 9 const reviews = state.review.reviews.get(state.selectedSubmission?.ID ?? -1n) 10 if (index < 0) { 11 const idx = reviews?.findIndex(r => isAuthor(state.self, r) || state.isCourseCreator) 12 state.review.selectedReview = idx && idx >= 0 ? idx : 0 13 } else { 14 state.review.selectedReview = index 15 } 16 } 17 18 /* Update the selected review */ 19 export const updateReview = async ({ state, effects }: Context): Promise<boolean> => { 20 if (!(state.review.canUpdate && state.review.currentReview)) { 21 // If canUpdate is false, the review cannot be updated 22 return false 23 } 24 const submissionID = state.selectedSubmission?.ID ?? -1n 25 const reviews = state.review.reviews.get(submissionID) 26 if (!reviews) { 27 // If there are no reviews, the review cannot be updated 28 return false 29 } 30 31 const review = state.review.currentReview 32 const response = await effects.api.client.updateReview({ 33 courseID: state.activeCourse, 34 review 35 }) 36 if (response.error) { 37 return false 38 } 39 40 const idx = reviews.findIndex(r => r.ID === review.ID) 41 if (idx === -1) { 42 // If the review was not found, abort 43 return false 44 } 45 reviews[idx] = response.message 46 47 // Copy the review map and update the review 48 const reviewMap = new Map(state.review.reviews) 49 reviewMap.set(submissionID, reviews) 50 state.review.reviews = reviewMap; 51 52 (state.selectedSubmission as Submission).score = response.message.score 53 return true 54 } 55 56 export const updateReady = async ({ state, actions }: Context, ready: boolean): Promise<void> => { 57 if (state.review.currentReview) { 58 state.review.currentReview.ready = ready 59 await actions.review.updateReview() 60 } 61 } 62 63 export const updateComment = async ({ actions }: Context, { grade, comment }: { grade: GradingBenchmark | GradingCriterion, comment: string }): Promise<void> => { 64 const oldComment = grade.comment 65 grade.comment = comment 66 const ok = await actions.review.updateReview() 67 if (!ok) { 68 grade.comment = oldComment 69 } 70 } 71 72 export const updateFeedback = async ({ state, actions }: Context, { feedback }: { feedback: string }): Promise<void> => { 73 if (state.review.currentReview) { 74 const oldFeedback = state.review.currentReview.feedback 75 state.review.currentReview.feedback = feedback 76 const ok = await actions.review.updateReview() 77 if (!ok) { 78 state.review.currentReview.feedback = oldFeedback 79 } 80 } 81 } 82 83 export const setGrade = async ({ actions }: Context, { criterion, grade }: { criterion: GradingCriterion, grade: GradingCriterion_Grade }): Promise<void> => { 84 const oldGrade = criterion.grade 85 criterion.grade = grade 86 const ok = await actions.review.updateReview() 87 if (!ok) { 88 criterion.grade = oldGrade 89 } 90 } 91 92 /* createReview creates a new review for the current submission and course */ 93 export const createReview = async ({ state, actions, effects }: Context): Promise<void> => { 94 if (!confirm('Are you sure you want to create a new review?')) { 95 return 96 } 97 98 const submission = state.selectedSubmission 99 // If there is no submission or active course, we cannot create a review 100 if (submission && state.activeCourse) { 101 // Set the current user as the reviewer 102 const review = new Review({ 103 ReviewerID: state.self.ID, 104 SubmissionID: submission.ID, 105 }) 106 107 const response = await effects.api.client.createReview({ 108 courseID: state.activeCourse, 109 review, 110 }) 111 if (response.error) { 112 return 113 } 114 // Adds the new review to the reviews list if the server responded with a review 115 const reviews = new Map(state.review.reviews) 116 const length = reviews.get(submission.ID)?.push(response.message) ?? 0 117 state.review.reviews = reviews 118 actions.review.setSelectedReview(length - 1) 119 } 120 } 121 122 123 export const setAssignmentID = ({ state }: Context, aid: bigint): void => { 124 const id = state.review.assignmentID > 0 ? BigInt(-1) : aid 125 state.review.assignmentID = id 126 } 127 128 export const setMinimumScore = ({ state }: Context, minimumScore: number): void => { 129 state.review.minimumScore = minimumScore 130 } 131 132 export const releaseAll = async ({ state, actions, effects }: Context, { release, approve }: { release: boolean, approve: boolean }): Promise<void> => { 133 const assignment = state.assignments[state.activeCourse.toString()].find(a => a.ID === state.review.assignmentID) 134 135 const releaseString = release && approve ? 'release and approve' 136 : release ? 'release' 137 : approve ? "approve" 138 : "" 139 const confirmText = `Are you sure you want to ${releaseString} all reviews for ${assignment?.name} above ${state.review.minimumScore} score?` 140 const invalidMinimumScore = state.review.minimumScore < 0 || state.review.minimumScore > 100 141 142 if (invalidMinimumScore || !confirm(confirmText)) { 143 invalidMinimumScore && actions.alert({ text: 'Minimum score must be in range [0, 100]', color: Color.YELLOW }) 144 return 145 } 146 147 const response = await effects.api.client.updateSubmissions({ 148 courseID: state.activeCourse, 149 assignmentID: state.review.assignmentID, 150 scoreLimit: state.review.minimumScore, 151 release, 152 approve, 153 }) 154 if (response.error) { 155 return 156 } 157 // Refresh submissions in state for the active course 158 await actions.refreshCourseSubmissions(state.activeCourse) 159 } 160 161 export const release = async ({ state, effects }: Context, { submission, owner }: { submission: Submission | null, owner: SubmissionOwner }): Promise<void> => { 162 if (!submission) { 163 return 164 } 165 const clone = submission.clone() 166 clone.released = !submission.released 167 const response = await effects.api.client.updateSubmission({ 168 courseID: state.activeCourse, 169 submissionID: submission.ID, 170 status: submission.status, 171 released: clone.released, 172 score: submission.score, 173 }) 174 if (response.error) { 175 return 176 } 177 submission.released = clone.released 178 state.submissionsForCourse.update(owner, submission) 179 }