Merged in feat/course-feature-toggles (pull request #295)

Introduce Course Configuration

Approved-by: Daniel Egger
This commit is contained in:
Livio Bieri 2024-03-06 19:24:53 +00:00
commit 709931c888
42 changed files with 501 additions and 334 deletions

View File

@ -23,7 +23,6 @@ import {
getMediaCenterUrl,
} from "@/utils/utils";
import { useCockpitStore } from "@/stores/cockpit";
import { VV_COURSE_IDS } from "@/constants";
log.debug("MainNavigationBar created");
@ -100,13 +99,13 @@ const hasNotificationsMenu = computed(() => {
});
const hasMentorManagementMenu = computed(() => {
if (courseSessionsStore.currentCourseSessionHasCockpit) {
if (courseSessionsStore.currentCourseSessionHasCockpit || !inCourse()) {
return false;
}
// learning mentor management is only available for VV courses
const currentCourseId = courseSessionsStore.currentCourseSession?.course.id || "";
return inCourse() && VV_COURSE_IDS.includes(currentCourseId);
return (
courseSessionsStore.currentCourseSession?.course.configuration
.enable_learning_mentor ?? false
);
});
</script>

View File

@ -37,7 +37,7 @@ const questionIndex = useRouteQuery("step", "0", { transform: Number, mode: "pus
const previousRoute = getPreviousRoute();
const learningUnitHasFeedbackPage = computed(
() => props.learningUnit?.feedback_user !== "NO_FEEDBACK"
() => courseSession.value.course.configuration.enable_learning_mentor
);
const currentQuestion = computed(() => questions.value[questionIndex.value]);
@ -163,7 +163,7 @@ onUnmounted(() => {
</div>
</div>
<SelfEvaluationRequestFeedbackPage
v-else-if="isLastStep && learningUnit.feedback_user == 'MENTOR_FEEDBACK'"
v-else-if="isLastStep && learningUnitHasFeedbackPage"
:learning-unit="props.learningUnit"
:criteria="questions"
/>

View File

@ -3,9 +3,3 @@ export const itCheckboxDefaultIconCheckedTailwindClass =
export const itCheckboxDefaultIconUncheckedTailwindClass =
"bg-[url(/static/icons/icon-checkbox-unchecked.svg)] hover:bg-[url(/static/icons/icon-checkbox-unchecked-hover.svg)]";
export const VV_COURSE_IDS = [
"-4", // vv-de
"-10", // vv-fr
"-11", // vv-it
];

View File

@ -19,9 +19,9 @@ const documents = {
"\n query attendanceCheckQuery($courseSessionId: ID!) {\n course_session_attendance_course(id: $courseSessionId) {\n id\n attendance_user_list {\n user_id\n status\n }\n }\n }\n": types.AttendanceCheckQueryDocument,
"\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 solution_sample {\n id\n url\n }\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 first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\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 courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n enable_circle_documents\n circle_contact_type\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 enable_circle_documents\n circle_contact_type\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 feedback_user\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 courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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 configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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 course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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 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 mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $learningContentType: String!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n learning_content_type: $learningContentType\n data: $data\n submitted: $submitted\n ) {\n feedback_response {\n id\n data\n submitted\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument,
@ -68,15 +68,15 @@ export function graphql(source: "\n query competenceCertificateQuery($courseSlu
/**
* 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 courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n enable_circle_documents\n circle_contact_type\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"): (typeof documents)["\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n enable_circle_documents\n circle_contact_type\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"];
export function graphql(source: "\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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"): (typeof documents)["\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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"];
/**
* 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 enable_circle_documents\n circle_contact_type\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 feedback_user\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 enable_circle_documents\n circle_contact_type\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 feedback_user\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"];
export function graphql(source: "\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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 configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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.
*/
export function graphql(source: "\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n }\n }\n"): (typeof documents)["\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n }\n }\n"];
export function graphql(source: "\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n }\n"): (typeof documents)["\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n }\n"];
/**
* 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

@ -200,6 +200,7 @@ type DashboardConfigType {
name: String!
slug: String!
dashboard_type: DashboardType!
course_configuration: CourseConfigurationObjectType!
}
enum DashboardType {
@ -208,6 +209,13 @@ enum DashboardType {
SIMPLE_DASHBOARD
}
type CourseConfigurationObjectType {
id: ID!
enable_circle_documents: Boolean!
enable_learning_mentor: Boolean!
enable_competence_certificates: Boolean!
}
type LearningPathObjectType implements CoursePageInterface {
id: ID!
title: String!
@ -236,21 +244,11 @@ type CourseObjectType {
title: String!
category_name: String!
slug: String!
enable_circle_documents: Boolean!
circle_contact_type: CourseCourseCircleContactTypeChoices!
configuration: CourseConfigurationObjectType!
learning_path: LearningPathObjectType!
action_competences: [ActionCompetenceObjectType!]!
}
"""An enumeration."""
enum CourseCourseCircleContactTypeChoices {
"""EXPERT"""
EXPERT
"""LEARNING_MENTOR"""
LEARNING_MENTOR
}
type ActionCompetenceObjectType implements CoursePageInterface {
competence_id: String!
id: ID!
@ -279,7 +277,6 @@ type PerformanceCriteriaObjectType implements CoursePageInterface {
type LearningUnitObjectType implements CoursePageInterface {
title_hidden: Boolean!
feedback_user: LearnpathLearningUnitFeedbackUserChoices!
id: ID!
title: String!
slug: String!
@ -293,15 +290,6 @@ type LearningUnitObjectType implements CoursePageInterface {
evaluate_url: String!
}
"""An enumeration."""
enum LearnpathLearningUnitFeedbackUserChoices {
"""NO_FEEDBACK"""
NO_FEEDBACK
"""MENTOR_FEEDBACK"""
MENTOR_FEEDBACK
}
interface LearningContentInterface {
id: ID!
title: String!

View File

@ -25,7 +25,7 @@ export const CompetenceRecordStatisticsType = "CompetenceRecordStatisticsType";
export const CompetencesStatisticsType = "CompetencesStatisticsType";
export const ContentDocumentObjectType = "ContentDocumentObjectType";
export const CoreUserLanguageChoices = "CoreUserLanguageChoices";
export const CourseCourseCircleContactTypeChoices = "CourseCourseCircleContactTypeChoices";
export const CourseConfigurationObjectType = "CourseConfigurationObjectType";
export const CourseObjectType = "CourseObjectType";
export const CoursePageInterface = "CoursePageInterface";
export const CourseProgressType = "CourseProgressType";
@ -69,7 +69,6 @@ export const LearningPathObjectType = "LearningPathObjectType";
export const LearningSequenceObjectType = "LearningSequenceObjectType";
export const LearningUnitObjectType = "LearningUnitObjectType";
export const LearnpathLearningContentAssignmentAssignmentTypeChoices = "LearnpathLearningContentAssignmentAssignmentTypeChoices";
export const LearnpathLearningUnitFeedbackUserChoices = "LearnpathLearningUnitFeedbackUserChoices";
export const Mutation = "Mutation";
export const PerformanceCriteriaObjectType = "PerformanceCriteriaObjectType";
export const PresenceRecordStatisticsType = "PresenceRecordStatisticsType";

View File

@ -126,8 +126,12 @@ export const COURSE_SESSION_DETAIL_QUERY = graphql(`
id
title
slug
enable_circle_documents
circle_contact_type
configuration {
id
enable_circle_documents
enable_learning_mentor
enable_competence_certificates
}
}
users {
id
@ -211,8 +215,12 @@ export const COURSE_QUERY = graphql(`
title
slug
category_name
enable_circle_documents
circle_contact_type
configuration {
id
enable_circle_documents
enable_learning_mentor
enable_competence_certificates
}
action_competences {
competence_id
...CoursePageFields
@ -239,7 +247,6 @@ export const COURSE_QUERY = graphql(`
icon
...CoursePageFields
learning_units {
feedback_user
evaluate_url
...CoursePageFields
performance_criteria {
@ -292,6 +299,12 @@ export const DASHBOARD_CONFIG = graphql(`
slug
name
dashboard_type
course_configuration {
id
enable_circle_documents
enable_learning_mentor
enable_competence_certificates
}
}
}
`);

View File

@ -59,7 +59,7 @@ const courseSessionDetailResult = useCourseSessionDetailQuery();
</div>
</div>
<div
v-if="courseSession.course.enable_circle_documents"
v-if="courseSession.course.configuration.enable_circle_documents"
class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0"
data-cy="circle-documents"
>
@ -70,13 +70,6 @@ const courseSessionDetailResult = useCourseSessionDetailQuery();
<div class="mb-4">
{{ $t("a.Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.") }}
</div>
<!-- <div-->
<!-- v-if="courseSessionsStore.circleDocuments.length"-->
<!-- class="mb-4 flex items-center gap-x-2"-->
<!-- >-->
<!-- <it-icon-document />-->
<!-- {{ courseSessionsStore.circleDocuments.length }} {{ $t("a.Unterlagen") }}-->
<!-- </div>-->
</div>
<div>
<router-link

View File

@ -12,7 +12,6 @@ import {
} from "@/pages/competence/utils";
import { useSelfEvaluationFeedbackSummaries } from "@/services/selfEvaluationFeedback";
import ItProgress from "@/components/ui/ItProgress.vue";
import { VV_COURSE_IDS } from "@/constants";
const props = defineProps<{
courseSlug: string;
@ -61,18 +60,7 @@ const feedbackEvaluationCounts = computed(
() => selfEvaluationFeedbackSummaries.aggregates.value?.feedback_assessment
);
const isFeedbackEvaluationVisible = computed(
() =>
selfEvaluationFeedbackSummaries.aggregates.value?.feedback_assessment_visible ??
false
);
// FIXME 22.02.24: To-be-tackled NEXT in a separate PR (shippable member comp.navi)
// -> Do not use the VV_COURSE_ID anymore (discuss with @chrigu) -> We do this next.
const currentCourseSession = useCurrentCourseSession();
const hasCompetenceCertificates = computed(() => {
return !VV_COURSE_IDS.includes(currentCourseSession.value.course.id);
});
const isLoaded = computed(
() =>
@ -83,7 +71,10 @@ const isLoaded = computed(
<template>
<div v-if="isLoaded" class="container-large lg:mt-4">
<!-- Competence certificates -->
<section v-if="hasCompetenceCertificates" class="mb-4 bg-white p-8">
<section
v-if="currentCourseSession.course.configuration.enable_competence_certificates"
class="mb-4 bg-white p-8"
>
<div class="flex items-center">
<h3>{{ $t("a.Kompetenznachweise") }}</h3>
</div>
@ -204,7 +195,10 @@ const isLoaded = computed(
</div>
<!-- Feedback evaluation -->
<div v-if="isFeedbackEvaluationVisible" class="mb-8 border-t pt-8">
<div
v-if="courseSession.course.configuration.enable_learning_mentor"
class="mb-8 border-t pt-8"
>
<h3 class="mb-4 pb-4 lg:pb-0">
{{ $t("a.Fremdeinschätzungen") }}
</h3>

View File

@ -1,8 +1,7 @@
<script setup lang="ts">
import * as log from "loglevel";
import { computed, onMounted } from "vue";
import { onMounted } from "vue";
import { useRoute } from "vue-router";
import { VV_COURSE_IDS } from "@/constants";
import { useCurrentCourseSession } from "@/composables";
log.debug("CompetenceParentPage created");
@ -29,12 +28,7 @@ function routeInSelfEvaluationAndFeedback() {
return route.path.endsWith("/self-evaluation-and-feedback");
}
// FIXME 22.02.24: To-be-tackled NEXT in a separate PR (shippable member comp.navi)
// -> Do not use the VV_COURSE_ID anymore (discuss with @chrigu) -> We do this next.
const currentCourseSession = useCurrentCourseSession();
const isVVCourse = computed(() => {
return VV_COURSE_IDS.includes(currentCourseSession.value.course.id);
});
onMounted(async () => {
log.debug("CompetenceParentPage mounted", props.courseSlug);
@ -54,7 +48,9 @@ onMounted(async () => {
</router-link>
</li>
<li
v-if="!isVVCourse"
v-if="
currentCourseSession.course.configuration.enable_competence_certificates
"
class="border-t-2 border-t-transparent lg:ml-12"
:class="{ 'border-b-2 border-b-blue-900': routeInCompetenceCertificate() }"
>
@ -76,7 +72,7 @@ onMounted(async () => {
class="block py-3"
>
{{
isVVCourse
currentCourseSession.course.configuration.enable_learning_mentor
? $t("a.Selbst- und Fremdeinschätzungen")
: $t("a.Selbsteinschätzungen")
}}

View File

@ -10,6 +10,9 @@ const selfEvaluationFeedbackSummaries = useSelfEvaluationFeedbackSummaries(
useCurrentCourseSession().value.id
);
const courseSession = useCurrentCourseSession();
const course = computed(() => courseSession.value.course);
const isLoaded = computed(() => !selfEvaluationFeedbackSummaries.loading.value);
const selectedCircle = ref({ name: t("a.AlleCircle"), id: "_all" });
@ -32,10 +35,7 @@ const summaries = computed(() => {
});
const headerTitle = computed(() => {
const canHaveFeedback =
selfEvaluationFeedbackSummaries.aggregates.value?.feedback_assessment_visible ??
false;
if (canHaveFeedback) {
if (course.value.configuration.enable_learning_mentor) {
return t("a.Selbst- und Fremdeinschätzungen");
} else {
return t("a.Selbsteinschätzungen");

View File

@ -10,7 +10,6 @@ import type {
} from "@/gql/graphql";
import CompetenceSummaryBox from "@/components/dashboard/CompetenceSummaryBox.vue";
import AssignmentProgressSummaryBox from "@/components/dashboard/AssignmentProgressSummaryBox.vue";
import { VV_COURSE_IDS } from "@/constants";
const dashboardStore = useDashboardStore();
@ -48,11 +47,12 @@ const competenceCriteriaUrl = computed(() => {
return `/course/${courseSlug.value}/competence/self-evaluation-and-feedback?courseSessionId=${courseSessionProgress.value?.session_to_continue_id}`;
});
const isVVCourse = computed(() => {
const showCompetenceCertificates = computed(() => {
if (!dashboardStore.currentDashboardConfig) {
return false;
}
return VV_COURSE_IDS.includes(dashboardStore.currentDashboardConfig.id);
return dashboardStore.currentDashboardConfig.course_configuration
.enable_competence_certificates;
});
</script>
@ -82,7 +82,7 @@ const isVVCourse = computed(() => {
</div>
<div class="grid auto-rows-fr grid-cols-1 gap-8 xl:grid-cols-2">
<AssignmentProgressSummaryBox
v-if="!isVVCourse"
v-if="showCompetenceCertificates"
:total-assignments="assignment.total_count"
:achieved-points-count="assignment.points_achieved_count"
:max-points-count="assignment.points_max_count"

View File

@ -57,33 +57,14 @@ const learningContentReadonly = computed(() => {
return props.readonly || !actions.includes("complete-learning-content");
});
const duration = computed(() => {
// if (circleStore.circle) {
// const minutes = sumBy(circleStore.circle.learningSequences, "minutes");
// return humanizeDuration(minutes);
// }
return "";
});
const showDuration = computed(() => {
// return (
// circleStore.circle && sumBy(circleStore.circle.learningSequences, "minutes") > 0
// );
return false;
});
const showDocumentSection = computed(() => {
return lpQueryResult.course.value?.enable_circle_documents && !props.readonly;
return (
lpQueryResult.course.value?.configuration.enable_circle_documents && !props.readonly
);
});
const courseConfig = computed(() => {
if (lpQueryResult.course.value?.circle_contact_type === "EXPERT") {
return {
contactDescription: "circlePage.contactExpertDescription",
contactButton: "circlePage.contactExpertButton",
showContact: true,
};
} else if (lpQueryResult.course.value?.circle_contact_type === "LEARNING_MENTOR") {
if (lpQueryResult.course.value?.configuration.enable_learning_mentor) {
return {
contactDescription: "circlePage.contactLearningMentorDescription",
contactButton: "circlePage.contactLearningMentorButton",
@ -91,8 +72,8 @@ const courseConfig = computed(() => {
};
} else {
return {
contactDescription: "",
contactButton: "",
contactDescription: "circlePage.contactExpertDescription",
contactButton: "circlePage.contactExpertButton",
showContact: true,
};
}
@ -117,12 +98,12 @@ interface Mentor {
const experts = computed<Expert[] | null>(() => {
if (courseConfig.value.showContact) {
if (lpQueryResult.course.value?.circle_contact_type === "EXPERT") {
return circleExperts.value;
} else if (lpQueryResult.course.value?.circle_contact_type === "LEARNING_MENTOR") {
if (lpQueryResult.course.value?.configuration.enable_learning_mentor) {
if (mentors.value?.length > 0) {
return mentors.value.map((m: Mentor) => m.mentor);
}
} else {
return circleExperts.value;
}
}
return null;
@ -216,11 +197,6 @@ watch(
{{ circle?.title }}
</h1>
<div v-if="showDuration" class="mt-2">
{{ $t("circlePage.duration") }}:
{{ duration }}
</div>
<div class="mt-8 w-full">
<CircleDiagram v-if="circle" :circle="circle"></CircleDiagram>
</div>

View File

@ -35,8 +35,6 @@ export interface FeedbackSummaryAggregates {
// totals across all learning units in the course session
self_assessment: FeedbackSummaryCounts;
feedback_assessment: FeedbackSummaryCounts;
// does this course have any feedback?
feedback_assessment_visible: boolean;
}
interface FeedbackAssessmentSummary {

View File

@ -5,7 +5,6 @@ import type {
AssignmentCompletionStatus as AssignmentCompletionStatusGenerated,
AssignmentObjectType,
CircleObjectType,
CourseCourseCircleContactTypeChoices,
CourseSessionObjectType,
CourseSessionUserObjectsType,
LearningContentAssignmentObjectType,
@ -190,13 +189,18 @@ export interface CourseCompletion {
additional_json_data: unknown;
}
export interface CourseConfiguration {
enable_circle_documents: boolean;
enable_learning_mentor: boolean;
enable_competence_certificates: boolean;
}
export interface Course {
id: string;
title: string;
category_name: string;
slug: string;
enable_circle_documents: boolean;
circle_contact_type: CourseCourseCircleContactTypeChoices;
configuration: CourseConfiguration;
}
export interface CourseCategory {

View File

@ -369,5 +369,5 @@ def command(
)
course = Course.objects.get(id=COURSE_TEST_ID)
course.enable_circle_documents = enable_circle_documents
course.save()
course.configuration.enable_circle_documents = enable_circle_documents
course.configuration.save()

View File

@ -1,6 +1,11 @@
from django.contrib import admin
from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser
from vbv_lernwelt.course.models import (
Course,
CourseConfiguration,
CourseSession,
CourseSessionUser,
)
from vbv_lernwelt.feedback.services import (
get_feedbacks_for_course_sessions,
get_feedbacks_for_courses,
@ -11,6 +16,16 @@ get_feedbacks_for_course_sessions.short_description = "Feedback export"
get_feedbacks_for_courses.short_description = "Feedback export"
@admin.register(CourseConfiguration)
class CourseConfigurationAdmin(admin.ModelAdmin):
list_display = [
"course",
"enable_circle_documents",
"enable_learning_mentor",
"enable_competence_certificates",
]
@admin.register(Course)
class CourseAdmin(admin.ModelAdmin):
list_display = [

View File

@ -10,3 +10,20 @@ COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID = -10
COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID = -11
COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID = -12
COURSE_MOTORFAHRZEUG_PRUEFUNG_ID = -13
VV_COURSE_IDS = [
COURSE_VERSICHERUNGSVERMITTLERIN_ID,
COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID,
COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID,
COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID,
COURSE_MOTORFAHRZEUG_PRUEFUNG_ID,
]
UK_COURSE_IDS = [
COURSE_UK,
COURSE_UK_FR,
COURSE_UK_IT,
COURSE_UK_TRAINING,
COURSE_UK_TRAINING_FR,
COURSE_UK_TRAINING_IT,
]

View File

@ -104,7 +104,10 @@ def create_test_course(include_uk=True, include_vv=True, with_sessions=False):
if UserImage.objects.count() == 0 and ContentImage.objects.count() == 0:
create_default_images()
course = create_test_course_with_categories()
course: Course = create_test_course_with_categories()
course.configuration.enable_learning_mentor = False
course.configuration.save()
competence_certificate = create_test_competence_navi()
# assignments create assignments parent page

View File

@ -24,6 +24,7 @@ from vbv_lernwelt.competence.factories import (
)
from vbv_lernwelt.competence.models import CompetenceCertificate, PerformanceCriteria
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.consts import COURSE_TEST_ID, UK_COURSE_IDS, VV_COURSE_IDS
from vbv_lernwelt.course.factories import CoursePageFactory
from vbv_lernwelt.course.models import (
Course,
@ -47,7 +48,6 @@ from vbv_lernwelt.learnpath.models import (
LearningContentEdoniqTest,
LearningPath,
LearningUnit,
LearningUnitPerformanceFeedbackType,
)
from vbv_lernwelt.learnpath.tests.learning_path_factories import (
CircleFactory,
@ -276,7 +276,6 @@ def create_learning_unit(
circle: Circle,
course: Course,
course_category_title: str = "Course Category",
feedback_user: LearningUnitPerformanceFeedbackType = LearningUnitPerformanceFeedbackType.NO_FEEDBACK,
) -> LearningUnit:
cat, _ = CourseCategory.objects.get_or_create(
course=course,
@ -287,7 +286,6 @@ def create_learning_unit(
title="Learning Unit",
parent=circle,
course_category=cat,
feedback_user=feedback_user.value,
)
@ -337,3 +335,68 @@ def create_circle_expert(
course_session_expert_user.expert.add(circle)
return course_session_expert_user
def apply_course_configuration(course: Course):
"""
Apply course configuration based on course id:
By default everything is enabled, disable unnecessary features
"""
if course.id == COURSE_TEST_ID:
pass # all features are enabled
elif course.id in VV_COURSE_IDS:
course.configuration.enable_competence_certificates = False
elif course.id in UK_COURSE_IDS:
course.configuration.enable_learning_mentor = False
else:
raise ValueError(f"Unknown course id {course.id}")
course.configuration.save()
def create_course_with_categories(
course_id,
title,
apps=None,
):
if apps is not None:
Course = apps.get_model("course", "Course")
CourseCategory = apps.get_model("course", "CourseCategory")
else:
# pylint: disable=import-outside-toplevel
from vbv_lernwelt.course.models import Course, CourseCategory
course, _ = Course.objects.get_or_create(
id=course_id,
title=title,
category_name="Handlungsfeld",
)
apply_course_configuration(course)
CourseCategory.objects.get_or_create(course=course, title="Allgemein", general=True)
for cat in [
"Fahrzeug",
"Reisen",
"Einkommenssicherung",
"Gesundheit",
"Haushalt",
"Sparen",
"Pensionierung",
"KMU",
"Wohneigentum",
"Rechtsstreitigkeiten",
"Erben / Vererben",
"Selbstständigkeit",
]:
CourseCategory.objects.get_or_create(course=course, title=cat)
course_page = CoursePageFactory(
title=title,
parent=get_wagtail_default_site().root_page,
course=course,
)
course.slug = course_page.slug
course.save()
return course

View File

@ -1,54 +0,0 @@
from vbv_lernwelt.course.consts import COURSE_VERSICHERUNGSVERMITTLERIN_ID
from vbv_lernwelt.course.factories import CoursePageFactory
from vbv_lernwelt.course.models import CircleContactType
from vbv_lernwelt.course.utils import get_wagtail_default_site
def create_versicherungsvermittlerin_with_categories(
apps=None,
schema_editor=None,
course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID,
title="Versicherungsvermittler/-in",
):
if apps is not None:
Course = apps.get_model("course", "Course")
CourseCategory = apps.get_model("course", "CourseCategory")
else:
# pylint: disable=import-outside-toplevel
from vbv_lernwelt.course.models import Course, CourseCategory
course, _ = Course.objects.get_or_create(
id=course_id,
title=title,
category_name="Handlungsfeld",
enable_circle_documents=False,
circle_contact_type=CircleContactType.LEARNING_MENTOR.value,
)
CourseCategory.objects.get_or_create(course=course, title="Allgemein", general=True)
for cat in [
"Fahrzeug",
"Reisen",
"Einkommenssicherung",
"Gesundheit",
"Haushalt",
"Sparen",
"Pensionierung",
"KMU",
"Wohneigentum",
"Rechtsstreitigkeiten",
"Erben / Vererben",
"Selbstständigkeit",
]:
CourseCategory.objects.get_or_create(course=course, title=cat)
course_page = CoursePageFactory(
title=title,
parent=get_wagtail_default_site().root_page,
course=course,
)
course.slug = course_page.slug
course.save()
return course

View File

@ -12,6 +12,7 @@ from vbv_lernwelt.course.models import (
CircleDocument,
Course,
CourseBasePage,
CourseConfiguration,
CoursePage,
CourseSession,
CourseSessionUser,
@ -86,11 +87,23 @@ def resolve_course_page(
raise e
class CourseConfigurationObjectType(DjangoObjectType):
class Meta:
model = CourseConfiguration
fields = (
"id",
"enable_circle_documents",
"enable_learning_mentor",
"enable_competence_certificates",
)
class CourseObjectType(DjangoObjectType):
learning_path = graphene.Field(LearningPathObjectType, required=True)
action_competences = graphene.List(
graphene.NonNull(ActionCompetenceObjectType), required=True
)
configuration = graphene.Field(CourseConfigurationObjectType, required=True)
class Meta:
model = Course
@ -99,8 +112,7 @@ class CourseObjectType(DjangoObjectType):
"title",
"category_name",
"slug",
"enable_circle_documents",
"circle_contact_type",
"configuration",
)
@staticmethod

View File

@ -66,6 +66,7 @@ from vbv_lernwelt.course.creators.test_course import (
create_edoniq_test_assignment,
create_test_course,
)
from vbv_lernwelt.course.creators.test_utils import create_course_with_categories
from vbv_lernwelt.course.creators.uk_course import (
create_competence_navi,
create_uk_fr_learning_path,
@ -75,9 +76,6 @@ from vbv_lernwelt.course.creators.uk_course import (
from vbv_lernwelt.course.creators.uk_training_course import (
create_uk_training_learning_path,
)
from vbv_lernwelt.course.creators.versicherungsvermittlerin import (
create_versicherungsvermittlerin_with_categories,
)
from vbv_lernwelt.course.models import (
Course,
CoursePage,
@ -202,7 +200,7 @@ def create_versicherungsvermittlerin_course(
"it": "Intermediario/a assicurativo/a",
}
# Versicherungsvermittler/in mit neuen Circles
course = create_versicherungsvermittlerin_with_categories(
course = create_course_with_categories(
course_id=course_id,
title=names[language],
)
@ -240,19 +238,20 @@ def create_versicherungsvermittlerin_course(
course_session=cs,
user=User.objects.get(username="student-vv@eiger-versicherungen.ch"),
)
expert1 = CourseSessionUser.objects.create(
CourseSessionUser.objects.create(
course_session=cs,
user=User.objects.get(username="expert-vv.expert1@eiger-versicherungen.ch"),
role=CourseSessionUser.Role.EXPERT,
)
expert2 = CourseSessionUser.objects.create(
CourseSessionUser.objects.create(
course_session=cs,
user=User.objects.get(username="expert-vv.expert2@eiger-versicherungen.ch"),
role=CourseSessionUser.Role.EXPERT,
)
expert3 = CourseSessionUser.objects.create(
CourseSessionUser.objects.create(
course_session=cs,
user=User.objects.get(username="expert-vv.expert3@eiger-versicherungen.ch"),
role=CourseSessionUser.Role.EXPERT,
@ -265,17 +264,6 @@ def create_versicherungsvermittlerin_course(
lemme.participants.add(csu)
experts = [expert1, expert2, expert3]
circles = Circle.objects.filter(
slug__startswith="versicherungsvermittler-in-lp"
)
# for i, circle in enumerate(circles):
# expert = experts[i % len(experts)]
# expert.expert.add(circle)
# create_feedback(circle, cs, 3)
for admin_email in ADMIN_EMAILS:
CourseSessionUser.objects.create(
course_session=cs,
@ -292,7 +280,7 @@ def create_versicherungsvermittlerin_pruefung_course(
"it": "Intermediario/a assicurativo/a AFA Esame",
}
# Versicherungsvermittler/in mit neuen Circles
course = create_versicherungsvermittlerin_with_categories(
course = create_course_with_categories( # noqa
course_id=course_id,
title=names[language],
)
@ -320,7 +308,7 @@ def create_motorfahrzeug_pruefung_course(
"it": "Veicolo a motore Intermediario/a assicurativo/a AFA Esame",
}
# Versicherungsvermittler/in mit neuen Circles
course = create_versicherungsvermittlerin_with_categories(
course = create_course_with_categories( # noqa
course_id=course_id,
title=names[language],
)
@ -345,9 +333,7 @@ def create_course_uk_de(course_id=COURSE_UK, lang="de"):
"fr": "Cours interentreprises",
"it": "Corsi interaziendali",
}
course = create_versicherungsvermittlerin_with_categories(
course_id=course_id, title=names[lang]
)
course = create_course_with_categories(course_id=course_id, title=names[lang])
# assignments create assignments parent page
_assignment_list_page = AssignmentListPageFactory(
@ -398,7 +384,7 @@ def create_course_uk_de_course_sessions():
tuesday_in_two_weeks = (
datetime.now() + relativedelta(weekday=TU(2)) + relativedelta(weeks=2)
)
csac.due_date.start = timezone.make_aware(
csac.due_date.start = timezone.make_aware( # noqa
tuesday_in_two_weeks.replace(hour=8, minute=30, second=0, microsecond=0)
)
csac.due_date.end = timezone.make_aware(
@ -488,25 +474,10 @@ def create_course_uk_de_course_sessions():
user=User.objects.get(username="patrick.muster@eiger-versicherungen.ch"),
)
# TODO: feedback must now contain a `feedback_user`
# create_feedback(
# Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-kickoff"),
# cs,
# 3,
# )
# create_feedback(
# Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-2"),
# cs,
# 14,
# )
# create_feedback(
# Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-basis"), cs, 4
# )
def create_course_uk_fr():
# Überbetriebliche Kurse FR
course = create_versicherungsvermittlerin_with_categories(
course = create_course_with_categories(
course_id=COURSE_UK_FR, title="Cours interentreprises"
)
@ -556,7 +527,7 @@ def create_course_uk_fr():
def create_course_uk_it():
# Überbetriebliche Kurse FR
course = create_versicherungsvermittlerin_with_categories(
course = create_course_with_categories(
course_id=COURSE_UK_IT, title="Corso interaziendale"
)
@ -657,7 +628,7 @@ def create_course_uk_de_completion_data(course_session):
def create_course_training_de():
# Test Lehrgang für üK Trainer
course = create_versicherungsvermittlerin_with_categories(
course = create_course_with_categories(
course_id=COURSE_UK_TRAINING, title="myVBV Training"
)
@ -708,7 +679,7 @@ def create_course_training_de():
cs = CourseSession.objects.get(course_id=COURSE_UK, title="Demo üK 2023 DE")
for user in users:
for user in users: # noqa
csu, _created = CourseSessionUser.objects.get_or_create(
course_session_id=cs.id, user_id=user.id
)
@ -747,7 +718,7 @@ def create_course_session_assignments(course_session, assignment_slug, i=1):
def create_course_training_fr():
# Test Lehrgang für üK Trainer FR
course = create_versicherungsvermittlerin_with_categories(
course = create_course_with_categories(
course_id=COURSE_UK_TRAINING_FR, title="myVBV Training (FR)"
)
@ -814,7 +785,7 @@ def create_course_training_fr():
title="Demo ci 2023 FR",
)
for user in users:
for user in users: # noqa
csu, _created = CourseSessionUser.objects.get_or_create(
course_session_id=cs.id, user_id=user.id
)
@ -834,7 +805,7 @@ def create_course_training_fr():
def create_course_training_it():
# Test Lehrgang für üK Trainer FR
course = create_versicherungsvermittlerin_with_categories(
course = create_course_with_categories(
course_id=COURSE_UK_TRAINING_IT, title="myVBV Training (IT)"
)
@ -901,7 +872,7 @@ def create_course_training_it():
title="Demo ci 2023 IT",
)
for user in users:
for user in users: # noqa
csu, _created = CourseSessionUser.objects.get_or_create(
course_session_id=cs.id, user_id=user.id
)

View File

@ -0,0 +1,111 @@
import django.db.models.deletion
from django.db import migrations, models
TEST_COURSE_ID = -1
UK_COURSE_IDS = [
-3, # uk-de
-6, # uk-training-de
-5, # uk-fr
-7, # uk-training-fr
-8, # uk-it
-9, # uk-training-it
]
VV_COURSE_IDS = [
-4, # vv-de
-10, # vv-fr
-11, # vv-it
-12, # vv-prüfung
]
def forward_migration(apps, schema_editor):
Course = apps.get_model("course", "Course")
CourseConfiguration = apps.get_model("course", "CourseConfiguration")
for course in Course.objects.all():
config, created = CourseConfiguration.objects.get_or_create(
course=course,
)
if created:
# moved -> use existing value from course
config.enable_circle_documents = course.enable_circle_documents
# by default everything is enabled
# -> disable unnecessary features
if course.id in UK_COURSE_IDS:
config.enable_learning_mentor = False
elif course.id in VV_COURSE_IDS:
config.enable_competence_certificates = False
config.save()
def backward_migration(apps, schema_editor):
CourseConfiguration = apps.get_model("course", "CourseConfiguration")
for config in CourseConfiguration.objects.all():
course = config.course
course.enable_circle_documents = config.enable_circle_documents
course.save()
class Migration(migrations.Migration):
dependencies = [
("course", "0006_auto_20231221_1411"),
]
operations = [
migrations.CreateModel(
name="CourseConfiguration",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"enable_circle_documents",
models.BooleanField(
default=True, verbose_name="Dokumente im Circle ein/aus"
),
),
(
"enable_learning_mentor",
models.BooleanField(
default=True, verbose_name="Lernmentor-Funktion ein/aus"
),
),
(
"enable_competence_certificates",
models.BooleanField(
default=True, verbose_name="Kompetenzweise ein/aus"
),
),
(
"course",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="configuration",
to="course.course",
),
),
],
),
migrations.RunPython(forward_migration, backward_migration),
migrations.RemoveField(
model_name="course",
name="circle_contact_type",
),
migrations.RemoveField(
model_name="course",
name="enable_circle_documents",
),
]

View File

@ -28,14 +28,6 @@ class Course(models.Model):
slug = models.SlugField(
_("Slug"), max_length=255, unique=True, blank=True, allow_unicode=True
)
enable_circle_documents = models.BooleanField(
_("Trainer Dokumente in Circles"), default=True
)
circle_contact_type = models.CharField(
max_length=50,
choices=[(cct.value, cct.value) for cct in CircleContactType],
default=CircleContactType.EXPERT.value,
)
def get_course_url(self):
return f"/course/{self.slug}"
@ -329,3 +321,24 @@ class CircleDocument(models.Model):
self.file.upload_finished_at = None
self.file.save()
return super().delete(*args, **kwargs)
class CourseConfiguration(models.Model):
course = models.OneToOneField(
Course, on_delete=models.CASCADE, related_name="configuration"
)
enable_circle_documents = models.BooleanField(
_("Dokumente im Circle ein/aus"), default=True
)
enable_learning_mentor = models.BooleanField(
_("Lernmentor-Funktion ein/aus"), default=True
)
enable_competence_certificates = models.BooleanField(
_("Kompetenzweise ein/aus"), default=True
)
def __str__(self):
return f"Course Configuration for '{self.course.title}'"

View File

@ -7,6 +7,7 @@ from vbv_lernwelt.course.models import (
Course,
CourseCategory,
CourseCompletion,
CourseConfiguration,
CourseSession,
)
from vbv_lernwelt.duedate.models import DueDate
@ -14,12 +15,27 @@ from vbv_lernwelt.duedate.serializers import DueDateSerializer
from vbv_lernwelt.iam.permissions import course_session_permissions
class CourseConfigurationSerializer(serializers.ModelSerializer):
class Meta:
model = CourseConfiguration
fields = [
"id",
"course",
"enable_circle_documents",
"enable_learning_mentor",
"enable_competence_certificates",
]
class CourseSerializer(serializers.ModelSerializer):
id = StringIDField()
configuration = CourseConfigurationSerializer(
read_only=True,
)
class Meta:
model = Course
fields = ["id", "title", "category_name", "slug", "enable_circle_documents"]
fields = ["id", "title", "category_name", "slug", "configuration"]
class CourseCategorySerializer(serializers.ModelSerializer):

View File

@ -0,0 +1,10 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from vbv_lernwelt.course.models import Course, CourseConfiguration
@receiver(post_save, sender=Course)
def create_course_configuration(sender, instance, created, **kwargs):
if created:
CourseConfiguration.objects.create(course=instance)

View File

@ -66,16 +66,16 @@ class DashboardQuery(graphene.ObjectType):
user = info.context.user
if user.is_superuser:
courses = Course.objects.all().values("id", "title", "slug")
return [
{
"id": c["id"],
"course_id": c["id"],
"name": c["title"],
"slug": c["slug"],
"id": c.id,
"course_id": c.id,
"name": c.title,
"slug": c.slug,
"dashboard_type": DashboardType.SIMPLE_DASHBOARD,
"course_configuration": c.configuration,
}
for c in courses
for c in Course.objects.all()
]
(
@ -176,6 +176,7 @@ def get_user_statistics_dashboards(user: User) -> Tuple[List[Dict[str, str]], Se
"name": course.title,
"slug": course.slug,
"dashboard_type": DashboardType.STATISTICS_DASHBOARD,
"course_configuration": course.configuration,
}
)
@ -194,6 +195,7 @@ def get_learning_mentor_dashboards(user: User) -> List[Dict[str, str]]:
"name": course.title,
"slug": course.slug,
"dashboard_type": DashboardType.SIMPLE_DASHBOARD,
"course_configuration": course.configuration,
}
)
@ -241,6 +243,7 @@ def get_user_course_session_dashboards(
"name": course.title,
"slug": course.slug,
"dashboard_type": resolved_dashboard_type,
"course_configuration": course.configuration,
}
)

View File

@ -1,6 +1,7 @@
import graphene
from graphene import Enum
from vbv_lernwelt.course.graphql.types import CourseConfigurationObjectType
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.dashboard.graphql.types.assignment import (
assignments,
@ -59,6 +60,7 @@ class DashboardConfigType(graphene.ObjectType):
name = graphene.String(required=True)
slug = graphene.String(required=True)
dashboard_type = graphene.Field(DashboardType, required=True)
course_configuration = graphene.Field(CourseConfigurationObjectType, required=True)
class ProgressDashboardCompetenceType(graphene.ObjectType):

View File

@ -153,6 +153,11 @@ class DashboardTestCase(GraphQLTestCase):
def test_dashboard_config(self):
# GIVEN
course_1, _ = create_course("Test Course 1")
course_1.configuration.enable_learning_mentor = False
course_1.configuration.enable_competence_certificates = False
course_1.configuration.enable_circle_documents = False
course_1.configuration.save()
course_2, _ = create_course("Test Course 2")
course_3, _ = create_course("Test Course 3")
@ -193,6 +198,11 @@ class DashboardTestCase(GraphQLTestCase):
name
slug
dashboard_type
course_configuration {
enable_circle_documents
enable_learning_mentor
enable_competence_certificates
}
}
}
"""
@ -213,6 +223,13 @@ class DashboardTestCase(GraphQLTestCase):
self.assertEqual(course_1_config["slug"], course_1.slug)
self.assertEqual(course_1_config["dashboard_type"], "PROGRESS_DASHBOARD")
course_1_course_configuration = course_1_config["course_configuration"]
self.assertFalse(course_1_course_configuration["enable_circle_documents"])
self.assertFalse(course_1_course_configuration["enable_learning_mentor"])
self.assertFalse(
course_1_course_configuration["enable_competence_certificates"]
)
course_2_config = find_dashboard_config_by_course_id(
dashboard_config, course_2.id
)
@ -221,6 +238,11 @@ class DashboardTestCase(GraphQLTestCase):
self.assertEqual(course_2_config["slug"], course_2.slug)
self.assertEqual(course_2_config["dashboard_type"], "STATISTICS_DASHBOARD")
course_2_course_configuration = course_2_config["course_configuration"]
self.assertTrue(course_2_course_configuration["enable_circle_documents"])
self.assertTrue(course_2_course_configuration["enable_learning_mentor"])
self.assertTrue(course_2_course_configuration["enable_competence_certificates"])
course_3_config = find_dashboard_config_by_course_id(
dashboard_config, course_3.id
)
@ -229,6 +251,11 @@ class DashboardTestCase(GraphQLTestCase):
self.assertEqual(course_3_config["slug"], course_3.slug)
self.assertEqual(course_3_config["dashboard_type"], "SIMPLE_DASHBOARD")
course_3_course_configuration = course_3_config["course_configuration"]
self.assertTrue(course_3_course_configuration["enable_circle_documents"])
self.assertTrue(course_3_course_configuration["enable_learning_mentor"])
self.assertTrue(course_3_course_configuration["enable_competence_certificates"])
def test_dashboard_config_mentor(self):
# GIVEN
course_1, _ = create_course("Test Course 1")

View File

@ -178,6 +178,16 @@ def has_role_in_course(user: User, course: Course) -> bool:
return False
def can_view_course(user: User, course: Course) -> bool:
if user.is_superuser:
return True
if has_role_in_course(user, course):
return True
return False
def can_view_profile(user: User, profile_user: CourseSessionUser) -> bool:
if user.is_superuser:
return True

View File

@ -10,10 +10,7 @@ from vbv_lernwelt.learning_mentor.entities import (
MentorAssignmentStatusType,
MentorCompletionStatus,
)
from vbv_lernwelt.learnpath.models import (
LearningUnit,
LearningUnitPerformanceFeedbackType,
)
from vbv_lernwelt.learnpath.models import LearningUnit
from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback
logger = structlog.get_logger(__name__)
@ -49,17 +46,16 @@ def get_self_feedback_evaluation(
evaluation_user: User,
course: Course,
) -> Tuple[List[MentorAssignmentStatus], Set[int]]:
if not participants or not course.configuration.enable_learning_mentor:
return [], set()
records: List[MentorAssignmentStatus] = []
circle_ids: Set[int] = set()
if not participants:
return records, circle_ids
# very unfortunate: we can't simply get all SelfEvaluationFeedback objects since then
# we would miss the one where no feedback was requested -> so we get all learning units
# and check if we have to take them into account (course, feedback type, etc.)
for learning_unit in LearningUnit.objects.filter(
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.value,
course_category__course_id=course.id,
):
feedbacks = SelfEvaluationFeedback.objects.filter(

View File

@ -23,7 +23,6 @@ from vbv_lernwelt.course.creators.test_utils import (
)
from vbv_lernwelt.course.models import CourseSessionUser
from vbv_lernwelt.learning_mentor.models import LearningMentor
from vbv_lernwelt.learnpath.models import LearningUnitPerformanceFeedbackType
from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback
@ -132,13 +131,6 @@ class LearningMentorAPITest(APITestCase):
course=self.course,
)
# performance criteria under this learning unit shall be evaluated by the mentor
learning_unit.feedback_user = (
LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.name
)
learning_unit.save()
# 1: we already evaluated
SelfEvaluationFeedback.objects.create(
feedback_requester_user=self.participant_1.user,

View File

@ -18,7 +18,6 @@ from vbv_lernwelt.course.consts import (
COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID,
)
from vbv_lernwelt.course.models import CourseCategory, CoursePage
from vbv_lernwelt.learnpath.models import LearningUnitPerformanceFeedbackType
from vbv_lernwelt.learnpath.tests.learning_path_factories import (
CircleFactory,
LearningContentAssignmentFactory,
@ -175,7 +174,6 @@ def create_circle_basis(lp, title="Basis", course_page=None):
LearningSequenceFactory(title="Arbeitsalltag", parent=circle)
lu = LearningUnitFactory(
title="Mein neuer Job, Arbeitstechnik, Soziale Medien, Datenschutz und Beratungspflichten",
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.name,
parent=circle,
)
@ -432,7 +430,6 @@ def create_circle_fahrzeug(lp, title="Fahrzeug", course_page=None):
title="Transfer",
title_hidden=True,
parent=circle,
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.name,
)
LearningContentPlaceholderFactory(

View File

@ -234,7 +234,7 @@ class LearningUnitObjectType(DjangoObjectType):
class Meta:
model = LearningUnit
interfaces = (CoursePageInterface,)
fields = ["evaluate_url", "title_hidden", "feedback_user"]
fields = ["evaluate_url", "title_hidden"]
def resolve_evaluate_url(self: LearningUnit, info, **kwargs):
return self.get_evaluate_url()

View File

@ -0,0 +1,16 @@
# Generated by Django 3.2.20 on 2024-02-29 10:22
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("learnpath", "0015_set_feedback_user_mentor_for_vv"),
]
operations = [
migrations.RemoveField(
model_name="learningunit",
name="feedback_user",
),
]

View File

@ -1,10 +1,9 @@
import re
from enum import Enum
from typing import Tuple
from django.db import models
from django.utils.text import slugify
from wagtail.admin.panels import FieldPanel, HelpPanel, PageChooserPanel
from wagtail.admin.panels import FieldPanel, PageChooserPanel
from wagtail.fields import RichTextField, StreamField
from wagtail.models import Page
@ -119,13 +118,6 @@ class Circle(CourseBasePage):
return f"{self.title}"
class LearningUnitPerformanceFeedbackType(Enum):
"""Defines how feedback on the performance criteria (n) of a learning unit are given."""
NO_FEEDBACK = "NO_FEEDBACK"
MENTOR_FEEDBACK = "MENTOR_FEEDBACK"
class LearningSequence(CourseBasePage):
serialize_field_names = ["icon"]
@ -178,21 +170,10 @@ class LearningUnit(CourseBasePage):
"course.CourseCategory", on_delete=models.SET_NULL, null=True, blank=True
)
title_hidden = models.BooleanField(default=False)
feedback_user = models.CharField(
max_length=255,
choices=[(tag.name, tag.name) for tag in LearningUnitPerformanceFeedbackType],
default=LearningUnitPerformanceFeedbackType.NO_FEEDBACK.name,
)
content_panels = Page.content_panels + [
FieldPanel("course_category"),
FieldPanel("title_hidden"),
FieldPanel("feedback_user"),
HelpPanel(
content="👆 Feedback zur Selbsteinschätzung: Normalerweise <code>NO_FEEDBACK</code>, "
"ausser bei den Lerninhalten Selbsteinschätzungen, die eine Bewertung haben von einer "
"Lernbegleitung haben sollen (z.B. VV)."
),
]
class Meta:

View File

@ -20,7 +20,6 @@ class LearningUnitSerializer(
"course_category",
"children",
"title_hidden",
"feedback_user",
],
)
):

View File

@ -0,0 +1,39 @@
# Generated by Django 3.2.20 on 2024-02-26 15:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("notify", "0005_alter_notification_notification_trigger"),
]
operations = [
migrations.AlterField(
model_name="notification",
name="notification_trigger",
field=models.CharField(
choices=[
("ATTENDANCE_COURSE_REMINDER", "Attendance Course Reminder"),
("ASSIGNMENT_REMINDER", "Assignment Reminder"),
(
"CASEWORK_EXPERT_EVALUATION_REMINDER",
"Casework Expert Evaluation Reminder",
),
("CASEWORK_SUBMITTED", "Casework Submitted"),
("CASEWORK_EVALUATED", "Casework Evaluated"),
("NEW_FEEDBACK", "New Feedback"),
(
"SELF_EVALUATION_FEEDBACK_REQUESTED",
"Self Evaluation Feedback Requested",
),
(
"SELF_EVALUATION_FEEDBACK_PROVIDED",
"Self Evaluation Feedback Provided",
),
],
default="",
max_length=255,
),
),
]

View File

@ -15,7 +15,6 @@ from vbv_lernwelt.course.creators.test_utils import (
from vbv_lernwelt.course.models import CourseCompletionStatus, CourseSessionUser
from vbv_lernwelt.course.services import mark_course_completion
from vbv_lernwelt.learning_mentor.models import LearningMentor
from vbv_lernwelt.learnpath.models import LearningUnitPerformanceFeedbackType
from vbv_lernwelt.self_evaluation_feedback.models import (
CourseCompletionFeedback,
SelfEvaluationFeedback,
@ -253,7 +252,6 @@ class SelfEvaluationFeedbackAPI(APITestCase):
learning_unit = create_learning_unit(
course=self.course,
circle=self.circle,
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
)
feedback = create_self_evaluation_feedback(
@ -337,7 +335,6 @@ class SelfEvaluationFeedbackAPI(APITestCase):
learning_unit_with_success_feedback = create_learning_unit(
course=self.course,
circle=self.circle,
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
)
performance_criteria_page = create_performance_criteria_page(
@ -382,7 +379,6 @@ class SelfEvaluationFeedbackAPI(APITestCase):
learning_unit = create_learning_unit( # noqa
course=self.course,
circle=self.circle,
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
)
create_performance_criteria_page(
@ -415,7 +411,6 @@ class SelfEvaluationFeedbackAPI(APITestCase):
learning_unit = create_learning_unit( # noqa
course=self.course,
circle=self.circle,
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
)
create_performance_criteria_page(

View File

@ -9,11 +9,7 @@ from vbv_lernwelt.core.models import User
from vbv_lernwelt.core.serializers import UserSerializer
from vbv_lernwelt.course.models import CourseCompletion, CourseSession
from vbv_lernwelt.learning_mentor.models import LearningMentor
from vbv_lernwelt.learnpath.models import (
Circle,
LearningUnit,
LearningUnitPerformanceFeedbackType,
)
from vbv_lernwelt.learnpath.models import Circle, LearningUnit
from vbv_lernwelt.notify.services import NotificationService
from vbv_lernwelt.self_evaluation_feedback.models import (
CourseCompletionFeedback,
@ -181,15 +177,6 @@ def get_self_evaluation_feedbacks_as_requester(request, course_session_id: int):
+ received_feedback_counts_aggregate.unknown_count,
)
# check if there are any learning units with mentor feedback
feedback_assessment_visible = (
LearningUnit.objects.filter(
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.value,
course_category__course=course_session.course,
).count()
> 0
)
return Response(
{
"results": results,
@ -197,7 +184,6 @@ def get_self_evaluation_feedbacks_as_requester(request, course_session_id: int):
Circle.objects.filter(id__in=circle_ids).values("id", "title")
),
"aggregates": {
"feedback_assessment_visible": feedback_assessment_visible,
"feedback_assessment": {
"pass": feedback_assessment_counts_aggregate.pass_count,
"fail": feedback_assessment_counts_aggregate.fail_count,