wip: Update UK feedback component
This commit is contained in:
parent
2a6b6c9658
commit
fa76989bbf
|
|
@ -25,7 +25,6 @@ const documents = {
|
|||
"\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n": types.DashboardProgressDocument,
|
||||
"\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n": types.CourseStatisticsDocument,
|
||||
"\n mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $learningContentType: String!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n learning_content_type: $learningContentType\n data: $data\n submitted: $submitted\n ) {\n feedback_response {\n id\n data\n submitted\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument,
|
||||
"\n mutation SendFeedbackMutation2(\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.SendFeedbackMutation2Document,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -90,10 +89,6 @@ 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 $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"];
|
||||
/**
|
||||
* 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 SendFeedbackMutation2(\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 SendFeedbackMutation2(\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
|
|
@ -1,36 +1,24 @@
|
|||
<script setup lang="ts">
|
||||
import { graphql } from "@/gql";
|
||||
import ItRadioGroup from "@/components/ui/ItRadioGroup.vue";
|
||||
import ItTextarea from "@/components/ui/ItTextarea.vue";
|
||||
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 { useMutation } from "@urql/vue";
|
||||
import type { LearningContentFeedback } from "@/types";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
import log from "loglevel";
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { LearningContentFeedbackUK, LearningContentFeedbackVV } from "@/types";
|
||||
import { computed } from "vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||
import { useCourseSessionDetailQuery } from "@/composables";
|
||||
import FeedbackBase from "@/pages/learningPath/learningContentPage/feedback/FeedbackBase.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
content: LearningContentFeedback;
|
||||
content: LearningContentFeedbackVV | LearningContentFeedbackUK;
|
||||
}>();
|
||||
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 circleExperts = computed(() => {
|
||||
if (props.content?.circle?.slug) {
|
||||
return courseSessionDetailResult.filterCircleExperts(props.content.circle.slug);
|
||||
|
|
@ -53,58 +41,6 @@ const stepLabels = [
|
|||
t("general.submission"),
|
||||
];
|
||||
|
||||
const numSteps = stepLabels.length;
|
||||
|
||||
// noinspection GraphQLUnresolvedReference -> mute IntelliJ warning
|
||||
const sendFeedbackMutation = graphql(`
|
||||
mutation SendFeedbackMutation2(
|
||||
$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
|
||||
) {
|
||||
feedback_response {
|
||||
id
|
||||
data
|
||||
submitted
|
||||
}
|
||||
errors {
|
||||
field
|
||||
messages
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const feedbackSubmitted = ref(false);
|
||||
|
||||
const { executeMutation } = useMutation(sendFeedbackMutation);
|
||||
|
||||
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",
|
||||
|
|
@ -154,134 +90,25 @@ const questionData = [
|
|||
component: ItTextarea,
|
||||
},
|
||||
];
|
||||
|
||||
const previousStep = () => {
|
||||
if (stepNo.value > 0) {
|
||||
stepNo.value -= 1;
|
||||
}
|
||||
};
|
||||
|
||||
const nextStep = () => {
|
||||
if (stepNo.value < numSteps && hasStepValidInput(stepNo.value)) {
|
||||
stepNo.value += 1;
|
||||
}
|
||||
log.debug(`next step ${stepNo.value} of ${numSteps}`);
|
||||
mutateFeedback(feedbackData);
|
||||
};
|
||||
|
||||
function hasStepValidInput(stepNumber: number) {
|
||||
const question = questionData[stepNumber - 1];
|
||||
if (question) {
|
||||
if (
|
||||
[
|
||||
"instructor_open_feedback",
|
||||
"course_negative_feedback",
|
||||
"course_positive_feedback",
|
||||
].includes(question.modelKey)
|
||||
) {
|
||||
// text response questions need to have a "truthy" value (not "" or null)
|
||||
return feedbackData[question.modelKey];
|
||||
} else {
|
||||
// other responses need to have data, can be `0` or `false`
|
||||
return feedbackData[question.modelKey] !== null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function mutateFeedback(data: FeedbackData, submit = false) {
|
||||
log.debug("mutate feedback", feedbackData);
|
||||
return executeMutation({
|
||||
courseSessionId: courseSession.value.id,
|
||||
learningContentId: props.content.id,
|
||||
data: data,
|
||||
submitted: submit,
|
||||
})
|
||||
.then((result) => {
|
||||
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 = "";
|
||||
}
|
||||
Object.assign(feedbackData, responseData);
|
||||
log.debug("feedback data", feedbackData);
|
||||
feedbackSubmitted.value =
|
||||
result.data?.send_feedback?.feedback_response?.submitted || false;
|
||||
}
|
||||
})
|
||||
.catch((e) => log.error(e));
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
log.debug("Feedback mounted");
|
||||
await mutateFeedback({});
|
||||
if (feedbackSubmitted.value) {
|
||||
stepNo.value = numSteps - 1;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LearningContentMultiLayout
|
||||
:title="title"
|
||||
sub-title="Feedback"
|
||||
:learning-content="content"
|
||||
:show-start-button="stepNo === 0"
|
||||
:show-next-button="stepNo > 0 && stepNo + 1 < numSteps"
|
||||
:disable-next-button="!hasStepValidInput(stepNo)"
|
||||
:show-previous-button="stepNo > 0 && !feedbackSubmitted"
|
||||
:show-exit-button="stepNo + 1 === numSteps"
|
||||
:current-step="stepNo"
|
||||
:steps-count="numSteps"
|
||||
:start-badge-text="$t('general.introduction')"
|
||||
:end-badge-text="$t('general.submission')"
|
||||
:base-url="props.content.frontend_url"
|
||||
close-button-variant="close"
|
||||
@previous="previousStep()"
|
||||
@next="nextStep()"
|
||||
>
|
||||
<div>
|
||||
<p v-if="stepNo === 0" class="mt-10">
|
||||
{{
|
||||
$t("feedback.intro", {
|
||||
name: `${circleExperts[0]?.first_name} ${circleExperts[0]?.last_name}`,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
<p v-if="stepNo > 0 && stepNo + 1 < numSteps" class="pb-2">
|
||||
{{ stepLabels[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-model="feedbackData[question.modelKey] as any"
|
||||
:items="question['items']"
|
||||
:cy-key="question.modelKey"
|
||||
/>
|
||||
<!-- 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')"
|
||||
:feedback-sent="feedbackSubmitted"
|
||||
@send-feedback="mutateFeedback(feedbackData, true)"
|
||||
/>
|
||||
</div>
|
||||
</LearningContentMultiLayout>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import {
|
|||
RATINGS,
|
||||
YES_NO,
|
||||
} from "@/pages/learningPath/learningContentPage/feedback/feedback.constants";
|
||||
import type { LearningContentFeedback } from "@/types";
|
||||
import { LearningContentFeedbackUK, LearningContentFeedbackVV } from "@/types";
|
||||
import FeedbackBase from "@/pages/learningPath/learningContentPage/feedback/FeedbackBase.vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
|
||||
const props = defineProps<{
|
||||
content: LearningContentFeedback;
|
||||
content: LearningContentFeedbackVV | LearningContentFeedbackUK;
|
||||
}>();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
|||
LearningContentAttendanceCourseFactory,
|
||||
LearningContentEdoniqTestFactory,
|
||||
LearningContentFeedbackUKFactory,
|
||||
LearningContentFeedbackVVFactory,
|
||||
LearningContentKnowledgeAssessmentFactory,
|
||||
LearningContentLearningModuleFactory,
|
||||
LearningContentMediaLibraryFactory,
|
||||
|
|
@ -80,7 +81,7 @@ from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
|||
LearningPathFactory,
|
||||
LearningSequenceFactory,
|
||||
LearningUnitFactory,
|
||||
TopicFactory, LearningContentFeedbackVVFactory,
|
||||
TopicFactory,
|
||||
)
|
||||
from vbv_lernwelt.media_library.tests.media_library_factories import (
|
||||
MediaLibraryCategoryPageFactory,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ from vbv_lernwelt.feedback.graphql.types import (
|
|||
from vbv_lernwelt.feedback.serializers import CourseFeedbackSerializer
|
||||
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 LearningContentFeedbackVV, LearningContentFeedbackUK
|
||||
from vbv_lernwelt.learnpath.models import (
|
||||
LearningContentFeedbackUK,
|
||||
LearningContentFeedbackVV,
|
||||
)
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue