diff --git a/server/vbv_lernwelt/dashboard/graphql/types/dashboard.py b/server/vbv_lernwelt/dashboard/graphql/types/dashboard.py index 863d7fe7..bfa683f4 100644 --- a/server/vbv_lernwelt/dashboard/graphql/types/dashboard.py +++ b/server/vbv_lernwelt/dashboard/graphql/types/dashboard.py @@ -56,8 +56,8 @@ class CourseDashboardType(graphene.ObjectType): for course_session in course_sessions: course_session_data.append( CourseSessionData( - session_id=course_session.id, - session_title=course_session.title, + session_id=course_session.id, # noqa + session_title=course_session.title, # noqa ) ) generations.add(course_session.generation) @@ -78,9 +78,9 @@ class CourseDashboardType(graphene.ObjectType): for circle in circles: circle_data.append( CircleData( - circle_id=circle.id, - circle_title=circle.title, - experts=[ + circle_id=circle.id, # noqa + circle_title=circle.title, # noqa + experts=[ # noqa f"{su.user.first_name} {su.user.last_name}" for su in circle.expert.all() ], @@ -88,7 +88,7 @@ class CourseDashboardType(graphene.ObjectType): ) return CourseSessionProperties( - sessions=course_session_data, - generations=list(generations), - circles=circle_data, + sessions=course_session_data, # noqa + generations=list(generations), # noqa + circles=circle_data, # noqa ) diff --git a/server/vbv_lernwelt/dashboard/graphql/types/feedback.py b/server/vbv_lernwelt/dashboard/graphql/types/feedback.py index d1704194..1d719d97 100644 --- a/server/vbv_lernwelt/dashboard/graphql/types/feedback.py +++ b/server/vbv_lernwelt/dashboard/graphql/types/feedback.py @@ -5,22 +5,25 @@ import graphene from vbv_lernwelt.core.models import User from vbv_lernwelt.course.models import CourseSession, CourseSessionUser from vbv_lernwelt.feedback.models import FeedbackResponse +from vbv_lernwelt.feedback.utils import feedback_users class FeedbackSummary(graphene.ObjectType): satisfaction_average = graphene.Float() - satisfaction_total = graphene.Int() + satisfaction_max = graphene.Int() total_responses = graphene.Int() -class Record(graphene.ObjectType): +class FeedbackRecord(graphene.ObjectType): course_session_id = graphene.ID() generation = graphene.String() circle_id = graphene.ID() + satisfaction_average = graphene.Float() + satisfaction_max = graphene.Int() class FeedbackResponses(graphene.ObjectType): - records = graphene.List(Record) + records = graphene.List(FeedbackRecord) summary = graphene.Field(FeedbackSummary) @@ -32,22 +35,38 @@ def feedback_responses(course_id: graphene.String(), user: User): course_id=course_id, ) + circle_feedbacks = [] + for course_session in course_sessions: fbs = FeedbackResponse.objects.filter( submitted=True, course_session=course_session, - # Only get feedbacks from members - feedback_user__in=CourseSessionUser.objects.filter( - course_session=course_session, role=CourseSessionUser.Role.MEMBER - ).values_list("user", flat=True), + feedback_user__in=feedback_users(course_session.id), ) - circle_feedbacks = circle_feedback_average(fbs) - return FeedbackResponses() + circle_feedbacks.extend( + circle_feedback_average(fbs, course_session.id, course_session.generation) + ) + + avg = sum([fb.satisfaction_average for fb in circle_feedbacks]) / len( + circle_feedbacks + ) + + return FeedbackResponses( + records=circle_feedbacks, # noqa + summary=FeedbackSummary( # noqa + satisfaction_average=avg, # noqa + satisfaction_max=4, # noqa + total_responses=len(fbs), # noqa + ), + ) -def circle_feedback_average(feedbacks: List[FeedbackResponse]): +def circle_feedback_average( + feedbacks: List[FeedbackResponse], course_session_id, generation: str +): circle_data = {} + records = [] for fb in feedbacks: circle_id = fb.circle.id @@ -60,6 +79,14 @@ def circle_feedback_average(feedbacks: List[FeedbackResponse]): circle_data[circle_id] = {"total": satisfaction, "count": 1} for circle_id, data in circle_data.items(): - circle_data[circle_id]["avg_satisfaction"] = data["total"] / data["count"] + records.append( + FeedbackRecord( + course_session_id=course_session_id, # noqa + generation=generation, # noqa + circle_id=circle_id, # noqa + satisfaction_average=data["total"] / data["count"], # noqa + satisfaction_max=4, # noqa + ) + ) - return circle_data + return records diff --git a/server/vbv_lernwelt/dashboard/tests/graphql/test_attendance.py b/server/vbv_lernwelt/dashboard/tests/graphql/test_attendance.py index e6ddc8ba..5b490898 100644 --- a/server/vbv_lernwelt/dashboard/tests/graphql/test_attendance.py +++ b/server/vbv_lernwelt/dashboard/tests/graphql/test_attendance.py @@ -82,7 +82,7 @@ class DashboardAttendanceTestCase(GraphQLTestCase): attendance_day_presences{{ summary{{ days_completed - participants_present + participants_present }} records{{ course_session_id @@ -91,8 +91,8 @@ class DashboardAttendanceTestCase(GraphQLTestCase): due_date participants_present participants_total - cockpit_url - }} + cockpit_url + }} }} }} }} diff --git a/server/vbv_lernwelt/dashboard/tests/graphql/test_dashboard.py b/server/vbv_lernwelt/dashboard/tests/graphql/test_dashboard.py index 4f305615..afea64f4 100644 --- a/server/vbv_lernwelt/dashboard/tests/graphql/test_dashboard.py +++ b/server/vbv_lernwelt/dashboard/tests/graphql/test_dashboard.py @@ -52,17 +52,17 @@ class DashboardTestCase(GraphQLTestCase): course_dashboard {{ course_id course_title - course_session_properties{{ - sessions{{ + course_session_properties {{ + sessions {{ session_id session_title }} generations - circles{{ + circles {{ circle_id circle_title experts - }} + }} }} }} }} diff --git a/server/vbv_lernwelt/dashboard/tests/graphql/test_feedback.py b/server/vbv_lernwelt/dashboard/tests/graphql/test_feedback.py index ccae60a2..cef2b066 100644 --- a/server/vbv_lernwelt/dashboard/tests/graphql/test_feedback.py +++ b/server/vbv_lernwelt/dashboard/tests/graphql/test_feedback.py @@ -1,7 +1,6 @@ from graphene_django.utils import GraphQLTestCase from vbv_lernwelt.course.models import CourseSessionUser -from vbv_lernwelt.dashboard.graphql.types.feedback import feedback_responses from vbv_lernwelt.dashboard.tests.graphql.utils import ( add_course_session_user, create_circle, @@ -29,6 +28,11 @@ class DashboardFeedbackTestCase(GraphQLTestCase): ) member = create_user("member") + add_course_session_user( + course_session=course_session, + user=member, + role=CourseSessionUser.Role.MEMBER, + ) circle1, _ = create_circle(title="Test Circle 1", course_page=course_page) circle2, _ = create_circle(title="Test Circle 2", course_page=course_page) @@ -38,29 +42,83 @@ class DashboardFeedbackTestCase(GraphQLTestCase): data={"satisfaction": 3}, circle=circle1, course_session=course_session, + submitted=True, ) FeedbackResponse.objects.create( feedback_user=member, data={"satisfaction": 4}, circle=circle1, course_session=course_session, + submitted=True, ) # Create Feedbacks for circle2 FeedbackResponse.objects.create( feedback_user=member, - data={"satisfaction": 5}, + data={"satisfaction": 1}, circle=circle2, course_session=course_session, + submitted=True, ) FeedbackResponse.objects.create( feedback_user=member, data={"satisfaction": 2}, circle=circle2, course_session=course_session, + submitted=True, ) - # Get average satisfaction per circle - result = feedback_responses(course.id, supervisor) + self.client.force_login(supervisor) + + query = f"""query($course_id: ID) {{ + course_dashboard(course_id: $course_id) {{ + course_id + feedback_responses {{ + records {{ + course_session_id + generation + circle_id + satisfaction_average + satisfaction_max + }} + summary {{ + satisfaction_average + satisfaction_max + total_responses + }} + }} + }} + }} + """ + variables = {"course_id": str(course.id)} + + # WHEN + response = self.query(query, variables=variables) # THEN + self.assertResponseNoErrors(response) + + course_dashboard = response.json()["data"]["course_dashboard"] + feedback_responses = course_dashboard[0]["feedback_responses"] + + records = feedback_responses["records"] + self.assertEqual(len(records), 2) + + circle1_record = next( + (r for r in records if r["circle_id"] == str(circle1.id)), None + ) + self.assertEqual(circle1_record["satisfaction_average"], 3.5) + self.assertEqual(circle1_record["course_session_id"], str(course_session.id)) + self.assertEqual(circle1_record["generation"], "2023") + + circle2_record = next( + (r for r in records if r["circle_id"] == str(circle2.id)), None + ) + self.assertEqual(circle2_record["satisfaction_average"], 1.5) + self.assertEqual(circle2_record["course_session_id"], str(course_session.id)) + self.assertEqual(circle2_record["generation"], "2023") + + summary = feedback_responses["summary"] + self.assertEqual(summary["satisfaction_average"], 2.5) + self.assertEqual(summary["satisfaction_max"], 4) + self.assertEqual(summary["total_responses"], 4) diff --git a/server/vbv_lernwelt/feedback/utils.py b/server/vbv_lernwelt/feedback/utils.py new file mode 100644 index 00000000..38ba733e --- /dev/null +++ b/server/vbv_lernwelt/feedback/utils.py @@ -0,0 +1,15 @@ +from django.db.models import Q + +from vbv_lernwelt.core.constants import ADMIN_USER_ID +from vbv_lernwelt.course.models import CourseSessionUser + + +def feedback_users(course_session_id): + """ + Solely accept feedback originating from members of the course session and the illustrious + administrative user, who serves as the repository for feedbacks heretofore submitted anonymously ;-) + """ + return CourseSessionUser.objects.filter( + Q(course_session_id=course_session_id, role=CourseSessionUser.Role.MEMBER) + | Q(user__id=ADMIN_USER_ID) + ).values_list("user", flat=True) diff --git a/server/vbv_lernwelt/feedback/views.py b/server/vbv_lernwelt/feedback/views.py index 85267a9c..ebfd27cf 100644 --- a/server/vbv_lernwelt/feedback/views.py +++ b/server/vbv_lernwelt/feedback/views.py @@ -5,9 +5,9 @@ from rest_framework.decorators import api_view from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response -from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course.permissions import is_course_session_expert from vbv_lernwelt.feedback.models import FeedbackResponse +from vbv_lernwelt.feedback.utils import feedback_users logger = structlog.get_logger(__name__) @@ -58,10 +58,7 @@ def get_feedback_for_circle(request, course_session_id, circle_id): course_session__id=course_session_id, submitted=True, circle_id=circle_id, - # filter out experts that might have submitted just for testing - feedback_user__in=CourseSessionUser.objects.filter( - course_session_id=course_session_id, role=CourseSessionUser.Role.MEMBER - ).values_list("user", flat=True), + feedback_user__in=feedback_users(course_session_id), ).order_by("created_at") # I guess this is ok for the üK case