VBV-585 feat: "Wissens- und Verständnisfragen im Cockpit"

This commit is contained in:
Daniel Egger 2023-11-07 15:01:46 +01:00
parent be2488ff73
commit 0ee783219b
18 changed files with 159 additions and 69 deletions

View File

@ -2,13 +2,13 @@
<li <li
class="flex flex-col justify-between gap-2 border-t border-gray-500 py-4 leading-[45px] lg:flex-row lg:gap-4" class="flex flex-col justify-between gap-2 border-t border-gray-500 py-4 leading-[45px] lg:flex-row lg:gap-4"
> >
<div class="flex flex-row items-center lg:w-1/3"> <div class="flex flex-row items-center lg:w-1/4">
<slot name="firstRow"></slot> <slot name="firstRow"></slot>
</div> </div>
<div class="flex flex-1 lg:items-center"> <div class="flex flex-1 lg:items-center">
<slot name="center"></slot> <slot name="center"></slot>
</div> </div>
<div class="flex items-center lg:w-1/4"> <div class="flex items-center">
<slot name="link"></slot> <slot name="link"></slot>
</div> </div>
</li> </li>

View File

@ -20,7 +20,7 @@ const documents = {
"\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n }\n assignment_user {\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument, "\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n }\n assignment_user {\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
"\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateQueryDocument, "\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateQueryDocument,
"\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n": types.CourseSessionDetailDocument, "\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n": types.CourseSessionDetailDocument,
"\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n": types.CourseQueryDocument, "\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n": types.CourseQueryDocument,
"\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n }\n }\n": types.DashboardConfigDocument, "\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n }\n }\n": types.DashboardConfigDocument,
"\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n": types.DashboardProgressDocument, "\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n": types.DashboardProgressDocument,
"\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n": types.CourseStatisticsDocument, "\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n": types.CourseStatisticsDocument,
@ -72,7 +72,7 @@ export function graphql(source: "\n query courseSessionDetail($courseSessionId:
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
export function graphql(source: "\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n"]; export function graphql(source: "\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n"];
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */

File diff suppressed because one or more lines are too long

View File

@ -241,6 +241,7 @@ export const COURSE_QUERY = graphql(`
assignment_type assignment_type
content_assignment { content_assignment {
id id
assignment_type
} }
competence_certificate { competence_certificate {
...CoursePageFields ...CoursePageFields
@ -251,6 +252,7 @@ export const COURSE_QUERY = graphql(`
has_extended_time_test has_extended_time_test
content_assignment { content_assignment {
id id
assignment_type
} }
competence_certificate { competence_certificate {
...CoursePageFields ...CoursePageFields

View File

@ -7,6 +7,7 @@ import type {
CourseSession, CourseSession,
CourseSessionUser, CourseSessionUser,
LearningContentAssignment, LearningContentAssignment,
LearningContentEdoniqTest,
} from "@/types"; } from "@/types";
import log from "loglevel"; import log from "loglevel";
import { computed, onMounted, reactive } from "vue"; import { computed, onMounted, reactive } from "vue";
@ -14,10 +15,13 @@ import AssignmentSubmissionProgress from "@/pages/cockpit/cockpitPage/Assignment
import { useCourseSessionDetailQuery } from "@/composables"; import { useCourseSessionDetailQuery } from "@/composables";
import { formatDueDate } from "../../../components/dueDates/dueDatesUtils"; import { formatDueDate } from "../../../components/dueDates/dueDatesUtils";
import { stringifyParse } from "@/utils/utils"; import { stringifyParse } from "@/utils/utils";
import { useTranslation } from "i18next-vue";
const { t } = useTranslation();
const props = defineProps<{ const props = defineProps<{
courseSession: CourseSession; courseSession: CourseSession;
learningContentAssignment: LearningContentAssignment; learningContent: LearningContentAssignment | LearningContentEdoniqTest;
}>(); }>();
log.debug("AssignmentDetails created", stringifyParse(props)); log.debug("AssignmentDetails created", stringifyParse(props));
@ -31,28 +35,54 @@ const state = reactive({
}); });
const assignmentDetail = computed(() => { const assignmentDetail = computed(() => {
return courseSessionDetailResult.findAssignment(props.learningContentAssignment.id); return courseSessionDetailResult.findAssignment(props.learningContent.id);
}); });
onMounted(async () => { onMounted(async () => {
const { gradedUsers, assignmentSubmittedUsers } = const { gradedUsers, assignmentSubmittedUsers } =
await loadAssignmentCompletionStatusData( await loadAssignmentCompletionStatusData(
props.learningContentAssignment.content_assignment.id, props.learningContent.content_assignment.id,
props.courseSession.id, props.courseSession.id,
props.learningContentAssignment.id props.learningContent.id
); );
state.gradedUsers = gradedUsers; state.gradedUsers = gradedUsers;
state.assignmentSubmittedUsers = assignmentSubmittedUsers; state.assignmentSubmittedUsers = assignmentSubmittedUsers;
}); });
function findGradedUser(userId: string) {
return state.gradedUsers.find((gu) => gu.user.user_id === userId);
}
function findUserPointsHtml(userId: string) {
let result = "";
const gradedUser = findGradedUser(userId);
if (gradedUser) {
result = `${gradedUser.points} ${t("assignment.von x Punkten", {
x: gradedUser.maxPoints,
})}`;
result +=
" (" +
(((gradedUser.points ?? 0) / (gradedUser.maxPoints ?? 1)) * 100).toFixed(0) +
"%)";
if (!gradedUser.passed) {
result += ` <span class="my-2 rounded-md bg-error-red-200 px-2.5 py-0.5">${t(
"a.Nicht bestanden"
)}</span>`;
}
}
return result;
}
</script> </script>
<template> <template>
<div> <div>
<h2 class="heading-2 font-bold"> <h2 class="heading-2 font-bold">
{{ learningContentAssignment.title }} {{ learningContent.title }}
</h2> </h2>
<div class="pt-1 underline"> <div class="pt-1 underline">
{{ $t("a.Circle") }} «{{ learningContentAssignment.circle?.title }}» {{ $t("a.Circle") }} «{{ learningContent.circle?.title }}»
</div> </div>
<div v-if="assignmentDetail"> <div v-if="assignmentDetail">
<span v-if="assignmentDetail.submission_deadline?.start"> <span v-if="assignmentDetail.submission_deadline?.start">
@ -73,7 +103,7 @@ onMounted(async () => {
<!-- how to determine assignment-type? how to get AssignmentLearningContent? --> <!-- how to determine assignment-type? how to get AssignmentLearningContent? -->
<AssignmentSubmissionProgress <AssignmentSubmissionProgress
:course-session="courseSession" :course-session="courseSession"
:learning-content-assignment="learningContentAssignment" :learning-content="learningContent"
:show-title="false" :show-title="false"
/> />
</div> </div>
@ -88,7 +118,7 @@ onMounted(async () => {
:data-cy="csu.last_name" :data-cy="csu.last_name"
> >
<template #center> <template #center>
<section class="flex w-full justify-between"> <section class="flex w-full flex-col justify-between lg:flex-row lg:gap-8">
<div <div
v-if=" v-if="
state.gradedUsers.map((gradedUser) => gradedUser.user).includes(csu) state.gradedUsers.map((gradedUser) => gradedUser.user).includes(csu)
@ -114,27 +144,28 @@ onMounted(async () => {
<div class="ml-2">{{ $t("a.Ergebnisse abgegeben") }}</div> <div class="ml-2">{{ $t("a.Ergebnisse abgegeben") }}</div>
</div> </div>
<!-- eslint-disable vue/no-v-html -->
<div <div
v-if=" v-if="findGradedUser(csu.user_id)"
state.gradedUsers.map((gradedUser) => gradedUser.user).includes(csu) v-html="findUserPointsHtml(csu.user_id)"
" ></div>
>
{{
state.gradedUsers.find((u) => u.user.user_id === csu.user_id)?.points
}}
{{ $t("a.Punkte") }}
</div>
</section> </section>
</template> </template>
<template #link> <template #link>
<router-link <div class="lg:ml-4">
v-if="state.assignmentSubmittedUsers.includes(csu)" <router-link
:to="`/course/${props.courseSession.course.slug}/cockpit/assignment/${learningContentAssignment.content_assignment.id}/${csu.user_id}`" v-if="
class="link lg:w-full lg:text-right" state.assignmentSubmittedUsers.includes(csu) &&
data-cy="show-results" props.learningContent.content_type !==
> 'learnpath.LearningContentEdoniqTest'
{{ $t("a.Ergebnisse anschauen") }} "
</router-link> :to="`/course/${props.courseSession.course.slug}/cockpit/assignment/${learningContent.content_assignment.id}/${csu.user_id}`"
class="link lg:w-full lg:text-right"
data-cy="show-results"
>
{{ $t("a.Ergebnisse anschauen") }}
</router-link>
</div>
</template> </template>
</ItPersonRow> </ItPersonRow>
</ul> </ul>

View File

@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { useCurrentCourseSession, useCourseData } from "@/composables"; import { useCourseData, useCurrentCourseSession } from "@/composables";
import AssignmentDetails from "@/pages/cockpit/assignmentsPage/AssignmentDetails.vue"; import AssignmentDetails from "@/pages/cockpit/assignmentsPage/AssignmentDetails.vue";
import * as log from "loglevel"; import * as log from "loglevel";
import { computed, onMounted } from "vue"; import { computed, onMounted } from "vue";
import type { LearningContentAssignment } from "@/types"; import type { LearningContentAssignment, LearningContentEdoniqTest } from "@/types";
const props = defineProps<{ const props = defineProps<{
courseSlug: string; courseSlug: string;
@ -42,7 +42,7 @@ const learningContentAssignment = computed(() => {
<AssignmentDetails <AssignmentDetails
v-if="learningContentAssignment" v-if="learningContentAssignment"
:course-session="courseSession" :course-session="courseSession"
:learning-content-assignment="learningContentAssignment as LearningContentAssignment" :learning-content="learningContentAssignment as (LearningContentAssignment | LearningContentEdoniqTest)"
/> />
</div> </div>
</main> </main>

View File

@ -6,14 +6,15 @@ import type {
AssignmentCompletionStatus, AssignmentCompletionStatus,
CourseSession, CourseSession,
LearningContentAssignment, LearningContentAssignment,
LearningContentEdoniqTest,
} from "@/types"; } from "@/types";
import log from "loglevel"; import log from "loglevel";
import { onMounted, reactive } from "vue"; import { computed, onMounted, reactive } from "vue";
import { stringifyParse } from "@/utils/utils"; import { stringifyParse } from "@/utils/utils";
const props = defineProps<{ const props = defineProps<{
courseSession: CourseSession; courseSession: CourseSession;
learningContentAssignment: LearningContentAssignment; learningContent: LearningContentAssignment | LearningContentEdoniqTest;
showTitle: boolean; showTitle: boolean;
}>(); }>();
@ -32,9 +33,9 @@ const state = reactive({
onMounted(async () => { onMounted(async () => {
const { assignmentSubmittedUsers, gradedUsers, total } = const { assignmentSubmittedUsers, gradedUsers, total } =
await loadAssignmentCompletionStatusData( await loadAssignmentCompletionStatusData(
props.learningContentAssignment.content_assignment.id, props.learningContent.content_assignment.id,
props.courseSession.id, props.courseSession.id,
props.learningContentAssignment.id props.learningContent.id
); );
state.submissionProgressStatusCount = { state.submissionProgressStatusCount = {
@ -56,16 +57,31 @@ const doneCount = (status: StatusCount) => {
const totalCount = (status: StatusCount) => { const totalCount = (status: StatusCount) => {
return doneCount(status) + status.UNKNOWN || 0; return doneCount(status) + status.UNKNOWN || 0;
}; };
const showEvaluationStatus = computed(() => {
return (
props.learningContent.content_assignment.assignment_type === "CASEWORK" ||
props.learningContent.content_assignment.assignment_type === "EDONIQ_TEST"
);
});
const showSubmissionStatus = computed(() => {
return (
props.learningContent.content_assignment.assignment_type === "PREP_ASSIGNMENT" ||
props.learningContent.content_assignment.assignment_type === "REFLECTION" ||
props.learningContent.content_assignment.assignment_type === "CONDITION_ACCEPTANCE"
);
});
</script> </script>
<template> <template>
<div> <div>
<div v-if="showTitle"> <div v-if="showTitle">
{{ props.learningContentAssignment.title }} {{ props.learningContent.title }}
</div> </div>
<ItProgress :status-count="state.submissionProgressStatusCount" /> <ItProgress :status-count="state.submissionProgressStatusCount" />
<div class="text-gray-900"> <div class="text-gray-900">
<div v-if="props.learningContentAssignment.assignment_type === 'CASEWORK'"> <div v-if="showEvaluationStatus">
{{ {{
$t("x von y Ergebnisse abgegeben", { $t("x von y Ergebnisse abgegeben", {
x: doneCount(state.submissionProgressStatusCount), x: doneCount(state.submissionProgressStatusCount),
@ -80,13 +96,7 @@ const totalCount = (status: StatusCount) => {
}) })
}} }}
</div> </div>
<div <div v-else-if="showSubmissionStatus">
v-else-if="
props.learningContentAssignment.assignment_type === 'PREP_ASSIGNMENT' ||
props.learningContentAssignment.assignment_type === 'CONDITION_ACCEPTANCE' ||
props.learningContentAssignment.assignment_type === 'REFLECTION'
"
>
{{ {{
$t("x von y abgeschlossen", { $t("x von y abgeschlossen", {
x: doneCount(state.submissionProgressStatusCount), x: doneCount(state.submissionProgressStatusCount),

View File

@ -127,7 +127,7 @@ const courseSessionDetailResult = useCourseSessionDetailQuery();
> >
<template #center> <template #center>
<div <div
class="mt-2 flex w-full flex-col items-center justify-between lg:mt-0 lg:flex-row" class="mt-2 flex w-full flex-col items-center justify-start lg:mt-0 lg:flex-row"
> >
<LearningPathDiagram <LearningPathDiagram
:course-session-id="courseSession.id" :course-session-id="courseSession.id"

View File

@ -5,6 +5,7 @@ import type {
CourseSession, CourseSession,
LearningContent, LearningContent,
LearningContentAssignment, LearningContentAssignment,
LearningContentEdoniqTest,
} from "@/types"; } from "@/types";
import log from "loglevel"; import log from "loglevel";
import { computed } from "vue"; import { computed } from "vue";
@ -12,8 +13,8 @@ import { useTranslation } from "i18next-vue";
import FeedbackSubmissionProgress from "@/pages/cockpit/cockpitPage/FeedbackSubmissionProgress.vue"; import FeedbackSubmissionProgress from "@/pages/cockpit/cockpitPage/FeedbackSubmissionProgress.vue";
import { learningContentTypeData } from "@/utils/typeMaps"; import { learningContentTypeData } from "@/utils/typeMaps";
import { import {
useCourseSessionDetailQuery,
useCourseDataWithCompletion, useCourseDataWithCompletion,
useCourseSessionDetailQuery,
} from "@/composables"; } from "@/composables";
import { circleFlatLearningContents } from "@/services/circle"; import { circleFlatLearningContents } from "@/services/circle";
@ -51,7 +52,8 @@ const submittables = computed(() => {
const learningContents = circleFlatLearningContents(circle).filter( const learningContents = circleFlatLearningContents(circle).filter(
(lc) => (lc) =>
lc.content_type === "learnpath.LearningContentAssignment" || lc.content_type === "learnpath.LearningContentAssignment" ||
lc.content_type === "learnpath.LearningContentFeedback" lc.content_type === "learnpath.LearningContentFeedback" ||
lc.content_type === "learnpath.LearningContentEdoniqTest"
); );
return learningContents.map((lc) => { return learningContents.map((lc) => {
@ -77,6 +79,10 @@ const isAssignment = (lc: LearningContent) => {
return lc.content_type === "learnpath.LearningContentAssignment"; return lc.content_type === "learnpath.LearningContentAssignment";
}; };
const isEdoniqTest = (lc: LearningContent) => {
return lc.content_type === "learnpath.LearningContentEdoniqTest";
};
const getLearningContentType = (lc: LearningContent) => { const getLearningContentType = (lc: LearningContent) => {
if (isAssignment(lc)) { if (isAssignment(lc)) {
const lcTypeData = learningContentTypeData(lc); const lcTypeData = learningContentTypeData(lc);
@ -84,8 +90,12 @@ const getLearningContentType = (lc: LearningContent) => {
return lcTypeData.title; return lcTypeData.title;
} }
return `${lcTypeData.title}: ${lc.title}`; return `${lcTypeData.title}: ${lc.title}`;
} else if (isEdoniqTest(lc)) {
return lc.title;
} else if (isFeedback(lc)) {
return t("Feedback: Feedback zum Lehrgang");
} }
return t("Feedback: Feedback zum Lehrgang"); return "!!unknown!!";
}; };
const getShowDetailsText = (lc: LearningContent) => { const getShowDetailsText = (lc: LearningContent) => {
@ -99,15 +109,21 @@ const getShowDetailsText = (lc: LearningContent) => {
) { ) {
return t("Status anschauen"); return t("Status anschauen");
} }
} else if (isEdoniqTest(lc)) {
return t("a.Ergebnisse anschauen");
} else if (isFeedback(lc)) {
return t("Feedback anschauen");
} }
return t("Feedback anschauen"); return "!!unknown!!";
}; };
const getDetailsLink = (lc: LearningContent, circleId: string) => { const getDetailsLink = (lc: LearningContent, circleId: string) => {
if (isFeedback(lc)) { if (isFeedback(lc)) {
return `cockpit/feedback/${circleId}`; return `cockpit/feedback/${circleId}`;
} else if (isAssignment(lc) || isEdoniqTest(lc)) {
return `cockpit/assignment/${lc.id}`;
} }
return `cockpit/assignment/${lc.id}`; return "";
}; };
const getIconName = (lc: LearningContent) => { const getIconName = (lc: LearningContent) => {
@ -153,9 +169,9 @@ const getIconName = (lc: LearningContent) => {
</div> </div>
</div> </div>
<AssignmentSubmissionProgress <AssignmentSubmissionProgress
v-if="isAssignment(submittable.content)" v-if="isAssignment(submittable.content) || isEdoniqTest(submittable.content)"
:course-session="props.courseSession" :course-session="props.courseSession"
:learning-content-assignment="submittable.content as LearningContentAssignment" :learning-content="submittable.content as (LearningContentAssignment | LearningContentEdoniqTest)"
:show-title="false" :show-title="false"
class="grow pr-8" class="grow pr-8"
/> />
@ -166,7 +182,7 @@ const getIconName = (lc: LearningContent) => {
class="grow pr-8" class="grow pr-8"
></FeedbackSubmissionProgress> ></FeedbackSubmissionProgress>
<div class="flex items-center lg:w-1/4 lg:justify-end"> <div class="flex items-center lg:w-1/4 lg:justify-end">
<button class="btn-primary"> <button v-if="submittable.detailsLink" class="btn-primary">
<router-link <router-link
:to="submittable.detailsLink" :to="submittable.detailsLink"
:data-cy=" :data-cy="

View File

@ -38,7 +38,7 @@ const assignmentStats = (metrics: AssignmentCompletionMetricsType) => {
}; };
const total = (metrics: AssignmentCompletionMetricsType) => { const total = (metrics: AssignmentCompletionMetricsType) => {
return metrics.passed_count + metrics.failed_count; return metrics.passed_count + metrics.failed_count + metrics.unranked_count;
}; };
</script> </script>

View File

@ -12,6 +12,8 @@ import pick from "lodash/pick";
export interface GradedUser { export interface GradedUser {
user: CourseSessionUser; user: CourseSessionUser;
points: number; points: number;
maxPoints: number;
passed: boolean;
} }
export async function loadAssignmentCompletionStatusData( export async function loadAssignmentCompletionStatusData(
@ -46,6 +48,8 @@ export async function loadAssignmentCompletionStatusData(
gradedUsers.push({ gradedUsers.push({
user: csu, user: csu,
points: userAssignmentStatus.evaluation_points ?? 0, points: userAssignmentStatus.evaluation_points ?? 0,
maxPoints: userAssignmentStatus.evaluation_max_points ?? 0,
passed: userAssignmentStatus.evaluation_passed ?? false,
}); });
} }
} }

View File

@ -531,6 +531,8 @@ export interface UserAssignmentCompletionStatus {
assignment_user_id: string; assignment_user_id: string;
completion_status: AssignmentCompletionStatus; completion_status: AssignmentCompletionStatus;
evaluation_points: number | null; evaluation_points: number | null;
evaluation_max_points: number | null;
evaluation_passed: boolean;
learning_content_page_id: string; learning_content_page_id: string;
} }

View File

@ -21,6 +21,7 @@ def request_assignment_completion_status(request, assignment_id, course_session_
"assignment_user_id", "assignment_user_id",
"completion_status", "completion_status",
"evaluation_points", "evaluation_points",
"evaluation_max_points",
"evaluation_passed", "evaluation_passed",
"learning_content_page_id", "learning_content_page_id",
) )

View File

@ -17,12 +17,12 @@ from vbv_lernwelt.assignment.tests.assignment_factories import (
from vbv_lernwelt.competence.factories import ( from vbv_lernwelt.competence.factories import (
ActionCompetenceFactory, ActionCompetenceFactory,
ActionCompetenceListPageFactory, ActionCompetenceListPageFactory,
CompetenceCertificateFactory,
CompetenceCertificateListFactory,
CompetenceNaviPageFactory, CompetenceNaviPageFactory,
PerformanceCriteriaFactory, PerformanceCriteriaFactory,
CompetenceCertificateListFactory,
CompetenceCertificateFactory,
) )
from vbv_lernwelt.competence.models import PerformanceCriteria, CompetenceCertificate from vbv_lernwelt.competence.models import CompetenceCertificate, PerformanceCriteria
from vbv_lernwelt.core.models import User from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.factories import CoursePageFactory from vbv_lernwelt.course.factories import CoursePageFactory
from vbv_lernwelt.course.models import ( from vbv_lernwelt.course.models import (

View File

@ -219,7 +219,7 @@ class CourseSessionEdoniqTest(models.Model):
self.deadline.url = self.learning_content.get_frontend_url( self.deadline.url = self.learning_content.get_frontend_url(
course_session_id=self.course_session.id course_session_id=self.course_session.id
) )
self.deadline.url_expert = f"/course/{self.course_session.course.slug}/cockpit?courseSessionId={self.course_session.id}" self.deadline.url_expert = f"/course/{self.course_session.course.slug}/cockpit/assignment/{self.learning_content_id}?courseSessionId={self.course_session.id}"
self.deadline.title = self.learning_content.title self.deadline.title = self.learning_content.title
self.deadline.page = self.learning_content.page_ptr self.deadline.page = self.learning_content.page_ptr
self.deadline.assignment_type_translation_key = ( self.deadline.assignment_type_translation_key = (

View File

@ -100,11 +100,11 @@ def get_assignment_completion_metrics(
average_passed = math.ceil(passed_count / participants_count * 100) average_passed = math.ceil(passed_count / participants_count * 100)
return AssignmentCompletionMetricsType( return AssignmentCompletionMetricsType(
_id=assignment.id, # noqa _id=f"{course_session.id}-{assignment.id}", # noqa
passed_count=passed_count, # noqa passed_count=passed_count, # noqa
failed_count=failed_count, # noqa failed_count=failed_count, # noqa
unranked_count=unranked_count, # noqa unranked_count=unranked_count, # noqa
ranking_completed=unranked_count == 0, # noqa ranking_completed=(passed_count > 0 or failed_count > 0), # noqa
average_passed=average_passed, # noqa average_passed=average_passed, # noqa
) )

View File

@ -11,13 +11,13 @@ from vbv_lernwelt.course.creators.test_utils import (
create_assignment_completion, create_assignment_completion,
create_assignment_learning_content, create_assignment_learning_content,
create_circle, create_circle,
create_competence_certificate,
create_course, create_course,
create_course_session, create_course_session,
create_course_session_assignment, create_course_session_assignment,
create_course_session_edoniq_test, create_course_session_edoniq_test,
create_course_session_group, create_course_session_group,
create_user, create_user,
create_competence_certificate,
) )
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.course_session.models import ( from vbv_lernwelt.course_session.models import (
@ -284,11 +284,11 @@ class AssignmentTestCase(GraphQLTestCase):
# 1 -> incomplete (not counted for average) # 1 -> incomplete (not counted for average)
# 2 -> complete 66% passed ... # 2 -> complete 66% passed ...
# 3 -> complete 100% passed --> 83.5% # 3 -> complete 100% passed --> 67%
# 4 -> not included in certificate # 4 -> not included in certificate
summary = dashboard["assignments"]["summary"] summary = dashboard["assignments"]["summary"]
self.assertEqual(summary["completed_count"], 2) self.assertEqual(summary["completed_count"], 3)
self.assertEqual(summary["average_passed"], 83.5) self.assertEqual(summary["average_passed"], 67.0)
records = dashboard["assignments"]["records"] records = dashboard["assignments"]["records"]
self.assertEqual(len(records), 3) self.assertEqual(len(records), 3)
@ -298,7 +298,7 @@ class AssignmentTestCase(GraphQLTestCase):
self.assertEqual(assignment_1_metrics["passed_count"], 1) self.assertEqual(assignment_1_metrics["passed_count"], 1)
self.assertEqual(assignment_1_metrics["failed_count"], 1) self.assertEqual(assignment_1_metrics["failed_count"], 1)
self.assertEqual(assignment_1_metrics["unranked_count"], 1) self.assertEqual(assignment_1_metrics["unranked_count"], 1)
self.assertEqual(assignment_1_metrics["ranking_completed"], False) self.assertEqual(assignment_1_metrics["ranking_completed"], True)
self.assertEqual(assignment_1_metrics["average_passed"], 34) self.assertEqual(assignment_1_metrics["average_passed"], 34)
# 2 -> assignment_2_results # 2 -> assignment_2_results

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.20 on 2023-11-08 06:47
from django.db import migrations
def set_url_expert_course_session_edoniq_test(apps, schema_editor):
# need to load concrete model, so that wagtail page has `specific` instance method...
from vbv_lernwelt.course_session.models import CourseSessionEdoniqTest
for edoniq_test in CourseSessionEdoniqTest.objects.all():
# trigger save to update due_date foreign key fields
edoniq_test.save()
class Migration(migrations.Migration):
dependencies = [
("duedate", "0007_auto_20231010_1505"),
]
operations = [
migrations.RunPython(
set_url_expert_course_session_edoniq_test, migrations.RunPython.noop
),
]