diff --git a/server/vbv_lernwelt/dashboard/graphql/queries.py b/server/vbv_lernwelt/dashboard/graphql/queries.py index f65acdbf..e8472928 100644 --- a/server/vbv_lernwelt/dashboard/graphql/queries.py +++ b/server/vbv_lernwelt/dashboard/graphql/queries.py @@ -54,7 +54,8 @@ class DashboardQuery(graphene.ObjectType): return None return CourseStatisticsType( - id=course.id, # noqa + _id=course.id, # noqa + course_id=course.id, # noqa course_title=course.title, # noqa course_slug=course.slug, # noqa course_session_selection_ids=list(course_session_ids), # noqa @@ -67,7 +68,8 @@ class DashboardQuery(graphene.ObjectType): courses = Course.objects.all().values("id", "title", "slug") return [ { - "id": c["id"], + "_id": c["id"], + "course_id": c["id"], "name": c["title"], "slug": c["slug"], "dashboard_type": DashboardType.SIMPLE_LIST_DASHBOARD, @@ -134,7 +136,8 @@ class DashboardQuery(graphene.ObjectType): ) return CourseProgressType( - id=course_id, # noqa + _id=course_id, # noqa + course_id=course_id, # noqa session_to_continue_id=newest.id if newest else None, # noqa competence=ProgressDashboardCompetenceType( # noqa _id=course_id, # noqa diff --git a/server/vbv_lernwelt/dashboard/graphql/types/assignment.py b/server/vbv_lernwelt/dashboard/graphql/types/assignment.py index 91a05e89..9d30555b 100644 --- a/server/vbv_lernwelt/dashboard/graphql/types/assignment.py +++ b/server/vbv_lernwelt/dashboard/graphql/types/assignment.py @@ -16,7 +16,8 @@ from vbv_lernwelt.course_session.models import ( ) -class AssignmentCompletionMetrics(graphene.ObjectType): +class AssignmentCompletionMetricsType(graphene.ObjectType): + _id = graphene.ID(required=True) passed_count = graphene.Int(required=True) failed_count = graphene.Int(required=True) unranked_count = graphene.Int(required=True) @@ -24,7 +25,8 @@ class AssignmentCompletionMetrics(graphene.ObjectType): average_passed = graphene.Float(required=True) -class AssignmentRecord(graphene.ObjectType): +class AssignmentStatisticsRecordType(graphene.ObjectType): + _id = graphene.ID(required=True) course_session_id = graphene.ID(required=True) course_session_assignment_id = graphene.ID(required=True) circle_id = graphene.ID(required=True) @@ -32,25 +34,29 @@ class AssignmentRecord(graphene.ObjectType): assignment_type_translation_key = graphene.String(required=True) assignment_title = graphene.String(required=True) deadline = graphene.DateTime(required=True) - metrics = graphene.Field(AssignmentCompletionMetrics, required=True) + metrics = graphene.Field(AssignmentCompletionMetricsType, required=True) details_url = graphene.String(required=True) -class AssignmentSummary(graphene.ObjectType): +class AssignmentStatisticsSummaryType(graphene.ObjectType): + _id = graphene.ID(required=True) completed_count = graphene.Int(required=True) average_passed = graphene.Float(required=True) -class Assignments(graphene.ObjectType): - records = graphene.List(AssignmentRecord, required=True) - summary = graphene.Field(AssignmentSummary, required=True) +class AssignmentsStatisticsType(graphene.ObjectType): + _id = graphene.ID(required=True) + records = graphene.List(AssignmentStatisticsRecordType, required=True) + summary = graphene.Field(AssignmentStatisticsSummaryType, required=True) -def create_assignment_summary(metrics) -> AssignmentSummary: +def create_assignment_summary(course_id, metrics) -> AssignmentStatisticsSummaryType: completed_metrics = [m for m in metrics if m.ranking_completed] if not completed_metrics: - return AssignmentSummary(completed_count=0, average_passed=0) # noqa + return AssignmentStatisticsSummaryType( + _id=course_id, completed_count=0, average_passed=0 # noqa + ) completed_count = len(completed_metrics) @@ -58,14 +64,14 @@ def create_assignment_summary(metrics) -> AssignmentSummary: sum([m.average_passed for m in completed_metrics]) / completed_count ) - return AssignmentSummary( + return AssignmentStatisticsSummaryType( completed_count=completed_count, average_passed=average_passed_completed # noqa ) def get_assignment_completion_metrics( course_session: CourseSession, assignment: vbv_lernwelt.assignment.models.Assignment -) -> AssignmentCompletionMetrics: +) -> AssignmentCompletionMetricsType: course_session_users = CourseSessionUser.objects.filter( course_session=course_session, role=CourseSessionUser.Role.MEMBER, @@ -89,7 +95,8 @@ def get_assignment_completion_metrics( else: average_passed = math.ceil(passed_count / participants_count * 100) - return AssignmentCompletionMetrics( + return AssignmentCompletionMetricsType( + _id=assignment.id, # noqa passed_count=passed_count, # noqa failed_count=failed_count, # noqa unranked_count=unranked_count, # noqa @@ -100,7 +107,7 @@ def get_assignment_completion_metrics( def create_record( course_session_assignment: CourseSessionAssignment | CourseSessionEdoniqTest, -) -> AssignmentRecord: +) -> AssignmentStatisticsRecordType: if isinstance(course_session_assignment, CourseSessionAssignment): due_date = course_session_assignment.submission_deadline else: @@ -108,7 +115,8 @@ def create_record( learning_content = course_session_assignment.learning_content - return AssignmentRecord( + return AssignmentStatisticsRecordType( + _id=str(learning_content.id), # noqa course_session_id=str(course_session_assignment.course_session.id), # noqa circle_id=learning_content.get_circle().id, # noqa course_session_assignment_id=str(course_session_assignment.id), # noqa @@ -125,13 +133,13 @@ def create_record( def assignments( + course_id: graphene.ID(required=True), course_session_selection_ids: graphene.List(graphene.ID), -) -> Assignments: +) -> AssignmentsStatisticsType: course_sessions = CourseSession.objects.filter( id__in=course_session_selection_ids, ) - - records: List[AssignmentRecord] = [] + records: List[AssignmentStatisticsRecordType] = [] for course_session in course_sessions: for csa in CourseSessionAssignment.objects.filter( @@ -141,16 +149,19 @@ def assignments( AssignmentType.PREP_ASSIGNMENT.value, ], ): - record = create_record(csa) + record = create_record(course_session_assignment=csa) records.append(record) for cset in CourseSessionEdoniqTest.objects.filter( course_session=course_session ): - record = create_record(cset) + record = create_record(course_session_assignment=cset) records.append(record) - return Assignments( + return AssignmentsStatisticsType( + _id=course_id, # noqa records=sorted(records, key=lambda r: r.deadline), # noqa - summary=create_assignment_summary([r.metrics for r in records]), # noqa + summary=create_assignment_summary( # noqa + course_id=course_id, metrics=[r.metrics for r in records] # noqa + ), ) diff --git a/server/vbv_lernwelt/dashboard/graphql/types/attendance.py b/server/vbv_lernwelt/dashboard/graphql/types/attendance.py index 032b4f2f..82ef2304 100644 --- a/server/vbv_lernwelt/dashboard/graphql/types/attendance.py +++ b/server/vbv_lernwelt/dashboard/graphql/types/attendance.py @@ -10,12 +10,14 @@ from vbv_lernwelt.course_session.services.attendance import AttendanceUserStatus from vbv_lernwelt.notify.email.email_services import format_swiss_datetime -class AttendanceSummary(graphene.ObjectType): +class AttendanceSummaryStatisticsType(graphene.ObjectType): + _id = graphene.ID(required=True) days_completed = graphene.Int(required=True) participants_present = graphene.Int(required=True) -class PresenceRecord(graphene.ObjectType): +class PresenceRecordStatisticsType(graphene.ObjectType): + _id = graphene.ID(required=True) course_session_id = graphene.ID(required=True) generation = graphene.String(required=True) circle_id = graphene.ID(required=True) @@ -26,11 +28,12 @@ class PresenceRecord(graphene.ObjectType): class AttendanceDayPresences(graphene.ObjectType): - records = graphene.List(PresenceRecord, required=True) - summary = graphene.Field(AttendanceSummary, required=True) + records = graphene.List(PresenceRecordStatisticsType, required=True) + summary = graphene.Field(AttendanceSummaryStatisticsType, required=True) def attendance_day_presences( + course_id: graphene.ID, course_session_selection_ids: graphene.List(graphene.ID), ) -> AttendanceDayPresences: completed = CourseSessionAttendanceCourse.objects.filter( @@ -60,7 +63,8 @@ def attendance_day_presences( ) records.append( - PresenceRecord( + PresenceRecordStatisticsType( + _id=attendance_day.learning_content.id, # noqa course_session_id=course_session.id, # noqa generation=course_session.generation, # noqa circle_id=circle.id, # noqa @@ -71,7 +75,8 @@ def attendance_day_presences( ) ) - summary = AttendanceSummary( + summary = AttendanceSummaryStatisticsType( + _id=course_id, # noqa days_completed=completed.count(), # noqa participants_present=calculate_avg_participation(records), # noqa ) @@ -79,7 +84,7 @@ def attendance_day_presences( return AttendanceDayPresences(summary=summary, records=records) # noqa -def calculate_avg_participation(records: List[PresenceRecord]) -> float: +def calculate_avg_participation(records: List[PresenceRecordStatisticsType]) -> float: if not records: return 0.0 diff --git a/server/vbv_lernwelt/dashboard/graphql/types/competence.py b/server/vbv_lernwelt/dashboard/graphql/types/competence.py index 80c21465..07c182e6 100644 --- a/server/vbv_lernwelt/dashboard/graphql/types/competence.py +++ b/server/vbv_lernwelt/dashboard/graphql/types/competence.py @@ -5,12 +5,14 @@ import graphene from vbv_lernwelt.course.models import CourseCompletion, CourseCompletionStatus -class CompletionSummary(graphene.ObjectType): +class CompetencePerformanceStatisticsSummaryType(graphene.ObjectType): + _id = graphene.ID(required=True) success_total = graphene.Int(required=True) fail_total = graphene.Int(required=True) -class CompetencePerformance(graphene.ObjectType): +class CompetencePerformanceStatisticsType(graphene.ObjectType): + _id = graphene.ID(required=True) course_session_id = graphene.ID(required=True) generation = graphene.String(required=True) circle_id = graphene.ID(required=True) @@ -19,16 +21,17 @@ class CompetencePerformance(graphene.ObjectType): details_url = graphene.String(required=True) -class Competences(graphene.ObjectType): - performances = graphene.List(CompetencePerformance, required=True) - summary = graphene.Field(CompletionSummary, required=True) +class CompetencesStatisticsType(graphene.ObjectType): + _id = graphene.ID(required=True) + summary = graphene.Field(CompetencePerformanceStatisticsSummaryType, required=True) + performances = graphene.List(CompetencePerformanceStatisticsType, required=True) def competences( course_session_selection_ids: List[str], course_slug: str, user_selection_ids: List[str] | None = None, -) -> Tuple[List[CompetencePerformance], int, int]: +) -> Tuple[List[CompetencePerformanceStatisticsType], int, int]: completions = CourseCompletion.objects.filter( course_session_id__in=course_session_selection_ids, page_type="competence.PerformanceCriteria", @@ -53,7 +56,8 @@ def competences( if circle.id not in competence_performances: details_url = f"/course/{course_slug}/cockpit?courseSessionId={completion.course_session.id}" - competence_performances[circle.id] = CompetencePerformance( + competence_performances[circle.id] = CompetencePerformanceStatisticsType( + _id=circle.id, # noqa course_session_id=completion.course_session.id, # noqa generation=completion.course_session.generation, # noqa circle_id=circle.id, # noqa diff --git a/server/vbv_lernwelt/dashboard/graphql/types/dashboard.py b/server/vbv_lernwelt/dashboard/graphql/types/dashboard.py index 777b143c..76c379d6 100644 --- a/server/vbv_lernwelt/dashboard/graphql/types/dashboard.py +++ b/server/vbv_lernwelt/dashboard/graphql/types/dashboard.py @@ -2,15 +2,18 @@ import graphene from graphene import Enum from vbv_lernwelt.course.models import CourseSession, CourseSessionUser -from vbv_lernwelt.dashboard.graphql.types.assignment import Assignments, assignments +from vbv_lernwelt.dashboard.graphql.types.assignment import ( + assignments, + AssignmentsStatisticsType, +) from vbv_lernwelt.dashboard.graphql.types.attendance import ( attendance_day_presences, AttendanceDayPresences, ) from vbv_lernwelt.dashboard.graphql.types.competence import ( + CompetencePerformanceStatisticsSummaryType, competences, - Competences, - CompletionSummary, + CompetencesStatisticsType, ) from vbv_lernwelt.dashboard.graphql.types.feedback import ( feedback_responses, @@ -19,27 +22,31 @@ from vbv_lernwelt.dashboard.graphql.types.feedback import ( from vbv_lernwelt.learnpath.models import Circle -class CourseSessionData(graphene.ObjectType): +class StatisticsCourseSessionDataType(graphene.ObjectType): + _id = graphene.ID(required=True) session_id = graphene.ID(required=True) session_title = graphene.String(required=True) -class CircleData(graphene.ObjectType): +class StatisticsCircleDataType(graphene.ObjectType): + _id = graphene.ID(required=True) circle_id = graphene.ID(required=True) circle_title = graphene.String(required=True) experts = graphene.List(graphene.String, required=True) -class CourseSessionsSelectionMetrics(graphene.ObjectType): +class StatisticsCourseSessionsSelectionMetricType(graphene.ObjectType): + _id = graphene.ID(required=True) session_count = graphene.Int(required=True) participant_count = graphene.Int(required=True) expert_count = graphene.Int(required=True) -class CourseSessionProperties(graphene.ObjectType): - sessions = graphene.List(CourseSessionData, required=True) +class StatisticsCourseSessionPropertiesType(graphene.ObjectType): + _id = graphene.ID(required=True) + sessions = graphene.List(StatisticsCourseSessionDataType, required=True) generations = graphene.List(graphene.String, required=True) - circles = graphene.List(CircleData, required=True) + circles = graphene.List(StatisticsCircleDataType, required=True) class DashboardType(Enum): @@ -49,7 +56,7 @@ class DashboardType(Enum): class DashboardConfigType(graphene.ObjectType): - id = graphene.ID(required=True) # course_id, named id for urql + id = graphene.ID(required=True) name = graphene.String(required=True) slug = graphene.String(required=True) dashboard_type = graphene.Field(DashboardType, required=True) @@ -70,29 +77,34 @@ class ProgressDashboardAssignmentType(graphene.ObjectType): class CourseProgressType(graphene.ObjectType): - id = graphene.ID(required=True) # course_id, named id for urql + _id = graphene.ID(required=True) + course_id = graphene.ID(required=True) session_to_continue_id = graphene.ID(required=False) competence = graphene.Field(ProgressDashboardCompetenceType, required=True) assignment = graphene.Field(ProgressDashboardAssignmentType, required=True) class CourseStatisticsType(graphene.ObjectType): - id = graphene.ID(required=True) # course_id, named id for urql + _id = graphene.ID(required=True) + course_id = graphene.ID(required=True) course_title = graphene.String(required=True) course_slug = graphene.String(required=True) - course_session_properties = graphene.Field(CourseSessionProperties, required=True) + course_session_properties = graphene.Field( + StatisticsCourseSessionPropertiesType, required=True + ) course_session_selection_ids = graphene.List(graphene.ID, required=True) course_session_selection_metrics = graphene.Field( - CourseSessionsSelectionMetrics, required=True + StatisticsCourseSessionsSelectionMetricType, required=True ) attendance_day_presences = graphene.Field(AttendanceDayPresences, required=True) feedback_responses = graphene.Field(FeedbackResponses, required=True) - assignments = graphene.Field(Assignments, required=True) - competences = graphene.Field(Competences, required=True) + assignments = graphene.Field(AssignmentsStatisticsType, required=True) + competences = graphene.Field(CompetencesStatisticsType, required=True) def resolve_attendance_day_presences(root, info) -> AttendanceDayPresences: return attendance_day_presences( - course_session_selection_ids=root.course_session_selection_ids + course_id=root.course_id, + course_session_selection_ids=root.course_session_selection_ids, ) def resolve_feedback_responses(root, info) -> FeedbackResponses: @@ -101,46 +113,51 @@ class CourseStatisticsType(graphene.ObjectType): course_slug=root.course_slug, ) - def resolve_competences(root, info) -> Competences: + def resolve_competences(root, info) -> CompetencesStatisticsType: performances, success_total, fail_total = competences( course_slug=str(root.course_slug), course_session_selection_ids=[ str(cs) for cs in root.course_session_selection_ids # noqa ], ) - return Competences( + return CompetencesStatisticsType( + _id=root._id, # noqa performances=performances, # noqa - summary=CompletionSummary( # noqa - success_total=success_total, fail_total=fail_total # noqa + summary=CompetencePerformanceStatisticsSummaryType( # noqa + _id=root._id, # noqa + success_total=success_total, # noqa + fail_total=fail_total, # noqa ), ) - def resolve_assignments(root, info) -> Assignments: + def resolve_assignments(root, info) -> AssignmentsStatisticsType: return assignments( - course_session_selection_ids=root.course_session_selection_ids + course_id=root.course_id, + course_session_selection_ids=root.course_session_selection_ids, ) def resolve_course_session_selection_metrics( root, info - ) -> CourseSessionsSelectionMetrics: + ) -> StatisticsCourseSessionsSelectionMetricType: course_session_count = CourseSession.objects.filter( id__in=root.course_session_selection_ids, - course_id=root.id, + course_id=root.course_id, ).count() expert_count = CourseSession.objects.filter( id__in=root.course_session_selection_ids, - course_id=root.id, + course_id=root.course_id, coursesessionuser__role=CourseSessionUser.Role.EXPERT, ).count() participant_count = CourseSession.objects.filter( id__in=root.course_session_selection_ids, - course_id=root.id, + course_id=root.course_id, coursesessionuser__role=CourseSessionUser.Role.MEMBER, ).count() - return CourseSessionsSelectionMetrics( + return StatisticsCourseSessionsSelectionMetricType( + _id=root._id, # noqa session_count=course_session_count, # noqa participant_count=participant_count, # noqa expert_count=expert_count, # noqa @@ -153,12 +170,13 @@ class CourseStatisticsType(graphene.ObjectType): course_sessions = CourseSession.objects.filter( id__in=root.course_session_selection_ids, - course_id=root.id, + course_id=root.course_id, ) for course_session in course_sessions: course_session_data.append( - CourseSessionData( + StatisticsCourseSessionDataType( + _id=course_session.id, # noqa session_id=course_session.id, # noqa session_title=course_session.title, # noqa ) @@ -180,7 +198,8 @@ class CourseStatisticsType(graphene.ObjectType): for circle in circles: circle_data.append( - CircleData( + StatisticsCircleDataType( + _id=circle.id, # noqa circle_id=circle.id, # noqa circle_title=circle.title, # noqa experts=[ # noqa @@ -190,7 +209,8 @@ class CourseStatisticsType(graphene.ObjectType): ) ) - return CourseSessionProperties( + return StatisticsCourseSessionPropertiesType( + _id=root.id, # noqa sessions=course_session_data, # noqa generations=list(generations), # noqa circles=circle_data, # noqa diff --git a/server/vbv_lernwelt/dashboard/tests/graphql/test_competence.py b/server/vbv_lernwelt/dashboard/tests/graphql/test_competence.py index 8a3af5da..169c2025 100644 --- a/server/vbv_lernwelt/dashboard/tests/graphql/test_competence.py +++ b/server/vbv_lernwelt/dashboard/tests/graphql/test_competence.py @@ -64,7 +64,6 @@ class DashboardCompetenceTestCase(GraphQLTestCase): query = f"""query($course_id: ID!) {{ course_statistics(course_id: $course_id) {{ - id competences {{ performances {{ course_session_id diff --git a/server/vbv_lernwelt/dashboard/tests/graphql/test_dashboard.py b/server/vbv_lernwelt/dashboard/tests/graphql/test_dashboard.py index 6950023f..5629502d 100644 --- a/server/vbv_lernwelt/dashboard/tests/graphql/test_dashboard.py +++ b/server/vbv_lernwelt/dashboard/tests/graphql/test_dashboard.py @@ -69,7 +69,7 @@ class DashboardTestCase(GraphQLTestCase): query = f"""query($course_id: ID!) {{ course_progress(course_id: $course_id) {{ - id + course_id session_to_continue_id competence {{ total_count @@ -95,7 +95,7 @@ class DashboardTestCase(GraphQLTestCase): course_progress = response.json()["data"]["course_progress"] - self.assertEqual(course_progress["id"], str(course.id)) + self.assertEqual(course_progress["course_id"], str(course.id)) self.assertEqual(course_progress["session_to_continue_id"], str(cs_2.id)) competence = course_progress["competence"] @@ -195,7 +195,7 @@ class DashboardTestCase(GraphQLTestCase): query = f"""query($course_id: ID!) {{ course_statistics(course_id: $course_id) {{ - id + course_id }} }} """ @@ -229,7 +229,7 @@ class DashboardTestCase(GraphQLTestCase): query = f"""query($course_id: ID!) {{ course_statistics(course_id: $course_id) {{ - id + course_id course_title course_slug }} @@ -245,7 +245,7 @@ class DashboardTestCase(GraphQLTestCase): course_statistics = response.json()["data"]["course_statistics"] - self.assertEqual(course_statistics["id"], str(course_2.id)) + self.assertEqual(course_statistics["course_id"], str(course_2.id)) self.assertEqual(course_statistics["course_title"], course_2.title) self.assertEqual(course_statistics["course_slug"], course_2.slug) diff --git a/server/vbv_lernwelt/dashboard/tests/graphql/test_feedback.py b/server/vbv_lernwelt/dashboard/tests/graphql/test_feedback.py index 4db7b77f..824f1772 100644 --- a/server/vbv_lernwelt/dashboard/tests/graphql/test_feedback.py +++ b/server/vbv_lernwelt/dashboard/tests/graphql/test_feedback.py @@ -71,7 +71,6 @@ class DashboardFeedbackTestCase(GraphQLTestCase): query = f"""query($course_id: ID!) {{ course_statistics(course_id: $course_id) {{ - id feedback_responses {{ records {{ course_session_id