Merged develop into feature/VBV-594-dashboard-feedback
This commit is contained in:
commit
2168feb74b
|
|
@ -87,10 +87,14 @@ def main(app_name, image_name, environment_file):
|
|||
"IT_DJANGO_SECRET_KEY": env.str(
|
||||
"IT_DJANGO_SECRET_KEY", generate_random_string(63)
|
||||
),
|
||||
"AWS_S3_ACCESS_KEY_ID": env.str("AWS_S3_ACCESS_KEY_ID", ""),
|
||||
"AWS_S3_ACCESS_KEY_ID": env.str(
|
||||
"AWS_S3_ACCESS_KEY_ID", "AKIAZJLREPUVWNBTJ5VY"
|
||||
),
|
||||
"AWS_S3_SECRET_ACCESS_KEY": env.str("AWS_S3_SECRET_ACCESS_KEY", ""),
|
||||
"AWS_S3_REGION_NAME": "eu-central-1",
|
||||
"AWS_STORAGE_BUCKET_NAME": "myvbv-dev.iterativ.ch",
|
||||
"AWS_S3_REGION_NAME": env.str("AWS_S3_REGION_NAME", "eu-central-1"),
|
||||
"AWS_STORAGE_BUCKET_NAME": env.str(
|
||||
"AWS_STORAGE_BUCKET_NAME", "myvbv-dev.iterativ.ch"
|
||||
),
|
||||
"FILE_UPLOAD_STORAGE": "s3",
|
||||
"IT_DJANGO_DEBUG": "false",
|
||||
"IT_SERVE_VUE": "false",
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ const dropdownSelected = computed<DropdownSelectable>({
|
|||
border: !props.borderless,
|
||||
'font-bold': !props.borderless,
|
||||
}"
|
||||
data-cy="dropdown-select"
|
||||
>
|
||||
<span v-if="dropdownSelected.iconName" class="mr-4">
|
||||
<component :is="dropdownSelected.iconName"></component>
|
||||
|
|
@ -75,6 +76,7 @@ const dropdownSelected = computed<DropdownSelectable>({
|
|||
'relative cursor-default select-none py-2 pl-3 pr-9',
|
||||
]"
|
||||
class="flex flex-row items-center"
|
||||
:data-cy="`dropdown-select-option-${item.name}`"
|
||||
>
|
||||
<span v-if="item.iconName" class="mr-4">
|
||||
<component :is="item.iconName"></component>
|
||||
|
|
|
|||
|
|
@ -17,14 +17,14 @@ const documents = {
|
|||
"\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n completion_data\n task_completion_data\n }\n }\n }\n": types.UpsertAssignmentCompletionDocument,
|
||||
"\n fragment CoursePageFields on CoursePageInterface {\n title\n id\n slug\n content_type\n frontend_url\n }\n": types.CoursePageFieldsFragmentDoc,
|
||||
"\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 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 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 }\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 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 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 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 }\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 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 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 $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\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,
|
||||
"\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,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -60,7 +60,7 @@ export function graphql(source: "\n query attendanceCheckQuery($courseSessionId
|
|||
/**
|
||||
* 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 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"): (typeof 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"];
|
||||
export function graphql(source: "\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 }\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"): (typeof 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 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 }\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"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
|
@ -68,11 +68,11 @@ 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 }\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 }\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 enable_circle_documents\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 }\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 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"];
|
||||
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 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 enable_circle_documents\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.
|
||||
*/
|
||||
|
|
@ -88,7 +88,7 @@ export function graphql(source: "\n query courseStatistics($courseId: ID!) {\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 mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\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"): (typeof documents)["\n mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\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"];
|
||||
export function graphql(source: "\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"): (typeof documents)["\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"];
|
||||
|
||||
export function graphql(source: string) {
|
||||
return (documents as any)[source] ?? {};
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -9,7 +9,8 @@ type Query {
|
|||
learning_content_media_library: LearningContentMediaLibraryObjectType
|
||||
learning_content_assignment: LearningContentAssignmentObjectType
|
||||
learning_content_attendance_course: LearningContentAttendanceCourseObjectType
|
||||
learning_content_feedback: LearningContentFeedbackObjectType
|
||||
learning_content_feedback_uk: LearningContentFeedbackUKObjectType
|
||||
learning_content_feedback_vv: LearningContentFeedbackVVObjectType
|
||||
learning_content_learning_module: LearningContentLearningModuleObjectType
|
||||
learning_content_knowledge_assessment: LearningContentKnowledgeAssessmentObjectType
|
||||
learning_content_placeholder: LearningContentPlaceholderObjectType
|
||||
|
|
@ -235,6 +236,7 @@ type CourseObjectType {
|
|||
title: String!
|
||||
category_name: String!
|
||||
slug: String!
|
||||
enable_circle_documents: Boolean!
|
||||
learning_path: LearningPathObjectType!
|
||||
action_competences: [ActionCompetenceObjectType!]!
|
||||
}
|
||||
|
|
@ -455,7 +457,7 @@ type AssignmentObjectType implements CoursePageInterface {
|
|||
assignment_type: AssignmentAssignmentAssignmentTypeChoices!
|
||||
|
||||
"""
|
||||
Muss der Auftrag durch eine Expertin oder einen Experten beurteilt werden?
|
||||
Muss der Auftrag durch eine/n Experten/in oder eine Lernbegleitung beurteilt werden?
|
||||
"""
|
||||
needs_expert_evaluation: Boolean!
|
||||
competence_certificate: CompetenceCertificateObjectType
|
||||
|
|
@ -485,10 +487,14 @@ type AssignmentObjectType implements CoursePageInterface {
|
|||
max_points: Int
|
||||
learning_content: LearningContentInterface
|
||||
completion(course_session_id: ID!, learning_content_page_id: ID, assignment_user_id: UUID): AssignmentCompletionObjectType
|
||||
solution_sample: ContentDocumentObjectType
|
||||
}
|
||||
|
||||
"""An enumeration."""
|
||||
enum AssignmentAssignmentAssignmentTypeChoices {
|
||||
"""PRAXIS_ASSIGNMENT"""
|
||||
PRAXIS_ASSIGNMENT
|
||||
|
||||
"""CASEWORK"""
|
||||
CASEWORK
|
||||
|
||||
|
|
@ -601,8 +607,20 @@ schema (one of the key benefits of GraphQL).
|
|||
"""
|
||||
scalar JSONString
|
||||
|
||||
type ContentDocumentObjectType {
|
||||
id: ID!
|
||||
display_text: String!
|
||||
description: String!
|
||||
link_display_text: String!
|
||||
thumbnail: String!
|
||||
url: String
|
||||
}
|
||||
|
||||
"""An enumeration."""
|
||||
enum LearnpathLearningContentAssignmentAssignmentTypeChoices {
|
||||
"""PRAXIS_ASSIGNMENT"""
|
||||
PRAXIS_ASSIGNMENT
|
||||
|
||||
"""CASEWORK"""
|
||||
CASEWORK
|
||||
|
||||
|
|
@ -701,7 +719,23 @@ type LearningContentMediaLibraryObjectType implements CoursePageInterface & Lear
|
|||
circle: CircleLightObjectType
|
||||
}
|
||||
|
||||
type LearningContentFeedbackObjectType implements CoursePageInterface & LearningContentInterface {
|
||||
type LearningContentFeedbackUKObjectType implements CoursePageInterface & LearningContentInterface {
|
||||
id: ID!
|
||||
title: String!
|
||||
slug: String!
|
||||
content_type: String!
|
||||
live: Boolean!
|
||||
translation_key: String!
|
||||
frontend_url: String!
|
||||
course: CourseObjectType
|
||||
minutes: Int
|
||||
description: String!
|
||||
content_url: String!
|
||||
can_user_self_toggle_course_completion: Boolean!
|
||||
circle: CircleLightObjectType
|
||||
}
|
||||
|
||||
type LearningContentFeedbackVVObjectType implements CoursePageInterface & LearningContentInterface {
|
||||
id: ID!
|
||||
title: String!
|
||||
slug: String!
|
||||
|
|
@ -827,7 +861,7 @@ type CompetenceCertificateListObjectType implements CoursePageInterface {
|
|||
}
|
||||
|
||||
type Mutation {
|
||||
send_feedback(course_session_id: ID!, data: GenericScalar, learning_content_page_id: ID!, submitted: Boolean = false): SendFeedbackMutation
|
||||
send_feedback(course_session_id: ID!, data: GenericScalar, learning_content_page_id: ID!, learning_content_type: String!, submitted: Boolean = false): SendFeedbackMutation
|
||||
update_course_session_attendance_course_users(attendance_user_list: [AttendanceUserInputType]!, id: ID!): AttendanceCourseUserMutation
|
||||
upsert_assignment_completion(assignment_id: ID!, assignment_user_id: UUID, completion_data_string: String, completion_status: AssignmentCompletionStatus, course_session_id: ID!, evaluation_passed: Boolean, evaluation_points: Float, initialize_completion: Boolean, learning_content_page_id: ID): AssignmentCompletionMutation
|
||||
}
|
||||
|
|
@ -869,4 +903,4 @@ enum AssignmentCompletionStatus {
|
|||
SUBMITTED
|
||||
EVALUATION_IN_PROGRESS
|
||||
EVALUATION_SUBMITTED
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export const CompetenceCertificateObjectType = "CompetenceCertificateObjectType"
|
|||
export const CompetencePerformanceStatisticsSummaryType = "CompetencePerformanceStatisticsSummaryType";
|
||||
export const CompetenceRecordStatisticsType = "CompetenceRecordStatisticsType";
|
||||
export const CompetencesStatisticsType = "CompetencesStatisticsType";
|
||||
export const ContentDocumentObjectType = "ContentDocumentObjectType";
|
||||
export const CoreUserLanguageChoices = "CoreUserLanguageChoices";
|
||||
export const CourseObjectType = "CourseObjectType";
|
||||
export const CoursePageInterface = "CoursePageInterface";
|
||||
|
|
@ -54,7 +55,8 @@ export const LearningContentAssignmentObjectType = "LearningContentAssignmentObj
|
|||
export const LearningContentAttendanceCourseObjectType = "LearningContentAttendanceCourseObjectType";
|
||||
export const LearningContentDocumentListObjectType = "LearningContentDocumentListObjectType";
|
||||
export const LearningContentEdoniqTestObjectType = "LearningContentEdoniqTestObjectType";
|
||||
export const LearningContentFeedbackObjectType = "LearningContentFeedbackObjectType";
|
||||
export const LearningContentFeedbackUKObjectType = "LearningContentFeedbackUKObjectType";
|
||||
export const LearningContentFeedbackVVObjectType = "LearningContentFeedbackVVObjectType";
|
||||
export const LearningContentInterface = "LearningContentInterface";
|
||||
export const LearningContentKnowledgeAssessmentObjectType = "LearningContentKnowledgeAssessmentObjectType";
|
||||
export const LearningContentLearningModuleObjectType = "LearningContentLearningModuleObjectType";
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ export const ASSIGNMENT_COMPLETION_QUERY = graphql(`
|
|||
tasks
|
||||
title
|
||||
translation_key
|
||||
solution_sample {
|
||||
id
|
||||
url
|
||||
}
|
||||
competence_certificate {
|
||||
...CoursePageFields
|
||||
}
|
||||
|
|
@ -117,6 +121,7 @@ export const COURSE_SESSION_DETAIL_QUERY = graphql(`
|
|||
id
|
||||
title
|
||||
slug
|
||||
enable_circle_documents
|
||||
}
|
||||
users {
|
||||
id
|
||||
|
|
@ -200,6 +205,7 @@ export const COURSE_QUERY = graphql(`
|
|||
title
|
||||
slug
|
||||
category_name
|
||||
enable_circle_documents
|
||||
action_competences {
|
||||
competence_id
|
||||
...CoursePageFields
|
||||
|
|
|
|||
|
|
@ -87,6 +87,8 @@
|
|||
"performanceObjectivesTitle": "Leistungsziele",
|
||||
"showAssessmentDocument": "Bewertungsinstrument anzeigen",
|
||||
"submissionNotificationDisclaimer": "{{name}} wird deine Ergebnisse bewerten. Du wirst per Benachrichtigung informiert, sobald die Bewertung für dich freigegeben wurde.",
|
||||
"submissionShowSampleSolution": "Musterlösung anzeigen",
|
||||
"submissionShowSampleSolutionText": "Hier findest du eine mögliche Lösung zu deinen Aufgaben. Vorgehen und Prozesse in deiner Organisation können von dieser Lösung abweichen.",
|
||||
"submitAssignment": "Ergebnisse abgeben",
|
||||
"taskDefinition": "Bearbeite die Teilaufgaben und dokumentiere deine Ergebnisse.",
|
||||
"taskDefinitionTitle": "Aufgabenstellung",
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ const appointments = computed(() => {
|
|||
.allDueDates()
|
||||
.filter(
|
||||
(dueDate) =>
|
||||
hasDueDate(dueDate) &&
|
||||
isMatchingCourse(dueDate) &&
|
||||
isMatchingSession(dueDate) &&
|
||||
isMatchingCircle(dueDate)
|
||||
|
|
@ -108,6 +109,10 @@ const isMatchingCircle = (dueDate: DueDate) =>
|
|||
const isMatchingCourse = (dueDate: DueDate) =>
|
||||
courseSessions.value.map((cs) => cs.id).includes(dueDate.course_session_id);
|
||||
|
||||
const hasDueDate = (dueDate: DueDate) => {
|
||||
return dueDate.start || dueDate.end;
|
||||
};
|
||||
|
||||
const numAppointmentsToShow = ref(7);
|
||||
const canLoadMore = computed(() => {
|
||||
return numAppointmentsToShow.value < appointments.value.length;
|
||||
|
|
|
|||
|
|
@ -18,70 +18,24 @@
|
|||
</span>
|
||||
{{ $t("feedback.feedbackPageInfo") }}
|
||||
</p>
|
||||
<ol v-if="feedbackData.amount > 0">
|
||||
<li
|
||||
v-for="(question, i) in orderedQuestions"
|
||||
:key="i"
|
||||
:data-cy="`question-${i + 1}`"
|
||||
>
|
||||
<RatingScale
|
||||
v-if="ratingKeys.includes(question.key)"
|
||||
class="mb-8 bg-white"
|
||||
:ratings="feedbackData.questions[question.key]"
|
||||
:title="`${$t('feedback.questionTitle')} ${i + 1}`"
|
||||
:text="question.question"
|
||||
/>
|
||||
<VerticalBarChart
|
||||
v-else-if="verticalChartKyes.includes(question.key)"
|
||||
class="mb-8 bg-white"
|
||||
:title="`${$t('feedback.questionTitle')} ${i + 1}`"
|
||||
:ratings="feedbackData.questions[question.key]"
|
||||
:text="question.question"
|
||||
:ratio="0.2"
|
||||
/>
|
||||
<OpenFeedback
|
||||
v-else-if="
|
||||
openKeys.includes(question.key) && feedbackData.questions[question.key]
|
||||
"
|
||||
class="mb-8 bg-white"
|
||||
:title="`${$t('feedback.questionTitle')} ${i + 1}`"
|
||||
:text="question.question"
|
||||
:answers="feedbackData.questions[question.key].filter((a: string) => a !== '')"
|
||||
></OpenFeedback>
|
||||
<HorizontalBarChart
|
||||
v-else-if="
|
||||
horizontalChartKeys.includes(question.key) &&
|
||||
feedbackData.questions[question.key]
|
||||
"
|
||||
class="mb-8 bg-white"
|
||||
:title="`${$t('feedback.questionTitle')} ${i}`"
|
||||
:text="question.question"
|
||||
:items="feedbackData.questions[question.key].map((a: string) => `${a}%`)"
|
||||
/>
|
||||
</li>
|
||||
</ol>
|
||||
<FeedbackPageVV v-if="feedbackType === 'vv'" :feedback-data="feedbackData" />
|
||||
<FeedbackPageUK
|
||||
v-else-if="feedbackType === 'uk'"
|
||||
:feedback-data="feedbackData"
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import HorizontalBarChart from "@/components/ui/HorizontalBarChart.vue";
|
||||
import OpenFeedback from "@/components/ui/OpenFeedback.vue";
|
||||
import RatingScale from "@/components/ui/RatingScale.vue";
|
||||
import VerticalBarChart from "@/components/ui/VerticalBarChart.vue";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { itGet } from "@/fetchHelpers";
|
||||
import * as log from "loglevel";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
|
||||
interface FeedbackData {
|
||||
amount: number;
|
||||
questions: {
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
import type { FeedbackData, FeedbackType } from "@/types";
|
||||
import FeedbackPageVV from "@/pages/cockpit/FeedbackPageVV.vue";
|
||||
import FeedbackPageUK from "@/pages/cockpit/FeedbackPageUK.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
|
|
@ -91,72 +45,21 @@ const props = defineProps<{
|
|||
log.debug("FeedbackPage created", props.circleId);
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const orderedQuestions = [
|
||||
{
|
||||
key: "satisfaction",
|
||||
question: t("feedback.satisfactionLabel"),
|
||||
},
|
||||
{
|
||||
key: "goal_attainment",
|
||||
question: t("feedback.goalAttainmentLabel"),
|
||||
},
|
||||
{
|
||||
key: "proficiency",
|
||||
question: t("feedback.proficiencyLabel"),
|
||||
},
|
||||
{
|
||||
key: "preparation_task_clarity",
|
||||
question: t("feedback.preparationTaskClarityLabel"),
|
||||
},
|
||||
{
|
||||
key: "instructor_competence",
|
||||
question: t("feedback.instructorCompetenceLabel"),
|
||||
},
|
||||
{
|
||||
key: "instructor_respect",
|
||||
question: t("feedback.instructorRespectLabel"),
|
||||
},
|
||||
{
|
||||
key: "instructor_open_feedback",
|
||||
question: t("feedback.instructorOpenFeedbackLabel"),
|
||||
},
|
||||
{
|
||||
key: "would_recommend",
|
||||
question: t("feedback.recommendLabel"),
|
||||
},
|
||||
{
|
||||
key: "course_negative_feedback",
|
||||
question: t("feedback.courseNegativeFeedbackLabel"),
|
||||
},
|
||||
{
|
||||
key: "course_positive_feedback",
|
||||
question: t("feedback.coursePositiveFeedbackLabel"),
|
||||
},
|
||||
];
|
||||
|
||||
const ratingKeys = [
|
||||
"satisfaction",
|
||||
"goal_attainment",
|
||||
"instructor_competence",
|
||||
"instructor_respect",
|
||||
];
|
||||
const verticalChartKyes = ["preparation_task_clarity", "would_recommend"];
|
||||
const horizontalChartKeys = ["proficiency"];
|
||||
const openKeys = [
|
||||
"course_negative_feedback",
|
||||
"course_positive_feedback",
|
||||
"instructor_open_feedback",
|
||||
];
|
||||
|
||||
const feedbackData = ref<FeedbackData | undefined>(undefined);
|
||||
const feedbackType = ref<FeedbackType | undefined>(undefined);
|
||||
|
||||
onMounted(async () => {
|
||||
log.debug("FeedbackPage mounted");
|
||||
feedbackData.value = await itGet(
|
||||
`/api/core/feedback/${courseSession.value.id}/${props.circleId}`
|
||||
);
|
||||
log.debug("FeedbackPage feedbackData", feedbackData.value);
|
||||
if (
|
||||
feedbackData.value &&
|
||||
["uk", "vv"].includes(feedbackData.value?.feedbackType ?? "")
|
||||
) {
|
||||
feedbackType.value = feedbackData.value.feedbackType;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<FeedbackResults
|
||||
:ordered-questions="orderedQuestions"
|
||||
:feedback-data="feedbackData"
|
||||
:rating-keys="ratingKeys"
|
||||
:vertical-chart-keys="verticalChartKeys"
|
||||
:horizontal-chart-keys="horizontalChartKeys"
|
||||
:open-keys="openKeys"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FeedbackResults from "@/pages/cockpit/FeedbackResults.vue";
|
||||
import type { FeedbackData } from "@/types";
|
||||
import * as log from "loglevel";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
|
||||
defineProps<{
|
||||
feedbackData: FeedbackData;
|
||||
}>();
|
||||
|
||||
log.debug("FeedbackPageUK created");
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const orderedQuestions = [
|
||||
{
|
||||
key: "satisfaction",
|
||||
question: t("feedback.satisfactionLabel"),
|
||||
},
|
||||
{
|
||||
key: "goal_attainment",
|
||||
question: t("feedback.goalAttainmentLabel"),
|
||||
},
|
||||
{
|
||||
key: "proficiency",
|
||||
question: t("feedback.proficiencyLabel"),
|
||||
},
|
||||
{
|
||||
key: "preparation_task_clarity",
|
||||
question: t("feedback.preparationTaskClarityLabel"),
|
||||
},
|
||||
{
|
||||
key: "instructor_competence",
|
||||
question: t("feedback.instructorCompetenceLabel"),
|
||||
},
|
||||
{
|
||||
key: "instructor_respect",
|
||||
question: t("feedback.instructorRespectLabel"),
|
||||
},
|
||||
{
|
||||
key: "instructor_open_feedback",
|
||||
question: t("feedback.instructorOpenFeedbackLabel"),
|
||||
},
|
||||
{
|
||||
key: "would_recommend",
|
||||
question: t("feedback.recommendLabel"),
|
||||
},
|
||||
{
|
||||
key: "course_negative_feedback",
|
||||
question: t("feedback.courseNegativeFeedbackLabel"),
|
||||
},
|
||||
{
|
||||
key: "course_positive_feedback",
|
||||
question: t("feedback.coursePositiveFeedbackLabel"),
|
||||
},
|
||||
];
|
||||
|
||||
const ratingKeys = [
|
||||
"satisfaction",
|
||||
"goal_attainment",
|
||||
"instructor_competence",
|
||||
"instructor_respect",
|
||||
];
|
||||
const verticalChartKeys = ["preparation_task_clarity", "would_recommend"];
|
||||
const horizontalChartKeys = ["proficiency"];
|
||||
const openKeys = [
|
||||
"course_negative_feedback",
|
||||
"course_positive_feedback",
|
||||
"instructor_open_feedback",
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<FeedbackResults
|
||||
:ordered-questions="orderedQuestions"
|
||||
:feedback-data="feedbackData"
|
||||
:rating-keys="ratingKeys"
|
||||
:vertical-chart-keys="verticalChartKeys"
|
||||
:horizontal-chart-keys="horizontalChartKeys"
|
||||
:open-keys="openKeys"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FeedbackResults from "@/pages/cockpit/FeedbackResults.vue";
|
||||
import type { FeedbackData } from "@/types";
|
||||
import * as log from "loglevel";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
|
||||
defineProps<{
|
||||
feedbackData: FeedbackData;
|
||||
}>();
|
||||
|
||||
log.debug("FeedbackPageVV created");
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const orderedQuestions = [
|
||||
{
|
||||
key: "satisfaction",
|
||||
question: t("feedback.satisfactionLabel"),
|
||||
},
|
||||
{
|
||||
key: "goal_attainment",
|
||||
question: t("feedback.goalAttainmentLabel"),
|
||||
},
|
||||
{
|
||||
key: "proficiency",
|
||||
question: t("feedback.proficiencyLabelVV"),
|
||||
},
|
||||
{
|
||||
key: "preparation_task_clarity",
|
||||
question: t("feedback.praxisAssignmentClarity"),
|
||||
},
|
||||
{
|
||||
key: "would_recommend",
|
||||
question: t("feedback.recommendLabelVV"),
|
||||
},
|
||||
{
|
||||
key: "course_negative_feedback",
|
||||
question: t("feedback.courseNegativeFeedbackLabel"),
|
||||
},
|
||||
{
|
||||
key: "course_positive_feedback",
|
||||
question: t("feedback.coursePositiveFeedbackLabel"),
|
||||
},
|
||||
];
|
||||
|
||||
const ratingKeys = ["satisfaction", "goal_attainment"];
|
||||
const verticalChartKeys = ["preparation_task_clarity", "would_recommend"];
|
||||
const horizontalChartKeys = ["proficiency"];
|
||||
const openKeys = ["course_negative_feedback", "course_positive_feedback"];
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<template>
|
||||
<ol v-if="feedbackData.amount > 0">
|
||||
<li
|
||||
v-for="(question, i) in orderedQuestions"
|
||||
:key="i"
|
||||
:data-cy="`question-${i + 1}`"
|
||||
>
|
||||
<RatingScale
|
||||
v-if="ratingKeys.includes(question.key)"
|
||||
class="mb-8 bg-white"
|
||||
:ratings="feedbackData.questions[question.key]"
|
||||
:title="`${$t('feedback.questionTitle')} ${i + 1}`"
|
||||
:text="question.question"
|
||||
/>
|
||||
<VerticalBarChart
|
||||
v-else-if="verticalChartKeys.includes(question.key)"
|
||||
class="mb-8 bg-white"
|
||||
:title="`${$t('feedback.questionTitle')} ${i + 1}`"
|
||||
:ratings="feedbackData.questions[question.key]"
|
||||
:text="question.question"
|
||||
:ratio="0.2"
|
||||
/>
|
||||
<OpenFeedback
|
||||
v-else-if="
|
||||
openKeys.includes(question.key) && feedbackData.questions[question.key]
|
||||
"
|
||||
class="mb-8 bg-white"
|
||||
:title="`${$t('feedback.questionTitle')} ${i + 1}`"
|
||||
:text="question.question"
|
||||
:answers="feedbackData.questions[question.key].filter((a: string) => a !== '')"
|
||||
></OpenFeedback>
|
||||
<HorizontalBarChart
|
||||
v-else-if="
|
||||
horizontalChartKeys.includes(question.key) &&
|
||||
feedbackData.questions[question.key]
|
||||
"
|
||||
class="mb-8 bg-white"
|
||||
:title="`${$t('feedback.questionTitle')} ${i + 1}`"
|
||||
:text="question.question"
|
||||
:items="feedbackData.questions[question.key].map((a: string) => `${a}%`)"
|
||||
/>
|
||||
</li>
|
||||
</ol>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import HorizontalBarChart from "@/components/ui/HorizontalBarChart.vue";
|
||||
import OpenFeedback from "@/components/ui/OpenFeedback.vue";
|
||||
import RatingScale from "@/components/ui/RatingScale.vue";
|
||||
import VerticalBarChart from "@/components/ui/VerticalBarChart.vue";
|
||||
import type { FeedbackData } from "@/types";
|
||||
import * as log from "loglevel";
|
||||
|
||||
interface Props {
|
||||
orderedQuestions?: {
|
||||
key: string;
|
||||
question: string;
|
||||
}[];
|
||||
feedbackData: FeedbackData;
|
||||
ratingKeys?: string[];
|
||||
verticalChartKeys?: string[];
|
||||
horizontalChartKeys?: string[];
|
||||
openKeys?: string[];
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
orderedQuestions: () => [],
|
||||
ratingKeys: () => [],
|
||||
verticalChartKeys: () => [],
|
||||
horizontalChartKeys: () => [],
|
||||
openKeys: () => [],
|
||||
});
|
||||
|
||||
log.debug("FeedbackBasePage created");
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -67,7 +67,7 @@ const assignment = computed(
|
|||
<div v-else-if="queryResult.error.value">{{ queryResult.error.value }}</div>
|
||||
<div v-else>
|
||||
<header
|
||||
class="relative flex h-12 w-full items-center justify-between border-b border-b-gray-400 bg-white px-4 lg:h-16 lg:px-8"
|
||||
class="relative flex h-12 w-full items-center justify-between border-b border-b-gray-400 bg-white px-4 md:h-16 md:px-8"
|
||||
>
|
||||
<div class="flex items-center text-gray-900">
|
||||
<it-icon-assignment class="h-6 w-6"></it-icon-assignment>
|
||||
|
|
@ -88,7 +88,7 @@ const assignment = computed(
|
|||
</button>
|
||||
</header>
|
||||
<div v-if="assignment && assignmentCompletion && assignmentUser" class="relative">
|
||||
<div class="md:h-content flex flex-col md:flex-row">
|
||||
<div class="flex flex-col md:h-[calc(100vh-64px)] md:flex-row">
|
||||
<div
|
||||
class="bg-white md:h-full md:overflow-y-auto"
|
||||
:class="{ 'md:w-1/2': assignment.needs_expert_evaluation }"
|
||||
|
|
@ -119,7 +119,7 @@ const assignment = computed(
|
|||
</div>
|
||||
<div
|
||||
v-if="assignment.needs_expert_evaluation"
|
||||
class="order-first bg-gray-200 md:order-last md:w-1/2 md:overflow-y-auto"
|
||||
class="md:h-content order-first bg-gray-200 md:order-last md:w-1/2 md:overflow-y-auto"
|
||||
>
|
||||
<EvaluationContainer
|
||||
:assignment-completion="assignmentCompletion"
|
||||
|
|
|
|||
|
|
@ -84,6 +84,22 @@ const taskExpertDataText = computed(() => {
|
|||
return result;
|
||||
});
|
||||
|
||||
const text = computed(() => {
|
||||
if (props.assignment.assignment_type === "CASEWORK") {
|
||||
return {
|
||||
evaluationFinish: "a.Bewertung abschliessen",
|
||||
};
|
||||
} else if (props.assignment.assignment_type === "PRAXIS_ASSIGNMENT") {
|
||||
return {
|
||||
evaluationFinish: "a.Feedback abschliessen",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
evaluationFinish: "UNKNOWN ASSIGNMENT TYPE",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function nextButtonEnabled() {
|
||||
if (inEvaluationTask.value) {
|
||||
return taskExpertDataText.value ?? false;
|
||||
|
|
@ -159,7 +175,7 @@ function finishButtonEnabled() {
|
|||
@click="emit('close')"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
{{ $t("a.Bewertung abschliessen") }}
|
||||
{{ $t(text.evaluationFinish) }}
|
||||
<it-icon-check class="ml-2 h-6 w-6"></it-icon-check>
|
||||
</span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useCurrentCourseSession } from "@/composables";
|
|||
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
|
||||
import type { Assignment, AssignmentCompletion, CourseSessionUser } from "@/types";
|
||||
import { useMutation } from "@urql/vue";
|
||||
import { computed } from "vue";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import * as log from "loglevel";
|
||||
|
||||
|
|
@ -17,6 +18,34 @@ const emit = defineEmits(["startEvaluation"]);
|
|||
|
||||
log.debug("EvaluationIntro setup");
|
||||
|
||||
const text = computed(() => {
|
||||
if (props.assignment.assignment_type === "CASEWORK") {
|
||||
return {
|
||||
evaluationTitle: "a.Bewertung",
|
||||
evaluationInstruction: "assignment.evaluationInstrumentDescriptionText",
|
||||
evaluationStart: "a.Bewertung starten",
|
||||
evaluationContinue: "a.Bewertung fortsetzen",
|
||||
evaluationView: "a.Bewertung ansehen",
|
||||
};
|
||||
} else if (props.assignment.assignment_type === "PRAXIS_ASSIGNMENT") {
|
||||
return {
|
||||
evaluationTitle: "Feedback",
|
||||
evaluationInstruction: "a.assignment.evaluationInstrumentDescriptionTextFeedback",
|
||||
evaluationStart: "a.Feedback geben",
|
||||
evaluationContinue: "a.Feedback fortsetzen",
|
||||
evaluationView: "a.Feedback ansehen",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
evaluationTitle: "UNKNOWN ASSIGNMENT TYPE",
|
||||
evaluationInstruction: "UNKNOWN ASSIGNMENT TYPE",
|
||||
evaluationStart: "UNKNOWN ASSIGNMENT TYPE",
|
||||
evaluationContinue: "UNKNOWN ASSIGNMENT TYPE",
|
||||
evaluationView: "UNKNOWN ASSIGNMENT TYPE",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
const upsertAssignmentCompletionMutation = useMutation(
|
||||
|
|
@ -57,9 +86,9 @@ async function startEvaluation() {
|
|||
}}
|
||||
</div>
|
||||
|
||||
<h3>{{ $t("a.Bewertung") }}</h3>
|
||||
<h3 data-cy="title">{{ $t(text.evaluationTitle) }}</h3>
|
||||
|
||||
<p v-if="props.dueDate" class="my-4">
|
||||
<p v-if="props.dueDate" class="my-4" data-cy="evaluation-duedate">
|
||||
{{
|
||||
$t(
|
||||
"assignment.Du musst die Bewertung bis am x um y Uhr abschliessen und freigeben",
|
||||
|
|
@ -71,16 +100,25 @@ async function startEvaluation() {
|
|||
}}
|
||||
</p>
|
||||
|
||||
<p class="my-4">
|
||||
{{ $t("assignment.evaluationInstrumentDescriptionText") }}
|
||||
<p class="my-4" data-cy="instruction">
|
||||
{{ $t(text.evaluationInstruction) }}
|
||||
</p>
|
||||
|
||||
<p class="my-4">
|
||||
<p v-if="props.assignment.assignment_type === 'CASEWORK'" class="my-4">
|
||||
<a :href="props.assignment.evaluation_document_url" class="link" target="_blank">
|
||||
{{ $t("a.Beurteilungsinstrument anzeigen") }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p
|
||||
v-if="props.assignment.solution_sample && props.assignment.solution_sample.url"
|
||||
class="my-4"
|
||||
>
|
||||
<a :href="props.assignment.solution_sample.url" class="link" target="_blank">
|
||||
{{ $t("assignment.submissionShowSampleSolution") }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<button
|
||||
class="btn-primary text-large"
|
||||
|
|
@ -92,16 +130,16 @@ async function startEvaluation() {
|
|||
props.assignmentCompletion.completion_status === 'EVALUATION_IN_PROGRESS'
|
||||
"
|
||||
>
|
||||
{{ $t("a.Bewertung fortsetzen") }}
|
||||
{{ $t(text.evaluationContinue) }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="
|
||||
props.assignmentCompletion.completion_status === 'EVALUATION_SUBMITTED'
|
||||
"
|
||||
>
|
||||
{{ $t("a.Bewertung ansehen") }}
|
||||
{{ $t(text.evaluationView) }}
|
||||
</span>
|
||||
<span v-else>{{ $t("a.Bewertung starten") }}</span>
|
||||
<span v-else>{{ $t(text.evaluationStart) }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,37 @@ const upsertAssignmentCompletionMutation = useMutation(
|
|||
UPSERT_ASSIGNMENT_COMPLETION_MUTATION
|
||||
);
|
||||
|
||||
const text = computed(() => {
|
||||
if (props.assignment.assignment_type === "CASEWORK") {
|
||||
return {
|
||||
evaluationCriteria: "a.Beurteilungskriterium",
|
||||
evaluationReason: "assignment.evaluationReason",
|
||||
evaluationSubmit: "a.Bewertung freigeben",
|
||||
evaluationSubmission: "a.Bewertung Freigabe",
|
||||
evaluationFromUser: "a.Bewertung von x y",
|
||||
evaluationSuccess: "a.Deine Bewertung für x y wurde freigegeben.",
|
||||
};
|
||||
} else if (props.assignment.assignment_type === "PRAXIS_ASSIGNMENT") {
|
||||
return {
|
||||
evaluationCriteria: "Feedback",
|
||||
evaluationReason: "assignment.evaluationFeedback",
|
||||
evaluationSubmit: "a.Feedback freigeben",
|
||||
evaluationSubmission: "a.Feedback Freigabe",
|
||||
evaluationFromUser: "a.Feedback von x y",
|
||||
evaluationSuccess: "a.Dein Feedback für x y wurde freigegeben.",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
evaluationCriteria: "UNKNOWN ASSIGNMENT TYPE",
|
||||
evaluationReason: "UNKNOWN ASSIGNMENT TYPE",
|
||||
evaluationSubmit: "UNKNOWN ASSIGNMENT TYPE",
|
||||
evaluationSubmission: "UNKNOWN ASSIGNMENT TYPE",
|
||||
evaluationFromUser: "UNKNOWN ASSIGNMENT TYPE",
|
||||
evaluationSuccess: "UNKNOWN ASSIGNMENT TYPE",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
async function submitEvaluation() {
|
||||
upsertAssignmentCompletionMutation.executeMutation({
|
||||
assignmentId: props.assignment.id,
|
||||
|
|
@ -58,7 +89,9 @@ async function submitEvaluation() {
|
|||
}
|
||||
|
||||
function subTaskByPoints(task: AssignmentEvaluationTask, points = 0) {
|
||||
return task.value.sub_tasks.find((subTask) => subTask.value.points === points);
|
||||
return task.value.sub_tasks !== undefined
|
||||
? task.value.sub_tasks.find((subTask) => subTask.value.points === points)
|
||||
: null;
|
||||
}
|
||||
|
||||
function evaluationForTask(task: AssignmentEvaluationTask) {
|
||||
|
|
@ -93,12 +126,19 @@ const evaluationUser = computed(() => {
|
|||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div>
|
||||
<h3 v-if="evaluationUser && props.showEvaluationUser" class="mb-6">
|
||||
Bewertung von {{ evaluationUser.first_name }} {{ evaluationUser.last_name }}
|
||||
{{
|
||||
$t(text.evaluationFromUser, {
|
||||
x: evaluationUser?.first_name,
|
||||
y: evaluationUser?.last_name,
|
||||
})
|
||||
}}
|
||||
</h3>
|
||||
<h3 v-else class="mb-6" data-cy="sub-title">{{ $t("a.Bewertung Freigabe") }}</h3>
|
||||
|
||||
<h3 v-else class="mb-6" data-cy="sub-title">{{ $t(text.evaluationSubmission) }}</h3>
|
||||
<section class="mb-6 border p-6" data-cy="result-section">
|
||||
<section class="flex items-center">
|
||||
<section
|
||||
v-if="props.assignment.assignment_type === 'CASEWORK'"
|
||||
class="flex items-center"
|
||||
>
|
||||
<div class="heading-1 py-4" data-cy="user-points">
|
||||
{{ userPoints }}
|
||||
</div>
|
||||
|
|
@ -116,6 +156,7 @@ const evaluationUser = computed(() => {
|
|||
|
||||
<div
|
||||
v-if="
|
||||
props.assignment.assignment_type === 'CASEWORK' &&
|
||||
props.assignmentCompletion.completion_status === 'EVALUATION_SUBMITTED' &&
|
||||
!props.assignmentCompletion.evaluation_passed
|
||||
"
|
||||
|
|
@ -125,20 +166,29 @@ const evaluationUser = computed(() => {
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<p class="my-4">
|
||||
{{ $t("assignment.evaluationInstrumentDescriptionText") }}
|
||||
</p>
|
||||
<div v-if="props.assignment.assignment_type === 'CASEWORK'">
|
||||
<p class="my-4">
|
||||
{{ $t("assignment.evaluationInstrumentDescriptionText") }}
|
||||
</p>
|
||||
<p class="my-4">
|
||||
<a
|
||||
:href="props.assignment.evaluation_document_url"
|
||||
class="link"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t("a.Beurteilungsinstrument anzeigen") }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p class="my-4">
|
||||
<a
|
||||
:href="props.assignment.evaluation_document_url"
|
||||
class="link"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t("a.Beurteilungsinstrument anzeigen") }}
|
||||
</a>
|
||||
<p
|
||||
v-if="
|
||||
props.assignment.assignment_type === 'PRAXIS_ASSIGNMENT' &&
|
||||
props.assignmentCompletion.completion_status !== 'EVALUATION_SUBMITTED'
|
||||
"
|
||||
>
|
||||
{{ $t("a.assignment.evaluationFeedbackDescriptionText") }}
|
||||
</p>
|
||||
|
||||
<div
|
||||
v-if="props.assignmentCompletion.completion_status === 'EVALUATION_SUBMITTED'"
|
||||
>
|
||||
|
|
@ -156,13 +206,18 @@ const evaluationUser = computed(() => {
|
|||
data-cy="submit-evaluation"
|
||||
@click="submitEvaluation()"
|
||||
>
|
||||
{{ $t("a.Bewertung freigeben") }}
|
||||
{{ $t(text.evaluationSubmit) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="state.showSuccessInfo" class="mt-4">
|
||||
<ItSuccessAlert
|
||||
:text="`Deine Bewertung für ${props.assignmentUser.first_name} ${props.assignmentUser.last_name} wurde freigegeben.`"
|
||||
:text="
|
||||
$t(text.evaluationSuccess, {
|
||||
x: props.assignmentUser.first_name,
|
||||
y: props.assignmentUser.last_name,
|
||||
})
|
||||
"
|
||||
></ItSuccessAlert>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -172,7 +227,7 @@ const evaluationUser = computed(() => {
|
|||
<article class="border-t py-4">
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="mb-4 text-gray-900">
|
||||
{{ $t("a.Beurteilungskriterium") }} {{ index + 1 }}:
|
||||
{{ $t(text.evaluationCriteria) }} {{ index + 1 }}:
|
||||
{{ task.value.title }}
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -209,13 +264,16 @@ const evaluationUser = computed(() => {
|
|||
open-links-in-new-tab
|
||||
/>
|
||||
|
||||
<div class="text-sm text-gray-800">
|
||||
<div
|
||||
v-if="assignment.assignment_type === 'CASEWORK'"
|
||||
class="text-sm text-gray-800"
|
||||
>
|
||||
{{ evaluationForTask(task).points }} Punkte
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div>
|
||||
<span class="font-bold">{{ $t("a.Begründung") }}:</span>
|
||||
<span class="font-bold">{{ $t(text.evaluationReason) }}:</span>
|
||||
{{ evaluationForTask(task).text }}
|
||||
</div>
|
||||
</article>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,28 @@ const expertData = computed(() => {
|
|||
return data;
|
||||
});
|
||||
|
||||
const text = computed(() => {
|
||||
if (props.assignment.assignment_type === "CASEWORK") {
|
||||
return {
|
||||
evaluationCriteria: "a.Beurteilungskriterium",
|
||||
evaluationReason: "assignment.evaluationReason",
|
||||
evaluationReasonPlaceholder: "assignment.justificationRequiredText",
|
||||
};
|
||||
} else if (props.assignment.assignment_type === "PRAXIS_ASSIGNMENT") {
|
||||
return {
|
||||
evaluationCriteria: "Feedback",
|
||||
evaluationReason: "assignment.evaluationFeedback",
|
||||
evaluationReasonPlaceholder: "assignment.feedbackRequiredText",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
evaluationCriteria: "UNKNOWN ASSIGNMENT TYPE",
|
||||
evaluationReason: "UNKNOWN ASSIGNMENT TYPE",
|
||||
evaluationReasonPlaceholder: "UNKNOWN ASSIGNMENT TYPE",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function changePoints(points: number) {
|
||||
log.debug("changePoints", points);
|
||||
evaluateAssignmentCompletion({
|
||||
|
|
@ -88,7 +110,7 @@ const evaluateAssignmentCompletionDebounced = useDebounceFn(
|
|||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div data-cy="evaluation-task">
|
||||
<div class="text-bold mb-4 text-sm">
|
||||
{{ $t("a.Beurteilungskriterium") }} {{ taskIndex + 1 }} /
|
||||
{{ $t(text.evaluationCriteria) }} {{ taskIndex + 1 }} /
|
||||
{{ props.assignment.evaluation_tasks.length }}
|
||||
{{ task.value.title }}
|
||||
</div>
|
||||
|
|
@ -127,9 +149,9 @@ const evaluateAssignmentCompletionDebounced = useDebounceFn(
|
|||
<ItTextarea
|
||||
class="mt-8"
|
||||
:model-value="expertData.text ?? ''"
|
||||
:label="$t('a.Begründung')"
|
||||
:label="$t(text.evaluationReason)"
|
||||
:disabled="!props.allowEdit"
|
||||
:placeholder="$t('assignment.justificationRequiredText')"
|
||||
:placeholder="$t(text.evaluationReasonPlaceholder)"
|
||||
data-cy="reason-text"
|
||||
@update:model-value="onUpdateText($event)"
|
||||
></ItTextarea>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@ const assignmentDetail = computed(() => {
|
|||
return courseSessionDetailResult.findAssignment(props.learningContent.id);
|
||||
});
|
||||
|
||||
const isPraxisAssignment = computed(() => {
|
||||
return (
|
||||
props.learningContent.content_assignment.assignment_type === "PRAXIS_ASSIGNMENT"
|
||||
);
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const { gradedUsers, assignmentSubmittedUsers } =
|
||||
await loadAssignmentCompletionStatusData(
|
||||
|
|
@ -130,7 +136,13 @@ function findUserPointsHtml(userId: string) {
|
|||
>
|
||||
<it-icon-check class="h-4/5 w-4/5"></it-icon-check>
|
||||
</div>
|
||||
<div class="ml-2">{{ $t("a.Bewertung freigegeben") }}</div>
|
||||
<div class="ml-2">
|
||||
{{
|
||||
isPraxisAssignment
|
||||
? $t("a.Feedback freigegeben")
|
||||
: $t("a.Bewertung freigegeben")
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="state.assignmentSubmittedUsers.includes(csu)"
|
||||
|
|
@ -146,7 +158,7 @@ function findUserPointsHtml(userId: string) {
|
|||
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div
|
||||
v-if="findGradedUser(csu.user_id)"
|
||||
v-if="findGradedUser(csu.user_id) && !isPraxisAssignment"
|
||||
v-html="findUserPointsHtml(csu.user_id)"
|
||||
></div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ const totalCount = (status: StatusCount) => {
|
|||
const showEvaluationStatus = computed(() => {
|
||||
return (
|
||||
props.learningContent.content_assignment.assignment_type === "CASEWORK" ||
|
||||
props.learningContent.content_assignment.assignment_type === "PRAXIS_ASSIGNMENT" ||
|
||||
props.learningContent.content_assignment.assignment_type === "EDONIQ_TEST"
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -55,7 +55,11 @@ const courseSessionDetailResult = useCourseSessionDetailQuery();
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||
<div
|
||||
v-if="courseSession.course.enable_circle_documents"
|
||||
class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0"
|
||||
data-cy="circle-documents"
|
||||
>
|
||||
<div>
|
||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||
{{ $t("a.Unterlagen für Teilnehmenden") }}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ const submittables = computed(() => {
|
|||
const learningContents = circleFlatLearningContents(circle).filter(
|
||||
(lc) =>
|
||||
lc.content_type === "learnpath.LearningContentAssignment" ||
|
||||
lc.content_type === "learnpath.LearningContentFeedback" ||
|
||||
lc.content_type === "learnpath.LearningContentFeedbackUK" ||
|
||||
lc.content_type === "learnpath.LearningContentFeedbackVV" ||
|
||||
lc.content_type === "learnpath.LearningContentEdoniqTest"
|
||||
);
|
||||
|
||||
|
|
@ -72,7 +73,10 @@ const submittables = computed(() => {
|
|||
});
|
||||
|
||||
const isFeedback = (lc: LearningContent) => {
|
||||
return lc.content_type === "learnpath.LearningContentFeedback";
|
||||
return (
|
||||
lc.content_type === "learnpath.LearningContentFeedbackUK" ||
|
||||
lc.content_type === "learnpath.LearningContentFeedbackVV"
|
||||
);
|
||||
};
|
||||
|
||||
const isAssignment = (lc: LearningContent) => {
|
||||
|
|
@ -101,7 +105,11 @@ const getLearningContentType = (lc: LearningContent) => {
|
|||
const getShowDetailsText = (lc: LearningContent) => {
|
||||
if (isAssignment(lc)) {
|
||||
const assignmentType = (lc as LearningContentAssignment).assignment_type;
|
||||
if (assignmentType === "CASEWORK" || assignmentType === "REFLECTION") {
|
||||
if (
|
||||
assignmentType === "PRAXIS_ASSIGNMENT" ||
|
||||
assignmentType === "CASEWORK" ||
|
||||
assignmentType === "REFLECTION"
|
||||
) {
|
||||
return t("a.Ergebnisse anschauen");
|
||||
} else if (
|
||||
assignmentType === "PREP_ASSIGNMENT" ||
|
||||
|
|
@ -132,6 +140,7 @@ const getIconName = (lc: LearningContent) => {
|
|||
if (
|
||||
assignmentType === "PREP_ASSIGNMENT" ||
|
||||
assignmentType === "CASEWORK" ||
|
||||
assignmentType === "PRAXIS_ASSIGNMENT" ||
|
||||
assignmentType === "CONDITION_ACCEPTANCE"
|
||||
) {
|
||||
return "it-icon-assignment-large";
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import CircleDiagram from "./CircleDiagram.vue";
|
|||
import CircleOverview from "./CircleOverview.vue";
|
||||
import DocumentSection from "./DocumentSection.vue";
|
||||
import {
|
||||
useCourseSessionDetailQuery,
|
||||
useCourseDataWithCompletion,
|
||||
useCourseSessionDetailQuery,
|
||||
} from "@/composables";
|
||||
import { stringifyParse } from "@/utils/utils";
|
||||
import { useCircleStore } from "@/stores/circle";
|
||||
|
|
@ -63,6 +63,10 @@ const showDuration = computed(() => {
|
|||
return false;
|
||||
});
|
||||
|
||||
const showDocumentSection = computed(() => {
|
||||
return lpQueryResult.course.value?.enable_circle_documents && !props.readonly;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => circle.value,
|
||||
() => {
|
||||
|
|
@ -189,7 +193,7 @@ watch(
|
|||
{{ $t("circlePage.learnMore") }}
|
||||
</button>
|
||||
</div>
|
||||
<DocumentSection v-if="!readonly" :circle="circle" />
|
||||
<DocumentSection v-if="showDocumentSection" :circle="circle" />
|
||||
<div v-if="!props.readonly" class="expert mt-8 border p-6">
|
||||
<h3 class="text-blue-dark">{{ $t("circlePage.gotQuestions") }}</h3>
|
||||
<div class="mt-4 leading-relaxed">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="mt-8 block border p-6">
|
||||
<div class="mt-8 block border p-6" data-cy="circle-document-section">
|
||||
<h3 class="text-blue-dark">
|
||||
{{ $t("circlePage.documents.title") }}
|
||||
</h3>
|
||||
|
|
|
|||
|
|
@ -14,12 +14,13 @@ import type { Component } from "vue";
|
|||
import { computed, onUnmounted } from "vue";
|
||||
import AssignmentBlock from "./blocks/AssignmentBlock.vue";
|
||||
import AttendanceCourseBlock from "./blocks/AttendanceCourseBlock.vue";
|
||||
import FeedbackBlock from "./feedback/FeedbackBlock.vue";
|
||||
import IframeBlock from "./blocks/IframeBlock.vue";
|
||||
import MediaLibraryBlock from "./blocks/MediaLibraryBlock.vue";
|
||||
import PlaceholderBlock from "./blocks/PlaceholderBlock.vue";
|
||||
import RichTextBlock from "./blocks/RichTextBlock.vue";
|
||||
import VideoBlock from "./blocks/VideoBlock.vue";
|
||||
import FeedbackBlockUK from "./feedback/FeedbackBlockUK.vue";
|
||||
import FeedbackBlockVV from "./feedback/FeedbackBlockVV.vue";
|
||||
import { getPreviousRoute } from "@/router/history";
|
||||
import { stringifyParse } from "@/utils/utils";
|
||||
import { useCourseDataWithCompletion } from "@/composables";
|
||||
|
|
@ -42,7 +43,8 @@ const COMPONENTS: Record<LearningContentContentType, Component> = {
|
|||
"learnpath.LearningContentAssignment": AssignmentBlock,
|
||||
"learnpath.LearningContentAttendanceCourse": AttendanceCourseBlock,
|
||||
"learnpath.LearningContentDocumentList": DocumentListBlock,
|
||||
"learnpath.LearningContentFeedback": FeedbackBlock,
|
||||
"learnpath.LearningContentFeedbackUK": FeedbackBlockUK,
|
||||
"learnpath.LearningContentFeedbackVV": FeedbackBlockVV,
|
||||
"learnpath.LearningContentLearningModule": IframeBlock,
|
||||
"learnpath.LearningContentKnowledgeAssessment": IframeBlock,
|
||||
"learnpath.LearningContentMediaLibrary": MediaLibraryBlock,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import DateEmbedding from "@/components/dueDates/DateEmbedding.vue";
|
||||
import RichText from "@/components/ui/RichText.vue";
|
||||
import type { Assignment } from "@/types";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
import log from "loglevel";
|
||||
import dayjs from "dayjs";
|
||||
import RichText from "@/components/ui/RichText.vue";
|
||||
import log from "loglevel";
|
||||
|
||||
interface Props {
|
||||
assignment: Assignment;
|
||||
|
|
@ -40,15 +40,13 @@ const step = useRouteQuery("step");
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="mb-4 mt-8">{{ $t("assignment.dueDateSubmission") }}</h3>
|
||||
|
||||
<p v-if="submissionDeadlineStart" class="text-large">
|
||||
<div v-if="submissionDeadlineStart" class="text-large">
|
||||
<h3 class="mb-4 mt-8">{{ $t("assignment.dueDateSubmission") }}</h3>
|
||||
{{ $t("assignment.dueDateIntroduction") }}
|
||||
<DateEmbedding :single-date="dayjs(submissionDeadlineStart)"></DateEmbedding>
|
||||
</p>
|
||||
<p v-else class="text-large">
|
||||
{{ $t("assignment.dueDateNotSet") }}
|
||||
</p>
|
||||
<p>
|
||||
<DateEmbedding :single-date="dayjs(submissionDeadlineStart)"></DateEmbedding>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="props.assignment.effort_required">
|
||||
<h3 class="mb-4 mt-8">{{ $t("assignment.effortTitle") }}</h3>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { computed, reactive } from "vue";
|
|||
import { useTranslation } from "i18next-vue";
|
||||
import eventBus from "@/utils/eventBus";
|
||||
import dayjs from "dayjs";
|
||||
import type { AssignmentAssignmentAssignmentTypeChoices } from "@/gql/graphql";
|
||||
|
||||
const props = defineProps<{
|
||||
assignment: Assignment;
|
||||
|
|
@ -76,14 +77,24 @@ const completionTaskData = computed(() => {
|
|||
return props.assignmentCompletion?.task_completion_data ?? {};
|
||||
});
|
||||
|
||||
const canSubmit = computed(() => {
|
||||
const cannotSubmit = computed(() => {
|
||||
return (
|
||||
!state.confirmInput ||
|
||||
(!state.confirmInput && !isPraxisAssignment.value) ||
|
||||
(props.assignment.assignment_type === "CASEWORK" && !state.confirmPerson)
|
||||
);
|
||||
});
|
||||
|
||||
const isCasework = computed(() => props.assignment.assignment_type === "CASEWORK");
|
||||
function checkAssignmentType(
|
||||
assignmentType: AssignmentAssignmentAssignmentTypeChoices[]
|
||||
) {
|
||||
return assignmentType.includes(props.assignment.assignment_type);
|
||||
}
|
||||
|
||||
const isCasework = computed(() => checkAssignmentType(["CASEWORK"]));
|
||||
const mayBeEvaluated = computed(() =>
|
||||
checkAssignmentType(["CASEWORK", "PRAXIS_ASSIGNMENT"])
|
||||
);
|
||||
const isPraxisAssignment = computed(() => checkAssignmentType(["PRAXIS_ASSIGNMENT"]));
|
||||
|
||||
const upsertAssignmentCompletionMutation = useMutation(
|
||||
UPSERT_ASSIGNMENT_COMPLETION_MUTATION
|
||||
|
|
@ -93,6 +104,14 @@ const onEditTask = (task: AssignmentTask) => {
|
|||
emit("editTask", task);
|
||||
};
|
||||
|
||||
const openSolutionSample = () => {
|
||||
const url = props.assignment.solution_sample?.url ?? "";
|
||||
|
||||
if (props.assignment.solution_sample) {
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
await upsertAssignmentCompletionMutation.executeMutation({
|
||||
|
|
@ -108,20 +127,24 @@ const onSubmit = async () => {
|
|||
bustItGetCache(
|
||||
`/api/course/completion/${courseSession.value.id}/${useUserStore().id}/`
|
||||
);
|
||||
eventBus.emit("finishedLearningContent", true);
|
||||
// if solution sample is available, do not close the assigment automatically
|
||||
if (!props.assignment.solution_sample) {
|
||||
eventBus.emit("finishedLearningContent", true);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error("Could not submit assignment", error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="w-full border border-gray-400 p-8">
|
||||
<div class="w-full border border-gray-400 p-8" data-cy="confirm-container">
|
||||
<h3 class="heading-3 border-b border-gray-400 pb-6">
|
||||
{{ $t("assignment.submitAssignment") }}
|
||||
</h3>
|
||||
|
||||
<div v-if="completionStatus === 'IN_PROGRESS'">
|
||||
<ItCheckbox
|
||||
v-if="!isPraxisAssignment"
|
||||
class="w-full border-b border-gray-400 py-10 sm:py-6"
|
||||
:checkbox-item="{
|
||||
label: $t('assignment.confirmSubmitResults'),
|
||||
|
|
@ -131,11 +154,14 @@ const onSubmit = async () => {
|
|||
data-cy="confirm-submit-results"
|
||||
@toggle="state.confirmInput = !state.confirmInput"
|
||||
></ItCheckbox>
|
||||
<div v-if="isCasework" class="w-full border-b border-gray-400">
|
||||
<div v-if="mayBeEvaluated" class="w-full border-b border-gray-400">
|
||||
<ItCheckbox
|
||||
v-if="mayBeEvaluated"
|
||||
class="py-6"
|
||||
:checkbox-item="{
|
||||
label: $t('assignment.confirmSubmitPerson'),
|
||||
label: isPraxisAssignment
|
||||
? $t('a.confirmSubmitPersonPraxisAssignment')
|
||||
: $t('assignment.confirmSubmitPerson'),
|
||||
value: 'value',
|
||||
checked: state.confirmPerson,
|
||||
}"
|
||||
|
|
@ -160,7 +186,7 @@ const onSubmit = async () => {
|
|||
{{ $t("assignment.showAssessmentDocument") }}
|
||||
</a>
|
||||
</div>
|
||||
<p v-if="isCasework && props.submissionDeadlineStart" class="pt-6">
|
||||
<p v-if="mayBeEvaluated && props.submissionDeadlineStart" class="pt-6">
|
||||
{{ $t("assignment.dueDateSubmission") }}
|
||||
<DateEmbedding
|
||||
:single-date="dayjs(props.submissionDeadlineStart)"
|
||||
|
|
@ -170,7 +196,7 @@ const onSubmit = async () => {
|
|||
class="mt-6"
|
||||
variant="blue"
|
||||
size="large"
|
||||
:disabled="canSubmit"
|
||||
:disabled="cannotSubmit"
|
||||
data-cy="submit-assignment"
|
||||
@click="onSubmit"
|
||||
>
|
||||
|
|
@ -187,6 +213,26 @@ const onSubmit = async () => {
|
|||
$t("assignment.submissionNotificationDisclaimer", { name: circleExpertName })
|
||||
}}
|
||||
</p>
|
||||
<div
|
||||
v-if="props.assignment.solution_sample"
|
||||
class="pt-2"
|
||||
data-cy="show-sample-solution"
|
||||
>
|
||||
<p>
|
||||
{{ $t("assignment.submissionShowSampleSolutionText") }}
|
||||
</p>
|
||||
|
||||
<ItButton
|
||||
class="mt-6"
|
||||
variant="primary"
|
||||
size="normal"
|
||||
:disabled="false"
|
||||
data-cy="show-sample-solution-button"
|
||||
@click="openSolutionSample"
|
||||
>
|
||||
<p>{{ $t("assignment.submissionShowSampleSolution") }}</p>
|
||||
</ItButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AssignmentSubmissionResponses
|
||||
|
|
|
|||
|
|
@ -67,17 +67,17 @@ function handleDelete() {
|
|||
id="upload"
|
||||
type="file"
|
||||
class="absolute opacity-0"
|
||||
accept=".pdf,.jpg,.jpeg,.png,.doc,.docx,.mov,.ppt,.pptx"
|
||||
accept=".pdf,.jpg,.jpeg,.png,.doc,.docx,.mov,.ppt,.pptx,.mp4"
|
||||
@change="fileSelected"
|
||||
/>
|
||||
<it-icon-document class="mr-1.5 h-7 w-7 text-blue-800" />
|
||||
{{ $t("a.Datei auswählen") }}
|
||||
</div>
|
||||
<p class="text-sm text-gray-900">
|
||||
{{ $t("a.Mögliche Formate") }}: .JPG, .PNG, .PDF, .DOC, .MOV, .PPT
|
||||
{{ $t("a.Mögliche Formate") }}: .JPG, .PNG, .PDF, .DOC, .MOV, .PPT, .MP4
|
||||
</p>
|
||||
<p class="mb-8 text-sm text-gray-900">
|
||||
{{ $t("a.Maximale Dateigrösse") }}: 20 MB
|
||||
{{ $t("a.Maximale Dateigrösse") }}: 50 MB
|
||||
</p>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,35 +1,32 @@
|
|||
<script setup lang="ts">
|
||||
import ItRadioGroup from "@/components/ui/ItRadioGroup.vue";
|
||||
import ItTextarea from "@/components/ui/ItTextarea.vue";
|
||||
import { graphql } from "@/gql";
|
||||
import FeedbackCompletition from "@/pages/learningPath/learningContentPage/feedback/FeedbackCompletition.vue";
|
||||
import {
|
||||
PERCENTAGES,
|
||||
RATINGS,
|
||||
YES_NO,
|
||||
} from "@/pages/learningPath/learningContentPage/feedback/feedback.constants";
|
||||
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
|
||||
import type { LearningContentFeedback } from "@/types";
|
||||
import type { LearningContentFeedbackUK, LearningContentFeedbackVV } from "@/types";
|
||||
import { useMutation } from "@urql/vue";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
import log from "loglevel";
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||
import { bustItGetCache } from "@/fetchHelpers";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
|
||||
const props = defineProps<{
|
||||
content: LearningContentFeedback;
|
||||
content: LearningContentFeedbackVV | LearningContentFeedbackUK;
|
||||
stepLabels: string[];
|
||||
questionData: any[];
|
||||
introduction: string;
|
||||
title: string;
|
||||
completionTitle: string;
|
||||
completionDescription: string;
|
||||
showAvatar: boolean;
|
||||
}>();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const stepNo = useRouteQuery("step", "0", { transform: Number, mode: "push" });
|
||||
|
||||
const title = computed(
|
||||
() => `«${props.content.circle?.title}»: ${t("feedback.areYouSatisfied")}`
|
||||
);
|
||||
const title = computed(() => `«${props.content.circle?.title}»: ${props.title}`);
|
||||
|
||||
const circleExperts = computed(() => {
|
||||
if (props.content?.circle?.slug) {
|
||||
|
|
@ -38,34 +35,29 @@ const circleExperts = computed(() => {
|
|||
return [];
|
||||
});
|
||||
|
||||
const stepLabels = [
|
||||
t("general.introduction"),
|
||||
t("feedback.satisfactionLabel"),
|
||||
t("feedback.goalAttainmentLabel"),
|
||||
t("feedback.proficiencyLabel"),
|
||||
t("feedback.preparationTaskClarityLabel"),
|
||||
t("feedback.instructorCompetenceLabel"),
|
||||
t("feedback.instructorRespectLabel"),
|
||||
t("feedback.instructorOpenFeedbackLabel"),
|
||||
t("feedback.recommendLabel"),
|
||||
t("feedback.coursePositiveFeedbackLabel"),
|
||||
t("feedback.courseNegativeFeedbackLabel"),
|
||||
t("general.submission"),
|
||||
];
|
||||
const localStepLabels = ref(props.stepLabels);
|
||||
const localQuestionData = ref(props.questionData);
|
||||
const feedbackData: FeedbackData = reactive(feedbackDataFactory());
|
||||
|
||||
const numSteps = stepLabels.length;
|
||||
const numSteps = computed(() => localStepLabels.value.length);
|
||||
const textQuestionKeys = computed(() =>
|
||||
props.questionData.filter((item) => isTextNode(item)).map((item) => item.modelKey)
|
||||
);
|
||||
const avatarUrl = computed(() => circleExperts.value[0]?.avatar_url);
|
||||
|
||||
// noinspection GraphQLUnresolvedReference -> mute IntelliJ warning
|
||||
const sendFeedbackMutation = graphql(`
|
||||
mutation SendFeedbackMutation(
|
||||
$courseSessionId: ID!
|
||||
$learningContentId: ID!
|
||||
$learningContentType: String!
|
||||
$data: GenericScalar!
|
||||
$submitted: Boolean
|
||||
) {
|
||||
send_feedback(
|
||||
course_session_id: $courseSessionId
|
||||
learning_content_page_id: $learningContentId
|
||||
learning_content_type: $learningContentType
|
||||
data: $data
|
||||
submitted: $submitted
|
||||
) {
|
||||
|
|
@ -90,69 +82,6 @@ interface FeedbackData {
|
|||
[key: string]: number | string | null;
|
||||
}
|
||||
|
||||
const feedbackData: FeedbackData = reactive({
|
||||
satisfaction: null,
|
||||
goal_attainment: null,
|
||||
proficiency: null,
|
||||
preparation_task_clarity: null,
|
||||
instructor_competence: null,
|
||||
instructor_respect: null,
|
||||
instructor_open_feedback: "",
|
||||
would_recommend: null,
|
||||
course_positive_feedback: "",
|
||||
course_negative_feedback: "",
|
||||
});
|
||||
|
||||
const questionData = [
|
||||
{
|
||||
modelKey: "satisfaction",
|
||||
items: RATINGS,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "goal_attainment",
|
||||
items: RATINGS,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "proficiency",
|
||||
items: PERCENTAGES,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "preparation_task_clarity",
|
||||
items: YES_NO,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "instructor_competence",
|
||||
items: RATINGS,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "instructor_respect",
|
||||
items: RATINGS,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "instructor_open_feedback",
|
||||
component: ItTextarea,
|
||||
},
|
||||
{
|
||||
modelKey: "would_recommend",
|
||||
items: YES_NO,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "course_positive_feedback",
|
||||
component: ItTextarea,
|
||||
},
|
||||
{
|
||||
modelKey: "course_negative_feedback",
|
||||
component: ItTextarea,
|
||||
},
|
||||
];
|
||||
|
||||
const previousStep = () => {
|
||||
if (stepNo.value > 0) {
|
||||
stepNo.value -= 1;
|
||||
|
|
@ -160,23 +89,17 @@ const previousStep = () => {
|
|||
};
|
||||
|
||||
const nextStep = () => {
|
||||
if (stepNo.value < numSteps && hasStepValidInput(stepNo.value)) {
|
||||
if (stepNo.value < numSteps.value && hasStepValidInput(stepNo.value)) {
|
||||
stepNo.value += 1;
|
||||
}
|
||||
log.debug(`next step ${stepNo.value} of ${numSteps}`);
|
||||
log.debug(`next step ${stepNo.value} of ${numSteps.value}`);
|
||||
mutateFeedback(feedbackData);
|
||||
};
|
||||
|
||||
function hasStepValidInput(stepNumber: number) {
|
||||
const question = questionData[stepNumber - 1];
|
||||
const question = localQuestionData.value[stepNumber - 1];
|
||||
if (question) {
|
||||
if (
|
||||
[
|
||||
"instructor_open_feedback",
|
||||
"course_negative_feedback",
|
||||
"course_positive_feedback",
|
||||
].includes(question.modelKey)
|
||||
) {
|
||||
if (textQuestionKeys.value.includes(question.modelKey)) {
|
||||
// text response questions need to have a "truthy" value (not "" or null)
|
||||
return feedbackData[question.modelKey];
|
||||
} else {
|
||||
|
|
@ -192,6 +115,7 @@ function mutateFeedback(data: FeedbackData, submit = false) {
|
|||
return executeMutation({
|
||||
courseSessionId: courseSession.value.id,
|
||||
learningContentId: props.content.id,
|
||||
learningContentType: props.content.content_type,
|
||||
data: data,
|
||||
submitted: submit,
|
||||
})
|
||||
|
|
@ -199,29 +123,40 @@ function mutateFeedback(data: FeedbackData, submit = false) {
|
|||
log.debug("feedback mutation result", result);
|
||||
if (result.data?.send_feedback?.feedback_response?.data) {
|
||||
const responseData = result.data.send_feedback.feedback_response.data;
|
||||
if (!responseData.instructor_open_feedback) {
|
||||
responseData.instructor_open_feedback = "";
|
||||
}
|
||||
if (!responseData.course_negative_feedback) {
|
||||
responseData.course_negative_feedback = "";
|
||||
}
|
||||
if (!responseData.course_positive_feedback) {
|
||||
responseData.course_positive_feedback = "";
|
||||
}
|
||||
textQuestionKeys.value.map((key) => {
|
||||
if (!responseData[key]) {
|
||||
responseData[key] = "";
|
||||
}
|
||||
});
|
||||
Object.assign(feedbackData, responseData);
|
||||
log.debug("feedback data", feedbackData);
|
||||
feedbackSubmitted.value =
|
||||
result.data?.send_feedback?.feedback_response?.submitted || false;
|
||||
}
|
||||
bustItGetCache(
|
||||
`/api/course/completion/${courseSession.value.id}/${useUserStore().id}/`
|
||||
);
|
||||
})
|
||||
.catch((e) => log.error(e));
|
||||
}
|
||||
|
||||
function feedbackDataFactory() {
|
||||
const data: FeedbackData = {};
|
||||
localQuestionData.value.map((item) => {
|
||||
data[item.modelKey] = isTextNode(item) ? "" : null;
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function isTextNode(item: any) {
|
||||
return item.component.props.placeholder;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
log.debug("Feedback mounted");
|
||||
await mutateFeedback({});
|
||||
if (feedbackSubmitted.value) {
|
||||
stepNo.value = numSteps - 1;
|
||||
stepNo.value = numSteps.value - 1;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -246,22 +181,22 @@ onMounted(async () => {
|
|||
@next="nextStep()"
|
||||
>
|
||||
<div>
|
||||
<p v-if="stepNo === 0" class="mt-10">
|
||||
{{
|
||||
$t("feedback.intro", {
|
||||
name: `${circleExperts[0]?.first_name} ${circleExperts[0]?.last_name}`,
|
||||
})
|
||||
}}
|
||||
<p v-if="stepNo === 0" class="mt-10" data-cy="introduction">
|
||||
{{ introduction }}
|
||||
</p>
|
||||
<p v-if="stepNo > 0 && stepNo + 1 < numSteps" class="pb-2">
|
||||
{{ stepLabels[stepNo] }}
|
||||
<p
|
||||
v-if="stepNo > 0 && stepNo + 1 < numSteps"
|
||||
class="pb-2"
|
||||
:data-cy="`question-${stepNo}`"
|
||||
>
|
||||
{{ localStepLabels[stepNo] }}
|
||||
</p>
|
||||
<div v-for="(question, index) in questionData" :key="index">
|
||||
<!-- eslint-disable -->
|
||||
<!-- eslint does not like the dynamic v-model... -->
|
||||
<component
|
||||
:is="question.component"
|
||||
v-if="index + 1 === stepNo"
|
||||
v-if="index + 1 === stepNo && feedbackData != undefined"
|
||||
v-model="feedbackData[question.modelKey] as any"
|
||||
:items="question['items']"
|
||||
:cy-key="question.modelKey"
|
||||
|
|
@ -269,14 +204,11 @@ onMounted(async () => {
|
|||
<!-- eslint-enable -->
|
||||
</div>
|
||||
<FeedbackCompletition
|
||||
v-if="stepNo === 11"
|
||||
:avatar-url="circleExperts[0].avatar_url"
|
||||
:title="
|
||||
$t('feedback.completionTitle', {
|
||||
name: `${circleExperts[0].first_name} ${circleExperts[0].last_name}`,
|
||||
})
|
||||
"
|
||||
:description="$t('feedback.completionDescription')"
|
||||
v-if="stepNo === numSteps - 1"
|
||||
:avatar-url="avatarUrl"
|
||||
:show-avatar="showAvatar"
|
||||
:title="completionTitle"
|
||||
:description="completionDescription"
|
||||
:feedback-sent="feedbackSubmitted"
|
||||
@send-feedback="mutateFeedback(feedbackData, true)"
|
||||
/>
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
<script setup lang="ts">
|
||||
import ItRadioGroup from "@/components/ui/ItRadioGroup.vue";
|
||||
import ItTextarea from "@/components/ui/ItTextarea.vue";
|
||||
import {
|
||||
PERCENTAGES,
|
||||
RATINGS,
|
||||
YES_NO,
|
||||
} from "@/pages/learningPath/learningContentPage/feedback/feedback.constants";
|
||||
import type { LearningContentFeedbackUK, LearningContentFeedbackVV } from "@/types";
|
||||
import { computed } from "vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { useCourseSessionDetailQuery } from "@/composables";
|
||||
import FeedbackBase from "@/pages/learningPath/learningContentPage/feedback/FeedbackBase.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
content: LearningContentFeedbackVV | LearningContentFeedbackUK;
|
||||
}>();
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const circleExperts = computed(() => {
|
||||
if (props.content?.circle?.slug) {
|
||||
return courseSessionDetailResult.filterCircleExperts(props.content.circle.slug);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const stepLabels = [
|
||||
t("general.introduction"),
|
||||
t("feedback.satisfactionLabel"),
|
||||
t("feedback.goalAttainmentLabel"),
|
||||
t("feedback.proficiencyLabel"),
|
||||
t("feedback.preparationTaskClarityLabel"),
|
||||
t("feedback.instructorCompetenceLabel"),
|
||||
t("feedback.instructorRespectLabel"),
|
||||
t("feedback.instructorOpenFeedbackLabel"),
|
||||
t("feedback.recommendLabel"),
|
||||
t("feedback.coursePositiveFeedbackLabel"),
|
||||
t("feedback.courseNegativeFeedbackLabel"),
|
||||
t("general.submission"),
|
||||
];
|
||||
|
||||
const questionData = [
|
||||
{
|
||||
modelKey: "satisfaction",
|
||||
items: RATINGS,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "goal_attainment",
|
||||
items: RATINGS,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "proficiency",
|
||||
items: PERCENTAGES,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "preparation_task_clarity",
|
||||
items: YES_NO,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "instructor_competence",
|
||||
items: RATINGS,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "instructor_respect",
|
||||
items: RATINGS,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "instructor_open_feedback",
|
||||
component: ItTextarea,
|
||||
},
|
||||
{
|
||||
modelKey: "would_recommend",
|
||||
items: YES_NO,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "course_positive_feedback",
|
||||
component: ItTextarea,
|
||||
},
|
||||
{
|
||||
modelKey: "course_negative_feedback",
|
||||
component: ItTextarea,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FeedbackBase
|
||||
:step-labels="stepLabels"
|
||||
:question-data="questionData"
|
||||
:content="props.content"
|
||||
:introduction="
|
||||
$t('feedback.intro', {
|
||||
name: `${circleExperts[0]?.first_name} ${circleExperts[0]?.last_name}`,
|
||||
})
|
||||
"
|
||||
:title="$t('feedback.areYouSatisfied')"
|
||||
:completion-title="
|
||||
$t('feedback.completionTitle', {
|
||||
name: `${circleExperts[0].first_name} ${circleExperts[0].last_name}`,
|
||||
})
|
||||
"
|
||||
:completion-description="$t('feedback.completionDescription')"
|
||||
:show-avatar="true"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<script setup lang="ts">
|
||||
import ItRadioGroup from "@/components/ui/ItRadioGroup.vue";
|
||||
import ItTextarea from "@/components/ui/ItTextarea.vue";
|
||||
import {
|
||||
PERCENTAGES,
|
||||
RATINGS,
|
||||
YES_NO,
|
||||
} from "@/pages/learningPath/learningContentPage/feedback/feedback.constants";
|
||||
import type { LearningContentFeedbackUK, LearningContentFeedbackVV } from "@/types";
|
||||
import FeedbackBase from "@/pages/learningPath/learningContentPage/feedback/FeedbackBase.vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
|
||||
const props = defineProps<{
|
||||
content: LearningContentFeedbackVV | LearningContentFeedbackUK;
|
||||
}>();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const stepLabels = [
|
||||
t("general.introduction"),
|
||||
t("feedback.satisfactionLabel"),
|
||||
t("feedback.goalAttainmentLabel"),
|
||||
t("feedback.proficiencyLabelVV"),
|
||||
t("feedback.praxisAssignmentClarity"),
|
||||
t("feedback.recommendLabelVV"),
|
||||
t("feedback.coursePositiveFeedbackLabel"),
|
||||
t("feedback.courseNegativeFeedbackLabel"),
|
||||
t("general.submission"),
|
||||
];
|
||||
|
||||
const questionData = [
|
||||
{
|
||||
modelKey: "satisfaction",
|
||||
items: RATINGS,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "goal_attainment",
|
||||
items: RATINGS,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "proficiency",
|
||||
items: PERCENTAGES,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "preparation_task_clarity",
|
||||
items: YES_NO,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "would_recommend",
|
||||
items: YES_NO,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "course_positive_feedback",
|
||||
component: ItTextarea,
|
||||
},
|
||||
{
|
||||
modelKey: "course_negative_feedback",
|
||||
component: ItTextarea,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FeedbackBase
|
||||
:step-labels="stepLabels"
|
||||
:question-data="questionData"
|
||||
:content="props.content"
|
||||
:introduction="$t('a.feedback.introductionVV')"
|
||||
:title="$t('Feedback')"
|
||||
:completion-title="$t('feedback.completionDescriptionVV')"
|
||||
:completion-description="$t('feedback.completionDescriptionVV')"
|
||||
:show-avatar="false"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -4,7 +4,11 @@
|
|||
<div
|
||||
class="b-0 flex flex-col lg:flex-row lg:items-center lg:border lg:border-gray-400 lg:p-8"
|
||||
>
|
||||
<img :src="avatarUrl" class="mb-6 h-16 w-16 rounded-full lg:mr-12" />
|
||||
<img
|
||||
v-if="showAvatar"
|
||||
:src="avatarUrl"
|
||||
class="mb-6 h-16 w-16 rounded-full lg:mr-12"
|
||||
/>
|
||||
<h2 class="mb-8 block lg:hidden">{{ title }}</h2>
|
||||
<div>
|
||||
<p class="mb-6">{{ description }}</p>
|
||||
|
|
@ -33,6 +37,7 @@ interface Props {
|
|||
title?: string;
|
||||
description?: string;
|
||||
feedbackSent?: boolean;
|
||||
showAvatar?: boolean;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
|
|
@ -40,6 +45,7 @@ withDefaults(defineProps<Props>(), {
|
|||
title: "",
|
||||
description: "",
|
||||
feedbackSent: false,
|
||||
showAvatar: true,
|
||||
});
|
||||
|
||||
defineEmits(["sendFeedback"]);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ import type {
|
|||
LearningContentAttendanceCourseObjectType,
|
||||
LearningContentDocumentListObjectType,
|
||||
LearningContentEdoniqTestObjectType,
|
||||
LearningContentFeedbackObjectType,
|
||||
LearningContentFeedbackUkObjectType,
|
||||
LearningContentFeedbackVvObjectType,
|
||||
LearningContentKnowledgeAssessmentObjectType,
|
||||
LearningContentLearningModuleObjectType,
|
||||
LearningContentMediaLibraryObjectType,
|
||||
|
|
@ -68,8 +69,12 @@ export type LearningContentEdoniqTest = LearningContentEdoniqTestObjectType & {
|
|||
readonly content_type: "learnpath.LearningContentEdoniqTest";
|
||||
};
|
||||
|
||||
export type LearningContentFeedback = LearningContentFeedbackObjectType & {
|
||||
readonly content_type: "learnpath.LearningContentFeedback";
|
||||
export type LearningContentFeedbackVV = LearningContentFeedbackVvObjectType & {
|
||||
readonly content_type: "learnpath.LearningContentFeedbackVV";
|
||||
};
|
||||
|
||||
export type LearningContentFeedbackUK = LearningContentFeedbackUkObjectType & {
|
||||
readonly content_type: "learnpath.LearningContentFeedbackUK";
|
||||
};
|
||||
|
||||
export type LearningContentLearningModule = LearningContentLearningModuleObjectType & {
|
||||
|
|
@ -102,7 +107,8 @@ export type LearningContent =
|
|||
| LearningContentAttendanceCourse
|
||||
| LearningContentDocumentList
|
||||
| LearningContentEdoniqTest
|
||||
| LearningContentFeedback
|
||||
| LearningContentFeedbackUK
|
||||
| LearningContentFeedbackVV
|
||||
| LearningContentLearningModule
|
||||
| LearningContentKnowledgeAssessment
|
||||
| LearningContentMediaLibrary
|
||||
|
|
@ -188,6 +194,7 @@ export interface Course {
|
|||
title: string;
|
||||
category_name: string;
|
||||
slug: string;
|
||||
enable_circle_documents: boolean;
|
||||
}
|
||||
|
||||
export interface CourseCategory {
|
||||
|
|
@ -342,7 +349,7 @@ export interface AssignmentEvaluationTask {
|
|||
title: string;
|
||||
description: string;
|
||||
max_points: number;
|
||||
sub_tasks: AssignmentEvaluationSubTask[];
|
||||
sub_tasks?: AssignmentEvaluationSubTask[];
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -559,3 +566,13 @@ export type DueDate = SimpleDueDate & {
|
|||
course_session_id: string;
|
||||
circle: CircleLight | null;
|
||||
};
|
||||
|
||||
export type FeedbackType = "uk" | "vv";
|
||||
|
||||
export interface FeedbackData {
|
||||
amount: number;
|
||||
questions: {
|
||||
[key: string]: any;
|
||||
};
|
||||
feedbackType: FeedbackType;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ export function learningContentTypeData(
|
|||
return { title: t("learningContentTypes.test"), icon: "it-icon-lc-test" };
|
||||
case "learnpath.LearningContentRichText":
|
||||
return { title: t("learningContentTypes.text"), icon: "it-icon-lc-resource" };
|
||||
case "learnpath.LearningContentFeedback":
|
||||
case "learnpath.LearningContentFeedbackUK":
|
||||
case "learnpath.LearningContentFeedbackVV":
|
||||
return { title: t("learningContentTypes.feedback"), icon: "it-icon-lc-feedback" };
|
||||
case "learnpath.LearningContentPlaceholder":
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ export function getAssignmentTypeTitle(assignmentType: AssignmentType): string {
|
|||
switch (assignmentType) {
|
||||
case "CASEWORK":
|
||||
return t("learningContentTypes.casework");
|
||||
case "PRAXIS_ASSIGNMENT":
|
||||
return t("learningContentTypes.praxisAssignment");
|
||||
case "PREP_ASSIGNMENT":
|
||||
return t("learningContentTypes.prepAssignment");
|
||||
case "REFLECTION":
|
||||
|
|
|
|||
|
|
@ -1,6 +1,121 @@
|
|||
import { TEST_STUDENT1_USER_ID } from "../../consts";
|
||||
import { login } from "../helpers";
|
||||
|
||||
function completePraxisAssignment(selectExpert = false) {
|
||||
cy.visit("/course/test-lehrgang/learn/reisen/mein-kundenstamm");
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 1: Filtere nach Kundeneigenschaften"
|
||||
);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 1.1");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-2"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 1.2");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-3"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 1.3");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-4"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 1.4");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-5"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 1.5");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
||||
// step 2
|
||||
cy.testLearningContentTitle("Teilaufgabe 2: Filtere nach Versicherungen");
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 2.1");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-2"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 2.2");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-3"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 2.3");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-4"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 2.4");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
||||
// check that results are stored on server
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion("assignment_user_id", TEST_STUDENT1_USER_ID).then(
|
||||
(ac) => {
|
||||
expect(ac.completion_status).to.equal("IN_PROGRESS");
|
||||
expect(JSON.stringify(ac.completion_data)).to.include(
|
||||
"Hallo Teilaufgabe 2.1"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// step 3
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 3: Filtere nach besonderen Ereignissen"
|
||||
);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 3.1");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-2"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 3.2");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
||||
// step 4
|
||||
cy.testLearningContentTitle("Teilaufgabe 4: Kundentelefonate");
|
||||
cy.get('[data-cy="it-textarea-user-text-input-0"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 4.1");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
||||
// step 5
|
||||
cy.testLearningContentTitle("Teilaufgabe 5: Kundentelefonate2");
|
||||
cy.get('[data-cy="it-textarea-user-text-input-0"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 5.1");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
||||
cy.get('[data-cy="confirm-submit-results"]').should("not.exist");
|
||||
cy.get('[data-cy="confirm-submit-person"]').should(
|
||||
"contain",
|
||||
"Folgende Person soll mir Feedback zu meinen Ergebnissen geben."
|
||||
);
|
||||
if (selectExpert) {
|
||||
cy.get('[data-cy="confirm-submit-person"]').click();
|
||||
}
|
||||
cy.get('[data-cy="submit-assignment"]').click();
|
||||
cy.get('[data-cy="success-text"]').should("exist");
|
||||
|
||||
// app goes back to circle view -> check if assignment is marked as completed
|
||||
cy.url().should((url) => {
|
||||
expect(url).to.match(/\/reisen#lu-reisen?$/);
|
||||
});
|
||||
cy.reload();
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm-checkbox"]'
|
||||
).should("have.class", "cy-checked");
|
||||
}
|
||||
|
||||
describe("assignmentStudent.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset");
|
||||
|
|
@ -10,234 +125,255 @@ describe("assignmentStudent.cy.js", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("can open assignment", () => {
|
||||
cy.testLearningContentTitle("Einleitung");
|
||||
cy.testLearningContentSubtitle(
|
||||
"Überprüfen einer Motorfahrzeugs-Versicherungspolice"
|
||||
);
|
||||
});
|
||||
|
||||
it("can navigate through assignment", () => {
|
||||
// 1 Step forward
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 1: Beispiel einer Versicherungspolice finden"
|
||||
);
|
||||
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 2: Kundensituation und Ausgangslage"
|
||||
);
|
||||
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle("Teilaufgabe 3: Aktuelle Versicherung");
|
||||
|
||||
cy.learningContentMultiLayoutPreviousStep();
|
||||
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 2: Kundensituation und Ausgangslage"
|
||||
);
|
||||
});
|
||||
|
||||
it("can save confirmation", () => {
|
||||
// 1 Step forward
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 1: Beispiel einer Versicherungspolice finden"
|
||||
);
|
||||
// Click confirmation
|
||||
cy.get('[data-cy="it-checkbox-confirmation-1"]').click();
|
||||
|
||||
cy.reload();
|
||||
cy.get('[data-cy="it-checkbox-confirmation-1"]').should(
|
||||
"have.class",
|
||||
"cy-checked"
|
||||
);
|
||||
});
|
||||
|
||||
it("can save text", () => {
|
||||
// 2 Steps forward
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 2: Kundensituation und Ausgangslage"
|
||||
);
|
||||
// Enter text
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallovelo");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.reload();
|
||||
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]').should(
|
||||
"have.value",
|
||||
"Hallovelo"
|
||||
);
|
||||
});
|
||||
|
||||
it("can visit sub step directly via url", () => {
|
||||
cy.visit(
|
||||
"/course/test-lehrgang/learn/fahrzeug/überprüfen-einer-motorfahrzeug-versicherungspolice?step=3"
|
||||
);
|
||||
cy.testLearningContentTitle("Teilaufgabe 3: Aktuelle Versicherung");
|
||||
});
|
||||
|
||||
it("can visit sub step by clicking navigation bar", () => {
|
||||
cy.get('[data-cy="nav-progress-step-4"]').click();
|
||||
cy.testLearningContentTitle("Teilaufgabe 4: Deine Empfehlungen");
|
||||
});
|
||||
|
||||
it("can submit assignment", () => {
|
||||
cy.visit(
|
||||
"/course/test-lehrgang/learn/fahrzeug/überprüfen-einer-motorfahrzeug-versicherungspolice?step=7"
|
||||
);
|
||||
cy.get('[data-cy="confirm-submit-results"] label').click();
|
||||
cy.get('[data-cy="confirm-submit-person"]').click();
|
||||
cy.get('[data-cy="submit-assignment"]').click();
|
||||
cy.get('[data-cy="success-text"]').should("exist");
|
||||
|
||||
// Check if trainer received notification
|
||||
cy.clearLocalStorage();
|
||||
cy.clearCookies();
|
||||
cy.reload(true);
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/notifications");
|
||||
cy.get(`[data-cy=notification-idx-0]`).within(() => {
|
||||
cy.get('[data-cy="unread"]').should("exist");
|
||||
cy.get('[data-cy="notification-target-idx-0-verb"]').contains(
|
||||
"Test Student1 hat die geleitete Fallarbeit «Überprüfen einer Motorfahrzeugs-Versicherungspolice» abgegeben."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("can make complete assignment", () => {
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 1: Beispiel einer Versicherungspolice finden"
|
||||
);
|
||||
// Click confirmation
|
||||
cy.get('[data-cy="it-checkbox-confirmation-1"]').click();
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
||||
// step 2
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 2: Kundensituation und Ausgangslage"
|
||||
);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 2");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
||||
// check that results are stored on server
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"assignment_user_id",
|
||||
TEST_STUDENT1_USER_ID
|
||||
).then((ac) => {
|
||||
expect(ac.completion_status).to.equal("IN_PROGRESS");
|
||||
expect(JSON.stringify(ac.completion_data)).to.include(
|
||||
"Hallo Teilaufgabe 2"
|
||||
describe("Assignment", () => {
|
||||
it("can open assignment", () => {
|
||||
cy.testLearningContentTitle("Einleitung");
|
||||
cy.testLearningContentSubtitle(
|
||||
"Überprüfen einer Motorfahrzeugs-Versicherungspolice"
|
||||
);
|
||||
});
|
||||
|
||||
// step 3
|
||||
cy.testLearningContentTitle("Teilaufgabe 3: Aktuelle Versicherung");
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 3");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
it("can navigate through assignment", () => {
|
||||
// 1 Step forward
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 1: Beispiel einer Versicherungspolice finden"
|
||||
);
|
||||
|
||||
// step 4
|
||||
cy.testLearningContentTitle("Teilaufgabe 4: Deine Empfehlungen");
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 4.1");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-2"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 4.2");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-3"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 4.3");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 2: Kundensituation und Ausgangslage"
|
||||
);
|
||||
|
||||
// step 5
|
||||
cy.testLearningContentTitle("Teilaufgabe 5: Reflexion");
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 5.1");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-2"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 5.2");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-3"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 5.3");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle("Teilaufgabe 3: Aktuelle Versicherung");
|
||||
|
||||
// step 6
|
||||
cy.testLearningContentTitle("Teilaufgabe 6: Learnings");
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 6.1");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-2"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 6.2");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.learningContentMultiLayoutPreviousStep();
|
||||
|
||||
cy.get('[data-cy="confirm-submit-results"] label').click();
|
||||
cy.get('[data-cy="confirm-submit-person"]').click();
|
||||
cy.get('[data-cy="submit-assignment"]').click();
|
||||
cy.get('[data-cy="success-text"]').should("exist");
|
||||
|
||||
// app goes back to circle view -> check if assignment is marked as completed
|
||||
cy.url().should((url) => {
|
||||
expect(url).to.match(/\/fahrzeug#lu-transfer?$/);
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 2: Kundensituation und Ausgangslage"
|
||||
);
|
||||
});
|
||||
cy.reload();
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice-checkbox"]'
|
||||
).should("have.class", "cy-checked");
|
||||
|
||||
// reopening page should get directly to last step
|
||||
cy.visit(
|
||||
"/course/test-lehrgang/learn/fahrzeug/überprüfen-einer-motorfahrzeug-versicherungspolice"
|
||||
);
|
||||
cy.url().should("include", "step=7");
|
||||
it("can save confirmation", () => {
|
||||
// 1 Step forward
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 1: Beispiel einer Versicherungspolice finden"
|
||||
);
|
||||
// Click confirmation
|
||||
cy.get('[data-cy="it-checkbox-confirmation-1"]').click();
|
||||
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"assignment_user_id",
|
||||
TEST_STUDENT1_USER_ID
|
||||
).then((ac) => {
|
||||
expect(ac.completion_status).to.equal("SUBMITTED");
|
||||
expect(ac.evaluation_max_points).to.equal(24);
|
||||
const completionString = JSON.stringify(ac.completion_data);
|
||||
console.log(completionString);
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 2");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 3");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 4.1");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 4.2");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 4.3");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 5.1");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 5.2");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 5.3");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 6.1");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 6.2");
|
||||
cy.reload();
|
||||
cy.get('[data-cy="it-checkbox-confirmation-1"]').should(
|
||||
"have.class",
|
||||
"cy-checked"
|
||||
);
|
||||
});
|
||||
|
||||
it("can save text", () => {
|
||||
// 2 Steps forward
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 2: Kundensituation und Ausgangslage"
|
||||
);
|
||||
// Enter text
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallovelo");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.reload();
|
||||
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]').should(
|
||||
"have.value",
|
||||
"Hallovelo"
|
||||
);
|
||||
});
|
||||
|
||||
it("can visit sub step directly via url", () => {
|
||||
cy.visit(
|
||||
"/course/test-lehrgang/learn/fahrzeug/überprüfen-einer-motorfahrzeug-versicherungspolice?step=3"
|
||||
);
|
||||
cy.testLearningContentTitle("Teilaufgabe 3: Aktuelle Versicherung");
|
||||
});
|
||||
|
||||
it("can visit sub step by clicking navigation bar", () => {
|
||||
cy.get('[data-cy="nav-progress-step-4"]').click();
|
||||
cy.testLearningContentTitle("Teilaufgabe 4: Deine Empfehlungen");
|
||||
});
|
||||
|
||||
it("can submit assignment", () => {
|
||||
cy.visit(
|
||||
"/course/test-lehrgang/learn/fahrzeug/überprüfen-einer-motorfahrzeug-versicherungspolice?step=7"
|
||||
);
|
||||
cy.get('[data-cy="confirm-submit-results"] label').click();
|
||||
cy.get('[data-cy="confirm-submit-person"]').click();
|
||||
cy.get('[data-cy="submit-assignment"]').click();
|
||||
cy.get('[data-cy="success-text"]').should("exist");
|
||||
|
||||
// Check if trainer received notification
|
||||
cy.clearLocalStorage();
|
||||
cy.clearCookies();
|
||||
cy.reload(true);
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/notifications");
|
||||
cy.get(`[data-cy=notification-idx-0]`).within(() => {
|
||||
cy.get('[data-cy="unread"]').should("exist");
|
||||
cy.get('[data-cy="notification-target-idx-0-verb"]').contains(
|
||||
"Test Student1 hat die geleitete Fallarbeit «Überprüfen einer Motorfahrzeugs-Versicherungspolice» abgegeben."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("can make complete assignment", () => {
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 1: Beispiel einer Versicherungspolice finden"
|
||||
);
|
||||
// Click confirmation
|
||||
cy.get('[data-cy="it-checkbox-confirmation-1"]').click();
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
||||
// step 2
|
||||
cy.testLearningContentTitle(
|
||||
"Teilaufgabe 2: Kundensituation und Ausgangslage"
|
||||
);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 2");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
||||
// check that results are stored on server
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"assignment_user_id",
|
||||
TEST_STUDENT1_USER_ID
|
||||
).then((ac) => {
|
||||
expect(ac.completion_status).to.equal("IN_PROGRESS");
|
||||
expect(JSON.stringify(ac.completion_data)).to.include(
|
||||
"Hallo Teilaufgabe 2"
|
||||
);
|
||||
});
|
||||
|
||||
// step 3
|
||||
cy.testLearningContentTitle("Teilaufgabe 3: Aktuelle Versicherung");
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 3");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
||||
// step 4
|
||||
cy.testLearningContentTitle("Teilaufgabe 4: Deine Empfehlungen");
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 4.1");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-2"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 4.2");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-3"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 4.3");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
||||
// step 5
|
||||
cy.testLearningContentTitle("Teilaufgabe 5: Reflexion");
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 5.1");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-2"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 5.2");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-3"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 5.3");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
||||
// step 6
|
||||
cy.testLearningContentTitle("Teilaufgabe 6: Learnings");
|
||||
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 6.1");
|
||||
cy.wait(550);
|
||||
cy.get('[data-cy="it-textarea-user-text-input-2"]')
|
||||
.clear()
|
||||
.type("Hallo Teilaufgabe 6.2");
|
||||
// wait because of input debounce
|
||||
cy.wait(550);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
||||
cy.get('[data-cy="confirm-submit-person"]').should(
|
||||
"contain",
|
||||
"Ja, die folgende Person soll meine Ergebnisse bewerten."
|
||||
);
|
||||
cy.get('[data-cy="confirm-submit-results"] label').click();
|
||||
cy.get('[data-cy="confirm-submit-person"]').click();
|
||||
cy.get('[data-cy="submit-assignment"]').click();
|
||||
cy.get('[data-cy="success-text"]').should("exist");
|
||||
|
||||
cy.get('[data-cy="confirm-container"]')
|
||||
.find('[data-cy="show-sample-solution"]')
|
||||
.then(($elements) => {
|
||||
if ($elements.length > 0) {
|
||||
// Ist die Musterlösung da?
|
||||
cy.get('[data-cy="show-sample-solution"]').should("exist");
|
||||
cy.get('[data-cy="show-sample-solution-button"]').should("exist");
|
||||
}
|
||||
});
|
||||
|
||||
cy.visit("/course/test-lehrgang/learn/fahrzeug/");
|
||||
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice-checkbox"]'
|
||||
).should("have.class", "cy-checked");
|
||||
|
||||
//reopening page should get directly to last step
|
||||
cy.visit(
|
||||
"/course/test-lehrgang/learn/fahrzeug/überprüfen-einer-motorfahrzeug-versicherungspolice"
|
||||
);
|
||||
cy.url().should("include", "step=7");
|
||||
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"assignment_user_id",
|
||||
TEST_STUDENT1_USER_ID
|
||||
).then((ac) => {
|
||||
expect(ac.completion_status).to.equal("SUBMITTED");
|
||||
expect(ac.evaluation_max_points).to.equal(24);
|
||||
const completionString = JSON.stringify(ac.completion_data);
|
||||
console.log(completionString);
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 2");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 3");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 4.1");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 4.2");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 4.3");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 5.1");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 5.2");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 5.3");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 6.1");
|
||||
expect(completionString).to.include("Hallo Teilaufgabe 6.2");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Praxis Assignment", () => {
|
||||
it("can make complete assignment without expert", () =>
|
||||
completePraxisAssignment());
|
||||
|
||||
it("can make complete assignment with expert", () =>
|
||||
completePraxisAssignment(true));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { TEST_STUDENT1_USER_ID } from "../../consts";
|
||||
import { TEST_TRAINER1_USER_ID } from "../../consts";
|
||||
import { login } from "../helpers";
|
||||
|
||||
describe("assignmentTrainer.cy.js", () => {
|
||||
|
|
@ -7,191 +7,334 @@ describe("assignmentTrainer.cy.js", () => {
|
|||
login("test-trainer1@example.com", "test");
|
||||
});
|
||||
|
||||
it("can open cockpit assignment page and open user assignment", () => {
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
describe("Casework", () => {
|
||||
it("can open cockpit assignment page and open user assignment", () => {
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').should("contain", "Ergebnisse abgegeben");
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
cy.get('[data-cy="Student1"]').should("contain", "Ergebnisse abgegeben");
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
|
||||
cy.get('[data-cy="student-submission"]').should("contain", "Ergebnisse");
|
||||
cy.get('[data-cy="student-submission"]').should("contain", "Student1");
|
||||
});
|
||||
|
||||
it("can start evaluation and store evaluation results", () => {
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
|
||||
cy.get('[data-cy="start-evaluation"]').click();
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 1 / 5"
|
||||
);
|
||||
|
||||
// without text input the button should be disabled
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
|
||||
// with text you can continue
|
||||
cy.get('[data-cy="subtask-4"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Gut gemacht!");
|
||||
// wait for debounce
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 2 / 5"
|
||||
);
|
||||
cy.get('[data-cy="subtask-2"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Nicht so gut");
|
||||
cy.wait(500);
|
||||
|
||||
// revisit step 1 will show stored data
|
||||
cy.get('[data-cy="previous-step"]').click();
|
||||
cy.get('[data-cy="reason-text"]')
|
||||
.find("textarea")
|
||||
.should("have.value", "Gut gemacht!");
|
||||
|
||||
// even after reload
|
||||
cy.reload();
|
||||
cy.get('[data-cy="reason-text"]')
|
||||
.find("textarea")
|
||||
.should("have.value", "Gut gemacht!");
|
||||
|
||||
// it can access step directly via url
|
||||
cy.url().then((url) => {
|
||||
const step2Url = url.replace("step=1", "step=2");
|
||||
console.log(step2Url);
|
||||
cy.visit(step2Url);
|
||||
cy.get('[data-cy="student-submission"]').should("contain", "Ergebnisse");
|
||||
cy.get('[data-cy="student-submission"]').should("contain", "Student1");
|
||||
});
|
||||
|
||||
cy.get('[data-cy="reason-text"]')
|
||||
.find("textarea")
|
||||
.should("have.value", "Nicht so gut");
|
||||
it("can start evaluation and store evaluation results", () => {
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"assignment_user_id",
|
||||
TEST_STUDENT1_USER_ID
|
||||
).then((ac) => {
|
||||
expect(ac.completion_status).to.equal("EVALUATION_IN_PROGRESS");
|
||||
expect(JSON.stringify(ac.completion_data)).to.include("Nicht so gut");
|
||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||
expert_data: { points: 2, text: "Nicht so gut" },
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
|
||||
cy.get('[data-cy="title"]').should("contain", "Bewertung");
|
||||
cy.get('[data-cy="evaluation-duedate"]').should("exist");
|
||||
cy.get('[data-cy="instruction"]').should(
|
||||
"contain",
|
||||
"Die Gesamtpunktzahl und die daraus resultierende Note wird auf Grund des hinterlegeten Beurteilungsinstrument berechnet."
|
||||
);
|
||||
cy.get('[data-cy="start-evaluation"]').click();
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 1 / 5"
|
||||
);
|
||||
|
||||
// without text input the button should be disabled
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
|
||||
// with text you can continue
|
||||
cy.get('[data-cy="subtask-4"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Gut gemacht!");
|
||||
// wait for debounce
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 2 / 5"
|
||||
);
|
||||
cy.get('[data-cy="subtask-2"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Nicht so gut");
|
||||
cy.wait(500);
|
||||
|
||||
// revisit step 1 will show stored data
|
||||
cy.get('[data-cy="previous-step"]').click();
|
||||
cy.get('[data-cy="reason-text"]')
|
||||
.find("textarea")
|
||||
.should("have.value", "Gut gemacht!");
|
||||
|
||||
// even after reload
|
||||
cy.reload();
|
||||
cy.get('[data-cy="reason-text"]')
|
||||
.find("textarea")
|
||||
.should("have.value", "Gut gemacht!");
|
||||
|
||||
// it can access step directly via url
|
||||
cy.url().then((url) => {
|
||||
const step2Url = url.replace("step=1", "step=2");
|
||||
console.log(step2Url);
|
||||
cy.visit(step2Url);
|
||||
});
|
||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||
expert_data: { points: 4, text: "Gut gemacht!" },
|
||||
|
||||
cy.get('[data-cy="reason-text"]')
|
||||
.find("textarea")
|
||||
.should("have.value", "Nicht so gut");
|
||||
|
||||
cy.wait(500);
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"evaluation_user_id",
|
||||
TEST_TRAINER1_USER_ID
|
||||
).then((ac) => {
|
||||
expect(ac.completion_status).to.equal("EVALUATION_IN_PROGRESS");
|
||||
expect(JSON.stringify(ac.completion_data)).to.include("Nicht so gut");
|
||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||
expert_data: { points: 2, text: "Nicht so gut" },
|
||||
});
|
||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||
expert_data: { points: 4, text: "Gut gemacht!" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("can make complete evaluation", () => {
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
|
||||
cy.get('[data-cy="start-evaluation"]').click();
|
||||
|
||||
// step 1
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 1 / 5"
|
||||
);
|
||||
cy.get('[data-cy="subtask-6"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 1");
|
||||
// wait for debounce
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// step 2
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 2 / 5"
|
||||
);
|
||||
cy.get('[data-cy="subtask-4"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 2");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// step 3
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 3 / 5"
|
||||
);
|
||||
cy.get('[data-cy="subtask-2"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 3");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// step 4
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 4 / 5"
|
||||
);
|
||||
cy.get('[data-cy="subtask-3"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 4");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// step 5
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 5 / 5"
|
||||
);
|
||||
cy.get('[data-cy="subtask-2"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 5");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// freigabe
|
||||
cy.get('[data-cy="sub-title"]').should("contain", "Bewertung Freigabe");
|
||||
cy.get('[data-cy="user-points"]').should("contain", "17");
|
||||
cy.get('[data-cy="total-points"]').should("contain", "24");
|
||||
cy.get('[data-cy="submit-evaluation"]').click();
|
||||
|
||||
cy.get('[data-cy="result-section"]').should(
|
||||
"contain",
|
||||
"Deine Bewertung für Test Student1 wurde freigegeben"
|
||||
);
|
||||
|
||||
// going back to cockpit should show points for student
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.reload();
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').should("contain", "Bewertung freigegeben");
|
||||
cy.get('[data-cy="Student1"]').should("contain", "17 von 24 Punkte");
|
||||
|
||||
// clicking on results page will go to last step
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
cy.url().should("include", "step=6");
|
||||
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"evaluation_user_id",
|
||||
TEST_TRAINER1_USER_ID
|
||||
).then((ac) => {
|
||||
expect(ac.completion_status).to.equal("EVALUATION_SUBMITTED");
|
||||
expect(ac.evaluation_points).to.equal(17);
|
||||
expect(ac.evaluation_max_points).to.equal(24);
|
||||
expect(ac.evaluation_passed).to.equal(true);
|
||||
const completionString = JSON.stringify(ac.completion_data);
|
||||
expect(completionString).to.include("Begründung Schritt 1");
|
||||
expect(completionString).to.include("Begründung Schritt 2");
|
||||
expect(completionString).to.include("Begründung Schritt 3");
|
||||
expect(completionString).to.include("Begründung Schritt 4");
|
||||
expect(completionString).to.include("Begründung Schritt 5");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("can make complete evaluation", () => {
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
//Todo: Move tests to Lernbegleitung once it is implemented
|
||||
describe("Praxis Assignment", () => {
|
||||
it("can start evaluation and store evaluation results", () => {
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get('[data-cy="dropdown-select"]').click();
|
||||
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]'
|
||||
).click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
|
||||
cy.get('[data-cy="start-evaluation"]').click();
|
||||
cy.get('[data-cy="title"]').should("contain", "Feedback");
|
||||
cy.get('[data-cy="evaluation-duedate]"').should("not.exist");
|
||||
cy.get('[data-cy="instruction"]').should("contain", "Intro für Feedback");
|
||||
cy.get('[data-cy="start-evaluation"]').click();
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Feedback 1 / 5");
|
||||
|
||||
// step 1
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 1 / 5"
|
||||
);
|
||||
cy.get('[data-cy="subtask-6"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 1");
|
||||
// wait for debounce
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
// without text input the button should be disabled
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
|
||||
// step 2
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 2 / 5"
|
||||
);
|
||||
cy.get('[data-cy="subtask-4"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 2");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
// with text you can continue
|
||||
cy.get('[data-cy="reason-text"]').type("Gut gemacht!");
|
||||
// wait for debounce
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// step 3
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 3 / 5"
|
||||
);
|
||||
cy.get('[data-cy="subtask-2"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 3");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Feedback 2 / 5");
|
||||
cy.get('[data-cy="reason-text"]').type("Nicht so gut");
|
||||
cy.wait(1000);
|
||||
|
||||
// step 4
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 4 / 5"
|
||||
);
|
||||
cy.get('[data-cy="subtask-3"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 4");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"evaluation_user_id",
|
||||
TEST_TRAINER1_USER_ID
|
||||
).then((ac) => {
|
||||
console.log(ac.completion_status);
|
||||
expect(ac.completion_status).to.equal("EVALUATION_IN_PROGRESS");
|
||||
expect(JSON.stringify(ac.completion_data)).to.include("Nicht so gut");
|
||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||
expert_data: { points: 0, text: "Nicht so gut" },
|
||||
});
|
||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||
expert_data: { points: 0, text: "Gut gemacht!" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// step 5
|
||||
cy.get('[data-cy="evaluation-task"]').should(
|
||||
"contain",
|
||||
"Beurteilungskriterium 5 / 5"
|
||||
);
|
||||
cy.get('[data-cy="subtask-2"]').click();
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 5");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
it("can make complete evaluation", () => {
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get('[data-cy="dropdown-select"]').click();
|
||||
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]'
|
||||
).click();
|
||||
|
||||
// freigabe
|
||||
cy.get('[data-cy="sub-title"]').should("contain", "Bewertung Freigabe");
|
||||
cy.get('[data-cy="user-points"]').should("contain", "17");
|
||||
cy.get('[data-cy="total-points"]').should("contain", "24");
|
||||
cy.get('[data-cy="submit-evaluation"]').click();
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
|
||||
cy.get('[data-cy="result-section"]').should(
|
||||
"contain",
|
||||
"Deine Bewertung für Test Student1 wurde freigegeben"
|
||||
);
|
||||
cy.get('[data-cy="start-evaluation"]').click();
|
||||
|
||||
// going back to cockpit should show points for student
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.reload();
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
||||
).click();
|
||||
// step 1
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Feedback 1 / 5");
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 1");
|
||||
// wait for debounce
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').should("contain", "Bewertung freigegeben");
|
||||
cy.get('[data-cy="Student1"]').should("contain", "17 von 24 Punkte");
|
||||
// step 2
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Feedback 2 / 5");
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 2");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// clicking on results page will go to last step
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
cy.url().should("include", "step=6");
|
||||
// step 3
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Feedback 3 / 5");
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 3");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"assignment_user_id",
|
||||
TEST_STUDENT1_USER_ID
|
||||
).then((ac) => {
|
||||
expect(ac.completion_status).to.equal("EVALUATION_SUBMITTED");
|
||||
expect(ac.evaluation_points).to.equal(17);
|
||||
expect(ac.evaluation_max_points).to.equal(24);
|
||||
expect(ac.evaluation_passed).to.equal(true);
|
||||
const completionString = JSON.stringify(ac.completion_data);
|
||||
expect(completionString).to.include("Begründung Schritt 1");
|
||||
expect(completionString).to.include("Begründung Schritt 2");
|
||||
expect(completionString).to.include("Begründung Schritt 3");
|
||||
expect(completionString).to.include("Begründung Schritt 4");
|
||||
expect(completionString).to.include("Begründung Schritt 5");
|
||||
// step 4
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Feedback 4 / 5");
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 4");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// step 5
|
||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Feedback 5 / 5");
|
||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 5");
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
// freigabe
|
||||
cy.get('[data-cy="sub-title"]').should("contain", "Feedback Freigabe");
|
||||
cy.get('[data-cy="total-points"]').should("not.exist");
|
||||
cy.get('[data-cy="submit-evaluation"]').click();
|
||||
|
||||
cy.get('[data-cy="result-section"]').should(
|
||||
"contain",
|
||||
"Dein Feedback für Test Student1 wurde freigegeben"
|
||||
);
|
||||
|
||||
// going back to cockpit should show points for student
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.reload();
|
||||
cy.get('[data-cy="dropdown-select"]').click();
|
||||
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
||||
cy.get(
|
||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]'
|
||||
).click();
|
||||
|
||||
cy.get('[data-cy="Student1"]').should("contain", "Feedback freigegeben");
|
||||
cy.get('[data-cy="Student1"]').should("not.contain", "Punkte");
|
||||
|
||||
// clicking on results page will go to last step
|
||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||
cy.url().should("include", "step=6");
|
||||
|
||||
// load AssignmentCompletion from DB and check
|
||||
cy.loadAssignmentCompletion(
|
||||
"evaluation_user_id",
|
||||
TEST_TRAINER1_USER_ID
|
||||
).then((ac) => {
|
||||
expect(ac.completion_status).to.equal("EVALUATION_SUBMITTED");
|
||||
expect(ac.evaluation_max_points).to.equal(0);
|
||||
const completionString = JSON.stringify(ac.completion_data);
|
||||
expect(completionString).to.include("Begründung Schritt 1");
|
||||
expect(completionString).to.include("Begründung Schritt 2");
|
||||
expect(completionString).to.include("Begründung Schritt 3");
|
||||
expect(completionString).to.include("Begründung Schritt 4");
|
||||
expect(completionString).to.include("Begründung Schritt 5");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -118,6 +118,6 @@ describe("circle.cy.js", () => {
|
|||
|
||||
cy.visit("/course/test-lehrgang/learn/reisen");
|
||||
cy.get('[data-cy="lp-learning-sequence"]').should("have.length", 3);
|
||||
cy.get('[data-cy="lp-learning-content"]').should("have.length", 8);
|
||||
cy.get('[data-cy="lp-learning-content"]').should("have.length", 9);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ describe("dashboardSupervisor.cy.js", () => {
|
|||
describe("feedback summary box", () => {
|
||||
it("contains correct numbers", () => {
|
||||
getDashboardStatistics("feedback.average").should("have.text", "3.3");
|
||||
getDashboardStatistics("feedback.count").should("have.text", "3");
|
||||
getDashboardStatistics("feedback.count").should("have.text", "6");
|
||||
});
|
||||
it("contains correct details link", () => {
|
||||
clickOnDetailsLink("feedback");
|
||||
|
|
|
|||
|
|
@ -5,153 +5,359 @@ describe("feedbackStudent.cy.js", () => {
|
|||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset");
|
||||
login("test-student1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/learn/fahrzeug/feedback");
|
||||
});
|
||||
|
||||
it("can open feedback page", () => {
|
||||
cy.testLearningContentTitle("Kursfeedback");
|
||||
cy.testLearningContentSubtitle("Feedback");
|
||||
describe("Feedback UK", () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/course/test-lehrgang/learn/fahrzeug/feedback");
|
||||
});
|
||||
|
||||
it("can open feedback page", () => {
|
||||
cy.testLearningContentTitle("Kursfeedback");
|
||||
cy.testLearningContentSubtitle("Feedback");
|
||||
});
|
||||
|
||||
it("can create feedback by giving answers to all steps", () => {
|
||||
// initial wait for step 0 (or none with step==0) is required for pipelines
|
||||
cy.url().should((url) => {
|
||||
expect(url).to.match(/\/fahrzeug\/feedback(\?step=0)?$/);
|
||||
});
|
||||
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// fill feedback form
|
||||
// step 1
|
||||
cy.url().should("include", "step=1");
|
||||
cy.get('[data-cy="question-1"]').should(
|
||||
"contain",
|
||||
"Zufriedenheit insgesamt"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-4"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 2
|
||||
cy.url().should("include", "step=2");
|
||||
cy.get('[data-cy="question-2"]').should(
|
||||
"contain",
|
||||
"Zielerreichung insgesamt"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
// the system should store after every step -> check stored data
|
||||
cy.loadFeedbackResponse("feedback_user_id", TEST_STUDENT1_USER_ID).then(
|
||||
(ac) => {
|
||||
expect(ac.submitted).to.be.false;
|
||||
expect(ac.data.satisfaction).to.equal(4);
|
||||
expect(ac.data.instructor_competence).to.equal(null);
|
||||
}
|
||||
);
|
||||
cy.get('[data-cy="radio-3"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 3
|
||||
cy.url().should("include", "step=3");
|
||||
cy.get('[data-cy="question-3"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Kurs?"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-80"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 4
|
||||
cy.url().should("include", "step=4");
|
||||
cy.get('[data-cy="question-4"]').should(
|
||||
"contain",
|
||||
"Waren die Vorbereitungsaufträge klar und verständlich?"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-false"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 5
|
||||
cy.url().should("include", "step=5");
|
||||
cy.get('[data-cy="question-5"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du die Themensicherheit und Fachkompetenz des Kursleiters/der Kursleiterin?"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-2"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 6
|
||||
cy.url().should("include", "step=6");
|
||||
cy.get('[data-cy="question-6"]').should(
|
||||
"contain",
|
||||
"Wurden Fragen und Anregungen der Kursteilnehmenden ernst genommen und aufgegriffen?"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-1"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 7
|
||||
cy.url().should("include", "step=7");
|
||||
cy.get('[data-cy="question-7"]').should(
|
||||
"contain",
|
||||
"Was möchtest du dem Kursleiter/der Kursleiterin sonst noch sagen?"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-instructor_open_feedback"]').type(
|
||||
"Der Kursleiter ist eigentlich ganz nett."
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 8
|
||||
cy.url().should("include", "step=8");
|
||||
cy.get('[data-cy="question-8"]').should(
|
||||
"contain",
|
||||
"Würdest du den Kurs weiterempfehlen?"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-true"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 9
|
||||
cy.url().should("include", "step=9");
|
||||
cy.get('[data-cy="question-9"]').should(
|
||||
"contain",
|
||||
"Was hat dir besonders gut gefallen?"
|
||||
);
|
||||
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-course_positive_feedback"]').type(
|
||||
"Ich bin zufrieden mit den meisten Dingen."
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 10
|
||||
cy.url().should("include", "step=10");
|
||||
cy.get('[data-cy="question-10"]').should(
|
||||
"contain",
|
||||
"Wo siehst du Verbesserungspotential?"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-course_negative_feedback"]').type(
|
||||
"Ich bin unzufrieden mit einigen Sachen."
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
cy.url().should("include", "step=11");
|
||||
cy.get('[data-cy="sendFeedbackButton"]').click();
|
||||
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||
|
||||
// marked complete in circle
|
||||
cy.url().should((url) => {
|
||||
expect(url).to.match(/\/fahrzeug#lu-transfer?$/);
|
||||
});
|
||||
cy.reload();
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-fahrzeug-lc-feedback-checkbox"]'
|
||||
).should("have.class", "cy-checked");
|
||||
|
||||
// reopening page should get directly to last step
|
||||
cy.visit("/course/test-lehrgang/learn/fahrzeug/feedback");
|
||||
cy.url().should("include", "step=11");
|
||||
|
||||
// check stored data
|
||||
cy.loadFeedbackResponse("feedback_user_id", TEST_STUDENT1_USER_ID).then(
|
||||
(ac) => {
|
||||
expect(ac.submitted).to.be.true;
|
||||
expect(ac.data).to.deep.equal({
|
||||
course_negative_feedback: "Ich bin unzufrieden mit einigen Sachen.",
|
||||
course_positive_feedback:
|
||||
"Ich bin zufrieden mit den meisten Dingen.",
|
||||
goal_attainment: 3,
|
||||
instructor_competence: 2,
|
||||
instructor_open_feedback:
|
||||
"Der Kursleiter ist eigentlich ganz nett.",
|
||||
instructor_respect: 1,
|
||||
preparation_task_clarity: false,
|
||||
proficiency: 80,
|
||||
satisfaction: 4,
|
||||
would_recommend: true,
|
||||
feedback_type: "uk",
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("can create feedback by giving answers to all steps", () => {
|
||||
// initial wait for step 0 (or none with step==0) is required for pipelines
|
||||
cy.url().should((url) => {
|
||||
expect(url).to.match(/\/fahrzeug\/feedback(\?step=0)?$/);
|
||||
describe("Feedback VV", () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/course/test-lehrgang/learn/reisen/feedback");
|
||||
});
|
||||
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// fill feedback form
|
||||
// step 1
|
||||
cy.url().should("include", "step=1");
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-4"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 2
|
||||
cy.url().should("include", "step=2");
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
// the system should store after every step -> check stored data
|
||||
cy.loadFeedbackResponse("feedback_user_id", TEST_STUDENT1_USER_ID).then(
|
||||
(ac) => {
|
||||
expect(ac.submitted).to.be.false;
|
||||
expect(ac.data.satisfaction).to.equal(4);
|
||||
expect(ac.data.instructor_competence).to.equal(null);
|
||||
}
|
||||
);
|
||||
cy.get('[data-cy="radio-3"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 3
|
||||
cy.url().should("include", "step=3");
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-80"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 4
|
||||
cy.url().should("include", "step=4");
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-false"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 5
|
||||
cy.url().should("include", "step=5");
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-2"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 6
|
||||
cy.url().should("include", "step=6");
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-1"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 7
|
||||
cy.url().should("include", "step=7");
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-instructor_open_feedback"]').type(
|
||||
"Der Kursleiter ist eigentlich ganz nett."
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 8
|
||||
cy.url().should("include", "step=8");
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-true"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 9
|
||||
cy.url().should("include", "step=9");
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-course_positive_feedback"]').type(
|
||||
"Ich bin zufrieden mit den meisten Dingen."
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 10
|
||||
cy.url().should("include", "step=10");
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-course_negative_feedback"]').type(
|
||||
"Ich bin unzufrieden mit einigen Sachen."
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
cy.url().should("include", "step=11");
|
||||
cy.get('[data-cy="sendFeedbackButton"]').click();
|
||||
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||
|
||||
// marked complete in circle
|
||||
cy.url().should((url) => {
|
||||
expect(url).to.match(/\/fahrzeug#lu-transfer?$/);
|
||||
it("can open feedback page", () => {
|
||||
cy.testLearningContentTitle("Feedback");
|
||||
cy.testLearningContentSubtitle("Feedback");
|
||||
});
|
||||
cy.reload();
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-fahrzeug-lc-feedback-checkbox"]'
|
||||
).should("have.class", "cy-checked");
|
||||
|
||||
// reopening page should get directly to last step
|
||||
cy.visit("/course/test-lehrgang/learn/fahrzeug/feedback");
|
||||
cy.url().should("include", "step=11");
|
||||
it("can create feedback by giving answers to all steps", () => {
|
||||
// initial wait for step 0 (or none with step==0) is required for pipelines
|
||||
cy.url().should((url) => {
|
||||
expect(url).to.match(/\/reisen\/feedback(\?step=0)?$/);
|
||||
});
|
||||
cy.get('[data-cy="introduction"]').contains(
|
||||
"Wir bitten dich um dein Feedback. Es hilft uns, damit wir deine Lernerlebnisse verbessern können."
|
||||
);
|
||||
|
||||
// check stored data
|
||||
cy.loadFeedbackResponse("feedback_user_id", TEST_STUDENT1_USER_ID).then(
|
||||
(ac) => {
|
||||
expect(ac.submitted).to.be.true;
|
||||
expect(ac.data).to.deep.equal({
|
||||
course_negative_feedback: "Ich bin unzufrieden mit einigen Sachen.",
|
||||
course_positive_feedback: "Ich bin zufrieden mit den meisten Dingen.",
|
||||
goal_attainment: 3,
|
||||
instructor_competence: 2,
|
||||
instructor_open_feedback: "Der Kursleiter ist eigentlich ganz nett.",
|
||||
instructor_respect: 1,
|
||||
preparation_task_clarity: false,
|
||||
proficiency: 80,
|
||||
satisfaction: 4,
|
||||
would_recommend: true,
|
||||
});
|
||||
}
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// fill feedback form
|
||||
// step 1
|
||||
cy.url().should("include", "step=1");
|
||||
cy.get('[data-cy="question-1"]').should(
|
||||
"contain",
|
||||
"Zufriedenheit insgesamt"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-4"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 2
|
||||
cy.url().should("include", "step=2");
|
||||
cy.get('[data-cy="question-2"]').should(
|
||||
"contain",
|
||||
"Zielerreichung insgesamt"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
// the system should store after every step -> check stored data
|
||||
cy.loadFeedbackResponse("feedback_user_id", TEST_STUDENT1_USER_ID).then(
|
||||
(ac) => {
|
||||
expect(ac.submitted).to.be.false;
|
||||
expect(ac.data.satisfaction).to.equal(4);
|
||||
expect(ac.data.course_positive_feedback).to.equal(null);
|
||||
}
|
||||
);
|
||||
cy.get('[data-cy="radio-3"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 3
|
||||
cy.url().should("include", "step=3");
|
||||
cy.get('[data-cy="question-3"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Circle?"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-80"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 4
|
||||
cy.url().should("include", "step=4");
|
||||
cy.get('[data-cy="question-4"]').should(
|
||||
"contain",
|
||||
"Waren die Praxisaufträge klar und verständlich?"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-false"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 5
|
||||
cy.url().should("include", "step=5");
|
||||
cy.get('[data-cy="question-5"]').should(
|
||||
"contain",
|
||||
"Würdest du den Circle weiterempfehlen?"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-false"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 6
|
||||
cy.url().should("include", "step=6");
|
||||
cy.get('[data-cy="question-6"]').should(
|
||||
"contain",
|
||||
"Was hat dir besonders gut gefallen?"
|
||||
);
|
||||
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-course_positive_feedback"]').type(
|
||||
"Der Circle ist eigentlich ganz nett."
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 7
|
||||
cy.url().should("include", "step=7");
|
||||
cy.get('[data-cy="question-7"]').should(
|
||||
"contain",
|
||||
"Wo siehst du Verbesserungspotential?"
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-course_negative_feedback"]').type(
|
||||
"Ich bin unzufrieden mit einigen Sachen."
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
cy.url().should("include", "step=8");
|
||||
cy.get('[data-cy="sendFeedbackButton"]').click();
|
||||
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||
|
||||
// marked complete in circle
|
||||
cy.url().should((url) => {
|
||||
expect(url).to.match(/\/reisen#lu-transfer-reflexion-feedback?$/);
|
||||
});
|
||||
cy.reload();
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-reisen-lc-feedback-checkbox"]'
|
||||
).should("have.class", "cy-checked");
|
||||
|
||||
// reopening page should get directly to last step
|
||||
cy.visit("/course/test-lehrgang/learn/reisen/feedback");
|
||||
cy.url().should("include", "step=8");
|
||||
|
||||
// check stored data
|
||||
cy.loadFeedbackResponse("feedback_user_id", TEST_STUDENT1_USER_ID).then(
|
||||
(ac) => {
|
||||
expect(ac.submitted).to.be.true;
|
||||
expect(ac.data).to.deep.equal({
|
||||
course_negative_feedback: "Ich bin unzufrieden mit einigen Sachen.",
|
||||
course_positive_feedback: "Der Circle ist eigentlich ganz nett.",
|
||||
goal_attainment: 3,
|
||||
preparation_task_clarity: false,
|
||||
proficiency: 80,
|
||||
satisfaction: 4,
|
||||
would_recommend: false,
|
||||
feedback_type: "vv",
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,77 +16,216 @@ describe("feedbackTrainer.cy.js", () => {
|
|||
cy.get('[data-cy="feedback-data-amount"]').should("contain", "0");
|
||||
});
|
||||
|
||||
it("can open feedback results page with results", () => {
|
||||
cy.manageCommand("cypress_reset --create-feedback-responses");
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get(
|
||||
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-fahrzeug-lc-feedback"]'
|
||||
).click();
|
||||
describe("FeedbackUK", function () {
|
||||
it("can open feedback results page with results", () => {
|
||||
cy.manageCommand("cypress_reset --create-feedback-responses");
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get(
|
||||
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-fahrzeug-lc-feedback"]'
|
||||
).click();
|
||||
|
||||
cy.get('[data-cy="feedback-data-amount"]').should("contain", "3");
|
||||
cy.get('[data-cy="feedback-data-amount"]').should("contain", "3");
|
||||
|
||||
cy.get('[data-cy="question-1"]')
|
||||
.find('[data-cy="rating-scale-average"]')
|
||||
.should("contain", "3.3");
|
||||
// check titles of questions
|
||||
cy.get('[data-cy="question-1"]').should(
|
||||
"contain",
|
||||
"Zufriedenheit insgesamt"
|
||||
);
|
||||
cy.get('[data-cy="question-2"]').should(
|
||||
"contain",
|
||||
"Zielerreichung insgesamt"
|
||||
);
|
||||
cy.get('[data-cy="question-3"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Kurs?"
|
||||
);
|
||||
cy.get('[data-cy="question-4"]').should(
|
||||
"contain",
|
||||
"Waren die Vorbereitungsaufträge klar und verständlich?"
|
||||
);
|
||||
cy.get('[data-cy="question-5"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du die Themensicherheit und Fachkompetenz des Kursleiters/der Kursleiterin?"
|
||||
);
|
||||
cy.get('[data-cy="question-6"]').should(
|
||||
"contain",
|
||||
"Wurden Fragen und Anregungen der Kursteilnehmenden ernst genommen und aufgegriffen?"
|
||||
);
|
||||
cy.get('[data-cy="question-7"]').should(
|
||||
"contain",
|
||||
"Was möchtest du dem Kursleiter/der Kursleiterin sonst noch sagen?"
|
||||
);
|
||||
cy.get('[data-cy="question-8"]').should(
|
||||
"contain",
|
||||
"Würdest du den Kurs weiterempfehlen?"
|
||||
);
|
||||
cy.get('[data-cy="question-9"]').should(
|
||||
"contain",
|
||||
"Wo siehst du Verbesserungspotential?"
|
||||
);
|
||||
cy.get('[data-cy="question-10"]').should(
|
||||
"contain",
|
||||
"Was hat dir besonders gut gefallen?"
|
||||
);
|
||||
|
||||
cy.get('[data-cy="question-2"]')
|
||||
.find('[data-cy="rating-scale-average"]')
|
||||
.should("contain", "3.0");
|
||||
cy.get('[data-cy="question-1"]')
|
||||
.find('[data-cy="rating-scale-average"]')
|
||||
.should("contain", "3.3");
|
||||
|
||||
cy.get('[data-cy="question-3"]')
|
||||
.find('[data-cy="percentage-value-40%"]')
|
||||
.should("contain", "33.3");
|
||||
cy.get('[data-cy="question-3"]')
|
||||
.find('[data-cy="percentage-value-80%"]')
|
||||
.should("contain", "33.3");
|
||||
cy.get('[data-cy="question-3"]')
|
||||
.find('[data-cy="percentage-value-100%"]')
|
||||
.should("contain", "33.3");
|
||||
cy.get('[data-cy="question-2"]')
|
||||
.find('[data-cy="rating-scale-average"]')
|
||||
.should("contain", "3.0");
|
||||
|
||||
cy.get('[data-cy="question-4"]')
|
||||
.find('[data-cy="popover-yes"]')
|
||||
.click()
|
||||
.find('[data-cy="num-yes"]')
|
||||
.should("contain", "3");
|
||||
cy.get('[data-cy="question-4"]')
|
||||
.find('[data-cy="popover-no"]')
|
||||
.click()
|
||||
.find('[data-cy="num-no"]')
|
||||
.should("contain", "0");
|
||||
cy.get('[data-cy="question-3"]')
|
||||
.find('[data-cy="percentage-value-40%"]')
|
||||
.should("contain", "33.3");
|
||||
cy.get('[data-cy="question-3"]')
|
||||
.find('[data-cy="percentage-value-80%"]')
|
||||
.should("contain", "33.3");
|
||||
cy.get('[data-cy="question-3"]')
|
||||
.find('[data-cy="percentage-value-100%"]')
|
||||
.should("contain", "33.3");
|
||||
|
||||
cy.get('[data-cy="question-5"]')
|
||||
.find('[data-cy="rating-scale-average"]')
|
||||
.should("contain", "2.7");
|
||||
cy.get('[data-cy="question-4"]')
|
||||
.find('[data-cy="popover-yes"]')
|
||||
.click()
|
||||
.find('[data-cy="num-yes"]')
|
||||
.should("contain", "3");
|
||||
cy.get('[data-cy="question-4"]')
|
||||
.find('[data-cy="popover-no"]')
|
||||
.click()
|
||||
.find('[data-cy="num-no"]')
|
||||
.should("contain", "0");
|
||||
|
||||
cy.get('[data-cy="question-6"]')
|
||||
.find('[data-cy="rating-scale-average"]')
|
||||
.should("contain", "3.0");
|
||||
cy.get('[data-cy="question-5"]')
|
||||
.find('[data-cy="rating-scale-average"]')
|
||||
.should("contain", "2.7");
|
||||
|
||||
cy.get('[data-cy="question-7"]')
|
||||
.should("contain", "Super Kurs!")
|
||||
.should("contain", "Super, bin begeistert")
|
||||
.should("contain", "Ok, entspricht den Erwartungen");
|
||||
cy.get('[data-cy="question-6"]')
|
||||
.find('[data-cy="rating-scale-average"]')
|
||||
.should("contain", "3.0");
|
||||
|
||||
cy.get('[data-cy="question-8"]')
|
||||
.find('[data-cy="popover-yes"]')
|
||||
.click()
|
||||
.find('[data-cy="num-yes"]')
|
||||
.should("contain", "2");
|
||||
cy.get('[data-cy="question-8"]')
|
||||
.find('[data-cy="popover-no"]')
|
||||
.click()
|
||||
.find('[data-cy="num-no"]')
|
||||
.should("contain", "1");
|
||||
cy.get('[data-cy="question-7"]')
|
||||
.should("contain", "Super Kurs!")
|
||||
.should("contain", "Super, bin begeistert")
|
||||
.should("contain", "Ok, entspricht den Erwartungen");
|
||||
|
||||
cy.get('[data-cy="question-9"]')
|
||||
.should("contain", "Nichts Schlechtes")
|
||||
.should("contain", "Es wäre praktisch, Zugang zu einer FAQ zu haben.")
|
||||
.should("contain", "Mehr Videos wären schön.");
|
||||
cy.get('[data-cy="question-8"]')
|
||||
.find('[data-cy="popover-yes"]')
|
||||
.click()
|
||||
.find('[data-cy="num-yes"]')
|
||||
.should("contain", "2");
|
||||
cy.get('[data-cy="question-8"]')
|
||||
.find('[data-cy="popover-no"]')
|
||||
.click()
|
||||
.find('[data-cy="num-no"]')
|
||||
.should("contain", "1");
|
||||
|
||||
cy.get('[data-cy="question-10"]')
|
||||
.should("contain", "Nur Gutes.")
|
||||
.should("contain", "Das Beispiel mit der Katze fand ich sehr gut")
|
||||
.should("contain", "Die Präsentation war super");
|
||||
cy.get('[data-cy="question-9"]')
|
||||
.should("contain", "Nichts Schlechtes")
|
||||
.should("contain", "Es wäre praktisch, Zugang zu einer FAQ zu haben.")
|
||||
.should("contain", "Mehr Videos wären schön.");
|
||||
|
||||
cy.get('[data-cy="question-10"]')
|
||||
.should("contain", "Nur Gutes.")
|
||||
.should("contain", "Das Beispiel mit der Katze fand ich sehr gut")
|
||||
.should("contain", "Die Präsentation war super");
|
||||
});
|
||||
});
|
||||
|
||||
describe("FeedbackVV", function () {
|
||||
it("can open feedback results page with results", () => {
|
||||
cy.manageCommand("cypress_reset --create-feedback-responses");
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get('[data-cy="dropdown-select"]').click();
|
||||
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
||||
cy.get(
|
||||
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-reisen-lc-feedback"]'
|
||||
).click();
|
||||
|
||||
cy.get('[data-cy="feedback-data-amount"]').should("contain", "3");
|
||||
|
||||
// check titles of questions
|
||||
cy.get('[data-cy="question-1"]').should(
|
||||
"contain",
|
||||
"Zufriedenheit insgesamt"
|
||||
);
|
||||
cy.get('[data-cy="question-2"]').should(
|
||||
"contain",
|
||||
"Zielerreichung insgesamt"
|
||||
);
|
||||
cy.get('[data-cy="question-3"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Circle?"
|
||||
);
|
||||
cy.get('[data-cy="question-4"]').should(
|
||||
"contain",
|
||||
"Waren die Praxisaufträge klar und verständlich?"
|
||||
);
|
||||
cy.get('[data-cy="question-5"]').should(
|
||||
"contain",
|
||||
"Würdest du den Circle weiterempfehlen?"
|
||||
);
|
||||
cy.get('[data-cy="question-6"]').should(
|
||||
"contain",
|
||||
"Wo siehst du Verbesserungspotential?"
|
||||
);
|
||||
cy.get('[data-cy="question-7"]').should(
|
||||
"contain",
|
||||
"Was hat dir besonders gut gefallen?"
|
||||
);
|
||||
|
||||
cy.get('[data-cy="question-1"]')
|
||||
.find('[data-cy="rating-scale-average"]')
|
||||
.should("contain", "3.3");
|
||||
|
||||
cy.get('[data-cy="question-2"]')
|
||||
.find('[data-cy="rating-scale-average"]')
|
||||
.should("contain", "3.0");
|
||||
|
||||
cy.get('[data-cy="question-3"]')
|
||||
.find('[data-cy="percentage-value-40%"]')
|
||||
.should("contain", "33.3");
|
||||
cy.get('[data-cy="question-3"]')
|
||||
.find('[data-cy="percentage-value-80%"]')
|
||||
.should("contain", "33.3");
|
||||
cy.get('[data-cy="question-3"]')
|
||||
.find('[data-cy="percentage-value-100%"]')
|
||||
.should("contain", "33.3");
|
||||
|
||||
cy.get('[data-cy="question-4"]')
|
||||
.find('[data-cy="popover-yes"]')
|
||||
.click()
|
||||
.find('[data-cy="num-yes"]')
|
||||
.should("contain", "3");
|
||||
cy.get('[data-cy="question-4"]')
|
||||
.find('[data-cy="popover-no"]')
|
||||
.click()
|
||||
.find('[data-cy="num-no"]')
|
||||
.should("contain", "0");
|
||||
|
||||
cy.get('[data-cy="question-5"]')
|
||||
.find('[data-cy="popover-yes"]')
|
||||
.click()
|
||||
.find('[data-cy="num-yes"]')
|
||||
.should("contain", "2");
|
||||
cy.get('[data-cy="question-5"]')
|
||||
.find('[data-cy="popover-no"]')
|
||||
.click()
|
||||
.find('[data-cy="num-no"]')
|
||||
.should("contain", "1");
|
||||
|
||||
cy.get('[data-cy="question-6"]')
|
||||
.should("contain", "Nichts Schlechtes")
|
||||
.should("contain", "Es wäre praktisch, Zugang zu einer FAQ zu haben.")
|
||||
.should("contain", "Mehr Videos wären schön.");
|
||||
|
||||
cy.get('[data-cy="question-7"]')
|
||||
.should("contain", "Nur Gutes.")
|
||||
.should("contain", "Das Beispiel mit der Katze fand ich sehr gut")
|
||||
.should("contain", "Die Präsentation war super");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
import { login } from "./helpers";
|
||||
|
||||
describe("settings.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset");
|
||||
});
|
||||
|
||||
describe("with circle documents enabled", () => {
|
||||
it("student can see circle documents", () => {
|
||||
login("test-student1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/learn/fahrzeug");
|
||||
cy.get('[data-cy="circle-document-section"]').should("exist");
|
||||
});
|
||||
|
||||
it("trainer can see circle documents", () => {
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get('[data-cy="circle-documents"]').should("exist");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with circle documents disabled", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset --no-enable-circle-documents");
|
||||
});
|
||||
|
||||
it("student cannot see circle documents", () => {
|
||||
login("test-student1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/learn/fahrzeug");
|
||||
cy.get('[data-cy="circle-title"]').should("contain", "Fahrzeug");
|
||||
cy.get('[data-cy="circle-document-section"]').should("not.exist");
|
||||
});
|
||||
|
||||
it("trainer cannot see circle documents", () => {
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/cockpit");
|
||||
cy.get('[data-cy="circle-documents"]').should("not.exist");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -60,6 +60,8 @@ Cypress.Commands.add("manageCommand", (command, preCommand = "") => {
|
|||
let pythonPaths = [
|
||||
"/Users/daniel/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin",
|
||||
"/Users/eliabieri/iterativ/vbv_lernwelt/.direnv/python-3.10.6/bin",
|
||||
"/Users/christiancueni/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin",
|
||||
"/Users/renzo/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin",
|
||||
];
|
||||
let bashCommand = `PATH=${pythonPaths.join(":")}:$PATH && ${execCommand}`;
|
||||
return cy
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
|
|
@ -0,0 +1,73 @@
|
|||
# Files handling
|
||||
|
||||
This document describes how files are handled in this appication.
|
||||
|
||||
# Types of files
|
||||
|
||||
static files: files that are not changed by the application, e.g. images, fonts, etc.¨
|
||||
|
||||
### content documents:
|
||||
|
||||
Files that belong to the content and are managed by the content editors in the CMS (pdf, excel, word, etc.)
|
||||
|
||||
### user documents:
|
||||
|
||||
Files that are uploaded by the users (pdf, etc.). Therefore not visible in the CMS.
|
||||
Images are handled seprately from documents since images require additional processing (resizing, cropping, etc.).
|
||||
Visible in the django admin.
|
||||
|
||||
### content images:
|
||||
|
||||
Images that belong to the content and are managed by the content editors in the CMS.
|
||||
|
||||
### user images:
|
||||
|
||||
Images that are uploaded by the users. Therefore not visible in the CMS. Visible in the django admin.
|
||||
|
||||
## Static files
|
||||
|
||||
These files are publicly served on S3.
|
||||
|
||||
## Content documents
|
||||
|
||||
These files are part of the content. Such as a pdf thas cointains additional information to a course.
|
||||
These files are not publicly available. The content files are uploaded by the editors in the wagtail cms.
|
||||
|
||||
https://www.hacksoft.io/blog/direct-to-s3-file-upload-with-django
|
||||
|
||||
Django handles the permissions to these files. Via a view django checks if the user has permissions to access the file,
|
||||
and gerates a temporary url that is valid for a limited time. Still the documents are served by django. This done for
|
||||
usability reasons. The user sees the url mydomain.com/media/documents/<document-id> and not a url to S3. Therefore the
|
||||
user can share the url with other users. (still they need to login and have the permissions to access the file)
|
||||
|
||||
The downside of this is that the django server processes these files. (could be circumvented by django-sendfile).
|
||||
|
||||

|
||||
|
||||
- These Files are handled stored as wagtail documents. As a model and the file itself is stored in S3.
|
||||
|
||||
### Frontend access to content documents
|
||||
|
||||
For the frontend django generates a fixed url per file /media/documents/<document-id>
|
||||
|
||||
When the frontend requests this file, django checks if the user has permissions to access the file.
|
||||
If so, django generates a temporary url that is valid for a limited time. Then sends a redirect to the frontend.
|
||||
|
||||
In this waz the frontend does not need to know about the permissions. Content grapql can be cached if needed and urls
|
||||
can be shared by the users.
|
||||
|
||||
content_documents
|
||||
user_documents
|
||||
|
||||
public files
|
||||
|
||||
## User documents
|
||||
|
||||
- User uploaded files are stored in S3. but the permissions is handled by django. Same process as content files.
|
||||
|
||||
Same process as content files. But the url is /media/user-uploads/<file-id>
|
||||
And the files are not managed by Wagtail. Due to another model, they are not visible to the user in the CMS.
|
||||
|
||||
## Content images
|
||||
|
||||
Content Images are served directly from S3. The permissions are handled by dja
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -18,8 +18,7 @@ def main():
|
|||
from django.conf import settings
|
||||
|
||||
settings.DEBUG = True
|
||||
from django.db import connection
|
||||
from django.db import reset_queries
|
||||
from django.db import connection, reset_queries
|
||||
|
||||
reset_queries()
|
||||
|
||||
|
|
|
|||
|
|
@ -12,16 +12,15 @@ os.environ.setdefault("IT_APP_ENVIRONMENT", "local")
|
|||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.base")
|
||||
django.setup()
|
||||
|
||||
from vbv_lernwelt.core.schema import Query
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.core.schema import Query
|
||||
|
||||
|
||||
def main():
|
||||
from django.conf import settings
|
||||
|
||||
settings.DEBUG = True
|
||||
from django.db import connection
|
||||
from django.db import reset_queries
|
||||
from django.db import connection, reset_queries
|
||||
|
||||
reset_queries()
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ os.environ.setdefault("IT_APP_ENVIRONMENT", "local")
|
|||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.base")
|
||||
django.setup()
|
||||
|
||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||
from vbv_lernwelt.notify.email.email_services import (
|
||||
create_template_data_from_course_session_attendance_course,
|
||||
EmailTemplate,
|
||||
send_email,
|
||||
create_template_data_from_course_session_attendance_course,
|
||||
)
|
||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ THIRD_PARTY_APPS = [
|
|||
|
||||
LOCAL_APPS = [
|
||||
"vbv_lernwelt.core",
|
||||
"vbv_lernwelt.media_files",
|
||||
"vbv_lernwelt.sso",
|
||||
"vbv_lernwelt.course",
|
||||
"vbv_lernwelt.learnpath",
|
||||
|
|
@ -212,23 +213,13 @@ STATICFILES_FINDERS = [
|
|||
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
||||
]
|
||||
|
||||
USE_AWS = env("USE_AWS", False)
|
||||
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME", "")
|
||||
AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID", "")
|
||||
AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY", "")
|
||||
AWS_S3_CUSTOM_DOMAIN = "%s.s3.amazonaws.com" % AWS_STORAGE_BUCKET_NAME
|
||||
|
||||
# MEDIA
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#media-root
|
||||
MEDIA_ROOT = str(APPS_DIR / "media")
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#media-url
|
||||
if USE_AWS:
|
||||
# https://wagtail.org/blog/amazon-s3-for-media-files/
|
||||
MEDIA_URL = "https://%s/" % AWS_S3_CUSTOM_DOMAIN
|
||||
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
|
||||
else:
|
||||
MEDIA_URL = "/server/media/"
|
||||
|
||||
MEDIA_URL = "/server/media/"
|
||||
|
||||
IT_SERVE_VUE = env.bool("IT_SERVE_VUE", DEBUG)
|
||||
IT_SERVE_VUE_URL = env("IT_SERVE_VUE_URL", "http://localhost:5173")
|
||||
|
|
@ -252,7 +243,19 @@ WAGTAIL_ENABLE_UPDATE_CHECK = False
|
|||
WAGTAIL_ENABLE_WHATS_NEW_BANNER = False
|
||||
WAGTAIL_CONTENT_LANGUAGES = LANGUAGES
|
||||
|
||||
WAGTAILDOCS_DOCUMENT_MODEL = "media_library.LibraryDocument"
|
||||
WAGTAILDOCS_DOCUMENT_MODEL = "media_files.ContentDocument"
|
||||
WAGTAILIMAGES_IMAGE_MODEL = "media_files.ContentImage"
|
||||
|
||||
# this setting makes that the document is served by django, and the url is the django url.
|
||||
# https://docs.wagtail.org/en/stable/reference/settings.html#wagtaildocs-serve-method
|
||||
# The file is served by django as streaming response. If it should be serverd by nginx, then install django sendfile
|
||||
WAGTAILDOCS_SERVE_METHOD = "serve_view"
|
||||
# WAGTAILDOCS_INLINE_CONTENT_TYPES = ['application/pdf', 'text/plain']
|
||||
|
||||
|
||||
WAGTAILIMAGES_MAX_UPLOAD_SIZE = 20 * 1024 * 1024 # 20MB
|
||||
# WAGTAILIMAGES_RENDITION_STORAGE = 'myapp.backends.MyCustomStorage'
|
||||
|
||||
|
||||
WAGTAILADMIN_RICH_TEXT_EDITORS = {
|
||||
"default": {
|
||||
|
|
@ -645,7 +648,7 @@ NOTIFICATIONS_NOTIFICATION_MODEL = "notify.Notification"
|
|||
SENDGRID_API_KEY = env("IT_SENDGRID_API_KEY", default="")
|
||||
|
||||
# S3 BUCKET CONFIGURATION
|
||||
FILE_UPLOAD_STORAGE = env("FILE_UPLOAD_STORAGE", default="local") # local | s3
|
||||
FILE_UPLOAD_STORAGE = env("FILE_UPLOAD_STORAGE", default="s3") # local | s3
|
||||
|
||||
if FILE_UPLOAD_STORAGE == "local":
|
||||
FILE_MAX_SIZE = env.int("FILE_MAX_SIZE", default=5242880)
|
||||
|
|
@ -654,18 +657,19 @@ if FILE_UPLOAD_STORAGE == "s3":
|
|||
# Using django-storages
|
||||
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html
|
||||
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
|
||||
|
||||
AWS_S3_ACCESS_KEY_ID = env("AWS_S3_ACCESS_KEY_ID")
|
||||
AWS_S3_ACCESS_KEY_ID = env("AWS_S3_ACCESS_KEY_ID", default="AKIAZJLREPUVWNBTJ5VY")
|
||||
AWS_S3_SECRET_ACCESS_KEY = env("AWS_S3_SECRET_ACCESS_KEY")
|
||||
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")
|
||||
AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME")
|
||||
AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME", "eu-central-1")
|
||||
AWS_S3_SIGNATURE_VERSION = env("AWS_S3_SIGNATURE_VERSION", default="s3v4")
|
||||
AWS_STORAGE_BUCKET_NAME = env(
|
||||
"AWS_STORAGE_BUCKET_NAME", default="myvbv-dev.iterativ.ch"
|
||||
)
|
||||
AWS_S3_FILE_OVERWRITE = env("AWS_S3_FILE_OVERWRITE", False)
|
||||
FILE_MAX_SIZE = env.int("FILE_MAX_SIZE", default=20971520) # 20MB
|
||||
|
||||
# https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl
|
||||
AWS_DEFAULT_ACL = env("AWS_DEFAULT_ACL", default="private")
|
||||
|
||||
AWS_PRESIGNED_EXPIRY = env.int("AWS_PRESIGNED_EXPIRY", default=300) # seconds
|
||||
AWS_PRESIGNED_EXPIRY = env.int("AWS_PRESIGNED_EXPIRY", default=7200) # seconds
|
||||
|
||||
WHITENOISE_SKIP_COMPRESS_EXTENSIONS = (
|
||||
"jpg",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# pylint: disable=unused-wildcard-import,wildcard-import,wrong-import-position
|
||||
import os
|
||||
|
||||
|
||||
os.environ["IT_APP_ENVIRONMENT"] = "local"
|
||||
|
||||
from .base import * # noqa
|
||||
|
|
@ -8,6 +9,7 @@ from .base import * # noqa
|
|||
# https://docs.djangoproject.com/en/dev/ref/settings/#test-runner
|
||||
TEST_RUNNER = "django.test.runner.DiscoverRunner"
|
||||
|
||||
# Select faster password hasher during tests
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
|
||||
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
||||
|
||||
|
|
@ -15,16 +17,7 @@ PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
|||
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
|
||||
|
||||
WHITENOISE_MANIFEST_STRICT = False
|
||||
|
||||
# Dummy data
|
||||
AWS_S3_ACCESS_KEY_ID = "SOMEKEY"
|
||||
AWS_S3_SECRET_ACCESS_KEY = "SOMEACCESSKEY"
|
||||
AWS_STORAGE_BUCKET_NAME = "myvbv-dev.iterativ.ch"
|
||||
AWS_S3_REGION_NAME = "eu-central-1"
|
||||
AWS_S3_SIGNATURE_VERSION = "s3v4"
|
||||
FILE_MAX_SIZE = 20971520 # 20MB
|
||||
AWS_DEFAULT_ACL = "private"
|
||||
AWS_PRESIGNED_EXPIRY = 300
|
||||
AWS_S3_FILE_OVERWRITE = True
|
||||
|
||||
|
||||
class DisableMigrations(dict):
|
||||
|
|
@ -36,8 +29,3 @@ class DisableMigrations(dict):
|
|||
|
||||
|
||||
MIGRATION_MODULES = DisableMigrations()
|
||||
|
||||
# Select faster password hasher during tests
|
||||
PASSWORD_HASHERS = [
|
||||
"django.contrib.auth.hashers.MD5PasswordHasher",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
import os
|
||||
|
||||
os.environ["IT_APP_ENVIRONMENT"] = "local"
|
||||
os.environ["AWS_S3_SECRET_ACCESS_KEY"] = os.environ.get(
|
||||
"AWS_S3_SECRET_ACCESS_KEY",
|
||||
"!!!default_for_quieting_cypress_within_pycharm!!!",
|
||||
)
|
||||
|
||||
from .base import * # noqa
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ from vbv_lernwelt.importer.views import (
|
|||
from vbv_lernwelt.notify.views import email_notification_settings
|
||||
from wagtail import urls as wagtail_urls
|
||||
from wagtail.admin import urls as wagtailadmin_urls
|
||||
from wagtail.documents import urls as wagtaildocs_urls
|
||||
from wagtail.documents import urls as media_library_urls
|
||||
|
||||
|
||||
class SignedIntConverter(IntConverter):
|
||||
|
|
@ -88,7 +88,7 @@ urlpatterns = [
|
|||
|
||||
# wagtail urls
|
||||
path('server/cms/', include(wagtailadmin_urls)),
|
||||
path('server/documents/', include(wagtaildocs_urls)),
|
||||
path('server/documents/', include(media_library_urls)),
|
||||
path('server/pages/', include(wagtail_urls)),
|
||||
|
||||
# core
|
||||
|
|
@ -132,6 +132,7 @@ urlpatterns = [
|
|||
name="request_assignment_completion_status"),
|
||||
|
||||
# documents
|
||||
# TODO: remfactor to files app
|
||||
path(r'api/core/document/start/', document_upload_start,
|
||||
name='file_upload_start'),
|
||||
path(r'api/core/document/<str:document_id>/', document_delete,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ mypy # https://github.com/python/mypy
|
|||
django-stubs # https://github.com/typeddjango/django-stubs
|
||||
pytest # https://github.com/pytest-dev/pytest
|
||||
pytest-sugar # https://github.com/Frozenball/pytest-sugar
|
||||
pytest-xdist #
|
||||
djangorestframework-stubs # https://github.com/typeddjango/djangorestframework-stubs
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,9 +38,7 @@ azure-core==1.29.1
|
|||
azure-identity==1.14.0
|
||||
# via -r requirements.in
|
||||
azure-storage-blob==12.17.0
|
||||
# via
|
||||
# -r requirements.in
|
||||
# django-storages
|
||||
# via -r requirements.in
|
||||
backcall==0.2.0
|
||||
# via ipython
|
||||
bcrypt==4.0.1
|
||||
|
|
@ -176,7 +174,7 @@ django-ratelimit==4.1.0
|
|||
# via -r requirements.in
|
||||
django-redis==5.3.0
|
||||
# via -r requirements.in
|
||||
django-storages[azure]==1.13.2
|
||||
django-storages==1.13.2
|
||||
# via -r requirements.in
|
||||
django-stubs==4.2.3
|
||||
# via
|
||||
|
|
@ -209,6 +207,8 @@ exceptiongroup==1.1.2
|
|||
# via
|
||||
# anyio
|
||||
# pytest
|
||||
execnet==2.0.2
|
||||
# via pytest-xdist
|
||||
executing==1.2.0
|
||||
# via stack-data
|
||||
factory-boy==3.3.0
|
||||
|
|
@ -397,7 +397,9 @@ pyflakes==3.1.0
|
|||
pygments==2.16.1
|
||||
# via ipython
|
||||
pyjwt[crypto]==2.8.0
|
||||
# via msal
|
||||
# via
|
||||
# msal
|
||||
# pyjwt
|
||||
pylint==2.17.5
|
||||
# via
|
||||
# pylint-django
|
||||
|
|
@ -415,10 +417,13 @@ pytest==7.4.0
|
|||
# -r requirements-dev.in
|
||||
# pytest-django
|
||||
# pytest-sugar
|
||||
# pytest-xdist
|
||||
pytest-django==4.5.2
|
||||
# via -r requirements-dev.in
|
||||
pytest-sugar==0.9.7
|
||||
# via -r requirements-dev.in
|
||||
pytest-xdist==3.5.0
|
||||
# via -r requirements-dev.in
|
||||
python-dateutil==2.8.2
|
||||
# via
|
||||
# -r requirements.in
|
||||
|
|
@ -616,7 +621,9 @@ wheel==0.41.1
|
|||
whitenoise[brotli]==6.5.0
|
||||
# via -r requirements.in
|
||||
willow[heif]==1.6.1
|
||||
# via wagtail
|
||||
# via
|
||||
# wagtail
|
||||
# willow
|
||||
wrapt==1.15.0
|
||||
# via astroid
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
pytest --junitxml=../test-reports/coverage.xml
|
||||
|
||||
# limit test to 6 parallel processes, otherwise ratelimit of s3 could be hit
|
||||
pytest --numprocesses auto --maxprocesses=6 --junitxml=../test-reports/coverage.xml
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
set -e
|
||||
|
||||
cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
coverage run -m pytest --junitxml=../test-reports/coverage.xml $1
|
||||
coverage run -m pytest --numprocesses auto --maxprocesses=6 --junitxml=../test-reports/coverage.xml $1
|
||||
|
||||
coverage_python=`coverage report -m | tail -n1 | awk '{print $4}'`
|
||||
commit=`git rev-parse HEAD`
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from vbv_lernwelt.course.consts import (
|
|||
COURSE_VERSICHERUNGSVERMITTLERIN_ID,
|
||||
)
|
||||
from vbv_lernwelt.course.models import CoursePage
|
||||
from vbv_lernwelt.media_files.models import ContentDocument
|
||||
from wagtail.blocks import StreamValue
|
||||
from wagtail.blocks.list_block import ListBlock, ListValue
|
||||
from wagtail.rich_text import RichText
|
||||
|
|
@ -39,6 +40,7 @@ def create_uk_fahrzeug_casework(course_id=COURSE_UK, competence_certificate=None
|
|||
needs_expert_evaluation=True,
|
||||
competence_certificate=competence_certificate,
|
||||
effort_required="ca. 5 Stunden",
|
||||
solution_sample=ContentDocument.objects.get(title="Musterlösung Fahrzeug"),
|
||||
intro_text=replace_whitespace(
|
||||
"""
|
||||
<h3>Ausgangslage</h3>
|
||||
|
|
@ -4208,6 +4210,8 @@ def create_vv_gewinnen_casework(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID):
|
|||
parent=assignment_list_page,
|
||||
title="Mein Kundenstamm",
|
||||
effort_required="60 bis 90 Minuten",
|
||||
needs_expert_evaluation=True,
|
||||
assignment_type=AssignmentType.PRAXIS_ASSIGNMENT.name,
|
||||
intro_text=replace_whitespace(
|
||||
"""
|
||||
<h3>Thema</h3>
|
||||
|
|
@ -4255,6 +4259,51 @@ def create_vv_gewinnen_casework(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID):
|
|||
)
|
||||
|
||||
assignment.evaluation_tasks = []
|
||||
assignment.evaluation_tasks.append(
|
||||
(
|
||||
"task",
|
||||
EvaluationTaskBlockFactory(
|
||||
title="Feedback zu Teilaufgabe 1",
|
||||
max_points=0,
|
||||
),
|
||||
),
|
||||
)
|
||||
assignment.evaluation_tasks.append(
|
||||
(
|
||||
"task",
|
||||
EvaluationTaskBlockFactory(
|
||||
title="Feedback zu Teilaufgabe 2",
|
||||
max_points=0,
|
||||
),
|
||||
),
|
||||
)
|
||||
assignment.evaluation_tasks.append(
|
||||
(
|
||||
"task",
|
||||
EvaluationTaskBlockFactory(
|
||||
title="Feedback zu Teilaufgabe 3",
|
||||
max_points=0,
|
||||
),
|
||||
),
|
||||
)
|
||||
assignment.evaluation_tasks.append(
|
||||
(
|
||||
"task",
|
||||
EvaluationTaskBlockFactory(
|
||||
title="Feedback zu Teilaufgabe 4",
|
||||
max_points=0,
|
||||
),
|
||||
),
|
||||
)
|
||||
assignment.evaluation_tasks.append(
|
||||
(
|
||||
"task",
|
||||
EvaluationTaskBlockFactory(
|
||||
title="Feedback zu Teilaufgabe 5",
|
||||
max_points=0,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assignment.tasks = []
|
||||
assignment.tasks.append(
|
||||
|
|
@ -4476,89 +4525,20 @@ def create_vv_gewinnen_casework(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID):
|
|||
),
|
||||
)
|
||||
)
|
||||
|
||||
assignment.tasks.append(
|
||||
(
|
||||
"task",
|
||||
TaskBlockFactory(
|
||||
title="Teilaufgabe 5: Kundenkontakte pflegen",
|
||||
title="Teilaufgabe 5: Kundentelefonate2",
|
||||
content=StreamValue(
|
||||
TaskContentStreamBlock(),
|
||||
stream_data=[
|
||||
(
|
||||
"explanation",
|
||||
ExplanationBlockFactory(
|
||||
text=RichText(
|
||||
replace_whitespace(
|
||||
"""
|
||||
<p>Dein bestehender Kundenstamm ist ein wertvoller Pool mit Potenzial, um weitere Versicherungslösungen anzubieten. Damit du die gute Beziehung zu deiner Kundschaft aufrechterhalten kannst, hast du nebst der Analyse des Kundenstamms noch weitere Optionen.</p>
|
||||
"""
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_text_input",
|
||||
UserTextInputBlockFactory(
|
||||
text=RichText(
|
||||
"""
|
||||
Welche weiteren Möglichkeiten hast du, um den Kontakt zu bestehenden Kundinnen/Kunden zu pflegen?
|
||||
"""
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
assignment.tasks.append(
|
||||
(
|
||||
"task",
|
||||
TaskBlockFactory(
|
||||
title="Teilaufgabe 6: Reflexion und Learnings",
|
||||
content=StreamValue(
|
||||
TaskContentStreamBlock(),
|
||||
stream_data=[
|
||||
(
|
||||
"explanation",
|
||||
ExplanationBlockFactory(
|
||||
text=RichText(
|
||||
replace_whitespace(
|
||||
"""
|
||||
Welche Erkenntnisse nimmst du aus diesem Praxisauftrag mit?
|
||||
"""
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_text_input",
|
||||
UserTextInputBlockFactory(
|
||||
text=RichText(
|
||||
"""
|
||||
Auf welche Weise wirst du deinen Kundenstamm durchforsten, um das Optimierungspotenzial deiner Kundinnen/Kunden zu erkennen?
|
||||
"""
|
||||
)
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_text_input",
|
||||
UserTextInputBlockFactory(
|
||||
text=RichText(
|
||||
"""
|
||||
Frage zwei, drei Kolleginnen/Kollegen, wie sie ihren Kundenstamm pflegen.
|
||||
"""
|
||||
)
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_text_input",
|
||||
UserTextInputBlockFactory(
|
||||
text=RichText(
|
||||
"""
|
||||
Wie wirst du deine bestehende Kundschaft pflegen?
|
||||
<p>Notiere, welche hundert Kunden du nächste Woche für ein Beratungsgespräch telefonisch kontaktieren willst.</p>
|
||||
"""
|
||||
)
|
||||
),
|
||||
|
|
@ -4588,6 +4568,7 @@ def create_vv_einkommenssicherung_casework(
|
|||
parent=assignment_list_page,
|
||||
title="Heirat: Was ändert sich",
|
||||
effort_required="45 bis 90 Minuten",
|
||||
assignment_type=AssignmentType.PRAXIS_ASSIGNMENT.name,
|
||||
intro_text=replace_whitespace(
|
||||
"""
|
||||
<h3>Thema</h3>
|
||||
|
|
@ -4639,6 +4620,43 @@ def create_vv_einkommenssicherung_casework(
|
|||
|
||||
assignment.evaluation_tasks = []
|
||||
|
||||
assignment.evaluation_tasks.append(
|
||||
(
|
||||
"task",
|
||||
EvaluationTaskBlockFactory(
|
||||
title="Feedback zu Teilaufgabe 1",
|
||||
max_points=0,
|
||||
),
|
||||
),
|
||||
)
|
||||
assignment.evaluation_tasks.append(
|
||||
(
|
||||
"task",
|
||||
EvaluationTaskBlockFactory(
|
||||
title="Feedback zu Teilaufgabe 2",
|
||||
max_points=0,
|
||||
),
|
||||
),
|
||||
)
|
||||
assignment.evaluation_tasks.append(
|
||||
(
|
||||
"task",
|
||||
EvaluationTaskBlockFactory(
|
||||
title="Feedback zu Teilaufgabe 3",
|
||||
max_points=0,
|
||||
),
|
||||
),
|
||||
)
|
||||
assignment.evaluation_tasks.append(
|
||||
(
|
||||
"task",
|
||||
EvaluationTaskBlockFactory(
|
||||
title="Feedback zu Teilaufgabe 4",
|
||||
max_points=0,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assignment.tasks = []
|
||||
assignment.tasks.append(
|
||||
(
|
||||
|
|
@ -4784,31 +4802,6 @@ def create_vv_einkommenssicherung_casework(
|
|||
)
|
||||
)
|
||||
|
||||
assignment.tasks.append(
|
||||
(
|
||||
"task",
|
||||
TaskBlockFactory(
|
||||
title="Teilaufgabe 5: Deine Meinung",
|
||||
content=StreamValue(
|
||||
TaskContentStreamBlock(),
|
||||
stream_data=[
|
||||
(
|
||||
"user_text_input",
|
||||
UserTextInputBlockFactory(
|
||||
text=RichText(
|
||||
"""
|
||||
<p>Nachdem du nun die Auswirkungen einer Heirat unter die Lupe genommen hast, interessiert uns deine persönliche Meinung.</p>
|
||||
<p>Heirat: Lohnt sich eine Heirat aus rein finanzieller Sicht? Begründe deine Ansicht.</p>
|
||||
"""
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
assignment.save()
|
||||
|
||||
return assignment
|
||||
|
|
@ -4826,6 +4819,7 @@ def create_vv_gesundheit_casework(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
|
|||
parent=assignment_list_page,
|
||||
title="Krankenversicherung: Passt die Lösung noch?",
|
||||
effort_required="60 bis 90 Minuten",
|
||||
assignment_type=AssignmentType.PRAXIS_ASSIGNMENT.name,
|
||||
intro_text=replace_whitespace(
|
||||
"""
|
||||
<h3>Ausgangslage</h3>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from vbv_lernwelt.course.graphql.interfaces import CoursePageInterface
|
|||
from vbv_lernwelt.course.models import CourseSession
|
||||
from vbv_lernwelt.iam.permissions import has_course_access, is_course_session_expert
|
||||
from vbv_lernwelt.learnpath.graphql.types import LearningContentInterface
|
||||
from vbv_lernwelt.media_files.graphql.types import ContentDocumentObjectType
|
||||
|
||||
|
||||
class AssignmentCompletionObjectType(DjangoObjectType):
|
||||
|
|
@ -52,6 +53,7 @@ class AssignmentObjectType(DjangoObjectType):
|
|||
learning_content_page_id=graphene.ID(required=False),
|
||||
assignment_user_id=graphene.UUID(required=False),
|
||||
)
|
||||
solution_sample = graphene.Field(ContentDocumentObjectType)
|
||||
|
||||
class Meta:
|
||||
model = Assignment
|
||||
|
|
@ -67,6 +69,9 @@ class AssignmentObjectType(DjangoObjectType):
|
|||
"competence_certificate",
|
||||
)
|
||||
|
||||
def resolve_solution_sample(self, info):
|
||||
return self.solution_sample
|
||||
|
||||
def resolve_max_points(self, info):
|
||||
return self.get_max_points()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 3.2.20 on 2023-12-05 16:15
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("media_files", "0001_initial"),
|
||||
("assignment", "0010_assignmentcompletion_edoniq_extended_time_flag"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="assignment",
|
||||
name="solution_sample",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Musterlösung",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="+",
|
||||
to="media_files.contentdocument",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -111,6 +111,7 @@ class EvaluationTaskBlock(blocks.StructBlock):
|
|||
|
||||
|
||||
class AssignmentType(Enum):
|
||||
PRAXIS_ASSIGNMENT = "PRAXIS_ASSIGNMENT" # Praxisauftrag
|
||||
CASEWORK = "CASEWORK" # Geleitete Fallarbeit
|
||||
PREP_ASSIGNMENT = "PREP_ASSIGNMENT" # Vorbereitungsauftrag
|
||||
REFLECTION = "REFLECTION" # Reflexion
|
||||
|
|
@ -143,7 +144,7 @@ class Assignment(CourseBasePage):
|
|||
|
||||
needs_expert_evaluation = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Muss der Auftrag durch eine Expertin oder einen Experten beurteilt werden?",
|
||||
help_text="Muss der Auftrag durch eine/n Experten/in oder eine Lernbegleitung beurteilt werden?",
|
||||
)
|
||||
|
||||
competence_certificate = models.ForeignKey(
|
||||
|
|
@ -199,12 +200,22 @@ class Assignment(CourseBasePage):
|
|||
help_text="Beurteilungsschritte",
|
||||
)
|
||||
|
||||
solution_sample = models.ForeignKey(
|
||||
"media_files.ContentDocument",
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="+",
|
||||
help_text="Musterlösung",
|
||||
)
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
FieldPanel("assignment_type"),
|
||||
FieldPanel("needs_expert_evaluation"),
|
||||
PageChooserPanel("competence_certificate", "competence.CompetenceCertificate"),
|
||||
FieldPanel("intro_text"),
|
||||
FieldPanel("effort_required"),
|
||||
FieldPanel("solution_sample"),
|
||||
FieldPanel("performance_objectives"),
|
||||
FieldPanel("tasks"),
|
||||
FieldPanel("evaluation_description"),
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ def update_assignment_completion(
|
|||
ac.evaluation_points = evaluation_points
|
||||
|
||||
# if no evaluation_passed is provided, we calculate it from the points
|
||||
if evaluation_passed is None:
|
||||
if evaluation_passed is None and ac.evaluation_max_points > 0:
|
||||
if evaluation_points is not None and ac.evaluation_max_points is not None:
|
||||
# if more or equal than 60% of the points are reached, the assignment is passed
|
||||
ac.evaluation_passed = (evaluation_points / ac.evaluation_max_points) >= 0.6
|
||||
|
|
|
|||
|
|
@ -106,14 +106,14 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
|
|||
)
|
||||
|
||||
task_data = data["task_completion_data"][task_id]
|
||||
self.assertDictEqual(
|
||||
task_data,
|
||||
{
|
||||
"user_data": {
|
||||
"fileId": file_id,
|
||||
"fileInfo": {"id": file_id, "name": "file.txt", "url": file_url},
|
||||
}
|
||||
},
|
||||
self.maxDiff = None
|
||||
self.assertEqual(task_data["user_data"]["fileId"], file_id)
|
||||
self.assertEqual(task_data["user_data"]["fileInfo"]["id"], file_id)
|
||||
self.assertEqual(task_data["user_data"]["fileInfo"]["name"], "file.txt")
|
||||
self.assertTrue(
|
||||
task_data["user_data"]["fileInfo"]["url"].startswith(
|
||||
"https://s3.eu-central-1.amazonaws.com/myvbv-dev.iterativ.ch"
|
||||
)
|
||||
)
|
||||
|
||||
# check DB data
|
||||
|
|
@ -194,31 +194,31 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
|
|||
# check notification
|
||||
self.assertEqual(Notification.objects.count(), 1)
|
||||
notification = Notification.objects.first()
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
"Test Student1 hat die geleitete Fallarbeit «Überprüfen einer Motorfahrzeugs-Versicherungspolice» abgegeben.",
|
||||
notification.verb,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
"test-trainer1@example.com",
|
||||
notification.recipient.email,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
"test-student1@example.com",
|
||||
notification.actor.email,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
"USER_INTERACTION",
|
||||
notification.notification_category,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
"CASEWORK_SUBMITTED",
|
||||
notification.notification_trigger,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
notification.action_object,
|
||||
db_entry,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
notification.course_session,
|
||||
self.course_session,
|
||||
)
|
||||
|
|
@ -422,35 +422,35 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
|
|||
# check notification
|
||||
self.assertEqual(Notification.objects.count(), 1)
|
||||
notification = Notification.objects.first()
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
"Test Trainer1 hat die geleitete Fallarbeit «Überprüfen einer Motorfahrzeugs-Versicherungspolice» bewertet.",
|
||||
notification.verb,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
"test-student1@example.com",
|
||||
notification.recipient.email,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
"test-trainer1@example.com",
|
||||
notification.actor.email,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
"USER_INTERACTION",
|
||||
notification.notification_category,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
"CASEWORK_EVALUATED",
|
||||
notification.notification_trigger,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
notification.action_object,
|
||||
db_entry,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
notification.course_session,
|
||||
self.course_session,
|
||||
)
|
||||
self.assertEquals(
|
||||
self.assertEqual(
|
||||
notification.target_url,
|
||||
"/course/test-lehrgang/learn/fahrzeug/überprüfen-einer-motorfahrzeug-versicherungspolice",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from vbv_lernwelt.core.constants import (
|
|||
TEST_TRAINER1_USER_ID,
|
||||
)
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.consts import COURSE_TEST_ID
|
||||
from vbv_lernwelt.course.creators.test_course import (
|
||||
create_edoniq_test_result_data,
|
||||
create_feedback_response_data,
|
||||
|
|
@ -20,6 +21,7 @@ from vbv_lernwelt.course.creators.test_course import (
|
|||
create_test_assignment_submitted_data,
|
||||
)
|
||||
from vbv_lernwelt.course.models import (
|
||||
Course,
|
||||
CourseCompletion,
|
||||
CourseCompletionStatus,
|
||||
CourseSession,
|
||||
|
|
@ -30,7 +32,8 @@ from vbv_lernwelt.course_session.services.attendance import AttendanceUserStatus
|
|||
from vbv_lernwelt.feedback.models import FeedbackResponse
|
||||
from vbv_lernwelt.learnpath.models import (
|
||||
LearningContentAttendanceCourse,
|
||||
LearningContentFeedback,
|
||||
LearningContentFeedbackUK,
|
||||
LearningContentFeedbackVV,
|
||||
)
|
||||
from vbv_lernwelt.notify.models import Notification
|
||||
|
||||
|
|
@ -73,6 +76,11 @@ from vbv_lernwelt.notify.models import Notification
|
|||
default=False,
|
||||
help="will create attendance days data",
|
||||
)
|
||||
@click.option(
|
||||
"--enable-circle-documents/--no-enable-circle-documents",
|
||||
default=True,
|
||||
help="will enable circle documents for test course",
|
||||
)
|
||||
def command(
|
||||
create_assignment_completion,
|
||||
create_assignment_evaluation,
|
||||
|
|
@ -81,6 +89,7 @@ def command(
|
|||
create_feedback_responses,
|
||||
create_course_completion_performance_criteria,
|
||||
create_attendance_days,
|
||||
enable_circle_documents,
|
||||
):
|
||||
print("cypress reset data")
|
||||
CourseCompletion.objects.all().delete()
|
||||
|
|
@ -101,6 +110,13 @@ def command(
|
|||
course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID),
|
||||
user=User.objects.get(id=TEST_STUDENT1_USER_ID),
|
||||
)
|
||||
create_test_assignment_submitted_data(
|
||||
assignment=Assignment.objects.get(
|
||||
slug="test-lehrgang-assignment-mein-kundenstamm"
|
||||
),
|
||||
course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID),
|
||||
user=User.objects.get(id=TEST_STUDENT1_USER_ID),
|
||||
)
|
||||
if create_assignment_evaluation:
|
||||
if not assignment_evaluation_scores:
|
||||
assignment_evaluation_scores = [6, 6, 6, 3, 3]
|
||||
|
|
@ -140,7 +156,9 @@ def command(
|
|||
if create_feedback_responses:
|
||||
print("create_feedback_responses")
|
||||
course_session = CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID)
|
||||
learning_content_feedback_page = LearningContentFeedback.objects.get(
|
||||
|
||||
# feedback fahrzeug
|
||||
learning_content_feedback_page = LearningContentFeedbackUK.objects.get(
|
||||
slug="test-lehrgang-lp-circle-fahrzeug-lc-feedback"
|
||||
)
|
||||
create_feedback_response_data(
|
||||
|
|
@ -159,6 +177,7 @@ def command(
|
|||
"would_recommend": True,
|
||||
"course_negative_feedback": "Nichts Schlechtes",
|
||||
"course_positive_feedback": "Nur Gutes.",
|
||||
"feedback_type": "uk",
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -178,6 +197,7 @@ def command(
|
|||
"would_recommend": True,
|
||||
"course_negative_feedback": "Es wäre praktisch, Zugang zu einer FAQ zu haben.",
|
||||
"course_positive_feedback": "Das Beispiel mit der Katze fand ich sehr gut veranschaulicht!",
|
||||
"feedback_type": "uk",
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -197,6 +217,62 @@ def command(
|
|||
"would_recommend": False,
|
||||
"course_negative_feedback": "Mehr Videos wären schön.",
|
||||
"course_positive_feedback": "Die Präsentation war super",
|
||||
"feedback_type": "uk",
|
||||
},
|
||||
)
|
||||
|
||||
# feedback reisen
|
||||
learning_content_feedback_page = LearningContentFeedbackVV.objects.get(
|
||||
slug="test-lehrgang-lp-circle-reisen-lc-feedback"
|
||||
)
|
||||
create_feedback_response_data(
|
||||
feedback_user=User.objects.get(id=TEST_STUDENT1_USER_ID),
|
||||
course_session=course_session,
|
||||
learning_content_feedback_page=learning_content_feedback_page,
|
||||
submitted=True,
|
||||
feedback_data={
|
||||
"satisfaction": 4,
|
||||
"goal_attainment": 3,
|
||||
"proficiency": 80,
|
||||
"preparation_task_clarity": True,
|
||||
"would_recommend": True,
|
||||
"course_negative_feedback": "Nichts Schlechtes",
|
||||
"course_positive_feedback": "Nur Gutes.",
|
||||
"feedback_type": "vv",
|
||||
},
|
||||
)
|
||||
|
||||
create_feedback_response_data(
|
||||
feedback_user=User.objects.get(id=TEST_STUDENT2_USER_ID),
|
||||
course_session=course_session,
|
||||
learning_content_feedback_page=learning_content_feedback_page,
|
||||
submitted=True,
|
||||
feedback_data={
|
||||
"satisfaction": 4,
|
||||
"goal_attainment": 4,
|
||||
"proficiency": 100,
|
||||
"preparation_task_clarity": True,
|
||||
"would_recommend": True,
|
||||
"course_negative_feedback": "Es wäre praktisch, Zugang zu einer FAQ zu haben.",
|
||||
"course_positive_feedback": "Das Beispiel mit der Katze fand ich sehr gut veranschaulicht!",
|
||||
"feedback_type": "vv",
|
||||
},
|
||||
)
|
||||
|
||||
create_feedback_response_data(
|
||||
feedback_user=User.objects.get(id=TEST_STUDENT3_USER_ID),
|
||||
course_session=course_session,
|
||||
learning_content_feedback_page=learning_content_feedback_page,
|
||||
submitted=True,
|
||||
feedback_data={
|
||||
"satisfaction": 2,
|
||||
"goal_attainment": 2,
|
||||
"proficiency": 40,
|
||||
"preparation_task_clarity": True,
|
||||
"would_recommend": False,
|
||||
"course_negative_feedback": "Mehr Videos wären schön.",
|
||||
"course_positive_feedback": "Die Präsentation war super",
|
||||
"feedback_type": "vv",
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -243,3 +319,7 @@ def command(
|
|||
datetime(year=2000, month=10, day=31, hour=11)
|
||||
)
|
||||
attendance_course.save()
|
||||
|
||||
course = Course.objects.get(id=COURSE_TEST_ID)
|
||||
course.enable_circle_documents = enable_circle_documents
|
||||
course.save()
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from vbv_lernwelt.assignment.creators.create_assignments import (
|
|||
create_uk_fahrzeug_casework,
|
||||
create_uk_fahrzeug_prep_assignment,
|
||||
create_uk_reflection,
|
||||
create_vv_gewinnen_casework,
|
||||
)
|
||||
from vbv_lernwelt.assignment.models import (
|
||||
Assignment,
|
||||
|
|
@ -69,7 +70,8 @@ from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
|||
LearningContentAssignmentFactory,
|
||||
LearningContentAttendanceCourseFactory,
|
||||
LearningContentEdoniqTestFactory,
|
||||
LearningContentFeedbackFactory,
|
||||
LearningContentFeedbackUKFactory,
|
||||
LearningContentFeedbackVVFactory,
|
||||
LearningContentKnowledgeAssessmentFactory,
|
||||
LearningContentLearningModuleFactory,
|
||||
LearningContentMediaLibraryFactory,
|
||||
|
|
@ -81,6 +83,12 @@ from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
|||
LearningUnitFactory,
|
||||
TopicFactory,
|
||||
)
|
||||
from vbv_lernwelt.media_files.create_default_documents import (
|
||||
create_default_collections,
|
||||
create_default_content_documents,
|
||||
)
|
||||
from vbv_lernwelt.media_files.create_default_images import create_default_images
|
||||
from vbv_lernwelt.media_files.models import ContentDocument, ContentImage, UserImage
|
||||
from vbv_lernwelt.media_library.tests.media_library_factories import (
|
||||
MediaLibraryCategoryPageFactory,
|
||||
MediaLibraryContentPageFactory,
|
||||
|
|
@ -91,14 +99,21 @@ from vbv_lernwelt.media_library.tests.media_library_factories import (
|
|||
|
||||
def create_test_course(include_uk=True, include_vv=True, with_sessions=False):
|
||||
# create_locales_for_wagtail()
|
||||
create_default_collections()
|
||||
create_default_content_documents()
|
||||
if UserImage.objects.count() == 0 and ContentImage.objects.count() == 0:
|
||||
create_default_images()
|
||||
|
||||
course = create_test_course_with_categories()
|
||||
competence_certificate = create_test_competence_navi()
|
||||
|
||||
# assignments create assignments parent page
|
||||
course_page = CoursePage.objects.get(course_id=COURSE_TEST_ID)
|
||||
_assignment_list_page = AssignmentListPageFactory(
|
||||
parent=course_page,
|
||||
)
|
||||
|
||||
if include_uk:
|
||||
# assignments create assignments parent page
|
||||
course_page = CoursePage.objects.get(course_id=COURSE_TEST_ID)
|
||||
_assignment_list_page = AssignmentListPageFactory(
|
||||
parent=course_page,
|
||||
)
|
||||
create_uk_fahrzeug_casework(
|
||||
course_id=COURSE_TEST_ID, competence_certificate=competence_certificate
|
||||
)
|
||||
|
|
@ -111,6 +126,9 @@ def create_test_course(include_uk=True, include_vv=True, with_sessions=False):
|
|||
competence_certificate=competence_certificate,
|
||||
)
|
||||
|
||||
if include_vv:
|
||||
create_vv_gewinnen_casework(course_id=COURSE_TEST_ID)
|
||||
|
||||
create_test_learning_path(include_uk=include_uk, include_vv=include_vv)
|
||||
create_test_media_library()
|
||||
|
||||
|
|
@ -190,6 +208,14 @@ def create_test_course(include_uk=True, include_vv=True, with_sessions=False):
|
|||
)
|
||||
cset.deadline.save()
|
||||
|
||||
if include_vv:
|
||||
_csa = CourseSessionAssignment.objects.create(
|
||||
course_session=cs_bern,
|
||||
learning_content=LearningContentAssignment.objects.get(
|
||||
slug=f"test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"
|
||||
),
|
||||
)
|
||||
|
||||
cs_zurich = CourseSession.objects.create(
|
||||
course_id=COURSE_TEST_ID,
|
||||
title="Test Zürich 2022 a",
|
||||
|
|
@ -213,6 +239,8 @@ def create_test_course(include_uk=True, include_vv=True, with_sessions=False):
|
|||
role=CourseSessionUser.Role.EXPERT,
|
||||
)
|
||||
csu.expert.add(Circle.objects.get(slug="test-lehrgang-lp-circle-fahrzeug"))
|
||||
if include_vv:
|
||||
csu.expert.add(Circle.objects.get(slug="test-lehrgang-lp-circle-reisen"))
|
||||
|
||||
trainer2 = User.objects.get(email="test-trainer2@example.com")
|
||||
csu = CourseSessionUser.objects.create(
|
||||
|
|
@ -344,6 +372,7 @@ def create_feedback_response_data(
|
|||
"would_recommend": True,
|
||||
"course_negative_feedback": "Nichts Schlechtes",
|
||||
"course_positive_feedback": "Nur Gutes.",
|
||||
"feedback_type": "uk",
|
||||
}
|
||||
|
||||
return update_feedback_response(
|
||||
|
|
@ -507,6 +536,14 @@ damit du erfolgreich mit deinem Lernpfad (durch-)starten kannst.
|
|||
slug__startswith=f"test-lehrgang-assignment-reflexion"
|
||||
),
|
||||
),
|
||||
|
||||
assignment = Assignment.objects.get(
|
||||
slug__startswith="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs"
|
||||
)
|
||||
assignment.solution_sample = ContentDocument.objects.get(
|
||||
title="Musterlösung Fahrzeug"
|
||||
)
|
||||
assignment.save()
|
||||
LearningContentAssignmentFactory(
|
||||
title="Überprüfen einer Motorfahrzeug-Versicherungspolice",
|
||||
parent=circle,
|
||||
|
|
@ -514,7 +551,8 @@ damit du erfolgreich mit deinem Lernpfad (durch-)starten kannst.
|
|||
slug__startswith="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs"
|
||||
),
|
||||
)
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackUKFactory(
|
||||
title="Feedback",
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
@ -555,6 +593,15 @@ def create_test_circle_reisen(lp):
|
|||
content_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-analyse-xapi-FZoZOP9y/index.html",
|
||||
)
|
||||
|
||||
LearningContentAssignmentFactory(
|
||||
title="Mein Kundenstamm",
|
||||
assignment_type="PRAXIS_ASSIGNMENT",
|
||||
parent=circle,
|
||||
content_assignment=Assignment.objects.get(
|
||||
slug__startswith="test-lehrgang-assignment-mein-kundenstamm"
|
||||
),
|
||||
),
|
||||
|
||||
PerformanceCriteriaFactory(
|
||||
parent=ActionCompetence.objects.get(competence_id="Y1"),
|
||||
competence_id=f"Y1.1",
|
||||
|
|
@ -589,7 +636,8 @@ def create_test_circle_reisen(lp):
|
|||
title="Reflexion",
|
||||
parent=parent,
|
||||
)
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackVVFactory(
|
||||
title="Feedback",
|
||||
parent=parent,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
|||
LearningContentAttendanceCourseFactory,
|
||||
LearningContentDocumentListFactory,
|
||||
LearningContentEdoniqTestFactory,
|
||||
LearningContentFeedbackFactory,
|
||||
LearningContentFeedbackUKFactory,
|
||||
LearningContentMediaLibraryFactory,
|
||||
LearningContentPlaceholderFactory,
|
||||
LearningPathFactory,
|
||||
|
|
@ -30,6 +30,11 @@ from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
|||
LearningUnitFactory,
|
||||
TopicFactory,
|
||||
)
|
||||
from vbv_lernwelt.media_files.create_default_documents import (
|
||||
create_default_collections,
|
||||
create_default_content_documents,
|
||||
)
|
||||
from vbv_lernwelt.media_files.create_default_images import create_default_images
|
||||
from vbv_lernwelt.media_library.tests.media_library_factories import (
|
||||
LearnMediaBlockFactory,
|
||||
)
|
||||
|
|
@ -40,6 +45,7 @@ def create_uk_learning_path(course_id=COURSE_UK, user=None, skip_locales=True):
|
|||
user = User.objects.get(username="info@iterativ.ch")
|
||||
|
||||
course_page = CoursePage.objects.get(course_id=course_id)
|
||||
|
||||
lp = LearningPathFactory(
|
||||
title="Lernpfad",
|
||||
parent=course_page,
|
||||
|
|
@ -254,7 +260,7 @@ damit du erfolgreich mit deinem Lernpfad (durch-)starten kannst.
|
|||
title="Unterlagen für den Unterricht",
|
||||
parent=circle,
|
||||
)
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackUKFactory(
|
||||
parent=circle,
|
||||
)
|
||||
LearningSequenceFactory(title="Transfer", parent=circle, icon="it-icon-ls-end")
|
||||
|
|
@ -364,7 +370,7 @@ In diesem Circle erfährst du wie die überbetrieblichen Kurse aufgebaut sind. Z
|
|||
# test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096897473,2147466097",
|
||||
# )
|
||||
LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle)
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackUKFactory(
|
||||
parent=circle,
|
||||
)
|
||||
LearningSequenceFactory(title="Transfer", parent=circle, icon="it-icon-ls-end")
|
||||
|
|
@ -479,7 +485,7 @@ Dans ce cercle, tu apprendras comment les cours interentreprises sont structuré
|
|||
test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096897473,2147466097",
|
||||
)
|
||||
LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle)
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackUKFactory(
|
||||
parent=circle,
|
||||
)
|
||||
LearningSequenceFactory(title="Transfert", parent=circle, icon="it-icon-ls-end")
|
||||
|
|
@ -594,7 +600,7 @@ In questo Circle imparerai come sono strutturati i corsi interaziendali. Imparer
|
|||
test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096897473,2147466097",
|
||||
)
|
||||
LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle)
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackUKFactory(
|
||||
parent=circle,
|
||||
)
|
||||
LearningSequenceFactory(title="Trasferimento", parent=circle, icon="it-icon-ls-end")
|
||||
|
|
@ -699,7 +705,7 @@ In diesem Circle lernst du die wichtigsten Grundlagen bezüglich Versicherungswi
|
|||
],
|
||||
)
|
||||
LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle)
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackUKFactory(
|
||||
parent=circle,
|
||||
)
|
||||
LearningSequenceFactory(title="Transfer", parent=circle, icon="it-icon-ls-end")
|
||||
|
|
@ -809,7 +815,7 @@ Dans ce cercle, tu apprends les bases les plus importantes en matière d'assuran
|
|||
],
|
||||
)
|
||||
LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle)
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackUKFactory(
|
||||
parent=circle,
|
||||
)
|
||||
LearningSequenceFactory(title="Transfert", parent=circle, icon="it-icon-ls-end")
|
||||
|
|
@ -918,7 +924,7 @@ In questo Circle imparerai le basi più importanti del settore assicurativo e de
|
|||
],
|
||||
)
|
||||
LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle)
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackUKFactory(
|
||||
parent=circle,
|
||||
)
|
||||
LearningSequenceFactory(title="Trasferimento", parent=circle, icon="it-icon-ls-end")
|
||||
|
|
@ -1058,7 +1064,7 @@ def create_uk_circle_fahrzeug(lp, title="Fahrzeug"):
|
|||
],
|
||||
)
|
||||
LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle)
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackUKFactory(
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
@ -1192,7 +1198,7 @@ def create_uk_fr_circle_fahrzeug(lp, title="Véhicule"):
|
|||
],
|
||||
)
|
||||
LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle)
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackUKFactory(
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
@ -1330,7 +1336,7 @@ def create_uk_it_circle_fahrzeug(lp, title="Veicolo"):
|
|||
],
|
||||
)
|
||||
LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle)
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackUKFactory(
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ from vbv_lernwelt.learnpath.graphql.types import (
|
|||
LearningContentAttendanceCourseObjectType,
|
||||
LearningContentDocumentListObjectType,
|
||||
LearningContentEdoniqTestObjectType,
|
||||
LearningContentFeedbackObjectType,
|
||||
LearningContentFeedbackUKObjectType,
|
||||
LearningContentFeedbackVVObjectType,
|
||||
LearningContentKnowledgeAssessmentObjectType,
|
||||
LearningContentLearningModuleObjectType,
|
||||
LearningContentMediaLibraryObjectType,
|
||||
|
|
@ -50,7 +51,8 @@ class CourseQuery(graphene.ObjectType):
|
|||
learning_content_attendance_course = graphene.Field(
|
||||
LearningContentAttendanceCourseObjectType
|
||||
)
|
||||
learning_content_feedback = graphene.Field(LearningContentFeedbackObjectType)
|
||||
learning_content_feedback_uk = graphene.Field(LearningContentFeedbackUKObjectType)
|
||||
learning_content_feedback_vv = graphene.Field(LearningContentFeedbackVVObjectType)
|
||||
learning_content_learning_module = graphene.Field(
|
||||
LearningContentLearningModuleObjectType
|
||||
)
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class CourseObjectType(DjangoObjectType):
|
|||
|
||||
class Meta:
|
||||
model = Course
|
||||
fields = ("id", "title", "category_name", "slug")
|
||||
fields = ("id", "title", "category_name", "slug", "enable_circle_documents")
|
||||
|
||||
@staticmethod
|
||||
def resolve_learning_path(root: Course, info):
|
||||
|
|
|
|||
|
|
@ -100,6 +100,12 @@ from vbv_lernwelt.learnpath.models import (
|
|||
LearningContentAssignment,
|
||||
LearningContentAttendanceCourse,
|
||||
)
|
||||
from vbv_lernwelt.media_files.create_default_documents import (
|
||||
create_default_collections,
|
||||
create_default_content_documents,
|
||||
create_default_user_documents,
|
||||
)
|
||||
from vbv_lernwelt.media_files.create_default_images import create_default_images
|
||||
from vbv_lernwelt.media_library.create_default_media_library import (
|
||||
create_default_media_library,
|
||||
)
|
||||
|
|
@ -128,6 +134,11 @@ ADMIN_EMAILS = ["info@iterativ.ch", "admin"]
|
|||
def command(course):
|
||||
print("Creating default courses", course)
|
||||
|
||||
create_default_collections()
|
||||
create_default_content_documents()
|
||||
create_default_user_documents()
|
||||
create_default_images()
|
||||
|
||||
if COURSE_VERSICHERUNGSVERMITTLERIN_ID in course:
|
||||
create_versicherungsvermittlerin_course()
|
||||
|
||||
|
|
@ -285,6 +296,9 @@ def create_course_uk_de(course_id=COURSE_UK, lang="de"):
|
|||
create_uk_competence_profile(course_id=course_id)
|
||||
create_default_media_library(course_id=course_id)
|
||||
|
||||
create_default_collections()
|
||||
create_default_content_documents()
|
||||
|
||||
|
||||
def create_course_uk_de_course_sessions():
|
||||
course = Course.objects.get(id=COURSE_UK)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 3.2.20 on 2023-11-23 10:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("course", "0004_auto_20230823_1744"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="course",
|
||||
name="enable_circle_documents",
|
||||
field=models.BooleanField(
|
||||
default=True, verbose_name="Trainer Dokumente in Circles"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -23,6 +23,9 @@ 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
|
||||
)
|
||||
|
||||
def get_course_url(self):
|
||||
return f"/course/{self.slug}"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class CourseSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Course
|
||||
fields = ["id", "title", "category_name", "slug"]
|
||||
fields = ["id", "title", "category_name", "slug", "enable_circle_documents"]
|
||||
|
||||
|
||||
class CourseCategorySerializer(serializers.ModelSerializer):
|
||||
|
|
|
|||
|
|
@ -74,12 +74,6 @@ class CourseSessionAttendanceCourseAdmin(admin.ModelAdmin):
|
|||
|
||||
@admin.register(CourseSessionAssignment)
|
||||
class CourseSessionAssignmentAdmin(admin.ModelAdmin):
|
||||
readonly_fields = [
|
||||
"course_session",
|
||||
"learning_content",
|
||||
"submission_deadline",
|
||||
"evaluation_deadline",
|
||||
]
|
||||
list_display = [
|
||||
"course_session",
|
||||
"circle",
|
||||
|
|
@ -88,6 +82,11 @@ class CourseSessionAssignmentAdmin(admin.ModelAdmin):
|
|||
"evaluation_date",
|
||||
]
|
||||
list_filter = ["course_session__course", "course_session"]
|
||||
raw_id_fields = [
|
||||
"course_session",
|
||||
"submission_deadline",
|
||||
"evaluation_deadline",
|
||||
]
|
||||
|
||||
def submission_date(self, obj):
|
||||
if obj.submission_deadline:
|
||||
|
|
@ -124,7 +123,7 @@ class CourseSessionAssignmentAdmin(admin.ModelAdmin):
|
|||
readonly_fields = super(CourseSessionAssignmentAdmin, self).get_readonly_fields(
|
||||
request, obj
|
||||
)
|
||||
return readonly_fields + ["circle_display"]
|
||||
return ["circle_display"]
|
||||
|
||||
# Override get_form to include circle_display
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ class CourseSessionAssignment(models.Model):
|
|||
on_delete=models.CASCADE,
|
||||
related_name="assignment_submission_deadline",
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
evaluation_deadline = models.OneToOneField(
|
||||
|
|
@ -116,6 +117,7 @@ class CourseSessionAssignment(models.Model):
|
|||
on_delete=models.CASCADE,
|
||||
related_name="assignment_evaluation_deadline",
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
|
@ -131,6 +133,7 @@ class CourseSessionAssignment(models.Model):
|
|||
assignment_type = self.learning_content.assignment_type
|
||||
assignment_type_translation_keys = {
|
||||
AssignmentType.CASEWORK.value: "learningContentTypes.casework",
|
||||
AssignmentType.PRAXIS_ASSIGNMENT.value: "learningContentTypes.praxisAssignment",
|
||||
AssignmentType.PREP_ASSIGNMENT.value: "learningContentTypes.prepAssignment",
|
||||
AssignmentType.REFLECTION.value: "learningContentTypes.reflection",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,8 +148,8 @@ class DashboardQuery(graphene.ObjectType):
|
|||
assignment=ProgressDashboardAssignmentType( # noqa
|
||||
_id=course_id, # noqa
|
||||
total_count=len(evaluation_results), # noqa
|
||||
points_max_count=points_max_count, # noqa
|
||||
points_achieved_count=points_achieved_count, # noqa
|
||||
points_max_count=int(points_max_count), # noqa
|
||||
points_achieved_count=int(points_achieved_count), # noqa
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
from django.contrib import admin
|
||||
from wagtail.models import Page
|
||||
|
||||
from vbv_lernwelt.course_session.models import (
|
||||
CourseSessionAssignment,
|
||||
CourseSessionAttendanceCourse,
|
||||
CourseSessionEdoniqTest,
|
||||
)
|
||||
from vbv_lernwelt.duedate.models import DueDate
|
||||
from vbv_lernwelt.learnpath.models import (
|
||||
Circle,
|
||||
|
|
@ -9,7 +14,16 @@ from vbv_lernwelt.learnpath.models import (
|
|||
)
|
||||
|
||||
|
||||
# Register your models here.
|
||||
@admin.action(description="Re-sync URLs from LearningContent")
|
||||
def sync_wagtail_due_date_url(modeladmin, request, queryset):
|
||||
for assignment in CourseSessionAssignment.objects.all():
|
||||
assignment.save()
|
||||
for edoniq_test in CourseSessionEdoniqTest.objects.all():
|
||||
edoniq_test.save()
|
||||
for attendance in CourseSessionAttendanceCourse.objects.all():
|
||||
attendance.save()
|
||||
|
||||
|
||||
@admin.register(DueDate)
|
||||
class DueDateAdmin(admin.ModelAdmin):
|
||||
date_hierarchy = "start"
|
||||
|
|
@ -23,6 +37,7 @@ class DueDateAdmin(admin.ModelAdmin):
|
|||
]
|
||||
list_filter = ["course_session__course", "course_session"]
|
||||
readonly_fields = ["course_session", "page"]
|
||||
actions = [sync_wagtail_due_date_url]
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
default_readonly = super(DueDateAdmin, self).get_readonly_fields(request, obj)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ class FeedbackResponseFactory(DjangoModelFactory):
|
|||
"Das Beispiel mit der Katze fand ich sehr gut veranschaulicht!",
|
||||
]
|
||||
),
|
||||
"feedback_type": FuzzyChoice(["uk", "vv"]),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,16 @@ from vbv_lernwelt.course.models import CourseSession
|
|||
from vbv_lernwelt.feedback.graphql.types import (
|
||||
FeedbackResponseObjectType as FeedbackResponseType,
|
||||
)
|
||||
from vbv_lernwelt.feedback.serializers import CourseFeedbackSerializer
|
||||
from vbv_lernwelt.feedback.serializers import (
|
||||
CourseFeedbackSerializerUK,
|
||||
CourseFeedbackSerializerVV,
|
||||
)
|
||||
from vbv_lernwelt.feedback.services import update_feedback_response
|
||||
from vbv_lernwelt.iam.permissions import has_course_session_access
|
||||
from vbv_lernwelt.learnpath.models import LearningContentFeedback
|
||||
from vbv_lernwelt.learnpath.models import (
|
||||
LearningContentFeedbackUK,
|
||||
LearningContentFeedbackVV,
|
||||
)
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
|
@ -25,6 +31,7 @@ class SendFeedbackMutation(graphene.Mutation):
|
|||
class Arguments:
|
||||
course_session_id = graphene.ID(required=True)
|
||||
learning_content_page_id = graphene.ID(required=True)
|
||||
learning_content_type = graphene.String(required=True)
|
||||
data = GenericScalar()
|
||||
submitted = graphene.Boolean(required=False, default_value=False)
|
||||
|
||||
|
|
@ -35,11 +42,29 @@ class SendFeedbackMutation(graphene.Mutation):
|
|||
info,
|
||||
course_session_id,
|
||||
learning_content_page_id,
|
||||
learning_content_type,
|
||||
data,
|
||||
submitted,
|
||||
):
|
||||
feedback_user_id = info.context.user.id
|
||||
learning_content = LearningContentFeedback.objects.get(
|
||||
|
||||
if learning_content_type == "learnpath.LearningContentFeedbackVV":
|
||||
learningContentFeedbackModel = LearningContentFeedbackVV
|
||||
serializerClass = CourseFeedbackSerializerVV
|
||||
data["feedback_type"] = "vv"
|
||||
elif learning_content_type == "learnpath.LearningContentFeedbackUK":
|
||||
learningContentFeedbackModel = LearningContentFeedbackUK
|
||||
serializerClass = CourseFeedbackSerializerUK
|
||||
data["feedback_type"] = "uk"
|
||||
else:
|
||||
errors = [
|
||||
ErrorType(
|
||||
field="learningContentType", messages="Invalid learningContentType"
|
||||
)
|
||||
]
|
||||
return SendFeedbackMutation(errors=errors)
|
||||
|
||||
learning_content = learningContentFeedbackModel.objects.get(
|
||||
id=learning_content_page_id
|
||||
)
|
||||
circle = learning_content.get_circle()
|
||||
|
|
@ -65,7 +90,7 @@ class SendFeedbackMutation(graphene.Mutation):
|
|||
course_session_id=course_session_id,
|
||||
)
|
||||
|
||||
serializer = CourseFeedbackSerializer(data=data)
|
||||
serializer = serializerClass(data=data)
|
||||
|
||||
if not serializer.is_valid():
|
||||
logger.error(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 3.2.20 on 2023-12-07 14:01
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def add_field_to_json(apps, _schema_editor):
|
||||
FeedbackResponse = apps.get_model("feedback", "FeedbackResponse")
|
||||
for instance in FeedbackResponse.objects.all():
|
||||
if instance.data is None:
|
||||
instance.data = {}
|
||||
instance.data["feedback_type"] = "uk" # Set the default value
|
||||
instance.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("feedback", "0006_auto_20230922_1131"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(add_field_to_json),
|
||||
]
|
||||
|
|
@ -5,6 +5,11 @@ from vbv_lernwelt.feedback.models import FeedbackResponse
|
|||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
FEEDBACK_TYPES = (
|
||||
("uk", "Feedback UK"),
|
||||
("vv", "Feedback VV"),
|
||||
)
|
||||
|
||||
|
||||
class FeedbackIntegerField(serializers.IntegerField):
|
||||
def __init__(self, **kwargs):
|
||||
|
|
@ -13,7 +18,8 @@ class FeedbackIntegerField(serializers.IntegerField):
|
|||
)
|
||||
|
||||
|
||||
class CourseFeedbackSerializer(serializers.Serializer):
|
||||
class CourseFeedbackSerializerUK(serializers.Serializer):
|
||||
feedback_type = serializers.ChoiceField(choices=FEEDBACK_TYPES)
|
||||
satisfaction = FeedbackIntegerField()
|
||||
goal_attainment = FeedbackIntegerField()
|
||||
proficiency = serializers.IntegerField(required=False, allow_null=True)
|
||||
|
|
@ -33,6 +39,22 @@ class CourseFeedbackSerializer(serializers.Serializer):
|
|||
)
|
||||
|
||||
|
||||
class CourseFeedbackSerializerVV(serializers.Serializer):
|
||||
feedback_type = serializers.ChoiceField(choices=FEEDBACK_TYPES)
|
||||
satisfaction = FeedbackIntegerField()
|
||||
goal_attainment = FeedbackIntegerField()
|
||||
proficiency = serializers.IntegerField(required=False, allow_null=True)
|
||||
preparation_task_clarity = serializers.BooleanField(required=False, allow_null=True)
|
||||
materials_rating = FeedbackIntegerField()
|
||||
would_recommend = serializers.BooleanField(required=False, allow_null=True)
|
||||
course_positive_feedback = serializers.CharField(
|
||||
required=False, allow_null=True, allow_blank=True
|
||||
)
|
||||
course_negative_feedback = serializers.CharField(
|
||||
required=False, allow_null=True, allow_blank=True
|
||||
)
|
||||
|
||||
|
||||
class CypressFeedbackResponseSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = FeedbackResponse
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
from typing import Union
|
||||
|
||||
import structlog
|
||||
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.models import CourseCompletionStatus, CourseSession
|
||||
from vbv_lernwelt.course.services import mark_course_completion
|
||||
from vbv_lernwelt.feedback.models import FeedbackResponse
|
||||
from vbv_lernwelt.learnpath.models import LearningContentFeedback
|
||||
from vbv_lernwelt.learnpath.models import (
|
||||
LearningContentFeedbackUK,
|
||||
LearningContentFeedbackVV,
|
||||
)
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
|
@ -12,7 +17,9 @@ logger = structlog.get_logger(__name__)
|
|||
def update_feedback_response(
|
||||
feedback_user: User,
|
||||
course_session: CourseSession,
|
||||
learning_content_feedback_page: LearningContentFeedback,
|
||||
learning_content_feedback_page: Union[
|
||||
LearningContentFeedbackUK, LearningContentFeedbackVV
|
||||
],
|
||||
submitted: bool,
|
||||
validated_data: dict,
|
||||
):
|
||||
|
|
@ -26,18 +33,7 @@ def update_feedback_response(
|
|||
|
||||
original_data = feedback_response.data
|
||||
updated_data = validated_data
|
||||
initial_data = {
|
||||
"satisfaction": None,
|
||||
"goal_attainment": None,
|
||||
"proficiency": None,
|
||||
"preparation_task_clarity": None,
|
||||
"instructor_competence": None,
|
||||
"instructor_respect": None,
|
||||
"instructor_open_feedback": "",
|
||||
"would_recommend": None,
|
||||
"course_negative_feedback": "",
|
||||
"course_positive_feedback": "",
|
||||
}
|
||||
initial_data = initial_data_for_feedback_page(learning_content_feedback_page)
|
||||
|
||||
merged_data = initial_data | {
|
||||
key: updated_data[key]
|
||||
|
|
@ -71,3 +67,36 @@ def update_feedback_response(
|
|||
)
|
||||
|
||||
return feedback_response
|
||||
|
||||
|
||||
def initial_data_for_feedback_page(
|
||||
learning_content_feedback_page: Union[
|
||||
LearningContentFeedbackUK, LearningContentFeedbackVV
|
||||
]
|
||||
):
|
||||
if hasattr(learning_content_feedback_page, "learningcontentfeedbackuk"):
|
||||
return {
|
||||
"satisfaction": None,
|
||||
"goal_attainment": None,
|
||||
"proficiency": None,
|
||||
"preparation_task_clarity": None,
|
||||
"instructor_competence": None,
|
||||
"instructor_respect": None,
|
||||
"instructor_open_feedback": "",
|
||||
"would_recommend": None,
|
||||
"course_negative_feedback": "",
|
||||
"course_positive_feedback": "",
|
||||
"feedback_type": "uk",
|
||||
}
|
||||
if hasattr(learning_content_feedback_page, "learningcontentfeedbackvv"):
|
||||
return {
|
||||
"satisfaction": None,
|
||||
"goal_attainment": None,
|
||||
"proficiency": None,
|
||||
"preparation_task_clarity": None,
|
||||
"would_recommend": None,
|
||||
"course_negative_feedback": "",
|
||||
"course_positive_feedback": "",
|
||||
"feedback_type": "vv",
|
||||
}
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ class FeedbackRestApiTestCase(FeedbackBaseTestCase):
|
|||
"course_negative_feedback": self.feedback_data[
|
||||
"course_negative_feedback"
|
||||
][i],
|
||||
"feedback_type": "uk",
|
||||
},
|
||||
feedback_user=self.feedback_users[i],
|
||||
submitted=True,
|
||||
|
|
@ -129,6 +130,7 @@ class FeedbackRestApiTestCase(FeedbackBaseTestCase):
|
|||
expected = {
|
||||
"amount": 3,
|
||||
"questions": self.feedback_data,
|
||||
"feedbackType": "uk",
|
||||
}
|
||||
print(response.data)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
import json
|
||||
|
||||
from graphene_django.utils.testing import GraphQLTestCase
|
||||
|
||||
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.consts import COURSE_TEST_ID
|
||||
from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||
from vbv_lernwelt.course.models import CourseSession
|
||||
from vbv_lernwelt.feedback.models import FeedbackResponse
|
||||
from vbv_lernwelt.learnpath.models import LearningContentFeedbackUK
|
||||
|
||||
|
||||
class FeedbackMutationTestCase(GraphQLTestCase):
|
||||
GRAPHQL_URL = "/server/graphql/"
|
||||
|
||||
def setUp(self):
|
||||
create_default_users()
|
||||
create_test_course(include_vv=False, with_sessions=True)
|
||||
self.course_session = CourseSession.objects.get(title="Test Bern 2022 a")
|
||||
self.learning_content_feedback_page = LearningContentFeedbackUK.objects.get(
|
||||
slug="test-lehrgang-lp-circle-fahrzeug-lc-feedback"
|
||||
)
|
||||
self.student = User.objects.get(username="test-student1@example.com")
|
||||
self.client.force_login(self.student)
|
||||
|
||||
def test_creates_response(self):
|
||||
data = {
|
||||
"course_negative_feedback": "schlecht",
|
||||
"course_positive_feedback": "gut",
|
||||
"feedback_type": "uk",
|
||||
"goal_attainment": 3,
|
||||
"preparation_task_clarity": False,
|
||||
"proficiency": 100,
|
||||
"satisfaction": 3,
|
||||
"would_recommend": False,
|
||||
"instructor_competence": None,
|
||||
"instructor_respect": None,
|
||||
"instructor_open_feedback": None,
|
||||
}
|
||||
|
||||
response = self.query(
|
||||
f"""
|
||||
mutation {{
|
||||
send_feedback(
|
||||
course_session_id: "{COURSE_TEST_ID}"
|
||||
learning_content_page_id: "{self.learning_content_feedback_page.id}"
|
||||
learning_content_type: "learnpath.LearningContentFeedbackUK"
|
||||
data: {{
|
||||
course_negative_feedback: "{data['course_negative_feedback']}",
|
||||
course_positive_feedback: "{data['course_positive_feedback']}",
|
||||
feedback_type: null,
|
||||
goal_attainment: {data['goal_attainment']},
|
||||
preparation_task_clarity: {str(data['preparation_task_clarity']).lower()},
|
||||
proficiency: {data['proficiency']},
|
||||
satisfaction: {data['satisfaction']},
|
||||
would_recommend: {str(data['would_recommend']).lower()},
|
||||
instructor_competence: null,
|
||||
instructor_respect: null,
|
||||
instructor_open_feedback: null,
|
||||
}},
|
||||
submitted: false
|
||||
) {{
|
||||
feedback_response {{
|
||||
id
|
||||
data
|
||||
submitted
|
||||
__typename
|
||||
}}
|
||||
errors {{
|
||||
field
|
||||
messages
|
||||
__typename
|
||||
}}
|
||||
__typename
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
)
|
||||
|
||||
content = json.loads(response.content)
|
||||
|
||||
self.assertResponseNoErrors(response)
|
||||
self.assertDictEqual(
|
||||
content["data"]["send_feedback"]["feedback_response"]["data"], data
|
||||
)
|
||||
|
||||
feedback = FeedbackResponse.objects.first()
|
||||
self.assertEqual(feedback.data, data)
|
||||
self.assertEqual(feedback.submitted, False)
|
||||
self.assertEqual(feedback.feedback_user, self.student)
|
||||
|
|
@ -61,12 +61,13 @@ def get_feedback_for_circle(request, course_session_id, circle_id):
|
|||
feedback_user__in=feedback_users(course_session_id),
|
||||
).order_by("created_at")
|
||||
|
||||
# I guess this is ok for the üK case
|
||||
feedback_data = {"amount": len(feedbacks), "questions": {}}
|
||||
feedback_data = {"amount": len(feedbacks), "questions": {}, "feedbackType": None}
|
||||
|
||||
if feedback_data["amount"] == 0:
|
||||
return Response(status=200, data=feedback_data)
|
||||
|
||||
feedback_data["feedbackType"] = feedbacks[0].data.get("feedback_type", None)
|
||||
|
||||
for field in FEEDBACK_FIELDS:
|
||||
feedback_data["questions"][field] = []
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
import datetime
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.test import TestCase
|
||||
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.files.integrations import s3_get_client
|
||||
from vbv_lernwelt.files.models import UploadFile
|
||||
|
||||
|
||||
class UploadFileIntegrationTest(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.s3_client = s3_get_client()
|
||||
|
||||
def setUp(self):
|
||||
self.user = User.objects.create(username="testuser")
|
||||
# Creating a dummy file for upload
|
||||
self.dummy_file = SimpleUploadedFile(
|
||||
"testfile.txt", b"these are the file contents!"
|
||||
)
|
||||
self.upload_file = UploadFile.objects.create(
|
||||
original_file_name="testfile.txt",
|
||||
file_name="testfile123.txt",
|
||||
file_type="text/plain",
|
||||
uploaded_by=self.user,
|
||||
file=self.dummy_file,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.upload_file.delete_file()
|
||||
|
||||
def test_upload_to_s3(self):
|
||||
# Verify if file is uploaded to S3
|
||||
response = self.s3_client.get_object(
|
||||
Bucket=settings.AWS_STORAGE_BUCKET_NAME, Key=str(self.upload_file.file)
|
||||
)
|
||||
self.assertEqual(response["Body"].read(), b"these are the file contents!")
|
||||
|
||||
def test_url_property(self):
|
||||
self.upload_file.upload_finished_at = datetime.datetime.now()
|
||||
self.upload_file.save()
|
||||
url = self.upload_file.url
|
||||
response = requests.get(url)
|
||||
# Assert that the URL is a valid presigned S3 URL and accessible
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, b"these are the file contents!")
|
||||
|
||||
def test_delete_file_method(self):
|
||||
file_path = str(self.upload_file.file)
|
||||
self.upload_file.delete_file()
|
||||
# Assert that the file is deleted from S3
|
||||
with self.assertRaises(self.s3_client.exceptions.NoSuchKey):
|
||||
self.s3_client.get_object(
|
||||
Bucket=settings.AWS_STORAGE_BUCKET_NAME, Key=file_path
|
||||
)
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
import os
|
||||
|
||||
import boto3
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.test import TestCase
|
||||
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.files.integrations import (
|
||||
s3_delete_file,
|
||||
s3_generate_presigned_post,
|
||||
s3_generate_presigned_url,
|
||||
s3_get_client,
|
||||
)
|
||||
|
||||
|
||||
class TestIntegrationsIntegrationTest(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.s3_client = s3_get_client()
|
||||
|
||||
def setUp(self):
|
||||
self.user = User.objects.create(username="testuser")
|
||||
|
||||
# Creating a dummy file for upload
|
||||
self.dummy_file = SimpleUploadedFile(
|
||||
"testfile.txt", b"these are the file contents!"
|
||||
)
|
||||
|
||||
def test_s3_generate_presigned_post(self):
|
||||
# Test generating a presigned POST for file upload
|
||||
presigned_post_data = s3_generate_presigned_post(
|
||||
file_path=f"{self.user.id}/testfile.txt",
|
||||
file_type="text/plain",
|
||||
file_name="testfile.txt",
|
||||
)
|
||||
self.assertIn("url", presigned_post_data)
|
||||
self.assertIn("fields", presigned_post_data)
|
||||
|
||||
# Upload file using the presigned URL
|
||||
files = {"file": self.dummy_file}
|
||||
response = requests.post(
|
||||
presigned_post_data["url"], data=presigned_post_data["fields"], files=files
|
||||
)
|
||||
self.assertEqual(response.status_code, 204)
|
||||
|
||||
def test_s3_generate_presigned_url(self):
|
||||
# First, manually upload a file to S3 for testing
|
||||
self.s3_client.upload_fileobj
|
||||
|
||||
self.s3_client.upload_fileobj(
|
||||
self.dummy_file, settings.AWS_STORAGE_BUCKET_NAME, "testfile.txt"
|
||||
)
|
||||
|
||||
# Test generating a presigned URL for the uploaded file
|
||||
presigned_url = s3_generate_presigned_url(file_path="testfile.txt")
|
||||
response = requests.get(presigned_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, b"these are the file contents!")
|
||||
|
||||
def test_s3_delete_file(self):
|
||||
# Upload a file to S3 for testing
|
||||
self.s3_client.upload_fileobj(
|
||||
self.dummy_file, settings.AWS_STORAGE_BUCKET_NAME, "testfile.txt"
|
||||
)
|
||||
|
||||
# Test deleting the file
|
||||
s3_delete_file(file_path="testfile.txt")
|
||||
# Assert that the file no longer exists
|
||||
with self.assertRaises(boto3.exceptions.botocore.client.ClientError):
|
||||
self.s3_client.head_object(
|
||||
Bucket=settings.AWS_STORAGE_BUCKET_NAME, Key="testfile.txt"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
# Clean up any remaining files in the S3 bucket
|
||||
s3_delete_file(file_path="testfile.txt")
|
||||
super().tearDownClass()
|
||||
|
|
@ -13,7 +13,7 @@ from vbv_lernwelt.course.models import CourseCategory, CoursePage
|
|||
from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
||||
CircleFactory,
|
||||
LearningContentAssignmentFactory,
|
||||
LearningContentFeedbackFactory,
|
||||
LearningContentFeedbackVVFactory,
|
||||
LearningContentLearningModuleFactory,
|
||||
LearningContentMediaLibraryFactory,
|
||||
LearningContentPlaceholderFactory,
|
||||
|
|
@ -201,7 +201,7 @@ def create_circle_basis(lp, title="Basis"):
|
|||
slug__startswith=f"versicherungsvermittler-in-assignment-reflexion"
|
||||
),
|
||||
),
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackVVFactory(
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
@ -278,7 +278,7 @@ def create_circle_gewinnen(lp, title="Gewinnen"):
|
|||
slug__startswith=f"{course_slug}-assignment-reflexion"
|
||||
),
|
||||
),
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackVVFactory(
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
@ -368,7 +368,7 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"):
|
|||
# slug__startswith=f"{circle.get_course().slug}-assignment-reflexion"
|
||||
# ),
|
||||
# ),
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackVVFactory(
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
@ -554,7 +554,7 @@ def create_circle_reisen(lp, title="Reisen"):
|
|||
slug__startswith=f"{circle.get_course().slug}-assignment-reflexion"
|
||||
),
|
||||
),
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackVVFactory(
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
@ -647,7 +647,7 @@ def create_circle_einkommenssicherung(lp, title="Einkommenssicherung"):
|
|||
slug__startswith=f"{circle.get_course().slug}-assignment-reflexion"
|
||||
),
|
||||
),
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackVVFactory(
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
@ -700,7 +700,7 @@ def create_circle_wohneigentum(lp, title="Wohneigentum"):
|
|||
slug__startswith=f"{circle.get_course().slug}-assignment-reflexion"
|
||||
),
|
||||
),
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackVVFactory(
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
@ -782,7 +782,7 @@ def create_circle_pensionierung(lp, title="Pensionierung"):
|
|||
slug__startswith=f"{circle.get_course().slug}-assignment-reflexion"
|
||||
),
|
||||
),
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackVVFactory(
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
@ -839,7 +839,7 @@ def create_circle_erben(lp, title="Erben/Vererben"):
|
|||
slug__startswith=f"{circle.get_course().slug}-assignment-reflexion"
|
||||
),
|
||||
),
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackVVFactory(
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
@ -929,7 +929,7 @@ def create_circle_gesundheit(lp, title="Gesundheit"):
|
|||
slug__startswith=f"{circle.get_course().slug}-assignment-reflexion"
|
||||
),
|
||||
),
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackVVFactory(
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
@ -1352,7 +1352,7 @@ def create_learning_sequence_transfer(parent, title, lc_praxis_title=None):
|
|||
slug__startswith=f"versicherungsvermittler-in-assignment-reflexion"
|
||||
),
|
||||
),
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackVVFactory(
|
||||
parent=parent,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ from vbv_lernwelt.learnpath.models import (
|
|||
LearningContentAttendanceCourse,
|
||||
LearningContentDocumentList,
|
||||
LearningContentEdoniqTest,
|
||||
LearningContentFeedback,
|
||||
LearningContentFeedbackUK,
|
||||
LearningContentFeedbackVV,
|
||||
LearningContentKnowledgeAssessment,
|
||||
LearningContentLearningModule,
|
||||
LearningContentMediaLibrary,
|
||||
|
|
@ -49,8 +50,10 @@ class LearningContentInterface(CoursePageInterface):
|
|||
return LearningContentAssignmentObjectType
|
||||
elif isinstance(instance, LearningContentAttendanceCourse):
|
||||
return LearningContentAttendanceCourseObjectType
|
||||
elif isinstance(instance, LearningContentFeedback):
|
||||
return LearningContentFeedbackObjectType
|
||||
elif isinstance(instance, LearningContentFeedbackUK):
|
||||
return LearningContentFeedbackUKObjectType
|
||||
elif isinstance(instance, LearningContentFeedbackVV):
|
||||
return LearningContentFeedbackVVObjectType
|
||||
elif isinstance(instance, LearningContentLearningModule):
|
||||
return LearningContentLearningModuleObjectType
|
||||
elif isinstance(instance, LearningContentKnowledgeAssessment):
|
||||
|
|
@ -105,9 +108,19 @@ class LearningContentPlaceholderObjectType(DjangoObjectType):
|
|||
fields = []
|
||||
|
||||
|
||||
class LearningContentFeedbackObjectType(DjangoObjectType):
|
||||
class LearningContentFeedbackUKObjectType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = LearningContentFeedback
|
||||
model = LearningContentFeedbackUK
|
||||
interfaces = (
|
||||
CoursePageInterface,
|
||||
LearningContentInterface,
|
||||
)
|
||||
fields = []
|
||||
|
||||
|
||||
class LearningContentFeedbackVVObjectType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = LearningContentFeedbackVV
|
||||
interfaces = (
|
||||
CoursePageInterface,
|
||||
LearningContentInterface,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
# Generated by Django 3.2.20 on 2023-11-29 07:27
|
||||
|
||||
import django.db.models.deletion
|
||||
import wagtail.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("wagtailcore", "0089_log_entry_data_json_null_to_object"),
|
||||
("learnpath", "0011_learningcontentknowledgeassessment"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel("LearningContentFeedback", "LearningContentFeedbackUK"),
|
||||
migrations.CreateModel(
|
||||
name="LearningContentFeedbackVV",
|
||||
fields=[
|
||||
(
|
||||
"page_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="wagtailcore.page",
|
||||
),
|
||||
),
|
||||
("minutes", models.PositiveIntegerField(default=15)),
|
||||
("description", wagtail.fields.RichTextField(blank=True)),
|
||||
("content_url", models.TextField(blank=True)),
|
||||
("has_course_completion_status", models.BooleanField(default=True)),
|
||||
(
|
||||
"can_user_self_toggle_course_completion",
|
||||
models.BooleanField(default=False),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=("wagtailcore.page",),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="learningcontentassignment",
|
||||
name="assignment_type",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("VOLUNTARY_CASEWORK", "VOLUNTARY_CASEWORK"),
|
||||
("MANDATORY_CASEWORK", "MANDATORY_CASEWORK"),
|
||||
("PREP_ASSIGNMENT", "PREP_ASSIGNMENT"),
|
||||
("REFLECTION", "REFLECTION"),
|
||||
("CONDITION_ACCEPTANCE", "CONDITION_ACCEPTANCE"),
|
||||
("EDONIQ_TEST", "EDONIQ_TEST"),
|
||||
],
|
||||
default="MANDATORY_CASEWORK",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -72,7 +72,8 @@ class Circle(CourseBasePage):
|
|||
"learnpath.LearningUnit",
|
||||
"learnpath.LearningContentAssignment",
|
||||
"learnpath.LearningContentAttendanceCourse",
|
||||
"learnpath.LearningContentFeedback",
|
||||
"learnpath.LearningContentFeedbackUK",
|
||||
"learnpath.LearningContentFeedbackVV",
|
||||
"learnpath.LearningContentLearningModule",
|
||||
"learnpath.LearningContentKnowledgeAssessment",
|
||||
"learnpath.LearningContentMediaLibrary",
|
||||
|
|
@ -318,7 +319,13 @@ class LearningContentPlaceholder(LearningContent):
|
|||
can_user_self_toggle_course_completion = models.BooleanField(default=True)
|
||||
|
||||
|
||||
class LearningContentFeedback(LearningContent):
|
||||
class LearningContentFeedbackUK(LearningContent):
|
||||
parent_page_types = ["learnpath.Circle"]
|
||||
subpage_types = []
|
||||
can_user_self_toggle_course_completion = models.BooleanField(default=False)
|
||||
|
||||
|
||||
class LearningContentFeedbackVV(LearningContent):
|
||||
parent_page_types = ["learnpath.Circle"]
|
||||
subpage_types = []
|
||||
can_user_self_toggle_course_completion = models.BooleanField(default=False)
|
||||
|
|
@ -441,6 +448,9 @@ class LearningContentAssignment(LearningContent):
|
|||
self.assignment_type = self.content_assignment.assignment_type
|
||||
super().save(**kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.id} - {self.title}"
|
||||
|
||||
@classmethod
|
||||
def get_serializer_class(cls):
|
||||
from vbv_lernwelt.learnpath.serializers import (
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ from vbv_lernwelt.learnpath.models import (
|
|||
LearningContentAttendanceCourse,
|
||||
LearningContentDocumentList,
|
||||
LearningContentEdoniqTest,
|
||||
LearningContentFeedback,
|
||||
LearningContentFeedbackUK,
|
||||
LearningContentFeedbackVV,
|
||||
LearningContentKnowledgeAssessment,
|
||||
LearningContentLearningModule,
|
||||
LearningContentMediaLibrary,
|
||||
|
|
@ -120,14 +121,24 @@ class LearningContentPlaceholderFactory(wagtail_factories.PageFactory):
|
|||
model = LearningContentPlaceholder
|
||||
|
||||
|
||||
class LearningContentFeedbackFactory(wagtail_factories.PageFactory):
|
||||
title = "Feedback"
|
||||
class LearningContentFeedbackVVFactory(wagtail_factories.PageFactory):
|
||||
title = "FeedbackVV"
|
||||
minutes = 0
|
||||
content_url = ""
|
||||
description = RichText("")
|
||||
|
||||
class Meta:
|
||||
model = LearningContentFeedback
|
||||
model = LearningContentFeedbackVV
|
||||
|
||||
|
||||
class LearningContentFeedbackUKFactory(wagtail_factories.PageFactory):
|
||||
title = "FeedbackUK"
|
||||
minutes = 0
|
||||
content_url = ""
|
||||
description = RichText("")
|
||||
|
||||
class Meta:
|
||||
model = LearningContentFeedbackUK
|
||||
|
||||
|
||||
class LearningContentLearningModuleFactory(wagtail_factories.PageFactory):
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class TestRetrieveLearingPathContents(APITestCase):
|
|||
# topics and circles
|
||||
self.assertEqual(4, len(data["children"]))
|
||||
# circle "analyse" contents
|
||||
self.assertEqual(14, len(data["children"][3]["children"]))
|
||||
self.assertEqual(15, len(data["children"][3]["children"]))
|
||||
|
||||
def test_normalUser_withoutCourseSession_cannotAccess(self):
|
||||
self.user = User.objects.get(username="student")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from vbv_lernwelt.media_files.models import UserDocument, UserImage
|
||||
|
||||
|
||||
@admin.register(UserDocument)
|
||||
class UserDocumentAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"title",
|
||||
"file",
|
||||
"created_at",
|
||||
"uploaded_by_user",
|
||||
"file_size",
|
||||
"file_hash",
|
||||
)
|
||||
search_fields = ("title", "uploaded_by_user__username", "tags__name")
|
||||
list_filter = ("created_at", "uploaded_by_user")
|
||||
autocomplete_fields = ["uploaded_by_user"]
|
||||
date_hierarchy = "created_at"
|
||||
readonly_fields = (
|
||||
"file_size",
|
||||
"file_hash",
|
||||
"created_at",
|
||||
"uploaded_by_user",
|
||||
"file",
|
||||
)
|
||||
|
||||
|
||||
@admin.register(UserImage)
|
||||
class UserImageAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"title",
|
||||
"file",
|
||||
"created_at",
|
||||
"uploaded_by_user",
|
||||
"file_size",
|
||||
)
|
||||
search_fields = ("title", "uploaded_by_user__username")
|
||||
list_filter = ("created_at", "uploaded_by_user")
|
||||
autocomplete_fields = ["uploaded_by_user"]
|
||||
date_hierarchy = "created_at"
|
||||
readonly_fields = (
|
||||
"file_size",
|
||||
"file_hash",
|
||||
"created_at",
|
||||
"uploaded_by_user",
|
||||
"file",
|
||||
"tags",
|
||||
"title",
|
||||
"focal_point_x",
|
||||
"focal_point_y",
|
||||
"focal_point_width",
|
||||
"focal_point_height",
|
||||
)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MediaLibraryConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "vbv_lernwelt.media_files"
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import os
|
||||
|
||||
import factory
|
||||
from django.conf import settings
|
||||
from wagtail.models import Collection
|
||||
|
||||
from vbv_lernwelt.media_files.models import ContentDocument, UserDocument
|
||||
from vbv_lernwelt.media_files.tests.media_library_factories import (
|
||||
ContentDocumentFactory,
|
||||
UserDocumentFactory,
|
||||
)
|
||||
|
||||
|
||||
def delete_default_documents():
|
||||
"""deletes all documents"""
|
||||
if "prod" in settings.APP_ENVIRONMENT:
|
||||
raise Exception("This command must not be used in production environment")
|
||||
|
||||
ContentDocument.objects.all().delete()
|
||||
UserDocument.objects.all().delete()
|
||||
|
||||
|
||||
def create_default_collections():
|
||||
root, created = Collection.objects.get_or_create(name="Root", depth=0)
|
||||
|
||||
|
||||
def create_default_content_documents():
|
||||
"""creates a default document for testing purposes"""
|
||||
|
||||
path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "./tests/test_documents/"
|
||||
)
|
||||
|
||||
filename = "Vermittler_Motorfahrzeug_Versicherung_Musterlösung.pdf"
|
||||
document = ContentDocumentFactory(
|
||||
title="Musterlösung Fahrzeug",
|
||||
display_text="Musterlösung Fahrzeug",
|
||||
description="Musterlösung für den Auftrag Fahrzeug",
|
||||
link_display_text="Dokument laden",
|
||||
file=factory.django.FileField(
|
||||
from_path=os.path.join(path, filename), filename=filename
|
||||
),
|
||||
)
|
||||
document.tags.set(("Fahrzeug", "Musterlösung", "Vermittler"))
|
||||
document.save()
|
||||
|
||||
filename = "TestExcelSheet.xlsx"
|
||||
document = ContentDocumentFactory(
|
||||
title="Mustertabelle",
|
||||
display_text="Mustertabelle",
|
||||
link_display_text="Dokument laden",
|
||||
file=factory.django.FileField(
|
||||
from_path=os.path.join(path, filename), filename=filename
|
||||
),
|
||||
)
|
||||
document.tags.set(("Vermittler"))
|
||||
document.save()
|
||||
|
||||
|
||||
def create_default_user_documents():
|
||||
"""creates a default document for testing purposes"""
|
||||
|
||||
path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "./tests/test_documents/"
|
||||
)
|
||||
|
||||
filename = "FallanalyseTeststudent.pdf"
|
||||
document = UserDocumentFactory(
|
||||
title="Lösung Fallanalyse",
|
||||
file=factory.django.FileField(
|
||||
from_path=os.path.join(path, filename), filename=filename
|
||||
),
|
||||
)
|
||||
document.save()
|
||||
|
||||
filename = "FallanalyseTeststudent.docx"
|
||||
document = UserDocumentFactory(
|
||||
title="Lösung Fallanalyse",
|
||||
file=factory.django.FileField(
|
||||
from_path=os.path.join(path, filename), filename=filename
|
||||
),
|
||||
)
|
||||
document.save()
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files import File
|
||||
|
||||
from vbv_lernwelt.media_files.models import ContentImage, UserImage
|
||||
|
||||
|
||||
def delete_default_images():
|
||||
"""deletes all images"""
|
||||
if "prod" in settings.APP_ENVIRONMENT:
|
||||
raise Exception("This command must not be used in production environment")
|
||||
ContentImage.objects.all().delete()
|
||||
UserImage.objects.all().delete()
|
||||
|
||||
|
||||
def create_default_images():
|
||||
create_default_content_images()
|
||||
create_default_user_images()
|
||||
|
||||
|
||||
def create_default_content_images():
|
||||
path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "./tests/test_images/"
|
||||
)
|
||||
|
||||
images = [
|
||||
("bike_accident.jpg", "Bike Accident"),
|
||||
("car_accident.jpg", "Car Accident"),
|
||||
]
|
||||
|
||||
for filename, title in images:
|
||||
file_path = os.path.join(path, filename)
|
||||
with open(file_path, "rb") as f:
|
||||
image, _ = ContentImage.objects.get_or_create(
|
||||
title=title,
|
||||
file=File(f, name=filename),
|
||||
focal_point_x=600,
|
||||
focal_point_y=600,
|
||||
focal_point_width=300,
|
||||
focal_point_height=300,
|
||||
)
|
||||
image.tags.set(("Fahrzeug", "Unfall", "Vermittler"))
|
||||
image.save()
|
||||
|
||||
|
||||
def create_default_user_images():
|
||||
path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "./tests/test_images/"
|
||||
)
|
||||
filename, title = ("user1_profile.jpg", "User1 Profile")
|
||||
file_path = os.path.join(path, filename)
|
||||
|
||||
with open(file_path, "rb") as f:
|
||||
image, _ = UserImage.objects.get_or_create(
|
||||
title=title,
|
||||
file=File(f, name=filename),
|
||||
focal_point_x=600,
|
||||
focal_point_y=600,
|
||||
focal_point_width=300,
|
||||
focal_point_height=300,
|
||||
)
|
||||
image.save()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue