Merged develop into bugfix/VBV-769-expert-feedback-view

This commit is contained in:
Christian Cueni 2024-11-06 14:18:54 +00:00
commit 78ba5cc37f
63 changed files with 1026 additions and 214 deletions

View File

@ -36,8 +36,8 @@ async function changeLocale(language: AvailableLanguages) {
</a>
</div>
<router-link
class="lg:ml-8"
v-if="isVVLearningMentor(courseSessionsStore.currentCourseSession)"
class="lg:ml-8"
:to="vvBuyLink.href.value"
data-cy="buy-vv-link"
>

View File

@ -55,6 +55,7 @@ const submittables = computed(() => {
lc.content_type === "learnpath.LearningContentAssignment" ||
lc.content_type === "learnpath.LearningContentFeedbackUK" ||
lc.content_type === "learnpath.LearningContentFeedbackVV" ||
lc.content_type === "learnpath.LearningContentFeedbackAutomobilGewerbe" ||
lc.content_type === "learnpath.LearningContentEdoniqTest"
);
@ -76,7 +77,8 @@ const submittables = computed(() => {
const isFeedback = (lc: LearningContent) => {
return (
lc.content_type === "learnpath.LearningContentFeedbackUK" ||
lc.content_type === "learnpath.LearningContentFeedbackVV"
lc.content_type === "learnpath.LearningContentFeedbackVV" ||
lc.content_type === "learnpath.LearningContentFeedbackAutomobilGewerbe"
);
};

View File

@ -29,7 +29,7 @@ onMounted(async () => {
</script>
<template>
<div class="w-[325px]">
<div class="w-60 sm:w-[325px]">
<BaseBox
:details-link="`/dashboard/persons?course=${props.courseId}`"
data-cy="dashboard.mentor.menteeCount"
@ -37,14 +37,16 @@ onMounted(async () => {
>
<template #title>{{ $t("a.Personen") }}</template>
<template #content>
<div :class="['flex flex-row space-x-3 bg-white', slim ? '' : 'pb-6']">
<div
:class="['flex flex-row flex-wrap items-center bg-white', slim ? '' : 'pb-6']"
>
<div
class="flex h-[74px] items-center justify-center py-1 pr-3 text-3xl font-bold"
data-cy="dashboard.mentor.menteeCountValue"
>
<span>{{ menteeCount }}</span>
</div>
<p class="ml-3 mt-0 leading-[74px]">
<p class="mt-0">
{{ $t("a.Personen, die du begleitest") }}
</p>
</div>

View File

@ -31,7 +31,7 @@ onMounted(async () => {
<template>
<div v-if="assignment">
<div class="w-[395px]">
<div class="sm:w-[395px]">
<AssignmentProgressSummaryBox
:total-assignments="assignment.total_count"
:achieved-points-count="assignment.points_achieved_count"

View File

@ -24,7 +24,7 @@ const data = computed(() => ({
</script>
w
<template>
<div class="flex flex-row items-center space-x-8">
<div class="flex flex-row flex-wrap items-center gap-y-4 space-x-8 sm:gap-y-0">
<div class="size-32">
<Pie
:data="data"
@ -39,7 +39,7 @@ w
}"
/>
</div>
<div class="flex flex-col space-y-2">
<div class="flex flex-col items-start">
<div
v-for="(value, key) in props.data"
:key="key"

View File

@ -25,7 +25,7 @@ onMounted(async () => {
</script>
<template>
<div v-if="competence" class="w-[395px]">
<div v-if="competence" class="sm:w-[395px]">
<CompetenceSummaryBox
:fail-count="competence.fail_count"
:success-count="competence.success_count"

View File

@ -9,6 +9,7 @@ import UkStatistics from "@/components/dashboard/UkStatistics.vue";
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
import type { DashboardCourseConfigType, WidgetType } from "@/services/dashboard";
import { getCockpitUrl, getLearningMentorUrl, getLearningPathUrl } from "@/utils/utils";
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
import { computed } from "vue";
import TrainingResponsibleStatistics from "./TrainingResponsibleStatistics.vue";
@ -23,6 +24,9 @@ const props = defineProps<{
courseConfig: DashboardCourseConfigType | undefined;
}>();
const breakpoints = useBreakpoints(breakpointsTailwind);
const smAndLarger = breakpoints.greaterOrEqual("sm");
const courseSlug = computed(() => props.courseConfig?.course_slug ?? "");
const courseName = computed(() => props.courseConfig?.course_title ?? "");
const numberOfMentorWidgets = computed(() => {
@ -96,8 +100,10 @@ function hasActionButton(): boolean {
>
<div class="flex flex-col space-y-8 bg-white p-6">
<div class="border-b border-gray-300 pb-8">
<div class="flex flex-row items-start justify-between">
<h3 class="mb-4 text-3xl" data-cy="db-course-title">{{ courseName }}</h3>
<div class="flex flex-row flex-wrap items-start justify-between pb-3 sm:pb-0">
<h3 class="mb-4 text-xl sm:text-3xl" data-cy="db-course-title">
{{ courseName }}
</h3>
<a
v-if="hasActionButton()"
:href="actionButtonProps.href"
@ -114,7 +120,7 @@ function hasActionButton(): boolean {
<router-link
v-if="courseConfig.has_preview"
:to="getLearningPathUrl(courseConfig.course_slug)"
class="inline-block pl-6"
class="inline-block pt-3 sm:pl-6 sm:pt-0"
target="_blank"
>
<div class="flex items-center">
@ -137,7 +143,7 @@ function hasActionButton(): boolean {
:key="courseSlug"
:course-slug="courseSlug"
:course-session-id="courseConfig.session_to_continue_id"
diagram-type="horizontal"
:diagram-type="smAndLarger ? 'horizontal' : 'horizontalSmall'"
></LearningPathDiagram>
</div>
@ -179,7 +185,7 @@ function hasActionButton(): boolean {
<div
v-if="numberOfMentorWidgets > 0"
class="flex flex-col flex-wrap items-stretch md:flex-row"
class="flex flex-col flex-wrap items-stretch space-y-4 sm:space-y-0 md:flex-row"
>
<AgentConnectionCount
v-if="hasWidget('MentorPersonWidget')"

View File

@ -29,7 +29,7 @@ onMounted(async () => {
</script>
<template>
<div v-if="summary" class="w-[325px]">
<div v-if="summary" class="w-60 sm:w-[325px]">
<BaseBox
:details-link="`/statistic/${props.agentRole}/${courseSlug}/assignment`"
data-cy="dashboard.mentor.competenceSummary"

View File

@ -18,14 +18,14 @@ onMounted(async () => {
</script>
<template>
<div class="w-[325px]">
<div class="w-60 sm:w-[325px]">
<BaseBox
:details-link="`/course/${props.courseSlug}/learning-mentor/tasks`"
data-cy="dashboard.mentor.openTasksCount"
>
<template #title>{{ $t("a.Zu erledigen") }}</template>
<template #content>
<div class="flex flex-row space-x-3 bg-white pb-6">
<div class="flex flex-row flex-wrap space-x-3 bg-white pb-6">
<div
class="flex h-[74px] w-[74px] items-center justify-center rounded-full border-2 border-green-500 px-3 py-1 text-3xl font-bold"
>

View File

@ -54,7 +54,7 @@ const attendanceCountPerChosenProfile = computed(() => {
<template>
<div class="flex flex-col">
<div class="flex flex-col flex-wrap items-stretch md:flex-row">
<div class="flex flex-col flex-wrap items-stretch gap-y-4 sm:gap-y-0 md:flex-row">
<BaseBox
:details-link="`/dashboard/cost/${courseSessionId}`"
data-cy="dashboard.stats.trainingResponsible.cost"

View File

@ -64,7 +64,7 @@ onMounted(async () => {
<template>
<div v-if="statistics" class="space-y-8">
<div
class="flex flex-col flex-wrap justify-between gap-x-5 border-b border-gray-300 pb-8 last:border-0 md:flex-row"
class="flex flex-col flex-wrap justify-between gap-x-5 gap-y-5 border-b border-gray-300 pb-8 last:border-0 md:flex-row"
>
<AttendanceSummaryBox
class="flex-grow"
@ -80,7 +80,7 @@ onMounted(async () => {
/>
</div>
<div
class="flex flex-col flex-wrap gap-x-5 border-b border-gray-300 align-top last:border-0 md:flex-row"
class="flex flex-col flex-wrap gap-x-5 gap-y-5 border-b border-gray-300 align-top last:border-0 md:flex-row"
>
<FeedbackSummaryBox
:feedback-count="feebackSummary.total_responses"

View File

@ -15,7 +15,7 @@ const { t } = useTranslation();
<nav class="bg-yellow-500">
<div class="mx-auto px-4 lg:px-8">
<div
class="relative flex h-16 w-full flex-col items-center justify-end space-x-8 lg:flex-row lg:items-stretch lg:justify-center"
class="relative flex h-auto min-h-16 w-full flex-col items-center justify-end space-x-8 lg:flex-row lg:items-stretch lg:justify-center"
>
<span class="flex items-center px-1 pt-1 font-bold text-black">
{{ t("a.Vorschau Teilnehmer") }} ({{ courseSession.title }})

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import { useCourseSessionsStore } from "@/stores/courseSessions";
import CoursePreviewBar from "./CoursePreviewBar.vue";
import MainNavigationBar from "./MainNavigationBar.vue";
const courseSessionsStore = useCourseSessionsStore();

View File

@ -26,14 +26,18 @@ const hasPendingTasks = computed(() => props.pendingTasks > 0);
<div
class="flex flex-col items-start justify-between gap-4 border-b py-2 pl-5 pr-5 last:border-b-0 md:flex-row md:items-center md:justify-between md:gap-16"
>
<div class="flex flex-grow flex-row items-center justify-start">
<div
class="flex flex-grow flex-row flex-wrap items-center justify-start space-y-4 sm:space-y-0"
>
<div class="w-80">
<div class="font-bold">{{ taskTitle }}</div>
<div class="text-small text-gray-900">
{{ $t("a.Circle") }} «{{ circleTitle }}»
</div>
</div>
<div class="flex flex-grow flex-row items-center justify-start space-x-2 pl-20">
<div
class="flex flex-grow flex-row items-center justify-start space-x-2 sm:pl-20"
>
<template v-if="hasPendingTasks">
<div
class="flex h-7 w-7 items-center justify-center rounded-full border-2 border-green-500 px-3 py-1 text-sm font-bold"

View File

@ -37,6 +37,7 @@ const circles = computed(() => {
(c) => props.showCircleSlugs?.includes(c.slug) ?? true
);
}
return lpQueryResult.circles.value ?? [];
});
@ -45,7 +46,10 @@ const wrapperClasses = computed(() => {
if (props.diagramType === "horizontal") {
classes += " flex-row h-8 space-x-2";
} else if (props.diagramType === "horizontalSmall") {
classes += " flex-row h-5 space-x-1";
classes +=
filteredCircles.value.length > 15
? " flex-row h-3 space-x-[1.1px]"
: " flex-row h-4 space-x-[1.8px]";
} else if (props.diagramType === "singleSmall") {
classes += " h-8";
}

View File

@ -32,5 +32,23 @@ const paymentMethods = [
<p class="mt-4">
{{ $t("shop.paymentCembraByjunoMessage") }}
</p>
<p class="mt-4">
<i18next
:translation="
$t('a.Es gelten die {cembraTos} und die {cembraPrivacy} der CembraPay AG.')
"
>
<template #cembraTos>
<a :href="t('cembraTosLink')" target="_blank" class="underline">
{{ $t("a.AGB") }}
</a>
</template>
<template #cembraPrivacy>
<a :href="t('cembraPrivacyLink')" target="_blank" class="underline">
{{ $t("a.Datenschutzerklärung") }}
</a>
</template>
</i18next>
</p>
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="flex flex-col sm:flex-row">
<div class="flex-none border-r bg-white p-4 lg:p-8">
<slot name="side"></slot>
</div>

View File

@ -662,6 +662,23 @@ export type LearningContentEdoniqTestObjectType = CoursePageInterface & Learning
translation_key: Scalars['String']['output'];
};
export type LearningContentFeedbackAutomobilGewerbeObjectType = CoursePageInterface & LearningContentInterface & {
__typename?: 'LearningContentFeedbackAutomobilGewerbeObjectType';
can_user_self_toggle_course_completion: Scalars['Boolean']['output'];
circle?: Maybe<CircleLightObjectType>;
content_type: Scalars['String']['output'];
content_url: Scalars['String']['output'];
course?: Maybe<CourseObjectType>;
description: Scalars['String']['output'];
frontend_url: Scalars['String']['output'];
id: Scalars['ID']['output'];
live: Scalars['Boolean']['output'];
minutes?: Maybe<Scalars['Int']['output']>;
slug: Scalars['String']['output'];
title: Scalars['String']['output'];
translation_key: Scalars['String']['output'];
};
export type LearningContentFeedbackUkObjectType = CoursePageInterface & LearningContentInterface & {
__typename?: 'LearningContentFeedbackUKObjectType';
can_user_self_toggle_course_completion: Scalars['Boolean']['output'];
@ -973,6 +990,7 @@ export type Query = {
learning_content_assignment?: Maybe<LearningContentAssignmentObjectType>;
learning_content_attendance_course?: Maybe<LearningContentAttendanceCourseObjectType>;
learning_content_document_list?: Maybe<LearningContentDocumentListObjectType>;
learning_content_feedback_automobil_gewerbe?: Maybe<LearningContentFeedbackAutomobilGewerbeObjectType>;
learning_content_feedback_uk?: Maybe<LearningContentFeedbackUkObjectType>;
learning_content_feedback_vv?: Maybe<LearningContentFeedbackVvObjectType>;
learning_content_knowledge_assessment?: Maybe<LearningContentKnowledgeAssessmentObjectType>;
@ -1206,6 +1224,8 @@ type CoursePageFieldsLearningContentDocumentListObjectTypeFragment = { __typenam
type CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment = { __typename?: 'LearningContentEdoniqTestObjectType', title: string, id: string, slug: string, content_type: string, frontend_url: string } & { ' $fragmentName'?: 'CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment' };
type CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment = { __typename?: 'LearningContentFeedbackAutomobilGewerbeObjectType', title: string, id: string, slug: string, content_type: string, frontend_url: string } & { ' $fragmentName'?: 'CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment' };
type CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment = { __typename?: 'LearningContentFeedbackUKObjectType', title: string, id: string, slug: string, content_type: string, frontend_url: string } & { ' $fragmentName'?: 'CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment' };
type CoursePageFieldsLearningContentFeedbackVvObjectTypeFragment = { __typename?: 'LearningContentFeedbackVVObjectType', title: string, id: string, slug: string, content_type: string, frontend_url: string } & { ' $fragmentName'?: 'CoursePageFieldsLearningContentFeedbackVvObjectTypeFragment' };
@ -1232,7 +1252,7 @@ type CoursePageFieldsPerformanceCriteriaObjectTypeFragment = { __typename?: 'Per
type CoursePageFieldsTopicObjectTypeFragment = { __typename?: 'TopicObjectType', title: string, id: string, slug: string, content_type: string, frontend_url: string } & { ' $fragmentName'?: 'CoursePageFieldsTopicObjectTypeFragment' };
export type CoursePageFieldsFragment = CoursePageFieldsActionCompetenceObjectTypeFragment | CoursePageFieldsAssignmentObjectTypeFragment | CoursePageFieldsCircleObjectTypeFragment | CoursePageFieldsCompetenceCertificateListObjectTypeFragment | CoursePageFieldsCompetenceCertificateObjectTypeFragment | CoursePageFieldsLearningContentAssignmentObjectTypeFragment | CoursePageFieldsLearningContentAttendanceCourseObjectTypeFragment | CoursePageFieldsLearningContentDocumentListObjectTypeFragment | CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment | CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment | CoursePageFieldsLearningContentFeedbackVvObjectTypeFragment | CoursePageFieldsLearningContentKnowledgeAssessmentObjectTypeFragment | CoursePageFieldsLearningContentLearningModuleObjectTypeFragment | CoursePageFieldsLearningContentMediaLibraryObjectTypeFragment | CoursePageFieldsLearningContentPlaceholderObjectTypeFragment | CoursePageFieldsLearningContentRichTextObjectTypeFragment | CoursePageFieldsLearningContentVideoObjectTypeFragment | CoursePageFieldsLearningPathObjectTypeFragment | CoursePageFieldsLearningSequenceObjectTypeFragment | CoursePageFieldsLearningUnitObjectTypeFragment | CoursePageFieldsPerformanceCriteriaObjectTypeFragment | CoursePageFieldsTopicObjectTypeFragment;
export type CoursePageFieldsFragment = CoursePageFieldsActionCompetenceObjectTypeFragment | CoursePageFieldsAssignmentObjectTypeFragment | CoursePageFieldsCircleObjectTypeFragment | CoursePageFieldsCompetenceCertificateListObjectTypeFragment | CoursePageFieldsCompetenceCertificateObjectTypeFragment | CoursePageFieldsLearningContentAssignmentObjectTypeFragment | CoursePageFieldsLearningContentAttendanceCourseObjectTypeFragment | CoursePageFieldsLearningContentDocumentListObjectTypeFragment | CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment | CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment | CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment | CoursePageFieldsLearningContentFeedbackVvObjectTypeFragment | CoursePageFieldsLearningContentKnowledgeAssessmentObjectTypeFragment | CoursePageFieldsLearningContentLearningModuleObjectTypeFragment | CoursePageFieldsLearningContentMediaLibraryObjectTypeFragment | CoursePageFieldsLearningContentPlaceholderObjectTypeFragment | CoursePageFieldsLearningContentRichTextObjectTypeFragment | CoursePageFieldsLearningContentVideoObjectTypeFragment | CoursePageFieldsLearningPathObjectTypeFragment | CoursePageFieldsLearningSequenceObjectTypeFragment | CoursePageFieldsLearningUnitObjectTypeFragment | CoursePageFieldsPerformanceCriteriaObjectTypeFragment | CoursePageFieldsTopicObjectTypeFragment;
export type AttendanceCheckQueryQueryVariables = Exact<{
courseSessionId: Scalars['ID']['input'];
@ -1276,6 +1296,9 @@ export type CompetenceCertificateQueryQuery = { __typename?: 'Query', competence
) | (
{ __typename?: 'LearningContentEdoniqTestObjectType', circle?: { __typename?: 'CircleLightObjectType', id: string, title: string, slug: string } | null }
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment': CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment } }
) | (
{ __typename?: 'LearningContentFeedbackAutomobilGewerbeObjectType', circle?: { __typename?: 'CircleLightObjectType', id: string, title: string, slug: string } | null }
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment': CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment } }
) | (
{ __typename?: 'LearningContentFeedbackUKObjectType', circle?: { __typename?: 'CircleLightObjectType', id: string, title: string, slug: string } | null }
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment': CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment } }
@ -1330,6 +1353,9 @@ export type CompetenceCertificateForUserQueryQuery = { __typename?: 'Query', com
) | (
{ __typename?: 'LearningContentEdoniqTestObjectType', circle?: { __typename?: 'CircleLightObjectType', id: string, title: string, slug: string } | null }
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment': CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment } }
) | (
{ __typename?: 'LearningContentFeedbackAutomobilGewerbeObjectType', circle?: { __typename?: 'CircleLightObjectType', id: string, title: string, slug: string } | null }
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment': CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment } }
) | (
{ __typename?: 'LearningContentFeedbackUKObjectType', circle?: { __typename?: 'CircleLightObjectType', id: string, title: string, slug: string } | null }
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment': CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment } }
@ -1407,6 +1433,9 @@ export type CourseQueryQuery = { __typename?: 'Query', course?: { __typename?: '
& { ' $fragmentRefs'?: { 'CoursePageFieldsCompetenceCertificateObjectTypeFragment': CoursePageFieldsCompetenceCertificateObjectTypeFragment } }
) | null }
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment': CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment } }
) | (
{ __typename?: 'LearningContentFeedbackAutomobilGewerbeObjectType', can_user_self_toggle_course_completion: boolean, content_url: string, minutes?: number | null, description: string }
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment': CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment } }
) | (
{ __typename?: 'LearningContentFeedbackUKObjectType', can_user_self_toggle_course_completion: boolean, content_url: string, minutes?: number | null, description: string }
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment': CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment } }

View File

@ -13,6 +13,7 @@ type Query {
learning_content_attendance_course: LearningContentAttendanceCourseObjectType
learning_content_feedback_uk: LearningContentFeedbackUKObjectType
learning_content_feedback_vv: LearningContentFeedbackVVObjectType
learning_content_feedback_automobil_gewerbe: LearningContentFeedbackAutomobilGewerbeObjectType
learning_content_learning_module: LearningContentLearningModuleObjectType
learning_content_knowledge_assessment: LearningContentKnowledgeAssessmentObjectType
learning_content_placeholder: LearningContentPlaceholderObjectType
@ -819,6 +820,22 @@ type LearningContentFeedbackVVObjectType implements CoursePageInterface & Learni
circle: CircleLightObjectType
}
type LearningContentFeedbackAutomobilGewerbeObjectType 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 LearningContentLearningModuleObjectType implements CoursePageInterface & LearningContentInterface {
id: ID!
title: String!

View File

@ -59,6 +59,7 @@ export const LearningContentAssignmentObjectType = "LearningContentAssignmentObj
export const LearningContentAttendanceCourseObjectType = "LearningContentAttendanceCourseObjectType";
export const LearningContentDocumentListObjectType = "LearningContentDocumentListObjectType";
export const LearningContentEdoniqTestObjectType = "LearningContentEdoniqTestObjectType";
export const LearningContentFeedbackAutomobilGewerbeObjectType = "LearningContentFeedbackAutomobilGewerbeObjectType";
export const LearningContentFeedbackUKObjectType = "LearningContentFeedbackUKObjectType";
export const LearningContentFeedbackVVObjectType = "LearningContentFeedbackVVObjectType";
export const LearningContentInterface = "LearningContentInterface";

View File

@ -8,6 +8,7 @@
"a.Abgabetermin": "Abgabetermin",
"a.Abgezogene Punkte": "Abgezogene Punkte",
"a.Adresse": "Adresse",
"a.AGB": "AGB",
"a.Aktuell begleitest du niemanden als Lernbegleitung.": "Aktuell begleitest du niemanden als Lernbegleitung.",
"a.Aktuell begleitest du niemanden als Praxisbildner.": "Aktuell begleitest du niemanden als Praxisbildner.",
"a.Aktuell bist du leider keiner Durchführung zugewiesen.": "Aktuell bist du leider keiner Durchführung zugewiesen.",
@ -72,6 +73,7 @@
"a.Datei auswählen": "Datei auswählen",
"a.Datei hochladen": "Datei hochladen",
"a.Datei kann nicht gespeichert werden.": "Datei kann nicht gespeichert werden.",
"a.Datenschutzerklärung": "Datenschutzerklärung",
"a.Datum": "Datum",
"a.Debit-/Kreditkarte/Twint": "Debit-/Kreditkarte/Twint",
"a.Dein Feedback für x y wurde freigegeben.": "Dein Feedback für {{x}} {{y}} wurde freigegeben.",
@ -113,6 +115,7 @@
"a.Ergebnisse bewerten": "Ergebnisse bewerten",
"a.Ergebnisse teilen": "Ergebnisse teilen",
"a.Erneut bearbeiten": "Erneut bearbeiten",
"a.Es gelten die {cembraTos} und die {cembraPrivacy} der CembraPay AG.": "Es gelten die {cembraTos} und die {cembraPrivacy} der CembraPay AG.",
"a.Experte": "Experte",
"a.Feedback abschliessen": "Feedback abschliessen",
"a.Feedback ansehen": "Feedback ansehen",
@ -208,6 +211,7 @@
"a.Nicht bestanden": "Nicht bestanden",
"a.Nicht bewertet": "Nicht bewertet",
"a.Nichtleben": "Nichtleben",
"a.Noch nicht bestätigt": "Noch nicht bestätigt",
"a.Note": "Note",
"a.NUMBER Elemente abgeschlossen": "{NUMBER} Elemente abgeschlossen",
"a.NUMBER Präsenztage abgeschlossen": "{NUMBER} Präsenztage abgeschlossen",
@ -351,6 +355,8 @@
"Berufsbildner": "Berufsbildner",
"Bestanden": "Bestanden",
"Bewertung von x y": "Bewertung von {{x}} {{y}}",
"cembraPrivacyLink": "https://cembrapay.ch/de/privacy",
"cembraTosLink": "https://cembrapay.ch/de/terms/CP",
"Circle": "Circle",
"circlePage.circleContentBoxTitle": "Das lernst du in diesem Circle",
"circlePage.contactExpertButton": "Trainer/-in kontaktieren",

View File

@ -8,6 +8,7 @@
"a.Abgabetermin": "Date de remise",
"a.Abgezogene Punkte": "Points déduits",
"a.Adresse": "Adresse",
"a.AGB": "CGV",
"a.Aktuell begleitest du niemanden als Lernbegleitung.": "Actuellement, vous n'accompagnez personne en tant que mentor d'apprentissage.",
"a.Aktuell begleitest du niemanden als Praxisbildner.": "Actuellement, vous n'accompagnez personne en tant que formateur/-trice pratique.",
"a.Aktuell bist du leider keiner Durchführung zugewiesen.": "Actuellement, vous n'êtes malheureusement affecté à aucune session.",
@ -72,6 +73,7 @@
"a.Datei auswählen": "Sélectionner le fichier",
"a.Datei hochladen": "Télécharger le fichier",
"a.Datei kann nicht gespeichert werden.": "Impossible d'enregistrer le fichier.",
"a.Datenschutzerklärung": "protection des données",
"a.Datum": "Date",
"a.Debit-/Kreditkarte/Twint": "Carte de débit/crédit / Twint",
"a.Dein Feedback für x y wurde freigegeben.": "Ton feedback pour {{x}} {{y}} a été validé.",
@ -113,6 +115,7 @@
"a.Ergebnisse bewerten": "Évaluer les résultats",
"a.Ergebnisse teilen": "Partager les résultats",
"a.Erneut bearbeiten": "Modifier à nouveau",
"a.Es gelten die {cembraTos} und die {cembraPrivacy} der CembraPay AG.": "Les {cembraTos} et la déclaration de {cembraPrivacy} de CembraPay AG s'appliquent.",
"a.Experte": "Expert",
"a.Feedback abschliessen": "Terminer le feedback",
"a.Feedback ansehen": "Voir le feedback",
@ -208,6 +211,7 @@
"a.Nicht bestanden": "Échoué",
"a.Nicht bewertet": "Non évalué",
"a.Nichtleben": "Non-vie",
"a.Noch nicht bestätigt": "Pas encore confirmé",
"a.Note": "Note",
"a.NUMBER Elemente abgeschlossen": "{NUMBER} éléments terminés",
"a.NUMBER Präsenztage abgeschlossen": "{NUMBER} jours de présence complétés",
@ -351,6 +355,8 @@
"Berufsbildner": "Formateur professionnel",
"Bestanden": "Réussi",
"Bewertung von x y": "Évaluation de {{x}} {{y}}",
"cembraPrivacyLink": "https://cembrapay.ch/fr/privacy",
"cembraTosLink": "https://cembrapay.ch/fr/terms/CP",
"Circle": "Cercle",
"circlePage.circleContentBoxTitle": "Ce que tu vas apprendre dans ce Circle",
"circlePage.contactExpertButton": "Contacter le formateur / la formatrice",

View File

@ -8,6 +8,7 @@
"a.Abgabetermin": "Termine di consegna",
"a.Abgezogene Punkte": "Punti detratti",
"a.Adresse": "Indirizzo",
"a.AGB": "Condizioni generali",
"a.Aktuell begleitest du niemanden als Lernbegleitung.": "Attualmente non stai accompagnando nessuno come mentore di apprendimento.",
"a.Aktuell begleitest du niemanden als Praxisbildner.": "Attualmente non stai accompagnando nessuno come formatore/-trice pratico/a.",
"a.Aktuell bist du leider keiner Durchführung zugewiesen.": "Attualmente non sei assegnato a nessuna sessione, purtroppo.",
@ -72,6 +73,7 @@
"a.Datei auswählen": "Selezionare il file",
"a.Datei hochladen": "Carica il file",
"a.Datei kann nicht gespeichert werden.": "Impossibile salvare il file.",
"a.Datenschutzerklärung": "Informativa sulla privacy",
"a.Datum": "Data",
"a.Debit-/Kreditkarte/Twint": "Carta di debito/credito / Twint",
"a.Dein Feedback für x y wurde freigegeben.": "Il tuo feedback per {{x}} {{y}} è stato pubblicato.",
@ -80,7 +82,7 @@
"a.Deine Selbsteinschätzung": "La tua autovalutazione",
"a.Deine Änderungen wurden gespeichert": "Le tue modifiche sono state salvate",
"a.Der Lehrgang und die Prüfung zum Erwerb des Verbandszertifikats als Versicherungsvermittler/-in.": "Il corso e l'esame per ottenere il certificato di associazione come intermediario/agente di assicurazione.",
"a.Der Preis für den Lehrgang {course} beträgt {price}.": "Il prezzo del {corso} è {prezzo} IVA esclusa.",
"a.Der Preis für den Lehrgang {course} beträgt {price}.": "Il prezzo del {course} è {price} IVA esclusa.",
"a.Details anschauen": "Visualizza dettagli",
"a.Details anzeigen": "Mostrare i dettagli",
"a.Deutsch": "Tedesco",
@ -113,6 +115,7 @@
"a.Ergebnisse bewerten": "Valutare i risultati",
"a.Ergebnisse teilen": "Condividere i risultati",
"a.Erneut bearbeiten": "Modifica di nuovo",
"a.Es gelten die {cembraTos} und die {cembraPrivacy} der CembraPay AG.": "Si applicano le {cembraTos} e l'{cembraPrivacy} di CembraPay AG.",
"a.Experte": "Esperto",
"a.Feedback abschliessen": "Completa il feedback",
"a.Feedback ansehen": "Visualizza il feedback",
@ -208,6 +211,7 @@
"a.Nicht bestanden": "Non superato",
"a.Nicht bewertet": "Non valutato",
"a.Nichtleben": "Non vita",
"a.Noch nicht bestätigt": "Non ancora confermato",
"a.Note": "Note",
"a.NUMBER Elemente abgeschlossen": "{NUMBER} elementi completati",
"a.NUMBER Präsenztage abgeschlossen": "{NUMBER} giorni di presenza completati",
@ -351,6 +355,8 @@
"Berufsbildner": "Formatore professionale",
"Bestanden": "Superato",
"Bewertung von x y": "Valutazione di {{x}} {{y}}",
"cembraPrivacyLink": "https://cembrapay.ch/it/privacy",
"cembraTosLink": "https://cembrapay.ch/it/terms/CP",
"Circle": "Cerchio",
"circlePage.circleContentBoxTitle": "Cosa apprenderai in questo Circle",
"circlePage.contactExpertButton": "Contattare il/la trainer",

View File

@ -34,6 +34,10 @@
v-else-if="feedbackType === 'uk'"
:feedback-data="feedbackData"
/>
<FeedbackPageAutomobilgewerbe
v-else-if="feedbackType === 'automobilgewerbe'"
:feedback-data="feedbackData"
/>
</main>
</div>
</div>
@ -42,6 +46,7 @@
<script setup lang="ts">
import { useCurrentCourseSession } from "@/composables";
import { itGet } from "@/fetchHelpers";
import FeedbackPageAutomobilgewerbe from "@/pages/cockpit/FeedbackPageAutomobilgewerbe.vue";
import FeedbackPageUK from "@/pages/cockpit/FeedbackPageUK.vue";
import FeedbackPageVV from "@/pages/cockpit/FeedbackPageVV.vue";
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
@ -84,7 +89,7 @@ onMounted(async () => {
log.debug("FeedbackPage feedbackData", feedbackData.value);
if (
feedbackData.value &&
["uk", "vv"].includes(feedbackData.value?.feedbackType ?? "")
["uk", "vv", "automobilgewerbe"].includes(feedbackData.value?.feedbackType ?? "")
) {
feedbackType.value = feedbackData.value.feedbackType;
}

View File

@ -0,0 +1,59 @@
<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 { useTranslation } from "i18next-vue";
import * as log from "loglevel";
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: "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>

View File

@ -177,12 +177,15 @@ watch(
</button>
</div>
<section v-if="attendanceCourses.length && state.attendanceCourseSelected">
<div class="flex flex-row justify-between bg-white p-6">
<div class="flex flex-row flex-wrap justify-between bg-white p-6">
<ItDropdownSelect
v-model="state.attendanceCourseSelected"
:items="presenceCoursesDropdownOptions ?? []"
></ItDropdownSelect>
<div v-if="!state.attendanceSaved" class="flex flex-row items-center">
<div
v-if="!state.attendanceSaved"
class="flex flex-row flex-wrap items-center space-y-2 md:space-y-0"
>
<ItCheckbox
:checkbox-item="{
value: true,

View File

@ -63,9 +63,14 @@ const itemDetailUrl = (item: AssignmentStatisticsRecordType) => {
<template>
<main>
<div class="mb-10 flex items-center justify-between">
<div class="mb-10 flex flex-wrap items-center justify-between">
<h3>{{ $t("a.Kompetenznachweis-Elemente") }}</h3>
<button v-if="true" class="flex" data-cy="export-button" @click="exportData">
<button
v-if="true"
class="flex pt-3 sm:pt-0"
data-cy="export-button"
@click="exportData"
>
<it-icon-export></it-icon-export>
<span class="ml inline-block">{{ $t("a.Als Excel exportieren") }}</span>
</button>
@ -78,7 +83,7 @@ const itemDetailUrl = (item: AssignmentStatisticsRecordType) => {
>
<template #default="{ item }">
<div
class="flex justify-between"
class="flex flex-wrap justify-between"
:data-cy="`${(item as AssignmentStatisticsRecordType).assignment_title}@${(item as AssignmentStatisticsRecordType).course_session_id}`"
>
<div>
@ -109,7 +114,7 @@ const itemDetailUrl = (item: AssignmentStatisticsRecordType) => {
{{ total((item as AssignmentStatisticsRecordType).metrics) }}
bestanden
</div>
<div v-else>Noch nicht bestätigt</div>
<div v-else>{{ $t("a.Noch nicht bestätigt") }}</div>
<ItProgress
:status-count="
assignmentStats((item as AssignmentStatisticsRecordType).metrics)

View File

@ -64,7 +64,7 @@ async function exportData() {
:items="courseStatistics.attendance_day_presences.records"
>
<template #default="{ item }">
<div class="flex justify-between">
<div class="flex flex-wrap justify-between">
<div>
<h4 class="font-bold">
{{ $t("a.Präsenztag") }}: Circle «{{

View File

@ -25,7 +25,7 @@ const props = defineProps<{
:items="courseStatistics.competences.records"
>
<template #default="{ item }">
<div class="flex justify-between">
<div class="flex flex-wrap justify-between space-y-2 md:space-y-0">
<div>
<h4 class="font-bold">
{{ $t("a.Selbsteinschätzung") }}:

View File

@ -52,7 +52,7 @@ async function exportData() {
:items="courseStatistics.feedback_responses.records"
>
<template #default="{ item }">
<div class="flex justify-between">
<div class="flex flex-wrap justify-between space-y-2 md:space-y-0">
<div>
<h4 class="font-bold">
Feedback: Circle «{{ circleMeta(item.circle_id)?.name }}»

View File

@ -242,7 +242,13 @@ watch(
</button>
</div>
<DocumentSection v-if="showDocumentSection" :circle="circle" />
<div v-if="!props.readonly" class="expert mt-8 border p-6">
<div
v-if="
!props.readonly &&
lpQueryResult.course.value?.configuration.enable_learning_mentor
"
class="expert mt-8 border p-6"
>
<h3 class="text-blue-dark">{{ $t("circlePage.gotQuestions") }}</h3>
<div
class="mt-4 leading-relaxed"

View File

@ -23,6 +23,7 @@ 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 FeedbackBlockAutomobilGewerbe from "./feedback/FeedbackBlockAutomobilGewerbe.vue";
import FeedbackBlockUK from "./feedback/FeedbackBlockUK.vue";
import FeedbackBlockVV from "./feedback/FeedbackBlockVV.vue";
@ -45,6 +46,7 @@ const COMPONENTS: Record<LearningContentContentType, Component> = {
"learnpath.LearningContentDocumentList": DocumentListBlock,
"learnpath.LearningContentFeedbackUK": FeedbackBlockUK,
"learnpath.LearningContentFeedbackVV": FeedbackBlockVV,
"learnpath.LearningContentFeedbackAutomobilGewerbe": FeedbackBlockAutomobilGewerbe,
"learnpath.LearningContentLearningModule": IframeBlock,
"learnpath.LearningContentKnowledgeAssessment": IframeBlock,
"learnpath.LearningContentMediaLibrary": MediaLibraryBlock,
@ -58,6 +60,7 @@ const DEFAULT_BLOCK = PlaceholderBlock;
const component = computed(() => {
return COMPONENTS[props.learningContent.content_type] || DEFAULT_BLOCK;
});
console.log("component", component);
function handleFinishedLearningContent() {
circleStore.continueFromLearningContent(

View File

@ -5,14 +5,14 @@ import { graphql } from "@/gql";
import FeedbackCompletition from "@/pages/learningPath/learningContentPage/feedback/FeedbackCompletition.vue";
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
import { useUserStore } from "@/stores/user";
import type { LearningContentFeedbackUK, LearningContentFeedbackVV } from "@/types";
import type { LearningContentFeedback } from "@/types";
import { useMutation } from "@urql/vue";
import { useRouteQuery } from "@vueuse/router";
import log from "loglevel";
import { computed, onMounted, reactive, ref } from "vue";
const props = defineProps<{
content: LearningContentFeedbackVV | LearningContentFeedbackUK;
content: LearningContentFeedback;
stepLabels: string[];
questionData: any[];
introduction: string;

View File

@ -0,0 +1,73 @@
<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 FeedbackBase from "@/pages/learningPath/learningContentPage/feedback/FeedbackBase.vue";
import type { LearningContentFeedback } from "@/types";
import { useTranslation } from "i18next-vue";
const props = defineProps<{
content: LearningContentFeedback;
}>();
const { t } = useTranslation();
const stepLabels = [
t("general.introduction"),
t("feedback.satisfactionLabel"),
t("feedback.goalAttainmentLabel"),
t("feedback.proficiencyLabelVV"),
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: "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.sendFeedback')"
:completion-description="$t('feedback.completionDescriptionVV')"
:show-avatar="false"
/>
</template>

View File

@ -8,12 +8,12 @@ import {
YES_NO,
} from "@/pages/learningPath/learningContentPage/feedback/feedback.constants";
import FeedbackBase from "@/pages/learningPath/learningContentPage/feedback/FeedbackBase.vue";
import type { LearningContentFeedbackUK, LearningContentFeedbackVV } from "@/types";
import type { LearningContentFeedback } from "@/types";
import { useTranslation } from "i18next-vue";
import { computed } from "vue";
const props = defineProps<{
content: LearningContentFeedbackVV | LearningContentFeedbackUK;
content: LearningContentFeedback;
}>();
const courseSessionDetailResult = useCourseSessionDetailQuery();

View File

@ -7,11 +7,11 @@ import {
YES_NO,
} from "@/pages/learningPath/learningContentPage/feedback/feedback.constants";
import FeedbackBase from "@/pages/learningPath/learningContentPage/feedback/FeedbackBase.vue";
import type { LearningContentFeedbackUK, LearningContentFeedbackVV } from "@/types";
import type { LearningContentFeedback } from "@/types";
import { useTranslation } from "i18next-vue";
const props = defineProps<{
content: LearningContentFeedbackVV | LearningContentFeedbackUK;
content: LearningContentFeedback;
}>();
const { t } = useTranslation();

View File

@ -133,7 +133,7 @@ function render() {
<div class="aspect-square content-center">
<pre hidden>{{ pieData }}</pre>
<pre hidden>{{ render() }}</pre>
<svg :id="svgId" class="h-full min-w-[20px]">
<svg :id="svgId" class="h-full">
<circle :cx="width / 2" :cy="height / 2" :r="radius" :color="colors.gray[300]" />
<circle :cx="width / 2" :cy="height / 2" :r="radius / 2.5" color="white" />
</svg>

View File

@ -11,6 +11,7 @@ import type {
LearningContentAttendanceCourseObjectType,
LearningContentDocumentListObjectType,
LearningContentEdoniqTestObjectType,
LearningContentFeedbackAutomobilGewerbeObjectType,
LearningContentFeedbackUkObjectType,
LearningContentFeedbackVvObjectType,
LearningContentKnowledgeAssessmentObjectType,
@ -77,6 +78,11 @@ export type LearningContentFeedbackUK = LearningContentFeedbackUkObjectType & {
readonly content_type: "learnpath.LearningContentFeedbackUK";
};
export type LearningContentFeedbackAutomobilGewerbe =
LearningContentFeedbackAutomobilGewerbeObjectType & {
readonly content_type: "learnpath.LearningContentFeedbackAutomobilGewerbe";
};
export type LearningContentLearningModule = LearningContentLearningModuleObjectType & {
readonly content_type: "learnpath.LearningContentLearningModule";
};
@ -109,6 +115,7 @@ export type LearningContent =
| LearningContentEdoniqTest
| LearningContentFeedbackUK
| LearningContentFeedbackVV
| LearningContentFeedbackAutomobilGewerbe
| LearningContentLearningModule
| LearningContentKnowledgeAssessment
| LearningContentMediaLibrary
@ -116,6 +123,11 @@ export type LearningContent =
| LearningContentRichText
| LearningContentVideo;
export type LearningContentFeedback =
| LearningContentFeedbackUK
| LearningContentFeedbackVV
| LearningContentFeedbackAutomobilGewerbe;
export type LearningContentWithCompletion = LearningContent &
Completable & {
continueUrl?: string;

View File

@ -51,6 +51,7 @@ export function learningContentTypeData(
return { title: t("learningContentTypes.text"), icon: "it-icon-lc-resource" };
case "learnpath.LearningContentFeedbackUK":
case "learnpath.LearningContentFeedbackVV":
case "learnpath.LearningContentFeedbackAutomobilGewerbe":
return { title: t("learningContentTypes.feedback"), icon: "it-icon-lc-feedback" };
case "learnpath.LearningContentPlaceholder":
return {

View File

@ -4,6 +4,9 @@
# Run every 6 hours
0 */6 * * * /usr/local/bin/python /app/manage.py simple_dummy_job
# Run every hour at minute 11
0 */11 * * * /usr/local/bin/python /app/manage.py handle_sso_sync_errors
# Run every hour at minute 17
17 * * * * /usr/local/bin/python /app/manage.py edoniq_import_results

View File

@ -26,7 +26,7 @@ describe("selfEvaluation.cy.js", () => {
cy.visit("/course/test-lehrgang/competence");
cy.get('[data-cy="self-evaluation-fail"]').should("have.text", "0");
cy.get('[data-cy="self-evaluation-success"]').should("have.text", "0");
cy.get('[data-cy="self-evaluation-unknown"]').should("have.text", "4");
cy.get('[data-cy="self-evaluation-unknown"]').should("have.text", "6");
// learning unit id = 692 also known as:
// Bedarfsanalyse, Ist- und Soll-Situation <<Reisen>>
@ -45,7 +45,7 @@ describe("selfEvaluation.cy.js", () => {
cy.makeSelfEvaluation([true, false]);
cy.url().should(
"include",
"/course/test-lehrgang/competence/self-evaluation-and-feedback"
"/course/test-lehrgang/competence/self-evaluation-and-feedback",
);
// check data again on KompetenzNavi
@ -57,7 +57,7 @@ describe("selfEvaluation.cy.js", () => {
cy.visit("/course/test-lehrgang/competence");
cy.get('[data-cy="self-evaluation-fail"]').should("have.text", "1");
cy.get('[data-cy="self-evaluation-success"]').should("have.text", "1");
cy.get('[data-cy="self-evaluation-unknown"]').should("have.text", "2");
cy.get('[data-cy="self-evaluation-unknown"]').should("have.text", "4");
});
it("should be able to make a happy self evaluation", () => {

View File

@ -16,7 +16,7 @@ describe("dashboardSupervisor.cy.js", () => {
describe("with data", () => {
beforeEach(() => {
cy.manageCommand(
"cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days"
"cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days",
);
login("test-supervisor1@example.com", "test");
cy.visit("/");
@ -28,7 +28,7 @@ describe("dashboardSupervisor.cy.js", () => {
// -> makes sure that the numbers are correct
getDashboardStatistics("assignments.completed").should(
"have.text",
"1"
"1",
);
getDashboardStatistics("assignments.passed").should("have.text", "34%");
});
@ -48,11 +48,11 @@ describe("dashboardSupervisor.cy.js", () => {
it("contains correct numbers", () => {
getDashboardStatistics("attendance.dayCompleted").should(
"have.text",
"1"
"1",
);
getDashboardStatistics("attendance.participantsPresent").should(
"have.text",
"34%"
"34%",
);
});
it("contains correct details link", () => {
@ -70,7 +70,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", "6");
getDashboardStatistics("feedback.count").should("have.text", "9");
});
it("contains correct details link", () => {
clickOnDetailsLink("feedback");
@ -106,7 +106,7 @@ describe("dashboardSupervisor.cy.js", () => {
describe("with deducted points", () => {
beforeEach(() => {
cy.manageCommand(
"cypress_reset --create-assignment-evaluation --assignment-evaluation-scores 6,6,6,3,3 --assignment-points-deducted 14 --create-edoniq-test-results 19 24 8"
"cypress_reset --create-assignment-evaluation --assignment-evaluation-scores 6,6,6,3,3 --assignment-points-deducted 14 --create-edoniq-test-results 19 24 8",
);
login("test-supervisor1@example.com", "test");
cy.visit("/");
@ -120,14 +120,14 @@ describe("dashboardSupervisor.cy.js", () => {
// check data on the details page
cy.get(
'[data-cy="dashboard.stats.assignments"] [data-cy="basebox.detailsLink"]'
'[data-cy="dashboard.stats.assignments"] [data-cy="basebox.detailsLink"]',
).click();
cy.get(
'[data-cy="Edoniq Wissens- und Verständisfragen - Circle Fahrzeug (Demo)@-1"]'
'[data-cy="Edoniq Wissens- und Verständisfragen - Circle Fahrzeug (Demo)@-1"]',
).should("contain", "0 von 3 bestanden");
cy.get(
'[data-cy="Überprüfen einer Motorfahrzeugs-Versicherungspolice@-1"]'
'[data-cy="Überprüfen einer Motorfahrzeugs-Versicherungspolice@-1"]',
).should("contain", "0 von 3 bestanden");
});
});

View File

@ -30,93 +30,93 @@ describe("feedbackStudent.cy.js", () => {
// fill feedback form
// step 1
cy.url().should("include", "step=1");
cy.get("[data-cy=\"question-1\"]").should(
cy.get('[data-cy="question-1"]').should(
"contain",
"Zufriedenheit insgesamt"
"Zufriedenheit insgesamt",
);
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
cy.get("[data-cy=\"radio-4\"]").click();
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(
cy.get('[data-cy="question-2"]').should(
"contain",
"Zielerreichung insgesamt"
"Zielerreichung insgesamt",
);
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
cy.get('[data-cy="next-step"]').should("be.disabled");
// the system should store after every step -> check stored data
cy.loadFeedbackResponse("feedback_user_id", TEST_STUDENT1_USER_ID).then(
(ac) => {
expect(ac.submitted).to.be.false;
expect(ac.data.satisfaction).to.equal(4);
expect(ac.data.instructor_competence).to.equal(null);
}
},
);
cy.get("[data-cy=\"radio-3\"]").click();
cy.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(
cy.get('[data-cy="question-3"]').should(
"contain",
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Kurs?"
"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.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(
cy.get('[data-cy="question-4"]').should(
"contain",
"Waren die Vorbereitungsaufträge klar und verständlich?"
"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.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(
cy.get('[data-cy="question-5"]').should(
"contain",
"Wie beurteilst du die Themensicherheit und Fachkompetenz des Kursleiters/der Kursleiterin?"
"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.get('[data-cy="next-step"]').should("be.disabled");
cy.get('[data-cy="radio-2"]').click();
cy.wait(200);
cy.learningContentMultiLayoutNextStep();
cy.wait(200);
// step 6
cy.url().should("include", "step=6");
cy.get("[data-cy=\"question-6\"]").should(
cy.get('[data-cy="question-6"]').should(
"contain",
"Wurden Fragen und Anregungen der Kursteilnehmenden ernst genommen und aufgegriffen?"
"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.get('[data-cy="next-step"]').should("be.disabled");
cy.get('[data-cy="radio-1"]').click();
cy.wait(200);
cy.learningContentMultiLayoutNextStep();
cy.wait(200);
// step 7
cy.url().should("include", "step=7");
cy.get("[data-cy=\"question-7\"]").should(
cy.get('[data-cy="question-7"]').should(
"contain",
"Was möchtest du dem Kursleiter/der Kursleiterin sonst noch sagen?"
"Was möchtest du dem Kursleiter/der Kursleiterin sonst noch sagen?",
);
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
cy.get("[data-cy=\"it-textarea-instructor_open_feedback\"]").type(
"Der Kursleiter ist eigentlich ganz nett."
cy.get('[data-cy="next-step"]').should("be.disabled");
cy.get('[data-cy="it-textarea-instructor_open_feedback"]').type(
"Der Kursleiter ist eigentlich ganz nett.",
);
cy.wait(200);
cy.learningContentMultiLayoutNextStep();
@ -124,26 +124,26 @@ describe("feedbackStudent.cy.js", () => {
// step 8
cy.url().should("include", "step=8");
cy.get("[data-cy=\"question-8\"]").should(
cy.get('[data-cy="question-8"]').should(
"contain",
"Würdest du den Kurs weiterempfehlen?"
"Würdest du den Kurs weiterempfehlen?",
);
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
cy.get("[data-cy=\"radio-true\"]").click();
cy.get('[data-cy="next-step"]').should("be.disabled");
cy.get('[data-cy="radio-true"]').click();
cy.wait(200);
cy.learningContentMultiLayoutNextStep();
cy.wait(200);
// step 9
cy.url().should("include", "step=9");
cy.get("[data-cy=\"question-9\"]").should(
cy.get('[data-cy="question-9"]').should(
"contain",
"Was hat dir besonders gut gefallen?"
"Was hat dir besonders gut gefallen?",
);
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
cy.get("[data-cy=\"it-textarea-course_positive_feedback\"]").type(
"Ich bin zufrieden mit den meisten Dingen."
cy.get('[data-cy="next-step"]').should("be.disabled");
cy.get('[data-cy="it-textarea-course_positive_feedback"]').type(
"Ich bin zufrieden mit den meisten Dingen.",
);
cy.wait(200);
cy.learningContentMultiLayoutNextStep();
@ -151,21 +151,21 @@ describe("feedbackStudent.cy.js", () => {
// step 10
cy.url().should("include", "step=10");
cy.get("[data-cy=\"question-10\"]").should(
cy.get('[data-cy="question-10"]').should(
"contain",
"Wo siehst du Verbesserungspotential?"
"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.get('[data-cy="next-step"]').should("be.disabled");
cy.get('[data-cy="it-textarea-course_negative_feedback"]').type(
"Ich bin unzufrieden mit einigen Sachen.",
);
cy.wait(200);
cy.learningContentMultiLayoutNextStep();
cy.wait(200);
cy.url().should("include", "step=11");
cy.get("[data-cy=\"sendFeedbackButton\"]").click();
cy.get("[data-cy=\"complete-and-continue\"]").click({ force: true });
cy.get('[data-cy="sendFeedbackButton"]').click();
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
// marked complete in circle
cy.url().should((url) => {
@ -173,7 +173,7 @@ describe("feedbackStudent.cy.js", () => {
});
cy.reload();
cy.get(
"[data-cy=\"test-lehrgang-lp-circle-fahrzeug-lc-feedback-status\"]"
'[data-cy="test-lehrgang-lp-circle-fahrzeug-lc-feedback-status"]',
).should("have.attr", "aria-checked", "true");
// reopening page should get directly to last step
@ -197,9 +197,9 @@ describe("feedbackStudent.cy.js", () => {
proficiency: 80,
satisfaction: 4,
would_recommend: true,
feedback_type: "uk"
feedback_type: "uk",
});
}
},
);
});
});
@ -219,8 +219,8 @@ describe("feedbackStudent.cy.js", () => {
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.get('[data-cy="introduction"]').contains(
"Wir bitten dich um dein Feedback. Es hilft uns, damit wir deine Lernerlebnisse verbessern können.",
);
cy.wait(200);
@ -230,82 +230,82 @@ describe("feedbackStudent.cy.js", () => {
// fill feedback form
// step 1
cy.url().should("include", "step=1");
cy.get("[data-cy=\"question-1\"]").should(
cy.get('[data-cy="question-1"]').should(
"contain",
"Zufriedenheit insgesamt"
"Zufriedenheit insgesamt",
);
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
cy.get("[data-cy=\"radio-4\"]").click();
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(
cy.get('[data-cy="question-2"]').should(
"contain",
"Zielerreichung insgesamt"
"Zielerreichung insgesamt",
);
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
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.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(
cy.get('[data-cy="question-3"]').should(
"contain",
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Circle?"
"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.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(
cy.get('[data-cy="question-4"]').should(
"contain",
"Waren die Praxisaufträge klar und verständlich?"
"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.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(
cy.get('[data-cy="question-5"]').should(
"contain",
"Würdest du den Circle weiterempfehlen?"
"Würdest du den Circle weiterempfehlen?",
);
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
cy.get("[data-cy=\"radio-false\"]").click();
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(
cy.get('[data-cy="question-6"]').should(
"contain",
"Was hat dir besonders gut gefallen?"
"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.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();
@ -313,21 +313,21 @@ describe("feedbackStudent.cy.js", () => {
// step 7
cy.url().should("include", "step=7");
cy.get("[data-cy=\"question-7\"]").should(
cy.get('[data-cy="question-7"]').should(
"contain",
"Wo siehst du Verbesserungspotential?"
"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.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 });
cy.get('[data-cy="sendFeedbackButton"]').click();
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
// marked complete in circle
cy.url().should((url) => {
@ -335,7 +335,7 @@ describe("feedbackStudent.cy.js", () => {
});
cy.reload();
cy.get(
"[data-cy=\"test-lehrgang-lp-circle-reisen-lc-feedback-status\"]"
'[data-cy="test-lehrgang-lp-circle-reisen-lc-feedback-status"]',
).should("have.attr", "aria-checked", "true");
// reopening page should get directly to last step
@ -354,9 +354,155 @@ describe("feedbackStudent.cy.js", () => {
proficiency: 80,
satisfaction: 4,
would_recommend: false,
feedback_type: "vv"
feedback_type: "vv",
});
}
},
);
});
});
describe("Feedback Automobilgewerbe", () => {
beforeEach(() => {
cy.visit("/course/test-lehrgang/learn/automobilgewerbe/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(/\/automobilgewerbe\/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",
"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 5
cy.url().should("include", "step=5");
cy.get('[data-cy="question-5"]').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 6
cy.url().should("include", "step=6");
cy.get('[data-cy="question-6"]').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=7");
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(
/\/automobilgewerbe#lu-transfer-reflexion-feedback?$/,
);
});
cy.reload();
cy.get(
'[data-cy="test-lehrgang-lp-circle-automobilgewerbe-lc-feedback-status"]',
).should("have.attr", "aria-checked", "true");
// reopening page should get directly to last step
cy.visit("/course/test-lehrgang/learn/automobilgewerbe/feedback");
cy.url().should("include", "step=7");
// 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,
proficiency: 80,
satisfaction: 4,
would_recommend: false,
feedback_type: "automobilgewerbe",
});
},
);
});
});

View File

@ -1,4 +1,4 @@
import {EXPERT_COCKPIT_URL, login} from "../helpers";
import { EXPERT_COCKPIT_URL, login } from "../helpers";
describe("feedbackTrainer.cy.js", () => {
beforeEach(() => {
@ -10,7 +10,7 @@ describe("feedbackTrainer.cy.js", () => {
login("test-trainer1@example.com", "test");
cy.visit(EXPERT_COCKPIT_URL);
cy.get(
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-fahrzeug-lc-feedback"]'
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-fahrzeug-lc-feedback"]',
).click();
cy.get('[data-cy="feedback-data-amount"]').should("contain", "0");
@ -22,7 +22,7 @@ describe("feedbackTrainer.cy.js", () => {
login("test-trainer1@example.com", "test");
cy.visit(EXPERT_COCKPIT_URL);
cy.get(
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-fahrzeug-lc-feedback"]'
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-fahrzeug-lc-feedback"]',
).click();
cy.get('[data-cy="feedback-data-amount"]').should("contain", "3");
@ -30,43 +30,43 @@ describe("feedbackTrainer.cy.js", () => {
// check titles of questions
cy.get('[data-cy="question-1"]').should(
"contain",
"Zufriedenheit insgesamt"
"Zufriedenheit insgesamt",
);
cy.get('[data-cy="question-2"]').should(
"contain",
"Zielerreichung insgesamt"
"Zielerreichung insgesamt",
);
cy.get('[data-cy="question-3"]').should(
"contain",
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Kurs?"
"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?"
"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?"
"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?"
"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?"
"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?"
"Würdest du den Kurs weiterempfehlen?",
);
cy.get('[data-cy="question-9"]').should(
"contain",
"Wo siehst du Verbesserungspotential?"
"Wo siehst du Verbesserungspotential?",
);
cy.get('[data-cy="question-10"]').should(
"contain",
"Was hat dir besonders gut gefallen?"
"Was hat dir besonders gut gefallen?",
);
cy.get('[data-cy="question-1"]')
@ -142,7 +142,7 @@ describe("feedbackTrainer.cy.js", () => {
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"]'
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-reisen-lc-feedback"]',
).click();
cy.get('[data-cy="feedback-data-amount"]').should("contain", "3");
@ -150,31 +150,31 @@ describe("feedbackTrainer.cy.js", () => {
// check titles of questions
cy.get('[data-cy="question-1"]').should(
"contain",
"Zufriedenheit insgesamt"
"Zufriedenheit insgesamt",
);
cy.get('[data-cy="question-2"]').should(
"contain",
"Zielerreichung insgesamt"
"Zielerreichung insgesamt",
);
cy.get('[data-cy="question-3"]').should(
"contain",
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Circle?"
"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?"
"Waren die Praxisaufträge klar und verständlich?",
);
cy.get('[data-cy="question-5"]').should(
"contain",
"Würdest du den Circle weiterempfehlen?"
"Würdest du den Circle weiterempfehlen?",
);
cy.get('[data-cy="question-6"]').should(
"contain",
"Wo siehst du Verbesserungspotential?"
"Wo siehst du Verbesserungspotential?",
);
cy.get('[data-cy="question-7"]').should(
"contain",
"Was hat dir besonders gut gefallen?"
"Was hat dir besonders gut gefallen?",
);
cy.get('[data-cy="question-1"]')
@ -228,4 +228,87 @@ describe("feedbackTrainer.cy.js", () => {
.should("contain", "Die Präsentation war super");
});
});
describe("FeedbackAutomobilGewerbe", function () {
it("can open feedback results page with results", () => {
cy.manageCommand("cypress_reset --create-feedback-responses");
login("test-trainer1@example.com", "test");
cy.visit(EXPERT_COCKPIT_URL);
cy.get('[data-cy="dropdown-select"]').click();
cy.get('[data-cy="dropdown-select-option-Automobilgewerbe"]').click();
cy.get(
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-automobilgewerbe-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",
"Würdest du den Circle weiterempfehlen?",
);
cy.get('[data-cy="question-5"]').should(
"contain",
"Wo siehst du Verbesserungspotential?",
);
cy.get('[data-cy="question-6"]').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", "2");
cy.get('[data-cy="question-4"]')
.find('[data-cy="popover-no"]')
.click()
.find('[data-cy="num-no"]')
.should("contain", "1");
cy.get('[data-cy="question-5"]')
.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-6"]')
.should("contain", "Nur Gutes.")
.should(
"contain",
"Das Beispiel mit der Katze fand ich sehr gut veranschaulicht!",
)
.should("contain", "Die Präsentation war super");
});
});
});

View File

@ -8,57 +8,61 @@ describe("learningPath.cy.js", () => {
});
it("can open learningPath page", () => {
cy.get("[data-cy=\"learning-path-title\"]").should(
cy.get('[data-cy="learning-path-title"]').should(
"contain",
"Test Lehrgang"
"Test Lehrgang",
);
});
it("can click on circle to open it", () => {
cy.get("[data-cy=\"circle-Fahrzeug\"]").click({ force: true });
cy.get('[data-cy="circle-Fahrzeug"]').click({ force: true });
cy.url().should("include", "/course/test-lehrgang/learn/fahrzeug");
cy.get("[data-cy=\"circle-title\"]").should("contain", "Fahrzeug");
cy.get('[data-cy="circle-title"]').should("contain", "Fahrzeug");
});
it("switch between list and path view", () => {
cy.get("[data-cy=\"lp-path-view\"]").should("be.visible");
cy.get("[data-cy=\"view-switch\"]").click();
cy.get("[data-cy=\"lp-list-view\"]").should("be.visible");
cy.get("[data-cy=\"view-switch\"]").click();
cy.get("[data-cy=\"lp-path-view\"]").should("be.visible");
cy.get('[data-cy="lp-path-view"]').should("be.visible");
cy.get('[data-cy="view-switch"]').click();
cy.get('[data-cy="lp-list-view"]').should("be.visible");
cy.get('[data-cy="view-switch"]').click();
cy.get('[data-cy="lp-path-view"]').should("be.visible");
});
it("weiter gehts button will open next circle", () => {
// first click will open first circle
cy.get("[data-cy=\"lp-continue-button\"]")
cy.get('[data-cy="lp-continue-button"]')
.filter(":visible")
.should("contain", "Los geht's")
.click();
cy.get("[data-cy=\"circle-title\"]").should("contain", "Fahrzeug");
cy.get("[data-cy=\"back-to-learning-path-button\"]").click();
cy.get('[data-cy="circle-title"]').should("contain", "Fahrzeug");
cy.get('[data-cy="back-to-learning-path-button"]').click();
// mark a learning content in second circle
cy.get("[data-cy=\"circle-Reisen\"]").click({ force: true });
cy.get("[data-cy=\"ls-continue-button\"]").click();
cy.get("[data-cy=\"complete-and-continue\"]").click({ force: true });
cy.get("[data-cy=\"back-to-learning-path-button\"]").click();
cy.get('[data-cy="circle-Reisen"]').click({ force: true });
cy.get('[data-cy="ls-continue-button"]').click();
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
cy.get('[data-cy="back-to-learning-path-button"]').click();
// click on continue should go to unit-test-circle
cy.get("[data-cy=\"lp-continue-button\"]")
cy.get('[data-cy="lp-continue-button"]')
.filter(":visible")
.should("contain", "Weiter geht's")
.click();
cy.get("[data-cy=\"circle-title\"]").should("contain", "Reisen");
cy.get('[data-cy="circle-title"]').should("contain", "Reisen");
});
it("checks contents", () => {
cy.get("[data-cy=\"lp-topic\"]").should("have.length", 2);
cy.get("[data-cy=\"lp-topic\"]").first().should("contain", "Circle ÜK");
cy.get("[data-cy=\"lp-topic\"]").eq(1).should("contain", "Circle VV");
cy.get('[data-cy="lp-topic"]').should("have.length", 3);
cy.get('[data-cy="lp-topic"]').first().should("contain", "Circle ÜK");
cy.get('[data-cy="lp-topic"]').eq(1).should("contain", "Circle VV");
cy.get('[data-cy="lp-topic"]')
.eq(2)
.should("contain", "Circle Automobilgewerbe");
cy.get(".cy-lp-circle").should("have.length", 2);
cy.get(".cy-lp-circle").should("have.length", 3);
cy.get(".cy-lp-circle").first().should("contain", "Fahrzeug");
cy.get(".cy-lp-circle").eq(1).should("contain", "Reisen");
cy.get(".cy-lp-circle").eq(2).should("contain", "Automobilgewerbe");
});
});

Binary file not shown.

View File

@ -53,6 +53,7 @@ from vbv_lernwelt.learning_mentor.models import (
)
from vbv_lernwelt.learnpath.models import (
LearningContentAttendanceCourse,
LearningContentFeedbackAutomobilGewerbe,
LearningContentFeedbackUK,
LearningContentFeedbackVV,
)
@ -404,6 +405,60 @@ def command(
},
)
# feedback automobilgewerbe
learning_content_feedback_page = (
LearningContentFeedbackAutomobilGewerbe.objects.get(
slug="test-lehrgang-lp-circle-automobilgewerbe-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,
"would_recommend": True,
"course_negative_feedback": "Nichts Schlechtes",
"course_positive_feedback": "Nur Gutes.",
"feedback_type": "automobilgewerbe",
},
)
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,
"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": "automobilgewerbe",
},
)
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,
"would_recommend": False,
"course_negative_feedback": "Mehr Videos wären schön.",
"course_positive_feedback": "Die Präsentation war super",
"feedback_type": "automobilgewerbe",
},
)
if create_course_completion_performance_criteria:
member = User.objects.get(id=TEST_STUDENT1_USER_ID)
course_session = CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID)

View File

@ -80,6 +80,7 @@ from vbv_lernwelt.learnpath.tests.learning_path_factories import (
LearningContentAssignmentFactory,
LearningContentAttendanceCourseFactory,
LearningContentEdoniqTestFactory,
LearningContentFeedbackAutomobilGewerbeFactory,
LearningContentFeedbackUKFactory,
LearningContentFeedbackVVFactory,
LearningContentKnowledgeAssessmentFactory,
@ -108,7 +109,11 @@ from vbv_lernwelt.media_library.tests.media_library_factories import (
def create_test_course(
include_uk=True, include_vv=True, with_sessions=False, with_documents=False
include_uk=True,
include_vv=True,
include_automobilgewerbe=True,
with_sessions=False,
with_documents=False,
):
# create_locales_for_wagtail()
create_default_collections()
@ -331,6 +336,11 @@ def create_test_course(
if include_vv:
csu.expert.add(Circle.objects.get(slug="test-lehrgang-lp-circle-reisen"))
if include_automobilgewerbe:
csu.expert.add(
Circle.objects.get(slug="test-lehrgang-lp-circle-automobilgewerbe")
)
trainer2 = User.objects.get(email="test-trainer2@example.com")
csu = CourseSessionUser.objects.create(
course_session=cs_zurich,
@ -594,7 +604,12 @@ def create_test_course_with_categories(apps=None, schema_editor=None):
return course
def create_test_learning_path(include_uk=True, include_vv=True, with_documents=False):
def create_test_learning_path(
include_uk=True,
include_vv=True,
include_automobil_gewerbe=True,
with_documents=False,
):
course_page = CoursePage.objects.get(course_id=COURSE_TEST_ID)
lp = LearningPathFactory(title="Test Lernpfad", parent=course_page)
@ -608,6 +623,10 @@ def create_test_learning_path(include_uk=True, include_vv=True, with_documents=F
TopicFactory(title="Circle VV", is_visible=False, parent=lp)
create_test_circle_reisen(lp)
if include_automobil_gewerbe:
TopicFactory(title="Circle Automobilgewerbe", is_visible=False, parent=lp)
create_test_circle_automobilgewerbe(lp)
def create_test_uk_circle_fahrzeug(lp, title="Fahrzeug", with_documents=False):
circle = CircleFactory(
@ -843,6 +862,76 @@ def create_test_circle_reisen(lp):
)
def create_test_circle_automobilgewerbe(lp):
circle = CircleFactory(
title="Automobilgewerbe",
parent=lp,
)
LearningSequenceFactory(title="Starten", parent=circle, icon="it-icon-ls-start")
LearningUnitFactory(title="Einführung", parent=circle)
LearningContentVideoFactory(
title="Verschaff dir einen Überblick",
parent=circle,
content_url="https://player.vimeo.com/video/772512710?h=30f912f15a",
description="Willkommen im Lehrgang Automobilgewerbe",
)
LearningSequenceFactory(title="Training", parent=circle)
LearningUnitFactory(title="Unterlagen", parent=circle)
LearningContentPlaceholderFactory(
title="Unterlagen für den Unterricht",
parent=circle,
)
LearningSequenceFactory(title="Analyse", parent=circle)
# analyse
lu = LearningUnitFactory(
title="Bedarfsanalyse, Ist- und Soll-Situation",
parent=circle,
course_category=CourseCategory.objects.get(
course_id=COURSE_TEST_ID, title="Reisen"
),
)
LearningContentLearningModuleFactory(
title="Emma und Ayla campen durch Amerika - Analyse",
parent=circle,
content_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-analyse-xapi-FZoZOP9y/index.html",
)
PerformanceCriteriaFactory(
parent=ActionCompetence.objects.get(competence_id="Y1"),
competence_id="Y1.1",
title="Ich bin fähig zu Reisen eine Gesprächsführung zu machen",
learning_unit=lu,
)
PerformanceCriteriaFactory(
parent=ActionCompetence.objects.get(competence_id="Y2"),
competence_id="Y2.1",
title="Ich bin fähig zu Reisen eine Analyse zu machen",
learning_unit=lu,
)
# transfer
parent = circle
LearningSequenceFactory(title="Transfer", parent=parent, icon="it-icon-ls-end")
LearningUnitFactory(title="Transfer, Reflexion, Feedback", parent=parent)
LearningContentKnowledgeAssessmentFactory(
title="Wissens- und Verständnisfragen",
parent=parent,
content_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-analyse-xapi-FZoZOP9y/index.html",
)
LearningContentPlaceholderFactory(
title="Reflexion",
parent=parent,
)
LearningContentFeedbackAutomobilGewerbeFactory(
title="Feedback",
parent=parent,
)
def create_test_competence_navi():
course_page = CoursePage.objects.get(course_id=COURSE_TEST_ID)

View File

@ -10,6 +10,7 @@ from vbv_lernwelt.learnpath.graphql.types import (
LearningContentAttendanceCourseObjectType,
LearningContentDocumentListObjectType,
LearningContentEdoniqTestObjectType,
LearningContentFeedbackAutomobilGewerbeObjectType,
LearningContentFeedbackUKObjectType,
LearningContentFeedbackVVObjectType,
LearningContentKnowledgeAssessmentObjectType,
@ -53,6 +54,9 @@ class CourseQuery(graphene.ObjectType):
)
learning_content_feedback_uk = graphene.Field(LearningContentFeedbackUKObjectType)
learning_content_feedback_vv = graphene.Field(LearningContentFeedbackVVObjectType)
learning_content_feedback_automobil_gewerbe = graphene.Field(
LearningContentFeedbackAutomobilGewerbeObjectType
)
learning_content_learning_module = graphene.Field(
LearningContentLearningModuleObjectType
)

View File

@ -68,7 +68,7 @@ class DocumentUploadApiTestCase(APITestCase):
self.assertEqual(
response.data["fields"]["Content-Disposition"],
f"attachment; filename={self.test_data['file_name']}",
f'attachment; filename="{self.test_data["file_name"]}"',
)
file_id = response.data["file_id"]

View File

@ -8,12 +8,14 @@ from vbv_lernwelt.feedback.graphql.types import (
FeedbackResponseObjectType as FeedbackResponseType,
)
from vbv_lernwelt.feedback.serializers import (
CourseFeedbackSerializerAutomobilGewerbe,
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 (
LearningContentFeedbackAutomobilGewerbe,
LearningContentFeedbackUK,
LearningContentFeedbackVV,
)
@ -56,6 +58,12 @@ class SendFeedbackMutation(graphene.Mutation):
learningContentFeedbackModel = LearningContentFeedbackUK
serializerClass = CourseFeedbackSerializerUK
data["feedback_type"] = "uk"
elif (
learning_content_type == "learnpath.LearningContentFeedbackAutomobilGewerbe"
):
learningContentFeedbackModel = LearningContentFeedbackAutomobilGewerbe
serializerClass = CourseFeedbackSerializerAutomobilGewerbe
data["feedback_type"] = "automobilgewerbe"
else:
errors = [
ErrorType(

View File

@ -8,6 +8,7 @@ logger = structlog.get_logger(__name__)
FEEDBACK_TYPES = (
("uk", "Feedback UK"),
("vv", "Feedback VV"),
("automobilgewerbe", "Feedback Automobilgewerbe"),
)
@ -55,6 +56,21 @@ class CourseFeedbackSerializerVV(serializers.Serializer):
)
class CourseFeedbackSerializerAutomobilGewerbe(serializers.Serializer):
feedback_type = serializers.ChoiceField(choices=FEEDBACK_TYPES)
satisfaction = FeedbackIntegerField()
goal_attainment = FeedbackIntegerField()
proficiency = serializers.IntegerField(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

View File

@ -104,6 +104,18 @@ def initial_data_for_feedback_page(
"course_positive_feedback": "",
"feedback_type": "vv",
}
if hasattr(
learning_content_feedback_page, "learningcontentfeedbackautomobilgewerbe"
):
return {
"satisfaction": None,
"goal_attainment": None,
"proficiency": None,
"would_recommend": None,
"course_negative_feedback": "",
"course_positive_feedback": "",
"feedback_type": "vv",
}
return {}

View File

@ -92,7 +92,7 @@ def s3_generate_presigned_post(
Fields={
"acl": acl,
"Content-Type": file_type,
"Content-Disposition": f"attachment; filename={file_name}",
"Content-Disposition": f'attachment; filename="{file_name}"',
},
Conditions=[
{"acl": acl},

View File

@ -10,6 +10,7 @@ from vbv_lernwelt.learnpath.models import (
LearningContentAttendanceCourse,
LearningContentDocumentList,
LearningContentEdoniqTest,
LearningContentFeedbackAutomobilGewerbe,
LearningContentFeedbackUK,
LearningContentFeedbackVV,
LearningContentKnowledgeAssessment,
@ -54,6 +55,8 @@ class LearningContentInterface(CoursePageInterface):
return LearningContentFeedbackUKObjectType
elif isinstance(instance, LearningContentFeedbackVV):
return LearningContentFeedbackVVObjectType
elif isinstance(instance, LearningContentFeedbackAutomobilGewerbe):
return LearningContentFeedbackAutomobilGewerbeObjectType
elif isinstance(instance, LearningContentLearningModule):
return LearningContentLearningModuleObjectType
elif isinstance(instance, LearningContentKnowledgeAssessment):
@ -128,6 +131,16 @@ class LearningContentFeedbackVVObjectType(DjangoObjectType):
fields = []
class LearningContentFeedbackAutomobilGewerbeObjectType(DjangoObjectType):
class Meta:
model = LearningContentFeedbackAutomobilGewerbe
interfaces = (
CoursePageInterface,
LearningContentInterface,
)
fields = []
class LearningContentLearningModuleObjectType(DjangoObjectType):
class Meta:
model = LearningContentLearningModule

View File

@ -0,0 +1,43 @@
# Generated by Django 4.2.13 on 2024-10-28 09:59
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", "0020_auto_20240730_0905"),
]
operations = [
migrations.CreateModel(
name="LearningContentFeedbackAutomobilGewerbe",
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",),
),
]

View File

@ -95,6 +95,7 @@ class Circle(CourseBasePage):
"learnpath.LearningContentAttendanceCourse",
"learnpath.LearningContentFeedbackUK",
"learnpath.LearningContentFeedbackVV",
"learnpath.LearningContentFeedbackAutomobilGewerbe",
"learnpath.LearningContentLearningModule",
"learnpath.LearningContentKnowledgeAssessment",
"learnpath.LearningContentMediaLibrary",
@ -373,6 +374,12 @@ class LearningContentFeedbackVV(LearningContent):
can_user_self_toggle_course_completion = models.BooleanField(default=False)
class LearningContentFeedbackAutomobilGewerbe(LearningContent):
parent_page_types = ["learnpath.Circle"]
subpage_types = []
can_user_self_toggle_course_completion = models.BooleanField(default=False)
class LearningContentLearningModule(LearningContent):
parent_page_types = ["learnpath.Circle"]
subpage_types = []

View File

@ -7,6 +7,7 @@ from vbv_lernwelt.learnpath.models import (
LearningContentAttendanceCourse,
LearningContentDocumentList,
LearningContentEdoniqTest,
LearningContentFeedbackAutomobilGewerbe,
LearningContentFeedbackUK,
LearningContentFeedbackVV,
LearningContentKnowledgeAssessment,
@ -141,6 +142,16 @@ class LearningContentFeedbackUKFactory(wagtail_factories.PageFactory):
model = LearningContentFeedbackUK
class LearningContentFeedbackAutomobilGewerbeFactory(wagtail_factories.PageFactory):
title = "FeedbackAutomobilGewerbe"
minutes = 0
content_url = ""
description = RichText("")
class Meta:
model = LearningContentFeedbackAutomobilGewerbe
class LearningContentLearningModuleFactory(wagtail_factories.PageFactory):
title = "Beispiel Lernmodul"
minutes = 0

View File

@ -26,7 +26,7 @@ class TestRetrieveLearingPathContents(APITestCase):
self.assertEqual(self.learning_path.title, data["title"])
# topics and circles
self.assertEqual(4, len(data["children"]))
self.assertEqual(6, len(data["children"]))
# circle "analyse" contents
self.assertEqual(20, len(data["children"][3]["children"]))

View File

@ -23,15 +23,17 @@ def create_sso_user_from_admin(user: User, request):
try:
create_and_update_user(user) # noqa
user.save()
messages.add_message(
request, messages.SUCCESS, "Der Bentuzer wurde in Keycloak erstellt."
)
if request:
messages.add_message(
request, messages.SUCCESS, "Der Bentuzer wurde in Keycloak erstellt."
)
except KeycloakPostError as e:
messages.add_message(
request,
messages.WARNING,
f"Der Benutzer {user} konnte nicht in Keycloak erstellt werden: {e}",
)
if request:
messages.add_message(
request,
messages.WARNING,
f"Der Benutzer {user} konnte nicht in Keycloak erstellt werden: {e}",
)
def sync_sso_roles_from_admin(user: User, request):
@ -53,21 +55,26 @@ def sync_sso_roles_from_admin(user: User, request):
try:
sync_roles_for_user(user, course_roles)
messages.add_message(
request, messages.SUCCESS, "Die Daten wurden mit Keycloak synchronisiert."
)
if request:
messages.add_message(
request,
messages.SUCCESS,
"Die Daten wurden mit Keycloak synchronisiert.",
)
except KeycloakDeleteError as e:
messages.add_message(
request,
messages.WARNING,
f"Die bestehenden Rollen für Benutzer ({user}) konnten in Keycloak nicht gelöscht werden: {e}",
)
if request:
messages.add_message(
request,
messages.WARNING,
f"Die bestehenden Rollen für Benutzer ({user}) konnten in Keycloak nicht gelöscht werden: {e}",
)
except KeycloakPostError as e:
messages.add_message(
request,
messages.WARNING,
f"Die neuen Rollen für Benutzer ({user}) konnten in Keycloak nicht erstellt werden: {e}",
)
if request:
messages.add_message(
request,
messages.WARNING,
f"Die neuen Rollen für Benutzer ({user}) konnten in Keycloak nicht erstellt werden: {e}",
)
@admin.action(description="KEYCLOAK: Sync SSO Roles")
@ -98,7 +105,14 @@ class SsoUserAdmin(auth_admin.UserAdmin):
"sso_id",
"intermedia_sso_id",
]
search_fields = ["first_name", "last_name", "email", "username", "sso_id"]
search_fields = [
"first_name",
"last_name",
"email",
"username",
"sso_id",
"additional_json_data__intermediate_sso_id",
]
actions = [sync_sso_roles, create_sso_user]
# Make fields read-only

View File

@ -0,0 +1,35 @@
import djclick as click
import structlog
from vbv_lernwelt.sso.admin import sync_sso_roles_from_admin
from vbv_lernwelt.sso.models import SsoSyncError
logger = structlog.get_logger(__name__)
@click.command()
@click.option(
"--delete-sync-errors/--no-delete-sync-errors",
default=True,
help="`delete-sync-errors` to delete the erros after sync, `no-delete-sync-errors` to keep the SyncErrors objects. Default is `delete-sync-errors`.",
)
def command(delete_sync_errors: bool):
errors = SsoSyncError.objects.all()
processed_users = set()
errors_to_delete = []
for error in errors:
user = error.user
if user.id not in processed_users:
sync_sso_roles_from_admin(user, None)
processed_users.add(user.id)
logger.info(
"sso_sync_error",
user=user.id,
)
if delete_sync_errors:
errors_to_delete.append(error.id)
# Perform the bulk delete operation
if errors_to_delete:
SsoSyncError.objects.filter(id__in=errors_to_delete).delete()