Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes to the custom view #1256

Merged
merged 11 commits into from
Mar 28, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
fetchCourseModuleExercisesAndSubmissionsByType,
fetchDefaultModuleIdByCourseId,
fetchModuleIdByChapterId,
getAllCourseModuleCompletionsForUserAndCourseInstance,
} from "../../../../services/backend"
import ErrorBanner from "../../../../shared-module/components/ErrorBanner"
import MessageChannelIFrame from "../../../../shared-module/components/MessageChannelIFrame"
Expand Down Expand Up @@ -37,8 +38,17 @@ const CustomViewIframe: React.FC<React.PropsWithChildren<CustomViewIframeProps>>
const courseInstanceId = pageContext.instance?.id
const courseId = pageContext.settings?.current_course_id

const courseModuleCompletionsQuery = useQuery({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a hook

queryKey: [`${courseInstanceId}-course-module-completions-${userInfo.data?.user_id}`],
queryFn: () =>
getAllCourseModuleCompletionsForUserAndCourseInstance(
assertNotNullOrUndefined(courseInstanceId),
assertNotNullOrUndefined(userInfo.data?.user_id),
),
enabled: !!courseInstanceId && !!userInfo.data?.user_id,
})
const courseInfo = useCourseInfo(pageContext.settings?.current_course_id)
console.log(courseInfo.data?.name)

const moduleIdByChapter = useQuery({
queryKey: [`course-modules-chapter-${chapterId}`],
queryFn: () => fetchModuleIdByChapterId(assertNotNullOrUndefined(chapterId)),
Expand All @@ -65,6 +75,10 @@ const CustomViewIframe: React.FC<React.PropsWithChildren<CustomViewIframeProps>>
enabled: !!moduleId && !!courseInstanceId,
})

const completionDate = courseModuleCompletionsQuery.data?.find(
(compl) => compl.course_module_id === moduleId,
)?.completion_date

const submission_data = submissions_by_exercise.data
const subs_by_exercise = useMemo(() => {
if (!submission_data) {
Expand All @@ -77,16 +91,11 @@ const CustomViewIframe: React.FC<React.PropsWithChildren<CustomViewIframeProps>>
exercise_tasks: submission_data.exercise_tasks.task_gradings
.filter((grading) => grading.exercise_id == exer.id)
.map((grading) => {
const answer = submission_data.exercise_tasks.task_submissions
.filter((sub) => sub.exercise_task_grading_id == grading.id)
.sort((a, b) => parseISO(b.created_at).getTime() - parseISO(a.created_at).getTime())
.filter(
(task_asnwer, index, array) =>
array.findIndex((el) => el.exercise_task_id === task_asnwer.exercise_task_id) ===
index,
)[0]
const answer = submission_data.exercise_tasks.task_submissions.find(
(sub) => sub.exercise_task_grading_id === grading.id,
)
const publicSpec = submission_data.exercise_tasks.exercise_tasks.find(
(task) => task.id == grading.exercise_task_id,
(task) => task.id === grading.exercise_task_id,
)?.public_spec
return {
task_id: grading.exercise_task_id,
Expand Down Expand Up @@ -129,6 +138,7 @@ const CustomViewIframe: React.FC<React.PropsWithChildren<CustomViewIframeProps>>
// eslint-disable-next-line i18next/no-literal-string
view_type: "custom-view",
course_name: courseInfo.data?.name,
module_completion_date: completionDate ? parseISO(completionDate).toLocaleDateString() : null,
user_information: {
user_id: userInfo.data.user_id,
first_name: userInfo.data.first_name,
Expand Down
12 changes: 12 additions & 0 deletions services/course-material/src/services/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
CourseMaterialExercise,
CourseMaterialPeerReviewDataWithToken,
CourseMaterialPeerReviewSubmission,
CourseModuleCompletion,
CoursePageWithUserData,
CustomViewExerciseSubmissions,
ExamData,
Expand Down Expand Up @@ -50,6 +51,7 @@ import {
isCourseInstance,
isCourseMaterialExercise,
isCourseMaterialPeerReviewDataWithToken,
isCourseModuleCompletion,
isCoursePageWithUserData,
isCustomViewExerciseSubmissions,
isExamData,
Expand Down Expand Up @@ -624,3 +626,13 @@ export const fetchDefaultModuleIdByCourseId = async (course_id: string) => {
})
return validateResponse(res, isUuid)
}

export const getAllCourseModuleCompletionsForUserAndCourseInstance = async (
courseInstanceId: string,
userId: string,
): Promise<CourseModuleCompletion[]> => {
const response = await courseMaterialClient.get(
`/course-instances/${courseInstanceId}/course-module-completions/${userId}`,
)
return validateResponse(response, isArray(isCourseModuleCompletion))
}

This file was deleted.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,8 @@ pub async fn get_user_exersice_task_submissions_by_course_module_and_exercise_ty
let res: Vec<CustomViewExerciseTaskSubmission> = sqlx::query_as!(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename the function to get_user_latest_exercise_task_submissions_by_course_module_and_exercise_type(

CustomViewExerciseTaskSubmission,
r#"
SELECT g.id,
SELECT DISTINCT ON (g.exercise_task_id)
g.id,
g.created_at,
g.exercise_slide_submission_id,
g.exercise_slide_id,
Expand All @@ -528,6 +529,7 @@ pub async fn get_user_exersice_task_submissions_by_course_module_and_exercise_ty
AND ess.deleted_at IS NULL
AND e.deleted_at IS NULL
AND c.deleted_at IS NULL
ORDER BY g.exercise_task_id, g.created_at DESC
"#,
user_id,
course_instance_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use models::{
course_background_question_answers::NewCourseBackgroundQuestionAnswer,
course_background_questions::CourseBackgroundQuestionsAndAnswers,
course_instance_enrollments::CourseInstanceEnrollment,
course_module_completions::CourseModuleCompletion,
library::progressing::UserModuleCompletionStatus,
user_exercise_states::{UserCourseInstanceChapterExerciseProgress, UserCourseInstanceProgress},
};
Expand Down Expand Up @@ -148,6 +149,36 @@ async fn save_course_settings(
token.authorized_ok(web::Json(enrollment))
}

/**
GET /course-instances/:id/course-module-completions/:user_id - Returns a list of all course module completions for a given user for this course instance.
*/
#[instrument(skip(pool))]

async fn get_all_get_all_course_module_completions_for_user_by_course_instance_id(
params: web::Path<(Uuid, Uuid)>,
pool: web::Data<PgPool>,
user: AuthUser,
) -> ControllerResult<web::Json<Vec<CourseModuleCompletion>>> {
let (course_instance_id, user_id) = params.into_inner();
let mut conn = pool.acquire().await?;
let token = authorize(
&mut conn,
Act::ViewUserProgressOrDetails,
Some(user.id),
Res::CourseInstance(course_instance_id),
)
.await?;

let res = models::course_module_completions::get_all_by_course_instance_and_user_id(
&mut conn,
course_instance_id,
user_id,
)
.await?;

token.authorized_ok(web::Json(res))
}

/**
GET /api/v0/course-material/course-instance/:course_instance_id/background-questions-and-answers - Gets background questions and answers for an course instance.
*/
Expand Down Expand Up @@ -190,6 +221,10 @@ pub fn _add_routes(cfg: &mut ServiceConfig) {
"/{course_instance_id}/module-completions",
web::get().to(get_module_completions_for_course_instance),
)
.route(
"/{course_instance_id}/course-module-completions/{user_id}",
web::get().to(get_all_get_all_course_module_completions_for_user_by_course_instance_id),
)
.route(
"/{course_instance_id}/background-questions-and-answers",
web::get().to(get_background_questions_and_answers),
Expand Down
2 changes: 2 additions & 0 deletions shared-module/src/exercise-service-protocol-types.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ export function isCustomViewIframeState(obj: unknown): obj is CustomViewIframeSt
typedObj["user_variables"] === null ||
(isUserVariablesMap(typedObj["user_variables"]) as boolean)) &&
typeof typedObj["course_name"] === "string" &&
(typedObj["module_completion_date"] === null ||
typeof typedObj["module_completion_date"] === "string") &&
((typedObj["data"] !== null && typeof typedObj["data"] === "object") ||
typeof typedObj["data"] === "function") &&
Array.isArray(typedObj["data"]["submissions_by_exercise"]) &&
Expand Down
1 change: 1 addition & 0 deletions shared-module/src/exercise-service-protocol-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export type CustomViewIframeState = {
user_information: UserInfo
user_variables?: UserVariablesMap | null
course_name: string
module_completion_date: string | null
data: {
submissions_by_exercise: Array<{
exercise_id: string
Expand Down
Loading