Merged in feat/geteilter-bereich (pull request #304)
Feat: Teilnehmer kann auch Mentor sein Approved-by: Daniel Egger
This commit is contained in:
commit
24aa5d9b8c
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import EvaluationIntro from "@/pages/cockpit/assignmentEvaluationPage/EvaluationIntro.vue";
|
import EvaluationIntro from "@/components/assignment/evaluation/EvaluationIntro.vue";
|
||||||
import EvaluationSummary from "@/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue";
|
import EvaluationSummary from "@/components/assignment/evaluation/EvaluationSummary.vue";
|
||||||
import EvaluationTask from "@/pages/cockpit/assignmentEvaluationPage/EvaluationTask.vue";
|
import EvaluationTask from "@/components/assignment/evaluation/EvaluationTask.vue";
|
||||||
import type {
|
import type {
|
||||||
Assignment,
|
Assignment,
|
||||||
AssignmentCompletion,
|
AssignmentCompletion,
|
||||||
|
|
@ -30,12 +30,10 @@ const courseSession = useCurrentCourseSession();
|
||||||
const task = computed(() => props.assignment.evaluation_tasks[props.taskIndex]);
|
const task = computed(() => props.assignment.evaluation_tasks[props.taskIndex]);
|
||||||
|
|
||||||
const expertData = computed(() => {
|
const expertData = computed(() => {
|
||||||
const data = (props.assignmentCompletion?.completion_data?.[task.value.id]
|
return (props.assignmentCompletion?.completion_data?.[task.value.id]?.expert_data ?? {
|
||||||
?.expert_data ?? {
|
|
||||||
points: 0,
|
points: 0,
|
||||||
text: "",
|
text: "",
|
||||||
}) as ExpertData;
|
}) as ExpertData;
|
||||||
return data;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const text = computed(() => {
|
const text = computed(() => {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
2
|
2
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AssignmentSubmissionProgress from "@/components/cockpit/AssignmentSubmissionProgress.vue";
|
import AssignmentSubmissionProgress from "@/components/assignment/AssignmentSubmissionProgress.vue";
|
||||||
import type {
|
import type {
|
||||||
CourseSession,
|
CourseSession,
|
||||||
LearningContent,
|
LearningContent,
|
||||||
|
|
@ -10,7 +10,7 @@ import type {
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import FeedbackSubmissionProgress from "@/components/cockpit/mentor/FeedbackSubmissionProgress.vue";
|
import FeedbackSubmissionProgress from "@/components/selfEvaluationFeedback/FeedbackSubmissionProgress.vue";
|
||||||
import { learningContentTypeData } from "@/utils/typeMaps";
|
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||||
import {
|
import {
|
||||||
useCourseDataWithCompletion,
|
useCourseDataWithCompletion,
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,9 @@ if (!courseSession) {
|
||||||
throw new Error("Course session not found");
|
throw new Error("Course session not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const isExpert = courseSessionsStore.hasCockpit(courseSession);
|
const url = courseSession.actions.includes("expert-cockpit")
|
||||||
const url = isExpert ? props.dueDate.url_expert : props.dueDate.url;
|
? props.dueDate.url_expert
|
||||||
|
: props.dueDate.url;
|
||||||
|
|
||||||
const courseSessionTitle = computed(() => {
|
const courseSessionTitle = computed(() => {
|
||||||
if (props.dueDate.course_session_id) {
|
if (props.dueDate.course_session_id) {
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,9 @@ import { useTranslation } from "i18next-vue";
|
||||||
import { useRouteLookups } from "@/utils/route";
|
import { useRouteLookups } from "@/utils/route";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
import { getCompetenceNaviUrl, getLearningPathUrl } from "@/utils/utils";
|
import { getCompetenceNaviUrl, getLearningPathUrl } from "@/utils/utils";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
|
||||||
|
|
||||||
const { inCompetenceProfile, inLearningPath } = useRouteLookups();
|
const { inCompetenceProfile, inLearningPath } = useRouteLookups();
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const cockpit = useCockpitStore();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -34,7 +32,6 @@ const { t } = useTranslation();
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
v-if="cockpit.hasExpertCockpitType"
|
|
||||||
data-cy="preview-competence-profile-link"
|
data-cy="preview-competence-profile-link"
|
||||||
:to="getCompetenceNaviUrl(courseSession.course.slug)"
|
:to="getCompetenceNaviUrl(courseSession.course.slug)"
|
||||||
class="preview-nav-item"
|
class="preview-nav-item"
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,10 @@ import CoursePreviewBar from "@/components/header/CoursePreviewBar.vue";
|
||||||
import {
|
import {
|
||||||
getCockpitUrl,
|
getCockpitUrl,
|
||||||
getCompetenceNaviUrl,
|
getCompetenceNaviUrl,
|
||||||
getLearningMentorManagementUrl,
|
getLearningMentorUrl,
|
||||||
getLearningPathUrl,
|
getLearningPathUrl,
|
||||||
getMediaCenterUrl,
|
getMediaCenterUrl,
|
||||||
} from "@/utils/utils";
|
} from "@/utils/utils";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
|
||||||
|
|
||||||
log.debug("MainNavigationBar created");
|
log.debug("MainNavigationBar created");
|
||||||
|
|
||||||
|
|
@ -70,47 +69,62 @@ onMounted(() => {
|
||||||
log.debug("MainNavigationBar mounted");
|
log.debug("MainNavigationBar mounted");
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasMediaLibraryMenu = computed(() => {
|
const hasLearningPathMenu = computed(() =>
|
||||||
if (useCockpitStore().hasMentorCockpitType) {
|
Boolean(
|
||||||
return false;
|
courseSessionsStore.currentCourseSession?.actions.includes("learning-path") &&
|
||||||
}
|
inCourse()
|
||||||
return inCourse() && Boolean(courseSessionsStore.currentCourseSession);
|
)
|
||||||
});
|
);
|
||||||
|
|
||||||
const hasCockpitMenu = computed(() => {
|
const hasCompetenceNaviMenu = computed(() =>
|
||||||
return courseSessionsStore.currentCourseSessionHasCockpit;
|
Boolean(
|
||||||
});
|
courseSessionsStore.currentCourseSession?.actions.includes("competence-navi") &&
|
||||||
|
inCourse()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const hasPreviewMenu = computed(() => {
|
const hasMediaLibraryMenu = computed(() =>
|
||||||
return (
|
Boolean(
|
||||||
useCockpitStore().hasExpertCockpitType || useCockpitStore().hasMentorCockpitType
|
courseSessionsStore.currentCourseSession?.actions.includes("media-library") &&
|
||||||
);
|
inCourse()
|
||||||
});
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const hasAppointmentsMenu = computed(() => {
|
const hasCockpitMenu = computed(() =>
|
||||||
if (useCockpitStore().hasMentorCockpitType) {
|
Boolean(courseSessionsStore.currentCourseSession?.actions.includes("expert-cockpit"))
|
||||||
return false;
|
);
|
||||||
}
|
|
||||||
return userStore.loggedIn;
|
const hasPreviewMenu = computed(() =>
|
||||||
});
|
Boolean(courseSessionsStore.currentCourseSession?.actions.includes("preview"))
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasAppointmentsMenu = computed(() =>
|
||||||
|
Boolean(
|
||||||
|
courseSessionsStore.currentCourseSession?.actions.includes("appointments") &&
|
||||||
|
userStore.loggedIn
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const hasNotificationsMenu = computed(() => {
|
const hasNotificationsMenu = computed(() => {
|
||||||
return userStore.loggedIn;
|
return userStore.loggedIn;
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasMentorManagementMenu = computed(() => {
|
const hasLearningMentor = computed(() => {
|
||||||
if (courseSessionsStore.currentCourseSessionHasCockpit || !inCourse()) {
|
if (!inCourse()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
courseSessionsStore.currentCourseSession?.course.configuration
|
if (!courseSessionsStore.currentCourseSession) {
|
||||||
.enable_learning_mentor ?? false
|
return false;
|
||||||
);
|
}
|
||||||
|
|
||||||
|
const courseSession = courseSessionsStore.currentCourseSession;
|
||||||
|
return courseSession.actions.includes("learning-mentor");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CoursePreviewBar v-if="courseSessionsStore.hasCourseSessionPreview" />
|
<CoursePreviewBar v-if="courseSessionsStore.isCourseSessionPreviewActive" />
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<MobileMenu
|
<MobileMenu
|
||||||
|
|
@ -120,6 +134,11 @@ const hasMentorManagementMenu = computed(() => {
|
||||||
:has-media-library-menu="hasMediaLibraryMenu"
|
:has-media-library-menu="hasMediaLibraryMenu"
|
||||||
:has-cockpit-menu="hasCockpitMenu"
|
:has-cockpit-menu="hasCockpitMenu"
|
||||||
:has-preview-menu="hasPreviewMenu"
|
:has-preview-menu="hasPreviewMenu"
|
||||||
|
:has-learning-path-menu="hasLearningPathMenu"
|
||||||
|
:has-competence-navi-menu="hasCompetenceNaviMenu"
|
||||||
|
:has-learning-mentor="hasLearningMentor"
|
||||||
|
:has-notifications-menu="hasNotificationsMenu"
|
||||||
|
:has-appointments-menu="hasAppointmentsMenu"
|
||||||
:media-url="
|
:media-url="
|
||||||
getMediaCenterUrl(courseSessionsStore.currentCourseSession?.course?.slug)
|
getMediaCenterUrl(courseSessionsStore.currentCourseSession?.course?.slug)
|
||||||
"
|
"
|
||||||
|
|
@ -177,79 +196,77 @@ const hasMentorManagementMenu = computed(() => {
|
||||||
only relevant if there is a current course session -->
|
only relevant if there is a current course session -->
|
||||||
<template v-if="courseSessionsStore.currentCourseSession">
|
<template v-if="courseSessionsStore.currentCourseSession">
|
||||||
<div class="hidden space-x-8 lg:flex">
|
<div class="hidden space-x-8 lg:flex">
|
||||||
<template v-if="courseSessionsStore.currentCourseSessionHasCockpit">
|
<router-link
|
||||||
<router-link
|
v-if="hasCockpitMenu"
|
||||||
v-if="hasCockpitMenu"
|
data-cy="navigation-cockpit-link"
|
||||||
data-cy="navigation-cockpit-link"
|
:to="
|
||||||
:to="
|
getCockpitUrl(
|
||||||
getCockpitUrl(
|
courseSessionsStore.currentCourseSession.course.slug
|
||||||
courseSessionsStore.currentCourseSession.course.slug
|
)
|
||||||
)
|
"
|
||||||
"
|
class="nav-item"
|
||||||
class="nav-item"
|
:class="{ 'nav-item--active': inCockpit() }"
|
||||||
:class="{ 'nav-item--active': inCockpit() }"
|
>
|
||||||
>
|
{{ t("cockpit.title") }}
|
||||||
{{ t("cockpit.title") }}
|
</router-link>
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
v-if="hasPreviewMenu"
|
v-if="hasPreviewMenu"
|
||||||
data-cy="navigation-preview-link"
|
data-cy="navigation-preview-link"
|
||||||
:to="
|
:to="
|
||||||
getLearningPathUrl(
|
getLearningPathUrl(
|
||||||
courseSessionsStore.currentCourseSession.course.slug
|
courseSessionsStore.currentCourseSession.course.slug
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span>{{ t("a.VorschauTeilnehmer") }}</span>
|
<span>{{ t("a.VorschauTeilnehmer") }}</span>
|
||||||
<it-icon-external-link class="ml-2" />
|
<it-icon-external-link class="ml-2" />
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
<router-link
|
||||||
<template v-else>
|
v-if="hasLearningPathMenu"
|
||||||
<router-link
|
data-cy="navigation-learning-path-link"
|
||||||
data-cy="navigation-learning-path-link"
|
:to="
|
||||||
:to="
|
getLearningPathUrl(
|
||||||
getLearningPathUrl(
|
courseSessionsStore.currentCourseSession.course.slug
|
||||||
courseSessionsStore.currentCourseSession.course.slug
|
)
|
||||||
)
|
"
|
||||||
"
|
class="nav-item"
|
||||||
class="nav-item"
|
:class="{ 'nav-item--active': inLearningPath() }"
|
||||||
:class="{ 'nav-item--active': inLearningPath() }"
|
>
|
||||||
>
|
{{ t("general.learningPath") }}
|
||||||
{{ t("general.learningPath") }}
|
</router-link>
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
data-cy="navigation-competence-profile-link"
|
v-if="hasCompetenceNaviMenu"
|
||||||
:to="
|
data-cy="navigation-competence-profile-link"
|
||||||
getCompetenceNaviUrl(
|
:to="
|
||||||
courseSessionsStore.currentCourseSession.course.slug
|
getCompetenceNaviUrl(
|
||||||
)
|
courseSessionsStore.currentCourseSession.course.slug
|
||||||
"
|
)
|
||||||
class="nav-item"
|
"
|
||||||
:class="{ 'nav-item--active': inCompetenceProfile() }"
|
class="nav-item"
|
||||||
>
|
:class="{ 'nav-item--active': inCompetenceProfile() }"
|
||||||
{{ t("competences.title") }}
|
>
|
||||||
</router-link>
|
{{ t("competences.title") }}
|
||||||
|
</router-link>
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
v-if="hasMentorManagementMenu"
|
v-if="hasLearningMentor"
|
||||||
data-cy="navigation-learning-mentor-link"
|
data-cy="navigation-learning-mentor-link"
|
||||||
:to="
|
:to="
|
||||||
getLearningMentorManagementUrl(
|
getLearningMentorUrl(
|
||||||
courseSessionsStore.currentCourseSession.course.slug
|
courseSessionsStore.currentCourseSession.course.slug
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
:class="{ 'nav-item--active': inLearningMentor() }"
|
:class="{ 'nav-item--active': inLearningMentor() }"
|
||||||
>
|
>
|
||||||
{{ t("a.Lernbegleitung") }}
|
{{ t("a.Lernbegleitung") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
|
||||||
import type { User } from "@/stores/user";
|
import type { User } from "@/stores/user";
|
||||||
import type { CourseSession } from "@/types";
|
import type { CourseSession } from "@/types";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import {
|
import {
|
||||||
getCockpitUrl,
|
getCockpitUrl,
|
||||||
getCompetenceNaviUrl,
|
getCompetenceNaviUrl,
|
||||||
|
getLearningMentorUrl,
|
||||||
getLearningPathUrl,
|
getLearningPathUrl,
|
||||||
getMediaCenterUrl,
|
getMediaCenterUrl,
|
||||||
} from "@/utils/utils";
|
} from "@/utils/utils";
|
||||||
|
|
@ -18,6 +18,11 @@ defineProps<{
|
||||||
hasMediaLibraryMenu: boolean;
|
hasMediaLibraryMenu: boolean;
|
||||||
hasPreviewMenu: boolean;
|
hasPreviewMenu: boolean;
|
||||||
hasCockpitMenu: boolean;
|
hasCockpitMenu: boolean;
|
||||||
|
hasLearningPathMenu: boolean;
|
||||||
|
hasCompetenceNaviMenu: boolean;
|
||||||
|
hasLearningMentor: boolean;
|
||||||
|
hasNotificationsMenu: boolean;
|
||||||
|
hasAppointmentsMenu: boolean;
|
||||||
courseSession: CourseSession | undefined;
|
courseSession: CourseSession | undefined;
|
||||||
mediaUrl?: string;
|
mediaUrl?: string;
|
||||||
user: User | undefined;
|
user: User | undefined;
|
||||||
|
|
@ -31,8 +36,6 @@ const clickLink = (to: string | undefined) => {
|
||||||
emit("closemodal");
|
emit("closemodal");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -57,42 +60,47 @@ const courseSessionsStore = useCourseSessionsStore();
|
||||||
<div v-if="courseSession" class="mt-6 border-b">
|
<div v-if="courseSession" class="mt-6 border-b">
|
||||||
<h4 class="text-sm text-gray-900">{{ courseSession.course.title }}</h4>
|
<h4 class="text-sm text-gray-900">{{ courseSession.course.title }}</h4>
|
||||||
<ul class="mt-6">
|
<ul class="mt-6">
|
||||||
<template v-if="courseSessionsStore.currentCourseSessionHasCockpit">
|
<li v-if="hasCockpitMenu" class="mb-6">
|
||||||
<li v-if="hasCockpitMenu" class="mb-6">
|
<button
|
||||||
<button
|
data-cy="navigation-mobile-cockpit-link"
|
||||||
data-cy="navigation-mobile-cockpit-link"
|
@click="clickLink(getCockpitUrl(courseSession.course.slug))"
|
||||||
@click="clickLink(getCockpitUrl(courseSession.course.slug))"
|
>
|
||||||
>
|
{{ $t("cockpit.title") }}
|
||||||
{{ $t("cockpit.title") }}
|
</button>
|
||||||
</button>
|
</li>
|
||||||
</li>
|
<li v-if="hasPreviewMenu" class="mb-6">
|
||||||
<li v-if="hasPreviewMenu" class="mb-6">
|
<button
|
||||||
<button
|
data-cy="navigation-mobile-preview-link"
|
||||||
data-cy="navigation-mobile-preview-link"
|
@click="clickLink(getLearningPathUrl(courseSession.course.slug))"
|
||||||
@click="clickLink(getLearningPathUrl(courseSession.course.slug))"
|
>
|
||||||
>
|
{{ $t("a.VorschauTeilnehmer") }}
|
||||||
{{ $t("a.VorschauTeilnehmer") }}
|
</button>
|
||||||
</button>
|
</li>
|
||||||
</li>
|
<li v-if="hasLearningPathMenu" class="mb-6">
|
||||||
</template>
|
<button
|
||||||
<template v-else>
|
data-cy="navigation-mobile-learning-path-link"
|
||||||
<li class="mb-6">
|
@click="clickLink(getLearningPathUrl(courseSession.course.slug))"
|
||||||
<button
|
>
|
||||||
data-cy="navigation-mobile-learning-path-link"
|
{{ $t("general.learningPath") }}
|
||||||
@click="clickLink(getLearningPathUrl(courseSession.course.slug))"
|
</button>
|
||||||
>
|
</li>
|
||||||
{{ $t("general.learningPath") }}
|
<li v-if="hasCompetenceNaviMenu" class="mb-6">
|
||||||
</button>
|
<button
|
||||||
</li>
|
data-cy="navigation-mobile-competence-profile-link"
|
||||||
<li class="mb-6">
|
@click="clickLink(getCompetenceNaviUrl(courseSession.course.slug))"
|
||||||
<button
|
>
|
||||||
data-cy="navigation-mobile-competence-profile-link"
|
{{ $t("competences.title") }}
|
||||||
@click="clickLink(getCompetenceNaviUrl(courseSession.course.slug))"
|
</button>
|
||||||
>
|
</li>
|
||||||
{{ $t("competences.title") }}
|
<li v-if="hasLearningMentor" class="mb-6">
|
||||||
</button>
|
<button
|
||||||
</li>
|
data-cy="navigation-mobile-mentor-link"
|
||||||
</template>
|
@click="clickLink(getLearningMentorUrl(courseSession.course.slug))"
|
||||||
|
>
|
||||||
|
{{ $t("a.Lernbegleitung") }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li v-if="hasMediaLibraryMenu" class="mb-6">
|
<li v-if="hasMediaLibraryMenu" class="mb-6">
|
||||||
<button
|
<button
|
||||||
data-cy="medialibrary-link"
|
data-cy="medialibrary-link"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useLearningMentees } from "@/services/learningMentees";
|
||||||
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
import { useCSRFFetch } from "@/fetchHelpers";
|
||||||
|
|
||||||
|
const courseSession = useCurrentCourseSession();
|
||||||
|
const { summary, fetchData } = useLearningMentees(courseSession.value.id);
|
||||||
|
|
||||||
|
const removeMyMentee = async (menteeId: string) => {
|
||||||
|
await useCSRFFetch(
|
||||||
|
`/api/mentor/${courseSession.value.id}/mentors/${summary.value?.mentor_id}/remove/${menteeId}`
|
||||||
|
).delete();
|
||||||
|
fetchData();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="summary">
|
||||||
|
<template v-if="summary.participants.length > 0">
|
||||||
|
<h2 class="heading-2 py-6">{{ $t("a.Personen, die du begleitest") }}</h2>
|
||||||
|
<div class="bg-white px-4 py-2">
|
||||||
|
<div
|
||||||
|
v-for="participant in summary.participants"
|
||||||
|
:key="participant.id"
|
||||||
|
data-cy="lm-my-mentee-list-item"
|
||||||
|
class="flex flex-col items-start justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:items-center md:gap-16"
|
||||||
|
>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<img
|
||||||
|
:alt="participant.last_name"
|
||||||
|
class="h-11 w-11 rounded-full"
|
||||||
|
:src="
|
||||||
|
participant.avatar_url || '/static/avatars/myvbv-default-avatar.png'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div class="text-bold">
|
||||||
|
{{ participant.first_name }}
|
||||||
|
{{ participant.last_name }}
|
||||||
|
</div>
|
||||||
|
{{ participant.email }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-x-5">
|
||||||
|
<router-link
|
||||||
|
data-cy="lm-my-mentee-profile"
|
||||||
|
:to="{
|
||||||
|
name: 'profileLearningPath',
|
||||||
|
params: {
|
||||||
|
userId: participant.id,
|
||||||
|
courseSlug: courseSession.course.slug,
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
class="underline"
|
||||||
|
>
|
||||||
|
{{ $t("cockpit.profileLink") }}
|
||||||
|
</router-link>
|
||||||
|
<button
|
||||||
|
class="underline"
|
||||||
|
data-cy="lm-my-mentee-remove"
|
||||||
|
@click="removeMyMentee(participant.id)"
|
||||||
|
>
|
||||||
|
{{ $t("a.Entfernen") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else>
|
||||||
|
<h2 class="heading-2 py-6">{{ $t("a.Personen, die du begleitest") }}</h2>
|
||||||
|
<div class="flex items-center bg-white px-4 py-2">
|
||||||
|
<it-icon-info class="it-icon mr-2 h-6 w-6" />
|
||||||
|
{{ $t("a.Aktuell begleitest du niemanden als Lernbegleitung.") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
import ItModal from "@/components/ui/ItModal.vue";
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
import { useCSRFFetch } from "@/fetchHelpers";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
|
||||||
|
const courseSession = useCurrentCourseSession();
|
||||||
|
|
||||||
|
const showInvitationModal = ref(false);
|
||||||
|
const inviteeEmail = ref("");
|
||||||
|
|
||||||
|
const {
|
||||||
|
execute: refreshMentors,
|
||||||
|
data: mentors,
|
||||||
|
isFetching: isFetchingMentors,
|
||||||
|
} = useCSRFFetch(`/api/mentor/${courseSession.value.id}/mentors`).json();
|
||||||
|
|
||||||
|
const {
|
||||||
|
execute: refreshInvitations,
|
||||||
|
data: invitations,
|
||||||
|
isFetching: isFetchingInvitations,
|
||||||
|
} = useCSRFFetch(`/api/mentor/${courseSession.value.id}/invitations`).json();
|
||||||
|
|
||||||
|
const isLoading = computed(
|
||||||
|
() => isFetchingMentors.value || isFetchingInvitations.value
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasMentors = computed(() => {
|
||||||
|
return (
|
||||||
|
(mentors.value && mentors.value.length > 0) ||
|
||||||
|
(invitations.value && invitations.value.length > 0)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const validEmail = computed(() => {
|
||||||
|
const isSelfInvitation = Boolean(inviteeEmail.value === useUserStore().email);
|
||||||
|
|
||||||
|
if (isSelfInvitation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||||
|
return emailRegex.test(inviteeEmail.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const removeInvitation = async (invitationId: string) => {
|
||||||
|
await useCSRFFetch(
|
||||||
|
`/api/mentor/${courseSession.value.id}/invitations/${invitationId}/delete`
|
||||||
|
).delete();
|
||||||
|
await refreshInvitations();
|
||||||
|
};
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const removeMyMentor = async (mentorId: string) => {
|
||||||
|
await useCSRFFetch(
|
||||||
|
`/api/mentor/${courseSession.value.id}/mentors/${mentorId}/remove/${userStore.id}`
|
||||||
|
).delete();
|
||||||
|
await refreshMentors();
|
||||||
|
};
|
||||||
|
|
||||||
|
const inviteMentor = async () => {
|
||||||
|
await useCSRFFetch(`/api/mentor/${courseSession.value.id}/invitations/create`).post({
|
||||||
|
email: inviteeEmail.value,
|
||||||
|
});
|
||||||
|
await refreshInvitations();
|
||||||
|
showInvitationModal.value = false;
|
||||||
|
inviteeEmail.value = "";
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="!isLoading" class="bg-gray-200">
|
||||||
|
<div class="flex flex-row items-center justify-between py-6">
|
||||||
|
<h2 class="heading-2">{{ $t("a.Meine Lernbegleitung") }}</h2>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="btn-secondary flex items-center"
|
||||||
|
@click="showInvitationModal = true"
|
||||||
|
>
|
||||||
|
<it-icon-add class="it-icon mr-2 h-6 w-6" />
|
||||||
|
{{ $t("a.Neue Lernbegleitung einladen") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-2">
|
||||||
|
<main>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="invitation in invitations"
|
||||||
|
:key="invitation.id"
|
||||||
|
class="flex flex-col justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:gap-16"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col md:flex-grow md:flex-row">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<img
|
||||||
|
:alt="invitation.email"
|
||||||
|
class="h-11 w-11 rounded-full"
|
||||||
|
:src="'/static/avatars/myvbv-default-avatar.png'"
|
||||||
|
/>
|
||||||
|
<span class="text-bold">{{ invitation.email }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center pl-8">
|
||||||
|
<it-icon-info class="it-icon mr-1 h-6 w-6" />
|
||||||
|
{{ $t("a.Die Einladung wurde noch nicht angenommen.") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="underline" @click="removeInvitation(invitation.id)">
|
||||||
|
{{ $t("a.Entfernen") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="learningMentor in mentors"
|
||||||
|
:key="learningMentor.id"
|
||||||
|
data-cy="lm-my-mentor-list-item"
|
||||||
|
class="flex flex-col justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:gap-16"
|
||||||
|
>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<img
|
||||||
|
:alt="learningMentor.mentor.last_name"
|
||||||
|
class="h-11 w-11 rounded-full"
|
||||||
|
:src="learningMentor.mentor.avatar_url"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div class="text-bold">
|
||||||
|
{{ learningMentor.mentor.first_name }}
|
||||||
|
{{ learningMentor.mentor.last_name }}
|
||||||
|
</div>
|
||||||
|
{{ learningMentor.mentor.email }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="underline"
|
||||||
|
data-cy="lm-my-mentor-remove"
|
||||||
|
@click="removeMyMentor(learningMentor.id)"
|
||||||
|
>
|
||||||
|
{{ $t("a.Entfernen") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!hasMentors">
|
||||||
|
<div class="j mx-1 my-3 flex w-fit items-center space-x-1 bg-sky-200 p-4">
|
||||||
|
<it-icon-info class="it-icon mr-2 h-6 w-6 text-sky-700" />
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
$t("a.Aktuell hast du noch keine Person als Lernbegleitung eingeladen.")
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<ItModal v-model="showInvitationModal">
|
||||||
|
<template #title>{{ $t("a.Neue Lernbegleitung einladen") }}</template>
|
||||||
|
<template #body>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="mentor-email">{{ $t("a.E-Mail Adresse") }}</label>
|
||||||
|
<input id="mentor-email" v-model="inviteeEmail" type="email" />
|
||||||
|
|
||||||
|
<button
|
||||||
|
:disabled="!validEmail"
|
||||||
|
class="btn-primary mt-8"
|
||||||
|
@click="inviteMentor()"
|
||||||
|
>
|
||||||
|
{{ $t("a.Einladung abschicken") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ItModal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -17,7 +17,7 @@ const currentCourseSession = useCurrentCourseSession();
|
||||||
</div>
|
</div>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
name: 'learningMentorManagement',
|
name: 'mentorsAndParticipants',
|
||||||
params: { courseSlug: currentCourseSession.course.slug },
|
params: { courseSlug: currentCourseSession.course.slug },
|
||||||
}"
|
}"
|
||||||
class="btn-blue px-4 py-2 font-bold"
|
class="btn-blue px-4 py-2 font-bold"
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AssignmentItem from "@/components/cockpit/mentor/AssignmentItem.vue";
|
import AssignmentItem from "@/components/learningMentor/AssignmentItem.vue";
|
||||||
import type { RouteLocationRaw } from "vue-router";
|
import type { RouteLocationRaw } from "vue-router";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
@ -16,8 +16,8 @@ defineProps<{
|
||||||
:circle-title="circleTitle"
|
:circle-title="circleTitle"
|
||||||
:pending-tasks="pendingTasks"
|
:pending-tasks="pendingTasks"
|
||||||
:task-link="taskLink"
|
:task-link="taskLink"
|
||||||
:pending-tasks-label="$t('a.Ergebnisse abgegeben')"
|
:pending-tasks-label="$t('a.Feedback freigegeben')"
|
||||||
:task-link-pending-label="$t('a.Ergebnisse bewerten')"
|
:task-link-pending-label="$t('a.Feedback geben')"
|
||||||
:task-link-label="$t('a.Praxisaufträge anschauen')"
|
:task-link-label="$t('a.Praxisaufträge anschauen')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AssignmentItem from "@/components/cockpit/mentor/AssignmentItem.vue";
|
import AssignmentItem from "@/components/learningMentor/AssignmentItem.vue";
|
||||||
import type { RouteLocationRaw } from "vue-router";
|
import type { RouteLocationRaw } from "vue-router";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
@ -17,7 +17,6 @@ import { computed, onUnmounted } from "vue";
|
||||||
import { getPreviousRoute } from "@/router/history";
|
import { getPreviousRoute } from "@/router/history";
|
||||||
import { getCompetenceNaviUrl } from "@/utils/utils";
|
import { getCompetenceNaviUrl } from "@/utils/utils";
|
||||||
import SelfEvaluationRequestFeedbackPage from "@/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedbackPage.vue";
|
import SelfEvaluationRequestFeedbackPage from "@/pages/learningPath/selfEvaluationPage/SelfEvaluationRequestFeedbackPage.vue";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
|
||||||
|
|
||||||
log.debug("LearningContent.vue setup");
|
log.debug("LearningContent.vue setup");
|
||||||
|
|
||||||
|
|
@ -30,11 +29,8 @@ const circleStore = useCircleStore();
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const courseCompletionData = useCourseDataWithCompletion();
|
const courseCompletionData = useCourseDataWithCompletion();
|
||||||
|
|
||||||
const isReadOnly = computed(
|
// if we have preview rights, we can only preview the learning content -> read only
|
||||||
// a hack: If we are a mentor or expert, we are in read only mode
|
const isReadOnly = computed(() => courseSession.value.actions.includes("preview"));
|
||||||
// we might preview / view this but can't change anything (buttons are disabled)
|
|
||||||
() => useCockpitStore().hasExpertCockpitType || useCockpitStore().hasMentorCockpitType
|
|
||||||
);
|
|
||||||
|
|
||||||
const questions = computed(() => props.learningUnit?.performance_criteria ?? []);
|
const questions = computed(() => props.learningUnit?.performance_criteria ?? []);
|
||||||
const numPages = computed(() => {
|
const numPages = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import { useMutation } from "@urql/vue";
|
||||||
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
|
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
|
||||||
import type { Assignment } from "@/types";
|
import type { Assignment } from "@/types";
|
||||||
import DateEmbedding from "@/components/dueDates/DateEmbedding.vue";
|
import DateEmbedding from "@/components/dueDates/DateEmbedding.vue";
|
||||||
import { useLearningMentors } from "@/composables";
|
import { useMyLearningMentors } from "@/composables";
|
||||||
import NoMentorInformationPanel from "@/components/mentor/NoMentorInformationPanel.vue";
|
import NoMentorInformationPanel from "@/components/learningMentor/NoMentorInformationPanel.vue";
|
||||||
import SampleSolution from "@/components/assignment/SampleSolution.vue";
|
import SampleSolution from "@/components/assignment/SampleSolution.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
@ -29,7 +29,7 @@ const upsertAssignmentCompletionMutation = useMutation(
|
||||||
UPSERT_ASSIGNMENT_COMPLETION_MUTATION
|
UPSERT_ASSIGNMENT_COMPLETION_MUTATION
|
||||||
);
|
);
|
||||||
|
|
||||||
const learningMentors = useLearningMentors().learningMentors;
|
const learningMentors = useMyLearningMentors().learningMentors;
|
||||||
const selectedLearningMentor = ref();
|
const selectedLearningMentor = ref();
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
|
|
|
||||||
|
|
@ -466,7 +466,7 @@ export function useFileUpload() {
|
||||||
return { upload, error, loading, fileInfo };
|
return { upload, error, loading, fileInfo };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLearningMentors() {
|
export function useMyLearningMentors() {
|
||||||
const learningMentors = ref<LearningMentor[]>([]);
|
const learningMentors = ref<LearningMentor[]>([]);
|
||||||
const currentCourseSessionId = useCurrentCourseSession().value.id;
|
const currentCourseSessionId = useCurrentCourseSession().value.id;
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
|
||||||
|
|
@ -464,6 +464,7 @@ export type DashboardConfigType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DashboardType =
|
export type DashboardType =
|
||||||
|
| 'MENTOR_DASHBOARD'
|
||||||
| 'PROGRESS_DASHBOARD'
|
| 'PROGRESS_DASHBOARD'
|
||||||
| 'SIMPLE_DASHBOARD'
|
| 'SIMPLE_DASHBOARD'
|
||||||
| 'STATISTICS_DASHBOARD';
|
| 'STATISTICS_DASHBOARD';
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,7 @@ enum DashboardType {
|
||||||
STATISTICS_DASHBOARD
|
STATISTICS_DASHBOARD
|
||||||
PROGRESS_DASHBOARD
|
PROGRESS_DASHBOARD
|
||||||
SIMPLE_DASHBOARD
|
SIMPLE_DASHBOARD
|
||||||
|
MENTOR_DASHBOARD
|
||||||
}
|
}
|
||||||
|
|
||||||
type CourseConfigurationObjectType {
|
type CourseConfigurationObjectType {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
|
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
|
||||||
import EvaluationContainer from "@/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue";
|
import EvaluationContainer from "@/components/assignment/evaluation/EvaluationContainer.vue";
|
||||||
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
||||||
import type { Assignment, AssignmentCompletion, CourseSessionUser } from "@/types";
|
import type { Assignment, AssignmentCompletion, CourseSessionUser } from "@/types";
|
||||||
import { useQuery } from "@urql/vue";
|
import { useQuery } from "@urql/vue";
|
||||||
|
|
@ -9,7 +9,7 @@ import log from "loglevel";
|
||||||
import { computed, onMounted } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { getPreviousRoute } from "@/router/history";
|
import { getPreviousRoute } from "@/router/history";
|
||||||
import { getAssignmentTypeTitle } from "../../../utils/utils";
|
import { getAssignmentTypeTitle } from "../../utils/utils";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
|
|
@ -42,9 +42,15 @@ function close() {
|
||||||
if (previousRoute) {
|
if (previousRoute) {
|
||||||
router.push(previousRoute);
|
router.push(previousRoute);
|
||||||
} else {
|
} else {
|
||||||
router.push({
|
if (assignment.value?.assignment_type === "PRAXIS_ASSIGNMENT") {
|
||||||
path: `/course/${props.courseSlug}/cockpit`,
|
router.push({
|
||||||
});
|
path: `/course/${props.courseSlug}/learning-mentor`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
router.push({
|
||||||
|
path: `/course/${props.courseSlug}/cockpit`,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -11,7 +11,7 @@ import type {
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed, onMounted, reactive } from "vue";
|
import { computed, onMounted, reactive } from "vue";
|
||||||
import AssignmentSubmissionProgress from "@/components/cockpit/AssignmentSubmissionProgress.vue";
|
import AssignmentSubmissionProgress from "@/components/assignment/AssignmentSubmissionProgress.vue";
|
||||||
import { useCourseSessionDetailQuery } from "@/composables";
|
import { useCourseSessionDetailQuery } from "@/composables";
|
||||||
import { formatDueDate } from "../../../components/dueDates/dueDatesUtils";
|
import { formatDueDate } from "../../../components/dueDates/dueDatesUtils";
|
||||||
import { stringifyParse } from "@/utils/utils";
|
import { stringifyParse } from "@/utils/utils";
|
||||||
|
|
@ -171,7 +171,7 @@ function findUserPointsHtml(userId: string) {
|
||||||
props.learningContent.content_type !==
|
props.learningContent.content_type !==
|
||||||
'learnpath.LearningContentEdoniqTest'
|
'learnpath.LearningContentEdoniqTest'
|
||||||
"
|
"
|
||||||
:to="`/course/${props.courseSession.course.slug}/cockpit/assignment/${learningContent.content_assignment.id}/${csu.user_id}`"
|
:to="`/course/${props.courseSession.course.slug}/assignment-evaluation/${learningContent.content_assignment.id}/${csu.user_id}`"
|
||||||
class="link lg:w-full lg:text-right"
|
class="link lg:w-full lg:text-right"
|
||||||
data-cy="show-results"
|
data-cy="show-results"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,10 @@ const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
</template>
|
</template>
|
||||||
<template #link>
|
<template #link>
|
||||||
<router-link
|
<router-link
|
||||||
:to="`/course/${props.courseSlug}/cockpit/profile/${csu.user_id}`"
|
:to="{
|
||||||
|
name: 'profileLearningPath',
|
||||||
|
params: { userId: csu.user_id, courseSlug: props.courseSlug },
|
||||||
|
}"
|
||||||
class="link w-full lg:text-right"
|
class="link w-full lg:text-right"
|
||||||
>
|
>
|
||||||
{{ $t("general.profileLink") }}
|
{{ $t("general.profileLink") }}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="bg-gray-200">
|
|
||||||
<nav class="border-b bg-white px-4 lg:px-8">
|
|
||||||
<ul class="flex flex-col lg:flex-row">
|
|
||||||
<li
|
|
||||||
class="border-t-2 border-t-transparent"
|
|
||||||
:class="{
|
|
||||||
'border-b-2 border-b-blue-900':
|
|
||||||
route.name === 'mentorCockpitOverview' || route.name === 'mentorCockpit',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<router-link :to="{ name: 'mentorCockpitOverview' }" class="block py-3">
|
|
||||||
{{ $t("a.Übersicht") }}
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
class="border-t-2 border-t-transparent lg:ml-12"
|
|
||||||
:class="{
|
|
||||||
'border-b-2 border-b-blue-900': route.name === 'mentorCockpitParticipants',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<router-link :to="{ name: 'mentorCockpitParticipants' }" class="block py-3">
|
|
||||||
{{ $t("a.Teilnehmer") }}
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<main class="container-large">
|
|
||||||
<router-view></router-view>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import type { Assignment } from "@/services/mentorCockpit";
|
|
||||||
import { useMentorCockpit } from "@/services/mentorCockpit";
|
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
|
||||||
import { computed, type Ref, ref } from "vue";
|
|
||||||
import PraxisAssignmentItem from "@/components/cockpit/mentor/PraxisAssignmentItem.vue";
|
|
||||||
import { useTranslation } from "i18next-vue";
|
|
||||||
import SelfAssignmentFeedbackAssignmentItem from "@/components/cockpit/mentor/SelfAssignmentFeedbackAssignmentItem.vue";
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const courseSession = useCurrentCourseSession();
|
|
||||||
|
|
||||||
const mentorCockpitStore = useMentorCockpit(courseSession.value.id);
|
|
||||||
const summary = mentorCockpitStore.summary;
|
|
||||||
|
|
||||||
const statusFilterValue = ref({ name: t("Alle"), id: "_all" });
|
|
||||||
|
|
||||||
const statusFilter = ref([
|
|
||||||
{ name: t("Alle"), id: "_all" },
|
|
||||||
{ name: t("a.Zu erledigen"), id: "todo" },
|
|
||||||
]);
|
|
||||||
|
|
||||||
const circleFilterValue = ref({ name: t("a.AlleCircle"), id: "_all" });
|
|
||||||
|
|
||||||
const circleFilter = computed(() => {
|
|
||||||
if (!summary.value) return [];
|
|
||||||
|
|
||||||
return [
|
|
||||||
{ name: t("a.AlleCircle"), id: "_all" },
|
|
||||||
...summary.value.circles.map((circle) => ({
|
|
||||||
name: `Circle: ${circle.title}`,
|
|
||||||
id: circle.id,
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
const filteredAssignments: Ref<Assignment[]> = computed(() => {
|
|
||||||
if (!summary.value) return [];
|
|
||||||
|
|
||||||
let filtered = summary.value.assignments;
|
|
||||||
|
|
||||||
if (statusFilterValue.value.id !== "_all") {
|
|
||||||
filtered = filtered.filter((item) => item.pending_evaluations > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (circleFilterValue.value.id !== "_all") {
|
|
||||||
filtered = filtered.filter(
|
|
||||||
(item) => item.circle_id === String(circleFilterValue.value.id)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return filtered;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="summary" class="bg-white">
|
|
||||||
<div class="flex flex-col space-x-2 lg:flex-row">
|
|
||||||
<ItDropdownSelect
|
|
||||||
v-model="statusFilterValue"
|
|
||||||
class="min-w-[10rem]"
|
|
||||||
:items="statusFilter"
|
|
||||||
borderless
|
|
||||||
></ItDropdownSelect>
|
|
||||||
<ItDropdownSelect
|
|
||||||
v-model="circleFilterValue"
|
|
||||||
class="min-w-[18rem]"
|
|
||||||
:items="circleFilter"
|
|
||||||
borderless
|
|
||||||
></ItDropdownSelect>
|
|
||||||
</div>
|
|
||||||
<template v-for="item in filteredAssignments" :key="item.id">
|
|
||||||
<PraxisAssignmentItem
|
|
||||||
v-if="item.type === 'praxis_assignment'"
|
|
||||||
:circle-title="mentorCockpitStore.getCircleTitleById(item.circle_id)"
|
|
||||||
:pending-tasks="item.pending_evaluations"
|
|
||||||
:task-link="{
|
|
||||||
name: 'mentorCockpitPraxisAssignments',
|
|
||||||
params: { praxisAssignmentId: item.id },
|
|
||||||
}"
|
|
||||||
:task-title="item.title"
|
|
||||||
/>
|
|
||||||
<SelfAssignmentFeedbackAssignmentItem
|
|
||||||
v-else-if="item.type === 'self_evaluation_feedback'"
|
|
||||||
:circle-title="mentorCockpitStore.getCircleTitleById(item.circle_id)"
|
|
||||||
:pending-tasks="item.pending_evaluations"
|
|
||||||
:task-link="{
|
|
||||||
name: 'mentorCockpitSelfEvaluationFeedbackAssignments',
|
|
||||||
params: { learningUnitId: item.id },
|
|
||||||
}"
|
|
||||||
:task-title="item.title"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useMentorCockpit } from "@/services/mentorCockpit";
|
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
|
||||||
const { summary } = useMentorCockpit(courseSession.value.id);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="summary" class="bg-white px-4 py-2">
|
|
||||||
<div
|
|
||||||
v-for="participant in summary.participants"
|
|
||||||
:key="participant.id"
|
|
||||||
class="flex flex-col items-start justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:items-center md:gap-16"
|
|
||||||
>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<img
|
|
||||||
:alt="participant.last_name"
|
|
||||||
class="h-11 w-11 rounded-full"
|
|
||||||
:src="participant.avatar_url || '/static/avatars/myvbv-default-avatar.png'"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<div class="text-bold">
|
|
||||||
{{ participant.first_name }}
|
|
||||||
{{ participant.last_name }}
|
|
||||||
</div>
|
|
||||||
{{ participant.email }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<router-link
|
|
||||||
:to="{ name: 'cockpitUserProfile', params: { userId: participant.id } }"
|
|
||||||
class="underline"
|
|
||||||
>
|
|
||||||
{{ $t("cockpit.profileLink") }}
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
<script setup lang="ts"></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="bg-gray-200">
|
|
||||||
<main>
|
|
||||||
<div>
|
|
||||||
<router-view></router-view>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -11,6 +11,7 @@ import SimpleCoursePage from "@/pages/dashboard/SimpleCoursePage.vue";
|
||||||
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||||
import CourseDetailDates from "@/components/dashboard/CourseDetailDates.vue";
|
import CourseDetailDates from "@/components/dashboard/CourseDetailDates.vue";
|
||||||
import NoCourseSession from "@/components/dashboard/NoCourseSession.vue";
|
import NoCourseSession from "@/components/dashboard/NoCourseSession.vue";
|
||||||
|
import MentorPage from "@/pages/dashboard/MentorPage.vue";
|
||||||
|
|
||||||
const dashboardStore = useDashboardStore();
|
const dashboardStore = useDashboardStore();
|
||||||
|
|
||||||
|
|
@ -23,6 +24,7 @@ const boards: Record<DashboardType, DashboardPage> = {
|
||||||
PROGRESS_DASHBOARD: { main: ProgressPage, aside: SimpleDates },
|
PROGRESS_DASHBOARD: { main: ProgressPage, aside: SimpleDates },
|
||||||
SIMPLE_DASHBOARD: { main: SimpleCoursePage, aside: SimpleDates },
|
SIMPLE_DASHBOARD: { main: SimpleCoursePage, aside: SimpleDates },
|
||||||
STATISTICS_DASHBOARD: { main: StatisticPage, aside: CourseDetailDates },
|
STATISTICS_DASHBOARD: { main: StatisticPage, aside: CourseDetailDates },
|
||||||
|
MENTOR_DASHBOARD: { main: MentorPage, aside: SimpleDates },
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(dashboardStore.loadDashboardDetails);
|
onMounted(dashboardStore.loadDashboardDetails);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getLearningMentorUrl } from "@/utils/utils";
|
||||||
|
import { useDashboardStore } from "@/stores/dashboard";
|
||||||
|
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="dashboardStore.currentDashboardConfig">
|
||||||
|
<div class="mb-14">
|
||||||
|
<div class="h-full bg-white p-6">
|
||||||
|
<h3 class="mb-4">{{ dashboardStore.currentDashboardConfig.name }}</h3>
|
||||||
|
<div>
|
||||||
|
<router-link
|
||||||
|
class="btn-blue"
|
||||||
|
data-cy="lm-dashboard-link"
|
||||||
|
:to="getLearningMentorUrl(dashboardStore.currentDashboardConfig.slug)"
|
||||||
|
>
|
||||||
|
{{ $t("a.Übersicht anschauen") }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -74,7 +74,7 @@ const showCompetenceCertificates = computed(() => {
|
||||||
<router-link
|
<router-link
|
||||||
class="btn-blue"
|
class="btn-blue"
|
||||||
:to="getLearningPathUrl(dashboardStore.currentDashboardConfig.slug)"
|
:to="getLearningPathUrl(dashboardStore.currentDashboardConfig.slug)"
|
||||||
:data-cy="`continue-course-${dashboardStore.currentDashboardConfig.id}`"
|
data-cy="progress-dashboard-continue-course-link"
|
||||||
>
|
>
|
||||||
{{ $t("general.nextStep") }}
|
{{ $t("general.nextStep") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ const dashboardStore = useDashboardStore();
|
||||||
<router-link
|
<router-link
|
||||||
class="btn-blue"
|
class="btn-blue"
|
||||||
:to="getCockpitUrl(dashboardStore.currentDashboardConfig.slug)"
|
:to="getCockpitUrl(dashboardStore.currentDashboardConfig.slug)"
|
||||||
:data-cy="`continue-course-${dashboardStore.currentDashboardConfig.id}`"
|
|
||||||
>
|
>
|
||||||
{{ $t("a.Cockpit anschauen") }}
|
{{ $t("a.Cockpit anschauen") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCSRFFetch } from "@/fetchHelpers";
|
import { useCSRFFetch } from "@/fetchHelpers";
|
||||||
import { getCockpitUrl } from "@/utils/utils";
|
import { getLearningMentorUrl } from "@/utils/utils";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseId: string;
|
courseId: string;
|
||||||
|
|
@ -56,8 +56,8 @@ const { data, error } = useCSRFFetch(
|
||||||
</template>
|
</template>
|
||||||
</i18next>
|
</i18next>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<a class="underline" :href="getCockpitUrl(data.course_slug)">
|
<a class="underline" :href="getLearningMentorUrl(data.course_slug)">
|
||||||
{{ $t("a.Cockpit anschauen") }}
|
{{ $t("a.Übersicht anschauen") }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
|
||||||
import ItModal from "@/components/ui/ItModal.vue";
|
|
||||||
import { computed, ref } from "vue";
|
|
||||||
import { useCSRFFetch } from "@/fetchHelpers";
|
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
|
||||||
|
|
||||||
const showInvitationModal = ref(false);
|
|
||||||
const inviteeEmail = ref("");
|
|
||||||
|
|
||||||
const { execute: refreshMentors, data: mentors } = useCSRFFetch(
|
|
||||||
`/api/mentor/${courseSession.value.id}/mentors`
|
|
||||||
).json();
|
|
||||||
|
|
||||||
const { execute: refreshInvitations, data: invitations } = useCSRFFetch(
|
|
||||||
`/api/mentor/${courseSession.value.id}/invitations`
|
|
||||||
).json();
|
|
||||||
|
|
||||||
const hasMentors = computed(() => {
|
|
||||||
return (
|
|
||||||
(mentors.value && mentors.value.length > 0) ||
|
|
||||||
(invitations.value && invitations.value.length > 0)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const validEmail = computed(() => {
|
|
||||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
||||||
return emailRegex.test(inviteeEmail.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
const removeInvitation = async (invitationId: string) => {
|
|
||||||
await useCSRFFetch(
|
|
||||||
`/api/mentor/${courseSession.value.id}/invitations/${invitationId}/delete`
|
|
||||||
).delete();
|
|
||||||
await refreshInvitations();
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeMentor = async (mentorId: string) => {
|
|
||||||
await useCSRFFetch(
|
|
||||||
`/api/mentor/${courseSession.value.id}/mentors/${mentorId}/leave`
|
|
||||||
).delete();
|
|
||||||
await refreshMentors();
|
|
||||||
};
|
|
||||||
|
|
||||||
const inviteMentor = async () => {
|
|
||||||
await useCSRFFetch(`/api/mentor/${courseSession.value.id}/invitations/create`).post({
|
|
||||||
email: inviteeEmail.value,
|
|
||||||
});
|
|
||||||
await refreshInvitations();
|
|
||||||
showInvitationModal.value = false;
|
|
||||||
inviteeEmail.value = "";
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="bg-gray-200">
|
|
||||||
<div class="container-large">
|
|
||||||
<header class="mb-8 mt-12">
|
|
||||||
<h1 class="mb-8">{{ $t("a.Lernbegleitung") }}</h1>
|
|
||||||
<p>
|
|
||||||
{{
|
|
||||||
$t(
|
|
||||||
"a.Hier kannst du Personen einladen, damit sie deine Lernbegleitung werden. Zudem siehst du jederzeit eine Übersicht aller Personen, die du bereits als Lernbegleitung hinzugefügt hast."
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<div class="bg-white p-6">
|
|
||||||
<div class="mb-8">
|
|
||||||
<button
|
|
||||||
class="btn-secondary flex items-center"
|
|
||||||
@click="showInvitationModal = true"
|
|
||||||
>
|
|
||||||
<it-icon-add class="it-icon mr-2 h-6 w-6" />
|
|
||||||
{{ $t("a.Neue Lernbegleitung einladen") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="border-t">
|
|
||||||
<div
|
|
||||||
v-for="invitation in invitations"
|
|
||||||
:key="invitation.id"
|
|
||||||
class="flex flex-col justify-between gap-4 border-b py-4 md:flex-row md:gap-16"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col justify-between md:flex-grow md:flex-row">
|
|
||||||
{{ invitation.email }}
|
|
||||||
<div class="flex items-center">
|
|
||||||
<it-icon-info class="it-icon mr-2 h-6 w-6" />
|
|
||||||
{{ $t("a.Die Einladung wurde noch nicht angenommen.") }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="underline" @click="removeInvitation(invitation.id)">
|
|
||||||
{{ $t("a.Entfernen") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-for="learningMentor in mentors"
|
|
||||||
:key="learningMentor.id"
|
|
||||||
class="flex flex-col justify-between gap-4 border-b py-4 md:flex-row md:gap-16"
|
|
||||||
>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<img
|
|
||||||
:alt="learningMentor.mentor.last_name"
|
|
||||||
class="h-11 w-11 rounded-full"
|
|
||||||
:src="learningMentor.mentor.avatar_url"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<div class="text-bold">
|
|
||||||
{{ learningMentor.mentor.first_name }}
|
|
||||||
{{ learningMentor.mentor.last_name }}
|
|
||||||
</div>
|
|
||||||
{{ learningMentor.mentor.email }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="underline" @click="removeMentor(learningMentor.id)">
|
|
||||||
{{ $t("a.Entfernen") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!hasMentors" class="mt-8 flex items-center">
|
|
||||||
<it-icon-info class="it-icon mr-2 h-6 w-6" />
|
|
||||||
{{
|
|
||||||
$t("a.Aktuell hast du noch keine Person als Lernbegleitung eingeladen.")
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
<ItModal v-model="showInvitationModal">
|
|
||||||
<template #title>{{ $t("a.Neue Lernbegleitung einladen") }}</template>
|
|
||||||
<template #body>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<label for="mentor-email">{{ $t("a.E-Mail Adresse") }}</label>
|
|
||||||
<input id="mentor-email" v-model="inviteeEmail" type="email" />
|
|
||||||
|
|
||||||
<button
|
|
||||||
:disabled="!validEmail"
|
|
||||||
class="btn-primary mt-8"
|
|
||||||
@click="inviteMentor()"
|
|
||||||
>
|
|
||||||
{{ $t("a.Einladung abschicken") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</ItModal>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'mentorCockpitOverview' }"
|
:to="{ name: 'learningMentorOverview' }"
|
||||||
class="btn-text mb-4 inline-flex items-center pl-0"
|
class="btn-text mb-4 inline-flex items-center pl-0"
|
||||||
>
|
>
|
||||||
<it-icon-arrow-left class="it-icon"></it-icon-arrow-left>
|
<it-icon-arrow-left class="it-icon"></it-icon-arrow-left>
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const courseSession = useCurrentCourseSession();
|
||||||
|
|
||||||
|
// if just a course session member -> hide navigation
|
||||||
|
// and automatically redirect to mentorsAndParticipants
|
||||||
|
const isMentoring = courseSession.value.actions.includes(
|
||||||
|
"learning-mentor::guide-members"
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!isMentoring) {
|
||||||
|
router.push({ name: "mentorsAndParticipants" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-gray-200">
|
||||||
|
<nav
|
||||||
|
v-if="isMentoring"
|
||||||
|
class="border-b bg-white px-4 lg:px-8"
|
||||||
|
data-cy="lm-main-navigation"
|
||||||
|
>
|
||||||
|
<ul class="flex flex-col lg:flex-row">
|
||||||
|
<li
|
||||||
|
class="border-t-2 border-t-transparent"
|
||||||
|
:class="{
|
||||||
|
'border-b-2 border-b-blue-900': route.name
|
||||||
|
?.toString()
|
||||||
|
.startsWith('learningMentor'),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
data-cy="lm-overview-navigation-link"
|
||||||
|
:to="{ name: 'learningMentorOverview' }"
|
||||||
|
class="block py-3"
|
||||||
|
>
|
||||||
|
{{ $t("a.Übersicht") }}
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li
|
||||||
|
data-cy="lm-mentees-navigation-link"
|
||||||
|
class="border-t-2 border-t-transparent lg:ml-12"
|
||||||
|
:class="{
|
||||||
|
'border-b-2 border-b-blue-900': route.name === 'mentorsAndParticipants',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<router-link :to="{ name: 'mentorsAndParticipants' }" class="block py-3">
|
||||||
|
{{ $t("a.Personen") }}
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<main class="container-large">
|
||||||
|
<router-view></router-view>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
|
import PraxisAssignmentItem from "@/components/learningMentor/PraxisAssignmentItem.vue";
|
||||||
|
import SelfAssignmentFeedbackAssignmentItem from "@/components/learningMentor/SelfAssignmentFeedbackAssignmentItem.vue";
|
||||||
|
import { useAssignmentTodoListStore } from "@/stores/learningMentor/assignmentTodoList";
|
||||||
|
|
||||||
|
const assignmentTodoListStore = useAssignmentTodoListStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h2 class="heading-2 mb-6 mt-6">{{ $t("a.Das wurde mit dir geteilt") }}</h2>
|
||||||
|
<div class="flex flex-col bg-white pb-5 pt-1">
|
||||||
|
<div class="flex flex-col lg:flex-row lg:space-x-2">
|
||||||
|
<ItDropdownSelect
|
||||||
|
v-model="assignmentTodoListStore.selectedStatus"
|
||||||
|
class="min-w-[10rem]"
|
||||||
|
:items="assignmentTodoListStore.statusOptions"
|
||||||
|
borderless
|
||||||
|
></ItDropdownSelect>
|
||||||
|
<ItDropdownSelect
|
||||||
|
v-model="assignmentTodoListStore.selectedCircle"
|
||||||
|
class="min-w-[18rem]"
|
||||||
|
:items="assignmentTodoListStore.circleOptions"
|
||||||
|
borderless
|
||||||
|
></ItDropdownSelect>
|
||||||
|
</div>
|
||||||
|
<template v-if="assignmentTodoListStore.filteredAssignments.length > 0">
|
||||||
|
<template
|
||||||
|
v-for="item in assignmentTodoListStore.filteredAssignments"
|
||||||
|
:key="item.id"
|
||||||
|
>
|
||||||
|
<PraxisAssignmentItem
|
||||||
|
v-if="item.type === 'praxis_assignment'"
|
||||||
|
:circle-title="item.circle_name"
|
||||||
|
:pending-tasks="item.pending_evaluations"
|
||||||
|
:task-link="{
|
||||||
|
name: 'learningMentorPraxisAssignments',
|
||||||
|
params: { praxisAssignmentId: item.id },
|
||||||
|
}"
|
||||||
|
:task-title="item.title"
|
||||||
|
/>
|
||||||
|
<SelfAssignmentFeedbackAssignmentItem
|
||||||
|
v-else-if="item.type === 'self_evaluation_feedback'"
|
||||||
|
:circle-title="item.circle_name"
|
||||||
|
:pending-tasks="item.pending_evaluations"
|
||||||
|
:task-link="{
|
||||||
|
name: 'learningMentorSelfEvaluationFeedbackAssignments',
|
||||||
|
params: { learningUnitId: item.id },
|
||||||
|
}"
|
||||||
|
:task-title="item.title"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<div v-else class="mx-5 flex flex-row items-center bg-green-200 pl-2">
|
||||||
|
<it-icon-check class="it-icon"></it-icon-check>
|
||||||
|
<p class="py-4 text-base">{{ $t("a.Du hast alles erledigt.") }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
import MyMentors from "@/components/learningMentor/MyMentors.vue";
|
||||||
|
import MyMentees from "@/components/learningMentor/MyMentees.vue";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const courseSession = useCurrentCourseSession();
|
||||||
|
|
||||||
|
const isMyMentorsVisible = computed(() =>
|
||||||
|
courseSession.value.actions.includes("learning-mentor::edit-mentors")
|
||||||
|
);
|
||||||
|
|
||||||
|
const isMyMenteesVisible = computed(() =>
|
||||||
|
courseSession.value.actions.includes("learning-mentor::guide-members")
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container-large space-y-20">
|
||||||
|
<template v-if="isMyMenteesVisible || isMyMentorsVisible">
|
||||||
|
<div v-if="isMyMentorsVisible">
|
||||||
|
<MyMentors data-cy="lm-my-mentors" />
|
||||||
|
</div>
|
||||||
|
<div v-if="isMyMenteesVisible">
|
||||||
|
<MyMentees data-cy="lm-my-mentees" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Assignment, Participant } from "@/services/mentorCockpit";
|
import type { Assignment, Participant } from "@/services/learningMentees";
|
||||||
import { useMentorCockpit } from "@/services/mentorCockpit";
|
import { useLearningMentees } from "@/services/learningMentees";
|
||||||
import { computed, onMounted, type Ref } from "vue";
|
import { computed, onMounted, type Ref } from "vue";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
|
|
@ -10,10 +10,10 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const mentorCockpitStore = useMentorCockpit(courseSession.value.id);
|
const learningMentees = useLearningMentees(courseSession.value.id);
|
||||||
const participants = computed(() => mentorCockpitStore.summary.value?.participants);
|
const participants = computed(() => learningMentees.summary.value?.participants);
|
||||||
const praxisAssignment: Ref<Assignment | null> = computed(() =>
|
const praxisAssignment: Ref<Assignment | null> = computed(() =>
|
||||||
mentorCockpitStore.getAssignmentById(props.praxisAssignmentId)
|
learningMentees.getAssignmentById(props.praxisAssignmentId)
|
||||||
);
|
);
|
||||||
|
|
||||||
const getParticipantById = (id: string): Participant | null => {
|
const getParticipantById = (id: string): Participant | null => {
|
||||||
|
|
@ -22,7 +22,7 @@ const getParticipantById = (id: string): Participant | null => {
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
log.debug("MentorPraxisAssignment mounted");
|
log.debug("MentorPraxisAssignment mounted");
|
||||||
mentorCockpitStore.fetchData();
|
learningMentees.fetchData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -30,9 +30,7 @@ onMounted(() => {
|
||||||
<div v-if="praxisAssignment">
|
<div v-if="praxisAssignment">
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<h2 class="mb-2">{{ $t("a.Praxisauftrag") }}: {{ praxisAssignment.title }}</h2>
|
<h2 class="mb-2">{{ $t("a.Praxisauftrag") }}: {{ praxisAssignment.title }}</h2>
|
||||||
<span class="text-gray-800">
|
<span class="text-gray-800">Circle «{{ praxisAssignment.circle_name }}»</span>
|
||||||
Circle «{{ mentorCockpitStore.getCircleTitleById(praxisAssignment.circle_id) }}»
|
|
||||||
</span>
|
|
||||||
<template v-if="praxisAssignment.pending_evaluations > 0">
|
<template v-if="praxisAssignment.pending_evaluations > 0">
|
||||||
<div class="flex flex-row items-center space-x-2 pt-4">
|
<div class="flex flex-row items-center space-x-2 pt-4">
|
||||||
<div
|
<div
|
||||||
|
|
@ -92,7 +90,7 @@ onMounted(() => {
|
||||||
<it-icon-check class="h-5 w-5"></it-icon-check>
|
<it-icon-check class="h-5 w-5"></it-icon-check>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span>{{ $t("a.Bewertung freigeben") }}</span>
|
<span>{{ $t("a.Ergebnisse abgegeben") }}</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<!-- Right -->
|
<!-- Right -->
|
||||||
|
|
@ -102,14 +100,14 @@ onMounted(() => {
|
||||||
class="btn-primary"
|
class="btn-primary"
|
||||||
:to="item.url"
|
:to="item.url"
|
||||||
>
|
>
|
||||||
{{ $t("a.Ergebnis bewerten") }}
|
{{ $t("a.Feedback geben") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
v-else-if="item.status == 'EVALUATED'"
|
v-else-if="item.status == 'EVALUATED'"
|
||||||
class="underline"
|
class="underline"
|
||||||
:to="item.url"
|
:to="item.url"
|
||||||
>
|
>
|
||||||
{{ $t("a.Bewertung ansehen") }}
|
{{ $t("a.Feedback ansehen") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Assignment, Participant } from "@/services/mentorCockpit";
|
import type { Assignment, Participant } from "@/services/learningMentees";
|
||||||
import { useMentorCockpit } from "@/services/mentorCockpit";
|
import { useLearningMentees } from "@/services/learningMentees";
|
||||||
import { computed, type Ref } from "vue";
|
import { computed, type Ref } from "vue";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
|
||||||
|
|
@ -9,15 +9,15 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const mentorCockpitStore = useMentorCockpit(courseSession.value.id);
|
const learningMentees = useLearningMentees(courseSession.value.id);
|
||||||
|
|
||||||
const selfEvaluationFeedback: Ref<Assignment | null> = computed(() =>
|
const selfEvaluationFeedback: Ref<Assignment | null> = computed(() =>
|
||||||
mentorCockpitStore.getAssignmentById(props.learningUnitId)
|
learningMentees.getAssignmentById(props.learningUnitId)
|
||||||
);
|
);
|
||||||
|
|
||||||
const getParticipantById = (id: string): Participant | null => {
|
const getParticipantById = (id: string): Participant | null => {
|
||||||
if (mentorCockpitStore.summary.value?.participants) {
|
if (learningMentees.summary.value?.participants) {
|
||||||
const found = mentorCockpitStore.summary.value.participants.find(
|
const found = learningMentees.summary.value.participants.find(
|
||||||
(item) => item.id === id
|
(item) => item.id === id
|
||||||
);
|
);
|
||||||
return found || null;
|
return found || null;
|
||||||
|
|
@ -33,9 +33,7 @@ const getParticipantById = (id: string): Participant | null => {
|
||||||
{{ $t("a.Selbsteinschätzung") }}: {{ selfEvaluationFeedback.title }}
|
{{ $t("a.Selbsteinschätzung") }}: {{ selfEvaluationFeedback.title }}
|
||||||
</h2>
|
</h2>
|
||||||
<span class="text-gray-800">
|
<span class="text-gray-800">
|
||||||
Circle «{{
|
Circle «{{ selfEvaluationFeedback.circle_name }}»
|
||||||
mentorCockpitStore.getCircleTitleById(selfEvaluationFeedback.circle_id)
|
|
||||||
}}»
|
|
||||||
</span>
|
</span>
|
||||||
<template v-if="selfEvaluationFeedback.pending_evaluations > 0">
|
<template v-if="selfEvaluationFeedback.pending_evaluations > 0">
|
||||||
<div class="flex flex-row items-center space-x-2 pt-4">
|
<div class="flex flex-row items-center space-x-2 pt-4">
|
||||||
|
|
@ -93,7 +93,7 @@ const handleContinue = () => {
|
||||||
const clickExit = () => {
|
const clickExit = () => {
|
||||||
console.log("clickExit");
|
console.log("clickExit");
|
||||||
router.push({
|
router.push({
|
||||||
name: "mentorCockpitSelfEvaluationFeedbackAssignments",
|
name: "learningMentorSelfEvaluationFeedbackAssignments",
|
||||||
params: {
|
params: {
|
||||||
learningUnitId: props.learningUnitId,
|
learningUnitId: props.learningUnitId,
|
||||||
},
|
},
|
||||||
|
|
@ -172,7 +172,10 @@ watch(
|
||||||
<div>
|
<div>
|
||||||
<router-link
|
<router-link
|
||||||
class="link"
|
class="link"
|
||||||
:to="`/course/${courseSlug}/cockpit/profile/${profileUser.user_id}`"
|
:to="{
|
||||||
|
name: 'profileLearningPath',
|
||||||
|
params: { userId: profileUser.user_id, courseSlug },
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
{{ $t("general.profileLink") }}
|
{{ $t("general.profileLink") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ defineEmits(["exit"]);
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="absolute bottom-0 left-0 top-0 w-full bg-white">
|
<div class="absolute bottom-0 left-0 top-0 w-full bg-white">
|
||||||
<CoursePreviewBar v-if="courseSessionsStore.hasCourseSessionPreview" />
|
<CoursePreviewBar v-if="courseSessionsStore.isCourseSessionPreviewActive" />
|
||||||
<div
|
<div
|
||||||
:class="{
|
:class="{
|
||||||
'h-content': !courseSessionsStore.hasCourseSessionPreview,
|
'h-content': !courseSessionsStore.isCourseSessionPreviewActive,
|
||||||
'h-content-preview': courseSessionsStore.hasCourseSessionPreview,
|
'h-content-preview': courseSessionsStore.isCourseSessionPreviewActive,
|
||||||
}"
|
}"
|
||||||
class="overflow-y-auto"
|
class="overflow-y-auto"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import * as log from "loglevel";
|
||||||
import { computed, onMounted, ref, watchEffect } from "vue";
|
import { computed, onMounted, ref, watchEffect } from "vue";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import { learningContentTypeData } from "@/utils/typeMaps";
|
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||||
import EvaluationSummary from "@/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue";
|
import EvaluationSummary from "@/components/assignment/evaluation/EvaluationSummary.vue";
|
||||||
import { bustItGetCache } from "@/fetchHelpers";
|
import { bustItGetCache } from "@/fetchHelpers";
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { LearningUnit, LearningUnitPerformanceCriteria } from "@/types";
|
import type { LearningUnit, LearningUnitPerformanceCriteria } from "@/types";
|
||||||
import { useLearningMentors } from "@/composables";
|
import { useMyLearningMentors } from "@/composables";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import ItButton from "@/components/ui/ItButton.vue";
|
import ItButton from "@/components/ui/ItButton.vue";
|
||||||
import NoMentorInformationPanel from "@/components/mentor/NoMentorInformationPanel.vue";
|
import NoMentorInformationPanel from "@/components/learningMentor/NoMentorInformationPanel.vue";
|
||||||
import { useSelfEvaluationFeedback } from "@/services/selfEvaluationFeedback";
|
import { useSelfEvaluationFeedback } from "@/services/selfEvaluationFeedback";
|
||||||
import FeedbackRequestedInformationPanel from "@/components/selfEvaluationFeedback/FeedbackRequestedInformationPanel.vue";
|
import FeedbackRequestedInformationPanel from "@/components/selfEvaluationFeedback/FeedbackRequestedInformationPanel.vue";
|
||||||
import FeedbackReceived from "@/components/selfEvaluationFeedback/FeedbackReceived.vue";
|
import FeedbackReceived from "@/components/selfEvaluationFeedback/FeedbackReceived.vue";
|
||||||
|
|
@ -24,7 +24,7 @@ const isStoredFeedbackLoading = computed(() => selfEvaluationFeedback.loading.va
|
||||||
const feedbackProvider = computed(() => storedFeedback.value?.feedback_provider_user);
|
const feedbackProvider = computed(() => storedFeedback.value?.feedback_provider_user);
|
||||||
|
|
||||||
// if no feedback is stored "current session" state management (mentor selection etc.)
|
// if no feedback is stored "current session" state management (mentor selection etc.)
|
||||||
const learningMentors = useLearningMentors();
|
const learningMentors = useMyLearningMentors();
|
||||||
const isMentorsLoading = computed(() => learningMentors.loading.value);
|
const isMentorsLoading = computed(() => learningMentors.loading.value);
|
||||||
|
|
||||||
const mentors = computed(() => {
|
const mentors = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import CockpitProfileContent from "@/components/cockpit/profile/CockpitProfileContent.vue";
|
import CockpitProfileContent from "@/components/userProfile/UserProfileContent.vue";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import SelfEvaluationAndFeedbackList from "@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackList.vue";
|
import SelfEvaluationAndFeedbackList from "@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackList.vue";
|
||||||
import SelfEvaluationAndFeedbackOverview from "@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackOverview.vue";
|
import SelfEvaluationAndFeedbackOverview from "@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackOverview.vue";
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { calculateCircleSectorData } from "@/pages/learningPath/learningPathPage/utils";
|
import { calculateCircleSectorData } from "@/pages/learningPath/learningPathPage/utils";
|
||||||
import { useCourseDataWithCompletion } from "@/composables";
|
import { useCourseDataWithCompletion } from "@/composables";
|
||||||
import CockpitProfileContent from "@/components/cockpit/profile/CockpitProfileContent.vue";
|
import UserProfileContent from "@/components/userProfile/UserProfileContent.vue";
|
||||||
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
||||||
import LearningSequence from "@/pages/learningPath/circlePage/LearningSequence.vue";
|
import LearningSequence from "@/pages/learningPath/circlePage/LearningSequence.vue";
|
||||||
import { ref, watch } from "vue";
|
import { ref, watch } from "vue";
|
||||||
|
|
@ -28,7 +28,7 @@ watch(lpQueryResult.learningPath, () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CockpitProfileContent>
|
<UserProfileContent>
|
||||||
<template #side>
|
<template #side>
|
||||||
<div
|
<div
|
||||||
v-for="topic in lpQueryResult.learningPath?.value?.topics ?? []"
|
v-for="topic in lpQueryResult.learningPath?.value?.topics ?? []"
|
||||||
|
|
@ -69,5 +69,5 @@ watch(lpQueryResult.learningPath, () => {
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</template>
|
</template>
|
||||||
</CockpitProfileContent>
|
</UserProfileContent>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -13,8 +13,8 @@ const props = defineProps<{
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const pages = ref([
|
const pages = ref([
|
||||||
{ label: t("general.learningPath"), route: "cockpitProfileLearningPath" },
|
{ label: t("general.learningPath"), route: "profileLearningPath" },
|
||||||
{ label: t("a.KompetenzNavi"), route: "cockpitProfileCompetence" },
|
{ label: t("a.KompetenzNavi"), route: "profileCompetence" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
|
|
@ -40,14 +40,6 @@ onMounted(() => {
|
||||||
<div v-if="user" class="flex flex-col bg-gray-200">
|
<div v-if="user" class="flex flex-col bg-gray-200">
|
||||||
<div class="relative border-b bg-white shadow-md">
|
<div class="relative border-b bg-white shadow-md">
|
||||||
<div class="container-large pb-0">
|
<div class="container-large pb-0">
|
||||||
<router-link
|
|
||||||
class="btn-text inline-flex items-center pl-0"
|
|
||||||
:to="`/course/${props.courseSlug}/cockpit`"
|
|
||||||
>
|
|
||||||
<it-icon-arrow-left />
|
|
||||||
<span>{{ $t("general.back") }}</span>
|
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<div class="mb-12 mt-2 flex items-center">
|
<div class="mb-12 mt-2 flex items-center">
|
||||||
<img class="mr-8 h-48 w-48 rounded-full" :src="user.avatar_url" />
|
<img class="mr-8 h-48 w-48 rounded-full" :src="user.avatar_url" />
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import { createPinia, setActivePinia } from "pinia";
|
|
||||||
import { beforeEach, describe, expect, vi } from "vitest";
|
|
||||||
import * as courseSessions from "../../stores/courseSessions";
|
|
||||||
import { expertRequired } from "../guards";
|
|
||||||
|
|
||||||
describe("Guards", () => {
|
|
||||||
afterEach(() => {
|
|
||||||
vi.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// creates a fresh pinia and make it active so it's automatically picked
|
|
||||||
// up by any useStore() call without having to pass it to it:
|
|
||||||
// `useStore(pinia)`
|
|
||||||
setActivePinia(createPinia());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cannot route to cockpit", () => {
|
|
||||||
vi.spyOn(courseSessions, "useCourseSessionsStore").mockReturnValue({
|
|
||||||
currentCourseSessionHasCockpit: false,
|
|
||||||
});
|
|
||||||
const slug = "test";
|
|
||||||
expect(expertRequired({ params: { courseSlug: "test" } })).toEqual(
|
|
||||||
`/course/${slug}/learn`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can route to cockpit", () => {
|
|
||||||
vi.spyOn(courseSessions, "useCourseSessionsStore").mockReturnValue({
|
|
||||||
currentCourseSessionHasCockpit: true,
|
|
||||||
});
|
|
||||||
const to = {
|
|
||||||
params: {
|
|
||||||
courseSlug: "test",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
expect(expertRequired(to)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { getLoginURLNext, shouldUseSSO } from "@/router/utils";
|
import { getLoginURLNext, shouldUseSSO } from "@/router/utils";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import type { NavigationGuard, RouteLocationNormalized } from "vue-router";
|
import type { NavigationGuard, RouteLocationNormalized } from "vue-router";
|
||||||
|
|
@ -52,16 +51,6 @@ const loginRequired = (to: RouteLocationNormalized) => {
|
||||||
return !to.meta?.public;
|
return !to.meta?.public;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const expertRequired: NavigationGuard = (to: RouteLocationNormalized) => {
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
|
||||||
if (courseSessionsStore.currentCourseSessionHasCockpit) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
const courseSlug = to.params.courseSlug as string;
|
|
||||||
return `/course/${courseSlug}/learn`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function handleCurrentCourseSession(to: RouteLocationNormalized) {
|
export async function handleCurrentCourseSession(to: RouteLocationNormalized) {
|
||||||
// register after login hooks
|
// register after login hooks
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
@ -112,31 +101,6 @@ export async function handleCourseSessionAsQueryParam(to: RouteLocationNormalize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleCockpit(to: RouteLocationNormalized) {
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
|
||||||
const courseId = courseSessionsStore.currentCourseSession?.course.id || null;
|
|
||||||
const cockpitStore = useCockpitStore();
|
|
||||||
await cockpitStore.fetchCockpitType(courseId);
|
|
||||||
|
|
||||||
if (to.name === "cockpit") {
|
|
||||||
if (cockpitStore.hasExpertCockpitType) {
|
|
||||||
return { name: "expertCockpit", params: to.params };
|
|
||||||
} else if (cockpitStore.hasMentorCockpitType) {
|
|
||||||
return { name: "mentorCockpitOverview", params: to.params };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cockpitType = to.meta?.cockpitType;
|
|
||||||
|
|
||||||
if (!cockpitType) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cockpitType !== cockpitStore.cockpitType) {
|
|
||||||
return "/";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleAcceptLearningMentorInvitation(
|
export async function handleAcceptLearningMentorInvitation(
|
||||||
to: RouteLocationNormalized
|
to: RouteLocationNormalized
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import UKStartPage from "@/pages/start/UKStartPage.vue";
|
||||||
import VVStartPage from "@/pages/start/VVStartPage.vue";
|
import VVStartPage from "@/pages/start/VVStartPage.vue";
|
||||||
import {
|
import {
|
||||||
handleAcceptLearningMentorInvitation,
|
handleAcceptLearningMentorInvitation,
|
||||||
handleCockpit,
|
|
||||||
handleCourseSessionAsQueryParam,
|
handleCourseSessionAsQueryParam,
|
||||||
handleCurrentCourseSession,
|
handleCurrentCourseSession,
|
||||||
redirectToLoginIfRequired,
|
redirectToLoginIfRequired,
|
||||||
|
|
@ -141,12 +140,6 @@ const router = createRouter({
|
||||||
import("../pages/learningPath/learningContentPage/LearningContentPage.vue"),
|
import("../pages/learningPath/learningContentPage/LearningContentPage.vue"),
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/course/:courseSlug/mentor",
|
|
||||||
component: () => import("@/pages/learningMentor/MentorManagementPage.vue"),
|
|
||||||
props: true,
|
|
||||||
name: "learningMentorManagement",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/lernbegleitung/:courseId/invitation/:invitationId",
|
path: "/lernbegleitung/:courseId/invitation/:invitationId",
|
||||||
component: () => import("@/pages/learningMentor/InvitationAcceptPage.vue"),
|
component: () => import("@/pages/learningMentor/InvitationAcceptPage.vue"),
|
||||||
|
|
@ -157,115 +150,89 @@ const router = createRouter({
|
||||||
public: true,
|
public: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/course/:courseSlug/profile/:userId",
|
||||||
|
component: () => import("@/pages/userProfile/UserProfilePage.vue"),
|
||||||
|
props: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "learning-path",
|
||||||
|
component: () => import("@/pages/userProfile/LearningPathProfilePage.vue"),
|
||||||
|
props: true,
|
||||||
|
name: "profileLearningPath",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "competence",
|
||||||
|
component: () => import("@/pages/userProfile/CompetenceProfilePage.vue"),
|
||||||
|
props: true,
|
||||||
|
name: "profileCompetence",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/course/:courseSlug/learning-mentor",
|
||||||
|
component: () => import("@/pages/learningMentor/mentor/MentorIndexPage.vue"),
|
||||||
|
props: true,
|
||||||
|
name: "learningMentor",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
component: () =>
|
||||||
|
import("@/pages/learningMentor/mentor/MentorOverviewPage.vue"),
|
||||||
|
name: "learningMentorOverview",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "participants",
|
||||||
|
component: () =>
|
||||||
|
import("@/pages/learningMentor/mentor/MentorParticipantsPage.vue"),
|
||||||
|
name: "mentorsAndParticipants",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "self-evaluation-feedback/:learningUnitId",
|
||||||
|
component: () =>
|
||||||
|
import("@/pages/learningMentor/mentor/SelfEvaluationFeedbackPage.vue"),
|
||||||
|
name: "mentorSelfEvaluationFeedback",
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "details",
|
||||||
|
component: () =>
|
||||||
|
import("@/pages/learningMentor/mentor/MentorDetailParentPage.vue"),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "praxis-assignments/:praxisAssignmentId",
|
||||||
|
component: () =>
|
||||||
|
import("@/pages/learningMentor/mentor/MentorPraxisAssignmentPage.vue"),
|
||||||
|
name: "learningMentorPraxisAssignments",
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "self-evaluation-feedback-assignments/:learningUnitId",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
"@/pages/learningMentor/mentor/MentorSelfEvaluationFeedbackAssignmentPage.vue"
|
||||||
|
),
|
||||||
|
name: "learningMentorSelfEvaluationFeedbackAssignments",
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/course/:courseSlug/assignment-evaluation/:assignmentId/:userId",
|
||||||
|
component: () =>
|
||||||
|
import("@/pages/assignmentEvaluation/AssignmentEvaluationPage.vue"),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/course/:courseSlug/cockpit",
|
path: "/course/:courseSlug/cockpit",
|
||||||
name: "cockpit",
|
name: "cockpit",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "expert",
|
path: "",
|
||||||
component: () => import("@/pages/cockpit/cockpitPage/CockpitExpertPage.vue"),
|
component: () => import("@/pages/cockpit/cockpitPage/CockpitExpertPage.vue"),
|
||||||
props: true,
|
props: true,
|
||||||
name: "expertCockpit",
|
|
||||||
meta: {
|
|
||||||
cockpitType: "expert",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "mentor",
|
|
||||||
component: () => import("@/pages/cockpit/cockpitPage/CockpitMentorPage.vue"),
|
|
||||||
name: "mentorCockpit",
|
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: "",
|
|
||||||
component: () =>
|
|
||||||
import("@/pages/cockpit/cockpitPage/mentor/MentorOverviewPage.vue"),
|
|
||||||
name: "mentorCockpitOverview",
|
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "participants",
|
|
||||||
component: () =>
|
|
||||||
import("@/pages/cockpit/cockpitPage/mentor/MentorParticipantsPage.vue"),
|
|
||||||
name: "mentorCockpitParticipants",
|
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "self-evaluation-feedback/:learningUnitId",
|
|
||||||
component: () =>
|
|
||||||
import(
|
|
||||||
"@/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedbackPage.vue"
|
|
||||||
),
|
|
||||||
name: "mentorSelfEvaluationFeedback",
|
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
props: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "details",
|
|
||||||
component: () =>
|
|
||||||
import("@/pages/cockpit/cockpitPage/mentor/MentorDetailParentPage.vue"),
|
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: "praxis-assignments/:praxisAssignmentId",
|
|
||||||
component: () =>
|
|
||||||
import(
|
|
||||||
"@/pages/cockpit/cockpitPage/mentor/MentorPraxisAssignmentPage.vue"
|
|
||||||
),
|
|
||||||
name: "mentorCockpitPraxisAssignments",
|
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
props: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "self-evaluation-feedback-assignments/:learningUnitId",
|
|
||||||
component: () =>
|
|
||||||
import(
|
|
||||||
"@/pages/cockpit/cockpitPage/mentor/MentorSelfEvaluationFeedbackAssignmentPage.vue"
|
|
||||||
),
|
|
||||||
name: "mentorCockpitSelfEvaluationFeedbackAssignments",
|
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
props: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "profile/:userId",
|
|
||||||
component: () =>
|
|
||||||
import("@/pages/cockpit/profilePage/CockpitUserProfilePage.vue"),
|
|
||||||
props: true,
|
|
||||||
name: "cockpitUserProfile",
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: "learning-path",
|
|
||||||
component: () =>
|
|
||||||
import("@/pages/cockpit/profilePage/LearningPathProfilePage.vue"),
|
|
||||||
props: true,
|
|
||||||
name: "cockpitProfileLearningPath",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "competence",
|
|
||||||
component: () =>
|
|
||||||
import("@/pages/cockpit/profilePage/CompetenceProfilePage.vue"),
|
|
||||||
props: true,
|
|
||||||
name: "cockpitProfileCompetence",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "profile/:userId/:circleSlug",
|
path: "profile/:userId/:circleSlug",
|
||||||
|
|
@ -283,14 +250,6 @@ const router = createRouter({
|
||||||
import("@/pages/cockpit/assignmentsPage/AssignmentsPage.vue"),
|
import("@/pages/cockpit/assignmentsPage/AssignmentsPage.vue"),
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "assignment/:assignmentId/:userId",
|
|
||||||
component: () =>
|
|
||||||
import(
|
|
||||||
"@/pages/cockpit/assignmentEvaluationPage/AssignmentEvaluationPage.vue"
|
|
||||||
),
|
|
||||||
props: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "attendance",
|
path: "attendance",
|
||||||
component: () =>
|
component: () =>
|
||||||
|
|
@ -431,8 +390,6 @@ router.beforeEach(redirectToLoginIfRequired);
|
||||||
router.beforeEach(handleCurrentCourseSession);
|
router.beforeEach(handleCurrentCourseSession);
|
||||||
router.beforeEach(handleCourseSessionAsQueryParam);
|
router.beforeEach(handleCourseSessionAsQueryParam);
|
||||||
|
|
||||||
router.beforeEach(handleCockpit);
|
|
||||||
|
|
||||||
router.beforeEach(addToHistory);
|
router.beforeEach(addToHistory);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -34,32 +34,26 @@ export interface Assignment {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
circle_id: string;
|
circle_id: string;
|
||||||
|
circle_name: string;
|
||||||
pending_evaluations: number;
|
pending_evaluations: number;
|
||||||
completions: Completion[];
|
completions: Completion[];
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Summary {
|
export interface Summary {
|
||||||
|
mentor_id: string;
|
||||||
participants: Participant[];
|
participants: Participant[];
|
||||||
circles: Circle[];
|
circles: Circle[];
|
||||||
assignments: Assignment[];
|
assignments: Assignment[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMentorCockpit = (
|
export const useLearningMentees = (
|
||||||
courseSessionId: string | Ref<string> | (() => string)
|
courseSessionId: string | Ref<string> | (() => string)
|
||||||
) => {
|
) => {
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const summary: Ref<Summary | null> = ref(null);
|
const summary: Ref<Summary | null> = ref(null);
|
||||||
const error = ref(null);
|
const error = ref(null);
|
||||||
|
|
||||||
const getCircleTitleById = (id: string): string => {
|
|
||||||
if (summary.value?.circles) {
|
|
||||||
const circle = summary.value.circles.find((circle) => String(circle.id) === id);
|
|
||||||
return circle ? circle.title : "";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAssignmentById = (id: string): Assignment | null => {
|
const getAssignmentById = (id: string): Assignment | null => {
|
||||||
if (summary.value?.assignments) {
|
if (summary.value?.assignments) {
|
||||||
const found = summary.value.assignments.find(
|
const found = summary.value.assignments.find(
|
||||||
|
|
@ -92,7 +86,6 @@ export const useMentorCockpit = (
|
||||||
isLoading,
|
isLoading,
|
||||||
summary,
|
summary,
|
||||||
error,
|
error,
|
||||||
getCircleTitleById,
|
|
||||||
fetchData,
|
fetchData,
|
||||||
getAssignmentById,
|
getAssignmentById,
|
||||||
};
|
};
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
import type { CourseSession } from "@/types";
|
|
||||||
import { createPinia, setActivePinia } from "pinia";
|
|
||||||
import { beforeEach, describe, expect, vi } from "vitest";
|
|
||||||
import { useCourseSessionsStore } from "../courseSessions";
|
|
||||||
import { useUserStore } from "../user";
|
|
||||||
|
|
||||||
let user = {};
|
|
||||||
let courseSessions: CourseSession[] = [];
|
|
||||||
|
|
||||||
describe("CourseSession Store", () => {
|
|
||||||
vi.mock("vue-router", () => ({
|
|
||||||
useRoute: () => ({
|
|
||||||
path: "/course/test-course/learn/",
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("@/fetchHelpers", () => {
|
|
||||||
const itGetCached = () => Promise.resolve([]);
|
|
||||||
const itPost = () => Promise.resolve([]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
itGetCached,
|
|
||||||
itPost,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// creates a fresh pinia and make it active so it's automatically picked
|
|
||||||
// up by any useStore() call without having to pass it to it:
|
|
||||||
// `useStore(pinia)`
|
|
||||||
setActivePinia(createPinia());
|
|
||||||
user = {
|
|
||||||
is_superuser: false,
|
|
||||||
course_session_expert: [],
|
|
||||||
};
|
|
||||||
courseSessions = [
|
|
||||||
{
|
|
||||||
id: "1",
|
|
||||||
created_at: "2021-05-11T10:00:00.000000Z",
|
|
||||||
updated_at: "2023-05-11T10:00:00.000000Z",
|
|
||||||
course: {
|
|
||||||
id: "1",
|
|
||||||
title: "Test Course",
|
|
||||||
category_name: "Test Category",
|
|
||||||
slug: "test-course",
|
|
||||||
},
|
|
||||||
title: "Test Course Session",
|
|
||||||
start_date: "2022-05-11T10:00:00.000000Z",
|
|
||||||
end_date: "2023-05-11T10:00:00.000000Z",
|
|
||||||
learning_path_url: "/course/test-course/learn/",
|
|
||||||
competence_url: "/course/test-course/competence/",
|
|
||||||
course_url: "/course/test-course/",
|
|
||||||
media_library_url: "/course/test-course/media/",
|
|
||||||
attendance_courses: [],
|
|
||||||
additional_json_data: {},
|
|
||||||
documents: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
it("normal user has no cockpit", () => {
|
|
||||||
const userStore = useUserStore();
|
|
||||||
userStore.$patch(user);
|
|
||||||
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
|
||||||
courseSessionsStore._currentCourseSlug = "test-course";
|
|
||||||
courseSessionsStore.allCourseSessions = courseSessions;
|
|
||||||
|
|
||||||
expect(courseSessionsStore.currentCourseSessionHasCockpit).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("superuser has cockpit", () => {
|
|
||||||
const userStore = useUserStore();
|
|
||||||
userStore.$patch(Object.assign(user, { is_superuser: true }));
|
|
||||||
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
|
||||||
courseSessionsStore._currentCourseSlug = "test-course";
|
|
||||||
courseSessionsStore.allCourseSessions = courseSessions;
|
|
||||||
|
|
||||||
expect(courseSessionsStore.currentCourseSessionHasCockpit).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("expert has cockpit", () => {
|
|
||||||
const userStore = useUserStore();
|
|
||||||
userStore.$patch(
|
|
||||||
Object.assign(user, { course_session_experts: [courseSessions[0].id] })
|
|
||||||
);
|
|
||||||
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
|
||||||
courseSessionsStore._currentCourseSlug = "test-course";
|
|
||||||
courseSessionsStore.allCourseSessions = courseSessions;
|
|
||||||
|
|
||||||
expect(courseSessionsStore.currentCourseSessionHasCockpit).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import { itGetCached } from "@/fetchHelpers";
|
|
||||||
import { defineStore } from "pinia";
|
|
||||||
import type { Ref } from "vue";
|
|
||||||
import { computed, ref } from "vue";
|
|
||||||
|
|
||||||
type CockpitType = "mentor" | "expert" | null;
|
|
||||||
|
|
||||||
export const useCockpitStore = defineStore("cockpit", () => {
|
|
||||||
const cockpitType: Ref<CockpitType> = ref(null);
|
|
||||||
const isLoading = ref(false);
|
|
||||||
|
|
||||||
const hasExpertCockpitType = computed(() => cockpitType.value === "expert");
|
|
||||||
const hasMentorCockpitType = computed(() => cockpitType.value === "mentor");
|
|
||||||
const hasNoCockpitType = computed(() => cockpitType.value === null);
|
|
||||||
|
|
||||||
async function fetchCockpitType(courseId: string | null) {
|
|
||||||
if (!courseId) {
|
|
||||||
cockpitType.value = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading.value = true;
|
|
||||||
const url = `/api/course/${courseId}/cockpit/`;
|
|
||||||
const response = await itGetCached(url);
|
|
||||||
cockpitType.value = response.type;
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
fetchCockpitType,
|
|
||||||
hasExpertCockpitType,
|
|
||||||
hasMentorCockpitType,
|
|
||||||
hasNoCockpitType,
|
|
||||||
isLoading,
|
|
||||||
cockpitType,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { itGetCached } from "@/fetchHelpers";
|
import { itGetCached } from "@/fetchHelpers";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
|
||||||
import type { CourseSession, DueDate } from "@/types";
|
import type { CourseSession, DueDate } from "@/types";
|
||||||
import eventBus from "@/utils/eventBus";
|
import eventBus from "@/utils/eventBus";
|
||||||
import { useRouteLookups } from "@/utils/route";
|
import { useRouteLookups } from "@/utils/route";
|
||||||
|
|
@ -59,7 +58,7 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
|
|
||||||
function selectedCourseSessionForCourse(courseSlug: string) {
|
function selectedCourseSessionForCourse(courseSlug: string) {
|
||||||
// Wir wollen pro Kurs wissen, welche Durchführung der User zuletzt ausgewählt hat.
|
// Wir wollen pro Kurs wissen, welche Durchführung der User zuletzt ausgewählt hat.
|
||||||
// Die letzte Durchführung wird im localStorage via `selectedCoruseSessionMap`
|
// Die letzte Durchführung wird im localStorage via `selectedCourseSessionMap`
|
||||||
// gespeichert und hier geladen.
|
// gespeichert und hier geladen.
|
||||||
// Wenn noch keine Durchführung ausgewählt wurde, wird die erste Durchführung
|
// Wenn noch keine Durchführung ausgewählt wurde, wird die erste Durchführung
|
||||||
// in `courseSessionForCourse` zurückgegeben.
|
// in `courseSessionForCourse` zurückgegeben.
|
||||||
|
|
@ -133,31 +132,11 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
return allCourseSessionsForCourse(_currentCourseSlug.value);
|
return allCourseSessionsForCourse(_currentCourseSlug.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentCourseSessionHasCockpit = computed(() => {
|
const isCourseSessionPreviewActive = computed(() => {
|
||||||
if (currentCourseSession.value) {
|
const hasPreview = currentCourseSession.value?.actions.includes("preview");
|
||||||
return hasCockpit(currentCourseSession.value);
|
return Boolean(hasPreview && (inLearningPath() || inCompetenceProfile()));
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasCourseSessionPreview = computed(() => {
|
|
||||||
const isCourseExpert =
|
|
||||||
currentCourseSession.value && currentCourseSessionHasCockpit.value;
|
|
||||||
return Boolean(isCourseExpert && (inLearningPath() || inCompetenceProfile()));
|
|
||||||
});
|
|
||||||
|
|
||||||
function hasCockpit(courseSession: CourseSession) {
|
|
||||||
const userStore = useUserStore();
|
|
||||||
return (
|
|
||||||
useCockpitStore().hasMentorCockpitType ||
|
|
||||||
useCockpitStore().hasExpertCockpitType ||
|
|
||||||
// for legacy reasons: don't forget course session supervisors!
|
|
||||||
userStore.course_session_experts.includes(courseSession.id) ||
|
|
||||||
userStore.is_superuser
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function allDueDates() {
|
function allDueDates() {
|
||||||
const allDueDatesReturn: DueDate[] = [];
|
const allDueDatesReturn: DueDate[] = [];
|
||||||
|
|
||||||
|
|
@ -185,12 +164,9 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
return {
|
return {
|
||||||
uniqueCourseSessionsByCourse,
|
uniqueCourseSessionsByCourse,
|
||||||
allCurrentCourseSessions,
|
allCurrentCourseSessions,
|
||||||
courseSessionForCourse,
|
|
||||||
getCourseSessionById,
|
getCourseSessionById,
|
||||||
switchCourseSessionById,
|
switchCourseSessionById,
|
||||||
hasCockpit,
|
isCourseSessionPreviewActive,
|
||||||
hasCourseSessionPreview,
|
|
||||||
currentCourseSessionHasCockpit,
|
|
||||||
allDueDates,
|
allDueDates,
|
||||||
|
|
||||||
// use `useCurrentCourseSession` whenever possible
|
// use `useCurrentCourseSession` whenever possible
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
import { useLearningMentees } from "@/services/learningMentees";
|
||||||
|
import { useTranslation } from "i18next-vue";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
|
interface FilterOption {
|
||||||
|
name: string;
|
||||||
|
id: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAssignmentTodoListStore = defineStore("filters", () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const courseSession = useCurrentCourseSession();
|
||||||
|
const learningMentees = useLearningMentees(courseSession.value.id);
|
||||||
|
const summary = computed(() => learningMentees.summary.value);
|
||||||
|
|
||||||
|
const statusOptions = ref<FilterOption[]>([
|
||||||
|
{ name: t("Alle"), id: "_all" },
|
||||||
|
{ name: t("a.Zu erledigen"), id: "_todo" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const selectedStatus = ref<FilterOption>(statusOptions.value[1]);
|
||||||
|
|
||||||
|
const allCircles = { name: t("a.AlleCircle"), id: "_all" };
|
||||||
|
const circleOptions = computed(() => {
|
||||||
|
if (!summary.value) return [allCircles];
|
||||||
|
return [
|
||||||
|
allCircles,
|
||||||
|
...summary.value.circles.map((circle) => ({
|
||||||
|
name: `Circle: ${circle.title}`,
|
||||||
|
id: circle.id,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedCircle = ref<FilterOption>(circleOptions.value[0]);
|
||||||
|
|
||||||
|
const filteredAssignments = computed(() => {
|
||||||
|
if (!summary.value) return [];
|
||||||
|
|
||||||
|
let filtered = summary.value.assignments;
|
||||||
|
|
||||||
|
if (selectedStatus.value.id !== "_all") {
|
||||||
|
filtered = filtered.filter((item) => item.pending_evaluations > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedCircle.value.id !== "_all") {
|
||||||
|
filtered = filtered.filter(
|
||||||
|
(item) => item.circle_id === String(selectedCircle.value.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedStatus,
|
||||||
|
statusOptions,
|
||||||
|
selectedCircle,
|
||||||
|
circleOptions,
|
||||||
|
filteredAssignments,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
@ -8,32 +8,32 @@ export function useRouteLookups() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function inCockpit() {
|
function inCockpit() {
|
||||||
const regex = new RegExp("/course/[^/]+/cockpit");
|
const regex = new RegExp("/course/[^/]+/cockpit($|/)");
|
||||||
return regex.test(route.path);
|
return regex.test(route.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
function inLearningPath() {
|
function inLearningPath() {
|
||||||
const regex = new RegExp("/course/[^/]+/learn");
|
const regex = new RegExp("/course/[^/]+/learn($|/)");
|
||||||
return regex.test(route.path);
|
return regex.test(route.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
function inCompetenceProfile() {
|
function inCompetenceProfile() {
|
||||||
const regex = new RegExp("/course/[^/]+/competence");
|
const regex = new RegExp("/course/[^/]+/competence($|/)");
|
||||||
return regex.test(route.path);
|
return regex.test(route.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
function inLearningMentor() {
|
function inLearningMentor() {
|
||||||
const regex = new RegExp("/course/[^/]+/mentor");
|
const regex = new RegExp("/course/[^/]+/learning-mentor($|/)");
|
||||||
return regex.test(route.path);
|
return regex.test(route.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
function inMediaLibrary() {
|
function inMediaLibrary() {
|
||||||
const regex = new RegExp("/course/[^/]+/media");
|
const regex = new RegExp("/course/[^/]+/media($|/)");
|
||||||
return regex.test(route.path);
|
return regex.test(route.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
function inAppointments() {
|
function inAppointments() {
|
||||||
const regex = new RegExp("/(?:[^/]+/)?appointments");
|
const regex = new RegExp("/(?:[^/]+/)?appointments($|/)");
|
||||||
return regex.test(route.path);
|
return regex.test(route.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,7 @@ function createCourseUrl(courseSlug: string | undefined, specificSub: string): s
|
||||||
return "/";
|
return "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (["learn", "media", "competence", "cockpit", "mentor"].includes(specificSub)) {
|
return `/course/${courseSlug}/${specificSub}`;
|
||||||
return `/course/${courseSlug}/${specificSub}`;
|
|
||||||
}
|
|
||||||
return `/course/${courseSlug}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCompetenceNaviUrl(courseSlug: string | undefined): string {
|
export function getCompetenceNaviUrl(courseSlug: string | undefined): string {
|
||||||
|
|
@ -32,12 +29,12 @@ export function getLearningPathUrl(courseSlug: string | undefined): string {
|
||||||
return createCourseUrl(courseSlug, "learn");
|
return createCourseUrl(courseSlug, "learn");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCockpitUrl(courseSlug: string | undefined): string {
|
export function getLearningMentorUrl(courseSlug: string | undefined): string {
|
||||||
return createCourseUrl(courseSlug, "cockpit");
|
return createCourseUrl(courseSlug, "learning-mentor");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLearningMentorManagementUrl(courseSlug: string | undefined): string {
|
export function getCockpitUrl(courseSlug: string | undefined): string {
|
||||||
return createCourseUrl(courseSlug, "mentor");
|
return createCourseUrl(courseSlug, "cockpit");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAssignmentTypeTitle(assignmentType: AssignmentType): string {
|
export function getAssignmentTypeTitle(assignmentType: AssignmentType): string {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { TEST_TRAINER1_USER_ID } from "../../consts";
|
import {TEST_TRAINER1_USER_ID} from "../../consts";
|
||||||
import { login } from "../helpers";
|
import {EXPERT_COCKPIT_URL, login} from "../helpers";
|
||||||
|
|
||||||
describe("assignmentTrainer.cy.js", () => {
|
describe("assignmentTrainer.cy.js", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
@ -9,10 +9,8 @@ describe("assignmentTrainer.cy.js", () => {
|
||||||
|
|
||||||
describe("Casework", () => {
|
describe("Casework", () => {
|
||||||
it("can open cockpit assignment page and open user assignment", () => {
|
it("can open cockpit assignment page and open user assignment", () => {
|
||||||
cy.visit("/course/test-lehrgang/cockpit");
|
cy.visit(EXPERT_COCKPIT_URL);
|
||||||
cy.get(
|
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]').click();
|
||||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
|
||||||
).click();
|
|
||||||
|
|
||||||
cy.get('[data-cy="Student1"]').should("contain", "Ergebnisse abgegeben");
|
cy.get('[data-cy="Student1"]').should("contain", "Ergebnisse abgegeben");
|
||||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||||
|
|
@ -22,24 +20,16 @@ describe("assignmentTrainer.cy.js", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can start evaluation and store evaluation results", () => {
|
it("can start evaluation and store evaluation results", () => {
|
||||||
cy.visit("/course/test-lehrgang/cockpit");
|
cy.visit(EXPERT_COCKPIT_URL)
|
||||||
cy.get(
|
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]').click();
|
||||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
|
||||||
).click();
|
|
||||||
|
|
||||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||||
|
|
||||||
cy.get('[data-cy="title"]').should("contain", "Bewertung");
|
cy.get('[data-cy="title"]').should("contain", "Bewertung");
|
||||||
cy.get('[data-cy="evaluation-duedate"]').should("exist");
|
cy.get('[data-cy="evaluation-duedate"]').should("exist");
|
||||||
cy.get('[data-cy="instruction"]').should(
|
cy.get('[data-cy="instruction"]').should("contain", "Die Gesamtpunktzahl und die daraus resultierende Note wird auf Grund des hinterlegeten Beurteilungsinstrument berechnet.");
|
||||||
"contain",
|
|
||||||
"Die Gesamtpunktzahl und die daraus resultierende Note wird auf Grund des hinterlegeten Beurteilungsinstrument berechnet."
|
|
||||||
);
|
|
||||||
cy.get('[data-cy="start-evaluation"]').click();
|
cy.get('[data-cy="start-evaluation"]').click();
|
||||||
cy.get('[data-cy="evaluation-task"]').should(
|
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 1 / 5");
|
||||||
"contain",
|
|
||||||
"Beurteilungskriterium 1 / 5"
|
|
||||||
);
|
|
||||||
|
|
||||||
// without text input the button should be disabled
|
// without text input the button should be disabled
|
||||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||||
|
|
@ -51,10 +41,7 @@ describe("assignmentTrainer.cy.js", () => {
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
cy.get('[data-cy="next-step"]').click();
|
cy.get('[data-cy="next-step"]').click();
|
||||||
|
|
||||||
cy.get('[data-cy="evaluation-task"]').should(
|
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 2 / 5");
|
||||||
"contain",
|
|
||||||
"Beurteilungskriterium 2 / 5"
|
|
||||||
);
|
|
||||||
cy.get('[data-cy="subtask-2"]').click();
|
cy.get('[data-cy="subtask-2"]').click();
|
||||||
cy.get('[data-cy="reason-text"]').type("Nicht so gut");
|
cy.get('[data-cy="reason-text"]').type("Nicht so gut");
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
|
|
@ -84,36 +71,28 @@ describe("assignmentTrainer.cy.js", () => {
|
||||||
|
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
// load AssignmentCompletion from DB and check
|
// load AssignmentCompletion from DB and check
|
||||||
cy.loadAssignmentCompletion(
|
cy.loadAssignmentCompletion("evaluation_user_id", TEST_TRAINER1_USER_ID).then((ac) => {
|
||||||
"evaluation_user_id",
|
|
||||||
TEST_TRAINER1_USER_ID
|
|
||||||
).then((ac) => {
|
|
||||||
expect(ac.completion_status).to.equal("EVALUATION_IN_PROGRESS");
|
expect(ac.completion_status).to.equal("EVALUATION_IN_PROGRESS");
|
||||||
expect(JSON.stringify(ac.completion_data)).to.include("Nicht so gut");
|
expect(JSON.stringify(ac.completion_data)).to.include("Nicht so gut");
|
||||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||||
expert_data: { points: 2, text: "Nicht so gut" },
|
expert_data: {points: 2, text: "Nicht so gut"},
|
||||||
});
|
});
|
||||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||||
expert_data: { points: 4, text: "Gut gemacht!" },
|
expert_data: {points: 4, text: "Gut gemacht!"},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can make complete evaluation", () => {
|
it("can make complete evaluation", () => {
|
||||||
cy.visit("/course/test-lehrgang/cockpit");
|
cy.visit(EXPERT_COCKPIT_URL)
|
||||||
cy.get(
|
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]').click();
|
||||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
|
||||||
).click();
|
|
||||||
|
|
||||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||||
|
|
||||||
cy.get('[data-cy="start-evaluation"]').click();
|
cy.get('[data-cy="start-evaluation"]').click();
|
||||||
|
|
||||||
// step 1
|
// step 1
|
||||||
cy.get('[data-cy="evaluation-task"]').should(
|
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 1 / 5");
|
||||||
"contain",
|
|
||||||
"Beurteilungskriterium 1 / 5"
|
|
||||||
);
|
|
||||||
cy.get('[data-cy="subtask-6"]').click();
|
cy.get('[data-cy="subtask-6"]').click();
|
||||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 1");
|
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 1");
|
||||||
// wait for debounce
|
// wait for debounce
|
||||||
|
|
@ -121,40 +100,28 @@ describe("assignmentTrainer.cy.js", () => {
|
||||||
cy.get('[data-cy="next-step"]').click();
|
cy.get('[data-cy="next-step"]').click();
|
||||||
|
|
||||||
// step 2
|
// step 2
|
||||||
cy.get('[data-cy="evaluation-task"]').should(
|
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 2 / 5");
|
||||||
"contain",
|
|
||||||
"Beurteilungskriterium 2 / 5"
|
|
||||||
);
|
|
||||||
cy.get('[data-cy="subtask-4"]').click();
|
cy.get('[data-cy="subtask-4"]').click();
|
||||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 2");
|
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 2");
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
cy.get('[data-cy="next-step"]').click();
|
cy.get('[data-cy="next-step"]').click();
|
||||||
|
|
||||||
// step 3
|
// step 3
|
||||||
cy.get('[data-cy="evaluation-task"]').should(
|
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 3 / 5");
|
||||||
"contain",
|
|
||||||
"Beurteilungskriterium 3 / 5"
|
|
||||||
);
|
|
||||||
cy.get('[data-cy="subtask-2"]').click();
|
cy.get('[data-cy="subtask-2"]').click();
|
||||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 3");
|
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 3");
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
cy.get('[data-cy="next-step"]').click();
|
cy.get('[data-cy="next-step"]').click();
|
||||||
|
|
||||||
// step 4
|
// step 4
|
||||||
cy.get('[data-cy="evaluation-task"]').should(
|
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 4 / 5");
|
||||||
"contain",
|
|
||||||
"Beurteilungskriterium 4 / 5"
|
|
||||||
);
|
|
||||||
cy.get('[data-cy="subtask-3"]').click();
|
cy.get('[data-cy="subtask-3"]').click();
|
||||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 4");
|
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 4");
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
cy.get('[data-cy="next-step"]').click();
|
cy.get('[data-cy="next-step"]').click();
|
||||||
|
|
||||||
// step 5
|
// step 5
|
||||||
cy.get('[data-cy="evaluation-task"]').should(
|
cy.get('[data-cy="evaluation-task"]').should("contain", "Beurteilungskriterium 5 / 5");
|
||||||
"contain",
|
|
||||||
"Beurteilungskriterium 5 / 5"
|
|
||||||
);
|
|
||||||
cy.get('[data-cy="subtask-2"]').click();
|
cy.get('[data-cy="subtask-2"]').click();
|
||||||
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 5");
|
cy.get('[data-cy="reason-text"]').type("Begründung Schritt 5");
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
|
|
@ -166,17 +133,12 @@ describe("assignmentTrainer.cy.js", () => {
|
||||||
cy.get('[data-cy="total-points"]').should("contain", "24");
|
cy.get('[data-cy="total-points"]').should("contain", "24");
|
||||||
cy.get('[data-cy="submit-evaluation"]').click();
|
cy.get('[data-cy="submit-evaluation"]').click();
|
||||||
|
|
||||||
cy.get('[data-cy="result-section"]').should(
|
cy.get('[data-cy="result-section"]').should("contain", "Deine Bewertung für Test Student1 wurde freigegeben");
|
||||||
"contain",
|
|
||||||
"Deine Bewertung für Test Student1 wurde freigegeben"
|
|
||||||
);
|
|
||||||
|
|
||||||
// going back to cockpit should show points for student
|
// going back to cockpit should show points for student
|
||||||
cy.visit("/course/test-lehrgang/cockpit");
|
cy.visit(EXPERT_COCKPIT_URL);
|
||||||
cy.reload();
|
cy.reload();
|
||||||
cy.get(
|
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]').click();
|
||||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"]'
|
|
||||||
).click();
|
|
||||||
|
|
||||||
cy.get('[data-cy="Student1"]').should("contain", "Bewertung freigegeben");
|
cy.get('[data-cy="Student1"]').should("contain", "Bewertung freigegeben");
|
||||||
cy.get('[data-cy="Student1"]').should("contain", "17 von 24 Punkte");
|
cy.get('[data-cy="Student1"]').should("contain", "17 von 24 Punkte");
|
||||||
|
|
@ -186,10 +148,7 @@ describe("assignmentTrainer.cy.js", () => {
|
||||||
cy.url().should("include", "step=6");
|
cy.url().should("include", "step=6");
|
||||||
|
|
||||||
// load AssignmentCompletion from DB and check
|
// load AssignmentCompletion from DB and check
|
||||||
cy.loadAssignmentCompletion(
|
cy.loadAssignmentCompletion("evaluation_user_id", TEST_TRAINER1_USER_ID).then((ac) => {
|
||||||
"evaluation_user_id",
|
|
||||||
TEST_TRAINER1_USER_ID
|
|
||||||
).then((ac) => {
|
|
||||||
expect(ac.completion_status).to.equal("EVALUATION_SUBMITTED");
|
expect(ac.completion_status).to.equal("EVALUATION_SUBMITTED");
|
||||||
expect(ac.evaluation_points).to.equal(17);
|
expect(ac.evaluation_points).to.equal(17);
|
||||||
expect(ac.evaluation_max_points).to.equal(24);
|
expect(ac.evaluation_max_points).to.equal(24);
|
||||||
|
|
@ -207,21 +166,16 @@ describe("assignmentTrainer.cy.js", () => {
|
||||||
//Todo: Move tests to Lernbegleitung once it is implemented
|
//Todo: Move tests to Lernbegleitung once it is implemented
|
||||||
describe("Praxis Assignment", () => {
|
describe("Praxis Assignment", () => {
|
||||||
it("can start evaluation and store evaluation results", () => {
|
it("can start evaluation and store evaluation results", () => {
|
||||||
cy.visit("/course/test-lehrgang/cockpit");
|
cy.visit(EXPERT_COCKPIT_URL);
|
||||||
cy.get('[data-cy="dropdown-select"]').click();
|
cy.get('[data-cy="dropdown-select"]').click();
|
||||||
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
||||||
cy.get(
|
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]').click();
|
||||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]'
|
|
||||||
).click();
|
|
||||||
|
|
||||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||||
|
|
||||||
cy.get('[data-cy="title"]').should("contain", "Feedback");
|
cy.get('[data-cy="title"]').should("contain", "Feedback");
|
||||||
cy.get('[data-cy="evaluation-duedate]"').should("not.exist");
|
cy.get('[data-cy="evaluation-duedate]"').should("not.exist");
|
||||||
cy.get('[data-cy="instruction"]').should(
|
cy.get('[data-cy="instruction"]').should("contain", "Bitte unterstütze Test Student1 und gib Feedback zum Auftrag.");
|
||||||
"contain",
|
|
||||||
"Bitte unterstütze Test Student1 und gib Feedback zum Auftrag."
|
|
||||||
);
|
|
||||||
cy.get('[data-cy="start-evaluation"]').click();
|
cy.get('[data-cy="start-evaluation"]').click();
|
||||||
cy.get('[data-cy="evaluation-task"]').should("contain", "Feedback 1 / 5");
|
cy.get('[data-cy="evaluation-task"]').should("contain", "Feedback 1 / 5");
|
||||||
|
|
||||||
|
|
@ -239,29 +193,24 @@ describe("assignmentTrainer.cy.js", () => {
|
||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
|
|
||||||
// load AssignmentCompletion from DB and check
|
// load AssignmentCompletion from DB and check
|
||||||
cy.loadAssignmentCompletion(
|
cy.loadAssignmentCompletion("evaluation_user_id", TEST_TRAINER1_USER_ID).then((ac) => {
|
||||||
"evaluation_user_id",
|
|
||||||
TEST_TRAINER1_USER_ID
|
|
||||||
).then((ac) => {
|
|
||||||
console.log(ac.completion_status);
|
console.log(ac.completion_status);
|
||||||
expect(ac.completion_status).to.equal("EVALUATION_IN_PROGRESS");
|
expect(ac.completion_status).to.equal("EVALUATION_IN_PROGRESS");
|
||||||
expect(JSON.stringify(ac.completion_data)).to.include("Nicht so gut");
|
expect(JSON.stringify(ac.completion_data)).to.include("Nicht so gut");
|
||||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||||
expert_data: { points: 0, text: "Nicht so gut" },
|
expert_data: {points: 0, text: "Nicht so gut"},
|
||||||
});
|
});
|
||||||
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
expect(Cypress._.values(ac.completion_data)).to.deep.include({
|
||||||
expert_data: { points: 0, text: "Gut gemacht!" },
|
expert_data: {points: 0, text: "Gut gemacht!"},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can make complete evaluation", () => {
|
it("can make complete evaluation", () => {
|
||||||
cy.visit("/course/test-lehrgang/cockpit");
|
cy.visit(EXPERT_COCKPIT_URL);
|
||||||
cy.get('[data-cy="dropdown-select"]').click();
|
cy.get('[data-cy="dropdown-select"]').click();
|
||||||
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
||||||
cy.get(
|
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]').click();
|
||||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]'
|
|
||||||
).click();
|
|
||||||
|
|
||||||
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
cy.get('[data-cy="Student1"]').find('[data-cy="show-results"]').click();
|
||||||
|
|
||||||
|
|
@ -303,19 +252,14 @@ describe("assignmentTrainer.cy.js", () => {
|
||||||
cy.get('[data-cy="total-points"]').should("not.exist");
|
cy.get('[data-cy="total-points"]').should("not.exist");
|
||||||
cy.get('[data-cy="submit-evaluation"]').click();
|
cy.get('[data-cy="submit-evaluation"]').click();
|
||||||
|
|
||||||
cy.get('[data-cy="result-section"]').should(
|
cy.get('[data-cy="result-section"]').should("contain", "Dein Feedback für Test Student1 wurde freigegeben");
|
||||||
"contain",
|
|
||||||
"Dein Feedback für Test Student1 wurde freigegeben"
|
|
||||||
);
|
|
||||||
|
|
||||||
// going back to cockpit should show points for student
|
// going back to cockpit should show points for student
|
||||||
cy.visit("/course/test-lehrgang/cockpit");
|
cy.visit(EXPERT_COCKPIT_URL);
|
||||||
cy.reload();
|
cy.reload();
|
||||||
cy.get('[data-cy="dropdown-select"]').click();
|
cy.get('[data-cy="dropdown-select"]').click();
|
||||||
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
||||||
cy.get(
|
cy.get('[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]').click();
|
||||||
'[data-cy="show-details-btn-test-lehrgang-lp-circle-reisen-lc-mein-kundenstamm"]'
|
|
||||||
).click();
|
|
||||||
|
|
||||||
cy.get('[data-cy="Student1"]').should("contain", "Feedback freigegeben");
|
cy.get('[data-cy="Student1"]').should("contain", "Feedback freigegeben");
|
||||||
cy.get('[data-cy="Student1"]').should("not.contain", "Punkte");
|
cy.get('[data-cy="Student1"]').should("not.contain", "Punkte");
|
||||||
|
|
@ -325,10 +269,7 @@ describe("assignmentTrainer.cy.js", () => {
|
||||||
cy.url().should("include", "step=6");
|
cy.url().should("include", "step=6");
|
||||||
|
|
||||||
// load AssignmentCompletion from DB and check
|
// load AssignmentCompletion from DB and check
|
||||||
cy.loadAssignmentCompletion(
|
cy.loadAssignmentCompletion("evaluation_user_id", TEST_TRAINER1_USER_ID).then((ac) => {
|
||||||
"evaluation_user_id",
|
|
||||||
TEST_TRAINER1_USER_ID
|
|
||||||
).then((ac) => {
|
|
||||||
expect(ac.completion_status).to.equal("EVALUATION_SUBMITTED");
|
expect(ac.completion_status).to.equal("EVALUATION_SUBMITTED");
|
||||||
expect(ac.evaluation_max_points).to.equal(0);
|
expect(ac.evaluation_max_points).to.equal(0);
|
||||||
const completionString = JSON.stringify(ac.completion_data);
|
const completionString = JSON.stringify(ac.completion_data);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { login } from "../helpers";
|
import {EXPERT_COCKPIT_URL, login} from "../helpers";
|
||||||
|
|
||||||
describe("feedbackTrainer.cy.js", () => {
|
describe("feedbackTrainer.cy.js", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
@ -8,7 +8,7 @@ describe("feedbackTrainer.cy.js", () => {
|
||||||
it("can open feedback results page with empty results", () => {
|
it("can open feedback results page with empty results", () => {
|
||||||
cy.manageCommand("cypress_reset");
|
cy.manageCommand("cypress_reset");
|
||||||
login("test-trainer1@example.com", "test");
|
login("test-trainer1@example.com", "test");
|
||||||
cy.visit("/course/test-lehrgang/cockpit");
|
cy.visit(EXPERT_COCKPIT_URL);
|
||||||
cy.get(
|
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();
|
).click();
|
||||||
|
|
@ -20,7 +20,7 @@ describe("feedbackTrainer.cy.js", () => {
|
||||||
it("can open feedback results page with results", () => {
|
it("can open feedback results page with results", () => {
|
||||||
cy.manageCommand("cypress_reset --create-feedback-responses");
|
cy.manageCommand("cypress_reset --create-feedback-responses");
|
||||||
login("test-trainer1@example.com", "test");
|
login("test-trainer1@example.com", "test");
|
||||||
cy.visit("/course/test-lehrgang/cockpit");
|
cy.visit(EXPERT_COCKPIT_URL);
|
||||||
cy.get(
|
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();
|
).click();
|
||||||
|
|
@ -138,7 +138,7 @@ describe("feedbackTrainer.cy.js", () => {
|
||||||
it("can open feedback results page with results", () => {
|
it("can open feedback results page with results", () => {
|
||||||
cy.manageCommand("cypress_reset --create-feedback-responses");
|
cy.manageCommand("cypress_reset --create-feedback-responses");
|
||||||
login("test-trainer1@example.com", "test");
|
login("test-trainer1@example.com", "test");
|
||||||
cy.visit("/course/test-lehrgang/cockpit");
|
cy.visit(EXPERT_COCKPIT_URL);
|
||||||
cy.get('[data-cy="dropdown-select"]').click();
|
cy.get('[data-cy="dropdown-select"]').click();
|
||||||
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
||||||
cy.get(
|
cy.get(
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
|
export const EXPERT_COCKPIT_URL = "course/test-lehrgang/cockpit"
|
||||||
|
|
||||||
export const login = (username, password) => {
|
export const login = (username, password) => {
|
||||||
cy.request({
|
cy.request({
|
||||||
method: "POST", url: "/api/core/login/", body: {username, password},
|
method: "POST", url: "/api/core/login/", body: {username, password},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const logout = () => {
|
export const logout = () => {
|
||||||
cy.request({
|
cy.request({
|
||||||
method: "POST", url: "/api/core/logout/",
|
method: "POST", url: "/api/core/logout/",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BASE_URL = "/course/test-lehrgang";
|
export const BASE_URL = "/course/test-lehrgang";
|
||||||
|
|
@ -15,9 +17,9 @@ export const EXPERT_LOGIN = ["test-trainer1@example.com", "test"];
|
||||||
export const PARTICIPANT_LOGIN = ["test-student1@example.com", "test"];
|
export const PARTICIPANT_LOGIN = ["test-student1@example.com", "test"];
|
||||||
|
|
||||||
export const visitCoursePage = (subPath) => {
|
export const visitCoursePage = (subPath) => {
|
||||||
cy.visit(`${BASE_URL}/${subPath}`);
|
cy.visit(`${BASE_URL}/${subPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkNavigationLink = (dataCy, expectedLink) => {
|
export const checkNavigationLink = (dataCy, expectedLink) => {
|
||||||
cy.get(`[data-cy="${dataCy}"]`).should('have.attr', 'href').and('eq', `${BASE_URL}/${expectedLink}`);
|
cy.get(`[data-cy="${dataCy}"]`).should('have.attr', 'href').and('eq', `${BASE_URL}/${expectedLink}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
Since the learning mentor behaves differently depending on the user's role, the learning mentor is tested in the
|
||||||
|
following roles:
|
||||||
|
|
||||||
|
- [x] As a learning mentor (no other roles). -> `justMentor.cy.js`
|
||||||
|
- [x] As a course session user (member, no other roles). -> `justMember.cy.js`
|
||||||
|
- [x] As a course session user (member) and a learning mentor. `mentorAndMember.cy.js`
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
export const MENTOR_OVERVIEW_URL = "/course/versicherungsvermittler-in/learning-mentor";
|
||||||
|
export const MENTOR_MENTEES_URL = "/course/versicherungsvermittler-in/learning-mentor/participants";
|
||||||
|
|
||||||
|
export const MENTOR_DASHBOARD_LINK = "[data-cy=lm-dashboard-link]";
|
||||||
|
export const MEMBER_DASHBOARD_LINK = "[data-cy=progress-dashboard-continue-course-link]";
|
||||||
|
export const MENTOR_MAIN_NAVIGATION = "[data-cy=lm-main-navigation]";
|
||||||
|
export const MENTOR_OVERVIEW_NAVIGATION_LINK = "[data-cy=lm-overview-navigation-link]";
|
||||||
|
export const MENTOR_MENTEES_NAVIGATION_LINK = "[data-cy=lm-mentees-navigation-link]";
|
||||||
|
|
||||||
|
// /participants
|
||||||
|
export const MENTOR_MY_MENTEES = "[data-cy=lm-my-mentees]";
|
||||||
|
export const MENTOR_MY_MENTORS = "[data-cy=lm-my-mentors]";
|
||||||
|
|
||||||
|
export const MENTOR_MENTEE_LIST_ITEM = "[data-cy=lm-my-mentee-list-item]";
|
||||||
|
export const MENTOR_MENTEE_REMOVE = "[data-cy=lm-my-mentee-remove]";
|
||||||
|
export const MENTOR_MENTEE_PROFILE = "[data-cy=lm-my-mentee-profile]";
|
||||||
|
|
||||||
|
export const MENTEE_MENTOR_LIST_ITEM = "[data-cy=lm-my-mentor-list-item]";
|
||||||
|
export const MENTEE_MENTOR_REMOVE = "[data-cy=lm-my-mentor-remove]";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import {login} from "../../helpers";
|
||||||
|
import {
|
||||||
|
MEMBER_DASHBOARD_LINK,
|
||||||
|
MENTEE_MENTOR_LIST_ITEM,
|
||||||
|
MENTEE_MENTOR_REMOVE,
|
||||||
|
MENTOR_MENTEES_NAVIGATION_LINK,
|
||||||
|
MENTOR_MENTEES_URL,
|
||||||
|
MENTOR_MY_MENTEES,
|
||||||
|
MENTOR_MY_MENTORS,
|
||||||
|
MENTOR_OVERVIEW_NAVIGATION_LINK,
|
||||||
|
MENTOR_OVERVIEW_URL
|
||||||
|
} from "../constants";
|
||||||
|
|
||||||
|
describe("memberOnly.cy.js", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.manageCommand("cypress_reset --create-learning-mentor");
|
||||||
|
login("student-vv@eiger-versicherungen.ch", "test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the correct dashboard", () => {
|
||||||
|
cy.visit("/");
|
||||||
|
cy.get(MEMBER_DASHBOARD_LINK).should("exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows NO mentees navigation link", () => {
|
||||||
|
cy.visit(MENTOR_OVERVIEW_URL);
|
||||||
|
cy.get(MENTOR_MENTEES_NAVIGATION_LINK).should("not.exist");
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shows NO overview navigation link", () => {
|
||||||
|
cy.visit(MENTOR_OVERVIEW_URL);
|
||||||
|
cy.get(MENTOR_OVERVIEW_NAVIGATION_LINK).should("not.exist");
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shows NO mentees", () => {
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.get(MENTOR_MY_MENTEES).should("not.exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows my mentors", () => {
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.get(MENTOR_MY_MENTORS).should("exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can remove a mentor", () => {
|
||||||
|
// given
|
||||||
|
const mentor = "Micheala Weber-Mentor";
|
||||||
|
|
||||||
|
// when
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.contains(MENTEE_MENTOR_LIST_ITEM, mentor)
|
||||||
|
.find(MENTEE_MENTOR_REMOVE)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// then
|
||||||
|
cy.contains(MENTOR_MY_MENTORS, mentor).should("not.exist");
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
import {login} from "../../helpers";
|
||||||
|
import {
|
||||||
|
MEMBER_DASHBOARD_LINK,
|
||||||
|
MENTEE_MENTOR_LIST_ITEM,
|
||||||
|
MENTEE_MENTOR_REMOVE,
|
||||||
|
MENTOR_MAIN_NAVIGATION,
|
||||||
|
MENTOR_MENTEE_LIST_ITEM,
|
||||||
|
MENTOR_MENTEE_PROFILE,
|
||||||
|
MENTOR_MENTEE_REMOVE,
|
||||||
|
MENTOR_MENTEES_NAVIGATION_LINK,
|
||||||
|
MENTOR_MENTEES_URL,
|
||||||
|
MENTOR_MY_MENTEES,
|
||||||
|
MENTOR_MY_MENTORS,
|
||||||
|
MENTOR_OVERVIEW_NAVIGATION_LINK,
|
||||||
|
MENTOR_OVERVIEW_URL
|
||||||
|
} from "../constants";
|
||||||
|
|
||||||
|
describe("mentorAndMember.cy.js", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.manageCommand("cypress_reset --create-learning-mentor");
|
||||||
|
login("test-student-and-mentor2@example.com", "test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the correct dashboard", () => {
|
||||||
|
cy.visit("/");
|
||||||
|
cy.get(MEMBER_DASHBOARD_LINK).should("exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the learning mentor navigation", () => {
|
||||||
|
cy.visit(MENTOR_OVERVIEW_URL);
|
||||||
|
cy.get(MENTOR_MAIN_NAVIGATION).should("exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the mentees navigation link", () => {
|
||||||
|
cy.visit(MENTOR_OVERVIEW_URL);
|
||||||
|
cy.get(MENTOR_MENTEES_NAVIGATION_LINK).click();
|
||||||
|
cy.url().should("include", MENTOR_MENTEES_URL);
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shows the overview navigation link", () => {
|
||||||
|
cy.visit(MENTOR_OVERVIEW_URL);
|
||||||
|
cy.get(MENTOR_OVERVIEW_NAVIGATION_LINK).click();
|
||||||
|
cy.url().should("include", MENTOR_OVERVIEW_URL);
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shows my mentees", () => {
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.get(MENTOR_MY_MENTEES).should("exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows my mentors", () => {
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.get(MENTOR_MY_MENTORS).should("exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the correct mentees", () => {
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.get(MENTOR_MY_MENTEES).should("contain", "Viktor Vollgas");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the profile of a mentee", () => {
|
||||||
|
// given
|
||||||
|
const mentee = "Viktor Vollgas";
|
||||||
|
|
||||||
|
// when
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee)
|
||||||
|
.find(MENTOR_MENTEE_PROFILE)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// then
|
||||||
|
const expectedMenteeProfileUrl = "/course/versicherungsvermittler-in/profile/5ff59857-8de5-415e-a387-4449f9a0337a"
|
||||||
|
cy.url().should("include", expectedMenteeProfileUrl);
|
||||||
|
cy.contains(mentee).should("exist");
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can remove a mentee", () => {
|
||||||
|
// given
|
||||||
|
const mentee = "Viktor Vollgas";
|
||||||
|
|
||||||
|
// when
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee)
|
||||||
|
.find(MENTOR_MENTEE_REMOVE)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// then
|
||||||
|
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee).should("not.exist");
|
||||||
|
cy.contains("Aktuell begleitest du niemanden als Lernbegleitung").should("exist");
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shows the correct mentors", () => {
|
||||||
|
const mentor = "Micheala Weber-Mentor";
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.get(MENTOR_MY_MENTORS).should("contain", mentor);
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can remove a mentor", () => {
|
||||||
|
// given
|
||||||
|
const mentor = "Micheala Weber-Mentor";
|
||||||
|
|
||||||
|
// when
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.contains(MENTEE_MENTOR_LIST_ITEM, mentor)
|
||||||
|
.find(MENTEE_MENTOR_REMOVE)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// then
|
||||||
|
cy.contains(MENTOR_MY_MENTORS, mentor).should("not.exist");
|
||||||
|
cy.contains("Aktuell hast du noch keine Person als Lernbegleitung eingeladen").should("exist");
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import {login} from "../../helpers";
|
||||||
|
import {
|
||||||
|
MENTOR_DASHBOARD_LINK,
|
||||||
|
MENTOR_MAIN_NAVIGATION,
|
||||||
|
MENTOR_MENTEE_LIST_ITEM,
|
||||||
|
MENTOR_MENTEE_PROFILE,
|
||||||
|
MENTOR_MENTEE_REMOVE,
|
||||||
|
MENTOR_MENTEES_NAVIGATION_LINK,
|
||||||
|
MENTOR_MENTEES_URL,
|
||||||
|
MENTOR_MY_MENTEES,
|
||||||
|
MENTOR_MY_MENTORS,
|
||||||
|
MENTOR_OVERVIEW_NAVIGATION_LINK,
|
||||||
|
MENTOR_OVERVIEW_URL
|
||||||
|
} from "../constants";
|
||||||
|
|
||||||
|
describe("mentorOnly.cy.js", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.manageCommand("cypress_reset --create-learning-mentor");
|
||||||
|
login("test-mentor1@example.com", "test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the correct dashboard", () => {
|
||||||
|
cy.visit("/");
|
||||||
|
cy.get(MENTOR_DASHBOARD_LINK).click();
|
||||||
|
cy.url().should("include", MENTOR_OVERVIEW_URL);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the learning mentor navigation", () => {
|
||||||
|
cy.visit(MENTOR_OVERVIEW_URL);
|
||||||
|
cy.get(MENTOR_MAIN_NAVIGATION).should("exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the mentees navigation link", () => {
|
||||||
|
cy.visit(MENTOR_OVERVIEW_URL);
|
||||||
|
cy.get(MENTOR_MENTEES_NAVIGATION_LINK).click();
|
||||||
|
cy.url().should("include", MENTOR_MENTEES_URL);
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shows the overview navigation link", () => {
|
||||||
|
cy.visit(MENTOR_OVERVIEW_URL);
|
||||||
|
cy.get(MENTOR_OVERVIEW_NAVIGATION_LINK).click();
|
||||||
|
cy.url().should("include", MENTOR_OVERVIEW_URL);
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shows my mentees", () => {
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.get(MENTOR_MY_MENTEES).should("exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows no mentors", () => {
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.get(MENTOR_MY_MENTORS).should("not.exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the correct mentees", () => {
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.get(MENTOR_MY_MENTEES).should("contain", "Robert Student-plus-Mentor");
|
||||||
|
cy.get(MENTOR_MY_MENTEES).should("contain", "Viktor Vollgas");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the profile of a mentee", () => {
|
||||||
|
// given
|
||||||
|
const mentee = "Viktor Vollgas";
|
||||||
|
|
||||||
|
// when
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee)
|
||||||
|
.find(MENTOR_MENTEE_PROFILE)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// then
|
||||||
|
const expectedMenteeProfileUrl = "/course/versicherungsvermittler-in/profile/5ff59857-8de5-415e-a387-4449f9a0337a"
|
||||||
|
cy.url().should("include", expectedMenteeProfileUrl);
|
||||||
|
cy.contains(mentee).should("exist");
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can remove a mentee", () => {
|
||||||
|
// given
|
||||||
|
const mentee = "Viktor Vollgas";
|
||||||
|
|
||||||
|
// when
|
||||||
|
cy.visit(MENTOR_MENTEES_URL);
|
||||||
|
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee)
|
||||||
|
.find(MENTOR_MENTEE_REMOVE)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// then
|
||||||
|
cy.contains(MENTOR_MENTEE_LIST_ITEM, mentee).should("not.exist");
|
||||||
|
cy.contains(MENTOR_MENTEE_LIST_ITEM, "Robert Student-plus-Mentor").should("exist")
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { login } from "./helpers";
|
import {EXPERT_COCKPIT_URL, login} from "./helpers";
|
||||||
|
|
||||||
describe("settings.cy.js", () => {
|
describe("settings.cy.js", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
@ -14,7 +14,7 @@ describe("settings.cy.js", () => {
|
||||||
|
|
||||||
it("trainer can see circle documents", () => {
|
it("trainer can see circle documents", () => {
|
||||||
login("test-trainer1@example.com", "test");
|
login("test-trainer1@example.com", "test");
|
||||||
cy.visit("/course/test-lehrgang/cockpit");
|
cy.visit(EXPERT_COCKPIT_URL);
|
||||||
cy.get('[data-cy="circle-documents"]').should("exist");
|
cy.get('[data-cy="circle-documents"]').should("exist");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -33,7 +33,7 @@ describe("settings.cy.js", () => {
|
||||||
|
|
||||||
it("trainer cannot see circle documents", () => {
|
it("trainer cannot see circle documents", () => {
|
||||||
login("test-trainer1@example.com", "test");
|
login("test-trainer1@example.com", "test");
|
||||||
cy.visit("/course/test-lehrgang/cockpit");
|
cy.visit(EXPERT_COCKPIT_URL);
|
||||||
cy.get('[data-cy="circle-documents"]').should("not.exist");
|
cy.get('[data-cy="circle-documents"]').should("not.exist");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,7 @@ from django_ratelimit.exceptions import Ratelimited
|
||||||
from graphene_django.views import GraphQLView
|
from graphene_django.views import GraphQLView
|
||||||
|
|
||||||
from vbv_lernwelt.api.directory import list_entities
|
from vbv_lernwelt.api.directory import list_entities
|
||||||
from vbv_lernwelt.api.user import (
|
from vbv_lernwelt.api.user import get_profile, me_user_view, post_avatar
|
||||||
get_cockpit_type,
|
|
||||||
get_profile,
|
|
||||||
me_user_view,
|
|
||||||
post_avatar,
|
|
||||||
)
|
|
||||||
from vbv_lernwelt.assignment.views import request_assignment_completion_status
|
from vbv_lernwelt.assignment.views import request_assignment_completion_status
|
||||||
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
|
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
|
||||||
from vbv_lernwelt.core.schema import schema
|
from vbv_lernwelt.core.schema import schema
|
||||||
|
|
@ -123,9 +118,6 @@ urlpatterns = [
|
||||||
|
|
||||||
# course
|
# course
|
||||||
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
||||||
# path(r"api/course/sessions/<signed_int:course_session_id>/users/",
|
|
||||||
# get_course_session_users,
|
|
||||||
# name="get_course_session_users"),
|
|
||||||
path(r"api/course/page/<slug_or_id>/", course_page_api_view,
|
path(r"api/course/page/<slug_or_id>/", course_page_api_view,
|
||||||
name="course_page_api_view"),
|
name="course_page_api_view"),
|
||||||
path(r"api/course/completion/mark/", mark_course_completion_view,
|
path(r"api/course/completion/mark/", mark_course_completion_view,
|
||||||
|
|
@ -136,9 +128,6 @@ urlpatterns = [
|
||||||
path(r"api/course/completion/<signed_int:course_session_id>/<uuid:user_id>/",
|
path(r"api/course/completion/<signed_int:course_session_id>/<uuid:user_id>/",
|
||||||
request_course_completion_for_user,
|
request_course_completion_for_user,
|
||||||
name="request_course_completion_for_user"),
|
name="request_course_completion_for_user"),
|
||||||
path(r"api/course/<signed_int:course_id>/cockpit/",
|
|
||||||
get_cockpit_type,
|
|
||||||
name="get_cockpit_type"),
|
|
||||||
|
|
||||||
path("api/mentor/<signed_int:course_session_id>/", include("vbv_lernwelt.learning_mentor.urls")),
|
path("api/mentor/<signed_int:course_session_id>/", include("vbv_lernwelt.learning_mentor.urls")),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
from django.urls import reverse
|
|
||||||
from rest_framework import status
|
|
||||||
from rest_framework.test import APITestCase
|
|
||||||
|
|
||||||
from vbv_lernwelt.course.creators.test_utils import (
|
|
||||||
create_course,
|
|
||||||
create_course_session,
|
|
||||||
create_user,
|
|
||||||
)
|
|
||||||
from vbv_lernwelt.course.models import CourseSessionUser
|
|
||||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
|
||||||
|
|
||||||
|
|
||||||
class MeUserViewTest(APITestCase):
|
|
||||||
def setUp(self) -> None:
|
|
||||||
self.course, _ = create_course("Test Course")
|
|
||||||
self.user = create_user("tester")
|
|
||||||
self.url = reverse("get_cockpit_type", kwargs={"course_id": self.course.id})
|
|
||||||
|
|
||||||
def test_no_cockpit(self) -> None:
|
|
||||||
# GIVEN
|
|
||||||
self.client.force_login(self.user)
|
|
||||||
|
|
||||||
# WHEN
|
|
||||||
response = self.client.get(self.url)
|
|
||||||
|
|
||||||
# THEN
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertEquals(response.data["type"], None)
|
|
||||||
|
|
||||||
def test_mentor_cockpit(self) -> None:
|
|
||||||
# GIVEN
|
|
||||||
self.client.force_login(self.user)
|
|
||||||
LearningMentor.objects.create(mentor=self.user, course=self.course)
|
|
||||||
|
|
||||||
# WHEN
|
|
||||||
response = self.client.get(self.url)
|
|
||||||
|
|
||||||
# THEN
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertEquals(response.data["type"], "mentor")
|
|
||||||
|
|
||||||
def test_trainer_cockpit(self) -> None:
|
|
||||||
# GIVEN
|
|
||||||
self.client.force_login(self.user)
|
|
||||||
course_session = create_course_session(course=self.course, title="Test Session")
|
|
||||||
|
|
||||||
CourseSessionUser.objects.create(
|
|
||||||
user=self.user,
|
|
||||||
course_session=course_session,
|
|
||||||
role=CourseSessionUser.Role.EXPERT,
|
|
||||||
)
|
|
||||||
|
|
||||||
# WHEN
|
|
||||||
response = self.client.get(self.url)
|
|
||||||
|
|
||||||
# THEN
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertEquals(response.data["type"], "expert")
|
|
||||||
|
|
||||||
def test_supervisor_cockpit(self):
|
|
||||||
# GIVEN
|
|
||||||
self.client.force_login(self.user)
|
|
||||||
course_session = create_course_session(course=self.course, title="Test Session")
|
|
||||||
|
|
||||||
csg = CourseSessionGroup.objects.create(
|
|
||||||
name="Test Group",
|
|
||||||
course=course_session.course,
|
|
||||||
)
|
|
||||||
|
|
||||||
csg.course_session.add(course_session)
|
|
||||||
csg.supervisor.add(self.user)
|
|
||||||
|
|
||||||
# WHEN
|
|
||||||
response = self.client.get(self.url)
|
|
||||||
|
|
||||||
# THEN
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertEquals(response.data["type"], "expert")
|
|
||||||
|
|
@ -5,10 +5,8 @@ from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from vbv_lernwelt.core.serializers import UserSerializer
|
from vbv_lernwelt.core.serializers import UserSerializer
|
||||||
from vbv_lernwelt.course.models import Course, CourseSessionUser
|
from vbv_lernwelt.course.models import CourseSessionUser
|
||||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
|
||||||
from vbv_lernwelt.iam.permissions import can_view_profile
|
from vbv_lernwelt.iam.permissions import can_view_profile
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
|
||||||
from vbv_lernwelt.media_files.models import UserImage
|
from vbv_lernwelt.media_files.models import UserImage
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -35,35 +33,6 @@ def me_user_view(request):
|
||||||
return Response(status=400)
|
return Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
@api_view(["GET"])
|
|
||||||
@permission_classes([IsAuthenticated])
|
|
||||||
def get_cockpit_type(request, course_id: int):
|
|
||||||
course = get_object_or_404(Course, id=course_id)
|
|
||||||
|
|
||||||
is_mentor = LearningMentor.objects.filter(
|
|
||||||
mentor=request.user, course=course
|
|
||||||
).exists()
|
|
||||||
|
|
||||||
is_expert = CourseSessionUser.objects.filter(
|
|
||||||
user=request.user,
|
|
||||||
course_session__course=course,
|
|
||||||
role=CourseSessionUser.Role.EXPERT,
|
|
||||||
).exists()
|
|
||||||
|
|
||||||
is_supervisor = CourseSessionGroup.objects.filter(
|
|
||||||
course_session__course=course, supervisor=request.user
|
|
||||||
).exists()
|
|
||||||
|
|
||||||
if is_mentor:
|
|
||||||
cockpit_type = "mentor"
|
|
||||||
elif is_expert or is_supervisor:
|
|
||||||
cockpit_type = "expert"
|
|
||||||
else:
|
|
||||||
cockpit_type = None
|
|
||||||
|
|
||||||
return Response({"type": cockpit_type})
|
|
||||||
|
|
||||||
|
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
def get_profile(request, course_session_id: int, user_id: str):
|
def get_profile(request, course_session_id: int, user_id: str):
|
||||||
|
|
|
||||||
|
|
@ -377,11 +377,8 @@ class AssignmentCompletion(models.Model):
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_assignment_evaluation_frontend_url(self):
|
def get_assignment_evaluation_frontend_url(self):
|
||||||
"""
|
"""Used by the expert to evaluate the assignment"""
|
||||||
Used by the expert to evaluate the assignment
|
return f"{self.course_session.course.get_course_url()}/assignment-evaluation/{self.assignment.id}/{self.assignment_user.id}"
|
||||||
Example: /course/überbetriebliche-kurse/cockpit/assignment/371/18
|
|
||||||
"""
|
|
||||||
return f"{self.course_session.course.get_cockpit_url()}/assignment/{self.assignment.id}/{self.assignment_user.id}"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def task_completion_data(self):
|
def task_completion_data(self):
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
|
||||||
file.save()
|
file.save()
|
||||||
|
|
||||||
file_id = str(file.id)
|
file_id = str(file.id)
|
||||||
file_url = file.url
|
|
||||||
|
|
||||||
completion_data_string = json.dumps(
|
completion_data_string = json.dumps(
|
||||||
{
|
{
|
||||||
|
|
@ -223,7 +222,7 @@ class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
|
||||||
self.course_session,
|
self.course_session,
|
||||||
)
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
f"/course/test-lehrgang/cockpit/assignment" in notification.target_url
|
f"/course/test-lehrgang/assignment-evaluation" in notification.target_url
|
||||||
)
|
)
|
||||||
|
|
||||||
# second submit will fail
|
# second submit will fail
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ TEST_STUDENT2_USER_ID = "19c40d94-15cc-4198-aaad-ef707c4b0900"
|
||||||
TEST_STUDENT3_USER_ID = "bcf94dba-53bc-474b-a22d-e4af39aa042b"
|
TEST_STUDENT3_USER_ID = "bcf94dba-53bc-474b-a22d-e4af39aa042b"
|
||||||
TEST_MENTOR1_USER_ID = "d1f5f5a9-5b0a-4e1a-9e1a-9e9b5b5e1b1b"
|
TEST_MENTOR1_USER_ID = "d1f5f5a9-5b0a-4e1a-9e1a-9e9b5b5e1b1b"
|
||||||
TEST_STUDENT1_VV_USER_ID = "5ff59857-8de5-415e-a387-4449f9a0337a"
|
TEST_STUDENT1_VV_USER_ID = "5ff59857-8de5-415e-a387-4449f9a0337a"
|
||||||
|
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID = "7e8ebf0b-e6e2-4022-88f4-6e663ba0a9db"
|
||||||
|
|
||||||
TEST_COURSE_SESSION_BERN_ID = -1
|
TEST_COURSE_SESSION_BERN_ID = -1
|
||||||
TEST_COURSE_SESSION_ZURICH_ID = -2
|
TEST_COURSE_SESSION_ZURICH_ID = -2
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ from vbv_lernwelt.core.constants import (
|
||||||
TEST_STUDENT1_USER_ID,
|
TEST_STUDENT1_USER_ID,
|
||||||
TEST_STUDENT1_VV_USER_ID,
|
TEST_STUDENT1_VV_USER_ID,
|
||||||
TEST_STUDENT2_USER_ID,
|
TEST_STUDENT2_USER_ID,
|
||||||
|
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||||
TEST_STUDENT3_USER_ID,
|
TEST_STUDENT3_USER_ID,
|
||||||
TEST_SUPERVISOR1_USER_ID,
|
TEST_SUPERVISOR1_USER_ID,
|
||||||
TEST_TRAINER1_USER_ID,
|
TEST_TRAINER1_USER_ID,
|
||||||
|
|
@ -372,6 +373,14 @@ def create_default_users(default_password="test", set_avatar=False):
|
||||||
language="de",
|
language="de",
|
||||||
avatar_image="uk1.patrizia.huggel.jpg",
|
avatar_image="uk1.patrizia.huggel.jpg",
|
||||||
)
|
)
|
||||||
|
_create_student_user(
|
||||||
|
id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||||
|
email="test-student-and-mentor2@example.com",
|
||||||
|
first_name="Robert",
|
||||||
|
last_name="Student-plus-Mentor",
|
||||||
|
password=default_password,
|
||||||
|
language="de",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _get_or_create_user(user_model, *args, **kwargs):
|
def _get_or_create_user(user_model, *args, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from vbv_lernwelt.core.constants import (
|
||||||
TEST_STUDENT1_USER_ID,
|
TEST_STUDENT1_USER_ID,
|
||||||
TEST_STUDENT1_VV_USER_ID,
|
TEST_STUDENT1_VV_USER_ID,
|
||||||
TEST_STUDENT2_USER_ID,
|
TEST_STUDENT2_USER_ID,
|
||||||
|
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||||
TEST_STUDENT3_USER_ID,
|
TEST_STUDENT3_USER_ID,
|
||||||
TEST_TRAINER1_USER_ID,
|
TEST_TRAINER1_USER_ID,
|
||||||
)
|
)
|
||||||
|
|
@ -343,31 +344,51 @@ def command(
|
||||||
attendance_course.save()
|
attendance_course.save()
|
||||||
|
|
||||||
if create_learning_mentor:
|
if create_learning_mentor:
|
||||||
|
cs_bern = CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID)
|
||||||
|
|
||||||
uk_mentor = LearningMentor.objects.create(
|
uk_mentor = LearningMentor.objects.create(
|
||||||
course=Course.objects.get(id=COURSE_TEST_ID),
|
|
||||||
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
||||||
|
course_session=cs_bern,
|
||||||
)
|
)
|
||||||
uk_mentor.participants.add(
|
uk_mentor.participants.add(
|
||||||
CourseSessionUser.objects.get(
|
CourseSessionUser.objects.get(
|
||||||
user__id=TEST_STUDENT1_USER_ID,
|
user__id=TEST_STUDENT1_USER_ID,
|
||||||
course_session=CourseSession.objects.get(
|
course_session=cs_bern,
|
||||||
id=TEST_COURSE_SESSION_BERN_ID
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
vv_course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
|
vv_course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
|
||||||
vv_course_session = CourseSession.objects.get(course=vv_course)
|
vv_course_session = CourseSession.objects.get(course=vv_course)
|
||||||
vv_mentor = LearningMentor.objects.create(
|
vv_mentor = LearningMentor.objects.create(
|
||||||
course=vv_course,
|
|
||||||
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
||||||
|
course_session=vv_course_session,
|
||||||
)
|
)
|
||||||
|
|
||||||
vv_mentor.participants.add(
|
vv_mentor.participants.add(
|
||||||
CourseSessionUser.objects.get(
|
CourseSessionUser.objects.get(
|
||||||
user__id=TEST_STUDENT1_VV_USER_ID, course_session=vv_course_session
|
user__id=TEST_STUDENT1_VV_USER_ID, course_session=vv_course_session
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
vv_mentor.participants.add(
|
||||||
|
CourseSessionUser.objects.get(
|
||||||
|
user__id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||||
|
course_session=vv_course_session,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
vv_student_and_mentor = LearningMentor.objects.create(
|
||||||
|
mentor=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID),
|
||||||
|
course_session=vv_course_session,
|
||||||
|
)
|
||||||
|
|
||||||
|
vv_student_and_mentor.participants.add(
|
||||||
|
CourseSessionUser.objects.get(
|
||||||
|
user__id=TEST_STUDENT1_VV_USER_ID,
|
||||||
|
course_session=vv_course_session,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
course = Course.objects.get(id=COURSE_TEST_ID)
|
course = Course.objects.get(id=COURSE_TEST_ID)
|
||||||
course.configuration.enable_circle_documents = enable_circle_documents
|
course.configuration.enable_circle_documents = enable_circle_documents
|
||||||
course.configuration.save()
|
course.configuration.save()
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ def add_mentor_to_course_session(
|
||||||
):
|
):
|
||||||
for mentor, mentee in mentor_mentee_pairs:
|
for mentor, mentee in mentor_mentee_pairs:
|
||||||
lm = LearningMentor.objects.create(
|
lm = LearningMentor.objects.create(
|
||||||
course=course_session.course,
|
course_session=course_session,
|
||||||
mentor=mentor,
|
mentor=mentor,
|
||||||
)
|
)
|
||||||
lm.participants.add(
|
lm.participants.add(
|
||||||
|
|
|
||||||
|
|
@ -101,9 +101,11 @@ def create_course_session(
|
||||||
|
|
||||||
|
|
||||||
def add_learning_mentor(
|
def add_learning_mentor(
|
||||||
course: Course, mentor: User, mentee: CourseSessionUser
|
course_session: CourseSession, mentor: User, mentee: CourseSessionUser
|
||||||
) -> LearningMentor:
|
) -> LearningMentor:
|
||||||
learning_mentor = LearningMentor.objects.create(course=course, mentor=mentor)
|
learning_mentor = LearningMentor.objects.create(
|
||||||
|
course_session=course_session, mentor=mentor
|
||||||
|
)
|
||||||
learning_mentor.participants.add(mentee)
|
learning_mentor.participants.add(mentee)
|
||||||
return learning_mentor
|
return learning_mentor
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,10 @@ from vbv_lernwelt.competence.create_vv_new_competence_profile import (
|
||||||
create_vv_new_competence_profile,
|
create_vv_new_competence_profile,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.competence.models import PerformanceCriteria
|
from vbv_lernwelt.competence.models import PerformanceCriteria
|
||||||
from vbv_lernwelt.core.constants import TEST_MENTOR1_USER_ID
|
from vbv_lernwelt.core.constants import (
|
||||||
|
TEST_MENTOR1_USER_ID,
|
||||||
|
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||||
|
)
|
||||||
from vbv_lernwelt.core.create_default_users import default_users
|
from vbv_lernwelt.core.create_default_users import default_users
|
||||||
from vbv_lernwelt.core.models import User
|
from vbv_lernwelt.core.models import User
|
||||||
from vbv_lernwelt.course.consts import (
|
from vbv_lernwelt.course.consts import (
|
||||||
|
|
@ -234,7 +237,7 @@ def create_versicherungsvermittlerin_course(
|
||||||
user=User.objects.get(username=user_data["email"]),
|
user=User.objects.get(username=user_data["email"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
csu = CourseSessionUser.objects.create(
|
student_1_csu = CourseSessionUser.objects.create(
|
||||||
course_session=cs,
|
course_session=cs,
|
||||||
user=User.objects.get(username="student-vv@eiger-versicherungen.ch"),
|
user=User.objects.get(username="student-vv@eiger-versicherungen.ch"),
|
||||||
)
|
)
|
||||||
|
|
@ -257,12 +260,29 @@ def create_versicherungsvermittlerin_course(
|
||||||
role=CourseSessionUser.Role.EXPERT,
|
role=CourseSessionUser.Role.EXPERT,
|
||||||
)
|
)
|
||||||
|
|
||||||
lemme = LearningMentor.objects.create(
|
mentor_and_student_2_learning_csu = CourseSessionUser.objects.create(
|
||||||
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
course_session=cs,
|
||||||
course=cs.course,
|
user=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID),
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
)
|
)
|
||||||
|
|
||||||
lemme.participants.add(csu)
|
# TEST_MENTOR1_USER_ID is only mentor
|
||||||
|
just_mentor = LearningMentor.objects.create(
|
||||||
|
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
||||||
|
course_session=cs,
|
||||||
|
)
|
||||||
|
|
||||||
|
just_mentor.participants.add(student_1_csu)
|
||||||
|
just_mentor.participants.add(mentor_and_student_2_learning_csu)
|
||||||
|
|
||||||
|
# TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID is both student and mentor
|
||||||
|
|
||||||
|
mentor_and_student_learning_mentor = LearningMentor.objects.create(
|
||||||
|
mentor=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID),
|
||||||
|
course_session=cs,
|
||||||
|
)
|
||||||
|
|
||||||
|
mentor_and_student_learning_mentor.participants.add(student_1_csu)
|
||||||
|
|
||||||
for admin_email in ADMIN_EMAILS:
|
for admin_email in ADMIN_EMAILS:
|
||||||
CourseSessionUser.objects.create(
|
CourseSessionUser.objects.create(
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ class CourseCompletionApiTestCase(APITestCase):
|
||||||
self.assertEqual(len(response.json()), 0)
|
self.assertEqual(len(response.json()), 0)
|
||||||
|
|
||||||
def test_api_courseSession_withCourseSessionUser(self):
|
def test_api_courseSession_withCourseSessionUser(self):
|
||||||
csu = CourseSessionUser.objects.create(
|
CourseSessionUser.objects.create(
|
||||||
course_session=self.course_session,
|
course_session=self.course_session,
|
||||||
user=self.user,
|
user=self.user,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -155,8 +155,8 @@ def get_course_sessions(request):
|
||||||
|
|
||||||
# enrich with mentor course sessions
|
# enrich with mentor course sessions
|
||||||
mentor_course_sessions = CourseSession.objects.filter(
|
mentor_course_sessions = CourseSession.objects.filter(
|
||||||
course__in=LearningMentor.objects.filter(mentor=request.user).values_list(
|
id__in=LearningMentor.objects.filter(mentor=request.user).values_list(
|
||||||
"course", flat=True
|
"course_session", flat=True
|
||||||
)
|
)
|
||||||
).prefetch_related("course")
|
).prefetch_related("course")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,11 +83,16 @@ class DashboardQuery(graphene.ObjectType):
|
||||||
statistics_dashboard_course_ids,
|
statistics_dashboard_course_ids,
|
||||||
) = get_user_statistics_dashboards(user=user)
|
) = get_user_statistics_dashboards(user=user)
|
||||||
|
|
||||||
course_session_dashboards = get_user_course_session_dashboards(
|
(
|
||||||
|
course_session_dashboards,
|
||||||
|
course_session_dashboard_course_ids,
|
||||||
|
) = get_user_course_session_dashboards(
|
||||||
user=user, exclude_course_ids=statistics_dashboard_course_ids
|
user=user, exclude_course_ids=statistics_dashboard_course_ids
|
||||||
)
|
)
|
||||||
|
|
||||||
learning_mentor_dashboards = get_learning_mentor_dashboards(user=user)
|
learning_mentor_dashboards, _ = get_learning_mentor_dashboards(
|
||||||
|
user=user, exclude_course_ids=course_session_dashboard_course_ids
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
statistic_dashboards
|
statistic_dashboards
|
||||||
|
|
@ -163,13 +168,13 @@ class DashboardQuery(graphene.ObjectType):
|
||||||
|
|
||||||
|
|
||||||
def get_user_statistics_dashboards(user: User) -> Tuple[List[Dict[str, str]], Set[int]]:
|
def get_user_statistics_dashboards(user: User) -> Tuple[List[Dict[str, str]], Set[int]]:
|
||||||
course_index = set()
|
course_ids = set()
|
||||||
dashboards = []
|
dashboards = []
|
||||||
|
|
||||||
for group in CourseSessionGroup.objects.all():
|
for group in CourseSessionGroup.objects.all():
|
||||||
if can_view_course_session_group_statistics(user=user, group=group):
|
if can_view_course_session_group_statistics(user=user, group=group):
|
||||||
course = group.course
|
course = group.course
|
||||||
course_index.add(course)
|
course_ids.add(course)
|
||||||
dashboards.append(
|
dashboards.append(
|
||||||
{
|
{
|
||||||
"id": str(course.id),
|
"id": str(course.id),
|
||||||
|
|
@ -180,31 +185,38 @@ def get_user_statistics_dashboards(user: User) -> Tuple[List[Dict[str, str]], Se
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return dashboards, course_index
|
return dashboards, course_ids
|
||||||
|
|
||||||
|
|
||||||
def get_learning_mentor_dashboards(user: User) -> List[Dict[str, str]]:
|
def get_learning_mentor_dashboards(
|
||||||
learning_mentor = LearningMentor.objects.filter(mentor=user)
|
user: User, exclude_course_ids: Set[int]
|
||||||
|
) -> Tuple[List[Dict[str, str]], Set[int]]:
|
||||||
|
learning_mentor = LearningMentor.objects.filter(mentor=user).exclude(
|
||||||
|
course_session__course__id__in=exclude_course_ids
|
||||||
|
)
|
||||||
|
|
||||||
dashboards = []
|
dashboards = []
|
||||||
|
course_ids = set()
|
||||||
|
|
||||||
for mentor in learning_mentor:
|
for mentor in learning_mentor:
|
||||||
course = mentor.course
|
course = mentor.course_session.course
|
||||||
|
course_ids.add(course.id)
|
||||||
dashboards.append(
|
dashboards.append(
|
||||||
{
|
{
|
||||||
"id": str(course.id),
|
"id": str(course.id),
|
||||||
"name": course.title,
|
"name": course.title,
|
||||||
"slug": course.slug,
|
"slug": course.slug,
|
||||||
"dashboard_type": DashboardType.SIMPLE_DASHBOARD,
|
"dashboard_type": DashboardType.MENTOR_DASHBOARD,
|
||||||
"course_configuration": course.configuration,
|
"course_configuration": course.configuration,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return dashboards
|
return dashboards, course_ids
|
||||||
|
|
||||||
|
|
||||||
def get_user_course_session_dashboards(
|
def get_user_course_session_dashboards(
|
||||||
user: User, exclude_course_ids: Set[int]
|
user: User, exclude_course_ids: Set[int]
|
||||||
) -> List[Dict[str, str]]:
|
) -> Tuple[List[Dict[str, str]], Set[int]]:
|
||||||
"""
|
"""
|
||||||
Edge case: what do we show to users with access to multiple
|
Edge case: what do we show to users with access to multiple
|
||||||
sessions of a course, but with varying permissions?
|
sessions of a course, but with varying permissions?
|
||||||
|
|
@ -212,6 +224,7 @@ def get_user_course_session_dashboards(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dashboards = []
|
dashboards = []
|
||||||
|
course_ids = set()
|
||||||
|
|
||||||
course_sessions = CourseSession.objects.exclude(course__in=exclude_course_ids)
|
course_sessions = CourseSession.objects.exclude(course__in=exclude_course_ids)
|
||||||
roles_by_course: Dict[Course, Set[DashboardType]] = {}
|
roles_by_course: Dict[Course, Set[DashboardType]] = {}
|
||||||
|
|
@ -237,6 +250,8 @@ def get_user_course_session_dashboards(
|
||||||
# fallback: just go with simple list dashboard
|
# fallback: just go with simple list dashboard
|
||||||
resolved_dashboard_type = DashboardType.SIMPLE_DASHBOARD
|
resolved_dashboard_type = DashboardType.SIMPLE_DASHBOARD
|
||||||
|
|
||||||
|
course_ids.add(course.id)
|
||||||
|
|
||||||
dashboards.append(
|
dashboards.append(
|
||||||
{
|
{
|
||||||
"id": str(course.id),
|
"id": str(course.id),
|
||||||
|
|
@ -247,4 +262,4 @@ def get_user_course_session_dashboards(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return dashboards
|
return dashboards, course_ids
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ class DashboardType(Enum):
|
||||||
STATISTICS_DASHBOARD = "StatisticsDashboard"
|
STATISTICS_DASHBOARD = "StatisticsDashboard"
|
||||||
PROGRESS_DASHBOARD = "ProgressDashboard"
|
PROGRESS_DASHBOARD = "ProgressDashboard"
|
||||||
SIMPLE_DASHBOARD = "SimpleDashboard"
|
SIMPLE_DASHBOARD = "SimpleDashboard"
|
||||||
|
MENTOR_DASHBOARD = "MentorDashboard"
|
||||||
|
|
||||||
|
|
||||||
class DashboardConfigType(graphene.ObjectType):
|
class DashboardConfigType(graphene.ObjectType):
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@ class DashboardTestCase(GraphQLTestCase):
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
)
|
)
|
||||||
|
|
||||||
add_learning_mentor(course=course_1, mentor=mentor, mentee=csu)
|
add_learning_mentor(course_session=cs_1, mentor=mentor, mentee=csu)
|
||||||
|
|
||||||
self.client.force_login(mentor)
|
self.client.force_login(mentor)
|
||||||
|
|
||||||
|
|
@ -291,7 +291,55 @@ class DashboardTestCase(GraphQLTestCase):
|
||||||
self.assertEqual(len(response.json()["data"]["dashboard_config"]), 1)
|
self.assertEqual(len(response.json()["data"]["dashboard_config"]), 1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
response.json()["data"]["dashboard_config"][0]["dashboard_type"],
|
response.json()["data"]["dashboard_config"][0]["dashboard_type"],
|
||||||
"SIMPLE_DASHBOARD",
|
"MENTOR_DASHBOARD",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_mentor_and_member_mixed(self):
|
||||||
|
# GIVEN
|
||||||
|
course, _ = create_course("Test Course")
|
||||||
|
cs = create_course_session(course=course, title="Test Course Session 1")
|
||||||
|
|
||||||
|
# in same course session
|
||||||
|
mentor_and_member = create_user("mentor_and_member")
|
||||||
|
|
||||||
|
mentee = add_course_session_user(
|
||||||
|
course_session=cs,
|
||||||
|
user=create_user("mentee"),
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
)
|
||||||
|
|
||||||
|
add_course_session_user(
|
||||||
|
course_session=cs,
|
||||||
|
user=mentor_and_member,
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
)
|
||||||
|
|
||||||
|
add_learning_mentor(course_session=cs, mentor=mentor_and_member, mentee=mentee)
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
self.client.force_login(mentor_and_member)
|
||||||
|
|
||||||
|
query = """query {
|
||||||
|
dashboard_config {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
dashboard_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
response = self.query(query)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertResponseNoErrors(response)
|
||||||
|
|
||||||
|
# given mentor + member -> should see the PROGRESS_DASHBOARD,
|
||||||
|
# not the MENTOR_DASHBOARD which is only for "pure" mentors.
|
||||||
|
self.assertEqual(len(response.json()["data"]["dashboard_config"]), 1)
|
||||||
|
self.assertEqual(
|
||||||
|
response.json()["data"]["dashboard_config"][0]["dashboard_type"],
|
||||||
|
"PROGRESS_DASHBOARD",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_course_statistics_deny_not_allowed_user(self):
|
def test_course_statistics_deny_not_allowed_user(self):
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ def has_course_access(user, course_id):
|
||||||
).exists():
|
).exists():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if LearningMentor.objects.filter(course_id=course_id, mentor=user).exists():
|
if LearningMentor.objects.filter(
|
||||||
|
course_session__course_id=course_id, mentor=user
|
||||||
|
).exists():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return CourseSessionUser.objects.filter(
|
return CourseSessionUser.objects.filter(
|
||||||
|
|
@ -39,7 +41,45 @@ def has_course_session_access(user, course_session_id: int):
|
||||||
).exists()
|
).exists()
|
||||||
|
|
||||||
|
|
||||||
def is_user_mentor(mentor: User, participant_user_id: str, course_session_id: int):
|
def has_course_session_preview(user, course_session_id: int):
|
||||||
|
if user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if is_course_session_member(user, course_session_id):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return is_learning_mentor(user, course_session_id) or is_course_session_expert(
|
||||||
|
user, course_session_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def has_media_library(user, course_session_id: int):
|
||||||
|
if user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if CourseSessionGroup.objects.filter(course_session=course_session_id).exists():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return CourseSessionUser.objects.filter(
|
||||||
|
course_session=course_session_id,
|
||||||
|
user=user,
|
||||||
|
).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def is_learning_mentor(mentor: User, course_session_id: int):
|
||||||
|
course_session = CourseSession.objects.get(id=course_session_id)
|
||||||
|
|
||||||
|
if course_session is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return LearningMentor.objects.filter(
|
||||||
|
mentor=mentor, course_session=course_session
|
||||||
|
).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def is_learning_mentor_for_user(
|
||||||
|
mentor: User, participant_user_id: str, course_session_id: int
|
||||||
|
):
|
||||||
csu = CourseSessionUser.objects.filter(
|
csu = CourseSessionUser.objects.filter(
|
||||||
course_session_id=course_session_id, user_id=participant_user_id
|
course_session_id=course_session_id, user_id=participant_user_id
|
||||||
).first()
|
).first()
|
||||||
|
|
@ -48,7 +88,7 @@ def is_user_mentor(mentor: User, participant_user_id: str, course_session_id: in
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return LearningMentor.objects.filter(
|
return LearningMentor.objects.filter(
|
||||||
course_id=csu.course_session.course_id, mentor=mentor, participants=csu
|
course_session_id=course_session_id, mentor=mentor, participants=csu
|
||||||
).exists()
|
).exists()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -98,7 +138,7 @@ def can_evaluate_assignments(
|
||||||
role=CourseSessionUser.Role.EXPERT,
|
role=CourseSessionUser.Role.EXPERT,
|
||||||
).exists()
|
).exists()
|
||||||
|
|
||||||
is_mentor = is_user_mentor(
|
is_mentor = is_learning_mentor_for_user(
|
||||||
mentor=evaluation_user,
|
mentor=evaluation_user,
|
||||||
participant_user_id=assignment_user_id,
|
participant_user_id=assignment_user_id,
|
||||||
course_session_id=course_session_id,
|
course_session_id=course_session_id,
|
||||||
|
|
@ -173,7 +213,9 @@ def has_role_in_course(user: User, course: Course) -> bool:
|
||||||
).exists():
|
).exists():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if LearningMentor.objects.filter(course=course, mentor=user).exists():
|
if LearningMentor.objects.filter(
|
||||||
|
course_session__course=course, mentor=user
|
||||||
|
).exists():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if CourseSessionGroup.objects.filter(course=course, supervisor=user).exists():
|
if CourseSessionGroup.objects.filter(course=course, supervisor=user).exists():
|
||||||
|
|
@ -192,6 +234,50 @@ def can_view_course(user: User, course: Course) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def has_appointments(user: User, course_session_id: int) -> bool:
|
||||||
|
if user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if CourseSessionGroup.objects.filter(course_session=course_session_id).exists():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return CourseSessionUser.objects.filter(course_session=course_session_id).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def has_learning_mentor(user: User, course_session_id: int) -> bool:
|
||||||
|
course_session = CourseSession.objects.get(id=course_session_id)
|
||||||
|
|
||||||
|
if course_session is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not course_session.course.configuration.enable_learning_mentor:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if is_learning_mentor(user, course_session_id):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if is_course_session_member(user, course_session_id):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def can_edit_mentors(user: User, course_session_id: int) -> bool:
|
||||||
|
if not has_learning_mentor(user, course_session_id):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# limit further, since has_learning_mentor is too broad
|
||||||
|
return is_course_session_member(user, course_session_id)
|
||||||
|
|
||||||
|
|
||||||
|
def can_guide_members(user: User, course_session_id: int) -> bool:
|
||||||
|
if not has_learning_mentor(user, course_session_id):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# limit further, since has_learning_mentor is too broad
|
||||||
|
return is_learning_mentor(user, course_session_id)
|
||||||
|
|
||||||
|
|
||||||
def can_view_profile(user: User, profile_user: CourseSessionUser) -> bool:
|
def can_view_profile(user: User, profile_user: CourseSessionUser) -> bool:
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
return True
|
return True
|
||||||
|
|
@ -199,7 +285,9 @@ def can_view_profile(user: User, profile_user: CourseSessionUser) -> bool:
|
||||||
if user == profile_user.user:
|
if user == profile_user.user:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if is_course_session_expert(user, profile_user.course_session.id) or is_user_mentor(
|
if is_course_session_expert(
|
||||||
|
user, profile_user.course_session.id
|
||||||
|
) or is_learning_mentor_for_user(
|
||||||
mentor=user,
|
mentor=user,
|
||||||
participant_user_id=profile_user.user.id,
|
participant_user_id=profile_user.user.id,
|
||||||
course_session_id=profile_user.course_session.id,
|
course_session_id=profile_user.course_session.id,
|
||||||
|
|
@ -215,7 +303,7 @@ def can_view_course_completions(
|
||||||
return (
|
return (
|
||||||
str(user.id) == target_user_id
|
str(user.id) == target_user_id
|
||||||
or is_course_session_expert(user=user, course_session_id=course_session_id)
|
or is_course_session_expert(user=user, course_session_id=course_session_id)
|
||||||
or is_user_mentor(
|
or is_learning_mentor_for_user(
|
||||||
mentor=user,
|
mentor=user,
|
||||||
participant_user_id=target_user_id,
|
participant_user_id=target_user_id,
|
||||||
course_session_id=course_session_id,
|
course_session_id=course_session_id,
|
||||||
|
|
@ -232,6 +320,17 @@ def can_complete_learning_content(user: User, course_session_id: int) -> bool:
|
||||||
def course_session_permissions(user: User, course_session_id: int) -> list[str]:
|
def course_session_permissions(user: User, course_session_id: int) -> list[str]:
|
||||||
return _action_list(
|
return _action_list(
|
||||||
{
|
{
|
||||||
|
"learning-mentor": has_learning_mentor(user, course_session_id),
|
||||||
|
"learning-mentor::edit-mentors": can_edit_mentors(user, course_session_id),
|
||||||
|
"learning-mentor::guide-members": can_guide_members(
|
||||||
|
user, course_session_id
|
||||||
|
),
|
||||||
|
"preview": has_course_session_preview(user, course_session_id),
|
||||||
|
"media-library": has_media_library(user, course_session_id),
|
||||||
|
"appointments": has_appointments(user, course_session_id),
|
||||||
|
"expert-cockpit": is_course_session_expert(user, course_session_id),
|
||||||
|
"learning-path": is_course_session_member(user, course_session_id),
|
||||||
|
"competence-navi": is_course_session_member(user, course_session_id),
|
||||||
"complete-learning-content": can_complete_learning_content(
|
"complete-learning-content": can_complete_learning_content(
|
||||||
user, course_session_id
|
user, course_session_id
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,7 @@ class ActionTestCase(TestCase):
|
||||||
def test_course_session_permissions(self):
|
def test_course_session_permissions(self):
|
||||||
# GIVEN
|
# GIVEN
|
||||||
lm = create_user("mentor")
|
lm = create_user("mentor")
|
||||||
LearningMentor.objects.create(
|
LearningMentor.objects.create(mentor=lm, course_session=self.course_session)
|
||||||
mentor=lm,
|
|
||||||
course=self.course,
|
|
||||||
)
|
|
||||||
|
|
||||||
participant = create_user("participant")
|
participant = create_user("participant")
|
||||||
add_course_session_user(
|
add_course_session_user(
|
||||||
|
|
@ -48,6 +45,34 @@ class ActionTestCase(TestCase):
|
||||||
trainer_actions = course_session_permissions(trainer, self.course_session.id)
|
trainer_actions = course_session_permissions(trainer, self.course_session.id)
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(len(mentor_actions), 0)
|
self.assertEqual(
|
||||||
self.assertEqual(participant_actions, ["complete-learning-content"])
|
mentor_actions,
|
||||||
self.assertEqual(trainer_actions, ["complete-learning-content"])
|
[
|
||||||
|
"learning-mentor",
|
||||||
|
"learning-mentor::guide-members",
|
||||||
|
"preview",
|
||||||
|
"appointments",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
participant_actions,
|
||||||
|
[
|
||||||
|
"learning-mentor",
|
||||||
|
"learning-mentor::edit-mentors",
|
||||||
|
"media-library",
|
||||||
|
"appointments",
|
||||||
|
"learning-path",
|
||||||
|
"competence-navi",
|
||||||
|
"complete-learning-content",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
trainer_actions,
|
||||||
|
[
|
||||||
|
"preview",
|
||||||
|
"media-library",
|
||||||
|
"appointments",
|
||||||
|
"expert-cockpit",
|
||||||
|
"complete-learning-content",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from vbv_lernwelt.course.creators.test_utils import (
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.course.models import CourseSessionUser
|
from vbv_lernwelt.course.models import CourseSessionUser
|
||||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||||
from vbv_lernwelt.iam.permissions import has_role_in_course, is_user_mentor
|
from vbv_lernwelt.iam.permissions import has_role_in_course, is_learning_mentor_for_user
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ class RoleTestCase(TestCase):
|
||||||
# GIVEN
|
# GIVEN
|
||||||
LearningMentor.objects.create(
|
LearningMentor.objects.create(
|
||||||
mentor=self.user,
|
mentor=self.user,
|
||||||
course=self.course,
|
course_session=self.course_session,
|
||||||
)
|
)
|
||||||
|
|
||||||
# WHEN
|
# WHEN
|
||||||
|
|
@ -75,14 +75,14 @@ class RoleTestCase(TestCase):
|
||||||
|
|
||||||
learning_mentor = LearningMentor.objects.create(
|
learning_mentor = LearningMentor.objects.create(
|
||||||
mentor=mentor,
|
mentor=mentor,
|
||||||
course=course,
|
course_session=course_session,
|
||||||
)
|
)
|
||||||
|
|
||||||
learning_mentor.participants.add(member_csu)
|
learning_mentor.participants.add(member_csu)
|
||||||
learning_mentor.save()
|
learning_mentor.save()
|
||||||
|
|
||||||
# WHEN
|
# WHEN
|
||||||
is_mentor = is_user_mentor(
|
is_mentor = is_learning_mentor_for_user(
|
||||||
mentor=mentor,
|
mentor=mentor,
|
||||||
participant_user_id=member.id,
|
participant_user_id=member.id,
|
||||||
course_session_id=course_session.id,
|
course_session_id=course_session.id,
|
||||||
|
|
@ -106,7 +106,7 @@ class RoleTestCase(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# WHEN
|
# WHEN
|
||||||
is_mentor = is_user_mentor(
|
is_mentor = is_learning_mentor_for_user(
|
||||||
mentor=wanna_be_mentor,
|
mentor=wanna_be_mentor,
|
||||||
participant_user_id=member.id,
|
participant_user_id=member.id,
|
||||||
course_session_id=course_session.id,
|
course_session_id=course_session.id,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class LearningMentorAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
participant_count.short_description = "Participants"
|
participant_count.short_description = "Participants"
|
||||||
|
|
||||||
list_display = ["mentor", "course", "participant_count"]
|
list_display = ["mentor", "course_session", "participant_count"]
|
||||||
|
|
||||||
search_fields = ["mentor__email"]
|
search_fields = ["mentor__email"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ def get_assignment_completions(
|
||||||
)[0],
|
)[0],
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
last_name=user.last_name,
|
last_name=user.last_name,
|
||||||
url=f"/course/{course_session.course.slug}/cockpit/assignment/{assignment.id}/{user.id}",
|
url=f"/course/{course_session.course.slug}/assignment-evaluation/{assignment.id}/{user.id}",
|
||||||
)
|
)
|
||||||
for user in sorted_participants
|
for user in sorted_participants
|
||||||
]
|
]
|
||||||
|
|
@ -110,19 +110,19 @@ def get_praxis_assignments(
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
circle_id = learning_content.get_circle().id
|
circle = learning_content.get_circle()
|
||||||
|
circle_ids.add(circle.id)
|
||||||
|
|
||||||
records.append(
|
records.append(
|
||||||
MentorAssignmentStatus(
|
MentorAssignmentStatus(
|
||||||
id=course_session_assignment.id,
|
id=course_session_assignment.id,
|
||||||
title=learning_content.content_assignment.title,
|
title=learning_content.content_assignment.title,
|
||||||
circle_id=circle_id,
|
circle_id=circle.id,
|
||||||
|
circle_name=circle.title,
|
||||||
pending_evaluations=submitted_count,
|
pending_evaluations=submitted_count,
|
||||||
completions=completions,
|
completions=completions,
|
||||||
type=MentorAssignmentStatusType.PRAXIS_ASSIGNMENT,
|
type=MentorAssignmentStatusType.PRAXIS_ASSIGNMENT,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
circle_ids.add(circle_id)
|
|
||||||
|
|
||||||
return records, circle_ids
|
return records, circle_ids
|
||||||
|
|
|
||||||
|
|
@ -64,9 +64,6 @@ def get_self_feedback_evaluation(
|
||||||
feedback_provider_user=evaluation_user,
|
feedback_provider_user=evaluation_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
circle_id = learning_unit.get_circle().id
|
|
||||||
circle_ids.add(circle_id)
|
|
||||||
|
|
||||||
pending_evaluations = len([f for f in feedbacks if not f.feedback_submitted])
|
pending_evaluations = len([f for f in feedbacks if not f.feedback_submitted])
|
||||||
|
|
||||||
completions = [
|
completions = [
|
||||||
|
|
@ -90,11 +87,15 @@ def get_self_feedback_evaluation(
|
||||||
participants=participants,
|
participants=participants,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
circle = learning_unit.get_circle()
|
||||||
|
circle_ids.add(circle.id)
|
||||||
|
|
||||||
records.append(
|
records.append(
|
||||||
MentorAssignmentStatus(
|
MentorAssignmentStatus(
|
||||||
id=learning_unit.id,
|
id=learning_unit.id,
|
||||||
title=learning_unit.title,
|
title=learning_unit.title,
|
||||||
circle_id=circle_id,
|
circle_id=circle.id,
|
||||||
|
circle_name=circle.title,
|
||||||
pending_evaluations=pending_evaluations,
|
pending_evaluations=pending_evaluations,
|
||||||
completions=completions,
|
completions=completions,
|
||||||
type=MentorAssignmentStatusType.SELF_EVALUATION_FEEDBACK,
|
type=MentorAssignmentStatusType.SELF_EVALUATION_FEEDBACK,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ class MentorAssignmentStatus:
|
||||||
id: str
|
id: str
|
||||||
title: str
|
title: str
|
||||||
circle_id: str
|
circle_id: str
|
||||||
|
circle_name: str
|
||||||
pending_evaluations: int
|
pending_evaluations: int
|
||||||
completions: List[MentorAssignmentCompletion]
|
completions: List[MentorAssignmentCompletion]
|
||||||
type: MentorAssignmentStatusType
|
type: MentorAssignmentStatusType
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Generated by Django 3.2.20 on 2024-03-19 09:58
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_migrate_course_session_to_course(apps, schema_editor):
|
||||||
|
LearningMentor = apps.get_model("learning_mentor", "LearningMentor")
|
||||||
|
for lm in LearningMentor.objects.all():
|
||||||
|
lm.course = lm.course_session.course
|
||||||
|
lm.save()
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_course_to_course_session(apps, schema_editor):
|
||||||
|
LearningMentor = apps.get_model("learning_mentor", "LearningMentor")
|
||||||
|
CourseSession = apps.get_model("course", "CourseSession")
|
||||||
|
|
||||||
|
for lm in LearningMentor.objects.all():
|
||||||
|
# first is fine for VV there is only one course session per course
|
||||||
|
course_session = CourseSession.objects.filter(course=lm.course).first()
|
||||||
|
lm.course_session = course_session
|
||||||
|
lm.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("course", "0007_auto_20240226_1553"),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("learning_mentor", "0005_alter_learningmentor_mentor"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="learningmentor",
|
||||||
|
name="course_session",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
# this is a dummy value, it will be replaced by the migration
|
||||||
|
default=-1,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="course.coursesession",
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.RunPython(
|
||||||
|
code=migrate_course_to_course_session,
|
||||||
|
reverse_code=reverse_migrate_course_session_to_course,
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="learningmentor",
|
||||||
|
unique_together={("mentor", "course_session")},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="learningmentor",
|
||||||
|
name="course",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -9,7 +9,7 @@ from vbv_lernwelt.course.models import CourseSessionUser
|
||||||
|
|
||||||
class LearningMentor(models.Model):
|
class LearningMentor(models.Model):
|
||||||
mentor = models.ForeignKey(User, on_delete=models.CASCADE)
|
mentor = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
course = models.ForeignKey("course.Course", on_delete=models.CASCADE)
|
course_session = models.ForeignKey("course.CourseSession", on_delete=models.CASCADE)
|
||||||
|
|
||||||
participants = models.ManyToManyField(
|
participants = models.ManyToManyField(
|
||||||
CourseSessionUser,
|
CourseSessionUser,
|
||||||
|
|
@ -18,12 +18,12 @@ class LearningMentor(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = [["mentor", "course"]]
|
unique_together = [["mentor", "course_session"]]
|
||||||
verbose_name = "Lernbegleiter"
|
verbose_name = "Lernbegleiter"
|
||||||
verbose_name_plural = "Lernbegleiter"
|
verbose_name_plural = "Lernbegleiter"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.mentor} ({self.course.title})"
|
return f"{self.mentor} ({self.course_session.title})"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def course_sessions(self):
|
def course_sessions(self):
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ class MentorAssignmentStatusSerializer(serializers.Serializer):
|
||||||
id = serializers.CharField()
|
id = serializers.CharField()
|
||||||
title = serializers.CharField()
|
title = serializers.CharField()
|
||||||
circle_id = serializers.CharField()
|
circle_id = serializers.CharField()
|
||||||
|
circle_name = serializers.CharField()
|
||||||
pending_evaluations = serializers.IntegerField()
|
pending_evaluations = serializers.IntegerField()
|
||||||
completions = MentorAssignmentCompletionSerializer(many=True)
|
completions = MentorAssignmentCompletionSerializer(many=True)
|
||||||
type = serializers.ReadOnlyField()
|
type = serializers.ReadOnlyField()
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ def validate_student(sender, instance, action, reverse, model, pk_set, **kwargs)
|
||||||
if action == "pre_add":
|
if action == "pre_add":
|
||||||
participants = model.objects.filter(pk__in=pk_set)
|
participants = model.objects.filter(pk__in=pk_set)
|
||||||
for participant in participants:
|
for participant in participants:
|
||||||
if participant.course_session.course != instance.course:
|
if participant.course_session != instance.course_session:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
"Participant (CourseSessionUser) does not match the course for this mentor."
|
"Participant (CourseSessionUser) does not match the course for this mentor."
|
||||||
)
|
)
|
||||||
|
if participant.user == instance.mentor:
|
||||||
|
raise ValidationError("You cannot mentor yourself.")
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ class AttendanceServicesTestCase(TestCase):
|
||||||
self.assertEqual(result.status, expected_statuses[result.last_name])
|
self.assertEqual(result.status, expected_statuses[result.last_name])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
result.url,
|
result.url,
|
||||||
f"/course/test-lehrgang/cockpit/assignment/{self.assignment.id}/{result.user_id}",
|
f"/course/test-lehrgang/assignment-evaluation/{self.assignment.id}/{result.user_id}",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_praxis_assignment_status(self):
|
def test_praxis_assignment_status(self):
|
||||||
|
|
@ -108,5 +108,6 @@ class AttendanceServicesTestCase(TestCase):
|
||||||
assignment = assignments[0]
|
assignment = assignments[0]
|
||||||
self.assertEqual(assignment.pending_evaluations, 1)
|
self.assertEqual(assignment.pending_evaluations, 1)
|
||||||
self.assertEqual(assignment.title, "Dummy Assignment (PRAXIS_ASSIGNMENT)")
|
self.assertEqual(assignment.title, "Dummy Assignment (PRAXIS_ASSIGNMENT)")
|
||||||
|
self.assertEqual(assignment.circle_name, "Circle")
|
||||||
self.assertEqual(assignment.circle_id, self.circle.id)
|
self.assertEqual(assignment.circle_id, self.circle.id)
|
||||||
self.assertEqual(list(circle_ids)[0], self.circle.id)
|
self.assertEqual(list(circle_ids)[0], self.circle.id)
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,34 @@ class LearningMentorInvitationTest(APITestCase):
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
@patch("vbv_lernwelt.learning_mentor.views.send_email")
|
||||||
|
def test_create_denies_self_invitation(self, mock_send_mail) -> None:
|
||||||
|
# GIVEN
|
||||||
|
self.client.force_login(self.participant)
|
||||||
|
|
||||||
|
add_course_session_user(
|
||||||
|
self.course_session,
|
||||||
|
self.participant,
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
)
|
||||||
|
|
||||||
|
invite_url = reverse(
|
||||||
|
"create_invitation", kwargs={"course_session_id": self.course_session.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
email = self.participant.email
|
||||||
|
response = self.client.post(invite_url, data={"email": email})
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertEqual(
|
||||||
|
response.data,
|
||||||
|
{
|
||||||
|
"message": "You cannot invite yourself",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@patch("vbv_lernwelt.learning_mentor.views.send_email")
|
@patch("vbv_lernwelt.learning_mentor.views.send_email")
|
||||||
def test_create_invitation(self, mock_send_mail) -> None:
|
def test_create_invitation(self, mock_send_mail) -> None:
|
||||||
# GIVEN
|
# GIVEN
|
||||||
|
|
@ -164,49 +192,11 @@ class LearningMentorInvitationTest(APITestCase):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_accept_invitation_role_collision(self) -> None:
|
|
||||||
# GIVEN
|
|
||||||
participant_cs_user = add_course_session_user(
|
|
||||||
self.course_session,
|
|
||||||
self.participant,
|
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
|
||||||
)
|
|
||||||
|
|
||||||
invitee = create_user("invitee")
|
|
||||||
invitation = MentorInvitation.objects.create(
|
|
||||||
participant=participant_cs_user, email=invitee.email
|
|
||||||
)
|
|
||||||
# Make invitee a trainer
|
|
||||||
add_course_session_user(
|
|
||||||
self.course_session,
|
|
||||||
invitee,
|
|
||||||
role=CourseSessionUser.Role.EXPERT,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.client.force_login(invitee)
|
|
||||||
|
|
||||||
accept_url = reverse(
|
|
||||||
"accept_invitation", kwargs={"course_session_id": self.course_session.id}
|
|
||||||
)
|
|
||||||
|
|
||||||
# WHEN
|
|
||||||
response = self.client.post(accept_url, data={"invitation_id": invitation.id})
|
|
||||||
|
|
||||||
# THEN
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
||||||
self.assertEqual(
|
|
||||||
response.data,
|
|
||||||
{
|
|
||||||
"message": "User already has a role in this course",
|
|
||||||
"code": "existingRole",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_accept_invitation(self) -> None:
|
def test_accept_invitation(self) -> None:
|
||||||
# GIVEN
|
# GIVEN
|
||||||
participant_cs_user = add_course_session_user(
|
participant_cs_user = add_course_session_user(
|
||||||
self.course_session,
|
course_session=self.course_session,
|
||||||
self.participant,
|
user=self.participant,
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -229,7 +219,9 @@ class LearningMentorInvitationTest(APITestCase):
|
||||||
self.assertFalse(MentorInvitation.objects.filter(id=invitation.id).exists())
|
self.assertFalse(MentorInvitation.objects.filter(id=invitation.id).exists())
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
LearningMentor.objects.filter(
|
LearningMentor.objects.filter(
|
||||||
mentor=invitee, course=self.course, participants=participant_cs_user
|
mentor=invitee,
|
||||||
|
course_session=self.course_session,
|
||||||
|
participants=participant_cs_user,
|
||||||
).exists()
|
).exists()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ class LearningMentorAPITest(APITestCase):
|
||||||
# GIVEN
|
# GIVEN
|
||||||
self.client.force_login(self.mentor)
|
self.client.force_login(self.mentor)
|
||||||
LearningMentor.objects.create(
|
LearningMentor.objects.create(
|
||||||
mentor=self.mentor, course=self.course_session.course
|
mentor=self.mentor, course_session=self.course_session
|
||||||
)
|
)
|
||||||
|
|
||||||
# WHEN
|
# WHEN
|
||||||
|
|
@ -93,8 +93,7 @@ class LearningMentorAPITest(APITestCase):
|
||||||
participants = [self.participant_1, self.participant_2, self.participant_3]
|
participants = [self.participant_1, self.participant_2, self.participant_3]
|
||||||
self.client.force_login(self.mentor)
|
self.client.force_login(self.mentor)
|
||||||
mentor = LearningMentor.objects.create(
|
mentor = LearningMentor.objects.create(
|
||||||
mentor=self.mentor,
|
mentor=self.mentor, course_session=self.course_session
|
||||||
course=self.course_session.course,
|
|
||||||
)
|
)
|
||||||
mentor.participants.set(participants)
|
mentor.participants.set(participants)
|
||||||
|
|
||||||
|
|
@ -114,14 +113,18 @@ class LearningMentorAPITest(APITestCase):
|
||||||
self.assertEqual(participant_1["first_name"], "Test")
|
self.assertEqual(participant_1["first_name"], "Test")
|
||||||
self.assertEqual(participant_1["last_name"], "Participant_1")
|
self.assertEqual(participant_1["last_name"], "Participant_1")
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
response.data["mentor_id"],
|
||||||
|
mentor.id,
|
||||||
|
)
|
||||||
|
|
||||||
def test_api_self_evaluation_feedback(self) -> None:
|
def test_api_self_evaluation_feedback(self) -> None:
|
||||||
# GIVEN
|
# GIVEN
|
||||||
participants = [self.participant_1, self.participant_2, self.participant_3]
|
participants = [self.participant_1, self.participant_2, self.participant_3]
|
||||||
self.client.force_login(self.mentor)
|
self.client.force_login(self.mentor)
|
||||||
|
|
||||||
mentor = LearningMentor.objects.create(
|
mentor = LearningMentor.objects.create(
|
||||||
mentor=self.mentor,
|
mentor=self.mentor, course_session=self.course_session
|
||||||
course=self.course_session.course,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
mentor.participants.set(participants)
|
mentor.participants.set(participants)
|
||||||
|
|
@ -204,7 +207,7 @@ class LearningMentorAPITest(APITestCase):
|
||||||
|
|
||||||
mentor = LearningMentor.objects.create(
|
mentor = LearningMentor.objects.create(
|
||||||
mentor=self.mentor,
|
mentor=self.mentor,
|
||||||
course=self.course_session.course,
|
course_session=self.course_session,
|
||||||
)
|
)
|
||||||
|
|
||||||
participants = [self.participant_1, self.participant_2, self.participant_3]
|
participants = [self.participant_1, self.participant_2, self.participant_3]
|
||||||
|
|
@ -265,8 +268,7 @@ class LearningMentorAPITest(APITestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
learning_mentor = LearningMentor.objects.create(
|
learning_mentor = LearningMentor.objects.create(
|
||||||
mentor=self.mentor,
|
mentor=self.mentor, course_session=self.course_session
|
||||||
course=self.course_session.course,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
learning_mentor.participants.add(participant_cs_user)
|
learning_mentor.participants.add(participant_cs_user)
|
||||||
|
|
@ -288,7 +290,41 @@ class LearningMentorAPITest(APITestCase):
|
||||||
self.assertEqual(mentor_user["email"], self.mentor.email)
|
self.assertEqual(mentor_user["email"], self.mentor.email)
|
||||||
self.assertEqual(mentor_user["id"], str(self.mentor.id))
|
self.assertEqual(mentor_user["id"], str(self.mentor.id))
|
||||||
|
|
||||||
def test_remove_user_mentor(self) -> None:
|
def test_remove_participant_as_mentor(self) -> None:
|
||||||
|
# GIVEN
|
||||||
|
self.client.force_login(self.mentor)
|
||||||
|
|
||||||
|
participant_cs_user = add_course_session_user(
|
||||||
|
self.course_session,
|
||||||
|
create_user("participant"),
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
)
|
||||||
|
|
||||||
|
learning_mentor = LearningMentor.objects.create(
|
||||||
|
mentor=self.mentor, course_session=self.course_session
|
||||||
|
)
|
||||||
|
|
||||||
|
learning_mentor.participants.add(participant_cs_user)
|
||||||
|
|
||||||
|
remove_url = reverse(
|
||||||
|
"remove_participant_from_mentor",
|
||||||
|
kwargs={
|
||||||
|
"course_session_id": self.course_session.id,
|
||||||
|
"mentor_id": learning_mentor.id,
|
||||||
|
"participant_user_id": participant_cs_user.user.id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
response = self.client.delete(remove_url)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
self.assertFalse(
|
||||||
|
LearningMentor.objects.filter(participants=participant_cs_user).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_remove_myself_from_mentor(self) -> None:
|
||||||
# GIVEN
|
# GIVEN
|
||||||
participant = create_user("participant")
|
participant = create_user("participant")
|
||||||
self.client.force_login(participant)
|
self.client.force_login(participant)
|
||||||
|
|
@ -300,22 +336,22 @@ class LearningMentorAPITest(APITestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
learning_mentor = LearningMentor.objects.create(
|
learning_mentor = LearningMentor.objects.create(
|
||||||
mentor=self.mentor,
|
mentor=self.mentor, course_session=self.course_session
|
||||||
course=self.course_session.course,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
learning_mentor.participants.add(participant_cs_user)
|
learning_mentor.participants.add(participant_cs_user)
|
||||||
|
|
||||||
remove_self_url = reverse(
|
remove_url = reverse(
|
||||||
"remove_self_from_mentor",
|
"remove_participant_from_mentor",
|
||||||
kwargs={
|
kwargs={
|
||||||
"course_session_id": self.course_session.id,
|
"course_session_id": self.course_session.id,
|
||||||
"mentor_id": learning_mentor.id,
|
"mentor_id": learning_mentor.id,
|
||||||
|
"participant_user_id": participant_cs_user.user.id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# WHEN
|
# WHEN
|
||||||
response = self.client.delete(remove_self_url)
|
response = self.client.delete(remove_url)
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
@ -326,11 +362,19 @@ class LearningMentorAPITest(APITestCase):
|
||||||
def test_mentor_multiple_courses(self) -> None:
|
def test_mentor_multiple_courses(self) -> None:
|
||||||
# GIVEN
|
# GIVEN
|
||||||
course_a, _ = create_course("Course A")
|
course_a, _ = create_course("Course A")
|
||||||
|
course_session_a = create_course_session(course=course_a, title="Test A")
|
||||||
|
|
||||||
course_b, _ = create_course("Course B")
|
course_b, _ = create_course("Course B")
|
||||||
|
course_session_b = create_course_session(course=course_b, title="Test B")
|
||||||
|
|
||||||
# WHEN
|
# WHEN
|
||||||
LearningMentor.objects.create(mentor=self.mentor, course=course_a)
|
LearningMentor.objects.create(
|
||||||
LearningMentor.objects.create(mentor=self.mentor, course=course_b)
|
mentor=self.mentor, course_session=course_session_a
|
||||||
|
)
|
||||||
|
|
||||||
|
LearningMentor.objects.create(
|
||||||
|
mentor=self.mentor, course_session=course_session_b
|
||||||
|
)
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(LearningMentor.objects.count(), 2)
|
self.assertEqual(LearningMentor.objects.count(), 2)
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ urlpatterns = [
|
||||||
path("summary", views.mentor_summary, name="mentor_summary"),
|
path("summary", views.mentor_summary, name="mentor_summary"),
|
||||||
path("mentors", views.list_user_mentors, name="list_user_mentors"),
|
path("mentors", views.list_user_mentors, name="list_user_mentors"),
|
||||||
path(
|
path(
|
||||||
"mentors/<int:mentor_id>/leave",
|
"mentors/<int:mentor_id>/remove/<uuid:participant_user_id>",
|
||||||
views.remove_self_from_mentor,
|
views.remove_participant_from_mentor,
|
||||||
name="remove_self_from_mentor",
|
name="remove_participant_from_mentor",
|
||||||
),
|
),
|
||||||
path("invitations", views.list_invitations, name="list_invitations"),
|
path("invitations", views.list_invitations, name="list_invitations"),
|
||||||
path("invitations/create", views.create_invitation, name="create_invitation"),
|
path("invitations/create", views.create_invitation, name="create_invitation"),
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue