Merged in feature/VBV-563-feedback-vv (pull request #242)
Feature/VBV-563 feedback vv Approved-by: Daniel Egger
This commit is contained in:
commit
987159a531
|
|
@ -24,7 +24,7 @@ const documents = {
|
|||
"\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,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -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
|
||||
|
|
@ -718,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!
|
||||
|
|
@ -844,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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,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";
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
<FeedbackPageVV v-if="feedbackType === 'vv'" :feedback-data="feedbackData" />
|
||||
<FeedbackPageUK
|
||||
v-else-if="feedbackType === 'uk'"
|
||||
:feedback-data="feedbackData"
|
||||
/>
|
||||
<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>
|
||||
</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>
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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,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
|
||||
|
|
@ -560,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 {
|
||||
|
|
|
|||
|
|
@ -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,6 +5,10 @@ describe("feedbackStudent.cy.js", () => {
|
|||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset");
|
||||
login("test-student1@example.com", "test");
|
||||
});
|
||||
|
||||
describe("Feedback UK", () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/course/test-lehrgang/learn/fahrzeug/feedback");
|
||||
});
|
||||
|
||||
|
|
@ -26,6 +30,10 @@ describe("feedbackStudent.cy.js", () => {
|
|||
// 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);
|
||||
|
|
@ -34,6 +42,10 @@ describe("feedbackStudent.cy.js", () => {
|
|||
|
||||
// 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(
|
||||
|
|
@ -50,6 +62,10 @@ describe("feedbackStudent.cy.js", () => {
|
|||
|
||||
// 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);
|
||||
|
|
@ -58,6 +74,10 @@ describe("feedbackStudent.cy.js", () => {
|
|||
|
||||
// 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);
|
||||
|
|
@ -66,6 +86,10 @@ describe("feedbackStudent.cy.js", () => {
|
|||
|
||||
// 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);
|
||||
|
|
@ -74,6 +98,10 @@ describe("feedbackStudent.cy.js", () => {
|
|||
|
||||
// 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);
|
||||
|
|
@ -82,6 +110,10 @@ describe("feedbackStudent.cy.js", () => {
|
|||
|
||||
// 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."
|
||||
|
|
@ -92,6 +124,10 @@ describe("feedbackStudent.cy.js", () => {
|
|||
|
||||
// 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);
|
||||
|
|
@ -100,6 +136,11 @@ describe("feedbackStudent.cy.js", () => {
|
|||
|
||||
// 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."
|
||||
|
|
@ -110,6 +151,10 @@ describe("feedbackStudent.cy.js", () => {
|
|||
|
||||
// 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."
|
||||
|
|
@ -141,17 +186,178 @@ describe("feedbackStudent.cy.js", () => {
|
|||
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.",
|
||||
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_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",
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Feedback VV", () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/course/test-lehrgang/learn/reisen/feedback");
|
||||
});
|
||||
|
||||
it("can open feedback page", () => {
|
||||
cy.testLearningContentTitle("Feedback");
|
||||
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(/\/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."
|
||||
);
|
||||
|
||||
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,6 +16,7 @@ describe("feedbackTrainer.cy.js", () => {
|
|||
cy.get('[data-cy="feedback-data-amount"]').should("contain", "0");
|
||||
});
|
||||
|
||||
describe("FeedbackUK", function () {
|
||||
it("can open feedback results page with results", () => {
|
||||
cy.manageCommand("cypress_reset --create-feedback-responses");
|
||||
login("test-trainer1@example.com", "test");
|
||||
|
|
@ -26,6 +27,48 @@ describe("feedbackTrainer.cy.js", () => {
|
|||
|
||||
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 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-1"]')
|
||||
.find('[data-cy="rating-scale-average"]')
|
||||
.should("contain", "3.3");
|
||||
|
|
@ -89,4 +132,100 @@ describe("feedbackTrainer.cy.js", () => {
|
|||
.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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -32,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
|
||||
|
||||
|
|
@ -155,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(
|
||||
|
|
@ -174,6 +177,7 @@ def command(
|
|||
"would_recommend": True,
|
||||
"course_negative_feedback": "Nichts Schlechtes",
|
||||
"course_positive_feedback": "Nur Gutes.",
|
||||
"feedback_type": "uk",
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -193,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",
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -212,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",
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
|||
LearningContentAssignmentFactory,
|
||||
LearningContentAttendanceCourseFactory,
|
||||
LearningContentEdoniqTestFactory,
|
||||
LearningContentFeedbackFactory,
|
||||
LearningContentFeedbackUKFactory,
|
||||
LearningContentFeedbackVVFactory,
|
||||
LearningContentKnowledgeAssessmentFactory,
|
||||
LearningContentLearningModuleFactory,
|
||||
LearningContentMediaLibraryFactory,
|
||||
|
|
@ -371,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(
|
||||
|
|
@ -549,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,
|
||||
)
|
||||
|
||||
|
|
@ -633,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,
|
||||
|
|
@ -260,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")
|
||||
|
|
@ -370,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")
|
||||
|
|
@ -485,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")
|
||||
|
|
@ -600,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")
|
||||
|
|
@ -705,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")
|
||||
|
|
@ -815,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")
|
||||
|
|
@ -924,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")
|
||||
|
|
@ -1064,7 +1064,7 @@ def create_uk_circle_fahrzeug(lp, title="Fahrzeug"):
|
|||
],
|
||||
)
|
||||
LearningUnitFactory(title="Feedback", title_hidden=True, parent=circle)
|
||||
LearningContentFeedbackFactory(
|
||||
LearningContentFeedbackUKFactory(
|
||||
parent=circle,
|
||||
)
|
||||
|
||||
|
|
@ -1198,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,
|
||||
)
|
||||
|
||||
|
|
@ -1336,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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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] = []
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Reference in New Issue