feat: kompetenznavi overview

This commit is contained in:
Livio Bieri 2024-02-22 12:58:59 +01:00
parent 0d402d7912
commit 964c1f7276
7 changed files with 128 additions and 32 deletions

View File

@ -8,7 +8,7 @@ const props = defineProps<{
}>();
const hasFeedbackReceived = computed(() => {
return props.summary.feedback_assessment?.provider_user !== undefined;
return props.summary.feedback_assessment?.submitted_by_provider ?? false;
});
const feedbackProviderAvatar = computed(() => {
@ -46,19 +46,19 @@ const feedbackProviderName = computed(() => {
</div>
<div class="cell">
<SmileyCell
:count="props.summary.self_assessment.counts.pass + 1"
:count="props.summary.self_assessment.counts.pass"
smiley="it-icon-smiley-happy"
/>
</div>
<div class="cell">
<SmileyCell
:count="props.summary.self_assessment.counts.fail + 1"
:count="props.summary.self_assessment.counts.fail"
smiley="it-icon-smiley-thinking"
/>
</div>
<div class="cell">
<SmileyCell
:count="props.summary.self_assessment.counts.unknown + 1"
:count="props.summary.self_assessment.counts.unknown"
smiley="it-icon-smiley-neutral"
/>
</div>

View File

@ -4,14 +4,14 @@ 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";
const props = defineProps<{
courseSlug: string;
@ -20,7 +20,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,9 +48,23 @@ 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
);
</script>
<template>
@ -80,7 +93,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,16 +143,15 @@ 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">
<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"
>
<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>
@ -147,13 +159,11 @@ const performanceCriteriaStatusCount = computed(() => {
class="ml-4 inline-block text-7xl font-bold"
data-cy="self-evaluation-fail"
>
{{ performanceCriteriaStatusCount.FAIL }}
{{ selfAssessmentCounts?.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.yes") }}»</h5>
<div class="flex flex-row items-center">
<it-icon-smiley-happy class="h-16 w-16"></it-icon-smiley-happy>
@ -161,11 +171,11 @@ const performanceCriteriaStatusCount = computed(() => {
class="ml-4 inline-block text-7xl font-bold"
data-cy="self-evaluation-success"
>
{{ performanceCriteriaStatusCount.SUCCESS }}
{{ selfAssessmentCounts?.pass }}
</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">
<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>
@ -173,12 +183,59 @@ const performanceCriteriaStatusCount = computed(() => {
class="ml-4 inline-block text-7xl font-bold"
data-cy="self-evaluation-unknown"
>
{{ performanceCriteriaStatusCount.UNKNOWN }}
{{ selfAssessmentCounts?.unknown }}
</p>
</div>
</li>
</ul>
<!-- Feedback evaluation -->
<template v-if="isFeedbackEvaluationVisible">
<h3 class="mb-4 border-t pb-4 pt-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"
>
<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"
data-cy="self-evaluation-fail"
>
{{ 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"
data-cy="self-evaluation-success"
>
{{ 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"
data-cy="self-evaluation-unknown"
>
{{ feedbackEvaluationCounts?.unknown }}
</p>
</div>
</li>
</ul>
</template>
<div>
<router-link
:to="`/course/${props.courseSlug}/competence/criteria`"

View File

@ -32,6 +32,17 @@ const summaries = computed(() => {
);
});
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");
}
});
log.info("SelfEvaluationsAndFeedbackPage created");
</script>
@ -39,7 +50,7 @@ log.info("SelfEvaluationsAndFeedbackPage created");
<div v-if="isLoaded">
<div class="container-large">
<div class="col flex items-center justify-between pb-4">
<h2 class="py-4">{{ $t("a.Selbst- und Fremdeinschätzungen") }}</h2>
<h2 class="py-4">{{ headerTitle }}</h2>
<ItDropdownSelect
v-model="selectedCircle"
class="text-bold w-24 min-w-[18rem] border-2 border-gray-300"

View File

@ -33,8 +33,10 @@ interface FeedbackSummaryCounts {
export interface FeedbackSummaryAggregates {
// totals across all learning units in the course session
self_assessment_counts: FeedbackSummaryCounts;
feedback_assessment_counts: FeedbackSummaryCounts;
self_assessment: FeedbackSummaryCounts;
feedback_assessment: FeedbackSummaryCounts;
// does this course have any feedback?
feedback_assessment_visible: boolean;
}
interface FeedbackAssessmentSummary {

View File

@ -12,7 +12,7 @@ from vbv_lernwelt.self_evaluation_feedback.views import (
urlpatterns = [
# /requester/* URLs -> For the user who requests feedback
path(
"requester/<int:course_session_id>/feedbacks/summaries",
"requester/<signed_int:course_session_id>/feedbacks/summaries",
get_self_evaluation_feedbacks_as_requester,
name="get_self_evaluation_feedbacks_as_requester",
),

View File

@ -17,6 +17,10 @@ class AssessmentCounts(NamedTuple):
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,

View File

@ -23,9 +23,9 @@ from vbv_lernwelt.self_evaluation_feedback.serializers import (
SelfEvaluationFeedbackSerializer,
)
from vbv_lernwelt.self_evaluation_feedback.utils import (
calculate_aggregate,
get_self_assessment_counts,
get_self_evaluation_feedback_counts,
AssessmentCounts,
)
logger = structlog.get_logger(__name__)
@ -105,7 +105,6 @@ def get_self_evaluation_feedbacks_as_requester(request, course_session_id: int):
all_feedback_assessment_counts = []
for learning_unit in LearningUnit.objects.filter(
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.value,
course_category__course=course_session.course,
):
# this is not a problem in real life, but in the test environment
@ -161,12 +160,34 @@ def get_self_evaluation_feedbacks_as_requester(request, course_session_id: int):
}
)
self_assessment_counts_aggregate = calculate_aggregate(
counts=all_self_assessment_counts
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),
)
feedback_assessment_counts_aggregate = calculate_aggregate(
counts=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(
@ -176,6 +197,7 @@ def get_self_evaluation_feedbacks_as_requester(request, course_session_id: int):
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,