feat: cockpit type / navigation
TODO dashboard -> cockpit/mentor (temporary) TODO dashboard -> cockpit/expert
This commit is contained in:
parent
d9cb334404
commit
742801bf22
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import {
|
||||||
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,69 @@ 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;
|
||||||
|
const course = courseSession.course;
|
||||||
|
|
||||||
|
if (!course.configuration.enable_learning_mentor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Use learning-mentor action instead of deprecated-mentor once we have moved everything from cockpit!
|
||||||
|
return courseSession.actions.includes("deprecated-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 +141,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 +203,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(
|
getLearningMentorManagementUrl(
|
||||||
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,6 +1,5 @@
|
||||||
<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";
|
||||||
|
|
@ -18,6 +17,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 +35,6 @@ const clickLink = (to: string | undefined) => {
|
||||||
emit("closemodal");
|
emit("closemodal");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -57,42 +59,38 @@ 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") }}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
<li v-if="hasMediaLibraryMenu" class="mb-6">
|
<li v-if="hasMediaLibraryMenu" class="mb-6">
|
||||||
<button
|
<button
|
||||||
data-cy="medialibrary-link"
|
data-cy="medialibrary-link"
|
||||||
|
|
|
||||||
|
|
@ -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(() => {
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -186,35 +185,23 @@ const router = createRouter({
|
||||||
component: () => import("@/pages/cockpit/cockpitPage/CockpitExpertPage.vue"),
|
component: () => import("@/pages/cockpit/cockpitPage/CockpitExpertPage.vue"),
|
||||||
props: true,
|
props: true,
|
||||||
name: "expertCockpit",
|
name: "expertCockpit",
|
||||||
meta: {
|
|
||||||
cockpitType: "expert",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "mentor",
|
path: "mentor",
|
||||||
component: () => import("@/pages/cockpit/cockpitPage/CockpitMentorPage.vue"),
|
component: () => import("@/pages/cockpit/cockpitPage/CockpitMentorPage.vue"),
|
||||||
name: "mentorCockpit",
|
name: "mentorCockpit",
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
component: () =>
|
component: () =>
|
||||||
import("@/pages/cockpit/cockpitPage/mentor/MentorOverviewPage.vue"),
|
import("@/pages/cockpit/cockpitPage/mentor/MentorOverviewPage.vue"),
|
||||||
name: "mentorCockpitOverview",
|
name: "mentorCockpitOverview",
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "participants",
|
path: "participants",
|
||||||
component: () =>
|
component: () =>
|
||||||
import("@/pages/cockpit/cockpitPage/mentor/MentorParticipantsPage.vue"),
|
import("@/pages/cockpit/cockpitPage/mentor/MentorParticipantsPage.vue"),
|
||||||
name: "mentorCockpitParticipants",
|
name: "mentorCockpitParticipants",
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "self-evaluation-feedback/:learningUnitId",
|
path: "self-evaluation-feedback/:learningUnitId",
|
||||||
|
|
@ -223,18 +210,12 @@ const router = createRouter({
|
||||||
"@/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedbackPage.vue"
|
"@/pages/cockpit/cockpitPage/mentor/SelfEvaluationFeedbackPage.vue"
|
||||||
),
|
),
|
||||||
name: "mentorSelfEvaluationFeedback",
|
name: "mentorSelfEvaluationFeedback",
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "details",
|
path: "details",
|
||||||
component: () =>
|
component: () =>
|
||||||
import("@/pages/cockpit/cockpitPage/mentor/MentorDetailParentPage.vue"),
|
import("@/pages/cockpit/cockpitPage/mentor/MentorDetailParentPage.vue"),
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "praxis-assignments/:praxisAssignmentId",
|
path: "praxis-assignments/:praxisAssignmentId",
|
||||||
|
|
@ -243,9 +224,6 @@ const router = createRouter({
|
||||||
"@/pages/cockpit/cockpitPage/mentor/MentorPraxisAssignmentPage.vue"
|
"@/pages/cockpit/cockpitPage/mentor/MentorPraxisAssignmentPage.vue"
|
||||||
),
|
),
|
||||||
name: "mentorCockpitPraxisAssignments",
|
name: "mentorCockpitPraxisAssignments",
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -255,9 +233,6 @@ const router = createRouter({
|
||||||
"@/pages/cockpit/cockpitPage/mentor/MentorSelfEvaluationFeedbackAssignmentPage.vue"
|
"@/pages/cockpit/cockpitPage/mentor/MentorSelfEvaluationFeedbackAssignmentPage.vue"
|
||||||
),
|
),
|
||||||
name: "mentorCockpitSelfEvaluationFeedbackAssignments",
|
name: "mentorCockpitSelfEvaluationFeedbackAssignments",
|
||||||
meta: {
|
|
||||||
cockpitType: "mentor",
|
|
||||||
},
|
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -428,8 +403,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;
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -136,9 +131,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):
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,52 @@ 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_expert_cockpit(user, course_session_id: int):
|
||||||
|
# FIXME: is_learning_mentor is just here WHILE we move cockpit -> learning-mentor THEN remove this!
|
||||||
|
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_id=course_session.course_id
|
||||||
|
).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()
|
||||||
|
|
@ -98,7 +143,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,
|
||||||
|
|
@ -192,6 +237,16 @@ 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 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 +254,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 +272,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 +289,15 @@ 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(
|
||||||
{
|
{
|
||||||
|
# FIXME: Just here WHILE we move cockpit -> learning-mentor THEN remove this!
|
||||||
|
"deprecated-mentor": is_course_session_member(user, course_session_id),
|
||||||
|
"learning-mentor": is_learning_mentor(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": has_expert_cockpit(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
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ class RoleTestCase(TestCase):
|
||||||
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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue