chore: introduce course configuration "feature" flags in client

This commit is contained in:
Livio Bieri 2024-02-28 14:14:23 +01:00
parent 51609591e1
commit e4329194ee
10 changed files with 59 additions and 109 deletions

View File

@ -23,7 +23,6 @@ import {
getMediaCenterUrl, getMediaCenterUrl,
} from "@/utils/utils"; } from "@/utils/utils";
import { useCockpitStore } from "@/stores/cockpit"; import { useCockpitStore } from "@/stores/cockpit";
import { VV_COURSE_IDS } from "@/constants";
log.debug("MainNavigationBar created"); log.debug("MainNavigationBar created");
@ -100,13 +99,13 @@ const hasNotificationsMenu = computed(() => {
}); });
const hasMentorManagementMenu = computed(() => { const hasMentorManagementMenu = computed(() => {
if (courseSessionsStore.currentCourseSessionHasCockpit) { if (courseSessionsStore.currentCourseSessionHasCockpit || !inCourse()) {
return false; return false;
} }
return (
// learning mentor management is only available for VV courses courseSessionsStore.currentCourseSession?.course.configuration
const currentCourseId = courseSessionsStore.currentCourseSession?.course.id || ""; .enable_learning_mentor ?? false
return inCourse() && VV_COURSE_IDS.includes(currentCourseId); );
}); });
</script> </script>

View File

@ -3,9 +3,3 @@ export const itCheckboxDefaultIconCheckedTailwindClass =
export const itCheckboxDefaultIconUncheckedTailwindClass = export const itCheckboxDefaultIconUncheckedTailwindClass =
"bg-[url(/static/icons/icon-checkbox-unchecked.svg)] hover:bg-[url(/static/icons/icon-checkbox-unchecked-hover.svg)]"; "bg-[url(/static/icons/icon-checkbox-unchecked.svg)] hover:bg-[url(/static/icons/icon-checkbox-unchecked-hover.svg)]";
export const VV_COURSE_IDS = [
"-4", // vv-de
"-10", // vv-fr
"-11", // vv-it
];

View File

