feat: kompnavi api

This commit is contained in:
Livio Bieri 2024-02-15 21:49:26 +01:00
parent 8e7f69f296
commit 7f8cfcba24
8 changed files with 434 additions and 11 deletions

View File

@ -27,6 +27,10 @@ function routeInActionCompetences() {
return route.path.endsWith("/competences");
}
function routeInSelfEvaluationAndFeedback() {
return route.path.endsWith("/self-evaluation-and-feedback");
}
onMounted(async () => {
log.debug("CompetenceParentPage mounted", props.courseSlug);
});
@ -79,6 +83,20 @@ onMounted(async () => {
</router-link>
</li>
<li
class="border-t-2 border-t-transparent lg:ml-12"
:class="{
'border-b-2 border-b-blue-900': routeInSelfEvaluationAndFeedback(),
}"
>
<router-link
:to="`/course/${courseSlug}/competence/self-evaluation-and-feedback`"
class="block py-3"
>
{{ $t("a.Selbst- und Fremdeinschätzungen") }}
</router-link>
</li>
<!-- Add similar logic for other `li` items as you expand the list -->
<li class="ml-6 inline-block lg:ml-12"></li>
</ul>

View File

@ -0,0 +1,9 @@
<script setup lang="ts"></script>
<template>
<div>
<h1>SelfEvaluationsAndFeedback</h1>
</div>
</template>
<style scoped></style>

View File

@ -92,7 +92,6 @@ const router = createRouter({
props: true,
component: () => import("@/pages/competence/CompetenceIndexPage.vue"),
},
{
path: "certificates",
props: true,
@ -110,6 +109,12 @@ const router = createRouter({
props: true,
component: () => import("@/pages/competence/PerformanceCriteriaPage.vue"),
},
{
path: "self-evaluation-and-feedback",
props: true,
component: () =>
import("@/pages/competence/SelfEvaluationAndFeedbackPage.vue"),
},
{
path: "competences",
props: true,

View File

@ -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",

View File

@ -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,205 @@ 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["pass"], 1)
self.assertEqual(self_assessment["fail"], 1)
self.assertEqual(self_assessment["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)
)
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]
self.assertEqual(result["self_assessment"]["pass"], 1)
self.assertEqual(result["self_assessment"]["fail"], 0)
self.assertEqual(result["self_assessment"]["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"]["pass"], 0)
self.assertEqual(result["self_assessment"]["fail"], 0)
self.assertEqual(result["self_assessment"]["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)
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 +496,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 +601,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]
),

View File

@ -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/<int:course_session_id>/feedbacks",
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,

View File

@ -0,0 +1,114 @@
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
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,
)

View File

@ -5,9 +5,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 +21,10 @@ 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 (
get_self_assessment_counts,
get_self_evaluation_feedback_counts,
)
@api_view(["POST"])
@ -80,6 +89,66 @@ 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()
for learning_unit in LearningUnit.objects.filter(
feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK.value,
course_category__course=course_session.course,
):
circle = learning_unit.get_parent().specific
circle_ids.add(circle.id)
feedback_assessment = None
feedback = SelfEvaluationFeedback.objects.filter(
learning_unit=learning_unit,
feedback_requester_user=request.user,
).first()
if feedback:
feedback_counts = get_self_evaluation_feedback_counts(feedback)
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)
results.append(
{
"id": learning_unit.id,
"title": learning_unit.title,
"feedback_assessment": feedback_assessment,
"self_assessment": {
"pass": self_assessment_counts.pass_count,
"fail": self_assessment_counts.fail_count,
"unknown": self_assessment_counts.unknown_count,
},
}
)
return Response(
{
"results": results,
"circles": list(
Circle.objects.filter(id__in=circle_ids).values("id", "title")
),
}
)
@api_view(["GET"])
@permission_classes([IsAuthenticated])
def get_self_evaluation_feedback_as_requester(request, learning_unit_id):