WIP: Use REST endpoint

This commit is contained in:
Christian Cueni 2024-04-08 12:47:55 +02:00
parent 5ba319e524
commit 4a982d8af2
8 changed files with 258 additions and 69 deletions

View File

@ -1,80 +1,71 @@
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import type { CourseProgressType, WidgetType } from "@/gql/graphql";
import { DashboardConfigType } from "@/gql/graphql";
import { fetchCourseData } from "@/services/dashboard";
import { computed } from "vue";
import type { DashboardConfigType, WidgetType } from "@/services/dashboard";
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
import CompetenceSummary from "@/components/dashboard/CompetenceSummary.vue";
import AssignmentSummary from "@/components/dashboard/AssignmentSummary.vue";
const mentorWidgets = [
"MENTOR_TASKS_WIDGET",
"MENTOR_PERSON_WIDGET",
"MENTOR_COMPETENCE_WIDGET",
"MentorTasksWidget",
"MentorPersonWidget",
"MentorCompetenceWidget",
];
const progressWidgets = ["COMPETENCE_WIDGET", "COMPETENCE_CERTIFICATE_WIDGET"];
const progressWidgets = ["CompetenceWidget", "CompetenceCertificateWidget"];
const props = defineProps<{
courseConfig: DashboardConfigType;
}>();
const isLoading = ref(true);
const data = ref<CourseProgressType | null>(null);
const courseSlug = computed(() => props.courseConfig?.slug);
const courseName = computed(() => props.courseConfig?.name);
const courseSlug = computed(() => props.courseConfig?.course_slug);
const courseName = computed(() => props.courseConfig?.course_title);
const numberOfMentorWidgets = computed(() => {
return data.value?.ui_config.widgets.filter((widget) =>
mentorWidgets.includes(widget)
).length;
return props.courseConfig.widgets.filter((widget) => mentorWidgets.includes(widget))
.length;
});
const numberOfProgressWidgets = computed(() => {
return data.value?.ui_config.widgets.filter((widget) =>
progressWidgets.includes(widget)
).length;
return props.courseConfig.widgets.filter((widget) => progressWidgets.includes(widget))
.length;
});
function hasWidget(widget: WidgetType) {
return data.value?.ui_config.widgets.includes(widget);
return props.courseConfig.widgets.includes(widget);
}
onMounted(async () => {
data.value = await fetchCourseData(props.courseConfig.id);
isLoading.value = false;
});
</script>
<template>
<div v-if="!isLoading && courseConfig" class="mb-14 space-y-8">
<div v-if="courseConfig" class="mb-14 space-y-8">
<div class="flex flex-col space-y-7 bg-white p-6">
<h3>{{ courseName }}</h3>
<p>{{ data.ui_config.role_key }}</p>
<p>{{ courseConfig.role_key }}</p>
<LearningPathDiagram
v-if="hasWidget('PROGRESS_WIDGET') && data.session_to_continue_id && courseSlug"
v-if="
hasWidget('ProgressWidget') &&
courseConfig.session_to_continue_id &&
courseSlug
"
:key="courseSlug"
:course-slug="courseSlug"
:course-session-id="data.session_to_continue_id"
:course-session-id="courseConfig.session_to_continue_id"
diagram-type="horizontal"
></LearningPathDiagram>
<div v-if="numberOfProgressWidgets" class="flex flex-col flex-wrap">
<CompetenceSummary
v-if="hasWidget('COMPETENCE_WIDGET')"
v-if="hasWidget('CompetenceWidget')"
:course-slug="courseSlug"
:session-to-continue-id="data.session_to_continue_id"
:course-id="courseConfig.id"
:session-to-continue-id="courseConfig.session_to_continue_id"
:course-id="courseConfig.course_id"
></CompetenceSummary>
<AssignmentSummary
v-if="hasWidget('COMPETENCE_CERTIFICATE_WIDGET')"
v-if="hasWidget('CompetenceCertificateWidget')"
:course-slug="courseSlug"
:session-to-continue-id="data.session_to_continue_id"
:course-id="courseConfig.id"
:session-to-continue-id="courseConfig.session_to_continue_id"
:course-id="courseConfig.course_id"
/>
</div>
<div v-if="numberOfMentorWidgets > 0" class="flex flex-col flex-wrap">
<div v-if="hasWidget('MENTOR_TASKS_WIDGET')">MENTOR_TASKS_WIDGET</div>
<div v-if="hasWidget('MENTOR_PERSON_WIDGET')">MENTOR_PERSON_WIDGET</div>
<div v-if="hasWidget('MENTOR_COMPETENCE_WIDGET')">MENTOR_COMPETENCE_WIDGET</div>
<div v-if="hasWidget('MentorTasksWidget')">MENTOR_TASKS_WIDGET</div>
<div v-if="hasWidget('MentorPersonWidget')">MENTOR_PERSON_WIDGET</div>
<div v-if="hasWidget('MentorCompetenceWidget')">MENTOR_COMPETENCE_WIDGET</div>
</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { Component } from "vue";
import { onMounted } from "vue";
import type { Component, Ref } from "vue";
import { onMounted, ref } from "vue";
import StatisticPage from "@/pages/dashboard/StatisticPage.vue";
import ProgressPage from "@/pages/dashboard/ProgressPage.vue";
import SimpleDates from "@/components/dashboard/SimpleDates.vue";
@ -13,6 +13,7 @@ import CourseDetailDates from "@/components/dashboard/CourseDetailDates.vue";
import NoCourseSession from "@/components/dashboard/NoCourseSession.vue";
import MentorPage from "@/pages/dashboard/MentorPage.vue";
import CoursePanel from "@/components/dashboard/CoursePanel.vue";
import { DashboardConfigType, fetchDashboardConfigv2 } from "@/services/dashboard";
const dashboardStore = useDashboardStore();
@ -29,7 +30,16 @@ const boards: Record<DashboardType, DashboardPage> = {
PRAXISBILDNER_DASHBOARD: { main: CoursePanel, aside: SimpleDates },
};
onMounted(dashboardStore.loadDashboardDetails);
const dashboardConfigv2: Ref<DashboardConfigType[]> = ref([]);
onMounted(async () => {
dashboardConfigv2.value = await fetchDashboardConfigv2();
await dashboardStore.loadDashboardDetails();
});
function newDashboardConfigForId(id: string) {
return dashboardConfigv2.value.find((config) => config.course_id == +id);
}
</script>
<template>
@ -54,8 +64,8 @@ onMounted(dashboardStore.loadDashboardDetails);
<ul>
<li v-for="config in dashboardStore.dashboardConfigs" :key="config.id">
<p>{{ config }}</p>
<CoursePanel :course-config="config" />
<p>{{ newDashboardConfigForId(config.id) }}</p>
<CoursePanel :course-config="newDashboardConfigForId(config.id)" />
</li>
</ul>
<!-- keep until we unify the dashboard -->

