VBV-673: Praxisbildner Übersicht KN

This commit is contained in:
Daniel Egger 2024-04-26 17:01:25 +02:00
parent 45be3ae296
commit f4be4e2418
10 changed files with 223 additions and 84 deletions

View File

@ -157,6 +157,7 @@ function hasActionButton(): boolean {
<MentorOpenTasksCount <MentorOpenTasksCount
v-if="hasWidget('MentorTasksWidget')" v-if="hasWidget('MentorTasksWidget')"
:course-id="courseConfig.course_id" :course-id="courseConfig.course_id"
:course-slug="courseSlug"
/> />
<MentorMenteeCount <MentorMenteeCount
v-if="hasWidget('MentorPersonWidget')" v-if="hasWidget('MentorPersonWidget')"

View File

@ -6,6 +6,7 @@ import BaseBox from "@/components/dashboard/BaseBox.vue";
const props = defineProps<{ const props = defineProps<{
courseId: string; courseId: string;
courseSlug: string;
}>(); }>();
const openTaskCount: Ref<number> = ref(0); const openTaskCount: Ref<number> = ref(0);
@ -18,7 +19,10 @@ onMounted(async () => {
<template> <template>
<div class="w-[325px]"> <div class="w-[325px]">
<BaseBox :details-link="'foo'" data-cy="dashboard.mentor.competenceSummary"> <BaseBox
:details-link="`/course/${props.courseSlug}/learning-mentor/tasks`"
data-cy="dashboard.mentor.competenceSummary"
>
<template #title>{{ $t("Zu erledigen") }}</template> <template #title>{{ $t("Zu erledigen") }}</template>
<template #content> <template #content>
<div class="flex flex-row space-x-3 bg-white pb-6"> <div class="flex flex-row space-x-3 bg-white pb-6">

View File

@ -37,6 +37,7 @@ import type {
CourseCompletionStatus, CourseCompletionStatus,
CourseSession, CourseSession,
CourseSessionDetail, CourseSessionDetail,
DashboardPersonsPageMode,
LearningContentWithCompletion, LearningContentWithCompletion,
LearningMentor, LearningMentor,
LearningPathType, LearningPathType,
@ -542,7 +543,7 @@ export function getUkRoleDisplay(role: DashboardPersonRoleType) {
} }
} }
export function useDashboardPersons() { export function useDashboardPersons(mode: DashboardPersonsPageMode = "default") {
const dashboardPersons = ref<DashboardPersonType[]>([]); const dashboardPersons = ref<DashboardPersonType[]>([]);
const dashboardDueDates = ref<DashboardDueDate[]>([]); const dashboardDueDates = ref<DashboardDueDate[]>([]);
const loading = ref(false); const loading = ref(false);
@ -554,7 +555,7 @@ export function useDashboardPersons() {
loading.value = true; loading.value = true;
try { try {
const [persons, dueDates] = await Promise.all([ const [persons, dueDates] = await Promise.all([
fetchDashboardPersons(), fetchDashboardPersons(mode),
fetchDashboardDueDates(), fetchDashboardDueDates(),
]); ]);
dashboardPersons.value = persons; dashboardPersons.value = persons;

View File

@ -8,9 +8,18 @@ import { useTranslation } from "i18next-vue";
import _ from "lodash"; import _ from "lodash";
import type { DashboardPersonCourseSessionType } from "@/services/dashboard"; import type { DashboardPersonCourseSessionType } from "@/services/dashboard";
import { useRouteQuery } from "@vueuse/router"; import { useRouteQuery } from "@vueuse/router";
import type { DashboardPersonsPageMode } from "@/types";
log.debug("DashboardPersonsPage created"); log.debug("DashboardPersonsPage created");
export interface Props {
mode?: DashboardPersonsPageMode;
}
const props = withDefaults(defineProps<Props>(), {
mode: "default",
});
const UNFILTERED = Number.MAX_SAFE_INTEGER.toString(); const UNFILTERED = Number.MAX_SAFE_INTEGER.toString();
type MenuItem = { type MenuItem = {
@ -20,7 +29,7 @@ type MenuItem = {
const { t } = useTranslation(); const { t } = useTranslation();
const { loading, dashboardPersons } = useDashboardPersons(); const { loading, dashboardPersons } = useDashboardPersons(props.mode);
const courses = computed(() => { const courses = computed(() => {
return [ return [
@ -323,15 +332,33 @@ watch(selectedRegion, () => {
class="w-full border-b pb-2 pt-2 first:pt-0 last:border-b-0 last:pb-0" class="w-full border-b pb-2 pt-2 first:pt-0 last:border-b-0 last:pb-0"
> >
<div class="flex flex-col md:flex-row md:items-center"> <div class="flex flex-col md:flex-row md:items-center">
<div class="md:w-1/2"> <div v-if="props.mode === 'default'" class="md:w-1/2">
<div class="text-gray-900">{{ cs.course_title }}</div> <div class="text-gray-900">{{ cs.course_title }}</div>
<div v-if="cs.is_uk">{{ cs.session_title }}</div> <div v-if="cs.is_uk">{{ cs.session_title }}</div>
</div> </div>
<div class="md:w-1/4"> <div v-if="props.mode === 'competenceMetrics'" class="md:w-1/3">
<!-- <div>{{ cs.user_role }}</div>--> <div
<!-- <div>my role: {{ cs.my_role }}</div>--> v-if="cs.competence_metrics?.passed_count || 0 > 0"
class="my-2 w-fit rounded-md bg-green-200 px-2.5 py-0.5"
>
{{ $t("a.Bestanden") }}:
{{ cs.competence_metrics?.passed_count }}
</div>
</div>
<div v-if="props.mode === 'default'" class="md:w-1/4">
{{ personRoleDisplayValue(cs) }} {{ personRoleDisplayValue(cs) }}
</div> </div>
<div v-else-if="props.mode === 'competenceMetrics'" class="md:w-1/3">
<div
v-if="cs.competence_metrics?.failed_count || 0 > 0"
class="my-2 w-fit rounded-md bg-error-red-200 px-2.5 py-0.5"
>
{{ $t("a.Nicht Bestanden") }}:
{{ cs.competence_metrics?.failed_count }}
</div>
</div>
<div class="md:w-1/4 md:text-right"> <div class="md:w-1/4 md:text-right">
<div <div
v-if=" v-if="

View File

@ -64,6 +64,11 @@ const router = createRouter({
path: "/dashboard/persons", path: "/dashboard/persons",
component: () => import("@/pages/dashboard/DashboardPersonsPage.vue"), component: () => import("@/pages/dashboard/DashboardPersonsPage.vue"),
}, },
{
path: "/dashboard/persons-competence",
component: () => import("@/pages/dashboard/DashboardPersonsPage.vue"),
props: { mode: "competenceMetrics" },
},
{ {
path: "/dashboard/due-dates", path: "/dashboard/due-dates",
component: () => import("@/pages/dashboard/DashboardDueDatesPage.vue"), component: () => import("@/pages/dashboard/DashboardDueDatesPage.vue"),

View File

@ -13,7 +13,7 @@ import type {
CourseStatisticsType, CourseStatisticsType,
DashboardConfigType, DashboardConfigType,
} from "@/gql/graphql"; } from "@/gql/graphql";
import type { DueDate } from "@/types"; import type { DashboardPersonsPageMode, DueDate } from "@/types";
export type DashboardPersonRoleType = export type DashboardPersonRoleType =
| "SUPERVISOR" | "SUPERVISOR"
@ -52,6 +52,10 @@ export type DashboardPersonCourseSessionType = {
my_role_display: string; my_role_display: string;
is_uk: boolean; is_uk: boolean;
is_vv: boolean; is_vv: boolean;
competence_metrics?: {
passed_count: number;
failed_count: number;
};
}; };
export type DashboardPersonType = { export type DashboardPersonType = {
@ -62,6 +66,10 @@ export type DashboardPersonType = {
course_sessions: DashboardPersonCourseSessionType[]; course_sessions: DashboardPersonCourseSessionType[];
avatar_url: string; avatar_url: string;
avatar_url_small: string; avatar_url_small: string;
competence_metrics?: {
passed_count: number;
failed_count: number;
};
}; };
export type DashboardCourseConfigType = { export type DashboardCourseConfigType = {
@ -153,8 +161,12 @@ export const fetchMentorCompetenceSummary = async (
} }
}; };
export async function fetchDashboardPersons() { export async function fetchDashboardPersons(mode: DashboardPersonsPageMode) {
return await itGetCached<DashboardPersonType[]>("/api/dashboard/persons/"); let url = "/api/dashboard/persons/";
if (mode === "competenceMetrics") {
url += "?with_competence_metrics=true";
}
return await itGetCached<DashboardPersonType[]>(url);
} }
export async function fetchDashboardDueDates() { export async function fetchDashboardDueDates() {

View File

@ -610,3 +610,5 @@ export type User = {
course_session_experts: any[]; course_session_experts: any[];
language: string; language: string;
}; };
export type DashboardPersonsPageMode = "default" | "competenceMetrics";

View File

@ -0,0 +1,42 @@
from vbv_lernwelt.assignment.models import AssignmentType
from vbv_lernwelt.course_session.models import (
CourseSessionAssignment,
CourseSessionEdoniqTest,
)
def query_competence_course_session_assignments(course_session_ids, circle_ids=None):
if circle_ids is None:
circle_ids = []
result = []
for csa in CourseSessionAssignment.objects.filter(
course_session_id__in=course_session_ids,
learning_content__content_assignment__assignment_type__in=[
AssignmentType.CASEWORK.value,
],
learning_content__content_assignment__competence_certificate__isnull=False,
):
if circle_ids and csa.learning_content.get_circle().id not in circle_ids:
continue
result.append(csa)
return result
def query_competence_course_session_edoniq_tests(course_session_ids, circle_ids=None):
if circle_ids is None:
circle_ids = []
result = []
for cset in CourseSessionEdoniqTest.objects.filter(
course_session_id__in=course_session_ids,
learning_content__content_assignment__competence_certificate__isnull=False,
):
if circle_ids and cset.learning_content.get_circle().id not in circle_ids:
continue
result.append(cset)
return result

View File

@ -9,6 +9,10 @@ from vbv_lernwelt.assignment.models import (
AssignmentCompletionStatus, AssignmentCompletionStatus,
AssignmentType, AssignmentType,
) )
from vbv_lernwelt.competence.services import (
query_competence_course_session_assignments,
query_competence_course_session_edoniq_tests,
)
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.course_session.models import ( from vbv_lernwelt.course_session.models import (
CourseSessionAssignment, CourseSessionAssignment,
@ -169,26 +173,15 @@ def assignments(
records: List[AssignmentStatisticsRecordType] = [] records: List[AssignmentStatisticsRecordType] = []
for course_session in course_sessions: for course_session in course_sessions:
for csa in CourseSessionAssignment.objects.filter( for csa in query_competence_course_session_assignments(
course_session=course_session, [course_session.id], circle_ids
learning_content__content_assignment__assignment_type__in=[
AssignmentType.CASEWORK.value,
],
learning_content__content_assignment__competence_certificate__isnull=False,
): ):
if circle_ids and csa.learning_content.get_circle().id not in circle_ids: record = create_record(csa, user_selection_ids)
continue
record = create_record(
course_session_assignment=csa, user_selection_ids=user_selection_ids
)
records.append(record) records.append(record)
for cset in CourseSessionEdoniqTest.objects.filter( for cset in query_competence_course_session_edoniq_tests(
course_session=course_session, [course_session.id], circle_ids
learning_content__content_assignment__competence_certificate__isnull=False,
): ):
if circle_ids and cset.learning_content.get_circle().id not in circle_ids:
continue
record = create_record( record = create_record(
course_session_assignment=cset, user_selection_ids=user_selection_ids course_session_assignment=cset, user_selection_ids=user_selection_ids
) )

View File

@ -11,6 +11,10 @@ from vbv_lernwelt.assignment.models import (
AssignmentCompletion, AssignmentCompletion,
AssignmentCompletionStatus, AssignmentCompletionStatus,
) )
from vbv_lernwelt.competence.services import (
query_competence_course_session_assignments,
query_competence_course_session_edoniq_tests,
)
from vbv_lernwelt.core.models import User from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import ( from vbv_lernwelt.course.models import (
CourseConfiguration, CourseConfiguration,
@ -142,8 +146,7 @@ def _create_course_session_dict(course_session_object, my_role, user_role):
} }
@api_view(["GET"]) def _create_person_list_with_roles(user):
def get_dashboard_persons(request):
def create_user_dict(user_object): def create_user_dict(user_object):
return { return {
"user_id": user_object.id, "user_id": user_object.id,
@ -154,72 +157,121 @@ def get_dashboard_persons(request):
"avatar_url": user_object.avatar_url, "avatar_url": user_object.avatar_url,
} }
try: course_sessions = get_course_sessions_with_roles_for_user(user)
course_sessions = get_course_sessions_with_roles_for_user(request.user)
result_persons = {} result_persons = {}
for cs in course_sessions: for cs in course_sessions:
if has_cs_role(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_users = CourseSessionUser.objects.filter(
course_session=cs.id course_session=cs.id
)
my_role = user_role(cs.roles)
for csu in course_session_users:
person_data = create_user_dict(csu.user)
person_data["course_sessions"] = [
_create_course_session_dict(cs, my_role, csu.role)
]
result_persons[csu.user.id] = person_data
# add persons where request.user is mentor
for cs in course_sessions:
if "LEARNING_MENTOR" in cs.roles:
lm = LearningMentor.objects.filter(
mentor=user, course_session=cs.id
).first()
for participant in lm.participants.all():
course_session_entry = _create_course_session_dict(
cs,
"LEARNING_MENTOR",
"LEARNING_MENTEE",
) )
my_role = user_role(cs.roles)
for csu in course_session_users:
person_data = create_user_dict(csu.user)
person_data["course_sessions"] = [
_create_course_session_dict(cs, my_role, csu.role)
]
result_persons[csu.user.id] = person_data
# add persons where request.user is mentor if participant.user.id not in result_persons:
for cs in course_sessions: person_data = create_user_dict(participant.user)
if "LEARNING_MENTOR" in cs.roles: person_data["course_sessions"] = [course_session_entry]
lm = LearningMentor.objects.filter( result_persons[participant.user.id] = person_data
mentor=request.user, course_session=cs.id else:
).first() # user is already in result_persons
result_persons[participant.user.id]["course_sessions"].append(
for participant in lm.participants.all(): course_session_entry
course_session_entry = _create_course_session_dict(
cs,
"LEARNING_MENTOR",
"LEARNING_MENTEE",
) )
if participant.user.id not in result_persons: # add persons where request.user is mentee
person_data = create_user_dict(participant.user) mentor_relation_qs = LearningMentor.objects.filter(
person_data["course_sessions"] = [course_session_entry] participants__user=user
result_persons[participant.user.id] = person_data ).prefetch_related("mentor", "course_session")
else: for mentor_relation in mentor_relation_qs:
# user is already in result_persons cs = mentor_relation.course_session
result_persons[participant.user.id]["course_sessions"].append( course_session_entry = _create_course_session_dict(
course_session_entry cs,
) "LEARNING_MENTEE",
"LEARNING_MENTOR",
)
# add persons where request.user is mentee if mentor_relation.mentor.id not in result_persons:
mentor_relation_qs = LearningMentor.objects.filter( person_data = create_user_dict(mentor_relation.mentor)
participants__user=request.user person_data["course_sessions"] = [course_session_entry]
).prefetch_related("mentor", "course_session") result_persons[mentor_relation.mentor.id] = person_data
for mentor_relation in mentor_relation_qs: else:
cs = mentor_relation.course_session # user is already in result_persons
course_session_entry = _create_course_session_dict( result_persons[mentor_relation.mentor.id]["course_sessions"].append(
cs, course_session_entry
"LEARNING_MENTEE",
"LEARNING_MENTOR",
) )
if mentor_relation.mentor.id not in result_persons: return result_persons.values()
person_data = create_user_dict(mentor_relation.mentor)
person_data["course_sessions"] = [course_session_entry]
result_persons[mentor_relation.mentor.id] = person_data def _persons_list_add_competence_metrics(persons):
else: course_session_ids = {cs["id"] for p in persons for cs in p["course_sessions"]}
# user is already in result_persons competence_assignments = query_competence_course_session_assignments(
result_persons[mentor_relation.mentor.id]["course_sessions"].append( course_session_ids
course_session_entry ) + query_competence_course_session_edoniq_tests(course_session_ids)
) assignment_ids = {
a.learning_content.content_assignment.id for a in competence_assignments
}
for p in persons:
passed_count = 0
failed_count = 0
for cs in p["course_sessions"]:
evaluation_results = AssignmentCompletion.objects.filter(
completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value,
assignment_user_id=p.get("user_id"),
course_session=cs.get("id"),
assignment_id__in=assignment_ids,
)
cs_passed_count = len(
[ac for ac in evaluation_results if ac.evaluation_passed]
)
cs_failed_count = len(evaluation_results) - cs_passed_count
cs["competence_metrics"] = {
"passed_count": cs_passed_count,
"failed_count": cs_failed_count,
}
passed_count += len(
[ac for ac in evaluation_results if ac.evaluation_passed]
)
failed_count += len(evaluation_results) - passed_count
p["competence_metrics"] = {
"passed_count": passed_count,
"failed_count": failed_count,
}
return persons
@api_view(["GET"])
def get_dashboard_persons(request):
try:
persons = list(_create_person_list_with_roles(request.user))
if request.GET.get("with_competence_metrics", "") == "true":
persons = _persons_list_add_competence_metrics(persons)
return Response( return Response(
status=200, status=200,
data=list(result_persons.values()), data=list(persons),
) )
except PermissionDenied as e: except PermissionDenied as e:
raise e raise e