Merged in feat/vv-kompotenz-navi (pull request #293)
VV KompetenzNavi für Teilnehmer Approved-by: Daniel Egger Approved-by: Christian Cueni
This commit is contained in:
commit
2ff51c5136
|
|
@ -97,12 +97,12 @@ js-linting: &js-linting
|
|||
|
||||
default-steps: &default-steps
|
||||
- parallel:
|
||||
- step: *e2e
|
||||
- step: *e2e
|
||||
- step: *python-tests
|
||||
- step: *python-linting
|
||||
- step: *js-tests
|
||||
- step: *js-linting
|
||||
- step: *e2e
|
||||
- step: *e2e
|
||||
- step: *python-tests
|
||||
- step: *python-linting
|
||||
- step: *js-tests
|
||||
- step: *js-linting
|
||||
|
||||
# main pipelines definitions
|
||||
pipelines:
|
||||
|
|
@ -132,16 +132,16 @@ pipelines:
|
|||
script:
|
||||
- echo "Release ready!"
|
||||
- parallel:
|
||||
- step:
|
||||
<<: *deploy
|
||||
name: deploy prod
|
||||
deployment: prod
|
||||
trigger: manual
|
||||
- step:
|
||||
<<: *deploy
|
||||
name: deploy prod-azure
|
||||
deployment: prod-azure
|
||||
trigger: manual
|
||||
- step:
|
||||
<<: *deploy
|
||||
name: deploy prod
|
||||
deployment: prod
|
||||
trigger: manual
|
||||
- step:
|
||||
<<: *deploy
|
||||
name: deploy prod-azure
|
||||
deployment: prod-azure
|
||||
trigger: manual
|
||||
custom:
|
||||
deploy-feature-branch:
|
||||
- step:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import type { LearningUnitSummary } from "@/services/selfEvaluationFeedback";
|
||||
import SmileyCell from "@/components/selfEvaluationFeedback/SmileyCell.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
summary: LearningUnitSummary;
|
||||
}>();
|
||||
|
||||
const hasFeedbackReceived = computed(() => {
|
||||
return props.summary.feedback_assessment?.submitted_by_provider ?? false;
|
||||
});
|
||||
|
||||
const feedbackProviderAvatar = computed(() => {
|
||||
return props.summary.feedback_assessment?.provider_user.avatar_url ?? "";
|
||||
});
|
||||
|
||||
const feedbackProviderName = computed(() => {
|
||||
if (!props.summary.feedback_assessment?.provider_user) {
|
||||
return "";
|
||||
} else {
|
||||
return `${props.summary.feedback_assessment.provider_user.first_name} ${props.summary.feedback_assessment.provider_user.last_name}`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white" data-cy>
|
||||
<!-- Top Row -->
|
||||
<div class="flex items-center justify-between border-b-2 border-gray-200 p-4">
|
||||
<div class="flex flex-col">
|
||||
<b>{{ props.summary.title }}</b>
|
||||
<span>Circle «{{ props.summary.circle_title }}»</span>
|
||||
</div>
|
||||
<span class="underline">
|
||||
<router-link
|
||||
:to="props.summary.detail_url"
|
||||
:data-cy="`self-eval-${summary.id}-detail-url`"
|
||||
>
|
||||
{{ $t("a.Selbsteinschätzung anschauen") }}
|
||||
</router-link>
|
||||
</span>
|
||||
</div>
|
||||
<div class="ml-4 mr-4">
|
||||
<!-- Self Assessment Row-->
|
||||
<div class="flex pb-2 pt-2">
|
||||
<div class="w-1/2">
|
||||
{{ $t("a.Deine Selbsteinschätzung") }}
|
||||
</div>
|
||||
<div class="cell">
|
||||
<SmileyCell
|
||||
:count="props.summary.self_assessment.counts.pass"
|
||||
:cypress-identifier="`self-eval-${props.summary.id}-pass`"
|
||||
smiley="it-icon-smiley-happy"
|
||||
/>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<SmileyCell
|
||||
:count="props.summary.self_assessment.counts.fail"
|
||||
:cypress-identifier="`self-eval-${props.summary.id}-fail`"
|
||||
smiley="it-icon-smiley-thinking"
|
||||
/>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<SmileyCell
|
||||
:count="props.summary.self_assessment.counts.unknown"
|
||||
:cypress-identifier="`self-eval-${props.summary.id}-unknown`"
|
||||
smiley="it-icon-smiley-neutral"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Feedback Assessment Row -->
|
||||
<div v-if="hasFeedbackReceived" class="border-t-2 border-gray-200">
|
||||
<div class="flex pb-2 pt-2">
|
||||
<div class="flex w-1/2 items-center">
|
||||
<span>
|
||||
{{
|
||||
$t("a.Fremdeinschätzung von FEEDBACK_PROVIDER_NAME", {
|
||||
FEEDBACK_PROVIDER_NAME: feedbackProviderName,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<img class="ml-2 h-7 w-7 rounded-full" :src="feedbackProviderAvatar" />
|
||||
</div>
|
||||
<div class="cell">
|
||||
<SmileyCell
|
||||
:count="props.summary.feedback_assessment?.counts.pass ?? 0"
|
||||
smiley="it-icon-smiley-happy"
|
||||
/>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<SmileyCell
|
||||
:count="props.summary.feedback_assessment?.counts.fail ?? 0"
|
||||
smiley="it-icon-smiley-thinking"
|
||||
/>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<SmileyCell
|
||||
:count="props.summary.feedback_assessment?.counts.unknown ?? 0"
|
||||
smiley="it-icon-smiley-neutral"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.cell {
|
||||
@apply w-12;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<script setup lang="ts">
|
||||
import log from "loglevel";
|
||||
|
||||
const props = defineProps<{
|
||||
count: number;
|
||||
smiley: string;
|
||||
cypressIdentifier?: string;
|
||||
}>();
|
||||
|
||||
log.info("Rendering SmileyCell:", props.cypressIdentifier);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="count > 0">
|
||||
<div class="flex items-center justify-center">
|
||||
<component :is="smiley" class="mr-1 inline-block h-6 w-6"></component>
|
||||
<p class="inline-block w-6" :data-cy="cypressIdentifier">
|
||||
{{ count }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -37,7 +37,7 @@ const dropdownSelected = computed<DropdownSelectable>({
|
|||
|
||||
<template>
|
||||
<Listbox v-model="dropdownSelected" as="div">
|
||||
<div class="relative mt-1 w-full">
|
||||
<div class="relative w-full">
|
||||
<ListboxButton
|
||||
class="relative flex w-full cursor-default flex-row items-center bg-white py-3 pl-5 pr-10 text-left"
|
||||
:class="{
|
||||
|
|
|
|||
|
|
@ -4,14 +4,15 @@ import { COMPETENCE_NAVI_CERTIFICATE_QUERY } from "@/graphql/queries";
|
|||
import { useQuery } from "@urql/vue";
|
||||
import { computed } from "vue";
|
||||
import type { CompetenceCertificate } from "@/types";
|
||||
import { useCurrentCourseSession, useCourseDataWithCompletion } from "@/composables";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import {
|
||||
assignmentsMaxEvaluationPoints,
|
||||
assignmentsUserPoints,
|
||||
competenceCertificateProgressStatusCount,
|
||||
} from "@/pages/competence/utils";
|
||||
import { useSelfEvaluationFeedbackSummaries } from "@/services/selfEvaluationFeedback";
|
||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||
import { calcPerformanceCriteriaStatusCount } from "@/services/competence";
|
||||
import { VV_COURSE_IDS } from "@/constants";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
|
|
@ -20,7 +21,6 @@ const props = defineProps<{
|
|||
log.debug("CompetenceIndexPage setup", props);
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const courseData = useCourseDataWithCompletion(props.courseSlug);
|
||||
|
||||
const certificatesQuery = useQuery({
|
||||
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
||||
|
|
@ -49,16 +49,41 @@ const userPointsEvaluatedAssignments = computed(() => {
|
|||
return assignmentsUserPoints(allAssignments.value);
|
||||
});
|
||||
|
||||
const performanceCriteriaStatusCount = computed(() => {
|
||||
return calcPerformanceCriteriaStatusCount(courseData.flatPerformanceCriteria.value);
|
||||
const selfEvaluationFeedbackSummaries = useSelfEvaluationFeedbackSummaries(
|
||||
useCurrentCourseSession().value.id
|
||||
);
|
||||
|
||||
const selfAssessmentCounts = computed(
|
||||
() => selfEvaluationFeedbackSummaries.aggregates.value?.self_assessment
|
||||
);
|
||||
|
||||
const feedbackEvaluationCounts = computed(
|
||||
() => selfEvaluationFeedbackSummaries.aggregates.value?.feedback_assessment
|
||||
);
|
||||
|
||||
const isFeedbackEvaluationVisible = computed(
|
||||
() =>
|
||||
selfEvaluationFeedbackSummaries.aggregates.value?.feedback_assessment_visible ??
|
||||
false
|
||||
);
|
||||
|
||||
// FIXME 22.02.24: To-be-tackled NEXT in a separate PR (shippable member comp.navi)
|
||||
// -> Do not use the VV_COURSE_ID anymore (discuss with @chrigu) -> We do this next.
|
||||
const currentCourseSession = useCurrentCourseSession();
|
||||
const hasCompetenceCertificates = computed(() => {
|
||||
return !VV_COURSE_IDS.includes(currentCourseSession.value.course.id);
|
||||
});
|
||||
|
||||
const isLoaded = computed(
|
||||
() =>
|
||||
!selfEvaluationFeedbackSummaries.loading.value && !certificatesQuery.fetching.value
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-large lg:mt-4">
|
||||
<h1 class="mb-8">{{ $t("a.KompetenzNavi") }}</h1>
|
||||
|
||||
<section class="mb-4 bg-white p-8">
|
||||
<div v-if="isLoaded" class="container-large lg:mt-4">
|
||||
<!-- Competence certificates -->
|
||||
<section v-if="hasCompetenceCertificates" class="mb-4 bg-white p-8">
|
||||
<div class="flex items-center">
|
||||
<h3>{{ $t("a.Kompetenznachweise") }}</h3>
|
||||
</div>
|
||||
|
|
@ -80,7 +105,7 @@ const performanceCriteriaStatusCount = computed(() => {
|
|||
<div
|
||||
v-for="certificate in competenceCertificates"
|
||||
:key="certificate.id"
|
||||
class="flex flex-col justify-between border-b py-4 first:border-t lg:flex-row lg:items-center"
|
||||
class="flex flex-col justify-between py-4 lg:flex-row lg:items-center"
|
||||
:data-cy="`certificate-${certificate.slug}`"
|
||||
>
|
||||
<div class="text-bold text-xl">
|
||||
|
|
@ -130,59 +155,96 @@ const performanceCriteriaStatusCount = computed(() => {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Self-evaluation -->
|
||||
<section class="mb-4 bg-white px-8 py-4 lg:mb-8 lg:py-8">
|
||||
<h3 class="mb-4 border-b pb-4 lg:border-0 lg:pb-0">
|
||||
{{ $t("a.Selbsteinschätzungen") }}
|
||||
</h3>
|
||||
<ul
|
||||
class="mb-6 flex flex-col lg:flex-row lg:items-center lg:justify-between lg:gap-8"
|
||||
>
|
||||
<li
|
||||
class="mb-4 inline-block flex-1 border-b pb-4 lg:mb-0 lg:w-1/3 lg:border-b-0 lg:border-r lg:pb-0"
|
||||
<div class="mb-8">
|
||||
<h3 class="mb-4 pb-4 lg:pb-0">
|
||||
{{ $t("a.Selbsteinschätzungen") }}
|
||||
</h3>
|
||||
<ul
|
||||
class="mb-6 flex flex-col lg:flex-row lg:items-center lg:justify-between lg:gap-8"
|
||||
>
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.no") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-thinking class="h-16 w-16"></it-icon-smiley-thinking>
|
||||
<p
|
||||
class="ml-4 inline-block text-7xl font-bold"
|
||||
data-cy="self-evaluation-fail"
|
||||
>
|
||||
{{ performanceCriteriaStatusCount.FAIL }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="mb-4 inline-block flex-1 border-b pb-4 lg:mb-0 lg:w-1/3 lg:border-b-0 lg:border-r lg:pb-0"
|
||||
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.no") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-thinking class="h-16 w-16"></it-icon-smiley-thinking>
|
||||
<p
|
||||
class="ml-4 inline-block text-7xl font-bold"
|
||||
data-cy="self-evaluation-fail"
|
||||
>
|
||||
{{ selfAssessmentCounts?.fail }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.yes") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-happy class="h-16 w-16"></it-icon-smiley-happy>
|
||||
<p
|
||||
class="ml-4 inline-block text-7xl font-bold"
|
||||
data-cy="self-evaluation-success"
|
||||
>
|
||||
{{ selfAssessmentCounts?.pass }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">{{ $t("competences.notAssessed") }}</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-neutral class="h-16 w-16"></it-icon-smiley-neutral>
|
||||
<p
|
||||
class="ml-4 inline-block text-7xl font-bold"
|
||||
data-cy="self-evaluation-unknown"
|
||||
>
|
||||
{{ selfAssessmentCounts?.unknown }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Feedback evaluation -->
|
||||
<div v-if="isFeedbackEvaluationVisible" class="mb-8 border-t pt-8">
|
||||
<h3 class="mb-4 pb-4 lg:pb-0">
|
||||
{{ $t("a.Fremdeinschätzungen") }}
|
||||
</h3>
|
||||
<ul
|
||||
class="mb-6 flex flex-col lg:flex-row lg:items-center lg:justify-between lg:gap-8"
|
||||
>
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("selfEvaluation.yes") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-happy class="h-16 w-16"></it-icon-smiley-happy>
|
||||
<p
|
||||
class="ml-4 inline-block text-7xl font-bold"
|
||||
data-cy="self-evaluation-success"
|
||||
>
|
||||
{{ performanceCriteriaStatusCount.SUCCESS }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex-1 border-b pb-4 lg:mb-0 lg:w-1/3 lg:border-b-0 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">{{ $t("competences.notAssessed") }}</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-neutral class="h-16 w-16"></it-icon-smiley-neutral>
|
||||
<p
|
||||
class="ml-4 inline-block text-7xl font-bold"
|
||||
data-cy="self-evaluation-unknown"
|
||||
>
|
||||
{{ performanceCriteriaStatusCount.UNKNOWN }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("receivedEvaluation.no") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-thinking class="h-16 w-16"></it-icon-smiley-thinking>
|
||||
<p class="ml-4 inline-block text-7xl font-bold">
|
||||
{{ feedbackEvaluationCounts?.fail }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="mb-4 inline-block flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">«{{ $t("receivedEvaluation.yes") }}»</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-happy class="h-16 w-16"></it-icon-smiley-happy>
|
||||
<p class="ml-4 inline-block text-7xl font-bold">
|
||||
{{ feedbackEvaluationCounts?.pass }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex-1 pb-4 lg:mb-0 lg:w-1/3 lg:pb-0">
|
||||
<h5 class="mb-4 text-gray-700">{{ $t("competences.notAssessed") }}</h5>
|
||||
<div class="flex flex-row items-center">
|
||||
<it-icon-smiley-neutral class="h-16 w-16"></it-icon-smiley-neutral>
|
||||
<p class="ml-4 inline-block text-7xl font-bold">
|
||||
{{ feedbackEvaluationCounts?.unknown }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<router-link
|
||||
:to="`/course/${props.courseSlug}/competence/criteria`"
|
||||
class="btn-text mt-4 inline-flex items-center py-2 pl-0"
|
||||
:to="`/course/${props.courseSlug}/competence/self-evaluation-and-feedback`"
|
||||
class="btn-text inline-flex items-center py-2 pl-0"
|
||||
>
|
||||
<span>{{ $t("general.showAll") }}</span>
|
||||
<it-icon-arrow-right></it-icon-arrow-right>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import * as log from "loglevel";
|
||||
import { onMounted } from "vue";
|
||||
import { computed, onMounted } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { VV_COURSE_IDS } from "@/constants";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
|
||||
log.debug("CompetenceParentPage created");
|
||||
|
||||
|
|
@ -19,14 +21,21 @@ function routeInCompetenceCertificate() {
|
|||
return route.path.includes("/certificate");
|
||||
}
|
||||
|
||||
function routeInPerformanceCriteria() {
|
||||
return route.path.endsWith("/criteria");
|
||||
}
|
||||
|
||||
function routeInActionCompetences() {
|
||||
return route.path.endsWith("/competences");
|
||||
}
|
||||
|
||||
function routeInSelfEvaluationAndFeedback() {
|
||||
return route.path.endsWith("/self-evaluation-and-feedback");
|
||||
}
|
||||
|
||||
// FIXME 22.02.24: To-be-tackled NEXT in a separate PR (shippable member comp.navi)
|
||||
// -> Do not use the VV_COURSE_ID anymore (discuss with @chrigu) -> We do this next.
|
||||
const currentCourseSession = useCurrentCourseSession();
|
||||
const isVVCourse = computed(() => {
|
||||
return VV_COURSE_IDS.includes(currentCourseSession.value.course.id);
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
log.debug("CompetenceParentPage mounted", props.courseSlug);
|
||||
});
|
||||
|
|
@ -45,6 +54,7 @@ onMounted(async () => {
|
|||
</router-link>
|
||||
</li>
|
||||
<li
|
||||
v-if="!isVVCourse"
|
||||
class="border-t-2 border-t-transparent lg:ml-12"
|
||||
:class="{ 'border-b-2 border-b-blue-900': routeInCompetenceCertificate() }"
|
||||
>
|
||||
|
|
@ -57,16 +67,21 @@ onMounted(async () => {
|
|||
</li>
|
||||
<li
|
||||
class="border-t-2 border-t-transparent lg:ml-12"
|
||||
:class="{ 'border-b-2 border-b-blue-900': routeInPerformanceCriteria() }"
|
||||
:class="{
|
||||
'border-b-2 border-b-blue-900': routeInSelfEvaluationAndFeedback(),
|
||||
}"
|
||||
>
|
||||
<router-link
|
||||
:to="`/course/${courseSlug}/competence/criteria`"
|
||||
:to="`/course/${courseSlug}/competence/self-evaluation-and-feedback`"
|
||||
class="block py-3"
|
||||
>
|
||||
{{ $t("a.Selbsteinschätzungen") }}
|
||||
{{
|
||||
isVVCourse
|
||||
? $t("a.Selbst- und Fremdeinschätzungen")
|
||||
: $t("a.Selbsteinschätzungen")
|
||||
}}
|
||||
</router-link>
|
||||
</li>
|
||||
|
||||
<li
|
||||
class="border-t-2 border-t-transparent lg:ml-12"
|
||||
:class="{ 'border-b-2 border-b-blue-900': routeInActionCompetences() }"
|
||||
|
|
|
|||
|
|
@ -1,115 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import * as log from "loglevel";
|
||||
import { computed } from "vue";
|
||||
import _ from "lodash";
|
||||
import { useCourseDataWithCompletion } from "@/composables";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
log.debug("PerformanceCriteriaPage created", props);
|
||||
|
||||
const courseCompletionData = useCourseDataWithCompletion(props.courseSlug);
|
||||
|
||||
const uniqueLearningUnits = computed(() => {
|
||||
// FIXME: this complex calculation can go away,
|
||||
// once the criteria are in its own learning content
|
||||
// get the learningUnits sorted by circle order in the course
|
||||
const circles = (courseCompletionData.circles.value ?? []).map((c, index) => {
|
||||
return { ...c, sortKey: index };
|
||||
});
|
||||
return _.orderBy(
|
||||
_.uniqBy(
|
||||
(courseCompletionData.flatPerformanceCriteria.value ?? [])
|
||||
.filter((pc) => Boolean(pc.learning_unit))
|
||||
.map((pc) => {
|
||||
return {
|
||||
luId: pc.learning_unit?.id,
|
||||
luTitle: pc.learning_unit?.title,
|
||||
luSlug: pc.learning_unit?.slug,
|
||||
circleId: pc.circle.id,
|
||||
circleTitle: pc.circle.title,
|
||||
url: pc.learning_unit?.evaluate_url,
|
||||
sortKey: circles.find((c) => c.id === pc.circle.id)?.sortKey,
|
||||
};
|
||||
}),
|
||||
"luId"
|
||||
),
|
||||
"sortKey"
|
||||
);
|
||||
});
|
||||
|
||||
const criteriaByLearningUnit = computed(() => {
|
||||
return uniqueLearningUnits.value.map((lu) => {
|
||||
const criteria = (courseCompletionData.flatPerformanceCriteria.value ?? []).filter(
|
||||
(pc) => pc.learning_unit?.id === lu.luId
|
||||
);
|
||||
return {
|
||||
...lu,
|
||||
countSuccess: criteria.filter((c) => c.completion_status === "SUCCESS").length,
|
||||
countFail: criteria.filter((c) => c.completion_status === "FAIL").length,
|
||||
countUnknown: criteria.filter((c) => c.completion_status === "UNKNOWN").length,
|
||||
criteria: criteria,
|
||||
};
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-large">
|
||||
<h2 class="mb-4 lg:py-4">{{ $t("a.Selbsteinschätzungen") }}</h2>
|
||||
<section class="mb-4 bg-white px-4 py-2">
|
||||
<div
|
||||
v-for="selfEvaluation in criteriaByLearningUnit"
|
||||
:key="selfEvaluation.luId"
|
||||
class="flex flex-col justify-between gap-4 border-b py-4 last:border-b-0 lg:flex-row lg:items-center"
|
||||
>
|
||||
<div class="lg:w-1/3">
|
||||
{{ $t("a.Circle") }}
|
||||
{{ selfEvaluation.circleTitle }}:
|
||||
{{ selfEvaluation.luTitle }}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center lg:w-1/3">
|
||||
<div class="mr-6 flex flex-row items-center">
|
||||
<it-icon-smiley-thinking
|
||||
class="mr-2 inline-block h-8 w-8"
|
||||
></it-icon-smiley-thinking>
|
||||
<div class="w-6" :data-cy="`${selfEvaluation.luSlug}-fail`">
|
||||
{{ selfEvaluation.countFail }}
|
||||
</div>
|
||||
</div>
|
||||
<li class="mr-6 flex flex-row items-center">
|
||||
<it-icon-smiley-happy
|
||||
class="mr-2 inline-block h-8 w-8"
|
||||
></it-icon-smiley-happy>
|
||||
<div class="w-6" :data-cy="`${selfEvaluation.luSlug}-success`">
|
||||
{{ selfEvaluation.countSuccess }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex flex-row items-center">
|
||||
<it-icon-smiley-neutral
|
||||
class="mr-2 inline-block h-8 w-8"
|
||||
></it-icon-smiley-neutral>
|
||||
<div class="w-6" :data-cy="`${selfEvaluation.luSlug}-unknown`">
|
||||
{{ selfEvaluation.countUnknown }}
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<router-link
|
||||
:to="selfEvaluation.url ?? '/'"
|
||||
class="link"
|
||||
:data-cy="`${selfEvaluation.luSlug}-open`"
|
||||
>
|
||||
{{ $t("a.Selbsteinschätzung anschauen") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<script setup lang="ts">
|
||||
import { useSelfEvaluationFeedbackSummaries } from "@/services/selfEvaluationFeedback";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { computed, ref } from "vue";
|
||||
import FeedbackByLearningUnitSummary from "@/components/selfEvaluationFeedback/FeedbackByLearningUnitSummary.vue";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import { t } from "i18next";
|
||||
|
||||
const selfEvaluationFeedbackSummaries = useSelfEvaluationFeedbackSummaries(
|
||||
useCurrentCourseSession().value.id
|
||||
);
|
||||
|
||||
const isLoaded = computed(() => !selfEvaluationFeedbackSummaries.loading.value);
|
||||
|
||||
const selectedCircle = ref({ name: t("a.AlleCircle"), id: "_all" });
|
||||
|
||||
const circles = computed(() => [
|
||||
{ name: t("a.AlleCircle"), id: "_all" },
|
||||
...selfEvaluationFeedbackSummaries.circles.value.map((circle) => ({
|
||||
name: `Circle: ${circle.title}`,
|
||||
id: circle.id,
|
||||
})),
|
||||
]);
|
||||
|
||||
const summaries = computed(() => {
|
||||
if (selectedCircle.value.id === "_all") {
|
||||
return selfEvaluationFeedbackSummaries.summaries.value;
|
||||
}
|
||||
return selfEvaluationFeedbackSummaries.summaries.value.filter(
|
||||
(summary) => summary.circle_id === selectedCircle.value.id
|
||||
);
|
||||
});
|
||||
|
||||
const headerTitle = computed(() => {
|
||||
const canHaveFeedback =
|
||||
selfEvaluationFeedbackSummaries.aggregates.value?.feedback_assessment_visible ??
|
||||
false;
|
||||
if (canHaveFeedback) {
|
||||
return t("a.Selbst- und Fremdeinschätzungen");
|
||||
} else {
|
||||
return t("a.Selbsteinschätzungen");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="isLoaded">
|
||||
<div class="container-large">
|
||||
<div class="col flex items-center justify-between pb-4">
|
||||
<h2 class="py-4">{{ headerTitle }}</h2>
|
||||
<ItDropdownSelect
|
||||
v-model="selectedCircle"
|
||||
class="text-bold w-24 min-w-[18rem] border-2 border-gray-300"
|
||||
:items="circles"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<FeedbackByLearningUnitSummary
|
||||
v-for="summary in summaries"
|
||||
:key="summary.id"
|
||||
:summary="summary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -45,7 +45,7 @@ const competenceCertificateUrl = computed(() => {
|
|||
});
|
||||
|
||||
const competenceCriteriaUrl = computed(() => {
|
||||
return `/course/${courseSlug.value}/competence/criteria?courseSessionId=${courseSessionProgress.value?.session_to_continue_id}`;
|
||||
return `/course/${courseSlug.value}/competence/self-evaluation-and-feedback?courseSessionId=${courseSessionProgress.value?.session_to_continue_id}`;
|
||||
});
|
||||
|
||||
const isVVCourse = computed(() => {
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ const router = createRouter({
|
|||
props: true,
|
||||
component: () => import("@/pages/competence/CompetenceIndexPage.vue"),
|
||||
},
|
||||
|
||||
{
|
||||
path: "certificates",
|
||||
props: true,
|
||||
|
|
@ -106,9 +105,10 @@ const router = createRouter({
|
|||
import("@/pages/competence/CompetenceCertificateDetailPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "criteria",
|
||||
path: "self-evaluation-and-feedback",
|
||||
props: true,
|
||||
component: () => import("@/pages/competence/PerformanceCriteriaPage.vue"),
|
||||
component: () =>
|
||||
import("@/pages/competence/SelfEvaluationAndFeedbackPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "competences",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useCSRFFetch } from "@/fetchHelpers";
|
|||
import type { User } from "@/types";
|
||||
import { toValue } from "@vueuse/core";
|
||||
import { t } from "i18next";
|
||||
import log from "loglevel";
|
||||
import type { Ref } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
|
||||
|
|
@ -24,6 +25,45 @@ export interface Criterion {
|
|||
feedback_assessment: "FAIL" | "SUCCESS" | "UNKNOWN";
|
||||
}
|
||||
|
||||
interface FeedbackSummaryCounts {
|
||||
pass: number;
|
||||
fail: number;
|
||||
unknown: number;
|
||||
}
|
||||
|
||||
export interface FeedbackSummaryAggregates {
|
||||
// totals across all learning units in the course session
|
||||
self_assessment: FeedbackSummaryCounts;
|
||||
feedback_assessment: FeedbackSummaryCounts;
|
||||
// does this course have any feedback?
|
||||
feedback_assessment_visible: boolean;
|
||||
}
|
||||
|
||||
interface FeedbackAssessmentSummary {
|
||||
counts: FeedbackSummaryCounts;
|
||||
submitted_by_provider: boolean;
|
||||
provider_user: User;
|
||||
}
|
||||
|
||||
interface SelfAssessmentSummary {
|
||||
counts: FeedbackSummaryCounts;
|
||||
}
|
||||
|
||||
export interface LearningUnitSummary {
|
||||
id: string;
|
||||
title: string;
|
||||
circle_id: string;
|
||||
circle_title: string;
|
||||
feedback_assessment?: FeedbackAssessmentSummary;
|
||||
self_assessment: SelfAssessmentSummary;
|
||||
detail_url: string;
|
||||
}
|
||||
|
||||
interface Circle {
|
||||
id: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/** To keep the backend permissions model simple, we have two endpoints:
|
||||
* 1. /requester/: for the user who requested the feedback
|
||||
* 2. /provider/: for the user who provides the feedback
|
||||
|
|
@ -47,7 +87,7 @@ export function useSelfEvaluationFeedback(
|
|||
error.value = undefined;
|
||||
loading.value = true;
|
||||
|
||||
console.log("Fetching feedback for learning unit", learningUnitId);
|
||||
log.info("Fetching feedback for learning unit", learningUnitId);
|
||||
const { data, statusCode, error: _error } = await useCSRFFetch(url.value).json();
|
||||
loading.value = false;
|
||||
|
||||
|
|
@ -126,6 +166,52 @@ export function useSelfEvaluationFeedback(
|
|||
};
|
||||
}
|
||||
|
||||
export function useSelfEvaluationFeedbackSummaries(
|
||||
courseSessionId: Ref<string> | string
|
||||
) {
|
||||
const summaries = ref<LearningUnitSummary[]>([]);
|
||||
const aggregates = ref<FeedbackSummaryAggregates>();
|
||||
const circles = ref<Circle[]>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref();
|
||||
|
||||
const url = computed(
|
||||
() =>
|
||||
`/api/self-evaluation-feedback/requester/${courseSessionId}/feedbacks/summaries`
|
||||
);
|
||||
|
||||
const fetchFeedbackSummaries = async () => {
|
||||
error.value = undefined;
|
||||
loading.value = true;
|
||||
|
||||
log.info("Fetching feedback summaries for course session", courseSessionId);
|
||||
const { data, error: _error } = await useCSRFFetch(url.value).json();
|
||||
loading.value = false;
|
||||
|
||||
if (_error.value) {
|
||||
error.value = _error;
|
||||
summaries.value = [];
|
||||
circles.value = [];
|
||||
aggregates.value = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
summaries.value = data.value.results;
|
||||
aggregates.value = data.value.aggregates;
|
||||
circles.value = data.value.circles;
|
||||
};
|
||||
|
||||
onMounted(fetchFeedbackSummaries);
|
||||
|
||||
return {
|
||||
summaries,
|
||||
aggregates,
|
||||
circles,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export const getSmiley = (assessment: "FAIL" | "SUCCESS" | "UNKNOWN") => {
|
||||
switch (assessment) {
|
||||
case "SUCCESS":
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { login } from "../helpers";
|
||||
import {login} from "../helpers";
|
||||
|
||||
describe("selfEvaluation.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -28,37 +28,28 @@ describe("selfEvaluation.cy.js", () => {
|
|||
cy.get('[data-cy="self-evaluation-success"]').should("have.text", "0");
|
||||
cy.get('[data-cy="self-evaluation-unknown"]').should("have.text", "4");
|
||||
|
||||
|
||||
// learning unit id = 687 also known as:
|
||||
// Bedarfsanalyse, Ist- und Soll-Situation <<Reisen>>
|
||||
const identifier = "self-eval-687"
|
||||
|
||||
// data in KompetenzNavi/Selbsteinschätzungen is correct
|
||||
cy.visit("/course/test-lehrgang/competence/criteria");
|
||||
cy.get('[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-fail"]').should(
|
||||
"have.text",
|
||||
"0"
|
||||
);
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-success"]'
|
||||
).should("have.text", "0");
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-unknown"]'
|
||||
).should("have.text", "2");
|
||||
cy.visit("/course/test-lehrgang/competence/self-evaluation-and-feedback");
|
||||
cy.get(`[data-cy="${identifier}-fail"]`).should("not.exist");
|
||||
cy.get(`[data-cy="${identifier}-pass"]`).should("not.exist");
|
||||
cy.get(`[data-cy="${identifier}-unknown"]`).should("have.text", "2");
|
||||
|
||||
// it can open self evaluation from within KompetenzNavi
|
||||
cy.get('[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-open"]').click();
|
||||
cy.get(`[data-cy="${identifier}-detail-url"]`).click();
|
||||
|
||||
// starting the self evaluation will return to KompetenzNavi
|
||||
cy.makeSelfEvaluation([true, false]);
|
||||
cy.url().should("include", "/course/test-lehrgang/competence/criteria");
|
||||
cy.url().should("include", "/course/test-lehrgang/competence/self-evaluation-and-feedback");
|
||||
|
||||
// check data again on KompetenzNavi
|
||||
cy.get('[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-fail"]').should(
|
||||
"have.text",
|
||||
"1"
|
||||
);
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-success"]'
|
||||
).should("have.text", "1");
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-unknown"]'
|
||||
).should("have.text", "0");
|
||||
cy.get(`[data-cy="${identifier}-fail"]`).should("have.text", "1");
|
||||
cy.get(`[data-cy="${identifier}-pass"]`).should("have.text", "1");
|
||||
cy.get(`[data-cy="${identifier}-unknown"]`).should("not.exist");
|
||||
|
||||
// data in KompetenzNavi/Übersicht is correct
|
||||
cy.visit("/course/test-lehrgang/competence");
|
||||
|
|
@ -76,19 +67,6 @@ describe("selfEvaluation.cy.js", () => {
|
|||
|
||||
// starting the self evaluation from circle should return to circle
|
||||
cy.url().should("include", "/course/test-lehrgang/learn/reisen");
|
||||
|
||||
// data in KompetenzNavi / Selbsteinschätzungen is correct
|
||||
cy.visit("/course/test-lehrgang/competence/criteria");
|
||||
cy.get('[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-fail"]').should(
|
||||
"have.text",
|
||||
"0"
|
||||
);
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-success"]'
|
||||
).should("have.text", "2");
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-unknown"]'
|
||||
).should("have.text", "0");
|
||||
});
|
||||
|
||||
it("should be able to make a fail self evaluation", () => {
|
||||
|
|
@ -97,19 +75,6 @@ describe("selfEvaluation.cy.js", () => {
|
|||
cy.get('[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen"]')
|
||||
.find('[data-cy="fail"]')
|
||||
.should("exist");
|
||||
|
||||
// data in KompetenzNavi / Selbsteinschätzungen is correct
|
||||
cy.visit("/course/test-lehrgang/competence/criteria");
|
||||
cy.get('[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-fail"]').should(
|
||||
"have.text",
|
||||
"2"
|
||||
);
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-success"]'
|
||||
).should("have.text", "0");
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-unknown"]'
|
||||
).should("have.text", "0");
|
||||
});
|
||||
|
||||
it("should be able to make a mixed self evaluation", () => {
|
||||
|
|
@ -118,18 +83,5 @@ describe("selfEvaluation.cy.js", () => {
|
|||
cy.get('[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen"]')
|
||||
.find('[data-cy="fail"]')
|
||||
.should("exist");
|
||||
|
||||
// data in KompetenzNavi / Selbsteinschätzungen is correct
|
||||
cy.visit("/course/test-lehrgang/competence/criteria");
|
||||
cy.get('[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-fail"]').should(
|
||||
"have.text",
|
||||
"1"
|
||||
);
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-success"]'
|
||||
).should("have.text", "1");
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-reisen-lu-reisen-unknown"]'
|
||||
).should("have.text", "0");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,12 +9,16 @@ from vbv_lernwelt.core.constants import (
|
|||
TEST_COURSE_SESSION_BERN_ID,
|
||||
TEST_MENTOR1_USER_ID,
|
||||
TEST_STUDENT1_USER_ID,
|
||||
TEST_STUDENT1_VV_USER_ID,
|
||||
TEST_STUDENT2_USER_ID,
|
||||
TEST_STUDENT3_USER_ID,
|
||||
TEST_TRAINER1_USER_ID,
|
||||
)
|
||||
from vbv_lernwelt.core.models import Organisation, User
|
||||
from vbv_lernwelt.course.consts import COURSE_TEST_ID
|
||||
from vbv_lernwelt.course.consts import (
|
||||
COURSE_TEST_ID,
|
||||
COURSE_VERSICHERUNGSVERMITTLERIN_ID,
|
||||
)
|
||||
from vbv_lernwelt.course.creators.test_course import (
|
||||
create_edoniq_test_result_data,
|
||||
create_feedback_response_data,
|
||||
|
|
@ -39,6 +43,10 @@ from vbv_lernwelt.learnpath.models import (
|
|||
LearningContentFeedbackVV,
|
||||
)
|
||||
from vbv_lernwelt.notify.models import Notification
|
||||
from vbv_lernwelt.self_evaluation_feedback.models import (
|
||||
CourseCompletionFeedback,
|
||||
SelfEvaluationFeedback,
|
||||
)
|
||||
|
||||
|
||||
@click.command()
|
||||
|
|
@ -107,6 +115,9 @@ def command(
|
|||
FeedbackResponse.objects.all().delete()
|
||||
CourseSessionAttendanceCourse.objects.all().update(attendance_user_list=[])
|
||||
|
||||
SelfEvaluationFeedback.objects.all().delete()
|
||||
CourseCompletionFeedback.objects.all().delete()
|
||||
|
||||
LearningMentor.objects.all().delete()
|
||||
User.objects.all().update(organisation=Organisation.objects.first())
|
||||
User.objects.all().update(language="de")
|
||||
|
|
@ -345,19 +356,17 @@ def command(
|
|||
)
|
||||
)
|
||||
|
||||
# FIXME: Add mentor to VV course as well, once we can:
|
||||
# -> https://bitbucket.org/iterativ/vbv_lernwelt/pull-requests/287
|
||||
# vv_course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
|
||||
# vv_course_session = CourseSession.objects.get(course=vv_course)
|
||||
# vv_mentor = LearningMentor.objects.create(
|
||||
# course=vv_course,
|
||||
# mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
||||
# )
|
||||
# vv_mentor.participants.add(
|
||||
# CourseSessionUser.objects.get(
|
||||
# user__id=TEST_STUDENT1_VV_USER_ID, course_session=vv_course_session
|
||||
# )
|
||||
# )
|
||||
vv_course = Course.objects.get(id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
|
||||
vv_course_session = CourseSession.objects.get(course=vv_course)
|
||||
vv_mentor = LearningMentor.objects.create(
|
||||
course=vv_course,
|
||||
mentor=User.objects.get(id=TEST_MENTOR1_USER_ID),
|
||||
)
|
||||
vv_mentor.participants.add(
|
||||
CourseSessionUser.objects.get(
|
||||
user__id=TEST_STUDENT1_VV_USER_ID, course_session=vv_course_session
|
||||
)
|
||||
)
|
||||
|
||||
course = Course.objects.get(id=COURSE_TEST_ID)
|
||||
course.enable_circle_documents = enable_circle_documents
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ from vbv_lernwelt.learnpath.models import (
|
|||
LearningContentEdoniqTest,
|
||||
LearningPath,
|
||||
LearningUnit,
|
||||
LearningUnitPerformanceFeedbackType,
|
||||
)
|
||||
from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
||||
CircleFactory,
|
||||
|
|
@ -275,6 +276,7 @@ def create_learning_unit(
|
|||
circle: Circle,
|
||||
course: Course,
|
||||
course_category_title: str = "Course Category",
|
||||
feedback_user: LearningUnitPerformanceFeedbackType = LearningUnitPerformanceFeedbackType.NO_FEEDBACK,
|
||||
) -> LearningUnit:
|
||||
cat, _ = CourseCategory.objects.get_or_create(
|
||||
course=course,
|
||||
|
|
@ -285,6 +287,7 @@ def create_learning_unit(
|
|||
title="Learning Unit",
|
||||
parent=circle,
|
||||
course_category=cat,
|
||||
feedback_user=feedback_user.value,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -292,7 +295,7 @@ def create_performance_criteria_page(
|
|||
course: Course,
|
||||
course_page: CoursePage,
|
||||
circle: Circle,
|
||||
learning_unit: LearningUnitFactory | None = None,
|
||||
learning_unit: LearningUnitFactory | LearningUnit | None = None,
|
||||
) -> PerformanceCriteria:
|
||||
competence_navi_page = CompetenceNaviPageFactory(
|
||||
title="Competence Navi",
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ def create_vv_new_learning_path(
|
|||
create_circle_gewinnen(lp)
|
||||
|
||||
TopicFactory(title="Beraten und Betreuen von Kunden", parent=lp)
|
||||
create_circle_fahrzeug(lp)
|
||||
create_circle_fahrzeug(lp, course_page=course_page)
|
||||
create_circle_haushalt(lp)
|
||||
create_circle_rechtsstreitigkeiten(lp)
|
||||
create_circle_reisen(lp)
|
||||
|
|
@ -340,7 +340,7 @@ def create_circle_gewinnen(lp, title="Gewinnen"):
|
|||
)
|
||||
|
||||
|
||||
def create_circle_fahrzeug(lp, title="Fahrzeug"):
|
||||
def create_circle_fahrzeug(lp, title="Fahrzeug", course_page=None):
|
||||
circle = CircleFactory(
|
||||
title=title,
|
||||
parent=lp,
|
||||
|
|
@ -404,7 +404,14 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"):
|
|||
)
|
||||
|
||||
LearningSequenceFactory(title="Transfer", parent=circle, icon="it-icon-ls-end")
|
||||
LearningUnitFactory(title="Transfer", title_hidden=True, parent=circle)
|
||||
|
||||
lu_transfer = LearningUnitFactory(
|
||||
title="Transfer",
|
||||
title_hidden=True,
|
||||
parent=circle,
|
||||
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.name,
|
||||
)
|
||||
|
||||
LearningContentPlaceholderFactory(
|
||||
title="Praxisauftrag",
|
||||
parent=circle,
|
||||
|
|
@ -429,6 +436,36 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"):
|
|||
parent=circle,
|
||||
)
|
||||
|
||||
competence_profile_page = ActionCompetenceListPageFactory(
|
||||
title="KompetenzNavi",
|
||||
parent=course_page,
|
||||
)
|
||||
|
||||
ace = ActionCompetenceFactory(
|
||||
parent=competence_profile_page,
|
||||
)
|
||||
|
||||
PerformanceCriteriaFactory(
|
||||
parent=ace,
|
||||
competence_id="VV-Transfer-A",
|
||||
title="Ich setze das Gelernte in der Praxis um.",
|
||||
learning_unit=lu_transfer,
|
||||
)
|
||||
|
||||
PerformanceCriteriaFactory(
|
||||
parent=ace,
|
||||
competence_id="VV-Transfer-B",
|
||||
title="Ich kenne den Unterschied zwischen einem Neuwagen und einem Occasionswagen.",
|
||||
learning_unit=lu_transfer,
|
||||
)
|
||||
|
||||
PerformanceCriteriaFactory(
|
||||
parent=ace,
|
||||
competence_id="VV-Transfer-C",
|
||||
title="Ich kenne den Unterschied zwischen einem Leasing und einem Kauf.",
|
||||
learning_unit=lu_transfer,
|
||||
)
|
||||
|
||||
|
||||
def create_circle_haushalt(lp, title="Haushalt"):
|
||||
circle = CircleFactory(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from vbv_lernwelt.course.creators.test_utils import (
|
|||
from vbv_lernwelt.course.models import CourseCompletionStatus, CourseSessionUser
|
||||
from vbv_lernwelt.course.services import mark_course_completion
|
||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
||||
from vbv_lernwelt.learnpath.models import LearningUnitPerformanceFeedbackType
|
||||
from vbv_lernwelt.self_evaluation_feedback.models import (
|
||||
CourseCompletionFeedback,
|
||||
SelfEvaluationFeedback,
|
||||
|
|
@ -154,7 +155,9 @@ class SelfEvaluationFeedbackAPI(APITestCase):
|
|||
"""Tests endpoint of feedback REQUESTER"""
|
||||
|
||||
# GIVEN
|
||||
learning_unit = create_learning_unit(course=self.course, circle=self.circle)
|
||||
learning_unit = create_learning_unit( # noqa
|
||||
course=self.course, circle=self.circle
|
||||
)
|
||||
|
||||
performance_criteria_1 = create_performance_criteria_page(
|
||||
course=self.course,
|
||||
|
|
@ -204,11 +207,11 @@ class SelfEvaluationFeedbackAPI(APITestCase):
|
|||
|
||||
feedback = response.data
|
||||
self.assertEqual(feedback["learning_unit_id"], learning_unit.id)
|
||||
self.assertEqual(feedback["feedback_submitted"], False)
|
||||
self.assertEqual(feedback["circle_name"], self.circle.title)
|
||||
self.assertFalse(feedback["feedback_submitted"])
|
||||
self.assertEqual(feedback["circle_name"], self.circle.title) # noqa
|
||||
|
||||
provider_user = feedback["feedback_provider_user"]
|
||||
self.assertEqual(provider_user["id"], str(self.mentor.id)) # noqa
|
||||
self.assertEqual(provider_user["id"], str(self.mentor.id))
|
||||
self.assertEqual(provider_user["first_name"], self.mentor.first_name)
|
||||
self.assertEqual(provider_user["last_name"], self.mentor.last_name)
|
||||
self.assertEqual(provider_user["avatar_url"], self.mentor.avatar_url)
|
||||
|
|
@ -243,11 +246,217 @@ class SelfEvaluationFeedbackAPI(APITestCase):
|
|||
CourseCompletionStatus.UNKNOWN.value,
|
||||
)
|
||||
|
||||
def test_feedbacks_with_mixed_completion_statuses(self):
|
||||
"""Case: CourseCompletion AND feedbacks with mixed completion statuses"""
|
||||
|
||||
# GIVEN
|
||||
learning_unit = create_learning_unit(
|
||||
course=self.course,
|
||||
circle=self.circle,
|
||||
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
|
||||
)
|
||||
|
||||
feedback = create_self_evaluation_feedback(
|
||||
learning_unit=learning_unit,
|
||||
feedback_requester_user=self.member,
|
||||
feedback_provider_user=self.mentor,
|
||||
)
|
||||
|
||||
feedback.feedback_submitted = True
|
||||
feedback.save()
|
||||
|
||||
for status in [
|
||||
CourseCompletionStatus.SUCCESS,
|
||||
CourseCompletionStatus.FAIL,
|
||||
CourseCompletionStatus.UNKNOWN,
|
||||
]:
|
||||
criteria_page = create_performance_criteria_page(
|
||||
course=self.course,
|
||||
course_page=self.course_page,
|
||||
circle=self.circle,
|
||||
learning_unit=learning_unit,
|
||||
)
|
||||
|
||||
# self assessment
|
||||
completion = mark_course_completion(
|
||||
page=criteria_page,
|
||||
user=self.member,
|
||||
course_session=self.course_session,
|
||||
completion_status=status.value,
|
||||
)
|
||||
|
||||
# feedback assessment
|
||||
CourseCompletionFeedback.objects.create(
|
||||
feedback=feedback,
|
||||
course_completion=completion,
|
||||
feedback_assessment=status.value,
|
||||
)
|
||||
|
||||
self.client.force_login(self.member)
|
||||
|
||||
# WHEN
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"get_self_evaluation_feedbacks_as_requester",
|
||||
args=[self.course_session.id],
|
||||
)
|
||||
)
|
||||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
result = response.data["results"][0]
|
||||
|
||||
self_assessment = result["self_assessment"]
|
||||
self.assertEqual(self_assessment["counts"]["pass"], 1)
|
||||
self.assertEqual(self_assessment["counts"]["fail"], 1)
|
||||
self.assertEqual(self_assessment["counts"]["unknown"], 1)
|
||||
|
||||
feedback_assessment = result["feedback_assessment"]
|
||||
self.assertEqual(feedback_assessment["counts"]["pass"], 1)
|
||||
self.assertEqual(feedback_assessment["counts"]["fail"], 1)
|
||||
self.assertEqual(feedback_assessment["counts"]["unknown"], 1)
|
||||
|
||||
self.assertTrue(feedback_assessment["submitted_by_provider"])
|
||||
self.assertEqual(
|
||||
feedback_assessment["provider_user"]["id"], str(self.mentor.id)
|
||||
)
|
||||
|
||||
aggregate = response.data["aggregates"]
|
||||
self.assertEqual(aggregate["self_assessment"]["pass"], 1)
|
||||
self.assertEqual(aggregate["self_assessment"]["fail"], 1)
|
||||
self.assertEqual(aggregate["self_assessment"]["unknown"], 1)
|
||||
self.assertEqual(aggregate["feedback_assessment"]["pass"], 1)
|
||||
self.assertEqual(aggregate["feedback_assessment"]["fail"], 1)
|
||||
self.assertEqual(aggregate["feedback_assessment"]["unknown"], 1)
|
||||
|
||||
def test_no_feedbacks_but_with_completion_status(self):
|
||||
"""Case: CourseCompletion but NO feedback"""
|
||||
|
||||
# GIVEN
|
||||
learning_unit_with_success_feedback = create_learning_unit(
|
||||
course=self.course,
|
||||
circle=self.circle,
|
||||
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
|
||||
)
|
||||
|
||||
performance_criteria_page = create_performance_criteria_page(
|
||||
course=self.course,
|
||||
course_page=self.course_page,
|
||||
circle=self.circle,
|
||||
learning_unit=learning_unit_with_success_feedback,
|
||||
)
|
||||
|
||||
# IMPORTANT: CourseCompletion but NO feedback!
|
||||
|
||||
mark_course_completion(
|
||||
page=performance_criteria_page,
|
||||
user=self.member,
|
||||
course_session=self.course_session,
|
||||
completion_status=CourseCompletionStatus.SUCCESS.value,
|
||||
)
|
||||
|
||||
self.client.force_login(self.member)
|
||||
|
||||
# WHEN
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"get_self_evaluation_feedbacks_as_requester",
|
||||
args=[self.course_session.id],
|
||||
)
|
||||
)
|
||||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
result = response.data["results"][0]
|
||||
counts = result["self_assessment"]["counts"]
|
||||
self.assertEqual(counts["pass"], 1)
|
||||
self.assertEqual(counts["fail"], 0)
|
||||
self.assertEqual(counts["unknown"], 0)
|
||||
|
||||
def test_feedbacks_not_started(self):
|
||||
"""Case: Learning unit with no completion status and no feedback"""
|
||||
|
||||
# GIVEN
|
||||
learning_unit = create_learning_unit( # noqa
|
||||
course=self.course,
|
||||
circle=self.circle,
|
||||
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
|
||||
)
|
||||
|
||||
create_performance_criteria_page(
|
||||
course=self.course,
|
||||
course_page=self.course_page,
|
||||
circle=self.circle,
|
||||
learning_unit=learning_unit,
|
||||
)
|
||||
|
||||
self.client.force_login(self.member)
|
||||
|
||||
# WHEN
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"get_self_evaluation_feedbacks_as_requester",
|
||||
args=[self.course_session.id],
|
||||
)
|
||||
)
|
||||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
result = response.data["results"][0]
|
||||
self.assertEqual(result["self_assessment"]["counts"]["pass"], 0)
|
||||
self.assertEqual(result["self_assessment"]["counts"]["fail"], 0)
|
||||
self.assertEqual(result["self_assessment"]["counts"]["unknown"], 1)
|
||||
|
||||
def test_feedbacks_metadata(self):
|
||||
# GIVEN
|
||||
learning_unit = create_learning_unit( # noqa
|
||||
course=self.course,
|
||||
circle=self.circle,
|
||||
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
|
||||
)
|
||||
|
||||
create_performance_criteria_page(
|
||||
course=self.course,
|
||||
course_page=self.course_page,
|
||||
circle=self.circle,
|
||||
learning_unit=learning_unit,
|
||||
)
|
||||
|
||||
self.client.force_login(self.member)
|
||||
|
||||
# WHEN
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"get_self_evaluation_feedbacks_as_requester",
|
||||
args=[self.course_session.id],
|
||||
)
|
||||
)
|
||||
|
||||
# THEN
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
result = response.data["results"][0]
|
||||
self.assertEqual(result["title"], learning_unit.title)
|
||||
self.assertEqual(result["id"], learning_unit.id)
|
||||
self.assertEqual(result["circle_id"], self.circle.id)
|
||||
self.assertEqual(result["circle_title"], self.circle.title)
|
||||
self.assertEqual(result["detail_url"], learning_unit.get_evaluate_url())
|
||||
|
||||
circles = response.data["circles"]
|
||||
self.assertEqual(len(circles), 1)
|
||||
self.assertEqual(circles[0]["id"], self.circle.id)
|
||||
self.assertEqual(circles[0]["title"], self.circle.title)
|
||||
|
||||
def test_get_self_evaluation_feedback_as_provider(self):
|
||||
"""Tests endpoint of feedback PROVIDER"""
|
||||
|
||||
# GIVEN
|
||||
learning_unit = create_learning_unit(course=self.course, circle=self.circle)
|
||||
learning_unit = create_learning_unit( # noqa
|
||||
course=self.course, circle=self.circle
|
||||
)
|
||||
|
||||
performance_criteria_1 = create_performance_criteria_page(
|
||||
course=self.course,
|
||||
|
|
@ -299,7 +508,7 @@ class SelfEvaluationFeedbackAPI(APITestCase):
|
|||
self.assertEqual(feedback["learning_unit_id"], learning_unit.id)
|
||||
self.assertEqual(feedback["title"], learning_unit.title)
|
||||
self.assertEqual(feedback["feedback_submitted"], False)
|
||||
self.assertEqual(feedback["circle_name"], self.circle.title)
|
||||
self.assertEqual(feedback["circle_name"], self.circle.title) # noqa
|
||||
|
||||
provider_user = feedback["feedback_provider_user"]
|
||||
self.assertEqual(provider_user["id"], str(self.mentor.id)) # noqa
|
||||
|
|
@ -404,7 +613,7 @@ class SelfEvaluationFeedbackAPI(APITestCase):
|
|||
self.client.force_login(self.mentor)
|
||||
|
||||
# WHEN
|
||||
response = self.client.put(
|
||||
self.client.put(
|
||||
reverse(
|
||||
"release_self_evaluation_feedback", args=[self_evaluation_feedback.id]
|
||||
),
|
||||
|
|
|
|||
|
|
@ -4,11 +4,18 @@ from vbv_lernwelt.self_evaluation_feedback.views import (
|
|||
add_provider_self_evaluation_feedback,
|
||||
get_self_evaluation_feedback_as_provider,
|
||||
get_self_evaluation_feedback_as_requester,
|
||||
get_self_evaluation_feedbacks_as_requester,
|
||||
release_provider_self_evaluation_feedback,
|
||||
start_self_evaluation_feedback,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
# /requester/* URLs -> For the user who requests feedback
|
||||
path(
|
||||
"requester/<signed_int:course_session_id>/feedbacks/summaries",
|
||||
get_self_evaluation_feedbacks_as_requester,
|
||||
name="get_self_evaluation_feedbacks_as_requester",
|
||||
),
|
||||
path(
|
||||
"requester/<int:learning_unit_id>/feedback/start",
|
||||
start_self_evaluation_feedback,
|
||||
|
|
@ -19,6 +26,7 @@ urlpatterns = [
|
|||
get_self_evaluation_feedback_as_requester,
|
||||
name="get_self_evaluation_feedback_as_requester",
|
||||
),
|
||||
# /provider/* URLs -> For the user who is providing feedback
|
||||
path(
|
||||
"provider/<int:learning_unit_id>/feedback",
|
||||
get_self_evaluation_feedback_as_provider,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
from typing import NamedTuple
|
||||
|
||||
from django.db.models import Case, Count, IntegerField, Sum, Value, When
|
||||
from django.db.models.functions import Coalesce
|
||||
|
||||
from vbv_lernwelt.core.admin import User
|
||||
from vbv_lernwelt.course.models import CourseCompletion, CourseCompletionStatus
|
||||
from vbv_lernwelt.learnpath.models import LearningUnit
|
||||
from vbv_lernwelt.self_evaluation_feedback.models import (
|
||||
CourseCompletionFeedback,
|
||||
SelfEvaluationFeedback,
|
||||
)
|
||||
|
||||
|
||||
class AssessmentCounts(NamedTuple):
|
||||
pass_count: int
|
||||
fail_count: int
|
||||
unknown_count: int
|
||||
|
||||
@property
|
||||
def total_count(self):
|
||||
return self.pass_count + self.fail_count + self.unknown_count
|
||||
|
||||
|
||||
def get_self_evaluation_feedback_counts(
|
||||
feedback: SelfEvaluationFeedback,
|
||||
):
|
||||
course_completion_feedback = CourseCompletionFeedback.objects.filter(
|
||||
feedback=feedback
|
||||
).aggregate(
|
||||
pass_count=Coalesce(
|
||||
Sum(
|
||||
Case(
|
||||
When(
|
||||
feedback_assessment=CourseCompletionStatus.SUCCESS.value,
|
||||
then=Value(1),
|
||||
),
|
||||
output_field=IntegerField(),
|
||||
)
|
||||
),
|
||||
Value(0),
|
||||
),
|
||||
fail_count=Coalesce(
|
||||
Sum(
|
||||
Case(
|
||||
When(
|
||||
feedback_assessment=CourseCompletionStatus.FAIL.value,
|
||||
then=Value(1),
|
||||
),
|
||||
output_field=IntegerField(),
|
||||
)
|
||||
),
|
||||
Value(0),
|
||||
),
|
||||
unknown_count=Coalesce(
|
||||
Sum(
|
||||
Case(
|
||||
When(
|
||||
feedback_assessment=CourseCompletionStatus.UNKNOWN.value,
|
||||
then=Value(1),
|
||||
),
|
||||
output_field=IntegerField(),
|
||||
)
|
||||
),
|
||||
Value(0),
|
||||
),
|
||||
)
|
||||
|
||||
return AssessmentCounts(
|
||||
pass_count=course_completion_feedback.get("pass_count", 0),
|
||||
fail_count=course_completion_feedback.get("fail_count", 0),
|
||||
unknown_count=course_completion_feedback.get("unknown_count", 0),
|
||||
)
|
||||
|
||||
|
||||
def get_self_assessment_counts(
|
||||
learning_unit: LearningUnit, user: User
|
||||
) -> AssessmentCounts:
|
||||
performance_criteria = learning_unit.performancecriteria_set.all()
|
||||
|
||||
completion_counts = CourseCompletion.objects.filter(
|
||||
page__in=performance_criteria, user=user
|
||||
).aggregate(
|
||||
pass_count=Count(
|
||||
Case(
|
||||
When(completion_status=CourseCompletionStatus.SUCCESS.value, then=1),
|
||||
output_field=IntegerField(),
|
||||
)
|
||||
),
|
||||
fail_count=Count(
|
||||
Case(
|
||||
When(completion_status=CourseCompletionStatus.FAIL.value, then=1),
|
||||
output_field=IntegerField(),
|
||||
)
|
||||
),
|
||||
unknown_count=Count(
|
||||
Case(
|
||||
When(completion_status=CourseCompletionStatus.UNKNOWN.value, then=1),
|
||||
output_field=IntegerField(),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
pass_count = completion_counts.get("pass_count", 0)
|
||||
fail_count = completion_counts.get("fail_count", 0)
|
||||
unknown_count = completion_counts.get("unknown_count", 0)
|
||||
|
||||
# not yet completed performance criteria are unknown
|
||||
if pass_count + fail_count + unknown_count < performance_criteria.count():
|
||||
unknown_count += performance_criteria.count() - (
|
||||
pass_count + fail_count + unknown_count
|
||||
)
|
||||
|
||||
return AssessmentCounts(
|
||||
pass_count=pass_count,
|
||||
fail_count=fail_count,
|
||||
unknown_count=unknown_count,
|
||||
)
|
||||
|
||||
|
||||
def calculate_aggregate(counts: [AssessmentCounts]):
|
||||
return AssessmentCounts(
|
||||
pass_count=sum(x.pass_count for x in counts),
|
||||
fail_count=sum(x.fail_count for x in counts),
|
||||
unknown_count=sum(x.unknown_count for x in counts),
|
||||
)
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import structlog
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
|
@ -5,9 +6,14 @@ from rest_framework.permissions import IsAuthenticated
|
|||
from rest_framework.response import Response
|
||||
|
||||
from vbv_lernwelt.core.models import User
|
||||
from vbv_lernwelt.course.models import CourseCompletion
|
||||
from vbv_lernwelt.core.serializers import UserSerializer
|
||||
from vbv_lernwelt.course.models import CourseCompletion, CourseSession
|
||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
||||
from vbv_lernwelt.learnpath.models import LearningUnit
|
||||
from vbv_lernwelt.learnpath.models import (
|
||||
Circle,
|
||||
LearningUnit,
|
||||
LearningUnitPerformanceFeedbackType,
|
||||
)
|
||||
from vbv_lernwelt.notify.services import NotificationService
|
||||
from vbv_lernwelt.self_evaluation_feedback.models import (
|
||||
CourseCompletionFeedback,
|
||||
|
|
@ -16,6 +22,13 @@ from vbv_lernwelt.self_evaluation_feedback.models import (
|
|||
from vbv_lernwelt.self_evaluation_feedback.serializers import (
|
||||
SelfEvaluationFeedbackSerializer,
|
||||
)
|
||||
from vbv_lernwelt.self_evaluation_feedback.utils import (
|
||||
AssessmentCounts,
|
||||
get_self_assessment_counts,
|
||||
get_self_evaluation_feedback_counts,
|
||||
)
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@api_view(["POST"])
|
||||
|
|
@ -80,6 +93,126 @@ def get_self_evaluation_feedback_as_provider(request, learning_unit_id):
|
|||
return Response(SelfEvaluationFeedbackSerializer(feedback).data)
|
||||
|
||||
|
||||
@api_view(["GET"])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def get_self_evaluation_feedbacks_as_requester(request, course_session_id: int):
|
||||
course_session = get_object_or_404(CourseSession, id=course_session_id)
|
||||
|
||||
results = []
|
||||
circle_ids = set()
|
||||
|
||||
all_self_assessment_counts = []
|
||||
all_feedback_assessment_counts = []
|
||||
|
||||
for learning_unit in LearningUnit.objects.filter(
|
||||
course_category__course=course_session.course,
|
||||
):
|
||||
# this is not a problem in real life, but in the test environment
|
||||
# we have a lot of learning units without self assessment criteria
|
||||
# -> just skip those learning units
|
||||
if len(learning_unit.performancecriteria_set.all()) == 0:
|
||||
continue
|
||||
|
||||
circle = learning_unit.get_parent().specific
|
||||
circle_ids.add(circle.id)
|
||||
|
||||
feedback = SelfEvaluationFeedback.objects.filter(
|
||||
learning_unit=learning_unit,
|
||||
feedback_requester_user=request.user,
|
||||
).first()
|
||||
|
||||
if not feedback:
|
||||
# no feedback given yet
|
||||
feedback_assessment = None
|
||||
else:
|
||||
# feedback given
|
||||
feedback_counts = get_self_evaluation_feedback_counts(feedback)
|
||||
all_feedback_assessment_counts.append(feedback_counts)
|
||||
|
||||
feedback_assessment = {
|
||||
"submitted_by_provider": feedback.feedback_submitted,
|
||||
"provider_user": UserSerializer(feedback.feedback_provider_user).data,
|
||||
"counts": {
|
||||
"pass": feedback_counts.pass_count,
|
||||
"fail": feedback_counts.fail_count,
|
||||
"unknown": feedback_counts.unknown_count,
|
||||
},
|
||||
}
|
||||
|
||||
self_assessment_counts = get_self_assessment_counts(learning_unit, request.user)
|
||||
all_self_assessment_counts.append(self_assessment_counts)
|
||||
|
||||
results.append(
|
||||
{
|
||||
"id": learning_unit.id,
|
||||
"title": learning_unit.title,
|
||||
"detail_url": learning_unit.get_evaluate_url(),
|
||||
"circle_id": circle.id,
|
||||
"circle_title": circle.title,
|
||||
"feedback_assessment": feedback_assessment,
|
||||
"self_assessment": {
|
||||
"counts": {
|
||||
"pass": self_assessment_counts.pass_count,
|
||||
"fail": self_assessment_counts.fail_count,
|
||||
"unknown": self_assessment_counts.unknown_count,
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
self_assessment_counts_aggregate = AssessmentCounts(
|
||||
pass_count=sum(x.pass_count for x in all_self_assessment_counts),
|
||||
fail_count=sum(x.fail_count for x in all_self_assessment_counts),
|
||||
unknown_count=sum(x.unknown_count for x in all_self_assessment_counts),
|
||||
)
|
||||
received_feedback_counts_aggregate = AssessmentCounts(
|
||||
pass_count=sum(x.pass_count for x in all_feedback_assessment_counts),
|
||||
fail_count=sum(x.fail_count for x in all_feedback_assessment_counts),
|
||||
unknown_count=sum(x.unknown_count for x in all_feedback_assessment_counts),
|
||||
)
|
||||
|
||||
# pad the feedback counts with unknowns for the
|
||||
# learning units where we have no feedback yet
|
||||
feedback_assessment_counts_aggregate = AssessmentCounts(
|
||||
pass_count=received_feedback_counts_aggregate.pass_count,
|
||||
fail_count=received_feedback_counts_aggregate.fail_count,
|
||||
unknown_count=self_assessment_counts_aggregate.total_count
|
||||
- received_feedback_counts_aggregate.total_count
|
||||
+ received_feedback_counts_aggregate.unknown_count,
|
||||
)
|
||||
|
||||
# check if there are any learning units with mentor feedback
|
||||
feedback_assessment_visible = (
|
||||
LearningUnit.objects.filter(
|
||||
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.value,
|
||||
course_category__course=course_session.course,
|
||||
).count()
|
||||
> 0
|
||||
)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"results": results,
|
||||
"circles": list(
|
||||
Circle.objects.filter(id__in=circle_ids).values("id", "title")
|
||||
),
|
||||
"aggregates": {
|
||||
"feedback_assessment_visible": feedback_assessment_visible,
|
||||
"feedback_assessment": {
|
||||
"pass": feedback_assessment_counts_aggregate.pass_count,
|
||||
"fail": feedback_assessment_counts_aggregate.fail_count,
|
||||
"unknown": feedback_assessment_counts_aggregate.unknown_count,
|
||||
},
|
||||
"self_assessment": {
|
||||
"pass": self_assessment_counts_aggregate.pass_count,
|
||||
"fail": self_assessment_counts_aggregate.fail_count,
|
||||
"unknown": self_assessment_counts_aggregate.unknown_count,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@api_view(["GET"])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def get_self_evaluation_feedback_as_requester(request, learning_unit_id):
|
||||
|
|
|
|||
Loading…
Reference in New Issue