@ -455,6 +455,7 @@ export type CourseStatisticsType = {
export type DashboardConfigType = { export type DashboardConfigType = {
__typename?: 'DashboardConfigType'; __typename?: 'DashboardConfigType';
course_configuration: CourseConfigurationObjectType;
dashboard_type: DashboardType; dashboard_type: DashboardType;
id: Scalars['ID']['output']; id: Scalars['ID']['output'];
name: Scalars['String']['output']; name: Scalars['String']['output'];

View File

@ -200,6 +200,7 @@ type DashboardConfigType {
name: String! name: String!
slug: String! slug: String!
dashboard_type: DashboardType! dashboard_type: DashboardType!
course_configuration: CourseConfigurationObjectType!
} }
enum DashboardType { enum DashboardType {
@ -208,6 +209,12 @@ enum DashboardType {
SIMPLE_DASHBOARD SIMPLE_DASHBOARD
} }
type CourseConfigurationObjectType {
enable_circle_documents: Boolean!
enable_learning_mentor: Boolean!
enable_competence_certificates: Boolean!
}
type LearningPathObjectType implements CoursePageInterface { type LearningPathObjectType implements CoursePageInterface {
id: ID! id: ID!
title: String! title: String!
@ -241,12 +248,6 @@ type CourseObjectType {
action_competences: [ActionCompetenceObjectType!]! action_competences: [ActionCompetenceObjectType!]!
} }
type CourseConfigurationObjectType {
enable_circle_documents: Boolean!
enable_learning_mentor: Boolean!
enable_competence_certificates: Boolean!
}
type ActionCompetenceObjectType implements CoursePageInterface { type ActionCompetenceObjectType implements CoursePageInterface {
competence_id: String! competence_id: String!
id: ID! id: ID!

View File

@ -12,7 +12,6 @@ import {
} from "@/pages/competence/utils"; } from "@/pages/competence/utils";
import { useSelfEvaluationFeedbackSummaries } from "@/services/selfEvaluationFeedback"; import { useSelfEvaluationFeedbackSummaries } from "@/services/selfEvaluationFeedback";
import ItProgress from "@/components/ui/ItProgress.vue"; import ItProgress from "@/components/ui/ItProgress.vue";
import { VV_COURSE_IDS } from "@/constants";
const props = defineProps<{ const props = defineProps<{
courseSlug: string; courseSlug: string;
@ -67,12 +66,7 @@ const isFeedbackEvaluationVisible = computed(
false false
); );
// FIXME 22.02.24: To-be-tackled NEXT in a separate PR (shippable member comp.navi)
// -> Do not use the VV_COURSE_ID anymore (discuss with @chrigu) -> We do this next.
const currentCourseSession = useCurrentCourseSession(); const currentCourseSession = useCurrentCourseSession();
const hasCompetenceCertificates = computed(() => {
return !VV_COURSE_IDS.includes(currentCourseSession.value.course.id);
});
const isLoaded = computed( const isLoaded = computed(
() => () =>
@ -83,7 +77,10 @@ const isLoaded = computed(
<template> <template>
<div v-if="isLoaded" class="container-large lg:mt-4"> <div v-if="isLoaded" class="container-large lg:mt-4">
<!-- Competence certificates --> <!-- Competence certificates -->
<section v-if="hasCompetenceCertificates" class="mb-4 bg-white p-8"> <section
v-if="currentCourseSession.course.configuration.enable_competence_certificates"
class="mb-4 bg-white p-8"
>
<div class="flex items-center"> <div class="flex items-center">
<h3>{{ $t("a.Kompetenznachweise") }}</h3> <h3>{{ $t("a.Kompetenznachweise") }}</h3>
</div> </div>

View File

@ -1,8 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import * as log from "loglevel"; import * as log from "loglevel";
import { computed, onMounted } from "vue"; import { onMounted } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { VV_COURSE_IDS } from "@/constants";
import { useCurrentCourseSession } from "@/composables"; import { useCurrentCourseSession } from "@/composables";
log.debug("CompetenceParentPage created"); log.debug("CompetenceParentPage created");
@ -29,12 +28,7 @@ function routeInSelfEvaluationAndFeedback() {
return route.path.endsWith("/self-evaluation-and-feedback"); return route.path.endsWith("/self-evaluation-and-feedback");
} }
// FIXME 22.02.24: To-be-tackled NEXT in a separate PR (shippable member comp.navi)
// -> Do not use the VV_COURSE_ID anymore (discuss with @chrigu) -> We do this next.
const currentCourseSession = useCurrentCourseSession(); const currentCourseSession = useCurrentCourseSession();
const isVVCourse = computed(() => {
return VV_COURSE_IDS.includes(currentCourseSession.value.course.id);
});
onMounted(async () => { onMounted(async () => {
log.debug("CompetenceParentPage mounted", props.courseSlug); log.debug("CompetenceParentPage mounted", props.courseSlug);
@ -54,7 +48,9 @@ onMounted(async () => {
</router-link> </router-link>
</li> </li>
<li <li
v-if="!isVVCourse" v-if="
currentCourseSession.course.configuration.enable_competence_certificates
"
class="border-t-2 border-t-transparent lg:ml-12" class="border-t-2 border-t-transparent lg:ml-12"
:class="{ 'border-b-2 border-b-blue-900': routeInCompetenceCertificate() }" :class="{ 'border-b-2 border-b-blue-900': routeInCompetenceCertificate() }"
> >
@ -76,7 +72,7 @@ onMounted(async () => {
class="block py-3" class="block py-3"
> >
{{ {{
isVVCourse currentCourseSession.course.configuration.enable_learning_mentor
? $t("a.Selbst- und Fremdeinschätzungen") ? $t("a.Selbst- und Fremdeinschätzungen")
: $t("a.Selbsteinschätzungen") : $t("a.Selbsteinschätzungen")
}} }}

View File

@ -10,7 +10,6 @@ import type {
} from "@/gql/graphql"; } from "@/gql/graphql";
import CompetenceSummaryBox from "@/components/dashboard/CompetenceSummaryBox.vue"; import CompetenceSummaryBox from "@/components/dashboard/CompetenceSummaryBox.vue";
import AssignmentProgressSummaryBox from "@/components/dashboard/AssignmentProgressSummaryBox.vue"; import AssignmentProgressSummaryBox from "@/components/dashboard/AssignmentProgressSummaryBox.vue";
import { VV_COURSE_IDS } from "@/constants";
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
@ -48,11 +47,12 @@ const competenceCriteriaUrl = computed(() => {
return `/course/${courseSlug.value}/competence/self-evaluation-and-feedback?courseSessionId=${courseSessionProgress.value?.session_to_continue_id}`; return `/course/${courseSlug.value}/competence/self-evaluation-and-feedback?courseSessionId=${courseSessionProgress.value?.session_to_continue_id}`;
}); });
const isVVCourse = computed(() => { const showCompetenceCertificates = computed(() => {
if (!dashboardStore.currentDashboardConfig) { if (!dashboardStore.currentDashboardConfig) {
return false; return false;
} }
return VV_COURSE_IDS.includes(dashboardStore.currentDashboardConfig.id); return dashboardStore.currentDashboardConfig.course_configuration
.enable_competence_certificates;
}); });
</script> </script>
@ -82,7 +82,7 @@ const isVVCourse = computed(() => {
</div> </div>
<div class="grid auto-rows-fr grid-cols-1 gap-8 xl:grid-cols-2"> <div class="grid auto-rows-fr grid-cols-1 gap-8 xl:grid-cols-2">
<AssignmentProgressSummaryBox <AssignmentProgressSummaryBox
v-if="!isVVCourse" v-if="showCompetenceCertificates"
:total-assignments="assignment.total_count" :total-assignments="assignment.total_count"
:achieved-points-count="assignment.points_achieved_count" :achieved-points-count="assignment.points_achieved_count"
:max-points-count="assignment.points_max_count" :max-points-count="assignment.points_max_count"

View File

@ -1,14 +1,12 @@
from typing import Dict, List, Set, Tuple from typing import Dict, List, Set, Tuple
import graphene import graphene
from graphql import GraphQLError
from vbv_lernwelt.assignment.models import ( from vbv_lernwelt.assignment.models import (
AssignmentCompletion, AssignmentCompletion,
AssignmentCompletionStatus, AssignmentCompletionStatus,
) )
from vbv_lernwelt.core.admin import User from vbv_lernwelt.core.admin import User
from vbv_lernwelt.course.graphql.types import CourseConfigurationObjectType
from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser
from vbv_lernwelt.course_session_group.models import CourseSessionGroup from vbv_lernwelt.course_session_group.models import CourseSessionGroup
from vbv_lernwelt.dashboard.graphql.types.competence import competences from vbv_lernwelt.dashboard.graphql.types.competence import competences
@ -24,7 +22,6 @@ from vbv_lernwelt.iam.permissions import (
can_view_course_session, can_view_course_session,
can_view_course_session_group_statistics, can_view_course_session_group_statistics,
can_view_course_session_progress, can_view_course_session_progress,
can_view_course,
) )
from vbv_lernwelt.learning_mentor.models import LearningMentor from vbv_lernwelt.learning_mentor.models import LearningMentor
@ -34,10 +31,6 @@ class DashboardQuery(graphene.ObjectType):
CourseStatisticsType, course_id=graphene.ID(required=True) CourseStatisticsType, course_id=graphene.ID(required=True)
) )
course_configuration = graphene.Field(
CourseConfigurationObjectType, course_id=graphene.ID(required=True)
)
course_progress = graphene.Field( course_progress = graphene.Field(
CourseProgressType, course_id=graphene.ID(required=True) CourseProgressType, course_id=graphene.ID(required=True)
) )
@ -46,14 +39,6 @@ class DashboardQuery(graphene.ObjectType):
graphene.NonNull(DashboardConfigType), required=True graphene.NonNull(DashboardConfigType), required=True
) )
def resolve_course_configuration(root, info, course_id: str): # noqa
course = Course.objects.get(id=course_id)
if not can_view_course(user=info.context.user, course=course):
raise GraphQLError("You do not have access to this course.")
return course.configuration
def resolve_course_statistics(root, info, course_id: str): # noqa def resolve_course_statistics(root, info, course_id: str): # noqa
user = info.context.user user = info.context.user
course = Course.objects.get(id=course_id) course = Course.objects.get(id=course_id)
@ -89,6 +74,7 @@ class DashboardQuery(graphene.ObjectType):
"name": c["title"], "name": c["title"],
"slug": c["slug"], "slug": c["slug"],
"dashboard_type": DashboardType.SIMPLE_DASHBOARD, "dashboard_type": DashboardType.SIMPLE_DASHBOARD,
"course_configuration": c.configuration,
} }
for c in courses for c in courses
] ]
@ -191,6 +177,7 @@ def get_user_statistics_dashboards(user: User) -> Tuple[List[Dict[str, str]], Se
"name": course.title, "name": course.title,
"slug": course.slug, "slug": course.slug,
"dashboard_type": DashboardType.STATISTICS_DASHBOARD, "dashboard_type": DashboardType.STATISTICS_DASHBOARD,
"course_configuration": course.configuration,
} }
) )
@ -209,6 +196,7 @@ def get_learning_mentor_dashboards(user: User) -> List[Dict[str, str]]:
"name": course.title, "name": course.title,
"slug": course.slug, "slug": course.slug,
"dashboard_type": DashboardType.SIMPLE_DASHBOARD, "dashboard_type": DashboardType.SIMPLE_DASHBOARD,
"course_configuration": course.configuration,
} }
) )
@ -256,6 +244,7 @@ def get_user_course_session_dashboards(
"name": course.title, "name": course.title,
"slug": course.slug, "slug": course.slug,
"dashboard_type": resolved_dashboard_type, "dashboard_type": resolved_dashboard_type,
"course_configuration": course.configuration,
} }
) )

