Merged in feature/VBV-585-cockpit-edoniq-tests (pull request #229)

Feature/VBV-585 cockpit edoniq tests

Approved-by: Christian Cueni
This commit is contained in:
Daniel Egger 2023-11-08 07:40:38 +00:00
commit f99e58ee88
20 changed files with 212 additions and 105 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,14 +38,14 @@ 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>
<template> <template>
<main v-if="statistics"> <main v-if="statistics">
<div class="mb-10 flex items-center justify-between"> <div class="mb-10 flex items-center justify-between">
<h3>{{ $t("a.Arbeiten") }}</h3> <h3>{{ $t("a.Kompetenznachweis-Elemente") }}</h3>
<ItDropdownSelect <ItDropdownSelect
:model-value="dashboardStore.currentDashboardConfig" :model-value="dashboardStore.currentDashboardConfig"
class="mt-4 w-full lg:mt-0 lg:w-96" class="mt-4 w-full lg:mt-0 lg:w-96"

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

@ -171,7 +171,7 @@ describe("assignmentTrainer.cy.js", () => {
).click(); ).click();
cy.get('[data-cy="Student1"]').should("contain", "Bewertung freigegeben"); cy.get('[data-cy="Student1"]').should("contain", "Bewertung freigegeben");
cy.get('[data-cy="Student1"]').should("contain", "17 Punkte"); cy.get('[data-cy="Student1"]').should("contain", "17 von 24 Punkte");
// clicking on results page will go to last step // clicking on results page will go to last step
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click(); cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();

View File

@ -1,4 +1,6 @@
import {login} from "../helpers"; import { login } from "../helpers";
// ignore automatic import mess-up...
const getDashboardStatistics = (what) => { const getDashboardStatistics = (what) => {
return cy.get(`[data-cy="dashboard.stats.${what}"]`); return cy.get(`[data-cy="dashboard.stats.${what}"]`);
@ -7,12 +9,14 @@ const getDashboardStatistics = (what) => {
const clickOnDetailsLink = (within) => { const clickOnDetailsLink = (within) => {
cy.get(`[data-cy="dashboard.stats.${within}"]`).within(() => { cy.get(`[data-cy="dashboard.stats.${within}"]`).within(() => {
cy.get('[data-cy="basebox.detailsLink"]').click(); cy.get('[data-cy="basebox.detailsLink"]').click();
}) });
}; };
describe("dashboardSupervisor.cy.js", () => { describe("dashboardSupervisor.cy.js", () => {
beforeEach(() => { beforeEach(() => {
cy.manageCommand("cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days"); cy.manageCommand(
"cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days"
);
login("test-supervisor1@example.com", "test"); login("test-supervisor1@example.com", "test");
cy.visit("/"); cy.visit("/");
}); });
@ -21,8 +25,8 @@ describe("dashboardSupervisor.cy.js", () => {
it("contains correct numbers", () => { it("contains correct numbers", () => {
// we have no completed assignments, but some are in progress // we have no completed assignments, but some are in progress
// -> makes sure that the numbers are correct // -> makes sure that the numbers are correct
getDashboardStatistics("assignments.completed").should("have.text", "0"); getDashboardStatistics("assignments.completed").should("have.text", "1");
getDashboardStatistics("assignments.passed").should("have.text", "0%"); getDashboardStatistics("assignments.passed").should("have.text", "34%");
}); });
it("contains correct details link", () => { it("contains correct details link", () => {
@ -31,15 +35,21 @@ describe("dashboardSupervisor.cy.js", () => {
// might be improved: roughly check // might be improved: roughly check
// that the correct data is displayed // that the correct data is displayed
cy.contains("Noch nicht bestätigt"); cy.contains("Noch nicht bestätigt");
cy.contains("Fahrzeug - Mein erstes Auto"); cy.contains("Überprüfen einer Motorfahrzeugs-Versicherungspolice");
cy.contains("Test Bern 2022 a"); cy.contains("Test Bern 2022 a");
}); });
}); });
describe("attendance day summary box", () => { describe("attendance day summary box", () => {
it("contains correct numbers", () => { it("contains correct numbers", () => {
getDashboardStatistics("attendance.dayCompleted").should("have.text", "1"); getDashboardStatistics("attendance.dayCompleted").should(
getDashboardStatistics("attendance.participantsPresent").should("have.text", "34%"); "have.text",
"1"
);
getDashboardStatistics("attendance.participantsPresent").should(
"have.text",
"34%"
);
}); });
it("contains correct details link", () => { it("contains correct details link", () => {
clickOnDetailsLink("attendance"); clickOnDetailsLink("attendance");
@ -53,7 +63,6 @@ describe("dashboardSupervisor.cy.js", () => {
}); });
}); });
describe("overall summary box", () => { describe("overall summary box", () => {
it("contains correct numbers (members, experts etc.)", () => { it("contains correct numbers (members, experts etc.)", () => {
getDashboardStatistics("participant.count").should("have.text", "4"); getDashboardStatistics("participant.count").should("have.text", "4");
@ -66,7 +75,6 @@ describe("dashboardSupervisor.cy.js", () => {
it("contains correct numbers", () => { it("contains correct numbers", () => {
getDashboardStatistics("feedback.average").should("have.text", "3.3"); getDashboardStatistics("feedback.average").should("have.text", "3.3");
getDashboardStatistics("feedback.count").should("have.text", "3"); getDashboardStatistics("feedback.count").should("have.text", "3");
}); });
it("contains correct details link", () => { it("contains correct details link", () => {
clickOnDetailsLink("feedback"); clickOnDetailsLink("feedback");
@ -76,8 +84,8 @@ describe("dashboardSupervisor.cy.js", () => {
// that the correct data is displayed // that the correct data is displayed
cy.contains("3.3 von 4"); cy.contains("3.3 von 4");
cy.contains("Test Trainer1"); cy.contains("Test Trainer1");
cy.contains("Durchführung «Test Bern 2022 a»") cy.contains("Durchführung «Test Bern 2022 a»");
cy.contains("Circle «Fahrzeug»") cy.contains("Circle «Fahrzeug»");
}); });
}); });
@ -94,8 +102,7 @@ describe("dashboardSupervisor.cy.js", () => {
// that the correct data is displayed // that the correct data is displayed
cy.contains("Selbsteinschätzung: Vorbereitung"); cy.contains("Selbsteinschätzung: Vorbereitung");
cy.contains("Durchführung «Test Bern 2022 a»"); cy.contains("Durchführung «Test Bern 2022 a»");
cy.contains("Circle «Fahrzeug»") cy.contains("Circle «Fahrzeug»");
}); });
}); });
}); });

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,10 +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,
) )
from vbv_lernwelt.competence.models import PerformanceCriteria 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 (
@ -160,6 +162,12 @@ def create_attendance_course(
) )
def create_competence_certificate(course: Course) -> CompetenceCertificate:
navi = CompetenceNaviPageFactory(parent=course.coursepage)
certificate_list = CompetenceCertificateListFactory(parent=navi)
return CompetenceCertificateFactory(parent=certificate_list)
def create_assignment( def create_assignment(
course: Course, course: Course,
assignment_type: AssignmentType, assignment_type: AssignmentType,

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
) )
@ -121,12 +121,14 @@ def create_record(
return AssignmentStatisticsRecordType( return AssignmentStatisticsRecordType(
# make sure it's unique, across all types of assignments! # make sure it's unique, across all types of assignments!
_id=f"{course_session_assignment._meta.model_name}#{course_session_assignment.id}", # noqa _id=f"{course_session_assignment._meta.model_name}#{course_session_assignment.id}",
# noqa
course_session_id=str(course_session_assignment.course_session.id), # noqa course_session_id=str(course_session_assignment.course_session.id), # noqa
circle_id=learning_content.get_circle().id, # noqa circle_id=learning_content.get_circle().id, # noqa
course_session_assignment_id=str(course_session_assignment.id), # noqa course_session_assignment_id=str(course_session_assignment.id), # noqa
generation=course_session_assignment.course_session.generation, # noqa generation=course_session_assignment.course_session.generation, # noqa
assignment_type_translation_key=due_date.assignment_type_translation_key, # noqa assignment_type_translation_key=due_date.assignment_type_translation_key,
# noqa
assignment_title=learning_content.content_assignment.title, # noqa assignment_title=learning_content.content_assignment.title, # noqa
metrics=get_assignment_completion_metrics( # noqa metrics=get_assignment_completion_metrics( # noqa
course_session=course_session_assignment.course_session, # noqa course_session=course_session_assignment.course_session, # noqa
@ -149,16 +151,17 @@ def assignments(
for course_session in course_sessions: for course_session in course_sessions:
for csa in CourseSessionAssignment.objects.filter( for csa in CourseSessionAssignment.objects.filter(
course_session=course_session, course_session=course_session,
learning_content__assignment_type__in=[ learning_content__content_assignment__assignment_type__in=[
AssignmentType.CASEWORK.value, AssignmentType.CASEWORK.value,
AssignmentType.PREP_ASSIGNMENT.value,
], ],
learning_content__content_assignment__competence_certificate__isnull=False,
): ):
record = create_record(course_session_assignment=csa) record = create_record(course_session_assignment=csa)
records.append(record) records.append(record)
for cset in CourseSessionEdoniqTest.objects.filter( for cset in CourseSessionEdoniqTest.objects.filter(
course_session=course_session course_session=course_session,
learning_content__content_assignment__competence_certificate__isnull=False,
): ):
record = create_record(course_session_assignment=cset) record = create_record(course_session_assignment=cset)
records.append(record) records.append(record)

View File

@ -11,6 +11,7 @@ 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,
@ -101,11 +102,6 @@ class AssignmentTestCase(GraphQLTestCase):
assignment_type=AssignmentType.CASEWORK assignment_type=AssignmentType.CASEWORK
) )
def test_dashboard_contains_prep_assignments(self):
self._test_assignment_type_dashboard_details(
assignment_type=AssignmentType.PREP_ASSIGNMENT
)
def test_dashboard_contains_edoniq_tests(self): def test_dashboard_contains_edoniq_tests(self):
self._test_assignment_type_dashboard_details( self._test_assignment_type_dashboard_details(
assignment_type=AssignmentType.EDONIQ_TEST assignment_type=AssignmentType.EDONIQ_TEST
@ -120,7 +116,6 @@ class AssignmentTestCase(GraphQLTestCase):
irrelevant_types_for_dashboard = set(AssignmentType) - { irrelevant_types_for_dashboard = set(AssignmentType) - {
AssignmentType.CASEWORK, AssignmentType.CASEWORK,
AssignmentType.PREP_ASSIGNMENT,
AssignmentType.EDONIQ_TEST, AssignmentType.EDONIQ_TEST,
} }
@ -134,6 +129,7 @@ class AssignmentTestCase(GraphQLTestCase):
deadline_at=datetime(2000, 4, 1), deadline_at=datetime(2000, 4, 1),
course_session=self.course_session, course_session=self.course_session,
circle=self.circle, circle=self.circle,
add_competence_certificate=True,
) )
create_assignment_completion( create_assignment_completion(
@ -203,6 +199,7 @@ class AssignmentTestCase(GraphQLTestCase):
assignment_type=AssignmentType.CASEWORK, assignment_type=AssignmentType.CASEWORK,
course_session=self.course_session, course_session=self.course_session,
circle=self.circle, circle=self.circle,
add_competence_certificate=True,
) )
assignment_2, _ = mix_assignment_cocktail( assignment_2, _ = mix_assignment_cocktail(
@ -210,13 +207,15 @@ class AssignmentTestCase(GraphQLTestCase):
assignment_type=AssignmentType.EDONIQ_TEST, assignment_type=AssignmentType.EDONIQ_TEST,
course_session=self.course_session, course_session=self.course_session,
circle=self.circle, circle=self.circle,
add_competence_certificate=True,
) )
assignment_3, _ = mix_assignment_cocktail( assignment_3, _ = mix_assignment_cocktail(
deadline_at=datetime(2010, 4, 1), deadline_at=datetime(2010, 4, 1),
assignment_type=AssignmentType.PREP_ASSIGNMENT, assignment_type=AssignmentType.CASEWORK,
course_session=self.course_session, course_session=self.course_session,
circle=self.circle, circle=self.circle,
add_competence_certificate=True,
) )
# no completions for this assignment yet # no completions for this assignment yet
@ -225,6 +224,7 @@ class AssignmentTestCase(GraphQLTestCase):
assignment_type=AssignmentType.EDONIQ_TEST, assignment_type=AssignmentType.EDONIQ_TEST,
course_session=self.course_session, course_session=self.course_session,
circle=self.circle, circle=self.circle,
add_competence_certificate=False,
) )
# assignment 1 # assignment 1
@ -284,21 +284,21 @@ 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 -> incomplete (not counted for average) # 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), 4) self.assertEqual(len(records), 3)
# 1 -> assigment_1_results (oldest) # 1 -> assigment_1_results (oldest)
assignment_1_metrics = records[0]["metrics"] assignment_1_metrics = records[0]["metrics"]
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
@ -317,19 +317,12 @@ class AssignmentTestCase(GraphQLTestCase):
self.assertEqual(assignment_3_metrics["ranking_completed"], True) self.assertEqual(assignment_3_metrics["ranking_completed"], True)
self.assertEqual(assignment_3_metrics["average_passed"], 100) self.assertEqual(assignment_3_metrics["average_passed"], 100)
# 4 -> no completions (newest)
assignment_4_metrics = records[3]["metrics"]
self.assertEqual(assignment_4_metrics["passed_count"], 0)
self.assertEqual(assignment_4_metrics["failed_count"], 0)
self.assertEqual(assignment_4_metrics["unranked_count"], 3)
self.assertEqual(assignment_4_metrics["ranking_completed"], False)
self.assertEqual(assignment_4_metrics["average_passed"], 0)
def mix_assignment_cocktail( def mix_assignment_cocktail(
assignment_type: AssignmentType, assignment_type: AssignmentType,
course_session: CourseSession, course_session: CourseSession,
circle: Circle, circle: Circle,
add_competence_certificate: bool = False,
deadline_at: datetime | None = None, deadline_at: datetime | None = None,
) -> Tuple[Assignment, CourseSessionAssignment | CourseSessionEdoniqTest]: ) -> Tuple[Assignment, CourseSessionAssignment | CourseSessionEdoniqTest]:
""" """
@ -338,9 +331,15 @@ def mix_assignment_cocktail(
""" """
assignment = create_assignment( assignment = create_assignment(
course=course_session.course, assignment_type=assignment_type course=course_session.course,
assignment_type=assignment_type,
) )
if add_competence_certificate:
certificate = create_competence_certificate(course=course_session.course)
assignment.competence_certificate = certificate
assignment.save()
if assignment_type == AssignmentType.EDONIQ_TEST: if assignment_type == AssignmentType.EDONIQ_TEST:
cset = create_course_session_edoniq_test( cset = create_course_session_edoniq_test(
deadline_at=deadline_at, deadline_at=deadline_at,

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
),
]