headless_lms_models/library/user_exercise_state_updater/
mod.rs1mod data_loader;
6mod state_deriver;
7mod validation;
8
9use crate::{
10 course_modules,
11 exercise_slide_submissions::ExerciseSlideSubmission,
12 exercises::Exercise,
13 peer_or_self_review_configs::PeerOrSelfReviewConfig,
14 peer_or_self_review_question_submissions::PeerOrSelfReviewQuestionSubmission,
15 peer_or_self_review_questions::PeerOrSelfReviewQuestion,
16 peer_or_self_review_submissions::PeerOrSelfReviewSubmission,
17 peer_review_queue_entries::PeerReviewQueueEntry,
18 prelude::*,
19 teacher_grading_decisions::TeacherGradingDecision,
20 user_exercise_slide_states::UserExerciseSlideStateGradingSummary,
21 user_exercise_states::{self, UserExerciseState, UserExerciseStateUpdate},
22};
23
24use std::default::Default;
25
26#[derive(Debug)]
28pub struct UserExerciseStateUpdateRequiredData {
29 pub exercise: Exercise,
30 pub current_user_exercise_state: UserExerciseState,
31 pub peer_or_self_review_information:
33 Option<UserExerciseStateUpdateRequiredDataPeerReviewInformation>,
34 pub latest_teacher_grading_decision: Option<TeacherGradingDecision>,
36 pub user_exercise_slide_state_grading_summary: UserExerciseSlideStateGradingSummary,
38}
39
40#[derive(Debug)]
42pub struct UserExerciseStateUpdateRequiredDataPeerReviewInformation {
43 pub given_peer_or_self_review_submissions: Vec<PeerOrSelfReviewSubmission>,
44 pub given_self_review_submission: Option<PeerOrSelfReviewSubmission>,
45 pub latest_exercise_slide_submission_received_peer_or_self_review_question_submissions:
46 Vec<PeerOrSelfReviewQuestionSubmission>,
47 pub peer_review_queue_entry: Option<PeerReviewQueueEntry>,
48 pub peer_or_self_review_config: PeerOrSelfReviewConfig,
49 pub peer_or_self_review_questions: Vec<PeerOrSelfReviewQuestion>,
50}
51
52#[derive(Default)]
56pub struct UserExerciseStateUpdateAlreadyLoadedRequiredData {
57 pub exercise: Option<Exercise>,
58 pub current_user_exercise_state: Option<UserExerciseState>,
59 pub peer_or_self_review_information:
60 Option<UserExerciseStateUpdateAlreadyLoadedRequiredDataPeerReviewInformation>,
61 pub latest_teacher_grading_decision: Option<Option<TeacherGradingDecision>>,
63 pub user_exercise_slide_state_grading_summary: Option<UserExerciseSlideStateGradingSummary>,
64}
65
66#[derive(Default)]
70pub struct UserExerciseStateUpdateAlreadyLoadedRequiredDataPeerReviewInformation {
71 pub given_peer_or_self_review_submissions: Option<Vec<PeerOrSelfReviewSubmission>>,
72 pub given_self_review_submission: Option<Option<PeerOrSelfReviewSubmission>>,
73 pub latest_exercise_slide_submission: Option<ExerciseSlideSubmission>,
74 pub latest_exercise_slide_submission_received_peer_or_self_review_question_submissions:
75 Option<Vec<PeerOrSelfReviewQuestionSubmission>>,
76 pub peer_review_queue_entry: Option<Option<PeerReviewQueueEntry>>,
78 pub peer_or_self_review_config: Option<PeerOrSelfReviewConfig>,
79 pub peer_or_self_review_questions: Option<Vec<PeerOrSelfReviewQuestion>>,
80}
81
82pub async fn update_user_exercise_state(
84 conn: &mut PgConnection,
85 user_exercise_state_id: Uuid,
86) -> ModelResult<UserExerciseState> {
87 update_user_exercise_state_with_some_already_loaded_data(
88 conn,
89 user_exercise_state_id,
90 Default::default(),
92 )
93 .await
94}
95
96#[instrument(skip(conn, already_loaded_internal_dependencies))]
124pub async fn update_user_exercise_state_with_some_already_loaded_data(
125 conn: &mut PgConnection,
126 user_exercise_state_id: Uuid,
127 already_loaded_internal_dependencies: UserExerciseStateUpdateAlreadyLoadedRequiredData,
128) -> ModelResult<UserExerciseState> {
129 let required_data = data_loader::load_required_data(
130 conn,
131 user_exercise_state_id,
132 already_loaded_internal_dependencies,
133 )
134 .await?;
135 let exercise_id = required_data.exercise.id;
136
137 let prev_user_exercise_state = required_data.current_user_exercise_state.clone();
138
139 let derived_user_exercise_state = state_deriver::derive_new_user_exercise_state(required_data)?;
140
141 if derived_user_exercise_state
143 == (UserExerciseStateUpdate {
144 id: prev_user_exercise_state.id,
145 score_given: prev_user_exercise_state.score_given,
146 activity_progress: prev_user_exercise_state.activity_progress,
147 reviewing_stage: prev_user_exercise_state.reviewing_stage,
148 grading_progress: prev_user_exercise_state.grading_progress,
149 })
150 {
151 info!("Update resulting in no changes, not updating the database.");
152 return Ok(prev_user_exercise_state);
153 }
154
155 let new_saved_user_exercise_state =
156 user_exercise_states::update(conn, derived_user_exercise_state).await?;
157
158 if let Some(course_instance_id) = new_saved_user_exercise_state.course_instance_id {
160 let course_module = course_modules::get_by_exercise_id(conn, exercise_id).await?;
161 super::progressing::update_automatic_completion_status_and_grant_if_eligible(
162 conn,
163 &course_module,
164 course_instance_id,
165 new_saved_user_exercise_state.user_id,
166 )
167 .await?;
168 }
169 Ok(new_saved_user_exercise_state)
170}