Skip to content

Commit

Permalink
Teachers only see ended exam submissions in grading page
Browse files Browse the repository at this point in the history
  • Loading branch information
Maija Y committed Jun 26, 2024
1 parent a202089 commit d8e19c0
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 49 deletions.

This file was deleted.

61 changes: 60 additions & 1 deletion services/headless-lms/models/src/exams.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use chrono::Duration;
use std::collections::HashMap;

use crate::{courses::Course, prelude::*};
use headless_lms_utils::document_schema_processor::GutenbergBlock;
Expand Down Expand Up @@ -351,12 +352,13 @@ pub async fn verify_exam_submission_can_be_made(
Ok(student_has_time && exam_is_ongoing)
}

#[derive(Debug, Serialize)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[cfg_attr(feature = "ts_rs", derive(TS))]
pub struct ExamEnrollment {
pub user_id: Uuid,
pub exam_id: Uuid,
pub started_at: DateTime<Utc>,
pub ended: bool,
pub is_teacher_testing: bool,
pub show_exercise_answers: Option<bool>,
}
Expand All @@ -372,6 +374,7 @@ pub async fn get_enrollment(
SELECT user_id,
exam_id,
started_at,
ended,
is_teacher_testing,
show_exercise_answers
FROM exam_enrollments
Expand All @@ -387,6 +390,40 @@ WHERE exam_id = $1
Ok(res)
}

pub async fn get_exam_enrollments_for_users(
conn: &mut PgConnection,
exam_id: Uuid,
user_ids: &[Uuid],
) -> ModelResult<HashMap<Uuid, ExamEnrollment>> {
let enrollments = sqlx::query_as!(
ExamEnrollment,
"
SELECT user_id,
exam_id,
started_at,
ended,
is_teacher_testing,
show_exercise_answers
FROM exam_enrollments
WHERE user_id IN (
SELECT UNNEST($1::uuid [])
)
AND exam_id = $2
AND deleted_at IS NULL
",
user_ids,
exam_id,
)
.fetch_all(conn)
.await?;

let mut res: HashMap<Uuid, ExamEnrollment> = HashMap::new();
for item in enrollments.into_iter() {
res.insert(item.user_id, item);
}
Ok(res)
}

pub async fn update_exam_start_time(
conn: &mut PgConnection,
exam_id: Uuid,
Expand All @@ -410,6 +447,28 @@ WHERE exam_id = $1
Ok(())
}

