headless_lms_models/
peer_or_self_review_question_submissions.rs

1use std::collections::HashMap;
2
3use crate::peer_or_self_review_questions::PeerOrSelfReviewQuestionType;
4use crate::prelude::*;
5
6#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
7#[cfg_attr(feature = "ts_rs", derive(TS))]
8pub struct PeerOrSelfReviewQuestionSubmission {
9    pub id: Uuid,
10    pub created_at: DateTime<Utc>,
11    pub updated_at: DateTime<Utc>,
12    pub deleted_at: Option<DateTime<Utc>>,
13    pub peer_or_self_review_question_id: Uuid,
14    pub peer_or_self_review_submission_id: Uuid,
15    pub text_data: Option<String>,
16    pub number_data: Option<f32>,
17}
18
19pub async fn insert(
20    conn: &mut PgConnection,
21    pkey_policy: PKeyPolicy<Uuid>,
22    peer_or_self_review_question_id: Uuid,
23    peer_or_self_review_submission_id: Uuid,
24    text_data: Option<String>,
25    number_data: Option<f32>,
26) -> ModelResult<Uuid> {
27    let res = sqlx::query!(
28        "
29INSERT INTO peer_or_self_review_question_submissions (
30    id,
31    peer_or_self_review_question_id,
32    peer_or_self_review_submission_id,
33    text_data,
34    number_data
35  )
36VALUES ($1, $2, $3, $4, $5)
37RETURNING id
38        ",
39        pkey_policy.into_uuid(),
40        peer_or_self_review_question_id,
41        peer_or_self_review_submission_id,
42        text_data,
43        number_data,
44    )
45    .fetch_one(conn)
46    .await?;
47    Ok(res.id)
48}
49
50pub async fn get_by_peer_reviews_question_ids(
51    conn: &mut PgConnection,
52    ids: &[Uuid],
53    user_id: Uuid,
54    exercise_slide_submission_id: Uuid,
55) -> ModelResult<Vec<PeerOrSelfReviewQuestionSubmission>> {
56    let res = sqlx::query_as!(
57        PeerOrSelfReviewQuestionSubmission,
58        "
59    SELECT qs.id,
60        qs.created_at,
61        qs.updated_at,
62        qs.deleted_at,
63        qs.peer_or_self_review_question_id,
64        qs.peer_or_self_review_submission_id,
65        qs.text_data,
66        qs.number_data
67    FROM peer_or_self_review_question_submissions qs
68        JOIN peer_or_self_review_submissions s ON (qs.peer_or_self_review_submission_id = s.id)
69        JOIN exercise_slide_submissions es ON (s.exercise_slide_submission_id = es.id)
70    WHERE peer_or_self_review_question_id IN (
71        SELECT UNNEST($1::uuid [])
72    )
73        AND s.exercise_slide_submission_id = $3
74        AND es.user_id = $2
75        AND qs.deleted_at IS NULL;
76        ",
77        ids,
78        user_id,
79        exercise_slide_submission_id
80    )
81    .fetch_all(conn)
82    .await?;
83    Ok(res)
84}
85
86pub async fn get_received_question_submissions_for_exercise_slide_submission(
87    conn: &mut PgConnection,
88    exercise_slide_submission_id: Uuid,
89) -> ModelResult<Vec<PeerOrSelfReviewQuestionSubmission>> {
90    let res = sqlx::query_as!(
91        PeerOrSelfReviewQuestionSubmission,
92        "
93SELECT prqs.*
94FROM peer_or_self_review_submissions prs
95  JOIN peer_or_self_review_question_submissions prqs on prs.id = prqs.peer_or_self_review_submission_id
96WHERE prs.exercise_slide_submission_id = $1
97  AND prs.deleted_at IS NULL
98  AND prqs.deleted_at IS NULL
99    ",
100        exercise_slide_submission_id
101    )
102    .fetch_all(conn)
103    .await?;
104    Ok(res)
105}
106
107pub async fn get_question_submissions_from_from_peer_or_self_review_submission_ids(
108    conn: &mut PgConnection,
109    peer_or_self_review_submission_ids: &[Uuid],
110) -> ModelResult<Vec<PeerOrSelfReviewQuestionSubmission>> {
111    let res = sqlx::query_as!(
112        PeerOrSelfReviewQuestionSubmission,
113        "
114SELECT *
115FROM peer_or_self_review_question_submissions
116WHERE peer_or_self_review_submission_id IN (
117    SELECT UNNEST($1::uuid [])
118  )
119  AND deleted_at IS NULL
120            ",
121        peer_or_self_review_submission_ids
122    )
123    .fetch_all(&mut *conn)
124    .await?;
125
126    Ok(res)
127}
128
129#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
130#[cfg_attr(feature = "ts_rs", derive(TS))]
131#[serde(tag = "type", rename_all = "kebab-case")]
132pub enum PeerOrSelfReviewAnswer {
133    NoAnswer,
134    Essay { value: String },
135    Scale { value: f32 },
136}
137
138impl PeerOrSelfReviewAnswer {
139    fn new(
140        question_type: PeerOrSelfReviewQuestionType,
141        text_data: Option<String>,
142        number_data: Option<f32>,
143    ) -> Self {
144        match (question_type, text_data, number_data) {
145            (PeerOrSelfReviewQuestionType::Essay, Some(value), _) => Self::Essay { value },
146            (PeerOrSelfReviewQuestionType::Scale, _, Some(value)) => Self::Scale { value },
147            _ => Self::NoAnswer,
148        }
149    }
150}
151
152#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
153#[cfg_attr(feature = "ts_rs", derive(TS))]
154pub struct PeerOrSelfReviewQuestionAndAnswer {
155    pub peer_or_self_review_config_id: Uuid,
156    pub peer_or_self_review_question_id: Uuid,
157    pub peer_or_self_review_submission_id: Uuid,
158    pub peer_review_question_submission_id: Uuid,
159    pub order_number: i32,
160    pub question: String,
161    pub answer: PeerOrSelfReviewAnswer,
162    pub answer_required: bool,
163}
164
165#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
166#[cfg_attr(feature = "ts_rs", derive(TS))]
167pub struct PeerReviewWithQuestionsAndAnswers {
168    pub peer_or_self_review_submission_id: Uuid,
169    pub peer_review_giver_user_id: Uuid,
170    pub questions_and_answers: Vec<PeerOrSelfReviewQuestionAndAnswer>,
171}
172
173pub async fn get_questions_and_answers_by_submission_id(
174    conn: &mut PgConnection,
175    exercise_slide_submission_id: Uuid,
176) -> ModelResult<Vec<PeerReviewWithQuestionsAndAnswers>> {
177    let res = sqlx::query!(
178        r#"
179SELECT answers.id AS peer_review_question_submission_id,
180  answers.text_data,
181  answers.number_data,
182  questions.peer_or_self_review_config_id,
183  questions.id AS peer_or_self_review_question_id,
184  questions.order_number,
185  questions.question,
186  questions.question_type AS "question_type: PeerOrSelfReviewQuestionType",
187  questions.answer_required,
188  submissions.id AS peer_or_self_review_submission_id
189FROM peer_or_self_review_question_submissions answers
190  JOIN peer_or_self_review_questions questions ON (
191    answers.peer_or_self_review_question_id = questions.id
192  )
193  JOIN peer_or_self_review_submissions submissions ON (
194    answers.peer_or_self_review_submission_id = submissions.id
195  )
196WHERE submissions.exercise_slide_submission_id = $1
197  AND questions.deleted_at IS NULL
198  AND answers.deleted_at IS NULL
199  AND submissions.deleted_at IS NULL
200        "#,
201        exercise_slide_submission_id,
202    )
203    .map(|x| PeerOrSelfReviewQuestionAndAnswer {
204        peer_or_self_review_config_id: x.peer_or_self_review_config_id,
205        peer_or_self_review_question_id: x.peer_or_self_review_question_id,
206        peer_review_question_submission_id: x.peer_review_question_submission_id,
207        peer_or_self_review_submission_id: x.peer_or_self_review_submission_id,
208        order_number: x.order_number,
209        question: x.question,
210        answer: PeerOrSelfReviewAnswer::new(x.question_type, x.text_data, x.number_data),
211        answer_required: x.answer_required,
212    })
213    .fetch_all(&mut *conn)
214    .await?;
215    let peer_or_self_review_submission_ids = res
216        .iter()
217        .map(|x| x.peer_or_self_review_submission_id)
218        .collect::<Vec<Uuid>>();
219    let peer_review_submission_id_to_giver_user_id =
220    crate::peer_or_self_review_submissions::get_mapping_from_peer_or_self_review_submission_ids_to_peer_review_giver_user_ids(
221        &mut *conn,
222        &peer_or_self_review_submission_ids,
223    )
224    .await?;
225    Ok(bundle_peer_or_self_review_questions_and_answers(
226        res,
227        peer_review_submission_id_to_giver_user_id,
228    ))
229}
230
231/** Returns the peer reviews given by this user. Does not return the self reviews given by the user. */
232pub async fn get_given_peer_reviews(
233    conn: &mut PgConnection,
234    user_id: Uuid,
235    exercise_id: Uuid,
236    course_instance_id: Uuid,
237) -> ModelResult<Vec<PeerReviewWithQuestionsAndAnswers>> {
238    let res = sqlx::query!(
239        r#"
240SELECT answers.id AS peer_review_question_submission_id,
241  answers.text_data,
242  answers.number_data,
243  questions.peer_or_self_review_config_id,
244  questions.id AS peer_or_self_review_question_id,
245  questions.order_number,
246  questions.question,
247  questions.question_type AS "question_type: PeerOrSelfReviewQuestionType",
248  questions.answer_required,
249  submissions.id AS peer_or_self_review_submission_id
250FROM peer_or_self_review_question_submissions answers
251  JOIN peer_or_self_review_questions questions ON (
252    answers.peer_or_self_review_question_id = questions.id
253  )
254  JOIN peer_or_self_review_submissions submissions ON (
255    answers.peer_or_self_review_submission_id = submissions.id
256  )
257  JOIN exercise_slide_submissions ess ON (
258    submissions.exercise_slide_submission_id = ess.id
259  )
260WHERE submissions.user_id = $1
261  AND submissions.exercise_id = $2
262  AND submissions.course_instance_id = $3
263  AND questions.deleted_at IS NULL
264  AND answers.deleted_at IS NULL
265  AND submissions.deleted_at IS NULL
266  AND ess.user_id != $1
267        "#,
268        user_id,
269        exercise_id,
270        course_instance_id,
271    )
272    .map(|x| PeerOrSelfReviewQuestionAndAnswer {
273        peer_or_self_review_config_id: x.peer_or_self_review_config_id,
274        peer_or_self_review_question_id: x.peer_or_self_review_question_id,
275        peer_review_question_submission_id: x.peer_review_question_submission_id,
276        peer_or_self_review_submission_id: x.peer_or_self_review_submission_id,
277        order_number: x.order_number,
278        question: x.question,
279        answer: PeerOrSelfReviewAnswer::new(x.question_type, x.text_data, x.number_data),
280        answer_required: x.answer_required,
281    })
282    .fetch_all(&mut *conn)
283    .await?;
284    let peer_or_self_review_submission_ids = res
285        .iter()
286        .map(|x| x.peer_or_self_review_submission_id)
287        .collect::<Vec<Uuid>>();
288    let peer_review_submission_id_to_giver_user_id =
289    crate::peer_or_self_review_submissions::get_mapping_from_peer_or_self_review_submission_ids_to_peer_review_giver_user_ids(
290        &mut *conn,
291        &peer_or_self_review_submission_ids,
292    )
293    .await?;
294    Ok(bundle_peer_or_self_review_questions_and_answers(
295        res,
296        peer_review_submission_id_to_giver_user_id,
297    ))
298}
299
300/// Groups answers to peer reviews by peer review ids.
301fn bundle_peer_or_self_review_questions_and_answers(
302    questions_and_answers: Vec<PeerOrSelfReviewQuestionAndAnswer>,
303    peer_review_submission_id_to_giver_user_id: HashMap<Uuid, Uuid>,
304) -> Vec<PeerReviewWithQuestionsAndAnswers> {
305    let mut mapped: HashMap<Uuid, Vec<PeerOrSelfReviewQuestionAndAnswer>> = HashMap::new();
306    questions_and_answers.into_iter().for_each(|x| {
307        mapped
308            .entry(x.peer_or_self_review_submission_id)
309            .or_default()
310            .push(x)
311    });
312
313    mapped
314        .into_iter()
315        .map(|(id, qa)| PeerReviewWithQuestionsAndAnswers {
316            peer_or_self_review_submission_id: id,
317            peer_review_giver_user_id: *(peer_review_submission_id_to_giver_user_id
318                .get(&id)
319                .unwrap_or(&Uuid::nil())),
320            questions_and_answers: qa,
321        })
322        .collect()
323}