View File

@ -1,6 +1,7 @@
import graphene import graphene
from graphene import Enum from graphene import Enum
from vbv_lernwelt.course.graphql.types import CourseConfigurationObjectType
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.dashboard.graphql.types.assignment import ( from vbv_lernwelt.dashboard.graphql.types.assignment import (
assignments, assignments,
@ -59,6 +60,7 @@ class DashboardConfigType(graphene.ObjectType):
name = graphene.String(required=True) name = graphene.String(required=True)
slug = graphene.String(required=True) slug = graphene.String(required=True)
dashboard_type = graphene.Field(DashboardType, required=True) dashboard_type = graphene.Field(DashboardType, required=True)
course_configuration = graphene.Field(CourseConfigurationObjectType, required=True)
class ProgressDashboardCompetenceType(graphene.ObjectType): class ProgressDashboardCompetenceType(graphene.ObjectType):

View File

@ -150,65 +150,14 @@ class DashboardTestCase(GraphQLTestCase):
self.assertEqual(assignment["points_max_count"], 50) self.assertEqual(assignment["points_max_count"], 50)
self.assertEqual(assignment["points_achieved_count"], 20) self.assertEqual(assignment["points_achieved_count"], 20)
def test_course_configuration_denied(self):
# GIVEN
role_less_user = create_user("sepp@blatter.fifa")
self.client.force_login(role_less_user)
course, _ = create_course("Course 1")
# WHEN
query = """query($course_id: ID!) {
course_configuration(course_id: $course_id) {
enable_circle_documents
enable_learning_mentor
enable_competence_certificates
}
}
"""
response = self.query(query, variables={"course_id": str(course.id)})
# THEN
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.json()["errors"][0]["message"],
"You do not have access to this course.",
)
def test_course_configuration(self):
# GIVEN
member = create_user("sepp@blatter.fifa")
self.client.force_login(member)
course, _ = create_course("Course 1")
add_course_session_user(
course_session=create_course_session(course=course, title="Whatever"),
role=CourseSessionUser.Role.MEMBER,
user=member,
)
# WHEN
query = """query($course_id: ID!) {
course_configuration(course_id: $course_id) {
enable_circle_documents
enable_learning_mentor
enable_competence_certificates
}
}
"""
response = self.query(query, variables={"course_id": str(course.id)})
# THEN
self.assertResponseNoErrors(response)
course_configuration = response.json()["data"]["course_configuration"]
self.assertEqual(course_configuration["enable_circle_documents"], True)
self.assertEqual(course_configuration["enable_learning_mentor"], True)
self.assertEqual(course_configuration["enable_competence_certificates"], True)
def test_dashboard_config(self): def test_dashboard_config(self):
# GIVEN # GIVEN
course_1, _ = create_course("Test Course 1") course_1, _ = create_course("Test Course 1")
course_1.configuration.enable_learning_mentor = False
course_1.configuration.enable_competence_certificates = False
course_1.configuration.enable_circle_documents = False
course_1.configuration.save()
course_2, _ = create_course("Test Course 2") course_2, _ = create_course("Test Course 2")
course_3, _ = create_course("Test Course 3") course_3, _ = create_course("Test Course 3")
@ -249,6 +198,11 @@ class DashboardTestCase(GraphQLTestCase):
name name
slug slug
dashboard_type dashboard_type
course_configuration {
enable_circle_documents
enable_learning_mentor
enable_competence_certificates
}
} }
} }
""" """
@ -269,6 +223,13 @@ class DashboardTestCase(GraphQLTestCase):
self.assertEqual(course_1_config["slug"], course_1.slug) self.assertEqual(course_1_config["slug"], course_1.slug)
self.assertEqual(course_1_config["dashboard_type"], "PROGRESS_DASHBOARD") self.assertEqual(course_1_config["dashboard_type"], "PROGRESS_DASHBOARD")
course_1_course_configuration = course_1_config["course_configuration"]
self.assertFalse(course_1_course_configuration["enable_circle_documents"])
self.assertFalse(course_1_course_configuration["enable_learning_mentor"])
self.assertFalse(
course_1_course_configuration["enable_competence_certificates"]
)
course_2_config = find_dashboard_config_by_course_id( course_2_config = find_dashboard_config_by_course_id(
dashboard_config, course_2.id dashboard_config, course_2.id
) )
@ -277,6 +238,11 @@ class DashboardTestCase(GraphQLTestCase):
self.assertEqual(course_2_config["slug"], course_2.slug) self.assertEqual(course_2_config["slug"], course_2.slug)
self.assertEqual(course_2_config["dashboard_type"], "STATISTICS_DASHBOARD") self.assertEqual(course_2_config["dashboard_type"], "STATISTICS_DASHBOARD")
course_2_course_configuration = course_2_config["course_configuration"]
self.assertTrue(course_2_course_configuration["enable_circle_documents"])
self.assertTrue(course_2_course_configuration["enable_learning_mentor"])
self.assertTrue(course_2_course_configuration["enable_competence_certificates"])
course_3_config = find_dashboard_config_by_course_id( course_3_config = find_dashboard_config_by_course_id(
dashboard_config, course_3.id dashboard_config, course_3.id
) )
@ -285,6 +251,11 @@ class DashboardTestCase(GraphQLTestCase):
self.assertEqual(course_3_config["slug"], course_3.slug) self.assertEqual(course_3_config["slug"], course_3.slug)
self.assertEqual(course_3_config["dashboard_type"], "SIMPLE_DASHBOARD") self.assertEqual(course_3_config["dashboard_type"], "SIMPLE_DASHBOARD")
course_3_course_configuration = course_3_config["course_configuration"]
self.assertTrue(course_3_course_configuration["enable_circle_documents"])
self.assertTrue(course_3_course_configuration["enable_learning_mentor"])
self.assertTrue(course_3_course_configuration["enable_competence_certificates"])
def test_dashboard_config_mentor(self): def test_dashboard_config_mentor(self):
# GIVEN # GIVEN
course_1, _ = create_course("Test Course 1") course_1, _ = create_course("Test Course 1")