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
v-if="hasWidget('MentorTasksWidget')"
:course-id="courseConfig.course_id"
:course-slug="courseSlug"
/>
<MentorMenteeCount
v-if="hasWidget('MentorPersonWidget')"

View File

@ -6,6 +6,7 @@ import BaseBox from "@/components/dashboard/BaseBox.vue";
const props = defineProps<{
courseId: string;
courseSlug: string;
}>();
const openTaskCount: Ref<number> = ref(0);
@ -18,7 +19,10 @@ onMounted(async () => {
<template>
<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 #content>
<div class="flex flex-row space-x-3 bg-white pb-6">

View File

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

View File

@ -8,9 +8,18 @@ import { useTranslation } from "i18next-vue";
import _ from "lodash";
import type { DashboardPersonCourseSessionType } from "@/services/dashboard";
import { useRouteQuery } from "@vueuse/router";
import type { DashboardPersonsPageMode } from "@/types";
log.debug("DashboardPersonsPage created");
export interface Props {
mode?: DashboardPersonsPageMode;
}
const props = withDefaults(defineProps<Props>(), {
mode: "default",
});
const UNFILTERED = Number.MAX_SAFE_INTEGER.toString();
type MenuItem = {
@ -20,7 +29,7 @@ type MenuItem = {
const { t } = useTranslation();
const { loading, dashboardPersons } = useDashboardPersons();
const { loading, dashboardPersons } = useDashboardPersons(props.mode);
const courses = computed(() => {
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"
>
<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 v-if="cs.is_uk">{{ cs.session_title }}</div>
</div>
<div class="md:w-1/4">
<!-- <div>{{ cs.user_role }}</div>-->
<!-- <div>my role: {{ cs.my_role }}</div>-->
<div v-if="props.mode === 'competenceMetrics'" class="md:w-1/3">
<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) }}
</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
v-if="

View File

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

View File

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

View File

@ -610,3 +610,5 @@ export type User = {
course_session_experts: any[];
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,
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_session.models import (
CourseSessionAssignment,
@ -169,26 +173,15 @@ def assignments(
records: List[AssignmentStatisticsRecordType] = []
for course_session in course_sessions:
for csa in CourseSessionAssignment.objects.filter(
course_session=course_session,
learning_content__content_assignment__assignment_type__in=[
AssignmentType.CASEWORK.value,
],
learning_content__content_assignment__competence_certificate__isnull=False,
for csa in query_competence_course_session_assignments(
[course_session.id], circle_ids
):
if circle_ids and csa.learning_content.get_circle().id not in circle_ids:
continue
record = create_record(
course_session_assignment=csa, user_selection_ids=user_selection_ids
)
record = create_record(csa, user_selection_ids)
records.append(record)
for cset in CourseSessionEdoniqTest.objects.filter(
course_session=course_session,
learning_content__content_assignment__competence_certificate__isnull=False,
for cset in query_competence_course_session_edoniq_tests(
[course_session.id], circle_ids
):
if circle_ids and cset.learning_content.get_circle().id not in circle_ids:
continue
record = create_record(
course_session_assignment=cset, user_selection_ids=user_selection_ids
)

View File

@ -11,6 +11,10 @@ from vbv_lernwelt.assignment.models import (
AssignmentCompletion,
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.course.models import (
CourseConfiguration,
@ -142,8 +146,7 @@ def _create_course_session_dict(course_session_object, my_role, user_role):
}
@api_view(["GET"])
def get_dashboard_persons(request):
def _create_person_list_with_roles(user):
def create_user_dict(user_object):
return {
"user_id": user_object.id,
@ -154,8 +157,7 @@ def get_dashboard_persons(request):
"avatar_url": user_object.avatar_url,
}
try:
course_sessions = get_course_sessions_with_roles_for_user(request.user)
course_sessions = get_course_sessions_with_roles_for_user(user)
result_persons = {}
for cs in course_sessions:
@ -175,7 +177,7 @@ def get_dashboard_persons(request):
for cs in course_sessions:
if "LEARNING_MENTOR" in cs.roles:
lm = LearningMentor.objects.filter(
mentor=request.user, course_session=cs.id
mentor=user, course_session=cs.id
).first()
for participant in lm.participants.all():
@ -197,7 +199,7 @@ def get_dashboard_persons(request):
# add persons where request.user is mentee
mentor_relation_qs = LearningMentor.objects.filter(
participants__user=request.user
participants__user=user
).prefetch_related("mentor", "course_session")
for mentor_relation in mentor_relation_qs:
cs = mentor_relation.course_session
@ -217,9 +219,59 @@ def get_dashboard_persons(request):
course_session_entry
)
return result_persons.values()
def _persons_list_add_competence_metrics(persons):
course_session_ids = {cs["id"] for p in persons for cs in p["course_sessions"]}
competence_assignments = query_competence_course_session_assignments(
course_session_ids
) + 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(
status=200,
data=list(result_persons.values()),
data=list(persons),
)
except PermissionDenied as e:
raise e