pub async fn update_exam_ended(
conn: &mut PgConnection,
exam_id: Uuid,
user_id: Uuid,
ended: bool,
) -> ModelResult<()> {
sqlx::query!(
"
UPDATE exam_enrollments
SET ended = $3
WHERE exam_id = $1
AND user_id = $2
AND deleted_at IS NULL
",
exam_id,
user_id,
ended
)
.execute(conn)
.await?;
Ok(())
}
pub async fn update_show_exercise_answers(
conn: &mut PgConnection,
exam_id: Uuid,
Expand Down
41 changes: 32 additions & 9 deletions services/headless-lms/models/src/exercise_slide_submissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use url::Url;

use crate::{
courses::Course,
exams::{self, ExamEnrollment},
exercise_service_info::ExerciseServiceInfoApi,
exercise_task_gradings::UserPointsUpdateStrategy,
exercise_tasks::CourseMaterialExerciseTask,
Expand Down Expand Up @@ -121,6 +122,7 @@ pub struct ExerciseSlideSubmissionAndUserExerciseState {
pub exercise_slide_submission: ExerciseSlideSubmission,
pub user_exercise_state: UserExerciseState,
pub teacher_grading_decision: Option<TeacherGradingDecision>,
pub user_exam_enrollment: ExamEnrollment,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
Expand Down Expand Up @@ -541,7 +543,8 @@ pub async fn get_latest_exercise_slide_submissions_and_user_exercise_state_list_
.map(|sub| sub.user_id)
.collect::<Vec<_>>();

let exam_id = submissions[0].exam_id;
let exercise = exercises::get_by_id(conn, exercise_id).await?;
let exam_id = exercise.exam_id;

let user_exercise_states_list =
user_exercise_states::get_or_create_user_exercise_state_for_users(
Expand All @@ -560,24 +563,44 @@ pub async fn get_latest_exercise_slide_submissions_and_user_exercise_state_list_
}

let exercise = exercises::get_by_id(conn, exercise_id).await?;
let exam_id = exercise
.exam_id
.ok_or_else(|| ModelError::new(ModelErrorType::Generic, "No exam id found".into(), None))?;

let teacher_grading_decisions_list = teacher_grading_decisions::try_to_get_latest_grading_decision_by_user_exercise_state_id_for_users(conn, &user_exercise_state_id_list).await?;

let user_exam_enrollments_list =
exams::get_exam_enrollments_for_users(conn, exam_id, &user_ids).await?;

let mut list: Vec<ExerciseSlideSubmissionAndUserExerciseState> = Vec::new();
for sub in submissions {
let user_exercise_state = user_exercise_states_list.get(&sub.user_id).ok_or_else(|| {
ModelError::new(ModelErrorType::Generic, "No user found".into(), None)
})?;

let teacher_grading_decision = teacher_grading_decisions_list.get(&user_exercise_state.id);

let data = ExerciseSlideSubmissionAndUserExerciseState {
exercise: exercise.clone(),
exercise_slide_submission: sub,
user_exercise_state: user_exercise_state.clone(),
teacher_grading_decision: teacher_grading_decision.cloned(),
};
list.push(data);
let user_exam_enrollment =
user_exam_enrollments_list
.get(&sub.user_id)
.ok_or_else(|| {
ModelError::new(
ModelErrorType::Generic,
"No users exam_enrollment found".into(),
None,
)
})?;

//Add submissions to the list only if the students exam time has ended
if user_exam_enrollment.ended {
let data = ExerciseSlideSubmissionAndUserExerciseState {
exercise: exercise.clone(),
exercise_slide_submission: sub,
user_exercise_state: user_exercise_state.clone(),
teacher_grading_decision: teacher_grading_decision.cloned(),
user_exam_enrollment: user_exam_enrollment.clone(),
};
list.push(data);
}
}

Ok(list)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ pub async fn fetch_exam_for_user(
&& Utc::now() > enrollment.started_at + Duration::minutes(exam.time_minutes.into())
{
// exam is still open but the student's time has expired
exams::update_exam_ended(&mut conn, *exam_id, user.id, true).await?;
let token = authorize(&mut conn, Act::View, Some(user.id), Res::Exam(*exam_id)).await?;
return token.authorized_ok(web::Json(ExamData {
id: exam.id,
Expand Down
3 changes: 2 additions & 1 deletion shared-module/packages/common/src/bindings.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1294,7 +1294,8 @@ export function isExerciseSlideSubmissionAndUserExerciseState(
(isExerciseSlideSubmission(typedObj["exercise_slide_submission"]) as boolean) &&
(isUserExerciseState(typedObj["user_exercise_state"]) as boolean) &&
(typedObj["teacher_grading_decision"] === null ||
(isTeacherGradingDecision(typedObj["teacher_grading_decision"]) as boolean))
(isTeacherGradingDecision(typedObj["teacher_grading_decision"]) as boolean)) &&
(isExamEnrollment(typedObj["user_exam_enrollment"]) as boolean)
)
}

Expand Down
1 change: 1 addition & 0 deletions shared-module/packages/common/src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,7 @@ export interface ExerciseSlideSubmissionAndUserExerciseState {
exercise_slide_submission: ExerciseSlideSubmission
user_exercise_state: UserExerciseState
teacher_grading_decision: TeacherGradingDecision | null
user_exam_enrollment: ExamEnrollment
}

export interface ExerciseSlideSubmissionAndUserExerciseStateList {
Expand Down

0 comments on commit d8e19c0

Please sign in to comment.