View File

@ -20,6 +20,21 @@ export type DashboardPersonRoleType =
| "LEARNING_MENTOR"
| "LEARNING_MENTEE";
export type DashboardRoleKeyType =
| "Supervisor"
| "Expert"
| "Member"
| "MentorUK"
| "MentorVV";
export type WidgetType =
| "ProgressWidget"
| "CompetenceWidget"
| "MentorTasksWidget"
| "MentorPersonWidget"
| "MentorCompetenceWidget"
| "CompetenceCertificateWidget";
export type DashboardPersonCourseSessionType = {
id: number;
session_title: string;
@ -40,6 +55,19 @@ export type DashboardPersonType = {
course_sessions: DashboardPersonCourseSessionType[];
};
export type DashboardConfigType = {
course_id: string;
course_slug: string;
course_title: string;
role_key: DashboardRoleKeyType;
is_uk: boolean;
is_vv: boolean;
is_mentor: boolean;
widgets: WidgetType[];
has_preview: boolean;
session_to_continue_id: string;
};
export const fetchStatisticData = async (
courseId: string
): Promise<CourseStatisticsType | null> => {
@ -113,3 +141,7 @@ export const fetchCourseData = async (
export async function fetchDashboardPersons() {
return await itGetCached<DashboardPersonType[]>("/api/dashboard/persons/");
}
export async function fetchDashboardConfigv2() {
return await itGetCached<DashboardConfigType[]>("/api/dashboard/config/");
}

View File

@ -39,7 +39,7 @@ from vbv_lernwelt.course.views import (
request_course_completion_for_user,
)
from vbv_lernwelt.course_session.views import get_course_session_documents
from vbv_lernwelt.dashboard.views import get_dashboard_persons
from vbv_lernwelt.dashboard.views import get_dashboard_config, get_dashboard_persons
from vbv_lernwelt.edoniq_test.views import (
export_students,
export_students_and_trainers,
@ -116,9 +116,10 @@ urlpatterns = [
# notify
re_path(r"api/notify/email_notification_settings/$", email_notification_settings,
name='email_notification_settings'),
# dashboard
path(r"api/dashboard/persons/", get_dashboard_persons, name="get_dashboard_persons"),
path(r"api/dashboard/config/", get_dashboard_config, name="get_dashboard_config"),
# course
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),

View File

@ -75,8 +75,9 @@ class DashboardQuery(graphene.ObjectType):
mentees_ids = set()
course_session_ids = set()
mentees = CourseSessionUser.objects.filter(participants__mentor=user,
course_session__course=course).values_list("user", "course_session")
mentees = CourseSessionUser.objects.filter(
participants__mentor=user, course_session__course=course
).values_list("user", "course_session")
for user_id, course_session_id in mentees:
mentees_ids.add(user_id)
course_session_ids.add(course_session_id)

View File

@ -74,8 +74,9 @@ def create_assignment_summary(course_id, metrics) -> AssignmentStatisticsSummary
def get_assignment_completion_metrics(
course_session: CourseSession, assignment: vbv_lernwelt.assignment.models.Assignment,
user_selection_ids: List[str] | None
course_session: CourseSession,
assignment: vbv_lernwelt.assignment.models.Assignment,
user_selection_ids: List[str] | None,
) -> AssignmentCompletionMetricsType:
course_session_users = CourseSessionUser.objects.filter(
course_session=course_session,
@ -112,7 +113,7 @@ def get_assignment_completion_metrics(
def create_record(
course_session_assignment: CourseSessionAssignment | CourseSessionEdoniqTest,
user_selection_ids: List[str] | None
user_selection_ids: List[str] | None,
) -> AssignmentStatisticsRecordType:
if isinstance(course_session_assignment, CourseSessionAssignment):
due_date = course_session_assignment.submission_deadline
@ -160,14 +161,18 @@ def assignments(
],
learning_content__content_assignment__competence_certificate__isnull=False,
):
record = create_record(course_session_assignment=csa, user_selection_ids=user_selection_ids)
record = create_record(
course_session_assignment=csa, user_selection_ids=user_selection_ids
)
records.append(record)
for cset in CourseSessionEdoniqTest.objects.filter(
course_session=course_session,
learning_content__content_assignment__competence_certificate__isnull=False,
):
record = create_record(course_session_assignment=cset, user_selection_ids=user_selection_ids)
record = create_record(
course_session_assignment=cset, user_selection_ids=user_selection_ids
)
records.append(record)
return AssignmentsStatisticsType(

View File

@ -241,8 +241,11 @@ class CourseStatisticsType(graphene.ObjectType):
)
def resolve_competences(root, info) -> CompetencesStatisticsType:
user_selection_ids = [str(user) for user in
root.user_selection_ids] if root.user_selection_ids else None # noqa
user_selection_ids = (
[str(user) for user in root.user_selection_ids]
if root.user_selection_ids
else None
) # noqa
records, success_total, fail_total = competences(
course_slug=str(root.course_slug),
course_session_selection_ids=[
@ -261,8 +264,11 @@ class CourseStatisticsType(graphene.ObjectType):
)
def resolve_assignments(root, info) -> AssignmentsStatisticsType:
user_selection_ids = [str(user) for user in
root.user_selection_ids] if root.user_selection_ids else None # noqa
user_selection_ids = (
[str(user) for user in root.user_selection_ids]
if root.user_selection_ids
else None
) # noqa
return assignments(
course_id=root.course_id,
course_session_selection_ids=root.course_session_selection_ids,
@ -381,7 +387,11 @@ def get_ui_config_for_course(course: Course, user: User) -> UIConfigType:
# mentors
if course.id in mentor_course_ids:
if not role_key:
role_key = RoleKeyType.MENTOR_UK if course.id in UK_COURSE_IDS else RoleKeyType.MENTOR_VV
role_key = (
RoleKeyType.MENTOR_UK
if course.id in UK_COURSE_IDS
else RoleKeyType.MENTOR_VV
)
has_preview = True
widgets.append(WidgetType.MENTOR_TASKS_WIDGET)
widgets.append(WidgetType.MENTOR_PERSON_WIDGET)

View File

@ -1,4 +1,4 @@
from dataclasses import dataclass
from dataclasses import asdict, dataclass
from typing import List, Set
from rest_framework.decorators import api_view
@ -9,6 +9,7 @@ from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.course.views import logger
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
from vbv_lernwelt.dashboard.graphql.types.dashboard import RoleKeyType, WidgetType
from vbv_lernwelt.learning_mentor.models import LearningMentor
@ -25,6 +26,20 @@ class CourseSessionWithRoles:
raise NotImplementedError("This proxy object cannot be saved.")
@dataclass(frozen=True)
class CourseConfig:
course_id: int
course_slug: str
course_title: str
role_key: str
is_uk: bool
is_vv: bool
is_mentor: bool
widgets: List[str]
has_preview: bool
session_to_continue_id: str | None
def get_course_sessions_with_roles_for_user(user: User) -> List[CourseSessionWithRoles]:
result_course_sessions = {}
@ -67,6 +82,18 @@ def get_course_sessions_with_roles_for_user(user: User) -> List[CourseSessionWit
]
def has_cs_role(roles: Set[str]) -> bool:
return bool(roles & {"SUPERVISOR", "EXPERT", "MEMBER"})
def user_role(roles: Set[str]) -> str:
return (
"SUPERVISOR"
if "SUPERVISOR" in roles
else ("EXPERT" if "EXPERT" in roles else "MEMBER")
)
@api_view(["GET"])
def get_dashboard_persons(request):
try:
@ -74,19 +101,11 @@ def get_dashboard_persons(request):
result_persons = {}
for cs in course_sessions:
if {
"SUPERVISOR",
"EXPERT",
"MEMBER",
} & cs.roles and cs.course.configuration.is_uk:
if has_cs_role(cs.roles) and cs.course.configuration.is_uk:
course_session_users = CourseSessionUser.objects.filter(
course_session=cs.id
)
my_role = (
"SUPERVISOR"
if "SUPERVISOR" in cs.roles
else ("EXPERT" if "EXPERT" in cs.roles else "MEMBER")
)
my_role = user_role(cs.roles)
for csu in course_session_users:
result_persons[csu.user.id] = {
"user_id": csu.user.id,
@ -183,3 +202,123 @@ def get_dashboard_persons(request):
except Exception as e:
logger.error(e, exc_info=True)
return Response({"error": str(e)}, status=404)
def get_widgets_for_course(
role_key: RoleKeyType, is_uk: bool, is_vv: bool, is_mentor: bool
) -> List[str]:
widgets = []
if role_key == RoleKeyType.MEMBER:
widgets.append(WidgetType.PROGRESS_WIDGET.value)
widgets.append(WidgetType.COMPETENCE_WIDGET.value)
if is_uk:
widgets.append(WidgetType.COMPETENCE_CERTIFICATE_WIDGET.value)
if is_mentor:
widgets.append(WidgetType.MENTOR_PERSON_WIDGET.value)
if is_vv:
widgets.append(WidgetType.MENTOR_TASKS_WIDGET.value)
if is_uk:
widgets.append(WidgetType.MENTOR_COMPETENCE_WIDGET.value)
return widgets
def get_role_and_mentor_key(
course_sessions: List[CourseSessionWithRoles], is_uk: bool, is_vv: bool
) -> tuple[RoleKeyType, bool]:
roles = set()
role = None
for cs in course_sessions:
roles.update(cs.roles)
if "SUPERVISOR" in roles:
role = RoleKeyType.SUPERVISOR
elif "EXPERT" in roles:
role = RoleKeyType.TRAINER
elif "MEMBER" in roles:
role = RoleKeyType.MEMBER
elif "LEARNING_MENTOR" in roles:
if is_uk:
role = RoleKeyType.MENTOR_UK
elif is_vv:
role = RoleKeyType.MENTOR_VV
is_mentor = "LEARNING_MENTOR" in roles
return role, is_mentor
def sort_course_sessions_by_course(
course_sessions: List[CourseSessionWithRoles],
) -> dict:
course_sessions_by_course = {}
for cs in course_sessions:
if cs.course.id not in course_sessions_by_course:
course_sessions_by_course[cs.course.id] = []
course_sessions_by_course[cs.course.id].append(cs)
return course_sessions_by_course
def has_preview(role_key: RoleKeyType) -> bool:
return role_key in [RoleKeyType.MENTOR_UK, RoleKeyType.MENTOR_VV]
def get_newest_cs(
course_sessions: List[CourseSessionWithRoles],
) -> CourseSessionWithRoles | None:
newest: CourseSessionWithRoles | None = None
for cs in course_sessions:
generation_newest = newest.generation if newest else None
if generation_newest is None or cs.generation > generation_newest:
newest = cs
return newest
def get_course_config(
course_sessions: List[CourseSessionWithRoles],
) -> List[CourseConfig]:
course_configs = []
cs_by_course = sort_course_sessions_by_course(course_sessions)
for _id, cs_in_course in cs_by_course.items():
is_uk = cs_in_course[0].course.configuration.is_uk
is_vv = cs_in_course[0].course.configuration.is_vv
role_key, is_mentor = get_role_and_mentor_key(cs_in_course, is_uk, is_vv)
session_to_continue = get_newest_cs(cs_in_course)
course_configs.append(
CourseConfig(
course_id=cs_in_course[0].course.id,
course_slug=cs_in_course[0].course.slug,
course_title=cs_in_course[0].course.title,
role_key=role_key.value,
is_uk=is_uk,
is_vv=is_vv,
is_mentor=is_mentor,
widgets=get_widgets_for_course(role_key, is_uk, is_vv, is_mentor),
has_preview=has_preview(role_key),
session_to_continue_id=str(session_to_continue.id)
if session_to_continue
else None,
)
)
return course_configs
@api_view(["GET"])
def get_dashboard_config(request):
try:
course_sessions = get_course_sessions_with_roles_for_user(request.user) # noqa
course_configs = get_course_config(course_sessions)
return Response(
status=200,
data=[asdict(cc) for cc in course_configs],
)
except PermissionDenied as e:
raise e
except Exception as e:
logger.error(e, exc_info=True)
return Response({"error": str(e)}, status=404)