Merged in feature/vbv-676-berufsbildner-2 (pull request #365)
Feature/vbv 676 berufsbildner 2
This commit is contained in:
commit
96dfa8bd31
|
|
@ -16,6 +16,7 @@ const props = defineProps<{
|
||||||
courseSession: CourseSession;
|
courseSession: CourseSession;
|
||||||
learningContent: LearningContentAssignment | LearningContentEdoniqTest;
|
learningContent: LearningContentAssignment | LearningContentEdoniqTest;
|
||||||
showTitle: boolean;
|
showTitle: boolean;
|
||||||
|
userSelectionIds?: string[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
log.debug("AssignmentSubmissionProgress created", stringifyParse(props));
|
log.debug("AssignmentSubmissionProgress created", stringifyParse(props));
|
||||||
|
|
@ -31,13 +32,19 @@ const state = reactive({
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const { assignmentSubmittedUsers, gradedUsers, total } =
|
// eslint-disable-next-line prefer-const
|
||||||
|
let { assignmentSubmittedUsers, gradedUsers, total } =
|
||||||
await loadAssignmentCompletionStatusData(
|
await loadAssignmentCompletionStatusData(
|
||||||
props.learningContent.content_assignment.id,
|
props.learningContent.content_assignment.id,
|
||||||
props.courseSession.id,
|
props.courseSession.id,
|
||||||
props.learningContent.id
|
props.learningContent.id,
|
||||||
|
props.userSelectionIds
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (props.userSelectionIds && props.userSelectionIds.length > 0) {
|
||||||
|
total = props.userSelectionIds.length;
|
||||||
|
}
|
||||||
|
|
||||||
state.submissionProgressStatusCount = {
|
state.submissionProgressStatusCount = {
|
||||||
SUCCESS: assignmentSubmittedUsers.length,
|
SUCCESS: assignmentSubmittedUsers.length,
|
||||||
UNKNOWN: total - assignmentSubmittedUsers.length,
|
UNKNOWN: total - assignmentSubmittedUsers.length,
|
||||||
|
|
@ -55,6 +62,9 @@ const doneCount = (status: StatusCount) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const totalCount = (status: StatusCount) => {
|
const totalCount = (status: StatusCount) => {
|
||||||
|
if (props.userSelectionIds && props.userSelectionIds.length > 0) {
|
||||||
|
return props.userSelectionIds.length;
|
||||||
|
}
|
||||||
return doneCount(status) + status.UNKNOWN || 0;
|
return doneCount(status) + status.UNKNOWN || 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,27 @@ import { onMounted, ref } from "vue";
|
||||||
import { fetchMenteeCount } from "@/services/dashboard";
|
import { fetchMenteeCount } from "@/services/dashboard";
|
||||||
import BaseBox from "@/components/dashboard/BaseBox.vue";
|
import BaseBox from "@/components/dashboard/BaseBox.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
courseId: string;
|
courseId: string;
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
}>();
|
slim?: boolean;
|
||||||
|
count?: number;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
count: -1,
|
||||||
|
slim: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const menteeCount: Ref<number> = ref(0);
|
const menteeCount: Ref<number> = ref(props.count);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
if (props.count == -1) {
|
||||||
|
menteeCount.value = 0;
|
||||||
const data = await fetchMenteeCount(props.courseId);
|
const data = await fetchMenteeCount(props.courseId);
|
||||||
menteeCount.value = data?.mentee_count;
|
menteeCount.value = data?.mentee_count;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -21,11 +32,12 @@ onMounted(async () => {
|
||||||
<div class="w-[325px]">
|
<div class="w-[325px]">
|
||||||
<BaseBox
|
<BaseBox
|
||||||
:details-link="`/dashboard/persons?course=${props.courseId}`"
|
:details-link="`/dashboard/persons?course=${props.courseId}`"
|
||||||
data-cy="dashboard.mentor.competenceSummary"
|
data-cy="dashboard.mentor.menteeCount"
|
||||||
|
:slim="props.slim"
|
||||||
>
|
>
|
||||||
<template #title>{{ $t("a.Personen") }}</template>
|
<template #title>{{ $t("a.Personen") }}</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', slim ? '' : 'pb-6']">
|
||||||
<div
|
<div
|
||||||
class="flex h-[74px] items-center justify-center py-1 pr-3 text-3xl font-bold"
|
class="flex h-[74px] items-center justify-center py-1 pr-3 text-3xl font-bold"
|
||||||
>
|
>
|
||||||
|
|
@ -7,6 +7,7 @@ const props = defineProps<{
|
||||||
assignmentsCompleted: number;
|
assignmentsCompleted: number;
|
||||||
avgPassed: number;
|
avgPassed: number;
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
|
detailsLink?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const progress = computed(() => {
|
const progress = computed(() => {
|
||||||
|
|
@ -20,7 +21,7 @@ const progress = computed(() => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BaseBox
|
<BaseBox
|
||||||
:details-link="`/statistic/${courseSlug}/assignment`"
|
:details-link="props.detailsLink || `/statistic/${courseSlug}/assignment`"
|
||||||
data-cy="dashboard.stats.assignments"
|
data-cy="dashboard.stats.assignments"
|
||||||
>
|
>
|
||||||
<template #title>{{ $t("a.Kompetenznachweis-Elemente") }}</template>
|
<template #title>{{ $t("a.Kompetenznachweis-Elemente") }}</template>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,23 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
detailsLink: string;
|
detailsLink: string;
|
||||||
}>();
|
slim?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
slim: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-full flex-col space-y-4 bg-white">
|
<div :class="['flex h-full flex-col bg-white', slim ? '' : 'space-y-4']">
|
||||||
<h4 class="mb-1 font-bold">
|
<h4 class="mb-1 font-bold">
|
||||||
<slot name="title"></slot>
|
<slot name="title"></slot>
|
||||||
</h4>
|
</h4>
|
||||||
<slot name="content"></slot>
|
<slot name="content"></slot>
|
||||||
<div class="flex-grow"></div>
|
<div class="flex-grow"></div>
|
||||||
<div class="pt-8">
|
<div class="pt-0">
|
||||||
<router-link class="underline" :to="detailsLink" data-cy="basebox.detailsLink">
|
<router-link class="underline" :to="detailsLink" data-cy="basebox.detailsLink">
|
||||||
{{ $t("a.Details anschauen") }}
|
{{ $t("a.Details anschauen") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
import {
|
||||||
|
type DashboardRoleKeyType,
|
||||||
|
fetchMentorCompetenceSummary,
|
||||||
|
} from "@/services/dashboard";
|
||||||
|
import type { BaseStatisticsType } from "@/gql/graphql";
|
||||||
|
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||||
|
import AssignmentSummaryBox from "@/components/dashboard/AssignmentSummaryBox.vue";
|
||||||
|
import BaseBox from "@/components/dashboard/BaseBox.vue";
|
||||||
|
import { percentToRoundedGrade } from "@/services/assignmentService";
|
||||||
|
import AgentConnectionCount from "@/components/dashboard/AgentConnectionCount.vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
courseSlug: string;
|
||||||
|
courseId: string;
|
||||||
|
agentRole: DashboardRoleKeyType;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const mentorData = ref<BaseStatisticsType | null>(null);
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
|
const assignmentStats = computed(() => {
|
||||||
|
return mentorData.value?.assignments ?? null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const numberOfPerson = computed(() => {
|
||||||
|
return mentorData.value?.user_selection_ids?.length ?? 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
mentorData.value = await fetchMentorCompetenceSummary(
|
||||||
|
props.courseId,
|
||||||
|
props.agentRole
|
||||||
|
);
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const averageGrade = computed(() => {
|
||||||
|
return percentToRoundedGrade(
|
||||||
|
assignmentStats.value?.summary.average_evaluation_percent ?? 0,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="loading" class="m-8 flex justify-center">
|
||||||
|
<LoadingSpinner />
|
||||||
|
</div>
|
||||||
|
<div v-if="assignmentStats" class="w-full space-y-8">
|
||||||
|
<div
|
||||||
|
class="flex flex-col flex-wrap justify-between gap-x-5 border-b border-gray-300 pb-8 last:border-0 md:flex-row"
|
||||||
|
>
|
||||||
|
<div class="flex-1">
|
||||||
|
<BaseBox
|
||||||
|
:details-link="`/statistic/berufsbildner/${props.courseSlug}/competence-grade`"
|
||||||
|
data-cy="dashboard.stats.competenceGrades"
|
||||||
|
>
|
||||||
|
<template #title>{{ $t("a.Kompetenznachweise") }}</template>
|
||||||
|
<template #content>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div class="min-w-12 text-center">
|
||||||
|
<div
|
||||||
|
class="heading-2 rounded bg-green-500 p-4"
|
||||||
|
:class="{ 'bg-red-400': averageGrade < 4 }"
|
||||||
|
>
|
||||||
|
{{ averageGrade }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ $t("a.Durchschnittsnote") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</BaseBox>
|
||||||
|
</div>
|
||||||
|
<AssignmentSummaryBox
|
||||||
|
class="flex-1"
|
||||||
|
:assignments-completed="assignmentStats.summary.completed_count"
|
||||||
|
:avg-passed="assignmentStats.summary.average_passed"
|
||||||
|
:course-slug="props.courseSlug"
|
||||||
|
:details-link="`/statistic/berufsbildner/${props.courseSlug}/assignment`"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<AgentConnectionCount
|
||||||
|
:course-id="props.courseId"
|
||||||
|
:course-slug="props.courseSlug"
|
||||||
|
:count="numberOfPerson"
|
||||||
|
:slim="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -5,10 +5,11 @@ import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.v
|
||||||
import CompetenceSummary from "@/components/dashboard/CompetenceSummary.vue";
|
import CompetenceSummary from "@/components/dashboard/CompetenceSummary.vue";
|
||||||
import AssignmentSummary from "@/components/dashboard/AssignmentSummary.vue";
|
import AssignmentSummary from "@/components/dashboard/AssignmentSummary.vue";
|
||||||
import MentorOpenTasksCount from "@/components/dashboard/MentorOpenTasksCount.vue";
|
import MentorOpenTasksCount from "@/components/dashboard/MentorOpenTasksCount.vue";
|
||||||
import MentorMenteeCount from "@/components/dashboard/MentorMenteeCount.vue";
|
import AgentConnectionCount from "@/components/dashboard/AgentConnectionCount.vue";
|
||||||
import MentorCompetenceSummary from "@/components/dashboard/MentorCompetenceSummary.vue";
|
import MentorCompetenceSummary from "@/components/dashboard/MentorCompetenceSummary.vue";
|
||||||
import { getCockpitUrl, getLearningMentorUrl, getLearningPathUrl } from "@/utils/utils";
|
import { getCockpitUrl, getLearningMentorUrl, getLearningPathUrl } from "@/utils/utils";
|
||||||
import UkStatistics from "@/components/dashboard/UkStatistics.vue";
|
import UkStatistics from "@/components/dashboard/UkStatistics.vue";
|
||||||
|
import BerufsbildnerStatistics from "@/components/dashboard/BerufsbildnerStatistics.vue";
|
||||||
|
|
||||||
const mentorWidgets = [
|
const mentorWidgets = [
|
||||||
"MentorTasksWidget",
|
"MentorTasksWidget",
|
||||||
|
|
@ -63,6 +64,13 @@ const actionButtonProps = computed<{ href: string; text: string; cyKey: string }
|
||||||
cyKey: "lm-dashboard-link",
|
cyKey: "lm-dashboard-link",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (props.courseConfig?.role_key === "Berufsbildner") {
|
||||||
|
return {
|
||||||
|
href: getLearningPathUrl(props.courseConfig?.course_slug),
|
||||||
|
text: "a.Vorschau Teilnehmer",
|
||||||
|
cyKey: "progress-dashboard-continue-course-link",
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
href: getLearningPathUrl(props.courseConfig?.course_slug),
|
href: getLearningPathUrl(props.courseConfig?.course_slug),
|
||||||
text: "Weiter lernen",
|
text: "Weiter lernen",
|
||||||
|
|
@ -77,7 +85,11 @@ function hasActionButton(): boolean {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="courseConfig" class="mb-14 space-y-8">
|
<div
|
||||||
|
v-if="courseConfig"
|
||||||
|
class="mb-14 space-y-8"
|
||||||
|
:data-cy="`panel-${courseConfig.course_slug}`"
|
||||||
|
>
|
||||||
<div class="flex flex-col space-y-8 bg-white p-6">
|
<div class="flex flex-col space-y-8 bg-white p-6">
|
||||||
<div class="border-b border-gray-300 pb-8">
|
<div class="border-b border-gray-300 pb-8">
|
||||||
<div class="flex flex-row items-start justify-between">
|
<div class="flex flex-row items-start justify-between">
|
||||||
|
|
@ -102,12 +114,13 @@ function hasActionButton(): boolean {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span>{{ $t("a.VorschauTeilnehmer") }}</span>
|
<span>{{ $t("a.Vorschau Teilnehmer") }}</span>
|
||||||
<it-icon-external-link class="ml-1 !h-4 !w-4" />
|
<it-icon-external-link class="ml-1 !h-4 !w-4" />
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
hasWidget('ProgressWidget') &&
|
hasWidget('ProgressWidget') &&
|
||||||
|
|
@ -123,6 +136,7 @@ function hasActionButton(): boolean {
|
||||||
diagram-type="horizontal"
|
diagram-type="horizontal"
|
||||||
></LearningPathDiagram>
|
></LearningPathDiagram>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="numberOfProgressWidgets"
|
v-if="numberOfProgressWidgets"
|
||||||
class="flex flex-col flex-wrap gap-x-[60px] border-b border-gray-300 pb-8 last:border-0 md:flex-row"
|
class="flex flex-col flex-wrap gap-x-[60px] border-b border-gray-300 pb-8 last:border-0 md:flex-row"
|
||||||
|
|
@ -140,17 +154,30 @@ function hasActionButton(): boolean {
|
||||||
:course-id="courseConfig.course_id"
|
:course-id="courseConfig.course_id"
|
||||||
></CompetenceSummary>
|
></CompetenceSummary>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="hasWidget('UKStatisticsWidget')"
|
v-if="hasWidget('UKStatisticsWidget')"
|
||||||
class="flex flex-col flex-wrap gap-x-[60px] border-b border-gray-300 pb-8 last:border-0 md:flex-row"
|
class="flex flex-col flex-wrap gap-x-[60px] border-b border-gray-300 pb-8 last:border-0 md:flex-row"
|
||||||
>
|
>
|
||||||
<UkStatistics :course-slug="courseSlug" :course-id="courseConfig.course_id" />
|
<UkStatistics :course-slug="courseSlug" :course-id="courseConfig.course_id" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="hasWidget('UKBerufsbildnerStatisticsWidget')"
|
||||||
|
class="flex flex-col flex-wrap gap-x-[60px] border-b border-gray-300 pb-8 last:border-0 md:flex-row"
|
||||||
|
>
|
||||||
|
<BerufsbildnerStatistics
|
||||||
|
:course-slug="courseSlug"
|
||||||
|
:course-id="courseConfig.course_id"
|
||||||
|
:agent-role="courseConfig.role_key"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="numberOfMentorWidgets > 0"
|
v-if="numberOfMentorWidgets > 0"
|
||||||
class="flex flex-col flex-wrap items-stretch md:flex-row"
|
class="flex flex-col flex-wrap items-stretch md:flex-row"
|
||||||
>
|
>
|
||||||
<MentorMenteeCount
|
<AgentConnectionCount
|
||||||
v-if="hasWidget('MentorPersonWidget')"
|
v-if="hasWidget('MentorPersonWidget')"
|
||||||
:course-id="courseConfig.course_id"
|
:course-id="courseConfig.course_id"
|
||||||
:course-slug="courseConfig?.course_slug"
|
:course-slug="courseConfig?.course_slug"
|
||||||
|
|
@ -163,6 +190,7 @@ function hasActionButton(): boolean {
|
||||||
<MentorCompetenceSummary
|
<MentorCompetenceSummary
|
||||||
v-if="hasWidget('MentorCompetenceWidget')"
|
v-if="hasWidget('MentorCompetenceWidget')"
|
||||||
:course-id="courseConfig.course_id"
|
:course-id="courseConfig.course_id"
|
||||||
|
:agent-role="courseConfig.role_key"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,28 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Ref } from "vue";
|
import { computed, onMounted, ref } from "vue";
|
||||||
import { onMounted, ref } from "vue";
|
import {
|
||||||
import { fetchMentorCompetenceSummary } from "@/services/dashboard";
|
type DashboardRoleKeyType,
|
||||||
import type { AssignmentsStatisticsType } from "@/gql/graphql";
|
fetchMentorCompetenceSummary,
|
||||||
|
} from "@/services/dashboard";
|
||||||
|
import type { BaseStatisticsType } from "@/gql/graphql";
|
||||||
import BaseBox from "@/components/dashboard/BaseBox.vue";
|
import BaseBox from "@/components/dashboard/BaseBox.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseId: string;
|
courseId: string;
|
||||||
|
agentRole: DashboardRoleKeyType;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const summary: Ref<AssignmentsStatisticsType | null> = ref(null);
|
const mentorAssignmentData = ref<BaseStatisticsType | null>(null);
|
||||||
|
|
||||||
|
const summary = computed(() => {
|
||||||
|
return mentorAssignmentData.value?.assignments ?? null;
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
summary.value = await fetchMentorCompetenceSummary(props.courseId);
|
mentorAssignmentData.value = await fetchMentorCompetenceSummary(
|
||||||
console.log(summary.value);
|
props.courseId,
|
||||||
|
props.agentRole
|
||||||
|
);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ onMounted(async () => {
|
||||||
<div class="w-[325px]">
|
<div class="w-[325px]">
|
||||||
<BaseBox
|
<BaseBox
|
||||||
:details-link="`/course/${props.courseSlug}/learning-mentor/tasks`"
|
:details-link="`/course/${props.courseSlug}/learning-mentor/tasks`"
|
||||||
data-cy="dashboard.mentor.competenceSummary"
|
data-cy="dashboard.mentor.openTasksCount"
|
||||||
>
|
>
|
||||||
<template #title>{{ $t("Zu erledigen") }}</template>
|
<template #title>{{ $t("Zu erledigen") }}</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
|
|
|
||||||
|
|
@ -4,27 +4,53 @@ import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import type { StatisticsCourseSessionPropertiesType } from "@/gql/graphql";
|
import type { StatisticsCourseSessionPropertiesType } from "@/gql/graphql";
|
||||||
import type { StatisticsFilterItem } from "@/types";
|
import type { StatisticsFilterItem } from "@/types";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
items: StatisticsFilterItem[];
|
items: StatisticsFilterItem[];
|
||||||
courseSessionProperties: StatisticsCourseSessionPropertiesType;
|
courseSessionProperties: StatisticsCourseSessionPropertiesType;
|
||||||
|
hideCircleFilter?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
defineExpose({ getFilteredItems });
|
defineExpose({ getFilteredItems });
|
||||||
|
|
||||||
|
const regionFilter = computed(() => {
|
||||||
|
const regions = _.uniq(
|
||||||
|
props.courseSessionProperties.sessions.map((session) => session.region)
|
||||||
|
);
|
||||||
|
const f = regions.map((region) => ({
|
||||||
|
name: `Region: ${region}`,
|
||||||
|
id: region,
|
||||||
|
}));
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: `${t("Region")}: ${t("a.Alle")}`,
|
||||||
|
id: "_all",
|
||||||
|
},
|
||||||
|
...f,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
const sessionFilter = computed(() => {
|
const sessionFilter = computed(() => {
|
||||||
const f = props.courseSessionProperties.sessions.map((session) => ({
|
let values = props.courseSessionProperties.sessions.map((session) => ({
|
||||||
name: `${t("a.Durchfuehrung")}: ${session.name}`,
|
name: session.name,
|
||||||
|
region: session.region,
|
||||||
id: session.id,
|
id: session.id,
|
||||||
}));
|
}));
|
||||||
return [{ name: t("a.AlleDurchführungen"), id: "_all" }, ...f];
|
|
||||||
|
// filter by selected region
|
||||||
|
if (regionFilterValue.value.id !== "_all") {
|
||||||
|
values = values.filter((cs) => cs.region === regionFilterValue.value.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{ name: t("a.AlleDurchführungen"), id: "_all" }, ...values];
|
||||||
});
|
});
|
||||||
|
|
||||||
const generationFilter = computed(() => {
|
const generationFilter = computed(() => {
|
||||||
const f = props.courseSessionProperties.generations.map((generation) => ({
|
const f = props.courseSessionProperties.generations.map((generation) => ({
|
||||||
name: `${t("a.Generation")}: ${generation}`,
|
name: generation,
|
||||||
id: generation,
|
id: generation,
|
||||||
}));
|
}));
|
||||||
return [{ name: t("a.AlleGenerationen"), id: "_all" }, ...f];
|
return [{ name: t("a.AlleGenerationen"), id: "_all" }, ...f];
|
||||||
|
|
@ -32,12 +58,13 @@ const generationFilter = computed(() => {
|
||||||
|
|
||||||
const circleFilter = computed(() => {
|
const circleFilter = computed(() => {
|
||||||
const f = props.courseSessionProperties.circles.map((circle) => ({
|
const f = props.courseSessionProperties.circles.map((circle) => ({
|
||||||
name: `Circle: ${circle.name}`,
|
name: circle.name,
|
||||||
id: circle.id,
|
id: circle.id,
|
||||||
}));
|
}));
|
||||||
return [{ name: t("a.AlleCircle"), id: "_all" }, ...f];
|
return [{ name: t("a.AlleCircle"), id: "_all" }, ...f];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const regionFilterValue = ref(regionFilter.value[0]);
|
||||||
const sessionFilterValue = ref(sessionFilter.value[0]);
|
const sessionFilterValue = ref(sessionFilter.value[0]);
|
||||||
const generationFilterValue = ref(generationFilter.value[0]);
|
const generationFilterValue = ref(generationFilter.value[0]);
|
||||||
const circleFilterValue = ref(circleFilter.value[0]);
|
const circleFilterValue = ref(circleFilter.value[0]);
|
||||||
|
|
@ -48,12 +75,21 @@ watch(
|
||||||
sessionFilterValue.value = sessionFilter.value[0];
|
sessionFilterValue.value = sessionFilter.value[0];
|
||||||
generationFilterValue.value = generationFilter.value[0];
|
generationFilterValue.value = generationFilter.value[0];
|
||||||
circleFilterValue.value = circleFilter.value[0];
|
circleFilterValue.value = circleFilter.value[0];
|
||||||
|
regionFilterValue.value = regionFilter.value[0];
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(regionFilterValue, () => {
|
||||||
|
console.log("regionFilterValue", regionFilterValue.value);
|
||||||
|
sessionFilterValue.value = sessionFilter.value[0];
|
||||||
|
});
|
||||||
|
|
||||||
const filteredItems = computed(() => {
|
const filteredItems = computed(() => {
|
||||||
return props.items.filter((item) => {
|
return props.items.filter((item) => {
|
||||||
|
const regionMatch =
|
||||||
|
regionFilterValue.value.id === "_all" ||
|
||||||
|
item.region === regionFilterValue.value.id;
|
||||||
const sessionMatch =
|
const sessionMatch =
|
||||||
sessionFilterValue.value.id === "_all" ||
|
sessionFilterValue.value.id === "_all" ||
|
||||||
item.course_session_id === sessionFilterValue.value.id;
|
item.course_session_id === sessionFilterValue.value.id;
|
||||||
|
|
@ -64,7 +100,7 @@ const filteredItems = computed(() => {
|
||||||
circleFilterValue.value.id === "_all" ||
|
circleFilterValue.value.id === "_all" ||
|
||||||
item.circle_id === circleFilterValue.value.id;
|
item.circle_id === circleFilterValue.value.id;
|
||||||
|
|
||||||
return sessionMatch && generationMatch && circleMatch;
|
return regionMatch && sessionMatch && generationMatch && circleMatch;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -75,30 +111,45 @@ function getFilteredItems() {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex flex-col space-x-2 lg:flex-row">
|
<div class="flex flex-col space-x-2 border-b lg:flex-row">
|
||||||
|
<ItDropdownSelect
|
||||||
|
v-if="regionFilter.length > 2"
|
||||||
|
v-model="regionFilterValue"
|
||||||
|
class="min-w-[12rem]"
|
||||||
|
:items="regionFilter"
|
||||||
|
borderless
|
||||||
|
></ItDropdownSelect>
|
||||||
<ItDropdownSelect
|
<ItDropdownSelect
|
||||||
v-model="sessionFilterValue"
|
v-model="sessionFilterValue"
|
||||||
class="min-w-[18rem]"
|
class="min-w-[12rem]"
|
||||||
:items="sessionFilter"
|
:items="sessionFilter"
|
||||||
borderless
|
borderless
|
||||||
></ItDropdownSelect>
|
></ItDropdownSelect>
|
||||||
<ItDropdownSelect
|
<ItDropdownSelect
|
||||||
v-model="generationFilterValue"
|
v-model="generationFilterValue"
|
||||||
class="min-w-[18rem]"
|
class="min-w-[12rem]"
|
||||||
:items="generationFilter"
|
:items="generationFilter"
|
||||||
borderless
|
borderless
|
||||||
></ItDropdownSelect>
|
></ItDropdownSelect>
|
||||||
<ItDropdownSelect
|
<ItDropdownSelect
|
||||||
|
v-if="!props.hideCircleFilter"
|
||||||
v-model="circleFilterValue"
|
v-model="circleFilterValue"
|
||||||
class="min-w-[18rem]"
|
class="min-w-[12rem]"
|
||||||
:items="circleFilter"
|
:items="circleFilter"
|
||||||
borderless
|
borderless
|
||||||
></ItDropdownSelect>
|
></ItDropdownSelect>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="item in filteredItems" :key="item._id" class="px-5">
|
<slot name="header"></slot>
|
||||||
<div class="border-t border-gray-500 py-4">
|
<section>
|
||||||
|
<div
|
||||||
|
v-for="item in filteredItems"
|
||||||
|
:key="item._id"
|
||||||
|
class="mx-6 border-t border-gray-500 first:border-t-0"
|
||||||
|
>
|
||||||
|
<div class="py-4">
|
||||||
<slot :item="item"></slot>
|
<slot :item="item"></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ const feebackSummary = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
statistics.value = await dashboardStore.loadStatisticsDatav2(props.courseId);
|
statistics.value = await dashboardStore.loadStatisticsData(props.courseId);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,12 @@ async function navigate(routeName: string) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="mt-6 flex items-center" @click="emit('logout')">
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mt-6 flex items-center"
|
||||||
|
data-cy="logout-button"
|
||||||
|
@click="emit('logout')"
|
||||||
|
>
|
||||||
<it-icon-logout class="inline-block" />
|
<it-icon-logout class="inline-block" />
|
||||||
<span class="ml-1">{{ $t("mainNavigation.logout") }}</span>
|
<span class="ml-1">{{ $t("mainNavigation.logout") }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ const { t } = useTranslation();
|
||||||
class="relative flex h-16 w-full flex-col items-center justify-end space-x-8 lg:flex-row lg:items-stretch lg:justify-center"
|
class="relative flex h-16 w-full flex-col items-center justify-end space-x-8 lg:flex-row lg:items-stretch lg:justify-center"
|
||||||
>
|
>
|
||||||
<span class="flex items-center px-1 pt-1 font-bold text-black">
|
<span class="flex items-center px-1 pt-1 font-bold text-black">
|
||||||
{{ t("a.VorschauTeilnehmer") }} ({{ courseSession.title }})
|
{{ t("a.Vorschau Teilnehmer") }} ({{ courseSession.title }})
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="flex space-x-8">
|
<div class="flex space-x-8">
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,8 @@ const hasPreviewMenu = computed(() =>
|
||||||
const hasAppointmentsMenu = computed(() =>
|
const hasAppointmentsMenu = computed(() =>
|
||||||
Boolean(
|
Boolean(
|
||||||
courseSessionsStore.currentCourseSession?.actions.includes("appointments") &&
|
courseSessionsStore.currentCourseSession?.actions.includes("appointments") &&
|
||||||
userStore.loggedIn
|
userStore.loggedIn &&
|
||||||
|
inCourse()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -130,6 +131,10 @@ const mentorTabTitle = computed(() =>
|
||||||
? "a.Praxisbildner"
|
? "a.Praxisbildner"
|
||||||
: "a.Lernbegleitung"
|
: "a.Lernbegleitung"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const hasSessionTitle = computed(() => {
|
||||||
|
return courseSessionsStore.currentCourseSession?.title && inCourse();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -231,7 +236,7 @@ const mentorTabTitle = computed(() =>
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span>{{ t("a.VorschauTeilnehmer") }}</span>
|
<span>{{ t("a.Vorschau Teilnehmer") }}</span>
|
||||||
<it-icon-external-link class="ml-2" />
|
<it-icon-external-link class="ml-2" />
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
@ -334,7 +339,7 @@ const mentorTabTitle = computed(() =>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="selectedCourseSessionTitle"
|
v-if="hasSessionTitle"
|
||||||
class="nav-item hidden items-center lg:inline-flex"
|
class="nav-item hidden items-center lg:inline-flex"
|
||||||
>
|
>
|
||||||
<div class="" data-cy="current-course-session-title">
|
<div class="" data-cy="current-course-session-title">
|
||||||
|
|
@ -343,7 +348,11 @@ const mentorTabTitle = computed(() =>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav-item">
|
<div class="nav-item">
|
||||||
<div v-if="userStore.loggedIn" class="flex items-center">
|
<div
|
||||||
|
v-if="userStore.loggedIn"
|
||||||
|
class="flex items-center"
|
||||||
|
data-cy="header-profile"
|
||||||
|
>
|
||||||
<Popover class="relative">
|
<Popover class="relative">
|
||||||
<PopoverButton @click="popoverClick($event)">
|
<PopoverButton @click="popoverClick($event)">
|
||||||
<div v-if="userStore.avatar_url">
|
<div v-if="userStore.avatar_url">
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ const mentorTabTitle = computed(() =>
|
||||||
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.Vorschau Teilnehmer") }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="hasLearningPathMenu" class="mb-6">
|
<li v-if="hasLearningPathMenu" class="mb-6">
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const { isLoading, summary, fetchData } = useLearningMentees(courseSession.value.id);
|
const { isLoading, summary, fetchData } = useLearningMentees(courseSession.value.id);
|
||||||
|
|
||||||
const removeMyMentee = async (menteeId: string) => {
|
const removeMyMentee = async (relationId: string) => {
|
||||||
await useCSRFFetch(
|
await useCSRFFetch(
|
||||||
`/api/mentor/${courseSession.value.id}/mentors/${summary.value?.mentor_id}/remove/${menteeId}`
|
`/api/mentor/${courseSession.value.id}/mentors/${relationId}/delete`
|
||||||
).delete();
|
).delete();
|
||||||
fetchData();
|
fetchData();
|
||||||
};
|
};
|
||||||
|
|
@ -28,25 +28,31 @@ const noMenteesText = computed(() =>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<h2 class="heading-2 py-6">{{ $t("a.Personen, die du begleitest") }}</h2>
|
<h2 class="heading-2 py-6">{{ $t("a.Personen, die du begleitest") }}</h2>
|
||||||
<div v-if="(summary?.participants?.length ?? 0) > 0" class="bg-white px-4 py-2">
|
|
||||||
<div
|
<div
|
||||||
v-for="participant in summary?.participants ?? []"
|
v-if="(summary?.participant_relations.length ?? 0) > 0"
|
||||||
:key="participant.id"
|
class="bg-white px-4 py-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="relation in summary?.participant_relations ?? []"
|
||||||
|
:key="relation.id"
|
||||||
data-cy="lm-my-mentee-list-item"
|
data-cy="lm-my-mentee-list-item"
|
||||||
class="flex flex-col items-start justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:items-center md:gap-16"
|
class="flex flex-col items-start justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:items-center md:gap-16"
|
||||||
>
|
>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<img
|
<img
|
||||||
:alt="participant.last_name"
|
:alt="relation.participant_user.last_name"
|
||||||
class="h-11 w-11 rounded-full"
|
class="h-11 w-11 rounded-full"
|
||||||
:src="participant.avatar_url || '/static/avatars/myvbv-default-avatar.png'"
|
:src="
|
||||||
|
relation.participant_user.avatar_url ||
|
||||||
|
'/static/avatars/myvbv-default-avatar.png'
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-bold">
|
<div class="text-bold">
|
||||||
{{ participant.first_name }}
|
{{ relation.participant_user.first_name }}
|
||||||
{{ participant.last_name }}
|
{{ relation.participant_user.last_name }}
|
||||||
</div>
|
</div>
|
||||||
{{ participant.email }}
|
{{ relation.participant_user.email }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-x-5">
|
<div class="space-x-5">
|
||||||
|
|
@ -55,7 +61,7 @@ const noMenteesText = computed(() =>
|
||||||
:to="{
|
:to="{
|
||||||
name: 'profileLearningPath',
|
name: 'profileLearningPath',
|
||||||
params: {
|
params: {
|
||||||
userId: participant.id,
|
userId: relation.participant_user.id,
|
||||||
courseSlug: courseSession.course.slug,
|
courseSlug: courseSession.course.slug,
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
|
|
@ -66,7 +72,7 @@ const noMenteesText = computed(() =>
|
||||||
<button
|
<button
|
||||||
class="underline"
|
class="underline"
|
||||||
data-cy="lm-my-mentee-remove"
|
data-cy="lm-my-mentee-remove"
|
||||||
@click="removeMyMentee(participant.id)"
|
@click="removeMyMentee(relation.id)"
|
||||||
>
|
>
|
||||||
{{ $t("a.Entfernen") }}
|
{{ $t("a.Entfernen") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -51,11 +51,9 @@ const removeInvitation = async (invitationId: string) => {
|
||||||
await refreshInvitations();
|
await refreshInvitations();
|
||||||
};
|
};
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const removeMyMentor = async (relationId: string) => {
|
||||||
|
|
||||||
const removeMyMentor = async (mentorId: string) => {
|
|
||||||
await useCSRFFetch(
|
await useCSRFFetch(
|
||||||
`/api/mentor/${courseSession.value.id}/mentors/${mentorId}/remove/${userStore.id}`
|
`/api/mentor/${courseSession.value.id}/mentors/${relationId}/delete`
|
||||||
).delete();
|
).delete();
|
||||||
await refreshMentors();
|
await refreshMentors();
|
||||||
};
|
};
|
||||||
|
|
@ -104,12 +102,13 @@ const noLearningMentors = computed(() =>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-white px-4 py-2">
|
<div class="bg-white px-4 py-2">
|
||||||
<main>
|
<main data-cy="my-mentors-list">
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
v-for="invitation in invitations"
|
v-for="invitation in invitations"
|
||||||
:key="invitation.id"
|
:key="invitation.id"
|
||||||
class="flex flex-col justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:gap-16"
|
class="flex flex-col justify-between gap-4 border-b py-2 last:border-b-0 md:flex-row md:gap-16"
|
||||||
|
:data-cy="`mentor-${invitation.email}`"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col md:flex-grow md:flex-row">
|
<div class="flex flex-col md:flex-grow md:flex-row">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
|
|
@ -137,16 +136,16 @@ const noLearningMentors = computed(() =>
|
||||||
>
|
>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<img
|
<img
|
||||||
:alt="learningMentor.mentor.last_name"
|
:alt="learningMentor.agent.last_name"
|
||||||
class="h-11 w-11 rounded-full"
|
class="h-11 w-11 rounded-full"
|
||||||
:src="learningMentor.mentor.avatar_url"
|
:src="learningMentor.agent.avatar_url"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-bold">
|
<div class="text-bold">
|
||||||
{{ learningMentor.mentor.first_name }}
|
{{ learningMentor.agent.first_name }}
|
||||||
{{ learningMentor.mentor.last_name }}
|
{{ learningMentor.agent.last_name }}
|
||||||
</div>
|
</div>
|
||||||
{{ learningMentor.mentor.email }}
|
{{ learningMentor.agent.email }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|
@ -178,6 +177,7 @@ const noLearningMentors = computed(() =>
|
||||||
<button
|
<button
|
||||||
:disabled="!validEmail"
|
:disabled="!validEmail"
|
||||||
class="btn-primary mt-8"
|
class="btn-primary mt-8"
|
||||||
|
data-cy="invite-mentor-button"
|
||||||
@click="inviteMentor()"
|
@click="inviteMentor()"
|
||||||
>
|
>
|
||||||
{{ $t("a.Einladung abschicken") }}
|
{{ $t("a.Einladung abschicken") }}
|
||||||
|
|
|
||||||
|
|
@ -74,9 +74,9 @@ const onSubmit = async () => {
|
||||||
<option
|
<option
|
||||||
v-for="learningMentor in learningMentors"
|
v-for="learningMentor in learningMentors"
|
||||||
:key="learningMentor.id"
|
:key="learningMentor.id"
|
||||||
:value="learningMentor.mentor.id"
|
:value="learningMentor.agent.id"
|
||||||
>
|
>
|
||||||
{{ learningMentor.mentor.first_name }} {{ learningMentor.mentor.last_name }}
|
{{ learningMentor.agent.first_name }} {{ learningMentor.agent.last_name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ const onFailed = () => {
|
||||||
<button
|
<button
|
||||||
class="inline-flex flex-1 items-center border-2 p-4 text-left"
|
class="inline-flex flex-1 items-center border-2 p-4 text-left"
|
||||||
:class="currentEvaluation === 'SUCCESS' ? 'border-green-500' : 'border-gray-300'"
|
:class="currentEvaluation === 'SUCCESS' ? 'border-green-500' : 'border-gray-300'"
|
||||||
|
data-cy="success"
|
||||||
@click="onPassed"
|
@click="onPassed"
|
||||||
>
|
>
|
||||||
<it-icon-smiley-happy class="mr-4 h-16 w-16"></it-icon-smiley-happy>
|
<it-icon-smiley-happy class="mr-4 h-16 w-16"></it-icon-smiley-happy>
|
||||||
|
|
@ -54,6 +55,7 @@ const onFailed = () => {
|
||||||
<button
|
<button
|
||||||
class="inline-flex flex-1 items-center border-2 p-4 text-left"
|
class="inline-flex flex-1 items-center border-2 p-4 text-left"
|
||||||
:class="currentEvaluation === 'FAIL' ? 'border-orange-500' : 'border-gray-300'"
|
:class="currentEvaluation === 'FAIL' ? 'border-orange-500' : 'border-gray-300'"
|
||||||
|
data-cy="fail"
|
||||||
@click="onFailed"
|
@click="onFailed"
|
||||||
>
|
>
|
||||||
<it-icon-smiley-thinking class="mr-4 h-16 w-16"></it-icon-smiley-thinking>
|
<it-icon-smiley-thinking class="mr-4 h-16 w-16"></it-icon-smiley-thinking>
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,12 @@ const emit = defineEmits(["release"]);
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<ItButton variant="primary" size="large" @click="emit('release')">
|
<ItButton
|
||||||
|
variant="primary"
|
||||||
|
size="large"
|
||||||
|
data-cy="feedback-release-button"
|
||||||
|
@click="emit('release')"
|
||||||
|
>
|
||||||
{{ $t("a.Fremdeinschätzung freigeben") }}
|
{{ $t("a.Fremdeinschätzung freigeben") }}
|
||||||
</ItButton>
|
</ItButton>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export interface Props {
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
withDefaults(defineProps<Props>(), {
|
||||||
label: undefined,
|
label: undefined,
|
||||||
cyKey: "",
|
cyKey: "default",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
});
|
});
|
||||||
const emit = defineEmits(["update:modelValue"]);
|
const emit = defineEmits(["update:modelValue"]);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { useCSRFFetch } from "@/fetchHelpers";
|
||||||
import type { CourseStatisticsType } from "@/gql/graphql";
|
import type { CourseStatisticsType } from "@/gql/graphql";
|
||||||
import { graphqlClient } from "@/graphql/client";
|
import { graphqlClient } from "@/graphql/client";
|
||||||
import {
|
import {
|
||||||
COMPETENCE_NAVI_CERTIFICATE_FOR_USER_QUERY,
|
|
||||||
COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
||||||
COURSE_QUERY,
|
COURSE_QUERY,
|
||||||
COURSE_SESSION_DETAIL_QUERY,
|
COURSE_SESSION_DETAIL_QUERY,
|
||||||
|
|
@ -31,6 +30,7 @@ import { useDashboardStore } from "@/stores/dashboard";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import type {
|
import type {
|
||||||
ActionCompetence,
|
ActionCompetence,
|
||||||
|
AgentParticipantRelation,
|
||||||
CircleType,
|
CircleType,
|
||||||
CompetenceCertificate,
|
CompetenceCertificate,
|
||||||
Course,
|
Course,
|
||||||
|
|
@ -40,7 +40,6 @@ import type {
|
||||||
CourseSessionDetail,
|
CourseSessionDetail,
|
||||||
DashboardPersonsPageMode,
|
DashboardPersonsPageMode,
|
||||||
LearningContentWithCompletion,
|
LearningContentWithCompletion,
|
||||||
LearningMentor,
|
|
||||||
LearningPathType,
|
LearningPathType,
|
||||||
LearningUnitPerformanceCriteria,
|
LearningUnitPerformanceCriteria,
|
||||||
PerformanceCriteria,
|
PerformanceCriteria,
|
||||||
|
|
@ -52,8 +51,7 @@ import orderBy from "lodash/orderBy";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import type { ComputedRef, Ref } from "vue";
|
import type { ComputedRef, Ref } from "vue";
|
||||||
import { computed, onMounted, ref, watchEffect } from "vue";
|
import { computed, onMounted, ref, watchEffect } from "vue";
|
||||||
import { useRouter, type RouteLocationRaw } from "vue-router";
|
import { type RouteLocationRaw, useRouter } from "vue-router";
|
||||||
import { getCertificates } from "./services/competence";
|
|
||||||
import { mergeCompetenceCertificates } from "./pages/competence/utils";
|
import { mergeCompetenceCertificates } from "./pages/competence/utils";
|
||||||
|
|
||||||
export function useCurrentCourseSession() {
|
export function useCurrentCourseSession() {
|
||||||
|
|
@ -81,14 +79,14 @@ export function useCurrentCourseSession() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCourseSessionDetailQuery(courSessionId?: string) {
|
export function useCourseSessionDetailQuery(courseSessionId?: string) {
|
||||||
if (!courSessionId) {
|
if (!courseSessionId) {
|
||||||
courSessionId = useCurrentCourseSession().value.id;
|
courseSessionId = useCurrentCourseSession().value.id;
|
||||||
}
|
}
|
||||||
const queryResult = useQuery({
|
const queryResult = useQuery({
|
||||||
query: COURSE_SESSION_DETAIL_QUERY,
|
query: COURSE_SESSION_DETAIL_QUERY,
|
||||||
variables: {
|
variables: {
|
||||||
courseSessionId: courSessionId,
|
courseSessionId: courseSessionId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -132,8 +130,11 @@ export function useCourseSessionDetailQuery(courSessionId?: string) {
|
||||||
return findUser(userId);
|
return findUser(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterMembers() {
|
function filterMembers(userSelectionIds: string[] | null = null) {
|
||||||
return (courseSessionDetail.value?.users ?? []).filter((u) => {
|
return (courseSessionDetail.value?.users ?? []).filter((u) => {
|
||||||
|
if (userSelectionIds && userSelectionIds.length > 0) {
|
||||||
|
return userSelectionIds.includes(u.user_id) && u.role === "MEMBER";
|
||||||
|
}
|
||||||
return u.role === "MEMBER";
|
return u.role === "MEMBER";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -442,28 +443,6 @@ export function useCourseDataWithCompletion(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCourseStatistics() {
|
|
||||||
const dashboardStore = useDashboardStore();
|
|
||||||
|
|
||||||
const statistics = computed(() => {
|
|
||||||
return dashboardStore.currentDashBoardData as CourseStatisticsType;
|
|
||||||
});
|
|
||||||
|
|
||||||
const courseSessionName = (courseSessionId: string) => {
|
|
||||||
return statistics.value.course_session_properties.sessions.find(
|
|
||||||
(session) => session.id === courseSessionId
|
|
||||||
)?.name;
|
|
||||||
};
|
|
||||||
|
|
||||||
const circleMeta = (circleId: string) => {
|
|
||||||
return statistics.value.course_session_properties.circles.find(
|
|
||||||
(circle) => circle.id === circleId
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return { courseSessionName, circleMeta };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useFileUpload() {
|
export function useFileUpload() {
|
||||||
const error = ref(false);
|
const error = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
@ -492,7 +471,7 @@ export function useFileUpload() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMyLearningMentors() {
|
export function useMyLearningMentors() {
|
||||||
const learningMentors = ref<LearningMentor[]>([]);
|
const learningMentors = ref<AgentParticipantRelation[]>([]);
|
||||||
const currentCourseSessionId = useCurrentCourseSession().value.id;
|
const currentCourseSessionId = useCurrentCourseSession().value.id;
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
|
|
@ -517,7 +496,7 @@ export function getVvRoleDisplay(role: DashboardPersonRoleType) {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case "LEARNING_MENTOR":
|
case "LEARNING_MENTOR":
|
||||||
return t("a.Lernbegleitung");
|
return t("a.Lernbegleitung");
|
||||||
case "LEARNING_MENTEE":
|
case "PARTICIPANT":
|
||||||
return t("a.Teilnehmer");
|
return t("a.Teilnehmer");
|
||||||
case "EXPERT":
|
case "EXPERT":
|
||||||
return t("a.Experte");
|
return t("a.Experte");
|
||||||
|
|
@ -534,7 +513,7 @@ export function getUkRoleDisplay(role: DashboardPersonRoleType) {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case "LEARNING_MENTOR":
|
case "LEARNING_MENTOR":
|
||||||
return t("a.Praxisbildner");
|
return t("a.Praxisbildner");
|
||||||
case "LEARNING_MENTEE":
|
case "PARTICIPANT":
|
||||||
return t("a.Teilnehmer");
|
return t("a.Teilnehmer");
|
||||||
case "EXPERT":
|
case "EXPERT":
|
||||||
return t("a.Trainer");
|
return t("a.Trainer");
|
||||||
|
|
@ -601,7 +580,7 @@ export function useDashboardPersonsDueDates(
|
||||||
return refDate >= dayjs().startOf("day");
|
return refDate >= dayjs().startOf("day");
|
||||||
});
|
});
|
||||||
|
|
||||||
// attach `LEARNING_MENTEE` to due dates for `LEARNING_MENTOR` persons
|
// attach `PARTICIPANT` to due dates for `LEARNING_MENTOR` persons
|
||||||
currentDueDates.value.forEach((dueDate) => {
|
currentDueDates.value.forEach((dueDate) => {
|
||||||
if (dueDate.course_session.my_role === "LEARNING_MENTOR") {
|
if (dueDate.course_session.my_role === "LEARNING_MENTOR") {
|
||||||
dueDate.persons = dashboardPersons.value.filter((person) => {
|
dueDate.persons = dashboardPersons.value.filter((person) => {
|
||||||
|
|
@ -611,7 +590,7 @@ export function useDashboardPersonsDueDates(
|
||||||
.includes(dueDate.course_session.id)
|
.includes(dueDate.course_session.id)
|
||||||
) {
|
) {
|
||||||
return person.course_sessions.some(
|
return person.course_sessions.some(
|
||||||
(cs) => cs.user_role === "LEARNING_MENTEE"
|
(cs) => cs.user_role === "PARTICIPANT"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -697,29 +676,19 @@ export function useCourseStatisticsv2(courseSlug: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCertificateQuery(
|
export function useCertificateQuery(
|
||||||
userId: string | undefined,
|
userIds: string[],
|
||||||
courseSlug: string,
|
courseSlug: string,
|
||||||
courseSession: CourseSession
|
courseSession: CourseSession
|
||||||
) {
|
) {
|
||||||
const certificatesQuery = (() => {
|
const certificatesQuery = (() => {
|
||||||
if (userId) {
|
|
||||||
return useQuery({
|
|
||||||
query: COMPETENCE_NAVI_CERTIFICATE_FOR_USER_QUERY,
|
|
||||||
variables: {
|
|
||||||
courseSlug: courseSlug,
|
|
||||||
courseSessionId: courseSession.id,
|
|
||||||
userId: userId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
||||||
variables: {
|
variables: {
|
||||||
courseSlug: courseSlug,
|
courseSlug: courseSlug,
|
||||||
courseSessionId: courseSession.id,
|
courseSessionId: courseSession.id,
|
||||||
|
userIds,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return { certificatesQuery };
|
return { certificatesQuery };
|
||||||
|
|
@ -750,21 +719,20 @@ export function useVVByLink() {
|
||||||
return { href };
|
return { href };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAllCompetenceCertificates(
|
export function useAllCompetenceCertificates(userId: string, courseSlug: string) {
|
||||||
userId: string | undefined,
|
|
||||||
courseSlug: string
|
|
||||||
) {
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
const certificateQueries = courseSessionsStore.allCourseSessions.map(
|
const certificateQueries = courseSessionsStore.allCourseSessions.map(
|
||||||
(courseSession) => {
|
(courseSession) => {
|
||||||
return useCertificateQuery(userId, courseSlug, courseSession).certificatesQuery;
|
return useCertificateQuery([userId], courseSlug, courseSession).certificatesQuery;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const competenceCertificatesPerCs = computed(() =>
|
const competenceCertificatesPerCs = computed(() =>
|
||||||
certificateQueries.map((query) => {
|
certificateQueries.map((query) => {
|
||||||
return getCertificates(query.data.value, userId ?? null)
|
return (
|
||||||
?.competence_certificates as unknown as CompetenceCertificate[];
|
(query.data.value?.competence_certificate_list
|
||||||
|
?.competence_certificates as unknown as CompetenceCertificate[]) ?? []
|
||||||
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const isLoaded = computed(() => !certificateQueries.some((q) => q.fetching.value));
|
const isLoaded = computed(() => !certificateQueries.some((q) => q.fetching.value));
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,6 @@
|
||||||
type Query {
|
type Query {
|
||||||
course_statistics(course_id: ID!): CourseStatisticsType
|
course_statistics(course_id: ID!): CourseStatisticsType
|
||||||
mentor_course_statistics(course_id: ID!): BaseStatisticsType
|
mentor_course_statistics(course_id: ID!, agent_role: String!): BaseStatisticsType
|
||||||
course_progress(course_id: ID!): CourseProgressType
|
course_progress(course_id: ID!): CourseProgressType
|
||||||
dashboard_config: [DashboardConfigType!]!
|
dashboard_config: [DashboardConfigType!]!
|
||||||
learning_path(id: ID, slug: String, course_id: ID, course_slug: String): LearningPathObjectType
|
learning_path(id: ID, slug: String, course_id: ID, course_slug: String): LearningPathObjectType
|
||||||
|
|
@ -20,8 +20,7 @@ type Query {
|
||||||
learning_content_video: LearningContentVideoObjectType
|
learning_content_video: LearningContentVideoObjectType
|
||||||
learning_content_document_list: LearningContentDocumentListObjectType
|
learning_content_document_list: LearningContentDocumentListObjectType
|
||||||
competence_certificate(id: ID, slug: String): CompetenceCertificateObjectType
|
competence_certificate(id: ID, slug: String): CompetenceCertificateObjectType
|
||||||
competence_certificate_list(id: ID, slug: String, course_id: ID, course_slug: String): CompetenceCertificateListObjectType
|
competence_certificate_list(id: ID, slug: String, course_id: ID, course_slug: String, user_ids: [UUID]): CompetenceCertificateListObjectType
|
||||||
competence_certificate_list_for_user(id: ID, slug: String, course_id: ID, course_slug: String, user_id: UUID): CompetenceCertificateListObjectType
|
|
||||||
assignment(id: ID, slug: String): AssignmentObjectType
|
assignment(id: ID, slug: String): AssignmentObjectType
|
||||||
assignment_completion(assignment_id: ID!, course_session_id: ID!, learning_content_page_id: ID, assignment_user_id: UUID): AssignmentCompletionObjectType
|
assignment_completion(assignment_id: ID!, course_session_id: ID!, learning_content_page_id: ID, assignment_user_id: UUID): AssignmentCompletionObjectType
|
||||||
}
|
}
|
||||||
|
|
@ -53,10 +52,15 @@ type AssignmentStatisticsRecordType {
|
||||||
course_session_assignment_id: ID!
|
course_session_assignment_id: ID!
|
||||||
circle_id: ID!
|
circle_id: ID!
|
||||||
generation: String!
|
generation: String!
|
||||||
|
region: String!
|
||||||
assignment_type_translation_key: String!
|
assignment_type_translation_key: String!
|
||||||
assignment_title: String!
|
assignment_title: String!
|
||||||
deadline: DateTime!
|
deadline: DateTime!
|
||||||
|
course_session_title: String
|
||||||
|
competence_certificate_id: ID
|
||||||
|
competence_certificate_title: String
|
||||||
metrics: AssignmentCompletionMetricsType!
|
metrics: AssignmentCompletionMetricsType!
|
||||||
|
learning_content_id: ID!
|
||||||
details_url: String!
|
details_url: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,6 +78,8 @@ type AssignmentCompletionMetricsType {
|
||||||
unranked_count: Int!
|
unranked_count: Int!
|
||||||
ranking_completed: Boolean!
|
ranking_completed: Boolean!
|
||||||
average_passed: Float!
|
average_passed: Float!
|
||||||
|
average_evaluation_percent: Float
|
||||||
|
competence_certificate_weight: Float
|
||||||
}
|
}
|
||||||
|
|
||||||
type AssignmentStatisticsSummaryType {
|
type AssignmentStatisticsSummaryType {
|
||||||
|
|
@ -82,6 +88,7 @@ type AssignmentStatisticsSummaryType {
|
||||||
average_passed: Float!
|
average_passed: Float!
|
||||||
total_passed: Int!
|
total_passed: Int!
|
||||||
total_failed: Int!
|
total_failed: Int!
|
||||||
|
average_evaluation_percent: Float
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatisticsCourseSessionPropertiesType {
|
type StatisticsCourseSessionPropertiesType {
|
||||||
|
|
@ -94,6 +101,7 @@ type StatisticsCourseSessionPropertiesType {
|
||||||
type StatisticsCourseSessionDataType {
|
type StatisticsCourseSessionDataType {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
|
region: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatisticsCircleDataType {
|
type StatisticsCircleDataType {
|
||||||
|
|
@ -118,6 +126,7 @@ type PresenceRecordStatisticsType {
|
||||||
_id: ID!
|
_id: ID!
|
||||||
course_session_id: ID!
|
course_session_id: ID!
|
||||||
generation: String!
|
generation: String!
|
||||||
|
region: String!
|
||||||
circle_id: ID!
|
circle_id: ID!
|
||||||
due_date: DateTime!
|
due_date: DateTime!
|
||||||
participants_present: Int!
|
participants_present: Int!
|
||||||
|
|
@ -141,6 +150,7 @@ type FeedbackStatisticsRecordType {
|
||||||
_id: ID!
|
_id: ID!
|
||||||
course_session_id: ID!
|
course_session_id: ID!
|
||||||
generation: String!
|
generation: String!
|
||||||
|
region: String!
|
||||||
circle_id: ID!
|
circle_id: ID!
|
||||||
satisfaction_average: Float!
|
satisfaction_average: Float!
|
||||||
satisfaction_max: Int!
|
satisfaction_max: Int!
|
||||||
|
|
@ -171,6 +181,7 @@ type CompetenceRecordStatisticsType {
|
||||||
_id: ID!
|
_id: ID!
|
||||||
course_session_id: ID!
|
course_session_id: ID!
|
||||||
generation: String!
|
generation: String!
|
||||||
|
region: String!
|
||||||
title: String!
|
title: String!
|
||||||
circle_id: ID!
|
circle_id: ID!
|
||||||
success_count: Int!
|
success_count: Int!
|
||||||
|
|
@ -186,6 +197,7 @@ type BaseStatisticsType {
|
||||||
course_session_selection_ids: [ID]!
|
course_session_selection_ids: [ID]!
|
||||||
user_selection_ids: [ID]
|
user_selection_ids: [ID]
|
||||||
assignments: AssignmentsStatisticsType!
|
assignments: AssignmentsStatisticsType!
|
||||||
|
course_session_properties: StatisticsCourseSessionPropertiesType!
|
||||||
}
|
}
|
||||||
|
|
||||||
type CourseProgressType {
|
type CourseProgressType {
|
||||||
|
|
@ -268,7 +280,6 @@ type CourseObjectType {
|
||||||
action_competences: [ActionCompetenceObjectType!]!
|
action_competences: [ActionCompetenceObjectType!]!
|
||||||
profiles: [String]
|
profiles: [String]
|
||||||
course_session_users(id: String): [CourseSessionUserType]!
|
course_session_users(id: String): [CourseSessionUserType]!
|
||||||
chosen_profile(user: String!): String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionCompetenceObjectType implements CoursePageInterface {
|
type ActionCompetenceObjectType implements CoursePageInterface {
|
||||||
|
|
@ -503,7 +514,7 @@ type AssignmentObjectType implements CoursePageInterface {
|
||||||
max_points: Int
|
max_points: Int
|
||||||
competence_certificate_weight: Float
|
competence_certificate_weight: Float
|
||||||
learning_content: LearningContentInterface
|
learning_content: LearningContentInterface
|
||||||
completion(course_session_id: ID!, learning_content_page_id: ID, assignment_user_id: UUID): AssignmentCompletionObjectType
|
completions(course_session_id: ID!, learning_content_page_id: ID, assignment_user_ids: [UUID]): [AssignmentCompletionObjectType]
|
||||||
solution_sample: ContentDocumentObjectType
|
solution_sample: ContentDocumentObjectType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -565,6 +576,7 @@ type AssignmentCompletionObjectType {
|
||||||
evaluation_points: Float
|
evaluation_points: Float
|
||||||
evaluation_points_final: Float
|
evaluation_points_final: Float
|
||||||
evaluation_max_points: Float
|
evaluation_max_points: Float
|
||||||
|
evaluation_percent: Float
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserObjectType {
|
type UserObjectType {
|
||||||
|
|
|
||||||
|
|
@ -90,8 +90,12 @@ export const ASSIGNMENT_COMPLETION_QUERY = graphql(`
|
||||||
`);
|
`);
|
||||||
|
|
||||||
export const COMPETENCE_NAVI_CERTIFICATE_QUERY = graphql(`
|
export const COMPETENCE_NAVI_CERTIFICATE_QUERY = graphql(`
|
||||||
query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {
|
query competenceCertificateQuery(
|
||||||
competence_certificate_list(course_slug: $courseSlug) {
|
$courseSlug: String!
|
||||||
|
$courseSessionId: ID!
|
||||||
|
$userIds: [UUID!]
|
||||||
|
) {
|
||||||
|
competence_certificate_list(course_slug: $courseSlug, user_ids: $userIds) {
|
||||||
...CoursePageFields
|
...CoursePageFields
|
||||||
competence_certificates {
|
competence_certificates {
|
||||||
...CoursePageFields
|
...CoursePageFields
|
||||||
|
|
@ -100,7 +104,7 @@ export const COMPETENCE_NAVI_CERTIFICATE_QUERY = graphql(`
|
||||||
assignment_type
|
assignment_type
|
||||||
max_points
|
max_points
|
||||||
competence_certificate_weight
|
competence_certificate_weight
|
||||||
completion(course_session_id: $courseSessionId) {
|
completions(course_session_id: $courseSessionId) {
|
||||||
id
|
id
|
||||||
completion_status
|
completion_status
|
||||||
submitted_at
|
submitted_at
|
||||||
|
|
@ -132,9 +136,9 @@ export const COMPETENCE_NAVI_CERTIFICATE_FOR_USER_QUERY = graphql(`
|
||||||
query competenceCertificateForUserQuery(
|
query competenceCertificateForUserQuery(
|
||||||
$courseSlug: String!
|
$courseSlug: String!
|
||||||
$courseSessionId: ID!
|
$courseSessionId: ID!
|
||||||
$userId: UUID!
|
$userIds: [UUID!]!
|
||||||
) {
|
) {
|
||||||
competence_certificate_list_for_user(course_slug: $courseSlug, user_id: $userId) {
|
competence_certificate_list(course_slug: $courseSlug, user_ids: $userIds) {
|
||||||
...CoursePageFields
|
...CoursePageFields
|
||||||
competence_certificates {
|
competence_certificates {
|
||||||
...CoursePageFields
|
...CoursePageFields
|
||||||
|
|
@ -143,7 +147,7 @@ export const COMPETENCE_NAVI_CERTIFICATE_FOR_USER_QUERY = graphql(`
|
||||||
assignment_type
|
assignment_type
|
||||||
max_points
|
max_points
|
||||||
competence_certificate_weight
|
competence_certificate_weight
|
||||||
completion(course_session_id: $courseSessionId) {
|
completions(course_session_id: $courseSessionId) {
|
||||||
id
|
id
|
||||||
completion_status
|
completion_status
|
||||||
submitted_at
|
submitted_at
|
||||||
|
|
@ -152,6 +156,10 @@ export const COMPETENCE_NAVI_CERTIFICATE_FOR_USER_QUERY = graphql(`
|
||||||
evaluation_points_deducted
|
evaluation_points_deducted
|
||||||
evaluation_max_points
|
evaluation_max_points
|
||||||
evaluation_passed
|
evaluation_passed
|
||||||
|
evaluation_percent
|
||||||
|
assignment_user {
|
||||||
|
id
|
||||||
|
}
|
||||||
course_session {
|
course_session {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
|
|
@ -422,6 +430,7 @@ export const DASHBOARD_COURSE_STATISTICS = graphql(`
|
||||||
sessions {
|
sessions {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
region
|
||||||
}
|
}
|
||||||
generations
|
generations
|
||||||
circles {
|
circles {
|
||||||
|
|
@ -442,6 +451,7 @@ export const DASHBOARD_COURSE_STATISTICS = graphql(`
|
||||||
_id
|
_id
|
||||||
course_session_id
|
course_session_id
|
||||||
generation
|
generation
|
||||||
|
region
|
||||||
circle_id
|
circle_id
|
||||||
due_date
|
due_date
|
||||||
participants_present
|
participants_present
|
||||||
|
|
@ -460,6 +470,7 @@ export const DASHBOARD_COURSE_STATISTICS = graphql(`
|
||||||
_id
|
_id
|
||||||
course_session_id
|
course_session_id
|
||||||
generation
|
generation
|
||||||
|
region
|
||||||
circle_id
|
circle_id
|
||||||
experts
|
experts
|
||||||
satisfaction_average
|
satisfaction_average
|
||||||
|
|
@ -488,8 +499,11 @@ export const DASHBOARD_COURSE_STATISTICS = graphql(`
|
||||||
course_session_assignment_id
|
course_session_assignment_id
|
||||||
circle_id
|
circle_id
|
||||||
generation
|
generation
|
||||||
|
region
|
||||||
assignment_title
|
assignment_title
|
||||||
assignment_type_translation_key
|
assignment_type_translation_key
|
||||||
|
competence_certificate_title
|
||||||
|
competence_certificate_id
|
||||||
details_url
|
details_url
|
||||||
deadline
|
deadline
|
||||||
metrics {
|
metrics {
|
||||||
|
|
@ -498,7 +512,9 @@ export const DASHBOARD_COURSE_STATISTICS = graphql(`
|
||||||
failed_count
|
failed_count
|
||||||
unranked_count
|
unranked_count
|
||||||
ranking_completed
|
ranking_completed
|
||||||
|
average_evaluation_percent
|
||||||
average_passed
|
average_passed
|
||||||
|
competence_certificate_weight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -513,6 +529,7 @@ export const DASHBOARD_COURSE_STATISTICS = graphql(`
|
||||||
_id
|
_id
|
||||||
course_session_id
|
course_session_id
|
||||||
generation
|
generation
|
||||||
|
region
|
||||||
circle_id
|
circle_id
|
||||||
title
|
title
|
||||||
success_count
|
success_count
|
||||||
|
|
@ -525,14 +542,27 @@ export const DASHBOARD_COURSE_STATISTICS = graphql(`
|
||||||
`);
|
`);
|
||||||
|
|
||||||
export const DASHBOARD_MENTOR_COMPETENCE_SUMMARY = graphql(`
|
export const DASHBOARD_MENTOR_COMPETENCE_SUMMARY = graphql(`
|
||||||
query mentorCourseStatistics($courseId: ID!) {
|
query mentorCourseStatistics($courseId: ID!, $agentRole: String!) {
|
||||||
mentor_course_statistics(course_id: $courseId) {
|
mentor_course_statistics(course_id: $courseId, agent_role: $agentRole) {
|
||||||
_id
|
_id
|
||||||
course_id
|
course_id
|
||||||
course_title
|
course_title
|
||||||
course_slug
|
course_slug
|
||||||
course_session_selection_ids
|
course_session_selection_ids
|
||||||
user_selection_ids
|
user_selection_ids
|
||||||
|
course_session_properties {
|
||||||
|
_id
|
||||||
|
sessions {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
region
|
||||||
|
}
|
||||||
|
generations
|
||||||
|
circles {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
assignments {
|
assignments {
|
||||||
_id
|
_id
|
||||||
summary {
|
summary {
|
||||||
|
|
@ -541,16 +571,22 @@ export const DASHBOARD_MENTOR_COMPETENCE_SUMMARY = graphql(`
|
||||||
average_passed
|
average_passed
|
||||||
total_passed
|
total_passed
|
||||||
total_failed
|
total_failed
|
||||||
|
average_evaluation_percent
|
||||||
}
|
}
|
||||||
records {
|
records {
|
||||||
_id
|
_id
|
||||||
course_session_id
|
course_session_id
|
||||||
course_session_assignment_id
|
course_session_assignment_id
|
||||||
|
course_session_title
|
||||||
circle_id
|
circle_id
|
||||||
generation
|
generation
|
||||||
|
region
|
||||||
assignment_title
|
assignment_title
|
||||||
assignment_type_translation_key
|
assignment_type_translation_key
|
||||||
|
competence_certificate_id
|
||||||
|
competence_certificate_title
|
||||||
details_url
|
details_url
|
||||||
|
learning_content_id
|
||||||
deadline
|
deadline
|
||||||
metrics {
|
metrics {
|
||||||
_id
|
_id
|
||||||
|
|
@ -558,6 +594,8 @@ export const DASHBOARD_MENTOR_COMPETENCE_SUMMARY = graphql(`
|
||||||
failed_count
|
failed_count
|
||||||
unranked_count
|
unranked_count
|
||||||
ranking_completed
|
ranking_completed
|
||||||
|
competence_certificate_weight
|
||||||
|
average_evaluation_percent
|
||||||
average_passed
|
average_passed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,14 @@ import { useTranslation } from "i18next-vue";
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const props = defineProps<{
|
export interface Props {
|
||||||
courseSession: CourseSession;
|
courseSession: CourseSession;
|
||||||
learningContent: LearningContentAssignment | LearningContentEdoniqTest;
|
learningContent: LearningContentAssignment | LearningContentEdoniqTest;
|
||||||
}>();
|
userSelectionIds?: string[];
|
||||||
|
linkToResults?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
log.debug("AssignmentDetails created", stringifyParse(props));
|
log.debug("AssignmentDetails created", stringifyParse(props));
|
||||||
|
|
||||||
|
|
@ -45,11 +49,13 @@ const isPraxisAssignment = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
log.debug("AssignmentDetails mounted", props.learningContent, props.userSelectionIds);
|
||||||
const { gradedUsers, assignmentSubmittedUsers } =
|
const { gradedUsers, assignmentSubmittedUsers } =
|
||||||
await loadAssignmentCompletionStatusData(
|
await loadAssignmentCompletionStatusData(
|
||||||
props.learningContent.content_assignment.id,
|
props.learningContent.content_assignment.id,
|
||||||
props.courseSession.id,
|
props.courseSession.id,
|
||||||
props.learningContent.id
|
props.learningContent.id,
|
||||||
|
props.userSelectionIds ?? []
|
||||||
);
|
);
|
||||||
state.gradedUsers = gradedUsers;
|
state.gradedUsers = gradedUsers;
|
||||||
state.assignmentSubmittedUsers = assignmentSubmittedUsers;
|
state.assignmentSubmittedUsers = assignmentSubmittedUsers;
|
||||||
|
|
@ -80,6 +86,17 @@ function findUserPointsHtml(userId: string) {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateCertificatesLink(userId: string) {
|
||||||
|
const parts = props.learningContent.competence_certificate?.frontend_url?.split("/");
|
||||||
|
|
||||||
|
if (!parts) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const certificatePart = parts[parts.length - 1];
|
||||||
|
return `/course/${props.courseSession.course.slug}/profile/${userId}/competence/certificates/${certificatePart}`;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -111,13 +128,14 @@ function findUserPointsHtml(userId: string) {
|
||||||
:course-session="courseSession"
|
:course-session="courseSession"
|
||||||
:learning-content="learningContent"
|
:learning-content="learningContent"
|
||||||
:show-title="false"
|
:show-title="false"
|
||||||
|
:user-selection-ids="props.userSelectionIds"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="courseSessionDetailResult.filterMembers().length" class="mt-6">
|
<div v-if="courseSessionDetailResult.filterMembers().length" class="mt-6">
|
||||||
<ul>
|
<ul>
|
||||||
<ItPersonRow
|
<ItPersonRow
|
||||||
v-for="csu in courseSessionDetailResult.filterMembers()"
|
v-for="csu in courseSessionDetailResult.filterMembers(props.userSelectionIds)"
|
||||||
:key="csu.user_id"
|
:key="csu.user_id"
|
||||||
:name="`${csu.first_name} ${csu.last_name}`"
|
:name="`${csu.first_name} ${csu.last_name}`"
|
||||||
:avatar-url="csu.avatar_url"
|
:avatar-url="csu.avatar_url"
|
||||||
|
|
@ -172,11 +190,19 @@ function findUserPointsHtml(userId: string) {
|
||||||
props.learningContent.content_type !==
|
props.learningContent.content_type !==
|
||||||
'learnpath.LearningContentEdoniqTest'
|
'learnpath.LearningContentEdoniqTest'
|
||||||
"
|
"
|
||||||
:to="`/course/${props.courseSession.course.slug}/assignment-evaluation/${learningContent.content_assignment.id}/${csu.user_id}`"
|
:to="
|
||||||
|
props.linkToResults
|
||||||
|
? `/course/${props.courseSession.course.slug}/assignment-evaluation/${learningContent.content_assignment.id}/${csu.user_id}`
|
||||||
|
: generateCertificatesLink(csu.user_id)
|
||||||
|
"
|
||||||
class="link lg:w-full lg:text-right"
|
class="link lg:w-full lg:text-right"
|
||||||
data-cy="show-results"
|
data-cy="show-results"
|
||||||
>
|
>
|
||||||
{{ $t("a.Ergebnisse anschauen") }}
|
{{
|
||||||
|
props.linkToResults
|
||||||
|
? $t("a.Ergebnisse anschauen")
|
||||||
|
: $t("a.Profil anzeigen")
|
||||||
|
}}
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import * as log from "loglevel";
|
||||||
import { computed, onMounted } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
import type { LearningContentAssignment, LearningContentEdoniqTest } from "@/types";
|
import type { LearningContentAssignment, LearningContentEdoniqTest } from "@/types";
|
||||||
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
|
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
|
||||||
|
import { getPreviousRoute } from "@/router/history";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
|
|
@ -25,16 +26,22 @@ const lpQueryResult = useCourseData(props.courseSlug);
|
||||||
const learningContentAssignment = computed(() => {
|
const learningContentAssignment = computed(() => {
|
||||||
return lpQueryResult.findLearningContent(props.assignmentId);
|
return lpQueryResult.findLearningContent(props.assignmentId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const previousRoute = getPreviousRoute();
|
||||||
|
|
||||||
|
const backRoute = computed(() => {
|
||||||
|
if (previousRoute?.path.endsWith("/assignment")) {
|
||||||
|
return previousRoute;
|
||||||
|
}
|
||||||
|
return `/course/${props.courseSlug}/cockpit`;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="!loading" class="bg-gray-200">
|
<div v-if="!loading" class="bg-gray-200">
|
||||||
<div class="container-large">
|
<div class="container-large">
|
||||||
<nav class="py-4 pb-4">
|
<nav class="py-4 pb-4">
|
||||||
<router-link
|
<router-link class="btn-text inline-flex items-center pl-0" :to="backRoute">
|
||||||
class="btn-text inline-flex items-center pl-0"
|
|
||||||
:to="`/course/${props.courseSlug}/cockpit`"
|
|
||||||
>
|
|
||||||
<it-icon-arrow-left />
|
<it-icon-arrow-left />
|
||||||
<span>{{ $t("general.back") }}</span>
|
<span>{{ $t("general.back") }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
@ -46,6 +53,7 @@ const learningContentAssignment = computed(() => {
|
||||||
v-if="learningContentAssignment"
|
v-if="learningContentAssignment"
|
||||||
:course-session="courseSession"
|
:course-session="courseSession"
|
||||||
:learning-content="learningContentAssignment as (LearningContentAssignment | LearningContentEdoniqTest)"
|
:learning-content="learningContentAssignment as (LearningContentAssignment | LearningContentEdoniqTest)"
|
||||||
|
:link-to-results="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ import {
|
||||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
export function useExpertCockpitPageData(courseSlug: string) {
|
export function useExpertCockpitPageData(
|
||||||
|
courseSlug: string,
|
||||||
|
userSelectionIds: string[] | null = null
|
||||||
|
) {
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
|
|
||||||
const cockpitStore = useExpertCockpitStore();
|
const cockpitStore = useExpertCockpitStore();
|
||||||
|
|
@ -19,8 +22,10 @@ export function useExpertCockpitPageData(courseSlug: string) {
|
||||||
courseSessionDetailResult.findCurrentUser()
|
courseSessionDetailResult.findCurrentUser()
|
||||||
);
|
);
|
||||||
|
|
||||||
const userDataPromises = courseSessionDetailResult.filterMembers().map((m) => {
|
const userDataPromises = courseSessionDetailResult
|
||||||
const completionData = useCourseDataWithCompletion(courseSlug, m.id);
|
.filterMembers(userSelectionIds)
|
||||||
|
.map((m) => {
|
||||||
|
const completionData = useCourseDataWithCompletion(courseSlug, m.user_id);
|
||||||
return completionData.resultPromise;
|
return completionData.resultPromise;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ const getIconName = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const openInCircle = (assignment: CompetenceCertificateAssignment) => {
|
const openInCircle = (assignment: CompetenceCertificateAssignment) => {
|
||||||
if (assignment.completion?.course_session !== currentCourseSession.value) {
|
if (assignment.completions[0]?.course_session !== currentCourseSession.value) {
|
||||||
switchCourseSessionById(assignment.completion!.course_session.id);
|
switchCourseSessionById(assignment.completions[0]!.course_session.id);
|
||||||
}
|
}
|
||||||
router.push(assignment.frontend_url);
|
router.push(assignment.frontend_url);
|
||||||
};
|
};
|
||||||
|
|
@ -48,7 +48,7 @@ const openInCircle = (assignment: CompetenceCertificateAssignment) => {
|
||||||
v-if="showCourseSession"
|
v-if="showCourseSession"
|
||||||
:data-cy="`assignment-${assignment.slug}-course-session`"
|
:data-cy="`assignment-${assignment.slug}-course-session`"
|
||||||
>
|
>
|
||||||
{{ assignment?.completion?.course_session.title }}
|
{{ assignment?.completions?.[0]?.course_session.title }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-800">
|
<p class="text-gray-800">
|
||||||
<button
|
<button
|
||||||
|
|
@ -69,7 +69,7 @@ const openInCircle = (assignment: CompetenceCertificateAssignment) => {
|
||||||
</div>
|
</div>
|
||||||
<div class="grow lg:px-8">
|
<div class="grow lg:px-8">
|
||||||
<div
|
<div
|
||||||
v-if="assignment.completion?.completion_status === 'EVALUATION_SUBMITTED'"
|
v-if="assignment.completions?.[0]?.completion_status === 'EVALUATION_SUBMITTED'"
|
||||||
class="flex items-center"
|
class="flex items-center"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
@ -82,7 +82,7 @@ const openInCircle = (assignment: CompetenceCertificateAssignment) => {
|
||||||
<div
|
<div
|
||||||
v-else-if="
|
v-else-if="
|
||||||
['EVALUATION_IN_PROGRESS', 'SUBMITTED'].includes(
|
['EVALUATION_IN_PROGRESS', 'SUBMITTED'].includes(
|
||||||
assignment.completion?.completion_status || ''
|
assignment.completions?.[0]?.completion_status || ''
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
class="flex items-center"
|
class="flex items-center"
|
||||||
|
|
@ -97,31 +97,33 @@ const openInCircle = (assignment: CompetenceCertificateAssignment) => {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
v-if="assignment.completion?.completion_status === 'EVALUATION_SUBMITTED'"
|
v-if="assignment.completions?.[0]?.completion_status === 'EVALUATION_SUBMITTED'"
|
||||||
class="flex flex-col lg:items-center"
|
class="flex flex-col lg:items-center"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col lg:items-center">
|
<div class="flex flex-col lg:items-center">
|
||||||
<div class="heading-2">
|
<div class="heading-2">
|
||||||
{{ assignment.completion?.evaluation_points_final }}
|
{{ assignment.completions[0]?.evaluation_points_final }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ $t("assignment.von x Punkten", { x: assignment.max_points }) }}
|
{{ $t("assignment.von x Punkten", { x: assignment.max_points }) }}
|
||||||
({{
|
({{
|
||||||
(
|
(
|
||||||
((assignment.completion?.evaluation_points_final ?? 0) /
|
((assignment.completions[0]?.evaluation_points_final ?? 0) /
|
||||||
(assignment.completion?.evaluation_max_points ?? 1)) *
|
(assignment.completions[0]?.evaluation_max_points ?? 1)) *
|
||||||
100
|
100
|
||||||
).toFixed(0)
|
).toFixed(0)
|
||||||
}}%)
|
}}%)
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="(assignment.completion?.evaluation_points_deducted ?? 0) > 0"
|
v-if="(assignment.completions[0]?.evaluation_points_deducted ?? 0) > 0"
|
||||||
class="text-gray-900"
|
class="text-gray-900"
|
||||||
>
|
>
|
||||||
{{ $t("a.mit Abzug") }}
|
{{ $t("a.mit Abzug") }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="assignment.completion && !assignment.completion.evaluation_passed"
|
v-if="
|
||||||
|
assignment.completions[0] && !assignment.completions[0].evaluation_passed
|
||||||
|
"
|
||||||
class="my-2 rounded-md bg-error-red-200 px-2.5 py-0.5"
|
class="my-2 rounded-md bg-error-red-200 px-2.5 py-0.5"
|
||||||
>
|
>
|
||||||
{{ $t("a.Nicht Bestanden") }}
|
{{ $t("a.Nicht Bestanden") }}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ const userGradeRounded2Places = computed(() => {
|
||||||
|
|
||||||
const numAssignmentsEvaluated = computed(() => {
|
const numAssignmentsEvaluated = computed(() => {
|
||||||
return props.competenceCertificate.assignments.filter((a) => {
|
return props.competenceCertificate.assignments.filter((a) => {
|
||||||
return a.completion?.completion_status === "EVALUATION_SUBMITTED";
|
return a?.completions?.[0]?.completion_status === "EVALUATION_SUBMITTED";
|
||||||
}).length;
|
}).length;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -57,7 +57,8 @@ const showCourseSession = computed(() => {
|
||||||
const currentCourseSession = useCurrentCourseSession();
|
const currentCourseSession = useCurrentCourseSession();
|
||||||
return props.competenceCertificate.assignments.some((assignment) => {
|
return props.competenceCertificate.assignments.some((assignment) => {
|
||||||
return (
|
return (
|
||||||
assignment.completion?.course_session.title !== currentCourseSession.value.title
|
assignment.completions?.[0]?.course_session.title !==
|
||||||
|
currentCourseSession.value.title
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useAllCompetenceCertificates } from "@/composables";
|
import { useAllCompetenceCertificates } from "@/composables";
|
||||||
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
|
|
||||||
import { getPreviousRoute } from "@/router/history";
|
import { getPreviousRoute } from "@/router/history";
|
||||||
|
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
certificateSlug: string;
|
certificateSlug: string;
|
||||||
|
|
@ -12,8 +14,9 @@ const props = defineProps<{
|
||||||
|
|
||||||
log.debug("CompetenceCertificateDetailPage setup", props);
|
log.debug("CompetenceCertificateDetailPage setup", props);
|
||||||
|
|
||||||
|
const { id: currentUserId } = useUserStore();
|
||||||
const { competenceCertificates } = useAllCompetenceCertificates(
|
const { competenceCertificates } = useAllCompetenceCertificates(
|
||||||
props.userId,
|
props.userId ?? currentUserId,
|
||||||
props.courseSlug
|
props.courseSlug
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import {
|
||||||
calcCompetencesTotalGrade,
|
calcCompetencesTotalGrade,
|
||||||
} from "@/pages/competence/utils";
|
} from "@/pages/competence/utils";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
|
|
@ -16,9 +18,9 @@ const props = defineProps<{
|
||||||
log.debug("CompetenceCertificateListPage setup", props);
|
log.debug("CompetenceCertificateListPage setup", props);
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const { id: currentUserId } = useUserStore();
|
||||||
const { competenceCertificates } = useAllCompetenceCertificates(
|
const { competenceCertificates } = useAllCompetenceCertificates(
|
||||||
props.userId,
|
props.userId ?? currentUserId,
|
||||||
props.courseSlug
|
props.courseSlug
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -36,7 +38,7 @@ const userPointsEvaluatedAssignments = computed(() => {
|
||||||
|
|
||||||
const numAssignmentsEvaluated = computed(() => {
|
const numAssignmentsEvaluated = computed(() => {
|
||||||
return (assignments.value ?? []).filter((a) => {
|
return (assignments.value ?? []).filter((a) => {
|
||||||
return a.completion?.completion_status === "EVALUATION_SUBMITTED";
|
return a.completions?.[0]?.completion_status === "EVALUATION_SUBMITTED";
|
||||||
}).length;
|
}).length;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,10 @@ const props = defineProps<{
|
||||||
|
|
||||||
log.debug("CompetenceIndexPage setup", props);
|
log.debug("CompetenceIndexPage setup", props);
|
||||||
|
|
||||||
|
const user = useUserStore();
|
||||||
|
|
||||||
const { competenceCertificates, isLoaded } = useAllCompetenceCertificates(
|
const { competenceCertificates, isLoaded } = useAllCompetenceCertificates(
|
||||||
undefined,
|
user.id,
|
||||||
props.courseSlug
|
props.courseSlug
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -88,7 +90,8 @@ const router = useRouter();
|
||||||
{{
|
{{
|
||||||
$t("assignment.x von y Kompetenznachweis-Elementen abgeschlossen", {
|
$t("assignment.x von y Kompetenznachweis-Elementen abgeschlossen", {
|
||||||
x: certificate.assignments.filter(
|
x: certificate.assignments.filter(
|
||||||
(a) => a.completion?.completion_status === "EVALUATION_SUBMITTED"
|
(a) =>
|
||||||
|
a.completions[0]?.completion_status === "EVALUATION_SUBMITTED"
|
||||||
).length,
|
).length,
|
||||||
y: certificate.assignments.length,
|
y: certificate.assignments.length,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export function assignmentsMaxEvaluationPoints(
|
||||||
): number {
|
): number {
|
||||||
return _.sum(
|
return _.sum(
|
||||||
assignments
|
assignments
|
||||||
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
|
.filter((a) => a.completions[0]?.completion_status === "EVALUATION_SUBMITTED")
|
||||||
.map((a) => a.max_points)
|
.map((a) => a.max_points)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -17,8 +17,8 @@ export function assignmentsMaxEvaluationPoints(
|
||||||
export function assignmentsUserPoints(assignments: CompetenceCertificateAssignment[]) {
|
export function assignmentsUserPoints(assignments: CompetenceCertificateAssignment[]) {
|
||||||
return +_.sum(
|
return +_.sum(
|
||||||
assignments
|
assignments
|
||||||
.filter((a) => a.completion?.completion_status === "EVALUATION_SUBMITTED")
|
.filter((a) => a.completions?.[0]?.completion_status === "EVALUATION_SUBMITTED")
|
||||||
.map((a) => a.completion?.evaluation_points_final ?? 0)
|
.map((a) => a.completions?.[0]?.evaluation_points_final ?? 0)
|
||||||
).toFixed(1);
|
).toFixed(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,12 +27,12 @@ export function calcCompetenceCertificateGrade(
|
||||||
roundedToHalfGrade = true
|
roundedToHalfGrade = true
|
||||||
) {
|
) {
|
||||||
const evaluatedAssignments = assignments.filter(
|
const evaluatedAssignments = assignments.filter(
|
||||||
(a) => a.completion?.completion_status === "EVALUATION_SUBMITTED"
|
(a) => a.completions?.[0]?.completion_status === "EVALUATION_SUBMITTED"
|
||||||
);
|
);
|
||||||
|
|
||||||
const adjustedResults = evaluatedAssignments.map((a) => {
|
const adjustedResults = evaluatedAssignments.map((a) => {
|
||||||
return (
|
return (
|
||||||
((a.completion?.evaluation_points_final ?? 0) / a.max_points) *
|
((a.completions?.[0]?.evaluation_points_final ?? 0) / a.max_points) *
|
||||||
a.competence_certificate_weight
|
a.competence_certificate_weight
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -77,7 +77,7 @@ export function competenceCertificateProgressStatusCount(
|
||||||
assignments: CompetenceCertificateAssignment[]
|
assignments: CompetenceCertificateAssignment[]
|
||||||
) {
|
) {
|
||||||
const numAssignmentsEvaluated = assignments.filter(
|
const numAssignmentsEvaluated = assignments.filter(
|
||||||
(a) => a.completion?.completion_status === "EVALUATION_SUBMITTED"
|
(a) => a.completions?.[0]?.completion_status === "EVALUATION_SUBMITTED"
|
||||||
).length;
|
).length;
|
||||||
return {
|
return {
|
||||||
SUCCESS: numAssignmentsEvaluated,
|
SUCCESS: numAssignmentsEvaluated,
|
||||||
|
|
@ -120,10 +120,10 @@ export function mergeCompetenceCertificates(
|
||||||
if (!existingAssignment) {
|
if (!existingAssignment) {
|
||||||
mergedCertificate.assignments.push(assignment);
|
mergedCertificate.assignments.push(assignment);
|
||||||
} else if (
|
} else if (
|
||||||
assignment.completion != null &&
|
assignment.completions?.[0] != null &&
|
||||||
(existingAssignment.completion == null ||
|
(existingAssignment.completions?.[0] == null ||
|
||||||
dayjs(existingAssignment.completion.evaluation_submitted_at).isBefore(
|
dayjs(existingAssignment.completions[0].evaluation_submitted_at).isBefore(
|
||||||
assignment.completion.evaluation_submitted_at
|
assignment.completions[0].evaluation_submitted_at
|
||||||
))
|
))
|
||||||
) {
|
) {
|
||||||
mergedCertificate.assignments.splice(
|
mergedCertificate.assignments.splice(
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,7 @@ function exportData() {
|
||||||
course_session_id: csId,
|
course_session_id: csId,
|
||||||
generation: "",
|
generation: "",
|
||||||
circle_id: "",
|
circle_id: "",
|
||||||
|
region: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
exportDataAsXls(items, exportPersons, userStore.language);
|
exportDataAsXls(items, exportPersons, userStore.language);
|
||||||
|
|
@ -407,8 +408,8 @@ watch(selectedRegion, () => {
|
||||||
v-if="
|
v-if="
|
||||||
(['SUPERVISOR', 'EXPERT'].includes(cs.my_role) &&
|
(['SUPERVISOR', 'EXPERT'].includes(cs.my_role) &&
|
||||||
cs.user_role === 'MEMBER') ||
|
cs.user_role === 'MEMBER') ||
|
||||||
(cs.my_role === 'LEARNING_MENTOR' &&
|
(['LEARNING_MENTOR', 'BERUFSBILDNER'].includes(cs.my_role) &&
|
||||||
cs.user_role === 'LEARNING_MENTEE')
|
cs.user_role === 'PARTICIPANT')
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<router-link
|
<router-link
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useCourseData, useCurrentCourseSession } from "@/composables";
|
||||||
|
import AssignmentDetails from "@/pages/cockpit/assignmentsPage/AssignmentDetails.vue";
|
||||||
|
import * as log from "loglevel";
|
||||||
|
import { computed } from "vue";
|
||||||
|
import type { LearningContentAssignment, LearningContentEdoniqTest } from "@/types";
|
||||||
|
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
courseSlug: string;
|
||||||
|
assignmentId: string;
|
||||||
|
agentRole: string;
|
||||||
|
participantUserIds: string[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"AgentAssignmentDetail created",
|
||||||
|
props.courseSlug,
|
||||||
|
props.agentRole,
|
||||||
|
props.participantUserIds
|
||||||
|
);
|
||||||
|
|
||||||
|
const courseSession = useCurrentCourseSession();
|
||||||
|
const { loading } = useExpertCockpitPageData(
|
||||||
|
props.courseSlug,
|
||||||
|
props.participantUserIds
|
||||||
|
);
|
||||||
|
|
||||||
|
const lpQueryResult = useCourseData(props.courseSlug);
|
||||||
|
|
||||||
|
const learningContentAssignment = computed(() => {
|
||||||
|
return lpQueryResult.findLearningContent(props.assignmentId);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="!loading" class="bg-gray-200">
|
||||||
|
<div class="container-large">
|
||||||
|
<nav class="py-4 pb-4">
|
||||||
|
<router-link
|
||||||
|
class="btn-text inline-flex items-center pl-0"
|
||||||
|
:to="`/statistic/${props.agentRole}/${props.courseSlug}/assignment`"
|
||||||
|
>
|
||||||
|
<it-icon-arrow-left />
|
||||||
|
<span>{{ $t("general.back") }}</span>
|
||||||
|
</router-link>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
<div class="bg-white p-6">
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
<AssignmentDetails
|
||||||
|
v-if="learningContentAssignment"
|
||||||
|
:course-session="courseSession"
|
||||||
|
:learning-content="learningContentAssignment as (LearningContentAssignment | LearningContentEdoniqTest)"
|
||||||
|
:user-selection-ids="participantUserIds"
|
||||||
|
:link-to-results="props.agentRole.toLowerCase() !== 'berufsbildner'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
import * as log from "loglevel";
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
import { fetchDashboardPersons } from "@/services/dashboard";
|
||||||
|
import AgentAssignmentDetail from "@/pages/dashboard/agentAssignment/AgentAssignmentDetail.vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
courseSlug: string;
|
||||||
|
assignmentId: string;
|
||||||
|
agentRole: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
log.debug("AgentAssignmentDetailPage created", props.courseSlug, props.agentRole);
|
||||||
|
|
||||||
|
const courseSession = useCurrentCourseSession();
|
||||||
|
|
||||||
|
const loading = ref(true);
|
||||||
|
const participantUserIds = ref<string[]>([]);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
log.debug("AgentAssignmentDetailPage mounted", courseSession);
|
||||||
|
|
||||||
|
const personData = await fetchDashboardPersons("default");
|
||||||
|
const participants = personData?.filter((p) => {
|
||||||
|
return p.course_sessions.find(
|
||||||
|
(cs) => cs.id === courseSession.value.id && cs.my_role === "BERUFSBILDNER"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
participantUserIds.value = participants?.map((p) => p.user_id);
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AgentAssignmentDetail
|
||||||
|
v-if="participantUserIds.length"
|
||||||
|
:assignment-id="props.assignmentId"
|
||||||
|
:agent-role="props.agentRole"
|
||||||
|
:course-slug="props.courseSlug"
|
||||||
|
:participant-user-ids="participantUserIds"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import log from "loglevel";
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
import {
|
||||||
|
courseIdForCourseSlug,
|
||||||
|
fetchMentorCompetenceSummary,
|
||||||
|
} from "@/services/dashboard";
|
||||||
|
import type { BaseStatisticsType } from "@/gql/graphql";
|
||||||
|
import { useDashboardStore } from "@/stores/dashboard";
|
||||||
|
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||||
|
import AssignmentList from "@/pages/dashboard/statistic/AssignmentList.vue";
|
||||||
|
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
agentRole: string;
|
||||||
|
courseSlug: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
log.debug("AgentStatisticParentPage created", props);
|
||||||
|
|
||||||
|
const loading = ref(true);
|
||||||
|
const courseId = ref<string | undefined>(undefined);
|
||||||
|
const agentAssignmentData = ref<BaseStatisticsType | null>(null);
|
||||||
|
|
||||||
|
const courseSessionName = (courseSessionId: string) => {
|
||||||
|
return (
|
||||||
|
agentAssignmentData.value?.course_session_properties?.sessions.find(
|
||||||
|
(session) => session.id === courseSessionId
|
||||||
|
)?.name ?? ""
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const circleMeta = (circleId: string) => {
|
||||||
|
return agentAssignmentData.value?.course_session_properties.circles.find(
|
||||||
|
(circle) => circle.id === circleId
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await dashboardStore.loadDashboardDetails();
|
||||||
|
courseId.value = courseIdForCourseSlug(
|
||||||
|
dashboardStore.dashboardConfigsv2,
|
||||||
|
props.courseSlug
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!courseId.value) {
|
||||||
|
log.error("CourseId not found for courseSlug", props.courseSlug);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("courseId", courseId.value);
|
||||||
|
agentAssignmentData.value = await fetchMentorCompetenceSummary(
|
||||||
|
courseId.value,
|
||||||
|
props.agentRole
|
||||||
|
);
|
||||||
|
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-gray-200">
|
||||||
|
<div v-if="loading" class="m-8 flex justify-center">
|
||||||
|
<LoadingSpinner />
|
||||||
|
</div>
|
||||||
|
<div v-else class="container-large flex flex-col space-y-8">
|
||||||
|
<router-link class="btn-text inline-flex items-center pl-0" to="/">
|
||||||
|
<it-icon-arrow-left />
|
||||||
|
<span>{{ $t("general.back") }}</span>
|
||||||
|
</router-link>
|
||||||
|
<AssignmentList
|
||||||
|
v-if="agentAssignmentData"
|
||||||
|
:course-statistics="agentAssignmentData"
|
||||||
|
:course-session-name="courseSessionName"
|
||||||
|
:circle-meta="circleMeta as any"
|
||||||
|
:detail-base-url="`/statistic/${props.agentRole}/${props.courseSlug}/assignment/`"
|
||||||
|
></AssignmentList>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import log from "loglevel";
|
||||||
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
import { type DashboardPersonType, fetchDashboardPersons } from "@/services/dashboard";
|
||||||
|
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||||
|
import { COMPETENCE_NAVI_CERTIFICATE_QUERY } from "@/graphql/queries";
|
||||||
|
import { graphqlClient } from "@/graphql/client";
|
||||||
|
import type { CompetenceCertificateObjectType } from "@/gql/graphql";
|
||||||
|
import { calcCompetenceCertificateGrade } from "@/pages/competence/utils";
|
||||||
|
import _ from "lodash";
|
||||||
|
import type { CompetenceCertificateAssignment } from "@/types";
|
||||||
|
import { percentToRoundedGrade } from "@/services/assignmentService";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
agentRole: string;
|
||||||
|
courseSlug: string;
|
||||||
|
competenceCertificateId: string;
|
||||||
|
courseSessionId: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
log.debug("AgentCompetenceGradeDetailPage created", props);
|
||||||
|
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
|
const participants = ref<DashboardPersonType[]>([]);
|
||||||
|
const participantUserIds = computed(() => {
|
||||||
|
return (participants.value ?? []).map((p) => p.user_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
const courseSession = computed(() => {
|
||||||
|
return participants.value[0]?.course_sessions.find(
|
||||||
|
(cs) => cs.id === props.courseSessionId
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const certificateData = ref<CompetenceCertificateObjectType | undefined>(undefined);
|
||||||
|
|
||||||
|
function userGrade(userId: string) {
|
||||||
|
if (certificateData.value) {
|
||||||
|
const assignmentsWithUserCompletions = _.cloneDeep(
|
||||||
|
certificateData.value.assignments
|
||||||
|
);
|
||||||
|
for (const assignment of assignmentsWithUserCompletions) {
|
||||||
|
assignment.completions = assignment.completions?.filter(
|
||||||
|
(c) => c?.assignment_user?.id === userId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return calcCompetenceCertificateGrade(
|
||||||
|
assignmentsWithUserCompletions as unknown as CompetenceCertificateAssignment[],
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalAverageGrade = computed(() => {
|
||||||
|
if (certificateData.value) {
|
||||||
|
let divisor = 0;
|
||||||
|
const assignmentAverageGrades = certificateData.value.assignments.map(
|
||||||
|
(assignment) => {
|
||||||
|
const relevantCompletions = (assignment.completions ?? []).filter(
|
||||||
|
(c) => c?.completion_status == "EVALUATION_SUBMITTED"
|
||||||
|
);
|
||||||
|
|
||||||
|
const averagePercent =
|
||||||
|
_.sumBy(relevantCompletions, (c) => c?.evaluation_percent ?? 0) /
|
||||||
|
relevantCompletions.length;
|
||||||
|
|
||||||
|
if (averagePercent > 0.0001) {
|
||||||
|
divisor += assignment.competence_certificate_weight ?? 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return averagePercent * (assignment.competence_certificate_weight ?? 1);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return percentToRoundedGrade(
|
||||||
|
_.sum(assignmentAverageGrades) / (divisor ?? 1),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
log.debug("AgentAssignmentDetailPage mounted");
|
||||||
|
|
||||||
|
const personData = await fetchDashboardPersons("default");
|
||||||
|
participants.value = personData?.filter((p) => {
|
||||||
|
return p.course_sessions.find(
|
||||||
|
(cs) => cs.id === props.courseSessionId && cs.my_role === "BERUFSBILDNER"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await graphqlClient.query(COMPETENCE_NAVI_CERTIFICATE_QUERY, {
|
||||||
|
courseSlug: props.courseSlug,
|
||||||
|
courseSessionId: props.courseSessionId,
|
||||||
|
userIds: participantUserIds.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
certificateData.value =
|
||||||
|
res.data?.competence_certificate_list?.competence_certificates.find(
|
||||||
|
// @ts-ignore
|
||||||
|
(cc) => cc.id === props.competenceCertificateId
|
||||||
|
);
|
||||||
|
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-gray-200">
|
||||||
|
<div v-if="loading" class="m-8 flex justify-center">
|
||||||
|
<LoadingSpinner />
|
||||||
|
</div>
|
||||||
|
<div v-else class="container-large flex flex-col space-y-4">
|
||||||
|
<router-link
|
||||||
|
class="btn-text inline-flex items-center pl-0"
|
||||||
|
:to="`/statistic/${props.agentRole}/${props.courseSlug}/competence-grade`"
|
||||||
|
>
|
||||||
|
<it-icon-arrow-left />
|
||||||
|
<span>{{ $t("general.back") }}</span>
|
||||||
|
</router-link>
|
||||||
|
<div>
|
||||||
|
<h2 class="mb-8">{{ certificateData?.title }}</h2>
|
||||||
|
|
||||||
|
<div class="border-b bg-white px-6 py-6">
|
||||||
|
{{ courseSession?.session_title }}
|
||||||
|
</div>
|
||||||
|
<div class="heading-3 border-b bg-white px-6 py-6">
|
||||||
|
{{ $t("a.Durchschnittsnote") }}:
|
||||||
|
{{ totalAverageGrade }}
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-2">
|
||||||
|
<div
|
||||||
|
v-for="person in participants"
|
||||||
|
:key="person.user_id"
|
||||||
|
data-cy="person"
|
||||||
|
class="flex flex-col justify-between gap-4 border-b p-2 last:border-b-0 md:flex-row md:items-center md:justify-between md:gap-16"
|
||||||
|
>
|
||||||
|
<div class="w-full flex-auto md:w-1/2">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<img
|
||||||
|
class="inline-block h-11 w-11 rounded-full"
|
||||||
|
:src="
|
||||||
|
person.avatar_url_small ||
|
||||||
|
'/static/avatars/myvbv-default-avatar.png'
|
||||||
|
"
|
||||||
|
:alt="`${person.first_name} ${person.last_name}`"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div class="text-bold">
|
||||||
|
{{ person.first_name }}
|
||||||
|
{{ person.last_name }}
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-900">{{ person.email }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-auto items-center gap-2 md:w-1/4">
|
||||||
|
<div>{{ $t("a.Note") }}:</div>
|
||||||
|
<div class="min-w-12 text-center">
|
||||||
|
<div
|
||||||
|
class="rounded px-2 py-1 font-bold"
|
||||||
|
:class="{ 'bg-red-400': (userGrade(person.user_id) ?? 4) < 4 }"
|
||||||
|
>
|
||||||
|
{{ userGrade(person.user_id) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex-auto items-end md:w-1/4 md:text-end">
|
||||||
|
<router-link
|
||||||
|
:to="{
|
||||||
|
name: 'profileLearningPath',
|
||||||
|
params: {
|
||||||
|
userId: person.user_id,
|
||||||
|
courseSlug: props.courseSlug,
|
||||||
|
},
|
||||||
|
query: { courseSessionId: props.courseSessionId },
|
||||||
|
}"
|
||||||
|
data-cy="person-learning-path-link"
|
||||||
|
class="link w-full lg:text-right"
|
||||||
|
>
|
||||||
|
{{ $t("a.Profil anzeigen") }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,222 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import log from "loglevel";
|
||||||
|
import { computed, onMounted, type Ref, ref } from "vue";
|
||||||
|
import {
|
||||||
|
courseIdForCourseSlug,
|
||||||
|
fetchMentorCompetenceSummary,
|
||||||
|
} from "@/services/dashboard";
|
||||||
|
import type { AssignmentStatisticsRecordType, BaseStatisticsType } from "@/gql/graphql";
|
||||||
|
import { useDashboardStore } from "@/stores/dashboard";
|
||||||
|
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { percentToRoundedGrade } from "@/services/assignmentService";
|
||||||
|
import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue";
|
||||||
|
import type { StatisticsFilterItem } from "@/types";
|
||||||
|
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
agentRole: string;
|
||||||
|
courseSlug: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
log.debug("AgentCompetenceGradePage created", props);
|
||||||
|
|
||||||
|
const loading = ref(true);
|
||||||
|
const courseId = ref<string | undefined>(undefined);
|
||||||
|
const agentAssignmentData = ref<BaseStatisticsType | null>(null);
|
||||||
|
|
||||||
|
const courseSessionName = (courseSessionId: string) => {
|
||||||
|
return (
|
||||||
|
agentAssignmentData.value?.course_session_properties?.sessions.find(
|
||||||
|
(session) => session.id === courseSessionId
|
||||||
|
)?.name ?? ""
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const statisticFilter: Ref<typeof StatisticFilterList | null> = ref(null);
|
||||||
|
const filteredItems = computed(() => {
|
||||||
|
if (!statisticFilter.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return statisticFilter.value.getFilteredItems();
|
||||||
|
});
|
||||||
|
const totalAverageGrade = computed(() => {
|
||||||
|
return percentToRoundedGrade(
|
||||||
|
_.sumBy(filteredItems.value, (i) => {
|
||||||
|
return (i as GroupedAssignmentEntry).averageEvaluationPercent ?? 0;
|
||||||
|
}) / filteredItems.value.length,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await dashboardStore.loadDashboardDetails();
|
||||||
|
courseId.value = courseIdForCourseSlug(
|
||||||
|
dashboardStore.dashboardConfigsv2,
|
||||||
|
props.courseSlug
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!courseId.value) {
|
||||||
|
log.error("CourseId not found for courseSlug", props.courseSlug);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("courseId", courseId.value);
|
||||||
|
agentAssignmentData.value = await fetchMentorCompetenceSummary(
|
||||||
|
courseId.value,
|
||||||
|
props.agentRole
|
||||||
|
);
|
||||||
|
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
interface GroupedAssignmentEntry extends StatisticsFilterItem {
|
||||||
|
competenceCertificateId: string;
|
||||||
|
competenceCertificateTitle: string;
|
||||||
|
course_session_title: string;
|
||||||
|
assignments: AssignmentStatisticsRecordType[];
|
||||||
|
sumAverageEvaluationPercent: number;
|
||||||
|
averageEvaluationPercent: number | null;
|
||||||
|
averageGrade: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGroupedAssignmentEntry(
|
||||||
|
item: StatisticsFilterItem
|
||||||
|
): item is GroupedAssignmentEntry {
|
||||||
|
return (item as GroupedAssignmentEntry).competenceCertificateId !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const courseSessionCompetenceAssignments = computed(() => {
|
||||||
|
let resultArray = [] as GroupedAssignmentEntry[];
|
||||||
|
|
||||||
|
// group assignments by competence and course session
|
||||||
|
for (const assignment of agentAssignmentData.value?.assignments.records ?? []) {
|
||||||
|
const entry = resultArray.find(
|
||||||
|
(r) =>
|
||||||
|
r.competenceCertificateId === assignment.competence_certificate_id &&
|
||||||
|
r.course_session_id === assignment.course_session_id
|
||||||
|
);
|
||||||
|
if (entry) {
|
||||||
|
if (assignment.metrics.ranking_completed) {
|
||||||
|
entry.assignments.push(assignment);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const newEntry = {
|
||||||
|
_id: `${assignment.competence_certificate_id}-${assignment.course_session_id}`,
|
||||||
|
competenceCertificateId: assignment.competence_certificate_id ?? "",
|
||||||
|
competenceCertificateTitle: assignment.competence_certificate_title ?? "",
|
||||||
|
generation: assignment.generation ?? "",
|
||||||
|
region: assignment.region ?? "",
|
||||||
|
course_session_id: assignment.course_session_id ?? "",
|
||||||
|
course_session_title: assignment.course_session_title ?? "",
|
||||||
|
assignments: [] as AssignmentStatisticsRecordType[],
|
||||||
|
sumAverageEvaluationPercent: 0,
|
||||||
|
averageEvaluationPercent: null,
|
||||||
|
averageGrade: null,
|
||||||
|
circle_id: assignment.circle_id ?? "",
|
||||||
|
};
|
||||||
|
if (assignment && assignment.metrics.ranking_completed) {
|
||||||
|
newEntry.assignments.push(assignment);
|
||||||
|
}
|
||||||
|
resultArray.push(newEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter out entries without assignments
|
||||||
|
resultArray = resultArray.filter((entry) => entry.assignments.length > 0);
|
||||||
|
|
||||||
|
// calculate average grade
|
||||||
|
for (const entry of resultArray) {
|
||||||
|
entry.sumAverageEvaluationPercent = _.sumBy(entry.assignments, (a) => {
|
||||||
|
return (
|
||||||
|
(a.metrics.average_evaluation_percent ?? 0) *
|
||||||
|
(a.metrics.competence_certificate_weight ?? 1)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
entry.averageEvaluationPercent =
|
||||||
|
entry.sumAverageEvaluationPercent /
|
||||||
|
_.sumBy(entry.assignments, (a) => {
|
||||||
|
return a.metrics.competence_certificate_weight ?? 1;
|
||||||
|
});
|
||||||
|
entry.averageGrade = percentToRoundedGrade(entry.averageEvaluationPercent, false);
|
||||||
|
}
|
||||||
|
return _.orderBy(
|
||||||
|
resultArray,
|
||||||
|
["course_session_title", "competenceCertificateTitle"],
|
||||||
|
["asc", "asc"]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-gray-200">
|
||||||
|
<div v-if="loading" class="m-8 flex justify-center">
|
||||||
|
<LoadingSpinner />
|
||||||
|
</div>
|
||||||
|
<div v-else class="container-large flex flex-col space-y-4">
|
||||||
|
<router-link class="btn-text inline-flex items-center pl-0" to="/">
|
||||||
|
<it-icon-arrow-left />
|
||||||
|
<span>{{ $t("general.back") }}</span>
|
||||||
|
</router-link>
|
||||||
|
<div>
|
||||||
|
<h2 class="mb-8">{{ $t("a.Kompetenznachweise") }}</h2>
|
||||||
|
|
||||||
|
<div class="bg-white py-2">
|
||||||
|
<StatisticFilterList
|
||||||
|
v-if="
|
||||||
|
agentAssignmentData?.course_session_properties &&
|
||||||
|
courseSessionCompetenceAssignments?.length
|
||||||
|
"
|
||||||
|
ref="statisticFilter"
|
||||||
|
:course-session-properties="agentAssignmentData?.course_session_properties"
|
||||||
|
:items="courseSessionCompetenceAssignments"
|
||||||
|
:hide-circle-filter="true"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="heading-3 border-b px-6 py-4">
|
||||||
|
{{ $t("a.Durchschnittsnote") }}: {{ totalAverageGrade }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #default="{ item: item }">
|
||||||
|
<div
|
||||||
|
v-if="isGroupedAssignmentEntry(item)"
|
||||||
|
class="flex flex-col justify-between gap-4 border-b last:border-b-0 md:flex-row md:items-center md:justify-between md:gap-16"
|
||||||
|
>
|
||||||
|
<div class="w-full flex-auto md:w-1/2">
|
||||||
|
<span class="text-bold">
|
||||||
|
{{ item.competenceCertificateTitle }}
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
{{ $t("a.Durchführung") }} «{{
|
||||||
|
courseSessionName(item.course_session_id)
|
||||||
|
}}»
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-auto items-center gap-2 md:w-1/4">
|
||||||
|
<div>{{ $t("a.Durchschnittsnote") }}:</div>
|
||||||
|
<div class="min-w-12 text-center">
|
||||||
|
<div
|
||||||
|
class="rounded px-2 py-1 font-bold"
|
||||||
|
:class="{ 'bg-red-400': (item.averageGrade ?? 4) < 4 }"
|
||||||
|
>
|
||||||
|
{{ item.averageGrade }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex-auto items-end md:w-1/4 md:text-end">
|
||||||
|
<router-link
|
||||||
|
class="underline"
|
||||||
|
:to="`/statistic/${props.agentRole}/${props.courseSlug}/competence-grade/${item.course_session_id}/${item.competenceCertificateId}`"
|
||||||
|
data-cy="basebox.detailsLink"
|
||||||
|
>
|
||||||
|
{{ $t("a.Details anschauen") }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</StatisticFilterList>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import type {
|
import type {
|
||||||
AssignmentCompletionMetricsType,
|
AssignmentCompletionMetricsType,
|
||||||
AssignmentStatisticsRecordType,
|
AssignmentStatisticsRecordType,
|
||||||
CourseStatisticsType,
|
BaseStatisticsType,
|
||||||
StatisticsCircleDataType,
|
StatisticsCircleDataType,
|
||||||
} from "@/gql/graphql";
|
} from "@/gql/graphql";
|
||||||
import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue";
|
import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue";
|
||||||
|
|
@ -16,9 +16,10 @@ import { useUserStore } from "@/stores/user";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseStatistics: CourseStatisticsType;
|
courseStatistics: BaseStatisticsType;
|
||||||
courseSessionName: (sessionId: string) => string;
|
courseSessionName: (sessionId: string) => string;
|
||||||
circleMeta: (circleId: string) => StatisticsCircleDataType;
|
circleMeta: (circleId: string) => StatisticsCircleDataType;
|
||||||
|
detailBaseUrl?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const statisticFilter: Ref<typeof StatisticFilterList | null> = ref(null);
|
const statisticFilter: Ref<typeof StatisticFilterList | null> = ref(null);
|
||||||
|
|
@ -51,6 +52,13 @@ async function exportData() {
|
||||||
const filteredItems = statisticFilter.value.getFilteredItems();
|
const filteredItems = statisticFilter.value.getFilteredItems();
|
||||||
await exportDataAsXls(filteredItems, exportCompetenceElements, userStore.language);
|
await exportDataAsXls(filteredItems, exportCompetenceElements, userStore.language);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const itemDetailUrl = (item: AssignmentStatisticsRecordType) => {
|
||||||
|
if (props.detailBaseUrl) {
|
||||||
|
return `${props.detailBaseUrl}${item.learning_content_id}?courseSessionId=${item.course_session_id}`;
|
||||||
|
}
|
||||||
|
return item.details_url;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -114,8 +122,7 @@ async function exportData() {
|
||||||
></ItProgress>
|
></ItProgress>
|
||||||
<router-link
|
<router-link
|
||||||
class="underline"
|
class="underline"
|
||||||
target="_blank"
|
:to="itemDetailUrl(item as AssignmentStatisticsRecordType)"
|
||||||
:to="(item as AssignmentStatisticsRecordType).details_url"
|
|
||||||
>
|
>
|
||||||
{{ $t("a.Details anschauen") }}
|
{{ $t("a.Details anschauen") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,36 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCSRFFetch } from "@/fetchHelpers";
|
import { itPost } from "@/fetchHelpers";
|
||||||
import { getLearningMentorUrl } from "@/utils/utils";
|
import { getLearningMentorUrl } from "@/utils/utils";
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseId: string;
|
courseId: string;
|
||||||
invitationId: string;
|
invitationId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { data, error } = useCSRFFetch(
|
const loaded = ref<boolean>(false);
|
||||||
`/api/mentor/${props.courseId}/invitations/accept`,
|
const responseData = ref<any>(null);
|
||||||
{
|
const hasError = ref<boolean>(false);
|
||||||
onFetchError(ctx) {
|
const errorMessage = ref<string>("");
|
||||||
ctx.error = ctx.data;
|
|
||||||
return ctx;
|
onMounted(async () => {
|
||||||
},
|
const url = `/api/mentor/${props.courseId}/invitations/accept`;
|
||||||
}
|
itPost(url, {
|
||||||
)
|
|
||||||
.post({
|
|
||||||
invitation_id: props.invitationId,
|
invitation_id: props.invitationId,
|
||||||
})
|
})
|
||||||
.json();
|
.then((data) => {
|
||||||
|
responseData.value = data;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
hasError.value = true;
|
||||||
|
if (error.toString().includes("404")) {
|
||||||
|
errorMessage.value = "Einladung bereits akzeptiert";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loaded.value = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -28,9 +39,9 @@ const { data, error } = useCSRFFetch(
|
||||||
<header class="mb-8 mt-12">
|
<header class="mb-8 mt-12">
|
||||||
<h1 class="mb-8">{{ $t("a.Einladung") }}</h1>
|
<h1 class="mb-8">{{ $t("a.Einladung") }}</h1>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main v-if="loaded">
|
||||||
<div class="bg-white p-6">
|
<div class="bg-white p-6">
|
||||||
<template v-if="error">
|
<template v-if="hasError">
|
||||||
{{
|
{{
|
||||||
$t(
|
$t(
|
||||||
"a.Die Einladung konnte nicht akzeptiert werden. Bitte melde dich beim Support."
|
"a.Die Einladung konnte nicht akzeptiert werden. Bitte melde dich beim Support."
|
||||||
|
|
@ -50,8 +61,8 @@ const { data, error } = useCSRFFetch(
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div v-if="error.message" class="my-4">
|
<div v-if="errorMessage" class="my-4">
|
||||||
{{ $t("a.Fehlermeldung") }}: {{ error.message }}
|
{{ $t("a.Fehlermeldung") }}: {{ errorMessage }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|
@ -61,11 +72,16 @@ const { data, error } = useCSRFFetch(
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<template #name>
|
<template #name>
|
||||||
<b>{{ data.user.first_name }} {{ data.user.last_name }}</b>
|
<b>
|
||||||
|
{{ responseData.user.first_name }} {{ responseData.user.last_name }}
|
||||||
|
</b>
|
||||||
</template>
|
</template>
|
||||||
</i18next>
|
</i18next>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<a class="underline" :href="getLearningMentorUrl(data.course_slug)">
|
<a
|
||||||
|
class="underline"
|
||||||
|
:href="getLearningMentorUrl(responseData.course_slug)"
|
||||||
|
>
|
||||||
{{ $t("a.Übersicht anschauen") }}
|
{{ $t("a.Übersicht anschauen") }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Assignment, Participant } from "@/services/learningMentees";
|
import type { Assignment, UserShort } from "@/services/learningMentees";
|
||||||
import { useLearningMentees } from "@/services/learningMentees";
|
import { useLearningMentees } from "@/services/learningMentees";
|
||||||
import { computed, onMounted, type Ref } from "vue";
|
import { computed, onMounted, type Ref } from "vue";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
|
@ -11,13 +11,16 @@ const props = defineProps<{
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const learningMentees = useLearningMentees(courseSession.value.id);
|
const learningMentees = useLearningMentees(courseSession.value.id);
|
||||||
const participants = computed(() => learningMentees.summary.value?.participants);
|
|
||||||
const praxisAssignment: Ref<Assignment | null> = computed(() =>
|
const praxisAssignment: Ref<Assignment | null> = computed(() =>
|
||||||
learningMentees.getAssignmentById(props.praxisAssignmentId)
|
learningMentees.getAssignmentById(props.praxisAssignmentId)
|
||||||
);
|
);
|
||||||
|
|
||||||
const getParticipantById = (id: string): Participant | null => {
|
const getParticipantById = (id: string): UserShort | undefined => {
|
||||||
return participants.value?.find((participant) => participant.id === id) || null;
|
return (learningMentees.summary.value?.participant_relations ?? [])
|
||||||
|
.map((rel) => {
|
||||||
|
return rel.participant_user;
|
||||||
|
})
|
||||||
|
.find((user) => user.id === id);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
@ -47,6 +50,7 @@ onMounted(() => {
|
||||||
v-for="item in praxisAssignment.completions"
|
v-for="item in praxisAssignment.completions"
|
||||||
:key="item.user_id"
|
:key="item.user_id"
|
||||||
class="flex flex-col items-start justify-between gap-4 border-b py-2 pl-5 pr-5 last:border-b-0 md:flex-row md:items-center md:justify-between md:gap-16"
|
class="flex flex-col items-start justify-between gap-4 border-b py-2 pl-5 pr-5 last:border-b-0 md:flex-row md:items-center md:justify-between md:gap-16"
|
||||||
|
:data-cy="`praxis-assignment-feedback-${item.user_id}`"
|
||||||
>
|
>
|
||||||
<!-- Left -->
|
<!-- Left -->
|
||||||
<div class="flex flex-grow flex-row items-center justify-start">
|
<div class="flex flex-grow flex-row items-center justify-start">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Assignment, Participant } from "@/services/learningMentees";
|
import type { Assignment, UserShort } from "@/services/learningMentees";
|
||||||
import { useLearningMentees } from "@/services/learningMentees";
|
import { useLearningMentees } from "@/services/learningMentees";
|
||||||
import { computed, type Ref } from "vue";
|
import { computed, type Ref } from "vue";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
|
@ -15,14 +15,12 @@ const selfEvaluationFeedback: Ref<Assignment | null> = computed(() =>
|
||||||
learningMentees.getAssignmentById(props.learningUnitId)
|
learningMentees.getAssignmentById(props.learningUnitId)
|
||||||
);
|
);
|
||||||
|
|
||||||
const getParticipantById = (id: string): Participant | null => {
|
const getParticipantById = (id: string): UserShort | undefined => {
|
||||||
if (learningMentees.summary.value?.participants) {
|
return (learningMentees.summary.value?.participant_relations ?? [])
|
||||||
const found = learningMentees.summary.value.participants.find(
|
.map((rel) => {
|
||||||
(item) => item.id === id
|
return rel.participant_user;
|
||||||
);
|
})
|
||||||
return found || null;
|
.find((user) => user.id === id);
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -51,6 +49,7 @@ const getParticipantById = (id: string): Participant | null => {
|
||||||
v-for="item in selfEvaluationFeedback.completions"
|
v-for="item in selfEvaluationFeedback.completions"
|
||||||
:key="item.user_id"
|
:key="item.user_id"
|
||||||
class="flex flex-col items-start justify-between gap-4 border-b py-2 pl-5 pr-5 last:border-b-0 md:flex-row md:items-center md:justify-between md:gap-16"
|
class="flex flex-col items-start justify-between gap-4 border-b py-2 pl-5 pr-5 last:border-b-0 md:flex-row md:items-center md:justify-between md:gap-16"
|
||||||
|
:data-cy="`self-evalution-feedback-${item.user_id}`"
|
||||||
>
|
>
|
||||||
<!-- Left -->
|
<!-- Left -->
|
||||||
<div class="flex flex-grow flex-row items-center justify-start">
|
<div class="flex flex-grow flex-row items-center justify-start">
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ const isMentorsLoading = computed(() => learningMentors.loading.value);
|
||||||
|
|
||||||
const mentors = computed(() => {
|
const mentors = computed(() => {
|
||||||
return learningMentors.learningMentors.value.map((mentor) => ({
|
return learningMentors.learningMentors.value.map((mentor) => ({
|
||||||
id: mentor.mentor.id,
|
id: mentor.agent.id,
|
||||||
name: `${mentor.mentor.first_name} ${mentor.mentor.last_name}`,
|
name: `${mentor.agent.first_name} ${mentor.agent.last_name}`,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -100,6 +100,7 @@ const onRequestFeedback = async () => {
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="large"
|
size="large"
|
||||||
:disabled="!currentSessionRequestedMentor"
|
:disabled="!currentSessionRequestedMentor"
|
||||||
|
data-cy="request-feedback-button"
|
||||||
@click="onRequestFeedback"
|
@click="onRequestFeedback"
|
||||||
>
|
>
|
||||||
<p v-if="!currentSessionRequestedMentor">
|
<p v-if="!currentSessionRequestedMentor">
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ const signUpURL = computed(() => getSignUpURL(constructParams()));
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<p class="mb-4 mt-12">{{ $t("a.Hast du schon ein Konto?") }}</p>
|
<p class="mb-4 mt-12">{{ $t("a.Hast du schon ein Konto?") }}</p>
|
||||||
<a :href="loginURL" class="btn-secondary">
|
<a :href="loginURL" class="btn-secondary" data-cy="login-button">
|
||||||
{{ $t("a.Anmelden") }}
|
{{ $t("a.Anmelden") }}
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import { useFetch } from "@vueuse/core";
|
import { useFetch } from "@vueuse/core";
|
||||||
|
|
@ -12,18 +12,24 @@ const props = defineProps<{
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const pages = ref([
|
const pages = [
|
||||||
{
|
{
|
||||||
label: t("general.learningPath"),
|
label: t("general.learningPath"),
|
||||||
route: "profileLearningPath",
|
route: "profileLearningPath",
|
||||||
routeMatch: "profileLearningPath",
|
routeMatch: "profileLearningPath",
|
||||||
|
childrenRouteMatches: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("a.KompetenzNavi"),
|
label: t("a.KompetenzNavi"),
|
||||||
route: "competenceMain",
|
route: "competenceMain",
|
||||||
routeMatch: "profileCompetence",
|
routeMatch: "profileCompetence",
|
||||||
|
childrenRouteMatches: [
|
||||||
|
"competenceCertificates",
|
||||||
|
"competenceCertificateDetail",
|
||||||
|
"competenceEvaluations",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]);
|
];
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const { data: user } = useFetch(
|
const { data: user } = useFetch(
|
||||||
|
|
@ -35,9 +41,16 @@ const router = useRouter();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// if current route name not in pages, redirect to first page
|
// if current route name not in pages, redirect to first page
|
||||||
if (route.name && !pages.value.find((page) => page.route === route.name)) {
|
if (
|
||||||
|
route.name &&
|
||||||
|
!pages.find((page) => {
|
||||||
|
const routeName = route.name?.toString() || "";
|
||||||
|
const routeMatch = [...page.childrenRouteMatches, page.routeMatch];
|
||||||
|
return routeMatch.some((match) => routeName.includes(match));
|
||||||
|
})
|
||||||
|
) {
|
||||||
router.push({
|
router.push({
|
||||||
name: pages.value[0].route,
|
name: pages[0].route,
|
||||||
params: { userId: props.userId, courseSlug: props.courseSlug },
|
params: { userId: props.userId, courseSlug: props.courseSlug },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -339,6 +339,32 @@ const router = createRouter({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: "/statistic/:agentRole/:courseSlug/assignment",
|
||||||
|
props: true,
|
||||||
|
component: () =>
|
||||||
|
import("@/pages/dashboard/agentAssignment/AgentAssignmentStatisticPage.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/statistic/:agentRole/:courseSlug/assignment/:assignmentId",
|
||||||
|
props: true,
|
||||||
|
component: () =>
|
||||||
|
import("@/pages/dashboard/agentAssignment/AgentAssignmentDetailPage.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/statistic/:agentRole/:courseSlug/competence-grade",
|
||||||
|
props: true,
|
||||||
|
component: () =>
|
||||||
|
import("@/pages/dashboard/agentAssignment/AgentCompetenceGradePage.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/statistic/:agentRole/:courseSlug/competence-grade/:courseSessionId/:competenceCertificateId",
|
||||||
|
props: true,
|
||||||
|
component: () =>
|
||||||
|
import("@/pages/dashboard/agentAssignment/AgentCompetenceGradeDetailPage.vue"),
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "/shop",
|
path: "/shop",
|
||||||
component: () => import("@/pages/ShopPage.vue"),
|
component: () => import("@/pages/ShopPage.vue"),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useCourseSessionDetailQuery } from "@/composables";
|
import { useCourseSessionDetailQuery } from "@/composables";
|
||||||
import { itGet } from "@/fetchHelpers";
|
import { itPost } from "@/fetchHelpers";
|
||||||
import type {
|
import type {
|
||||||
Assignment,
|
Assignment,
|
||||||
AssignmentCompletion,
|
AssignmentCompletion,
|
||||||
|
|
@ -19,12 +19,16 @@ export interface GradedUser {
|
||||||
export async function loadAssignmentCompletionStatusData(
|
export async function loadAssignmentCompletionStatusData(
|
||||||
assignmentId: string,
|
assignmentId: string,
|
||||||
courseSessionId: string,
|
courseSessionId: string,
|
||||||
learningContentId: string
|
learningContentId: string,
|
||||||
|
userSelectionIds: string[] | undefined = undefined
|
||||||
) {
|
) {
|
||||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
|
||||||
const assignmentCompletionData = (await itGet(
|
const assignmentCompletionData = (await itPost(
|
||||||
`/api/assignment/${assignmentId}/${courseSessionId}/status/`
|
`/api/assignment/${assignmentId}/${courseSessionId}/status/`,
|
||||||
|
{
|
||||||
|
user_selection_ids: userSelectionIds ?? [],
|
||||||
|
}
|
||||||
)) as UserAssignmentCompletionStatus[];
|
)) as UserAssignmentCompletionStatus[];
|
||||||
|
|
||||||
const members = courseSessionDetailResult.filterMembers();
|
const members = courseSessionDetailResult.filterMembers();
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,3 @@
|
||||||
import type {
|
|
||||||
CompetenceCertificateForUserQueryQuery,
|
|
||||||
CompetenceCertificateListObjectType,
|
|
||||||
CompetenceCertificateQueryQuery,
|
|
||||||
} from "@/gql/graphql";
|
|
||||||
import type { PerformanceCriteria } from "@/types";
|
import type { PerformanceCriteria } from "@/types";
|
||||||
import groupBy from "lodash/groupBy";
|
import groupBy from "lodash/groupBy";
|
||||||
|
|
||||||
|
|
@ -22,42 +17,3 @@ export function calcPerformanceCriteriaStatusCount(criteria: PerformanceCriteria
|
||||||
FAIL: 0,
|
FAIL: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type guards
|
|
||||||
export function isCompetenceCertificateForUserQueryQuery(
|
|
||||||
data: any
|
|
||||||
): data is CompetenceCertificateForUserQueryQuery {
|
|
||||||
return (
|
|
||||||
(data as CompetenceCertificateForUserQueryQuery)
|
|
||||||
.competence_certificate_list_for_user !== undefined
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCompetenceCertificateQueryQuery(
|
|
||||||
data: any
|
|
||||||
): data is CompetenceCertificateQueryQuery {
|
|
||||||
return (
|
|
||||||
(data as CompetenceCertificateQueryQuery).competence_certificate_list !== undefined
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCertificates(
|
|
||||||
data: any,
|
|
||||||
userId: string | null
|
|
||||||
): CompetenceCertificateListObjectType | null {
|
|
||||||
if (!data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let certificates = null;
|
|
||||||
|
|
||||||
if (userId && isCompetenceCertificateForUserQueryQuery(data)) {
|
|
||||||
certificates = data.competence_certificate_list_for_user;
|
|
||||||
} else if (isCompetenceCertificateQueryQuery(data)) {
|
|
||||||
certificates = data.competence_certificate_list;
|
|
||||||
} else {
|
|
||||||
// Handle case where data does not match expected types
|
|
||||||
console.error("Data structure is not recognized!");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (certificates as unknown as CompetenceCertificateListObjectType) ?? null;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
|
|
||||||
import { itGetCached, itPost } from "@/fetchHelpers";
|
import { itGetCached, itPost } from "@/fetchHelpers";
|
||||||
import type {
|
import type {
|
||||||
AssignmentsStatisticsType,
|
BaseStatisticsType,
|
||||||
CourseProgressType,
|
CourseProgressType,
|
||||||
CourseStatisticsType,
|
CourseStatisticsType,
|
||||||
DashboardConfigType,
|
DashboardConfigType,
|
||||||
|
|
@ -25,14 +25,16 @@ export type DashboardPersonRoleType =
|
||||||
| "EXPERT"
|
| "EXPERT"
|
||||||
| "MEMBER"
|
| "MEMBER"
|
||||||
| "LEARNING_MENTOR"
|
| "LEARNING_MENTOR"
|
||||||
| "LEARNING_MENTEE";
|
| "BERUFSBILDNER"
|
||||||
|
| "PARTICIPANT";
|
||||||
|
|
||||||
export type DashboardRoleKeyType =
|
export type DashboardRoleKeyType =
|
||||||
| "Supervisor"
|
| "Supervisor"
|
||||||
| "Trainer"
|
| "Trainer"
|
||||||
| "Member"
|
| "Member"
|
||||||
| "MentorUK"
|
| "MentorUK"
|
||||||
| "MentorVV";
|
| "MentorVV"
|
||||||
|
| "Berufsbildner";
|
||||||
|
|
||||||
export type WidgetType =
|
export type WidgetType =
|
||||||
| "ProgressWidget"
|
| "ProgressWidget"
|
||||||
|
|
@ -41,7 +43,8 @@ export type WidgetType =
|
||||||
| "MentorPersonWidget"
|
| "MentorPersonWidget"
|
||||||
| "MentorCompetenceWidget"
|
| "MentorCompetenceWidget"
|
||||||
| "CompetenceCertificateWidget"
|
| "CompetenceCertificateWidget"
|
||||||
| "UKStatisticsWidget";
|
| "UKStatisticsWidget"
|
||||||
|
| "UKBerufsbildnerStatisticsWidget";
|
||||||
|
|
||||||
export type DashboardPersonCourseSessionType = {
|
export type DashboardPersonCourseSessionType = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -107,6 +110,7 @@ export const fetchStatisticData = async (
|
||||||
console.error("Error fetching statistics for course ID:", courseId, res.error);
|
console.error("Error fetching statistics for course ID:", courseId, res.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
return res.data?.course_statistics || null;
|
return res.data?.course_statistics || null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching statistics for course ID: ${courseId}`, error);
|
console.error(`Error fetching statistics for course ID: ${courseId}`, error);
|
||||||
|
|
@ -149,17 +153,33 @@ export const fetchDashboardConfig = async (): Promise<DashboardConfigType[] | nu
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchMentorCompetenceSummary = async (
|
export const fetchMentorCompetenceSummary = async (
|
||||||
courseId: string
|
courseId: string,
|
||||||
): Promise<AssignmentsStatisticsType | null> => {
|
roleKey: string
|
||||||
|
): Promise<BaseStatisticsType | null> => {
|
||||||
|
let agentRole = "";
|
||||||
|
if (
|
||||||
|
["MentorUK".toLowerCase(), "MentorVV".toLowerCase()].includes(roleKey.toLowerCase())
|
||||||
|
) {
|
||||||
|
agentRole = "LEARNING_MENTOR";
|
||||||
|
} else if (roleKey.toLowerCase() === "Berufsbildner".toLowerCase()) {
|
||||||
|
agentRole = "BERUFSBILDNER";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!agentRole) {
|
||||||
|
console.error(`Invalid role key for competence summary: ${roleKey}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await graphqlClient.query(DASHBOARD_MENTOR_COMPETENCE_SUMMARY, {
|
const res = await graphqlClient.query(DASHBOARD_MENTOR_COMPETENCE_SUMMARY, {
|
||||||
courseId,
|
courseId,
|
||||||
|
agentRole,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
console.error("Error fetching data for course ID:", courseId, res.error);
|
console.error("Error fetching data for course ID:", courseId, res.error);
|
||||||
}
|
}
|
||||||
return res.data?.mentor_course_statistics?.assignments || null;
|
return res.data?.mentor_course_statistics || null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching data for course ID: ${courseId}`, error);
|
console.error(`Error fetching data for course ID: ${courseId}`, error);
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { itGet } from "@/fetchHelpers";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { ref, watchEffect } from "vue";
|
import { ref, watchEffect } from "vue";
|
||||||
|
|
||||||
export interface Participant {
|
export interface UserShort {
|
||||||
id: string;
|
id: string;
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
|
|
@ -12,6 +12,14 @@ export interface Participant {
|
||||||
language: string;
|
language: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AgentParticipantRelation {
|
||||||
|
id: string;
|
||||||
|
role: "LEARNING_MENTOR";
|
||||||
|
course_session_id: number;
|
||||||
|
agent: UserShort;
|
||||||
|
participant_user: UserShort;
|
||||||
|
}
|
||||||
|
|
||||||
interface Circle {
|
interface Circle {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -40,9 +48,8 @@ export interface Assignment {
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Summary {
|
export interface LearningMentorSummary {
|
||||||
mentor_id: string;
|
participant_relations: AgentParticipantRelation[];
|
||||||
participants: Participant[];
|
|
||||||
circles: Circle[];
|
circles: Circle[];
|
||||||
assignments: Assignment[];
|
assignments: Assignment[];
|
||||||
}
|
}
|
||||||
|
|
@ -51,7 +58,7 @@ export const useLearningMentees = (
|
||||||
courseSessionId: string | Ref<string> | (() => string)
|
courseSessionId: string | Ref<string> | (() => string)
|
||||||
) => {
|
) => {
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const summary: Ref<Summary | null> = ref(null);
|
const summary: Ref<LearningMentorSummary | null> = ref(null);
|
||||||
const error = ref(null);
|
const error = ref(null);
|
||||||
|
|
||||||
const getAssignmentById = (id: string): Assignment | null => {
|
const getAssignmentById = (id: string): Assignment | null => {
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,6 @@ export const useDashboardStore = defineStore("dashboard", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadStatisticsData = async (id: string) => {
|
const loadStatisticsData = async (id: string) => {
|
||||||
const data = await fetchStatisticData(id);
|
|
||||||
dashBoardDataCache[id] = data;
|
|
||||||
currentDashBoardData.value = data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadStatisticsDatav2 = async (id: string) => {
|
|
||||||
const data = await fetchStatisticData(id);
|
const data = await fetchStatisticData(id);
|
||||||
dashBoardDataCache[id] = data;
|
dashBoardDataCache[id] = data;
|
||||||
return data;
|
return data;
|
||||||
|
|
@ -68,6 +62,5 @@ export const useDashboardStore = defineStore("dashboard", () => {
|
||||||
currentDashBoardData,
|
currentDashBoardData,
|
||||||
loading,
|
loading,
|
||||||
loadStatisticsData,
|
loadStatisticsData,
|
||||||
loadStatisticsDatav2,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -399,7 +399,7 @@ export interface CompetenceCertificateAssignment extends BaseCourseWagtailPage {
|
||||||
circle: CircleLight;
|
circle: CircleLight;
|
||||||
})
|
})
|
||||||
| null;
|
| null;
|
||||||
completion: {
|
completions: {
|
||||||
id: string;
|
id: string;
|
||||||
completion_status: AssignmentCompletionStatus;
|
completion_status: AssignmentCompletionStatus;
|
||||||
evaluation_submitted_at: string | null;
|
evaluation_submitted_at: string | null;
|
||||||
|
|
@ -413,7 +413,7 @@ export interface CompetenceCertificateAssignment extends BaseCourseWagtailPage {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
} | null;
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompetenceCertificate extends BaseCourseWagtailPage {
|
export interface CompetenceCertificate extends BaseCourseWagtailPage {
|
||||||
|
|
@ -473,15 +473,16 @@ export interface ExpertSessionUser extends CourseSessionUser {
|
||||||
role: "EXPERT";
|
role: "EXPERT";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Mentor {
|
export interface Agent {
|
||||||
id: number;
|
id: number;
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LearningMentor {
|
export interface AgentParticipantRelation {
|
||||||
id: number;
|
id: number;
|
||||||
mentor: Mentor;
|
role: "LEARNING_MENTOR";
|
||||||
|
agent: Agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CourseSessionDetail = CourseSessionObjectType;
|
export type CourseSessionDetail = CourseSessionObjectType;
|
||||||
|
|
@ -627,6 +628,7 @@ export type DashboardPersonsPageMode = "default" | "competenceMetrics";
|
||||||
export interface StatisticsFilterItem {
|
export interface StatisticsFilterItem {
|
||||||
_id: string;
|
_id: string;
|
||||||
course_session_id: string;
|
course_session_id: string;
|
||||||
|
region: string;
|
||||||
generation: string;
|
generation: string;
|
||||||
circle_id: string;
|
circle_id: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,9 @@ export default defineConfig(({ mode }) => {
|
||||||
],
|
],
|
||||||
define: {},
|
define: {},
|
||||||
server: {
|
server: {
|
||||||
|
host: true,
|
||||||
port: 5173,
|
port: 5173,
|
||||||
hmr: { port: 5173 },
|
strictPort: true,
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
const { defineConfig } = require("cypress");
|
import { defineConfig } from "cypress";
|
||||||
const { cloudPlugin } = require("cypress-cloud/plugin");
|
import { cloudPlugin } from "cypress-cloud/plugin";
|
||||||
|
import tasks from "./cypress/plugins/index.mjs";
|
||||||
|
|
||||||
module.exports = defineConfig({
|
export default defineConfig({
|
||||||
projectId: "RVEZS1",
|
projectId: "RVEZS1",
|
||||||
|
chromeWebSecurity: false,
|
||||||
watchForFileChanges: false,
|
watchForFileChanges: false,
|
||||||
video: true,
|
video: true,
|
||||||
viewportWidth: 1280,
|
viewportWidth: 1280,
|
||||||
|
|
@ -19,6 +21,7 @@ module.exports = defineConfig({
|
||||||
e2e: {
|
e2e: {
|
||||||
// experimentalSessionAndOrigin: true,
|
// experimentalSessionAndOrigin: true,
|
||||||
setupNodeEvents(on, config) {
|
setupNodeEvents(on, config) {
|
||||||
|
tasks(on, config);
|
||||||
return cloudPlugin(on, config);
|
return cloudPlugin(on, config);
|
||||||
},
|
},
|
||||||
baseUrl: "http://localhost:8001",
|
baseUrl: "http://localhost:8001",
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { login } from "../helpers";
|
||||||
|
import { TEST_STUDENT1_VV_USER_ID } from "../../consts";
|
||||||
|
|
||||||
|
describe("mentorInvitation.cy.js", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.manageCommand("cypress_reset");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Teilnehmer macht lädt Lernbegleitung ein; Lernbegleitung akzeptiert Einladung", () => {
|
||||||
|
login("student-vv@eiger-versicherungen.ch", "test");
|
||||||
|
cy.visit("/course/versicherungsvermittler-in/learn");
|
||||||
|
cy.get("[data-cy=navigation-learning-mentor-link]").click();
|
||||||
|
cy.get('[data-cy="lm-invite-mentor-button"]').click();
|
||||||
|
cy.get("#mentor-email").type("empty@example.com");
|
||||||
|
cy.get('[data-cy="invite-mentor-button"]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="mentor-empty@example.com"]').should(
|
||||||
|
"contain",
|
||||||
|
"Die Einladung wurde noch nicht angenommen.",
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.task(
|
||||||
|
"runSql",
|
||||||
|
"select target_url from learning_mentor_mentorinvitation where email = 'empty@example.com'",
|
||||||
|
).then((res) => {
|
||||||
|
const invitationUrl = res.rows[0].target_url;
|
||||||
|
console.log(invitationUrl);
|
||||||
|
|
||||||
|
cy.visit("/");
|
||||||
|
cy.get('[data-cy="header-profile"]').click();
|
||||||
|
cy.get('[data-cy="logout-button"]').click();
|
||||||
|
cy.wait(1000);
|
||||||
|
|
||||||
|
// try to accept invitation
|
||||||
|
cy.visit(invitationUrl);
|
||||||
|
cy.get('[data-cy="login-button"]').click();
|
||||||
|
cy.get("#username").type("empty@example.com");
|
||||||
|
cy.get("#password").type("test");
|
||||||
|
cy.get('[data-cy="login-button"]').click();
|
||||||
|
|
||||||
|
cy.get(".bg-white").should(
|
||||||
|
"contain",
|
||||||
|
"Du hast die Einladung von Viktor Vollgas erfolgreich akzeptiert.",
|
||||||
|
);
|
||||||
|
cy.contains("Übersicht anschauen").click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="lm-my-mentees"]').should(
|
||||||
|
"contain",
|
||||||
|
"Personen, die du begleitest",
|
||||||
|
);
|
||||||
|
cy.get('[data-cy="lm-my-mentees"]').should(
|
||||||
|
"contain",
|
||||||
|
"student-vv@eiger-versicherungen.ch",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { login, logout } from "../../helpers";
|
||||||
|
import { TEST_STUDENT1_VV_USER_ID } from "../../../consts";
|
||||||
|
|
||||||
|
describe("fremdeinschätzung.cy.js", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.manageCommand("cypress_reset --create-learning-mentor");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("teilnehmer macht selbsteinschätzung; begleiter kann fremdeinschätzung machen", () => {
|
||||||
|
// teilnehmer macht selbsteinschätzung
|
||||||
|
login("student-vv@eiger-versicherungen.ch", "test");
|
||||||
|
cy.visit(
|
||||||
|
"/course/versicherungsvermittler-in/learn/basis/evaluate/mein-neuer-job-arbeitstechnik-soziale-medien-datenschutz-und-beratungspflichten",
|
||||||
|
);
|
||||||
|
cy.makeSelfEvaluation([true, false, true], false);
|
||||||
|
|
||||||
|
cy.get('[data-cy="dropdown-select"]').click();
|
||||||
|
cy.get('[data-cy="dropdown-select-option-Micheala Weber-Mentor"]').click();
|
||||||
|
cy.get('[data-cy="request-feedback-button"]').click();
|
||||||
|
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||||
|
cy.visit("/");
|
||||||
|
cy.get('[data-cy="header-profile"]').click();
|
||||||
|
cy.get('[data-cy="logout-button"]').click();
|
||||||
|
cy.wait(1000);
|
||||||
|
|
||||||
|
// fremdeinschätzung vornehmen
|
||||||
|
login("test-mentor1@example.com", "test");
|
||||||
|
cy.visit("/");
|
||||||
|
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="panel-versicherungsvermittler-in"] [data-cy="dashboard.mentor.openTasksCount"]',
|
||||||
|
).should("contain", "1");
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="panel-versicherungsvermittler-in"] [data-cy="dashboard.mentor.openTasksCount"] [data-cy="basebox.detailsLink"]',
|
||||||
|
).click();
|
||||||
|
|
||||||
|
cy.contains("Fremdeinschätzung vornehmen").click();
|
||||||
|
|
||||||
|
// viktor vollgas auswählen
|
||||||
|
cy.get(
|
||||||
|
`[data-cy="self-evalution-feedback-${TEST_STUDENT1_VV_USER_ID}"]`,
|
||||||
|
).should("contain", "Selbsteinschätzung geteilt");
|
||||||
|
cy.contains("Fremdeinschätzung vornehmen").click();
|
||||||
|
cy.makeSelfEvaluation([true, true, true], false);
|
||||||
|
|
||||||
|
cy.get('[data-cy="feedback-release-button"]').click();
|
||||||
|
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||||
|
|
||||||
|
cy.visit("/");
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="panel-versicherungsvermittler-in"] [data-cy="dashboard.mentor.openTasksCount"]',
|
||||||
|
).should("contain", "0");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
import { login, logout } from "../../helpers";
|
||||||
|
import { TEST_STUDENT1_VV_USER_ID } from "../../../consts";
|
||||||
|
|
||||||
|
describe("praxisauftrag.cy.js", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.manageCommand("cypress_reset --create-learning-mentor");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Teilnehmer macht Praxisauftrag; Begleiter kann Feedback geben", () => {
|
||||||
|
// teilnehmer macht selbsteinschätzung
|
||||||
|
login("student-vv@eiger-versicherungen.ch", "test");
|
||||||
|
cy.visit(
|
||||||
|
"/course/versicherungsvermittler-in/learn/gewinnen/mein-kundenstamm",
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.learningContentMultiLayoutNextStep();
|
||||||
|
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||||
|
.clear()
|
||||||
|
.type("Hallo Teilaufgabe 1");
|
||||||
|
// wait because of input debounce
|
||||||
|
cy.wait(550);
|
||||||
|
cy.learningContentMultiLayoutNextStep();
|
||||||
|
|
||||||
|
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||||
|
.clear()
|
||||||
|
.type("Hallo Teilaufgabe 2.1");
|
||||||
|
cy.wait(550);
|
||||||
|
cy.learningContentMultiLayoutNextStep();
|
||||||
|
|
||||||
|
cy.get('[data-cy="it-textarea-user-text-input-1"]')
|
||||||
|
.clear()
|
||||||
|
.type("Hallo Teilaufgabe 3.1");
|
||||||
|
// wait because of input debounce
|
||||||
|
cy.wait(550);
|
||||||
|
cy.learningContentMultiLayoutNextStep();
|
||||||
|
|
||||||
|
cy.get('[data-cy="it-textarea-user-text-input-0"]')
|
||||||
|
.clear()
|
||||||
|
.type("Hallo Teilaufgabe 4.1");
|
||||||
|
// wait because of input debounce
|
||||||
|
cy.wait(550);
|
||||||
|
cy.learningContentMultiLayoutNextStep();
|
||||||
|
|
||||||
|
cy.get('[data-cy="it-textarea-user-text-input-0"]')
|
||||||
|
.clear()
|
||||||
|
.type("Hallo Teilaufgabe 5.1");
|
||||||
|
// wait because of input debounce
|
||||||
|
cy.wait(550);
|
||||||
|
cy.learningContentMultiLayoutNextStep();
|
||||||
|
|
||||||
|
cy.get('[data-cy="confirm-submit-person"]').click();
|
||||||
|
cy.get('[data-cy="select-learning-mentor"]').select(
|
||||||
|
"Micheala Weber-Mentor",
|
||||||
|
);
|
||||||
|
cy.get('[data-cy="submit-assignment"]').click();
|
||||||
|
|
||||||
|
cy.visit("/");
|
||||||
|
cy.get('[data-cy="header-profile"]').click();
|
||||||
|
cy.get('[data-cy="logout-button"]').click();
|
||||||
|
cy.wait(1000);
|
||||||
|
|
||||||
|
// mentor feedback geben
|
||||||
|
login("test-mentor1@example.com", "test");
|
||||||
|
cy.visit("/");
|
||||||
|
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="panel-versicherungsvermittler-in"] [data-cy="dashboard.mentor.openTasksCount"]',
|
||||||
|
).should("contain", "1");
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="panel-versicherungsvermittler-in"] [data-cy="dashboard.mentor.openTasksCount"] [data-cy="basebox.detailsLink"]',
|
||||||
|
).click();
|
||||||
|
|
||||||
|
cy.contains("Feedback geben").click();
|
||||||
|
|
||||||
|
// viktor vollgas auswählen
|
||||||
|
cy.get(
|
||||||
|
`[data-cy="praxis-assignment-feedback-${TEST_STUDENT1_VV_USER_ID}"]`,
|
||||||
|
).should("contain", "Ergebnisse abgegeben");
|
||||||
|
cy.contains("Feedback geben").click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="start-evaluation"]').click();
|
||||||
|
cy.get('[data-cy="it-textarea-default"]').clear().type("Hallo Feedback 1");
|
||||||
|
cy.wait(550);
|
||||||
|
cy.get('[data-cy="next-step"]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="it-textarea-default"]').clear().type("Hallo Feedback 2");
|
||||||
|
cy.wait(550);
|
||||||
|
cy.get('[data-cy="next-step"]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="it-textarea-default"]').clear().type("Hallo Feedback 3");
|
||||||
|
cy.wait(550);
|
||||||
|
cy.get('[data-cy="next-step"]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="it-textarea-default"]').clear().type("Hallo Feedback 4");
|
||||||
|
cy.wait(550);
|
||||||
|
cy.get('[data-cy="next-step"]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="it-textarea-default"]').clear().type("Hallo Feedback 5");
|
||||||
|
cy.wait(550);
|
||||||
|
cy.get('[data-cy="next-step"]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="submit-evaluation"]').click();
|
||||||
|
cy.get('[data-cy="next-step"]').click();
|
||||||
|
|
||||||
|
cy.visit("/");
|
||||||
|
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="panel-versicherungsvermittler-in"] [data-cy="dashboard.mentor.openTasksCount"]',
|
||||||
|
).should("contain", "0");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
/// <reference types="cypress" />
|
|
||||||
// ***********************************************************
|
|
||||||
// This example plugins/index.js can be used to load plugins
|
|
||||||
//
|
|
||||||
// You can change the location of this file or turn off loading
|
|
||||||
// the plugins file with the 'pluginsFile' configuration option.
|
|
||||||
//
|
|
||||||
// You can read more here:
|
|
||||||
// https://on.cypress.io/plugins-guide
|
|
||||||
// ***********************************************************
|
|
||||||
|
|
||||||
// This function is called when a project is opened or re-opened (e.g. due to
|
|
||||||
// the project's config changing)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {Cypress.PluginConfig}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
module.exports = (on, config) => {
|
|
||||||
// `on` is used to hook into various events Cypress emits
|
|
||||||
// `config` is the resolved Cypress config
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { runSql } from "./tasks.mjs";
|
||||||
|
|
||||||
|
export default (on, config) => {
|
||||||
|
on("task", {
|
||||||
|
runSql,
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import pg from "pg";
|
||||||
|
|
||||||
|
const cypressDatabaseUrl = process.env?.CYPRESS_DATABASE_URL || "postgres://postgres@localhost:5432/vbv_lernwelt_cypress";
|
||||||
|
|
||||||
|
if(!cypressDatabaseUrl) {
|
||||||
|
throw new Error(
|
||||||
|
"CYPRESS_DATABASE_URL must be set"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runSql(sqlString) {
|
||||||
|
// I could not make postgres.js make work, so I use pg directly
|
||||||
|
const client = new pg.Client(cypressDatabaseUrl);
|
||||||
|
await client.connect();
|
||||||
|
const res = await client.query(sqlString);
|
||||||
|
await client.end();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
@ -87,14 +87,14 @@ function loadObjectJson(
|
||||||
value,
|
value,
|
||||||
djangoModelPath,
|
djangoModelPath,
|
||||||
serializerModelPath,
|
serializerModelPath,
|
||||||
valueAsString = false
|
valueAsString = false,
|
||||||
) {
|
) {
|
||||||
const djangoModel = _.last(djangoModelPath.split("."))
|
const djangoModel = _.last(djangoModelPath.split("."))
|
||||||
const djangoModelImportPath = _.initial(djangoModelPath.split(".")).join(".")
|
const djangoModelImportPath = _.initial(djangoModelPath.split(".")).join(".")
|
||||||
const serializerModel = _.last(serializerModelPath.split("."))
|
const serializerModel = _.last(serializerModelPath.split("."))
|
||||||
const serializerModelImportPath = _.initial(
|
const serializerModelImportPath = _.initial(
|
||||||
serializerModelPath.split(".")
|
serializerModelPath.split("."),
|
||||||
).join(".")
|
).join(".");
|
||||||
|
|
||||||
let filterPart = `${key}=${value}`
|
let filterPart = `${key}=${value}`
|
||||||
if (valueAsString) {
|
if (valueAsString) {
|
||||||
|
|
@ -134,9 +134,10 @@ Cypress.Commands.add("loadAssignmentCompletion", (key, value) => {
|
||||||
value,
|
value,
|
||||||
"vbv_lernwelt.assignment.models.AssignmentCompletion",
|
"vbv_lernwelt.assignment.models.AssignmentCompletion",
|
||||||
"vbv_lernwelt.assignment.serializers.CypressAssignmentCompletionSerializer",
|
"vbv_lernwelt.assignment.serializers.CypressAssignmentCompletionSerializer",
|
||||||
true
|
true,
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("loadSecurityRequestResponseLog", (key, value) => {
|
Cypress.Commands.add("loadSecurityRequestResponseLog", (key, value) => {
|
||||||
return loadObjectJson(
|
return loadObjectJson(
|
||||||
|
|
@ -144,9 +145,10 @@ Cypress.Commands.add("loadSecurityRequestResponseLog", (key, value) => {
|
||||||
value,
|
value,
|
||||||
"vbv_lernwelt.core.models.SecurityRequestResponseLog",
|
"vbv_lernwelt.core.models.SecurityRequestResponseLog",
|
||||||
"vbv_lernwelt.core.serializers.CypressSecurityRequestResponseLogSerializer",
|
"vbv_lernwelt.core.serializers.CypressSecurityRequestResponseLogSerializer",
|
||||||
true
|
true,
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("loadExternalApiRequestLog", (key, value) => {
|
Cypress.Commands.add("loadExternalApiRequestLog", (key, value) => {
|
||||||
return loadObjectJson(
|
return loadObjectJson(
|
||||||
|
|
@ -154,9 +156,10 @@ Cypress.Commands.add("loadExternalApiRequestLog", (key, value) => {
|
||||||
value,
|
value,
|
||||||
"vbv_lernwelt.core.models.ExternalApiRequestLog",
|
"vbv_lernwelt.core.models.ExternalApiRequestLog",
|
||||||
"vbv_lernwelt.core.serializers.CypressExternalApiRequestLogSerializer",
|
"vbv_lernwelt.core.serializers.CypressExternalApiRequestLogSerializer",
|
||||||
true
|
true,
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("loadFeedbackResponse", (key, value) => {
|
Cypress.Commands.add("loadFeedbackResponse", (key, value) => {
|
||||||
return loadObjectJson(
|
return loadObjectJson(
|
||||||
|
|
@ -164,9 +167,10 @@ Cypress.Commands.add("loadFeedbackResponse", (key, value) => {
|
||||||
value,
|
value,
|
||||||
"vbv_lernwelt.feedback.models.FeedbackResponse",
|
"vbv_lernwelt.feedback.models.FeedbackResponse",
|
||||||
"vbv_lernwelt.feedback.serializers.CypressFeedbackResponseSerializer",
|
"vbv_lernwelt.feedback.serializers.CypressFeedbackResponseSerializer",
|
||||||
true
|
true,
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("loadCheckoutInformation", (key, value) => {
|
Cypress.Commands.add("loadCheckoutInformation", (key, value) => {
|
||||||
return loadObjectJson(
|
return loadObjectJson(
|
||||||
|
|
@ -174,9 +178,10 @@ Cypress.Commands.add("loadCheckoutInformation", (key, value) => {
|
||||||
value,
|
value,
|
||||||
"vbv_lernwelt.shop.models.CheckoutInformation",
|
"vbv_lernwelt.shop.models.CheckoutInformation",
|
||||||
"vbv_lernwelt.shop.serializers.CypressCheckoutInformationSerializer",
|
"vbv_lernwelt.shop.serializers.CypressCheckoutInformationSerializer",
|
||||||
true
|
true,
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("loadUser", (key, value) => {
|
Cypress.Commands.add("loadUser", (key, value) => {
|
||||||
return loadObjectJson(
|
return loadObjectJson(
|
||||||
|
|
@ -188,7 +193,6 @@ Cypress.Commands.add("loadUser", (key, value) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("loadCourseSessionUser", (key, value) => {
|
Cypress.Commands.add("loadCourseSessionUser", (key, value) => {
|
||||||
return loadObjectJson(
|
return loadObjectJson(
|
||||||
key,
|
key,
|
||||||
|
|
@ -199,29 +203,33 @@ Cypress.Commands.add("loadCourseSessionUser", (key, value) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add("makeSelfEvaluation", (answers, withCompletion = true) => {
|
||||||
Cypress.Commands.add("makeSelfEvaluation", (answers) => {
|
|
||||||
for (let i = 0; i < answers.length; i++) {
|
for (let i = 0; i < answers.length; i++) {
|
||||||
const answer = answers[i]
|
const answer = answers[i];
|
||||||
if (answer) {
|
if (answer) {
|
||||||
cy.get('[data-cy="success"]').click()
|
cy.get('[data-cy="success"]').click();
|
||||||
} else {
|
} else {
|
||||||
cy.get('[data-cy="fail"]').click()
|
cy.get('[data-cy="fail"]').click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (withCompletion) {
|
||||||
if (i < answers.length - 1) {
|
if (i < answers.length - 1) {
|
||||||
cy.get('[data-cy="next-step"]').click({ force: true })
|
cy.get('[data-cy="next-step"]').click({force: true});
|
||||||
} else {
|
} else {
|
||||||
cy.get('[data-cy="complete-and-continue"]').click({ force: true })
|
cy.get('[data-cy="complete-and-continue"]').click({force: true});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cy.get('[data-cy="next-step"]').click({force: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("learningContentMultiLayoutNextStep", () => {
|
Cypress.Commands.add("learningContentMultiLayoutNextStep", () => {
|
||||||
return cy.get('[data-cy="next-step"]').click({ force: true })
|
return cy.get('[data-cy="next-step"]').click({force: true})
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("learningContentMultiLayoutPreviousStep", () => {
|
Cypress.Commands.add("learningContentMultiLayoutPreviousStep", () => {
|
||||||
return cy.get('[data-cy="previous-step"]').click({ force: true })
|
return cy.get('[data-cy="previous-step"]').click({force: true})
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("testLearningContentTitle", (title) => {
|
Cypress.Commands.add("testLearningContentTitle", (title) => {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -10,7 +10,8 @@
|
||||||
"prettier": "npm run prettier --prefix client"
|
"prettier": "npm run prettier --prefix client"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cypress": "^12.15.0",
|
"cypress": "^12.17.4",
|
||||||
"cypress-cloud": "^1.7.4"
|
"cypress-cloud": "^1.10.2",
|
||||||
|
"pg": "^8.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import django
|
||||||
|
from django.contrib.auth.hashers import make_password
|
||||||
|
|
||||||
|
sys.path.append("../server")
|
||||||
|
|
||||||
|
os.environ.setdefault("IT_APP_ENVIRONMENT", "local")
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.base")
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
from vbv_lernwelt.course.models import CourseSessionUser
|
||||||
|
from vbv_lernwelt.core.admin import User
|
||||||
|
from vbv_lernwelt.learning_mentor.models import (
|
||||||
|
AgentParticipantRelation,
|
||||||
|
AgentParticipantRoleType,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
berufsbildner, _ = User.objects.get_or_create(
|
||||||
|
id="5f984be9-3024-4169-9c7b-c9e827c18fd8"
|
||||||
|
)
|
||||||
|
berufsbildner.username = "berufsbildner-mobi@example.com"
|
||||||
|
berufsbildner.email = "berufsbildner-mobi@example.com"
|
||||||
|
berufsbildner.language = "de"
|
||||||
|
berufsbildner.first_name = "Berufsbildner"
|
||||||
|
berufsbildner.last_name = "Mobi"
|
||||||
|
berufsbildner.password = make_password("test")
|
||||||
|
berufsbildner.save()
|
||||||
|
|
||||||
|
for csu in (
|
||||||
|
CourseSessionUser.objects.filter(user__username__contains="@mobi")
|
||||||
|
.filter(course_session__course__configuration__is_uk=True)
|
||||||
|
.filter(role=CourseSessionUser.Role.MEMBER.value)
|
||||||
|
.exclude(course_session_id__in=[4, 5, 6])
|
||||||
|
):
|
||||||
|
AgentParticipantRelation.objects.get_or_create(
|
||||||
|
agent=berufsbildner,
|
||||||
|
participant=csu,
|
||||||
|
role=AgentParticipantRoleType.BERUFSBILDNER.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -39,9 +39,9 @@ def send_learning_mentor_invitation():
|
||||||
recipient_email="daniel.egger+sendgrid@gmail.com",
|
recipient_email="daniel.egger+sendgrid@gmail.com",
|
||||||
template=EmailTemplate.LEARNING_MENTOR_INVITATION,
|
template=EmailTemplate.LEARNING_MENTOR_INVITATION,
|
||||||
template_data={
|
template_data={
|
||||||
"inviter_name": f"Daniel Egger",
|
"inviter_name": "Daniel Egger",
|
||||||
"inviter_email": "daniel.egger@example.com",
|
"inviter_email": "daniel.egger@example.com",
|
||||||
"target_url": f"https://stage.vbv-afa.ch/foobar",
|
"target_url": "https://stage.vbv-afa.ch/foobar",
|
||||||
},
|
},
|
||||||
template_language="de",
|
template_language="de",
|
||||||
fail_silently=True,
|
fail_silently=True,
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ class AssignmentCompletionMutation(graphene.Mutation):
|
||||||
):
|
):
|
||||||
if not can_evaluate_assignments(
|
if not can_evaluate_assignments(
|
||||||
evaluation_user=info.context.user,
|
evaluation_user=info.context.user,
|
||||||
assignment_user_id=assignment_user_id,
|
assignment_user_ids=[assignment_user_id],
|
||||||
course_session_id=course_session_id,
|
course_session_id=course_session_id,
|
||||||
):
|
):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ class AssignmentCompletionObjectType(DjangoObjectType):
|
||||||
evaluation_points = graphene.Float()
|
evaluation_points = graphene.Float()
|
||||||
evaluation_points_final = graphene.Float()
|
evaluation_points_final = graphene.Float()
|
||||||
evaluation_max_points = graphene.Float()
|
evaluation_max_points = graphene.Float()
|
||||||
|
evaluation_percent = graphene.Float()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AssignmentCompletion
|
model = AssignmentCompletion
|
||||||
|
|
@ -61,6 +62,11 @@ class AssignmentCompletionObjectType(DjangoObjectType):
|
||||||
return round(self.evaluation_max_points, 1) # noqa
|
return round(self.evaluation_max_points, 1) # noqa
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def resolve_evaluation_percent(self, info):
|
||||||
|
if self.evaluation_points:
|
||||||
|
return self.evaluation_percent
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class AssignmentObjectType(DjangoObjectType):
|
class AssignmentObjectType(DjangoObjectType):
|
||||||
tasks = JSONStreamField()
|
tasks = JSONStreamField()
|
||||||
|
|
@ -69,11 +75,11 @@ class AssignmentObjectType(DjangoObjectType):
|
||||||
max_points = graphene.Int()
|
max_points = graphene.Int()
|
||||||
competence_certificate_weight = graphene.Float()
|
competence_certificate_weight = graphene.Float()
|
||||||
learning_content = graphene.Field(LearningContentInterface)
|
learning_content = graphene.Field(LearningContentInterface)
|
||||||
completion = graphene.Field(
|
completions = graphene.List(
|
||||||
AssignmentCompletionObjectType,
|
AssignmentCompletionObjectType,
|
||||||
course_session_id=graphene.ID(required=True),
|
course_session_id=graphene.ID(required=True),
|
||||||
learning_content_page_id=graphene.ID(required=False),
|
learning_content_page_id=graphene.ID(required=False),
|
||||||
assignment_user_id=graphene.UUID(required=False),
|
assignment_user_ids=graphene.List(graphene.UUID, required=False),
|
||||||
)
|
)
|
||||||
solution_sample = graphene.Field(ContentDocumentObjectType)
|
solution_sample = graphene.Field(ContentDocumentObjectType)
|
||||||
|
|
||||||
|
|
@ -103,28 +109,33 @@ class AssignmentObjectType(DjangoObjectType):
|
||||||
def resolve_learning_content(self, info):
|
def resolve_learning_content(self, info):
|
||||||
return self.find_attached_learning_content()
|
return self.find_attached_learning_content()
|
||||||
|
|
||||||
def resolve_completion(
|
def resolve_completions(
|
||||||
self,
|
self,
|
||||||
info,
|
info,
|
||||||
course_session_id,
|
course_session_id,
|
||||||
learning_content_page_id=None,
|
learning_content_page_id=None,
|
||||||
assignment_user_id=None,
|
assignment_user_ids=None,
|
||||||
):
|
):
|
||||||
if learning_content_page_id is None:
|
if learning_content_page_id is None:
|
||||||
lp = self.find_attached_learning_content()
|
lp = self.find_attached_learning_content()
|
||||||
if lp:
|
if lp:
|
||||||
learning_content_page_id = lp.id
|
learning_content_page_id = lp.id
|
||||||
|
|
||||||
if not assignment_user_id:
|
if not assignment_user_ids:
|
||||||
assignment_user_id = getattr(info.context, "assignment_user_id", None)
|
assignment_user_ids = getattr(info.context, "assignment_user_ids", [])
|
||||||
|
|
||||||
return resolve_assignment_completion(
|
completions = []
|
||||||
|
for user_id in assignment_user_ids:
|
||||||
|
completion = resolve_assignment_completion(
|
||||||
info=info,
|
info=info,
|
||||||
course_session_id=course_session_id,
|
course_session_id=course_session_id,
|
||||||
learning_content_page_id=learning_content_page_id,
|
learning_content_page_id=learning_content_page_id,
|
||||||
assignment_user_id=assignment_user_id,
|
assignment_user_id=user_id,
|
||||||
assignment_id=self.id,
|
assignment_id=self.id,
|
||||||
)
|
)
|
||||||
|
if completion:
|
||||||
|
completions.append(completion)
|
||||||
|
return completions
|
||||||
|
|
||||||
|
|
||||||
def resolve_assignment_completion(
|
def resolve_assignment_completion(
|
||||||
|
|
@ -139,7 +150,7 @@ def resolve_assignment_completion(
|
||||||
|
|
||||||
if str(assignment_user_id) == str(info.context.user.id) or can_evaluate_assignments(
|
if str(assignment_user_id) == str(info.context.user.id) or can_evaluate_assignments(
|
||||||
evaluation_user=info.context.user,
|
evaluation_user=info.context.user,
|
||||||
assignment_user_id=assignment_user_id,
|
assignment_user_ids=[assignment_user_id],
|
||||||
course_session_id=course_session_id,
|
course_session_id=course_session_id,
|
||||||
):
|
):
|
||||||
course_id = CourseSession.objects.get(id=course_session_id).course_id
|
course_id = CourseSession.objects.get(id=course_session_id).course_id
|
||||||
|
|
|
||||||
|
|
@ -368,6 +368,10 @@ class AssignmentCompletion(models.Model):
|
||||||
return None
|
return None
|
||||||
return self.evaluation_points - self.evaluation_points_deducted
|
return self.evaluation_points - self.evaluation_points_deducted
|
||||||
|
|
||||||
|
@property
|
||||||
|
def evaluation_percent(self):
|
||||||
|
return (self.evaluation_points_final or 0) / (self.evaluation_max_points or 1)
|
||||||
|
|
||||||
assignment_user = models.ForeignKey(User, on_delete=models.CASCADE)
|
assignment_user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE)
|
assignment = models.ForeignKey(Assignment, on_delete=models.CASCADE)
|
||||||
course_session = models.ForeignKey("course.CourseSession", on_delete=models.CASCADE)
|
course_session = models.ForeignKey("course.CourseSession", on_delete=models.CASCADE)
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,23 @@ from vbv_lernwelt.iam.permissions import can_evaluate_assignments
|
||||||
logger = structlog.get_logger(__name__)
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@api_view(["GET"])
|
@api_view(["GET", "POST"])
|
||||||
def request_assignment_completion_status(request, assignment_id, course_session_id):
|
def request_assignment_completion_status(request, assignment_id, course_session_id):
|
||||||
# TODO quickfix before GraphQL...
|
# TODO quickfix before GraphQL...
|
||||||
if can_evaluate_assignments(request.user, course_session_id):
|
|
||||||
|
user_selection_ids = request.data.get("user_selection_ids", [])
|
||||||
|
|
||||||
|
if can_evaluate_assignments(
|
||||||
|
request.user, course_session_id, assignment_user_ids=user_selection_ids
|
||||||
|
):
|
||||||
qs = AssignmentCompletion.objects.filter(
|
qs = AssignmentCompletion.objects.filter(
|
||||||
course_session_id=course_session_id,
|
course_session_id=course_session_id,
|
||||||
assignment_id=assignment_id,
|
assignment_id=assignment_id,
|
||||||
).values(
|
)
|
||||||
|
if len(user_selection_ids) > 0:
|
||||||
|
qs = qs.filter(assignment_user_id__in=user_selection_ids)
|
||||||
|
|
||||||
|
values = qs.values(
|
||||||
"id",
|
"id",
|
||||||
"assignment_user_id",
|
"assignment_user_id",
|
||||||
"completion_status",
|
"completion_status",
|
||||||
|
|
@ -28,7 +37,7 @@ def request_assignment_completion_status(request, assignment_id, course_session_
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert the learning_content_page_id to a string
|
# Convert the learning_content_page_id to a string
|
||||||
data = list(qs) # Evaluate the queryset
|
data = list(values) # Evaluate the queryset
|
||||||
for item in data:
|
for item in data:
|
||||||
if item["evaluation_points"] is not None:
|
if item["evaluation_points"] is not None:
|
||||||
# only `evaluation_points_final` is relevant for the frontend
|
# only `evaluation_points_final` is relevant for the frontend
|
||||||
|
|
|
||||||
|
|
@ -24,47 +24,23 @@ class CompetenceCertificateQuery(object):
|
||||||
slug=graphene.String(),
|
slug=graphene.String(),
|
||||||
course_id=graphene.ID(),
|
course_id=graphene.ID(),
|
||||||
course_slug=graphene.String(),
|
course_slug=graphene.String(),
|
||||||
)
|
user_ids=graphene.List(graphene.UUID),
|
||||||
|
|
||||||
competence_certificate_list_for_user = graphene.Field(
|
|
||||||
CompetenceCertificateListObjectType,
|
|
||||||
id=graphene.ID(),
|
|
||||||
slug=graphene.String(),
|
|
||||||
course_id=graphene.ID(),
|
|
||||||
course_slug=graphene.String(),
|
|
||||||
user_id=graphene.UUID(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def resolve_competence_certificate(root, info, id=None, slug=None):
|
def resolve_competence_certificate(root, info, id=None, slug=None):
|
||||||
return resolve_course_page(CompetenceCertificate, root, info, id=id, slug=slug)
|
return resolve_course_page(CompetenceCertificate, root, info, id=id, slug=slug)
|
||||||
|
|
||||||
def resolve_competence_certificate_list(
|
def resolve_competence_certificate_list(
|
||||||
root, info, id=None, slug=None, course_id=None, course_slug=None
|
root, info, id=None, slug=None, course_id=None, course_slug=None, user_ids=None
|
||||||
):
|
):
|
||||||
return resolve_course_page(
|
for user_id in user_ids:
|
||||||
CompetenceCertificateList,
|
|
||||||
root,
|
|
||||||
info,
|
|
||||||
id=id,
|
|
||||||
slug=slug,
|
|
||||||
course_id=course_id,
|
|
||||||
course_slug=course_slug,
|
|
||||||
)
|
|
||||||
|
|
||||||
def resolve_competence_certificate_list_for_user(
|
|
||||||
root, info, id=None, slug=None, course_id=None, course_slug=None, user_id=None
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
course_session_user = CourseSessionUser.objects.filter(
|
course_session_user = CourseSessionUser.objects.filter(
|
||||||
user__id=user_id
|
user__id=user_id
|
||||||
).first()
|
).first()
|
||||||
except CourseSessionUser.DoesNotExist:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if not can_view_profile(info.context.user, course_session_user):
|
if not can_view_profile(info.context.user, course_session_user):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
setattr(info.context, "assignment_user_id", user_id)
|
setattr(info.context, "assignment_user_ids", user_ids)
|
||||||
|
|
||||||
return resolve_course_page(
|
return resolve_course_page(
|
||||||
CompetenceCertificateList,
|
CompetenceCertificateList,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ def query_competence_course_session_assignments(course_session_ids, circle_ids=N
|
||||||
AssignmentType.CASEWORK.value,
|
AssignmentType.CASEWORK.value,
|
||||||
],
|
],
|
||||||
learning_content__content_assignment__competence_certificate__isnull=False,
|
learning_content__content_assignment__competence_certificate__isnull=False,
|
||||||
|
learning_content__live=True,
|
||||||
).select_related(
|
).select_related(
|
||||||
"submission_deadline",
|
"submission_deadline",
|
||||||
"learning_content",
|
"learning_content",
|
||||||
|
|
@ -39,6 +40,7 @@ def query_competence_course_session_edoniq_tests(course_session_ids, circle_ids=
|
||||||
for cset in CourseSessionEdoniqTest.objects.filter(
|
for cset in CourseSessionEdoniqTest.objects.filter(
|
||||||
course_session_id__in=course_session_ids,
|
course_session_id__in=course_session_ids,
|
||||||
learning_content__content_assignment__competence_certificate__isnull=False,
|
learning_content__content_assignment__competence_certificate__isnull=False,
|
||||||
|
learning_content__live=True,
|
||||||
).select_related(
|
).select_related(
|
||||||
"deadline",
|
"deadline",
|
||||||
"learning_content",
|
"learning_content",
|
||||||
|
|
|
||||||
|
|
@ -71,177 +71,23 @@ class TestCertificateList(GraphQLTestCase):
|
||||||
def test_supervisor_userprofile_certificate_summary(self):
|
def test_supervisor_userprofile_certificate_summary(self):
|
||||||
self.client.force_login(self.supervisor)
|
self.client.force_login(self.supervisor)
|
||||||
|
|
||||||
query = f"""query competenceCertificateForUserQuery(
|
query = """query competenceCertificateForUserQuery(
|
||||||
$courseSlug: String!,
|
$courseSlug: String!,
|
||||||
$courseSessionId: ID!,
|
$courseSessionId: ID!,
|
||||||
$userId: UUID!
|
$userIds: [UUID!]
|
||||||
) {{
|
) {
|
||||||
competence_certificate_list_for_user(
|
|
||||||
course_slug: $courseSlug,
|
|
||||||
user_id: $userId
|
|
||||||
) {{
|
|
||||||
...CoursePageFields
|
|
||||||
competence_certificates {{
|
|
||||||
...CoursePageFields
|
|
||||||
assignments {{
|
|
||||||
...CoursePageFields
|
|
||||||
assignment_type
|
|
||||||
max_points
|
|
||||||
completion(course_session_id: $courseSessionId) {{
|
|
||||||
id
|
|
||||||
completion_status
|
|
||||||
submitted_at
|
|
||||||
evaluation_points
|
|
||||||
evaluation_max_points
|
|
||||||
evaluation_passed
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
learning_content {{
|
|
||||||
...CoursePageFields
|
|
||||||
circle {{
|
|
||||||
id
|
|
||||||
title
|
|
||||||
slug
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
|
|
||||||
fragment CoursePageFields on CoursePageInterface {{
|
|
||||||
title
|
|
||||||
id
|
|
||||||
slug
|
|
||||||
content_type
|
|
||||||
frontend_url
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
|
|
||||||
"""
|
|
||||||
variables = {
|
|
||||||
"courseSessionId": str(self.course_session.id),
|
|
||||||
"courseSlug": self.course.slug,
|
|
||||||
"userId": str(self.member_one.id),
|
|
||||||
}
|
|
||||||
|
|
||||||
# WHEN
|
|
||||||
response = self.query(query, variables=variables)
|
|
||||||
|
|
||||||
# THEN
|
|
||||||
self.assertResponseNoErrors(response)
|
|
||||||
|
|
||||||
certificates = response.json()["data"]["competence_certificate_list_for_user"][
|
|
||||||
"competence_certificates"
|
|
||||||
]
|
|
||||||
self.assertEqual(len(certificates), 1)
|
|
||||||
|
|
||||||
assignments = certificates[0]["assignments"]
|
|
||||||
self.assertEqual(len(assignments), 2)
|
|
||||||
|
|
||||||
completion1 = assignments[0]["completion"]
|
|
||||||
self.assertEqual(completion1["completion_status"], "SUBMITTED")
|
|
||||||
self.assertEqual(completion1["evaluation_points"], 5)
|
|
||||||
self.assertEqual(completion1["evaluation_max_points"], 10)
|
|
||||||
self.assertEqual(completion1["evaluation_passed"], False)
|
|
||||||
|
|
||||||
completion2 = assignments[1]["completion"]
|
|
||||||
self.assertIsNone(completion2)
|
|
||||||
|
|
||||||
def test_member_cannot_see_other_user_certificate_summary(self):
|
|
||||||
self.client.force_login(self.member_one)
|
|
||||||
|
|
||||||
query = f"""query competenceCertificateForUserQuery(
|
|
||||||
$courseSlug: String!,
|
|
||||||
$courseSessionId: ID!,
|
|
||||||
$userId: UUID!
|
|
||||||
) {{
|
|
||||||
competence_certificate_list_for_user(
|
|
||||||
course_slug: $courseSlug,
|
|
||||||
user_id: $userId
|
|
||||||
) {{
|
|
||||||
...CoursePageFields
|
|
||||||
competence_certificates {{
|
|
||||||
...CoursePageFields
|
|
||||||
assignments {{
|
|
||||||
...CoursePageFields
|
|
||||||
assignment_type
|
|
||||||
max_points
|
|
||||||
completion(course_session_id: $courseSessionId) {{
|
|
||||||
id
|
|
||||||
completion_status
|
|
||||||
submitted_at
|
|
||||||
evaluation_points
|
|
||||||
evaluation_max_points
|
|
||||||
evaluation_passed
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
learning_content {{
|
|
||||||
...CoursePageFields
|
|
||||||
circle {{
|
|
||||||
id
|
|
||||||
title
|
|
||||||
slug
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
|
|
||||||
fragment CoursePageFields on CoursePageInterface {{
|
|
||||||
title
|
|
||||||
id
|
|
||||||
slug
|
|
||||||
content_type
|
|
||||||
frontend_url
|
|
||||||
__typename
|
|
||||||
}}
|
|
||||||
|
|
||||||
"""
|
|
||||||
variables = {
|
|
||||||
"courseSessionId": str(self.course_session.id),
|
|
||||||
"courseSlug": self.course.slug,
|
|
||||||
"userId": str(self.member_two.id),
|
|
||||||
}
|
|
||||||
|
|
||||||
# WHEN
|
|
||||||
response = self.query(query, variables=variables)
|
|
||||||
|
|
||||||
# THEN
|
|
||||||
self.assertResponseNoErrors(response)
|
|
||||||
self.assertIsNone(
|
|
||||||
response.json()["data"]["competence_certificate_list_for_user"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_member_userprofile_certificate_summary(self):
|
|
||||||
self.client.force_login(self.member_one)
|
|
||||||
|
|
||||||
query = f"""query competenceCertificateForUserQuery(
|
|
||||||
$courseSlug: String!,
|
|
||||||
$courseSessionId: ID!,
|
|
||||||
) {{
|
|
||||||
competence_certificate_list(
|
competence_certificate_list(
|
||||||
course_slug: $courseSlug,
|
course_slug: $courseSlug,
|
||||||
) {{
|
user_ids: $userIds
|
||||||
|
) {
|
||||||
...CoursePageFields
|
...CoursePageFields
|
||||||
competence_certificates {{
|
competence_certificates {
|
||||||
...CoursePageFields
|
...CoursePageFields
|
||||||
assignments {{
|
assignments {
|
||||||
...CoursePageFields
|
...CoursePageFields
|
||||||
assignment_type
|
assignment_type
|
||||||
max_points
|
max_points
|
||||||
completion(course_session_id: $courseSessionId) {{
|
completions(course_session_id: $courseSessionId) {
|
||||||
id
|
id
|
||||||
completion_status
|
completion_status
|
||||||
submitted_at
|
submitted_at
|
||||||
|
|
@ -249,38 +95,39 @@ fragment CoursePageFields on CoursePageInterface {{
|
||||||
evaluation_max_points
|
evaluation_max_points
|
||||||
evaluation_passed
|
evaluation_passed
|
||||||
__typename
|
__typename
|
||||||
}}
|
}
|
||||||
learning_content {{
|
learning_content {
|
||||||
...CoursePageFields
|
...CoursePageFields
|
||||||
circle {{
|
circle {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
slug
|
slug
|
||||||
__typename
|
__typename
|
||||||
}}
|
}
|
||||||
__typename
|
__typename
|
||||||
}}
|
}
|
||||||
__typename
|
__typename
|
||||||
}}
|
}
|
||||||
__typename
|
__typename
|
||||||
}}
|
}
|
||||||
__typename
|
__typename
|
||||||
}}
|
}
|
||||||
}}
|
}
|
||||||
|
|
||||||
fragment CoursePageFields on CoursePageInterface {{
|
fragment CoursePageFields on CoursePageInterface {
|
||||||
title
|
title
|
||||||
id
|
id
|
||||||
slug
|
slug
|
||||||
content_type
|
content_type
|
||||||
frontend_url
|
frontend_url
|
||||||
__typename
|
__typename
|
||||||
}}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
variables = {
|
variables = {
|
||||||
"courseSessionId": str(self.course_session.id),
|
"courseSessionId": str(self.course_session.id),
|
||||||
"courseSlug": self.course.slug,
|
"courseSlug": self.course.slug,
|
||||||
|
"userIds": [str(self.member_one.id)],
|
||||||
}
|
}
|
||||||
|
|
||||||
# WHEN
|
# WHEN
|
||||||
|
|
@ -297,11 +144,165 @@ fragment CoursePageFields on CoursePageInterface {{
|
||||||
assignments = certificates[0]["assignments"]
|
assignments = certificates[0]["assignments"]
|
||||||
self.assertEqual(len(assignments), 2)
|
self.assertEqual(len(assignments), 2)
|
||||||
|
|
||||||
completion1 = assignments[0]["completion"]
|
completion1 = assignments[0]["completions"][0]
|
||||||
self.assertEqual(completion1["completion_status"], "SUBMITTED")
|
self.assertEqual(completion1["completion_status"], "SUBMITTED")
|
||||||
self.assertEqual(completion1["evaluation_points"], 5)
|
self.assertEqual(completion1["evaluation_points"], 5)
|
||||||
self.assertEqual(completion1["evaluation_max_points"], 10)
|
self.assertEqual(completion1["evaluation_max_points"], 10)
|
||||||
self.assertEqual(completion1["evaluation_passed"], False)
|
self.assertEqual(completion1["evaluation_passed"], False)
|
||||||
|
|
||||||
completion2 = assignments[1]["completion"]
|
completion2 = assignments[1]["completions"]
|
||||||
self.assertIsNone(completion2)
|
self.assertEqual(len(completion2), 0)
|
||||||
|
|
||||||
|
def test_member_cannot_see_other_user_certificate_summary(self):
|
||||||
|
self.client.force_login(self.member_one)
|
||||||
|
|
||||||
|
query = """query competenceCertificateForUserQuery(
|
||||||
|
$courseSlug: String!,
|
||||||
|
$courseSessionId: ID!,
|
||||||
|
$userIds: [UUID!]
|
||||||
|
) {
|
||||||
|
competence_certificate_list(
|
||||||
|
course_slug: $courseSlug,
|
||||||
|
user_ids: $userIds
|
||||||
|
) {
|
||||||
|
...CoursePageFields
|
||||||
|
competence_certificates {
|
||||||
|
...CoursePageFields
|
||||||
|
assignments {
|
||||||
|
...CoursePageFields
|
||||||
|
assignment_type
|
||||||
|
max_points
|
||||||
|
completions(course_session_id: $courseSessionId) {
|
||||||
|
id
|
||||||
|
completion_status
|
||||||
|
submitted_at
|
||||||
|
evaluation_points
|
||||||
|
evaluation_max_points
|
||||||
|
evaluation_passed
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
learning_content {
|
||||||
|
...CoursePageFields
|
||||||
|
circle {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
slug
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment CoursePageFields on CoursePageInterface {
|
||||||
|
title
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
content_type
|
||||||
|
frontend_url
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
variables = {
|
||||||
|
"courseSessionId": str(self.course_session.id),
|
||||||
|
"courseSlug": self.course.slug,
|
||||||
|
"userIds": [str(self.member_two.id)],
|
||||||
|
}
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
response = self.query(query, variables=variables)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertResponseNoErrors(response)
|
||||||
|
self.assertIsNone(response.json()["data"]["competence_certificate_list"])
|
||||||
|
|
||||||
|
def test_member_userprofile_certificate_summary(self):
|
||||||
|
self.client.force_login(self.member_one)
|
||||||
|
|
||||||
|
query = """query competenceCertificateForUserQuery(
|
||||||
|
$courseSlug: String!,
|
||||||
|
$courseSessionId: ID!,
|
||||||
|
$userIds: [UUID!],
|
||||||
|
) {
|
||||||
|
competence_certificate_list(
|
||||||
|
course_slug: $courseSlug,
|
||||||
|
user_ids: $userIds
|
||||||
|
) {
|
||||||
|
...CoursePageFields
|
||||||
|
competence_certificates {
|
||||||
|
...CoursePageFields
|
||||||
|
assignments {
|
||||||
|
...CoursePageFields
|
||||||
|
assignment_type
|
||||||
|
max_points
|
||||||
|
completions(course_session_id: $courseSessionId) {
|
||||||
|
id
|
||||||
|
completion_status
|
||||||
|
submitted_at
|
||||||
|
evaluation_points
|
||||||
|
evaluation_max_points
|
||||||
|
evaluation_passed
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
learning_content {
|
||||||
|
...CoursePageFields
|
||||||
|
circle {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
slug
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment CoursePageFields on CoursePageInterface {
|
||||||
|
title
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
content_type
|
||||||
|
frontend_url
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
variables = {
|
||||||
|
"courseSessionId": str(self.course_session.id),
|
||||||
|
"courseSlug": self.course.slug,
|
||||||
|
"userIds": [str(self.member_one.id)],
|
||||||
|
}
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
response = self.query(query, variables=variables)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertResponseNoErrors(response)
|
||||||
|
|
||||||
|
certificates = response.json()["data"]["competence_certificate_list"][
|
||||||
|
"competence_certificates"
|
||||||
|
]
|
||||||
|
self.assertEqual(len(certificates), 1)
|
||||||
|
|
||||||
|
assignments = certificates[0]["assignments"]
|
||||||
|
self.assertEqual(len(assignments), 2)
|
||||||
|
|
||||||
|
completion1 = assignments[0]["completions"][0]
|
||||||
|
self.assertEqual(completion1["completion_status"], "SUBMITTED")
|
||||||
|
self.assertEqual(completion1["evaluation_points"], 5)
|
||||||
|
self.assertEqual(completion1["evaluation_max_points"], 10)
|
||||||
|
self.assertEqual(completion1["evaluation_passed"], False)
|
||||||
|
|
||||||
|
completion2 = assignments[1]["completions"]
|
||||||
|
self.assertEqual(len(completion2), 0)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin, messages
|
||||||
from django.contrib.auth import admin as auth_admin, get_user_model
|
from django.contrib.auth import admin as auth_admin, get_user_model
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
@ -10,6 +10,9 @@ from vbv_lernwelt.core.models import (
|
||||||
SecurityRequestResponseLog,
|
SecurityRequestResponseLog,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.core.utils import pretty_print_json
|
from vbv_lernwelt.core.utils import pretty_print_json
|
||||||
|
from vbv_lernwelt.learning_mentor.services import (
|
||||||
|
create_or_sync_berufsbildner as create_or_sync_bb,
|
||||||
|
)
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
@ -31,6 +34,26 @@ class LogAdmin(admin.ModelAdmin):
|
||||||
return pretty_print_json(json_string)
|
return pretty_print_json(json_string)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.action(description="Berufsbildner: Create or Sync")
|
||||||
|
def create_or_sync_berufsbildner(modeladmin, request, queryset):
|
||||||
|
# keep it easy
|
||||||
|
success = []
|
||||||
|
for user in queryset:
|
||||||
|
success.append(create_or_sync_bb(user))
|
||||||
|
if all(success):
|
||||||
|
messages.add_message(
|
||||||
|
request,
|
||||||
|
messages.SUCCESS,
|
||||||
|
f"Berufsbildner erfolgreich erstellt oder synchronisiert",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
messages.add_message(
|
||||||
|
request,
|
||||||
|
messages.ERROR,
|
||||||
|
f"Einige Berufsbildner konnten nicht erstellt oder synchronisiert werden",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(User)
|
@admin.register(User)
|
||||||
class UserAdmin(auth_admin.UserAdmin):
|
class UserAdmin(auth_admin.UserAdmin):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
|
|
@ -91,6 +114,7 @@ class UserAdmin(auth_admin.UserAdmin):
|
||||||
"sso_id",
|
"sso_id",
|
||||||
]
|
]
|
||||||
search_fields = ["first_name", "last_name", "email", "username", "sso_id"]
|
search_fields = ["first_name", "last_name", "email", "username", "sso_id"]
|
||||||
|
actions = [create_or_sync_berufsbildner]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(JobLog)
|
@admin.register(JobLog)
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,10 @@ from vbv_lernwelt.course.services import mark_course_completion
|
||||||
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||||
from vbv_lernwelt.course_session.services.attendance import AttendanceUserStatus
|
from vbv_lernwelt.course_session.services.attendance import AttendanceUserStatus
|
||||||
from vbv_lernwelt.feedback.models import FeedbackResponse
|
from vbv_lernwelt.feedback.models import FeedbackResponse
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
from vbv_lernwelt.learning_mentor.models import (
|
||||||
|
AgentParticipantRelation,
|
||||||
|
MentorInvitation,
|
||||||
|
)
|
||||||
from vbv_lernwelt.learnpath.models import (
|
from vbv_lernwelt.learnpath.models import (
|
||||||
LearningContentAttendanceCourse,
|
LearningContentAttendanceCourse,
|
||||||
LearningContentFeedbackUK,
|
LearningContentFeedbackUK,
|
||||||
|
|
@ -186,7 +189,9 @@ def command(
|
||||||
SelfEvaluationFeedback.objects.all().delete()
|
SelfEvaluationFeedback.objects.all().delete()
|
||||||
CourseCompletionFeedback.objects.all().delete()
|
CourseCompletionFeedback.objects.all().delete()
|
||||||
|
|
||||||
LearningMentor.objects.all().delete()
|
AgentParticipantRelation.objects.all().delete()
|
||||||
|
# LearningMentor.objects.all().delete()
|
||||||
|
MentorInvitation.objects.all().delete()
|
||||||
User.objects.all().update(organisation=Organisation.objects.first())
|
User.objects.all().update(organisation=Organisation.objects.first())
|
||||||
User.objects.all().update(language="de")
|
User.objects.all().update(language="de")
|
||||||
User.objects.all().update(additional_json_data={})
|
User.objects.all().update(additional_json_data={})
|
||||||
|
|
@ -461,48 +466,40 @@ def command(
|
||||||
|
|
||||||
if create_learning_mentor:
|
if create_learning_mentor:
|
||||||
cs_bern = CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID)
|
cs_bern = CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID)
|
||||||
|
AgentParticipantRelation.objects.create(
|
||||||
uk_mentor = LearningMentor.objects.create(
|
agent=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
||||||
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
participant=CourseSessionUser.objects.get(
|
||||||
course_session=cs_bern,
|
user__id=TEST_STUDENT1_USER_ID, course_session=cs_bern
|
||||||
)
|
),
|
||||||
uk_mentor.participants.add(
|
role="LEARNING_MENTOR",
|
||||||
CourseSessionUser.objects.get(
|
|
||||||
user__id=TEST_STUDENT1_USER_ID,
|
|
||||||
course_session=cs_bern,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
vv_course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
|
vv_course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
|
||||||
vv_course_session = CourseSession.objects.get(course=vv_course)
|
vv_course_session = CourseSession.objects.get(course=vv_course)
|
||||||
vv_mentor = LearningMentor.objects.create(
|
|
||||||
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
|
||||||
course_session=vv_course_session,
|
|
||||||
)
|
|
||||||
|
|
||||||
vv_mentor.participants.add(
|
AgentParticipantRelation.objects.create(
|
||||||
CourseSessionUser.objects.get(
|
agent=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
||||||
user__id=TEST_STUDENT1_VV_USER_ID, course_session=vv_course_session
|
participant=CourseSessionUser.objects.get(
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
vv_mentor.participants.add(
|
|
||||||
CourseSessionUser.objects.get(
|
|
||||||
user__id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
|
||||||
course_session=vv_course_session,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
vv_student_and_mentor = LearningMentor.objects.create(
|
|
||||||
mentor=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID),
|
|
||||||
course_session=vv_course_session,
|
|
||||||
)
|
|
||||||
|
|
||||||
vv_student_and_mentor.participants.add(
|
|
||||||
CourseSessionUser.objects.get(
|
|
||||||
user__id=TEST_STUDENT1_VV_USER_ID,
|
user__id=TEST_STUDENT1_VV_USER_ID,
|
||||||
course_session=vv_course_session,
|
course_session=vv_course_session,
|
||||||
|
),
|
||||||
|
role="LEARNING_MENTOR",
|
||||||
)
|
)
|
||||||
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
||||||
|
participant=CourseSessionUser.objects.get(
|
||||||
|
user__id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||||
|
course_session=vv_course_session,
|
||||||
|
),
|
||||||
|
role="LEARNING_MENTOR",
|
||||||
|
)
|
||||||
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID),
|
||||||
|
participant=CourseSessionUser.objects.get(
|
||||||
|
user__id=TEST_STUDENT1_VV_USER_ID,
|
||||||
|
course_session=vv_course_session,
|
||||||
|
),
|
||||||
|
role="LEARNING_MENTOR",
|
||||||
)
|
)
|
||||||
|
|
||||||
course = Course.objects.get(id=COURSE_TEST_ID)
|
course = Course.objects.get(id=COURSE_TEST_ID)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,10 @@ from vbv_lernwelt.course_session.models import (
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||||
from vbv_lernwelt.feedback.models import FeedbackResponse
|
from vbv_lernwelt.feedback.models import FeedbackResponse
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
from vbv_lernwelt.learning_mentor.models import (
|
||||||
|
AgentParticipantRelation,
|
||||||
|
AgentParticipantRoleType,
|
||||||
|
)
|
||||||
from vbv_lernwelt.learnpath.models import Circle
|
from vbv_lernwelt.learnpath.models import Circle
|
||||||
from vbv_lernwelt.notify.models import Notification
|
from vbv_lernwelt.notify.models import Notification
|
||||||
|
|
||||||
|
|
@ -71,7 +74,7 @@ def create_or_update_uk(language="de"):
|
||||||
cs = CourseSession.objects.get(import_id=data["ID"])
|
cs = CourseSession.objects.get(import_id=data["ID"])
|
||||||
|
|
||||||
members, trainer, regionenleiter = get_or_create_users_uk()
|
members, trainer, regionenleiter = get_or_create_users_uk()
|
||||||
delete_cs_data(cs, members + [trainer, regionenleiter])
|
delete_cs_data(cs)
|
||||||
|
|
||||||
add_to_course_session(cs, members)
|
add_to_course_session(cs, members)
|
||||||
add_trainers_to_course_session(cs, [trainer], uk_circle_keys, language)
|
add_trainers_to_course_session(cs, [trainer], uk_circle_keys, language)
|
||||||
|
|
@ -89,13 +92,13 @@ def create_or_update_vv(language="de"):
|
||||||
|
|
||||||
create_or_update_assignment_course_session(cs)
|
create_or_update_assignment_course_session(cs)
|
||||||
members, member_with_mentor, mentor = get_or_create_users_vv()
|
members, member_with_mentor, mentor = get_or_create_users_vv()
|
||||||
delete_cs_data(cs, members + [member_with_mentor, mentor])
|
delete_cs_data(cs)
|
||||||
|
|
||||||
add_to_course_session(cs, members + [member_with_mentor])
|
add_to_course_session(cs, members + [member_with_mentor])
|
||||||
add_mentor_to_course_session(cs, [(mentor, member_with_mentor)])
|
add_mentor_to_course_session(cs, [(mentor, member_with_mentor)])
|
||||||
|
|
||||||
|
|
||||||
def delete_cs_data(cs: CourseSession, users: list[User]):
|
def delete_cs_data(cs: CourseSession):
|
||||||
if cs:
|
if cs:
|
||||||
CourseCompletion.objects.filter(course_session=cs).delete()
|
CourseCompletion.objects.filter(course_session=cs).delete()
|
||||||
Notification.objects.filter(course_session=cs).delete()
|
Notification.objects.filter(course_session=cs).delete()
|
||||||
|
|
@ -105,16 +108,8 @@ def delete_cs_data(cs: CourseSession, users: list[User]):
|
||||||
)
|
)
|
||||||
CourseSessionEdoniqTest.objects.filter(course_session=cs).delete()
|
CourseSessionEdoniqTest.objects.filter(course_session=cs).delete()
|
||||||
CourseSessionUser.objects.filter(course_session=cs).delete()
|
CourseSessionUser.objects.filter(course_session=cs).delete()
|
||||||
learning_mentor_ids = (
|
|
||||||
LearningMentor.objects.filter(participants__course_session=cs)
|
AgentParticipantRelation.objects.filter(course_session=cs).delete()
|
||||||
.values_list("id", flat=True)
|
|
||||||
.distinct()
|
|
||||||
| LearningMentor.objects.filter(mentor__in=users)
|
|
||||||
.values_list("id", flat=True)
|
|
||||||
.distinct()
|
|
||||||
)
|
|
||||||
# cannot call delete on distinct objects
|
|
||||||
LearningMentor.objects.filter(id__in=list(learning_mentor_ids)).delete()
|
|
||||||
else:
|
else:
|
||||||
logger.info("no_course_session_found", import_id=cs.import_id)
|
logger.info("no_course_session_found", import_id=cs.import_id)
|
||||||
|
|
||||||
|
|
@ -138,15 +133,12 @@ def add_mentor_to_course_session(
|
||||||
course_session: CourseSession, mentor_mentee_pairs: list[tuple[User, User]]
|
course_session: CourseSession, mentor_mentee_pairs: list[tuple[User, User]]
|
||||||
):
|
):
|
||||||
for mentor, mentee in mentor_mentee_pairs:
|
for mentor, mentee in mentor_mentee_pairs:
|
||||||
lm = LearningMentor.objects.create(
|
AgentParticipantRelation.objects.create(
|
||||||
course_session=course_session,
|
agent=mentor,
|
||||||
mentor=mentor,
|
participant=CourseSessionUser.objects.get(
|
||||||
)
|
user__id=mentee.id, course_session=course_session
|
||||||
lm.participants.add(
|
),
|
||||||
CourseSessionUser.objects.get(
|
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
|
||||||
user__id=mentee.id,
|
|
||||||
course_session=course_session,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,20 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class UserShortSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"email",
|
||||||
|
"username",
|
||||||
|
"avatar_url",
|
||||||
|
"language",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class CypressUserSerializer(serializers.ModelSerializer):
|
class CypressUserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,10 @@ from vbv_lernwelt.course_session.models import (
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||||
from vbv_lernwelt.duedate.models import DueDate
|
from vbv_lernwelt.duedate.models import DueDate
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
from vbv_lernwelt.learning_mentor.models import (
|
||||||
|
AgentParticipantRelation,
|
||||||
|
AgentParticipantRoleType,
|
||||||
|
)
|
||||||
from vbv_lernwelt.learnpath.models import (
|
from vbv_lernwelt.learnpath.models import (
|
||||||
Circle,
|
Circle,
|
||||||
LearningContentAssignment,
|
LearningContentAssignment,
|
||||||
|
|
@ -101,13 +104,13 @@ def create_course_session(
|
||||||
|
|
||||||
|
|
||||||
def add_learning_mentor(
|
def add_learning_mentor(
|
||||||
course_session: CourseSession, mentor: User, mentee: CourseSessionUser
|
mentor: User, mentee: CourseSessionUser
|
||||||
) -> LearningMentor:
|
) -> AgentParticipantRelation:
|
||||||
learning_mentor = LearningMentor.objects.create(
|
return AgentParticipantRelation.objects.create(
|
||||||
course_session=course_session, mentor=mentor
|
agent=mentor,
|
||||||
|
participant=mentee,
|
||||||
|
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
|
||||||
)
|
)
|
||||||
learning_mentor.participants.add(mentee)
|
|
||||||
return learning_mentor
|
|
||||||
|
|
||||||
|
|
||||||
def add_course_session_user(
|
def add_course_session_user(
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,7 @@ from vbv_lernwelt.competence.create_vv_new_competence_profile import (
|
||||||
create_vv_new_competence_profile,
|
create_vv_new_competence_profile,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.competence.models import PerformanceCriteria
|
from vbv_lernwelt.competence.models import PerformanceCriteria
|
||||||
from vbv_lernwelt.core.constants import (
|
from vbv_lernwelt.core.constants import TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID
|
||||||
TEST_MENTOR1_USER_ID,
|
|
||||||
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
|
||||||
)
|
|
||||||
from vbv_lernwelt.core.create_default_users import default_users
|
from vbv_lernwelt.core.create_default_users import default_users
|
||||||
from vbv_lernwelt.core.models import User
|
from vbv_lernwelt.core.models import User
|
||||||
from vbv_lernwelt.course.consts import (
|
from vbv_lernwelt.course.consts import (
|
||||||
|
|
@ -95,7 +92,6 @@ from vbv_lernwelt.importer.services import (
|
||||||
import_students_from_excel,
|
import_students_from_excel,
|
||||||
import_trainers_from_excel_for_training,
|
import_trainers_from_excel_for_training,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
|
||||||
from vbv_lernwelt.learnpath.create_vv_new_learning_path import (
|
from vbv_lernwelt.learnpath.create_vv_new_learning_path import (
|
||||||
create_vv_motorfahrzeug_pruefung_learning_path,
|
create_vv_motorfahrzeug_pruefung_learning_path,
|
||||||
create_vv_new_learning_path,
|
create_vv_new_learning_path,
|
||||||
|
|
@ -245,6 +241,11 @@ def create_versicherungsvermittlerin_course(
|
||||||
course_session=cs,
|
course_session=cs,
|
||||||
user=User.objects.get(username="student-vv@eiger-versicherungen.ch"),
|
user=User.objects.get(username="student-vv@eiger-versicherungen.ch"),
|
||||||
)
|
)
|
||||||
|
mentor_and_student_2_learning_csu = CourseSessionUser.objects.create(
|
||||||
|
course_session=cs,
|
||||||
|
user=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID),
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
)
|
||||||
|
|
||||||
CourseSessionUser.objects.create(
|
CourseSessionUser.objects.create(
|
||||||
course_session=cs,
|
course_session=cs,
|
||||||
|
|
@ -264,30 +265,6 @@ def create_versicherungsvermittlerin_course(
|
||||||
role=CourseSessionUser.Role.EXPERT,
|
role=CourseSessionUser.Role.EXPERT,
|
||||||
)
|
)
|
||||||
|
|
||||||
mentor_and_student_2_learning_csu = CourseSessionUser.objects.create(
|
|
||||||
course_session=cs,
|
|
||||||
user=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID),
|
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
|
||||||
)
|
|
||||||
|
|
||||||
# TEST_MENTOR1_USER_ID is only mentor
|
|
||||||
just_mentor = LearningMentor.objects.create(
|
|
||||||
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
|
||||||
course_session=cs,
|
|
||||||
)
|
|
||||||
|
|
||||||
just_mentor.participants.add(student_1_csu)
|
|
||||||
just_mentor.participants.add(mentor_and_student_2_learning_csu)
|
|
||||||
|
|
||||||
# TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID is both student and mentor
|
|
||||||
|
|
||||||
mentor_and_student_learning_mentor = LearningMentor.objects.create(
|
|
||||||
mentor=User.objects.get(id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID),
|
|
||||||
course_session=cs,
|
|
||||||
)
|
|
||||||
|
|
||||||
mentor_and_student_learning_mentor.participants.add(student_1_csu)
|
|
||||||
|
|
||||||
for admin_email in ADMIN_EMAILS:
|
for admin_email in ADMIN_EMAILS:
|
||||||
CourseSessionUser.objects.create(
|
CourseSessionUser.objects.create(
|
||||||
course_session=cs,
|
course_session=cs,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 3.2.25 on 2024-07-17 14:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import vbv_lernwelt.course.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("course", "0008_auto_20240403_1132"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="coursecompletion",
|
||||||
|
name="completion_status",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("SUCCESS", "Success"),
|
||||||
|
("FAIL", "Fail"),
|
||||||
|
("UNKNOWN", "Unknown"),
|
||||||
|
],
|
||||||
|
default=vbv_lernwelt.course.models.CourseCompletionStatus["UNKNOWN"],
|
||||||
|
max_length=255,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Generated by Django 4.2.13 on 2024-08-05 05:31
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("course", "0009_alter_coursecompletion_completion_status"),
|
||||||
|
("course", "0009_coursesessionuser_required_attendance_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = []
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Generated by Django 4.2.13 on 2024-08-10 11:36
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("course", "0010_merge_20240805_0731"),
|
||||||
|
("course", "0011_merge_20240807_1317"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = []
|
||||||
|
|
@ -19,7 +19,7 @@ class CourseGraphQLTestCase(TestCase):
|
||||||
create_versicherungsvermittlerin_course()
|
create_versicherungsvermittlerin_course()
|
||||||
|
|
||||||
def test_update_course_profile(self):
|
def test_update_course_profile(self):
|
||||||
user = User.objects.get(username="student-vv@eiger-versicherungen.ch")
|
user = User.objects.get(username="test-student-and-mentor2@example.com")
|
||||||
request = RequestFactory().get("/")
|
request = RequestFactory().get("/")
|
||||||
request.user = user
|
request.user = user
|
||||||
client = Client(schema=schema, context_value=request)
|
client = Client(schema=schema, context_value=request)
|
||||||
|
|
@ -98,7 +98,7 @@ class CourseGraphQLTestCase(TestCase):
|
||||||
self.assertEqual(chosen_profile, profile)
|
self.assertEqual(chosen_profile, profile)
|
||||||
|
|
||||||
def test_mentor_profile_view(self):
|
def test_mentor_profile_view(self):
|
||||||
user = User.objects.get(username="test-mentor1@example.com")
|
user = User.objects.get(username="test-student-and-mentor2@example.com")
|
||||||
request = RequestFactory().get("/")
|
request = RequestFactory().get("/")
|
||||||
request.user = user
|
request.user = user
|
||||||
client = Client(schema=schema, context_value=request)
|
client = Client(schema=schema, context_value=request)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ from vbv_lernwelt.iam.permissions import (
|
||||||
has_course_access_by_page_request,
|
has_course_access_by_page_request,
|
||||||
is_circle_expert,
|
is_circle_expert,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation
|
||||||
|
|
||||||
logger = structlog.get_logger(__name__)
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
@ -155,9 +155,10 @@ def get_course_sessions(request):
|
||||||
|
|
||||||
# enrich with mentor course sessions
|
# enrich with mentor course sessions
|
||||||
mentor_course_sessions = CourseSession.objects.filter(
|
mentor_course_sessions = CourseSession.objects.filter(
|
||||||
id__in=LearningMentor.objects.filter(mentor=request.user).values_list(
|
id__in=[
|
||||||
"course_session", flat=True
|
rel.participant.course_session_id
|
||||||
)
|
for rel in AgentParticipantRelation.objects.filter(agent=request.user)
|
||||||
|
]
|
||||||
).prefetch_related("course")
|
).prefetch_related("course")
|
||||||
|
|
||||||
all_to_serialize = (
|
all_to_serialize = (
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,48 @@ from vbv_lernwelt.iam.permissions import (
|
||||||
can_view_course_session_group_statistics,
|
can_view_course_session_group_statistics,
|
||||||
can_view_course_session_progress,
|
can_view_course_session_progress,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
from vbv_lernwelt.learning_mentor.models import (
|
||||||
|
AgentParticipantRelation,
|
||||||
|
AgentParticipantRoleType,
|
||||||
|
)
|
||||||
from vbv_lernwelt.learnpath.models import Circle
|
from vbv_lernwelt.learnpath.models import Circle
|
||||||
|
|
||||||
|
|
||||||
|
def _agent_course_statistics(user, course_id: str, role: str):
|
||||||
|
course = Course.objects.get(id=course_id)
|
||||||
|
|
||||||
|
participant_ids = set()
|
||||||
|
course_session_ids = set()
|
||||||
|
|
||||||
|
relations_qs = AgentParticipantRelation.objects.filter(
|
||||||
|
agent=user,
|
||||||
|
role=role,
|
||||||
|
participant__course_session__course=course,
|
||||||
|
)
|
||||||
|
|
||||||
|
for relation in relations_qs:
|
||||||
|
participant_ids.add(relation.participant.user_id)
|
||||||
|
course_session_ids.add(relation.participant.course_session_id)
|
||||||
|
|
||||||
|
return CourseStatisticsType(
|
||||||
|
_id=f"{role}:{course.id}", # noqa
|
||||||
|
course_id=course.id, # noqa
|
||||||
|
course_title=course.title, # noqa
|
||||||
|
course_slug=course.slug, # noqa
|
||||||
|
course_session_selection_ids=list(course_session_ids), # noqa
|
||||||
|
user_selection_ids=list(participant_ids), # noqa
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DashboardQuery(graphene.ObjectType):
|
class DashboardQuery(graphene.ObjectType):
|
||||||
course_statistics = graphene.Field(
|
course_statistics = graphene.Field(
|
||||||
CourseStatisticsType, course_id=graphene.ID(required=True)
|
CourseStatisticsType, course_id=graphene.ID(required=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
mentor_course_statistics = graphene.Field(
|
mentor_course_statistics = graphene.Field(
|
||||||
BaseStatisticsType, course_id=graphene.ID(required=True)
|
BaseStatisticsType,
|
||||||
|
course_id=graphene.ID(required=True),
|
||||||
|
agent_role=graphene.String(required=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
course_progress = graphene.Field(
|
course_progress = graphene.Field(
|
||||||
|
|
@ -88,28 +119,12 @@ class DashboardQuery(graphene.ObjectType):
|
||||||
course_session_selection_ids=list(course_session_ids), # noqa
|
course_session_selection_ids=list(course_session_ids), # noqa
|
||||||
)
|
)
|
||||||
|
|
||||||
def resolve_mentor_course_statistics(root, info, course_id: str): # noqa
|
def resolve_mentor_course_statistics(
|
||||||
|
root, info, course_id: str, agent_role: str
|
||||||
|
): # noqa
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
course = Course.objects.get(id=course_id)
|
|
||||||
|
|
||||||
mentees_ids = set()
|
return _agent_course_statistics(user, course_id, role=agent_role)
|
||||||
course_session_ids = set()
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
return CourseStatisticsType(
|
|
||||||
_id=f"mentor:{course.id}", # noqa
|
|
||||||
course_id=course.id, # noqa
|
|
||||||
course_title=course.title, # noqa
|
|
||||||
course_slug=course.slug, # noqa
|
|
||||||
course_session_selection_ids=list(course_session_ids), # noqa
|
|
||||||
user_selection_ids=list(mentees_ids), # noqa
|
|
||||||
)
|
|
||||||
|
|
||||||
def resolve_dashboard_config(root, info): # noqa
|
def resolve_dashboard_config(root, info): # noqa
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
|
|
@ -249,20 +264,22 @@ def get_user_statistics_dashboards(user: User) -> Tuple[List[Dict[str, str]], Se
|
||||||
def get_learning_mentor_dashboards(
|
def get_learning_mentor_dashboards(
|
||||||
user: User, exclude_course_ids: Set[int]
|
user: User, exclude_course_ids: Set[int]
|
||||||
) -> Tuple[List[Dict[str, str]], Set[int]]:
|
) -> Tuple[List[Dict[str, str]], Set[int]]:
|
||||||
learning_mentor = LearningMentor.objects.filter(mentor=user).exclude(
|
learning_mentor_relation_qs = AgentParticipantRelation.objects.filter(
|
||||||
course_session__course__id__in=exclude_course_ids
|
agent=user, role=AgentParticipantRoleType.LEARNING_MENTOR.value
|
||||||
)
|
).exclude(participant__course_session__course__id__in=exclude_course_ids)
|
||||||
|
|
||||||
dashboards = []
|
dashboards = []
|
||||||
course_ids = set()
|
course_ids = set()
|
||||||
|
|
||||||
for mentor in learning_mentor:
|
for rel in learning_mentor_relation_qs:
|
||||||
course = mentor.course_session.course
|
course = rel.participant.course_session.course
|
||||||
course_ids.add(course.id)
|
|
||||||
if course.id in UK_COURSE_IDS:
|
if course.id in UK_COURSE_IDS:
|
||||||
dashboard_type = DashboardType.PRAXISBILDNER_DASHBOARD
|
dashboard_type = DashboardType.PRAXISBILDNER_DASHBOARD
|
||||||
else:
|
else:
|
||||||
dashboard_type = DashboardType.MENTOR_DASHBOARD
|
dashboard_type = DashboardType.MENTOR_DASHBOARD
|
||||||
|
|
||||||
|
if course.id not in course_ids:
|
||||||
|
course_ids.add(course.id)
|
||||||
dashboards.append(
|
dashboards.append(
|
||||||
{
|
{
|
||||||
"id": str(course.id),
|
"id": str(course.id),
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ class AssignmentCompletionMetricsType(graphene.ObjectType):
|
||||||
unranked_count = graphene.Int(required=True)
|
unranked_count = graphene.Int(required=True)
|
||||||
ranking_completed = graphene.Boolean(required=True)
|
ranking_completed = graphene.Boolean(required=True)
|
||||||
average_passed = graphene.Float(required=True)
|
average_passed = graphene.Float(required=True)
|
||||||
|
average_evaluation_percent = graphene.Float()
|
||||||
|
competence_certificate_weight = graphene.Float()
|
||||||
|
|
||||||
|
|
||||||
class AssignmentStatisticsRecordType(graphene.ObjectType):
|
class AssignmentStatisticsRecordType(graphene.ObjectType):
|
||||||
|
|
@ -34,10 +36,15 @@ class AssignmentStatisticsRecordType(graphene.ObjectType):
|
||||||
course_session_assignment_id = graphene.ID(required=True)
|
course_session_assignment_id = graphene.ID(required=True)
|
||||||
circle_id = graphene.ID(required=True)
|
circle_id = graphene.ID(required=True)
|
||||||
generation = graphene.String(required=True)
|
generation = graphene.String(required=True)
|
||||||
|
region = graphene.String(required=True)
|
||||||
assignment_type_translation_key = graphene.String(required=True)
|
assignment_type_translation_key = graphene.String(required=True)
|
||||||
assignment_title = graphene.String(required=True)
|
assignment_title = graphene.String(required=True)
|
||||||
deadline = graphene.DateTime(required=True)
|
deadline = graphene.DateTime(required=True)
|
||||||
|
course_session_title = graphene.String()
|
||||||
|
competence_certificate_id = graphene.ID()
|
||||||
|
competence_certificate_title = graphene.String()
|
||||||
metrics = graphene.Field(AssignmentCompletionMetricsType, required=True)
|
metrics = graphene.Field(AssignmentCompletionMetricsType, required=True)
|
||||||
|
learning_content_id = graphene.ID(required=True)
|
||||||
details_url = graphene.String(required=True)
|
details_url = graphene.String(required=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -47,6 +54,7 @@ class AssignmentStatisticsSummaryType(graphene.ObjectType):
|
||||||
average_passed = graphene.Float(required=True)
|
average_passed = graphene.Float(required=True)
|
||||||
total_passed = graphene.Int(required=True)
|
total_passed = graphene.Int(required=True)
|
||||||
total_failed = graphene.Int(required=True)
|
total_failed = graphene.Int(required=True)
|
||||||
|
average_evaluation_percent = graphene.Float()
|
||||||
|
|
||||||
|
|
||||||
class AssignmentsStatisticsType(graphene.ObjectType):
|
class AssignmentsStatisticsType(graphene.ObjectType):
|
||||||
|
|
@ -83,12 +91,17 @@ def create_assignment_summary(
|
||||||
total_passed = sum([m.passed_count for m in completed_metrics])
|
total_passed = sum([m.passed_count for m in completed_metrics])
|
||||||
total_failed = sum([m.failed_count for m in completed_metrics])
|
total_failed = sum([m.failed_count for m in completed_metrics])
|
||||||
|
|
||||||
|
total_average_evaluation_percent = (
|
||||||
|
sum([m.average_evaluation_percent for m in completed_metrics]) / completed_count
|
||||||
|
)
|
||||||
|
|
||||||
return AssignmentStatisticsSummaryType(
|
return AssignmentStatisticsSummaryType(
|
||||||
_id=urql_id, # noqa
|
_id=urql_id, # noqa
|
||||||
completed_count=completed_count, # noqa
|
completed_count=completed_count, # noqa
|
||||||
average_passed=average_passed_completed, # noqa
|
average_passed=average_passed_completed, # noqa
|
||||||
total_passed=total_passed, # noqa
|
total_passed=total_passed, # noqa
|
||||||
total_failed=total_failed, # noqa
|
total_failed=total_failed, # noqa
|
||||||
|
average_evaluation_percent=total_average_evaluation_percent, # noqa
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -102,28 +115,29 @@ def get_assignment_completion_metrics(
|
||||||
if not context:
|
if not context:
|
||||||
context = {}
|
context = {}
|
||||||
|
|
||||||
if user_selection_ids:
|
csu_qs = CourseSessionUser.objects.filter(
|
||||||
course_session_users = user_selection_ids
|
course_session_id=course_session.id, role=CourseSessionUser.Role.MEMBER
|
||||||
else:
|
)
|
||||||
key = f"CourseSessionUser_{course_session.id}"
|
|
||||||
|
key = f"CourseSessionUsers_{course_session.id}"
|
||||||
if not key in context:
|
if not key in context:
|
||||||
course_session_users = CourseSessionUser.objects.filter(
|
if user_selection_ids:
|
||||||
course_session=course_session,
|
csu_qs = csu_qs.filter(user_id__in=user_selection_ids)
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
|
||||||
).values_list("user", flat=True)
|
context[key] = list(csu_qs.values_list("user", flat=True))
|
||||||
context[key] = course_session_users
|
|
||||||
else:
|
|
||||||
course_session_users = context[key]
|
course_session_users = context[key]
|
||||||
|
|
||||||
evaluation_results = AssignmentCompletion.objects.filter(
|
assignment_completions = AssignmentCompletion.objects.filter(
|
||||||
completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value,
|
completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value,
|
||||||
assignment_user__in=course_session_users,
|
assignment_user__in=course_session_users,
|
||||||
course_session=course_session,
|
course_session=course_session,
|
||||||
assignment=assignment,
|
assignment=assignment,
|
||||||
).values_list("evaluation_passed", flat=True)
|
)
|
||||||
|
evaluation_passed_results = [ac.evaluation_passed for ac in assignment_completions]
|
||||||
|
|
||||||
passed_count = len([passed for passed in evaluation_results if passed])
|
passed_count = len([passed for passed in evaluation_passed_results if passed])
|
||||||
failed_count = len(evaluation_results) - passed_count
|
failed_count = len(evaluation_passed_results) - passed_count
|
||||||
|
|
||||||
participants_count = len(course_session_users)
|
participants_count = len(course_session_users)
|
||||||
unranked_count = participants_count - passed_count - failed_count
|
unranked_count = participants_count - passed_count - failed_count
|
||||||
|
|
@ -133,6 +147,17 @@ def get_assignment_completion_metrics(
|
||||||
else:
|
else:
|
||||||
average_passed = math.ceil(passed_count / participants_count * 100)
|
average_passed = math.ceil(passed_count / participants_count * 100)
|
||||||
|
|
||||||
|
# calculate average points in percent
|
||||||
|
evaluation_percent_results = [
|
||||||
|
((ac.evaluation_points_final or 0) / (ac.evaluation_max_points or 1))
|
||||||
|
for ac in assignment_completions
|
||||||
|
]
|
||||||
|
average_evaluation_percent = (
|
||||||
|
sum(evaluation_percent_results) / (len(evaluation_percent_results) or 1)
|
||||||
|
if evaluation_percent_results
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
|
||||||
return AssignmentCompletionMetricsType(
|
return AssignmentCompletionMetricsType(
|
||||||
_id=f"{course_session.id}-{assignment.id}@{urql_id_postfix}", # noqa
|
_id=f"{course_session.id}-{assignment.id}@{urql_id_postfix}", # noqa
|
||||||
passed_count=passed_count, # noqa
|
passed_count=passed_count, # noqa
|
||||||
|
|
@ -140,6 +165,8 @@ def get_assignment_completion_metrics(
|
||||||
unranked_count=unranked_count, # noqa
|
unranked_count=unranked_count, # noqa
|
||||||
ranking_completed=(passed_count > 0 or failed_count > 0), # noqa
|
ranking_completed=(passed_count > 0 or failed_count > 0), # noqa
|
||||||
average_passed=average_passed, # noqa
|
average_passed=average_passed, # noqa
|
||||||
|
average_evaluation_percent=average_evaluation_percent, # noqa
|
||||||
|
competence_certificate_weight=assignment.competence_certificate_weight, # noqa
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -171,6 +198,7 @@ def create_record(
|
||||||
circle_id = context[key]
|
circle_id = context[key]
|
||||||
|
|
||||||
learning_content = course_session_assignment.learning_content
|
learning_content = course_session_assignment.learning_content
|
||||||
|
competence_certificate = learning_content.content_assignment.competence_certificate
|
||||||
|
|
||||||
return (
|
return (
|
||||||
AssignmentStatisticsRecordType(
|
AssignmentStatisticsRecordType(
|
||||||
|
|
@ -178,12 +206,16 @@ def create_record(
|
||||||
_id=f"{course_session_assignment._meta.model_name}#{course_session_assignment.id}@{urql_id_postfix}",
|
_id=f"{course_session_assignment._meta.model_name}#{course_session_assignment.id}@{urql_id_postfix}",
|
||||||
# noqa
|
# noqa
|
||||||
course_session_id=str(course_session_assignment.course_session.id), # noqa
|
course_session_id=str(course_session_assignment.course_session.id), # noqa
|
||||||
|
course_session_title=course_session_assignment.course_session.title, # noqa
|
||||||
circle_id=circle_id, # noqa
|
circle_id=circle_id, # noqa
|
||||||
course_session_assignment_id=str(course_session_assignment.id), # noqa
|
course_session_assignment_id=str(course_session_assignment.id), # noqa
|
||||||
generation=course_session_assignment.course_session.generation, # noqa
|
generation=course_session_assignment.course_session.generation, # noqa
|
||||||
assignment_type_translation_key=due_date.assignment_type_translation_key,
|
region=course_session_assignment.course_session.region, # noqa
|
||||||
# noqa
|
assignment_type_translation_key=due_date.assignment_type_translation_key, # noqa
|
||||||
|
competence_certificate_id=str(competence_certificate.id), # noqa
|
||||||
|
competence_certificate_title=competence_certificate.title, # noqa
|
||||||
assignment_title=learning_content.content_assignment.title, # noqa
|
assignment_title=learning_content.content_assignment.title, # noqa
|
||||||
|
learning_content_id=str(learning_content.id), # noqa
|
||||||
metrics=get_assignment_completion_metrics( # noqa
|
metrics=get_assignment_completion_metrics( # noqa
|
||||||
course_session=course_session_assignment.course_session, # noqa
|
course_session=course_session_assignment.course_session, # noqa
|
||||||
assignment=learning_content.content_assignment, # noqa
|
assignment=learning_content.content_assignment, # noqa
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ class PresenceRecordStatisticsType(graphene.ObjectType):
|
||||||
_id = graphene.ID(required=True)
|
_id = graphene.ID(required=True)
|
||||||
course_session_id = graphene.ID(required=True)
|
course_session_id = graphene.ID(required=True)
|
||||||
generation = graphene.String(required=True)
|
generation = graphene.String(required=True)
|
||||||
|
region = graphene.String(required=True)
|
||||||
circle_id = graphene.ID(required=True)
|
circle_id = graphene.ID(required=True)
|
||||||
due_date = graphene.DateTime(required=True)
|
due_date = graphene.DateTime(required=True)
|
||||||
participants_present = graphene.Int(required=True)
|
participants_present = graphene.Int(required=True)
|
||||||
|
|
@ -83,6 +84,7 @@ def attendance_day_presences(
|
||||||
_id=f"{urql_id}:attendance_day:{attendance_day.id}", # noqa
|
_id=f"{urql_id}:attendance_day:{attendance_day.id}", # noqa
|
||||||
course_session_id=course_session.id, # noqa
|
course_session_id=course_session.id, # noqa
|
||||||
generation=course_session.generation, # noqa
|
generation=course_session.generation, # noqa
|
||||||
|
region=course_session.region, # noqa
|
||||||
circle_id=circle.id, # noqa
|
circle_id=circle.id, # noqa
|
||||||
due_date=attendance_day.due_date.end, # noqa
|
due_date=attendance_day.due_date.end, # noqa
|
||||||
participants_present=participants_present, # noqa
|
participants_present=participants_present, # noqa
|
||||||
|
|
@ -98,7 +100,9 @@ def attendance_day_presences(
|
||||||
)
|
)
|
||||||
|
|
||||||
return AttendanceDayPresencesStatisticsType(
|
return AttendanceDayPresencesStatisticsType(
|
||||||
summary=summary, records=records, _id=course_id # noqa
|
summary=summary,
|
||||||
|
records=records,
|
||||||
|
_id=course_id, # noqa
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ class CompetenceRecordStatisticsType(graphene.ObjectType):
|
||||||
_id = graphene.ID(required=True)
|
_id = graphene.ID(required=True)
|
||||||
course_session_id = graphene.ID(required=True)
|
course_session_id = graphene.ID(required=True)
|
||||||
generation = graphene.String(required=True)
|
generation = graphene.String(required=True)
|
||||||
|
region = graphene.String(required=True)
|
||||||
title = graphene.String(required=True)
|
title = graphene.String(required=True)
|
||||||
circle_id = graphene.ID(required=True)
|
circle_id = graphene.ID(required=True)
|
||||||
success_count = graphene.Int(required=True)
|
success_count = graphene.Int(required=True)
|
||||||
|
|
@ -71,22 +72,20 @@ def competences(
|
||||||
|
|
||||||
combined_id = f"{circle.id}-{completion.course_session.id}@{urql_id_postfix}"
|
combined_id = f"{circle.id}-{completion.course_session.id}@{urql_id_postfix}"
|
||||||
|
|
||||||
if combined_id not in competence_records:
|
competence_records.setdefault(combined_id, {}).setdefault(
|
||||||
competence_records[combined_id] = {}
|
learning_unit,
|
||||||
|
CompetenceRecordStatisticsType(
|
||||||
if learning_unit not in competence_records[combined_id]:
|
_id=combined_id, # noqa
|
||||||
competence_records[combined_id][
|
title=learning_unit.title, # noqa
|
||||||
learning_unit
|
course_session_id=completion.course_session.id, # noqa
|
||||||
] = CompetenceRecordStatisticsType(
|
generation=completion.course_session.generation, # noqa
|
||||||
_id=combined_id,
|
region=completion.course_session.region, # noqa
|
||||||
title=learning_unit.title,
|
circle_id=circle.id, # noqa
|
||||||
course_session_id=completion.course_session.id,
|
success_count=0, # noqa
|
||||||
generation=completion.course_session.generation,
|
fail_count=0, # noqa
|
||||||
circle_id=circle.id,
|
|
||||||
success_count=0,
|
|
||||||
fail_count=0,
|
|
||||||
details_url=f"/course/{course_slug}/cockpit?courseSessionId={completion.course_session.id}",
|
details_url=f"/course/{course_slug}/cockpit?courseSessionId={completion.course_session.id}",
|
||||||
# noqa
|
# noqa
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if completion.completion_status == CourseCompletionStatus.SUCCESS.value:
|
if completion.completion_status == CourseCompletionStatus.SUCCESS.value:
|
||||||
|
|
@ -100,7 +99,7 @@ def competences(
|
||||||
for record in circle_records.values()
|
for record in circle_records.values()
|
||||||
]
|
]
|
||||||
|
|
||||||
success_count = sum(c.success_count for c in values)
|
success_count = sum([c.success_count for c in values])
|
||||||
fail_count = sum(c.fail_count for c in values)
|
fail_count = sum([c.fail_count for c in values])
|
||||||
|
|
||||||
return values, success_count, fail_count
|
return values, success_count, fail_count
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ from vbv_lernwelt.learnpath.models import Circle
|
||||||
class StatisticsCourseSessionDataType(graphene.ObjectType):
|
class StatisticsCourseSessionDataType(graphene.ObjectType):
|
||||||
id = graphene.ID(required=True)
|
id = graphene.ID(required=True)
|
||||||
name = graphene.String(required=True)
|
name = graphene.String(required=True)
|
||||||
|
region = graphene.String(required=True)
|
||||||
|
|
||||||
|
|
||||||
class StatisticsCircleDataType(graphene.ObjectType):
|
class StatisticsCircleDataType(graphene.ObjectType):
|
||||||
|
|
@ -96,6 +97,10 @@ class BaseStatisticsType(graphene.ObjectType):
|
||||||
user_selection_ids = graphene.List(graphene.ID, required=False)
|
user_selection_ids = graphene.List(graphene.ID, required=False)
|
||||||
assignments = graphene.Field(AssignmentsStatisticsType, required=True)
|
assignments = graphene.Field(AssignmentsStatisticsType, required=True)
|
||||||
|
|
||||||
|
course_session_properties = graphene.Field(
|
||||||
|
StatisticsCourseSessionPropertiesType, required=True
|
||||||
|
)
|
||||||
|
|
||||||
def resolve_assignments(root, _info) -> AssignmentsStatisticsType:
|
def resolve_assignments(root, _info) -> AssignmentsStatisticsType:
|
||||||
user_selection_ids = (
|
user_selection_ids = (
|
||||||
[str(user) for user in root.user_selection_ids]
|
[str(user) for user in root.user_selection_ids]
|
||||||
|
|
@ -110,14 +115,55 @@ class BaseStatisticsType(graphene.ObjectType):
|
||||||
urql_id=str(root._id),
|
urql_id=str(root._id),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def resolve_course_session_properties(root, info):
|
||||||
|
course_session_data = []
|
||||||
|
circle_data = []
|
||||||
|
generations = set()
|
||||||
|
|
||||||
|
course_sessions = CourseSession.objects.filter(
|
||||||
|
id__in=root.course_session_selection_ids,
|
||||||
|
course_id=root.course_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
for course_session in course_sessions:
|
||||||
|
course_session_data.append(
|
||||||
|
StatisticsCourseSessionDataType(
|
||||||
|
id=course_session.id, # noqa
|
||||||
|
name=course_session.title, # noqa
|
||||||
|
region=course_session.region, # noqa
|
||||||
|
)
|
||||||
|
)
|
||||||
|
generations.add(course_session.generation)
|
||||||
|
|
||||||
|
circles = (
|
||||||
|
course_session.course.get_learning_path()
|
||||||
|
.get_descendants()
|
||||||
|
.live()
|
||||||
|
.specific()
|
||||||
|
.exact_type(Circle)
|
||||||
|
)
|
||||||
|
|
||||||
|
for circle in circles:
|
||||||
|
if not any(c.id == circle.id for c in circle_data):
|
||||||
|
circle_data.append(
|
||||||
|
StatisticsCircleDataType(
|
||||||
|
id=circle.id, # noqa
|
||||||
|
name=circle.title, # noqa
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return StatisticsCourseSessionPropertiesType(
|
||||||
|
_id=root._id, # noqa
|
||||||
|
sessions=course_session_data, # noqa
|
||||||
|
generations=list(generations), # noqa
|
||||||
|
circles=circle_data, # noqa
|
||||||
|
)
|
||||||
|
|
||||||
def get_circle_ids(self, info):
|
def get_circle_ids(self, info):
|
||||||
return getattr(info.context, "circle_ids", None)
|
return getattr(info.context, "circle_ids", None)
|
||||||
|
|
||||||
|
|
||||||
class CourseStatisticsType(BaseStatisticsType):
|
class CourseStatisticsType(BaseStatisticsType):
|
||||||
course_session_properties = graphene.Field(
|
|
||||||
StatisticsCourseSessionPropertiesType, required=True
|
|
||||||
)
|
|
||||||
course_session_selection_metrics = graphene.Field(
|
course_session_selection_metrics = graphene.Field(
|
||||||
StatisticsCourseSessionsSelectionMetricType, required=True
|
StatisticsCourseSessionsSelectionMetricType, required=True
|
||||||
)
|
)
|
||||||
|
|
@ -195,46 +241,3 @@ class CourseStatisticsType(BaseStatisticsType):
|
||||||
participant_count=participant_count, # noqa
|
participant_count=participant_count, # noqa
|
||||||
expert_count=expert_count, # noqa
|
expert_count=expert_count, # noqa
|
||||||
)
|
)
|
||||||
|
|
||||||
def resolve_course_session_properties(root, info):
|
|
||||||
course_session_data = []
|
|
||||||
circle_data = []
|
|
||||||
generations = set()
|
|
||||||
|
|
||||||
course_sessions = CourseSession.objects.filter(
|
|
||||||
id__in=root.course_session_selection_ids,
|
|
||||||
course_id=root.course_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
for course_session in course_sessions:
|
|
||||||
course_session_data.append(
|
|
||||||
StatisticsCourseSessionDataType(
|
|
||||||
id=course_session.id, # noqa
|
|
||||||
name=course_session.title, # noqa
|
|
||||||
)
|
|
||||||
)
|
|
||||||
generations.add(course_session.generation)
|
|
||||||
|
|
||||||
circles = (
|
|
||||||
course_session.course.get_learning_path()
|
|
||||||
.get_descendants()
|
|
||||||
.live()
|
|
||||||
.specific()
|
|
||||||
.exact_type(Circle)
|
|
||||||
)
|
|
||||||
|
|
||||||
for circle in circles:
|
|
||||||
if not any(c.id == circle.id for c in circle_data):
|
|
||||||
circle_data.append(
|
|
||||||
StatisticsCircleDataType(
|
|
||||||
id=circle.id, # noqa
|
|
||||||
name=circle.title, # noqa
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return StatisticsCourseSessionPropertiesType(
|
|
||||||
_id=root._id, # noqa
|
|
||||||
sessions=course_session_data, # noqa
|
|
||||||
generations=list(generations), # noqa
|
|
||||||
circles=circle_data, # noqa
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ class FeedbackStatisticsRecordType(graphene.ObjectType):
|
||||||
_id = graphene.ID(required=True)
|
_id = graphene.ID(required=True)
|
||||||
course_session_id = graphene.ID(required=True)
|
course_session_id = graphene.ID(required=True)
|
||||||
generation = graphene.String(required=True)
|
generation = graphene.String(required=True)
|
||||||
|
region = graphene.String(required=True)
|
||||||
circle_id = graphene.ID(required=True)
|
circle_id = graphene.ID(required=True)
|
||||||
satisfaction_average = graphene.Float(required=True)
|
satisfaction_average = graphene.Float(required=True)
|
||||||
satisfaction_max = graphene.Int(required=True)
|
satisfaction_max = graphene.Int(required=True)
|
||||||
|
|
@ -68,6 +69,7 @@ def feedback_responses(
|
||||||
feedbacks=fbs,
|
feedbacks=fbs,
|
||||||
course_session_id=course_session.id,
|
course_session_id=course_session.id,
|
||||||
generation=course_session.generation,
|
generation=course_session.generation,
|
||||||
|
region=course_session.region,
|
||||||
course_slug=str(course_slug),
|
course_slug=str(course_slug),
|
||||||
urql_id_postfix=urql_id,
|
urql_id_postfix=urql_id,
|
||||||
)
|
)
|
||||||
|
|
@ -96,6 +98,7 @@ def circle_feedback_average(
|
||||||
feedbacks: List[FeedbackResponse],
|
feedbacks: List[FeedbackResponse],
|
||||||
course_session_id,
|
course_session_id,
|
||||||
generation: str,
|
generation: str,
|
||||||
|
region: str,
|
||||||
course_slug: str,
|
course_slug: str,
|
||||||
urql_id_postfix: str = "",
|
urql_id_postfix: str = "",
|
||||||
):
|
):
|
||||||
|
|
@ -128,6 +131,7 @@ def circle_feedback_average(
|
||||||
_id=f"circle:{circle_id}-course_session:{course_session_id}@{urql_id_postfix}", # noqa
|
_id=f"circle:{circle_id}-course_session:{course_session_id}@{urql_id_postfix}", # noqa
|
||||||
course_session_id=course_session_id, # noqa
|
course_session_id=course_session_id, # noqa
|
||||||
generation=generation, # noqa
|
generation=generation, # noqa
|
||||||
|
region=region, # noqa
|
||||||
circle_id=circle_id, # noqa
|
circle_id=circle_id, # noqa
|
||||||
satisfaction_average=data["total"] / data["count"], # noqa
|
satisfaction_average=data["total"] / data["count"], # noqa
|
||||||
satisfaction_max=4, # noqa
|
satisfaction_max=4, # noqa
|
||||||
|
|
|
||||||
|
|
@ -110,22 +110,22 @@ class DashboardTestCase(GraphQLTestCase):
|
||||||
|
|
||||||
self.client.force_login(member)
|
self.client.force_login(member)
|
||||||
|
|
||||||
query = f"""query($course_id: ID!) {{
|
query = """query($course_id: ID!) {
|
||||||
course_progress(course_id: $course_id) {{
|
course_progress(course_id: $course_id) {
|
||||||
course_id
|
course_id
|
||||||
session_to_continue_id
|
session_to_continue_id
|
||||||
competence {{
|
competence {
|
||||||
total_count
|
total_count
|
||||||
success_count
|
success_count
|
||||||
fail_count
|
fail_count
|
||||||
}}
|
}
|
||||||
assignment {{
|
assignment {
|
||||||
total_count
|
total_count
|
||||||
points_max_count
|
points_max_count
|
||||||
points_achieved_count
|
points_achieved_count
|
||||||
}}
|
}
|
||||||
}}
|
}
|
||||||
}}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
variables = {"course_id": str(course.id)}
|
variables = {"course_id": str(course.id)}
|
||||||
|
|
@ -268,7 +268,7 @@ class DashboardTestCase(GraphQLTestCase):
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
)
|
)
|
||||||
|
|
||||||
add_learning_mentor(course_session=cs_1, mentor=mentor, mentee=csu)
|
add_learning_mentor(mentor=mentor, mentee=csu)
|
||||||
|
|
||||||
self.client.force_login(mentor)
|
self.client.force_login(mentor)
|
||||||
|
|
||||||
|
|
@ -287,6 +287,7 @@ class DashboardTestCase(GraphQLTestCase):
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertResponseNoErrors(response)
|
self.assertResponseNoErrors(response)
|
||||||
|
print(response.json())
|
||||||
|
|
||||||
self.assertEqual(len(response.json()["data"]["dashboard_config"]), 1)
|
self.assertEqual(len(response.json()["data"]["dashboard_config"]), 1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
@ -314,7 +315,7 @@ class DashboardTestCase(GraphQLTestCase):
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
)
|
)
|
||||||
|
|
||||||
add_learning_mentor(course_session=cs, mentor=mentor_and_member, mentee=mentee)
|
add_learning_mentor(mentor=mentor_and_member, mentee=mentee)
|
||||||
|
|
||||||
# WHEN
|
# WHEN
|
||||||
self.client.force_login(mentor_and_member)
|
self.client.force_login(mentor_and_member)
|
||||||
|
|
@ -350,11 +351,11 @@ class DashboardTestCase(GraphQLTestCase):
|
||||||
|
|
||||||
self.client.force_login(disallowed_user)
|
self.client.force_login(disallowed_user)
|
||||||
|
|
||||||
query = f"""query($course_id: ID!) {{
|
query = """query($course_id: ID!) {
|
||||||
course_statistics(course_id: $course_id) {{
|
course_statistics(course_id: $course_id) {
|
||||||
course_id
|
course_id
|
||||||
}}
|
}
|
||||||
}}
|
}
|
||||||
"""
|
"""
|
||||||
variables = {"course_id": str(course.id)}
|
variables = {"course_id": str(course.id)}
|
||||||
|
|
||||||
|
|
@ -384,13 +385,13 @@ class DashboardTestCase(GraphQLTestCase):
|
||||||
|
|
||||||
self.client.force_login(supervisor)
|
self.client.force_login(supervisor)
|
||||||
|
|
||||||
query = f"""query($course_id: ID!) {{
|
query = """query($course_id: ID!) {
|
||||||
course_statistics(course_id: $course_id) {{
|
course_statistics(course_id: $course_id) {
|
||||||
course_id
|
course_id
|
||||||
course_title
|
course_title
|
||||||
course_slug
|
course_slug
|
||||||
}}
|
}
|
||||||
}}
|
}
|
||||||
"""
|
"""
|
||||||
variables = {"course_id": str(course_2.id)}
|
variables = {"course_id": str(course_2.id)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from vbv_lernwelt.assignment.models import (
|
||||||
from vbv_lernwelt.course.creators.test_utils import add_course_session_user, create_user
|
from vbv_lernwelt.course.creators.test_utils import add_course_session_user, create_user
|
||||||
from vbv_lernwelt.course.models import CourseSessionUser
|
from vbv_lernwelt.course.models import CourseSessionUser
|
||||||
from vbv_lernwelt.dashboard.tests.test_views import BaseMentorAssignmentTestCase
|
from vbv_lernwelt.dashboard.tests.test_views import BaseMentorAssignmentTestCase
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation
|
||||||
|
|
||||||
|
|
||||||
class MentorStatisticsTestCase(BaseMentorAssignmentTestCase, GraphQLTestCase):
|
class MentorStatisticsTestCase(BaseMentorAssignmentTestCase, GraphQLTestCase):
|
||||||
|
|
@ -19,15 +19,13 @@ class MentorStatisticsTestCase(BaseMentorAssignmentTestCase, GraphQLTestCase):
|
||||||
self.course.configuration.save()
|
self.course.configuration.save()
|
||||||
|
|
||||||
self.mentor = create_user("mentor")
|
self.mentor = create_user("mentor")
|
||||||
self.lm = LearningMentor.objects.create(
|
|
||||||
mentor=self.mentor, course_session=self.course_session
|
|
||||||
)
|
|
||||||
self.participants = [create_user(f"participant{i}") for i in range(4)]
|
self.participants = [create_user(f"participant{i}") for i in range(4)]
|
||||||
|
|
||||||
def test_assignment_statistics(self):
|
def test_assignment_statistics(self):
|
||||||
# WHEN
|
# WHEN
|
||||||
has_lb = [True, True, True, False]
|
has_lb = [True, True, True, False]
|
||||||
has_passed = [True, False, True, False]
|
has_passed = [True, False, True, False]
|
||||||
|
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
csu = add_course_session_user(
|
csu = add_course_session_user(
|
||||||
self.course_session,
|
self.course_session,
|
||||||
|
|
@ -35,7 +33,9 @@ class MentorStatisticsTestCase(BaseMentorAssignmentTestCase, GraphQLTestCase):
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
)
|
)
|
||||||
if has_lb[i]:
|
if has_lb[i]:
|
||||||
self.lm.participants.add(csu)
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=self.mentor, participant=csu, role="LEARNING_MENTOR"
|
||||||
|
)
|
||||||
|
|
||||||
AssignmentCompletion.objects.create(
|
AssignmentCompletion.objects.create(
|
||||||
course_session=self.course_session,
|
course_session=self.course_session,
|
||||||
|
|
@ -47,25 +47,25 @@ class MentorStatisticsTestCase(BaseMentorAssignmentTestCase, GraphQLTestCase):
|
||||||
)
|
)
|
||||||
# THEN
|
# THEN
|
||||||
# WHEN
|
# WHEN
|
||||||
query = f"""query ($courseId: ID!) {{
|
query = """query ($courseId: ID!, $agentRole: String!) {
|
||||||
mentor_course_statistics(course_id: $courseId) {{
|
mentor_course_statistics(course_id: $courseId, agent_role: $agentRole) {
|
||||||
course_session_selection_ids
|
course_session_selection_ids
|
||||||
user_selection_ids
|
user_selection_ids
|
||||||
assignments {{
|
assignments {
|
||||||
_id
|
_id
|
||||||
summary {{
|
summary {
|
||||||
_id
|
_id
|
||||||
completed_count
|
completed_count
|
||||||
average_passed
|
average_passed
|
||||||
total_passed
|
total_passed
|
||||||
total_failed
|
total_failed
|
||||||
}}
|
}
|
||||||
}}
|
}
|
||||||
}}
|
}
|
||||||
}}"""
|
}"""
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
variables = {"courseId": str(self.course.id)}
|
variables = {"courseId": str(self.course.id), "agentRole": "LEARNING_MENTOR"}
|
||||||
self.client.force_login(self.mentor)
|
self.client.force_login(self.mentor)
|
||||||
response = self.query(query, variables=variables)
|
response = self.query(query, variables=variables)
|
||||||
self.assertResponseNoErrors(response)
|
self.assertResponseNoErrors(response)
|
||||||
|
|
|
||||||
|
|
@ -139,9 +139,7 @@ class PersonsExportTestCase(ExportBaseTestCase):
|
||||||
self.assertEqual(wb.sheetnames[0], "Test Zürich 2022 a")
|
self.assertEqual(wb.sheetnames[0], "Test Zürich 2022 a")
|
||||||
wb.active = wb["Test Zürich 2022 a"]
|
wb.active = wb["Test Zürich 2022 a"]
|
||||||
|
|
||||||
data = self._generate_expected_data([[None] * 6])
|
self._check_export(wb, [[None] * 6], 1, 6)
|
||||||
|
|
||||||
self._check_export(wb, data, 1, 6)
|
|
||||||
|
|
||||||
def test_export_in_fr(self):
|
def test_export_in_fr(self):
|
||||||
activate("fr")
|
activate("fr")
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ from vbv_lernwelt.dashboard.views import (
|
||||||
get_course_config,
|
get_course_config,
|
||||||
get_course_sessions_with_roles_for_user,
|
get_course_sessions_with_roles_for_user,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation
|
||||||
from vbv_lernwelt.learnpath.models import Circle, LearningUnit
|
from vbv_lernwelt.learnpath.models import Circle, LearningUnit
|
||||||
from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback
|
from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback
|
||||||
|
|
||||||
|
|
@ -98,14 +98,16 @@ class GetCourseSessionsForUserTestCase(TestCase):
|
||||||
|
|
||||||
def test_learning_mentor_get_sessions(self):
|
def test_learning_mentor_get_sessions(self):
|
||||||
mentor = create_user("mentor")
|
mentor = create_user("mentor")
|
||||||
LearningMentor.objects.create(mentor=mentor, course_session=self.course_session)
|
|
||||||
|
|
||||||
participant = create_user("participant")
|
participant = create_user("participant")
|
||||||
add_course_session_user(
|
csu = add_course_session_user(
|
||||||
self.course_session,
|
self.course_session,
|
||||||
participant,
|
participant,
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
)
|
)
|
||||||
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=mentor, participant=csu, role="LEARNING_MENTOR"
|
||||||
|
)
|
||||||
|
|
||||||
sessions = get_course_sessions_with_roles_for_user(mentor)
|
sessions = get_course_sessions_with_roles_for_user(mentor)
|
||||||
|
|
||||||
|
|
@ -122,7 +124,7 @@ class GetDashboardConfig(TestCase):
|
||||||
course=self.course, title="Test Session"
|
course=self.course, title="Test Session"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _test_config(self, user, role, is_uk, is_vv, is_mentor, has_preview, widgets):
|
def _test_config(self, user, role, is_uk, is_vv, has_preview, widgets):
|
||||||
# WHEN
|
# WHEN
|
||||||
sessions = get_course_sessions_with_roles_for_user(user)
|
sessions = get_course_sessions_with_roles_for_user(user)
|
||||||
course_configs = get_course_config(sessions)
|
course_configs = get_course_config(sessions)
|
||||||
|
|
@ -132,13 +134,12 @@ class GetDashboardConfig(TestCase):
|
||||||
self.assertEqual(course_configs[0].course_title, self.course.title)
|
self.assertEqual(course_configs[0].course_title, self.course.title)
|
||||||
self.assertEqual(course_configs[0].is_uk, is_uk)
|
self.assertEqual(course_configs[0].is_uk, is_uk)
|
||||||
self.assertEqual(course_configs[0].is_vv, is_vv)
|
self.assertEqual(course_configs[0].is_vv, is_vv)
|
||||||
self.assertEqual(course_configs[0].is_mentor, is_mentor)
|
|
||||||
self.assertEqual(course_configs[0].has_preview, has_preview)
|
self.assertEqual(course_configs[0].has_preview, has_preview)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
course_configs[0].session_to_continue_id, str(self.course_session.id)
|
course_configs[0].session_to_continue_id, str(self.course_session.id)
|
||||||
)
|
)
|
||||||
self.assertEqual(course_configs[0].role_key, role)
|
self.assertEqual(course_configs[0].role_key, role)
|
||||||
self.assertEqual(course_configs[0].widgets, widgets)
|
self.assertEqual(set(course_configs[0].widgets), set(widgets))
|
||||||
|
|
||||||
def test_participant_uk_get_config(self):
|
def test_participant_uk_get_config(self):
|
||||||
participant = create_user("participant")
|
participant = create_user("participant")
|
||||||
|
|
@ -155,7 +156,6 @@ class GetDashboardConfig(TestCase):
|
||||||
role="Member",
|
role="Member",
|
||||||
is_uk=True,
|
is_uk=True,
|
||||||
is_vv=False,
|
is_vv=False,
|
||||||
is_mentor=False,
|
|
||||||
has_preview=False,
|
has_preview=False,
|
||||||
widgets=[
|
widgets=[
|
||||||
"ProgressWidget",
|
"ProgressWidget",
|
||||||
|
|
@ -179,7 +179,6 @@ class GetDashboardConfig(TestCase):
|
||||||
role="Member",
|
role="Member",
|
||||||
is_uk=False,
|
is_uk=False,
|
||||||
is_vv=True,
|
is_vv=True,
|
||||||
is_mentor=False,
|
|
||||||
has_preview=False,
|
has_preview=False,
|
||||||
widgets=["ProgressWidget", "CompetenceWidget"],
|
widgets=["ProgressWidget", "CompetenceWidget"],
|
||||||
)
|
)
|
||||||
|
|
@ -187,7 +186,16 @@ class GetDashboardConfig(TestCase):
|
||||||
def test_mentor_uk_get_config(self):
|
def test_mentor_uk_get_config(self):
|
||||||
# GIVEN
|
# GIVEN
|
||||||
mentor = create_user("mentor")
|
mentor = create_user("mentor")
|
||||||
LearningMentor.objects.create(mentor=mentor, course_session=self.course_session)
|
|
||||||
|
participant = create_user("participant")
|
||||||
|
csu = add_course_session_user(
|
||||||
|
self.course_session,
|
||||||
|
participant,
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
)
|
||||||
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=mentor, participant=csu, role="LEARNING_MENTOR"
|
||||||
|
)
|
||||||
|
|
||||||
self.course.configuration.is_uk = True
|
self.course.configuration.is_uk = True
|
||||||
self.course.configuration.save()
|
self.course.configuration.save()
|
||||||
|
|
@ -197,7 +205,6 @@ class GetDashboardConfig(TestCase):
|
||||||
role="MentorUK",
|
role="MentorUK",
|
||||||
is_uk=True,
|
is_uk=True,
|
||||||
is_vv=False,
|
is_vv=False,
|
||||||
is_mentor=True,
|
|
||||||
has_preview=True,
|
has_preview=True,
|
||||||
widgets=["MentorPersonWidget", "MentorCompetenceWidget"],
|
widgets=["MentorPersonWidget", "MentorCompetenceWidget"],
|
||||||
)
|
)
|
||||||
|
|
@ -205,7 +212,15 @@ class GetDashboardConfig(TestCase):
|
||||||
def test_mentor_vv_get_config(self):
|
def test_mentor_vv_get_config(self):
|
||||||
# GIVEN
|
# GIVEN
|
||||||
mentor = create_user("mentor")
|
mentor = create_user("mentor")
|
||||||
LearningMentor.objects.create(mentor=mentor, course_session=self.course_session)
|
participant = create_user("participant")
|
||||||
|
csu = add_course_session_user(
|
||||||
|
self.course_session,
|
||||||
|
participant,
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
)
|
||||||
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=mentor, participant=csu, role="LEARNING_MENTOR"
|
||||||
|
)
|
||||||
|
|
||||||
self.course.configuration.is_vv = True
|
self.course.configuration.is_vv = True
|
||||||
self.course.configuration.save()
|
self.course.configuration.save()
|
||||||
|
|
@ -215,7 +230,6 @@ class GetDashboardConfig(TestCase):
|
||||||
role="MentorVV",
|
role="MentorVV",
|
||||||
is_uk=False,
|
is_uk=False,
|
||||||
is_vv=True,
|
is_vv=True,
|
||||||
is_mentor=True,
|
|
||||||
has_preview=True,
|
has_preview=True,
|
||||||
widgets=["MentorPersonWidget", "MentorTasksWidget"],
|
widgets=["MentorPersonWidget", "MentorTasksWidget"],
|
||||||
)
|
)
|
||||||
|
|
@ -228,7 +242,16 @@ class GetDashboardConfig(TestCase):
|
||||||
mentor,
|
mentor,
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
)
|
)
|
||||||
LearningMentor.objects.create(mentor=mentor, course_session=self.course_session)
|
|
||||||
|
participant = create_user("participant")
|
||||||
|
csu = add_course_session_user(
|
||||||
|
self.course_session,
|
||||||
|
participant,
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
)
|
||||||
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=mentor, participant=csu, role="LEARNING_MENTOR"
|
||||||
|
)
|
||||||
|
|
||||||
self.course.configuration.is_vv = True
|
self.course.configuration.is_vv = True
|
||||||
self.course.configuration.save()
|
self.course.configuration.save()
|
||||||
|
|
@ -238,7 +261,6 @@ class GetDashboardConfig(TestCase):
|
||||||
role="Member",
|
role="Member",
|
||||||
is_uk=False,
|
is_uk=False,
|
||||||
is_vv=True,
|
is_vv=True,
|
||||||
is_mentor=True,
|
|
||||||
has_preview=False,
|
has_preview=False,
|
||||||
widgets=[
|
widgets=[
|
||||||
"ProgressWidget",
|
"ProgressWidget",
|
||||||
|
|
@ -264,9 +286,6 @@ class GetMenteeCountTestCase(TestCase):
|
||||||
participants_with_mentor = [create_user(f"participant{i}") for i in range(2)]
|
participants_with_mentor = [create_user(f"participant{i}") for i in range(2)]
|
||||||
participant = create_user("participant")
|
participant = create_user("participant")
|
||||||
mentor = create_user("mentor")
|
mentor = create_user("mentor")
|
||||||
lm = LearningMentor.objects.create(
|
|
||||||
mentor=mentor, course_session=self.course_session
|
|
||||||
)
|
|
||||||
|
|
||||||
# WHEN
|
# WHEN
|
||||||
for p in participants_with_mentor:
|
for p in participants_with_mentor:
|
||||||
|
|
@ -275,7 +294,9 @@ class GetMenteeCountTestCase(TestCase):
|
||||||
p,
|
p,
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
)
|
)
|
||||||
lm.participants.add(csu)
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=mentor, participant=csu, role="LEARNING_MENTOR"
|
||||||
|
)
|
||||||
|
|
||||||
add_course_session_user(
|
add_course_session_user(
|
||||||
self.course_session,
|
self.course_session,
|
||||||
|
|
@ -305,9 +326,6 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase):
|
||||||
self.course.configuration.save()
|
self.course.configuration.save()
|
||||||
|
|
||||||
self.mentor = create_user("mentor")
|
self.mentor = create_user("mentor")
|
||||||
self.lm = LearningMentor.objects.create(
|
|
||||||
mentor=self.mentor, course_session=self.course_session
|
|
||||||
)
|
|
||||||
self.participants = [create_user(f"participant{i}") for i in range(2)]
|
self.participants = [create_user(f"participant{i}") for i in range(2)]
|
||||||
|
|
||||||
def create_and_test_count(
|
def create_and_test_count(
|
||||||
|
|
@ -337,7 +355,10 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase):
|
||||||
self.participants[0],
|
self.participants[0],
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
)
|
)
|
||||||
self.lm.participants.add(csu)
|
|
||||||
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=self.mentor, participant=csu, role="LEARNING_MENTOR"
|
||||||
|
)
|
||||||
|
|
||||||
add_course_session_user(
|
add_course_session_user(
|
||||||
self.course_session,
|
self.course_session,
|
||||||
|
|
@ -367,7 +388,9 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase):
|
||||||
self.participants[0],
|
self.participants[0],
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
)
|
)
|
||||||
self.lm.participants.add(csu)
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=self.mentor, participant=csu, role="LEARNING_MENTOR"
|
||||||
|
)
|
||||||
|
|
||||||
add_course_session_user(
|
add_course_session_user(
|
||||||
self.course_session,
|
self.course_session,
|
||||||
|
|
@ -389,8 +412,9 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase):
|
||||||
self.participants[0],
|
self.participants[0],
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
)
|
)
|
||||||
self.lm.participants.add(csu)
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=self.mentor, participant=csu, role="LEARNING_MENTOR"
|
||||||
|
)
|
||||||
SelfEvaluationFeedback.objects.create(
|
SelfEvaluationFeedback.objects.create(
|
||||||
feedback_submitted=False,
|
feedback_submitted=False,
|
||||||
feedback_requester_user=self.participants[0],
|
feedback_requester_user=self.participants[0],
|
||||||
|
|
@ -411,8 +435,9 @@ class GetMentorOpenTasksTestCase(BaseMentorAssignmentTestCase):
|
||||||
self.participants[0],
|
self.participants[0],
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
)
|
)
|
||||||
self.lm.participants.add(csu)
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=self.mentor, participant=csu, role="LEARNING_MENTOR"
|
||||||
|
)
|
||||||
SelfEvaluationFeedback.objects.create(
|
SelfEvaluationFeedback.objects.create(
|
||||||
feedback_submitted=True,
|
feedback_submitted=True,
|
||||||
feedback_requester_user=self.participants[0],
|
feedback_requester_user=self.participants[0],
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue