feat: competence records

This commit is contained in:
Reto Aebersold 2023-10-30 12:17:31 +01:00
parent 37b2042e7f
commit 17b2d03245
10 changed files with 100 additions and 31 deletions

View File

@ -23,7 +23,7 @@ const documents = {
"\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n": types.CourseQueryDocument,
"\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n }\n }\n": types.DashboardConfigDocument,
"\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n": types.DashboardProgressDocument,
"\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n experts\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n performances {\n _id\n course_session_id\n generation\n circle_id\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n": types.CourseStatisticsDocument,
"\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n experts\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n": types.CourseStatisticsDocument,
"\n mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n data: $data\n submitted: $submitted\n ) {\n feedback_response {\n id\n data\n submitted\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument,
};
@ -84,7 +84,7 @@ export function graphql(source: "\n query dashboardProgress($courseId: ID!) {\n
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n experts\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n performances {\n _id\n course_session_id\n generation\n circle_id\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n"): (typeof documents)["\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n experts\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n performances {\n _id\n course_session_id\n generation\n circle_id\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n"];
export function graphql(source: "\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n experts\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n"): (typeof documents)["\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n experts\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/

File diff suppressed because one or more lines are too long

View File

@ -151,7 +151,7 @@ type AssignmentStatisticsSummaryType {
type CompetencesStatisticsType {
_id: ID!
summary: CompetencePerformanceStatisticsSummaryType!
performances: [CompetencePerformanceStatisticsType]!
records: [CompetenceRecordStatisticsType!]!
}
type CompetencePerformanceStatisticsSummaryType {
@ -160,7 +160,7 @@ type CompetencePerformanceStatisticsSummaryType {
fail_total: Int!
}
type CompetencePerformanceStatisticsType {
type CompetenceRecordStatisticsType {
_id: ID!
course_session_id: ID!
generation: String!

View File

@ -21,7 +21,7 @@ export const CircleObjectType = "CircleObjectType";
export const CompetenceCertificateListObjectType = "CompetenceCertificateListObjectType";
export const CompetenceCertificateObjectType = "CompetenceCertificateObjectType";
export const CompetencePerformanceStatisticsSummaryType = "CompetencePerformanceStatisticsSummaryType";
export const CompetencePerformanceStatisticsType = "CompetencePerformanceStatisticsType";
export const CompetenceRecordStatisticsType = "CompetenceRecordStatisticsType";
export const CompetencesStatisticsType = "CompetencesStatisticsType";
export const CoreUserLanguageChoices = "CoreUserLanguageChoices";
export const CourseObjectType = "CourseObjectType";

View File

@ -399,7 +399,7 @@ export const DASHBOARD_COURSE_STATISTICS = graphql(`
success_total
fail_total
}
performances {
records {
_id
course_session_id
generation

View File

@ -0,0 +1,67 @@
<script setup lang="ts">
import { useDashboardStore } from "@/stores/dashboard";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import { computed } from "vue";
import type {
CompetenceRecordStatisticsType,
CourseStatisticsType,
} from "@/gql/graphql";
import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue";
import { useCourseStatistics } from "@/composables";
const dashboardStore = useDashboardStore();
const statistics = computed(() => {
return dashboardStore.currentDashBoardData as CourseStatisticsType;
});
const { courseSessionName, circleMeta } = useCourseStatistics();
</script>
<template>
<main v-if="statistics">
<div class="mb-10 flex items-center justify-between">
<h3>{{ $t("a.Selbsteinschätzung") }}</h3>
<ItDropdownSelect
:model-value="dashboardStore.currentDashboardConfig"
class="mt-4 w-full lg:mt-0 lg:w-96"
:items="dashboardStore.dashboardConfigs"
@update:model-value="dashboardStore.switchAndLoadDashboardConfig"
></ItDropdownSelect>
</div>
<div v-if="statistics.competences.records" class="mt-8 bg-white">
<StatisticFilterList
:course-session-properties="statistics.course_session_properties"
:items="statistics.competences.records"
>
<template #default="{ item }">
<div class="flex justify-between">
<div>
<h4 class="font-bold">{{ $t("a.Selbsteinschätzung") }}: TODO</h4>
<div>
Durchführung «{{ courseSessionName(item.course_session_id) }}» - Circle
«{{ circleMeta(item.circle_id)?.name }}»
</div>
</div>
<div>
<div class="mb-4 flex items-center space-x-2">
<it-icon-smiley-happy class="h-8 w-8"></it-icon-smiley-happy>
<span class="pr-3">
{{ (item as CompetenceRecordStatisticsType).success_count }}
</span>
<it-icon-smiley-thinking class="h-8 w-8"></it-icon-smiley-thinking>
<span>{{ (item as CompetenceRecordStatisticsType).fail_count }}</span>
</div>
<router-link
class="whitespace-nowrap underline"
:to="(item as CompetenceRecordStatisticsType).details_url"
>
{{ $t("a.Details anschauen") }}
</router-link>
</div>
</div>
</template>
</StatisticFilterList>
</div>
</main>
</template>

View File

@ -191,7 +191,7 @@ const router = createRouter({
{
path: "competence",
props: true,
component: () => import("@/pages/dashboard/statistic/AttendanceList.vue"),
component: () => import("@/pages/dashboard/statistic/CompetenceList.vue"),
},
{
path: "feedback",

View File

@ -11,7 +11,7 @@ class CompetencePerformanceStatisticsSummaryType(graphene.ObjectType):
fail_total = graphene.Int(required=True)
class CompetencePerformanceStatisticsType(graphene.ObjectType):
class CompetenceRecordStatisticsType(graphene.ObjectType):
_id = graphene.ID(required=True)
course_session_id = graphene.ID(required=True)
generation = graphene.String(required=True)
@ -24,14 +24,16 @@ class CompetencePerformanceStatisticsType(graphene.ObjectType):
class CompetencesStatisticsType(graphene.ObjectType):
_id = graphene.ID(required=True)
summary = graphene.Field(CompetencePerformanceStatisticsSummaryType, required=True)
performances = graphene.List(CompetencePerformanceStatisticsType, required=True)
records = graphene.List(
graphene.NonNull(CompetenceRecordStatisticsType), required=True
)
def competences(
course_session_selection_ids: List[str],
course_slug: str,
user_selection_ids: List[str] | None = None,
) -> Tuple[List[CompetencePerformanceStatisticsType], int, int]:
) -> Tuple[List[CompetenceRecordStatisticsType], int, int]:
completions = CourseCompletion.objects.filter(
course_session_id__in=course_session_selection_ids,
page_type="competence.PerformanceCriteria",
@ -40,7 +42,7 @@ def competences(
if user_selection_ids is not None:
completions = completions.filter(user_id__in=user_selection_ids)
competence_performances = {}
competence_records = {}
# purely for performance reasons, since looking up
# the circle for each completion is expensive :-/
@ -54,9 +56,9 @@ def competences(
circle = circle_cache[completion.page.id]
if circle.id not in competence_performances:
if circle.id not in competence_records:
details_url = f"/course/{course_slug}/cockpit?courseSessionId={completion.course_session.id}"
competence_performances[circle.id] = CompetencePerformanceStatisticsType(
competence_records[circle.id] = CompetenceRecordStatisticsType(
_id=circle.id, # noqa
course_session_id=completion.course_session.id, # noqa
generation=completion.course_session.generation, # noqa
@ -66,11 +68,11 @@ def competences(
details_url=details_url, # noqa
)
if completion.completion_status == CourseCompletionStatus.SUCCESS.value:
competence_performances[circle.id].success_count += 1
competence_records[circle.id].success_count += 1
elif completion.completion_status == CourseCompletionStatus.FAIL.value:
competence_performances[circle.id].fail_count += 1
competence_records[circle.id].fail_count += 1
values = list(competence_performances.values())
values = list(competence_records.values())
success_count = sum([c.success_count for c in values])
fail_count = sum([c.fail_count for c in values])

View File

@ -119,7 +119,7 @@ class CourseStatisticsType(graphene.ObjectType):
)
def resolve_competences(root, info) -> CompetencesStatisticsType:
performances, success_total, fail_total = competences(
records, 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
@ -127,7 +127,7 @@ class CourseStatisticsType(graphene.ObjectType):
)
return CompetencesStatisticsType(
_id=root._id, # noqa
performances=performances, # noqa
records=records, # noqa
summary=CompetencePerformanceStatisticsSummaryType( # noqa
_id=root._id, # noqa
success_total=success_total, # noqa

View File

@ -65,7 +65,7 @@ class DashboardCompetenceTestCase(GraphQLTestCase):
query = f"""query($course_id: ID!) {{
course_statistics(course_id: $course_id) {{
competences {{
performances {{
records {{
course_session_id
generation
circle_id
@ -90,15 +90,15 @@ class DashboardCompetenceTestCase(GraphQLTestCase):
self.assertResponseNoErrors(response)
competences = response.json()["data"]["course_statistics"]["competences"]
performances = competences["performances"]
records = competences["records"]
self.assertEqual(performances[0]["success_count"], 1)
self.assertEqual(performances[0]["fail_count"], 1)
self.assertEqual(performances[0]["circle_id"], str(circle.id))
self.assertEqual(performances[0]["course_session_id"], str(course_session.id))
self.assertEqual(performances[0]["generation"], "2023")
self.assertEqual(records[0]["success_count"], 1)
self.assertEqual(records[0]["fail_count"], 1)
self.assertEqual(records[0]["circle_id"], str(circle.id))
self.assertEqual(records[0]["course_session_id"], str(course_session.id))
self.assertEqual(records[0]["generation"], "2023")
self.assertEqual(
performances[0]["details_url"],
records[0]["details_url"],
f"/course/{course.slug}/cockpit?courseSessionId={course_session.id}",
)