Add `UK_BERUFSBILDNER_STATISTICS_WIDGET`

This commit is contained in:
Daniel Egger 2024-07-22 10:44:10 +02:00
parent 0581f3d820
commit 40ff65ad2d
10 changed files with 205 additions and 98 deletions

View File

@ -0,0 +1,49 @@
<script setup lang="ts">
import { computed, onMounted, type Ref, ref } from "vue";
import {
type DashboardRoleKeyType,
fetchMentorCompetenceSummary,
} from "@/services/dashboard";
import type { AssignmentsStatisticsType, BaseStatisticsType } from "@/gql/graphql";
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
import AssignmentSummaryBox from "@/components/dashboard/AssignmentSummaryBox.vue";
const props = defineProps<{
courseSlug: string;
courseId: string;
agentRole: DashboardRoleKeyType;
}>();
const mentorData = ref<BaseStatisticsType | null>(null);
const loading = ref(true);
const assignmentStats = computed(() => {
return mentorData.value?.assignments ?? null;
});
onMounted(async () => {
mentorData.value = await fetchMentorCompetenceSummary(
props.courseId,
props.agentRole
);
loading.value = false;
});
</script>
<template>
<div v-if="loading" class="m-8 flex justify-center">
<LoadingSpinner />
</div>
<div v-if="assignmentStats" class="space-y-8">
<div
class="flex flex-col flex-wrap justify-between gap-x-5 border-b border-gray-300 pb-8 last:border-0 md:flex-row"
>
<AssignmentSummaryBox
class="flex-grow"
:assignments-completed="assignmentStats.summary.completed_count"
:avg-passed="assignmentStats.summary.average_passed"
:course-slug="props.courseSlug"
/>
</div>
</div>
</template>

View File

@ -9,6 +9,7 @@ import MentorMenteeCount from "@/components/dashboard/MentorMenteeCount.vue";
import MentorCompetenceSummary from "@/components/dashboard/MentorCompetenceSummary.vue";
import { getCockpitUrl, getLearningMentorUrl, getLearningPathUrl } from "@/utils/utils";
import UkStatistics from "@/components/dashboard/UkStatistics.vue";
import BerufsbildnerStatistics from "@/components/dashboard/BerufsbildnerStatistics.vue";
const mentorWidgets = [
"MentorTasksWidget",
@ -112,6 +113,7 @@ function hasActionButton(): boolean {
</router-link>
</p>
</div>
<div
v-if="
hasWidget('ProgressWidget') &&
@ -127,6 +129,7 @@ function hasActionButton(): boolean {
diagram-type="horizontal"
></LearningPathDiagram>
</div>
<div
v-if="numberOfProgressWidgets"
class="flex flex-col flex-wrap gap-x-[60px] border-b border-gray-300 pb-8 last:border-0 md:flex-row"
@ -144,12 +147,25 @@ function hasActionButton(): boolean {
:course-id="courseConfig.course_id"
></CompetenceSummary>
</div>
<div
v-if="hasWidget('UKStatisticsWidget')"
class="flex flex-col flex-wrap gap-x-[60px] border-b border-gray-300 pb-8 last:border-0 md:flex-row"
>
<UkStatistics :course-slug="courseSlug" :course-id="courseConfig.course_id" />
</div>
<div
v-if="hasWidget('UKBerufsbildnerStatisticsWidget')"
class="flex flex-col flex-wrap gap-x-[60px] border-b border-gray-300 pb-8 last:border-0 md:flex-row"
>
<BerufsbildnerStatistics
:course-slug="courseSlug"
:course-id="courseConfig.course_id"
:agent-role="courseConfig.role_key"
/>
</div>
<div
v-if="numberOfMentorWidgets > 0"
class="flex flex-col flex-wrap items-stretch md:flex-row"

View File

@ -1,11 +1,10 @@
<script setup lang="ts">
import type { Ref } from "vue";
import { onMounted, ref } from "vue";
import { computed, onMounted, ref } from "vue";
import {
type DashboardRoleKeyType,
fetchMentorCompetenceSummary,
} from "@/services/dashboard";
import type { AssignmentsStatisticsType } from "@/gql/graphql";
import type { BaseStatisticsType } from "@/gql/graphql";
import BaseBox from "@/components/dashboard/BaseBox.vue";
const props = defineProps<{
@ -13,11 +12,17 @@ const props = defineProps<{
agentRole: DashboardRoleKeyType;
}>();
const summary: Ref<AssignmentsStatisticsType | null> = ref(null);
const mentorAssignmentData = ref<BaseStatisticsType | null>(null);
const summary = computed(() => {
return mentorAssignmentData.value?.assignments ?? null;
});
onMounted(async () => {
summary.value = await fetchMentorCompetenceSummary(props.courseId, props.agentRole);
console.log(summary.value);
mentorAssignmentData.value = await fetchMentorCompetenceSummary(
props.courseId,
props.agentRole
);
});
</script>

View File

@ -250,6 +250,7 @@ export type BaseStatisticsType = {
_id: Scalars['ID']['output'];
assignments: AssignmentsStatisticsType;
course_id: Scalars['ID']['output'];
course_session_properties: StatisticsCourseSessionPropertiesType;
course_session_selection_ids: Array<Maybe<Scalars['ID']['output']>>;
course_slug: Scalars['String']['output'];
course_title: Scalars['String']['output'];

View File

@ -189,6 +189,7 @@ type BaseStatisticsType {
course_session_selection_ids: [ID]!
user_selection_ids: [ID]
assignments: AssignmentsStatisticsType!
course_session_properties: StatisticsCourseSessionPropertiesType!
}
type CourseProgressType {

View File

@ -0,0 +1,30 @@
<script setup lang="ts">
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
import { useCourseStatisticsv2 } from "@/composables";
const props = defineProps<{
courseSlug: string;
}>();
const { courseStatistics, loading, courseSessionName, circleMeta } =
useCourseStatisticsv2(props.courseSlug);
</script>
<template>
<div class="bg-gray-200">
<div v-if="loading" class="m-8 flex justify-center">
<LoadingSpinner />
</div>
<div v-else class="container-large flex flex-col space-y-8">
<router-link class="btn-text inline-flex items-center pl-0" to="/">
<it-icon-arrow-left />
<span>{{ $t("general.back") }}</span>
</router-link>
<router-view
:course-statistics="courseStatistics"
:course-session-name="courseSessionName"
:circle-meta="circleMeta"
></router-view>
</div>
</div>
</template>

View File

@ -8,10 +8,10 @@ import {
import { itGetCached, itPost } from "@/fetchHelpers";
import type {
AssignmentsStatisticsType,
AssignmentsStatisticsType, BaseStatisticsType,
CourseProgressType,
CourseStatisticsType,
DashboardConfigType,
DashboardConfigType
} from "@/gql/graphql";
import type {
DashboardPersonsPageMode,
@ -42,7 +42,8 @@ export type WidgetType =
| "MentorPersonWidget"
| "MentorCompetenceWidget"
| "CompetenceCertificateWidget"
| "UKStatisticsWidget";
| "UKStatisticsWidget"
| "UKBerufsbildnerStatisticsWidget";
export type DashboardPersonCourseSessionType = {
id: string;
@ -152,7 +153,7 @@ export const fetchDashboardConfig = async (): Promise<DashboardConfigType[] | nu
export const fetchMentorCompetenceSummary = async (
courseId: string,
roleKey: DashboardRoleKeyType
): Promise<AssignmentsStatisticsType | null> => {
): Promise<BaseStatisticsType | null> => {
let agentRole = "";
if (["MentorUK", "MentorVV"].includes(roleKey)) {
agentRole = "LEARNING_MENTOR";
@ -174,7 +175,7 @@ export const fetchMentorCompetenceSummary = async (
if (res.error) {
console.error("Error fetching data for course ID:", courseId, res.error);
}
return res.data?.mentor_course_statistics?.assignments || null;
return res.data?.mentor_course_statistics || null;
} catch (error) {
console.error(`Error fetching data for course ID: ${courseId}`, error);
return null;

View File

@ -33,6 +33,7 @@ def main():
for csu in (
CourseSessionUser.objects.filter(user__username__contains="@mobi")
.filter(course_session__course__configuration__is_uk=True)
.filter(role=CourseSessionUser.Role.MEMBER.value)
.exclude(course_session_id__in=[4, 5, 6])
):
AgentParticipantRelation.objects.get_or_create(

View File

@ -96,6 +96,10 @@ class BaseStatisticsType(graphene.ObjectType):
user_selection_ids = graphene.List(graphene.ID, required=False)
assignments = graphene.Field(AssignmentsStatisticsType, required=True)
course_session_properties = graphene.Field(
StatisticsCourseSessionPropertiesType, required=True
)
def resolve_assignments(root, _info) -> AssignmentsStatisticsType:
user_selection_ids = (
[str(user) for user in root.user_selection_ids]
@ -110,92 +114,6 @@ class BaseStatisticsType(graphene.ObjectType):
urql_id=str(root._id),
)
def get_circle_ids(self, info):
return getattr(info.context, "circle_ids", None)
class CourseStatisticsType(BaseStatisticsType):
course_session_properties = graphene.Field(
StatisticsCourseSessionPropertiesType, required=True
)
course_session_selection_metrics = graphene.Field(
StatisticsCourseSessionsSelectionMetricType, required=True
)
attendance_day_presences = graphene.Field(
AttendanceDayPresencesStatisticsType, required=True
)
feedback_responses = graphene.Field(FeedbackStatisticsResponsesType, required=True)
competences = graphene.Field(CompetencesStatisticsType, required=True)
def resolve_attendance_day_presences(
root, info
) -> AttendanceDayPresencesStatisticsType:
return attendance_day_presences(
course_id=root.course_id,
course_session_selection_ids=root.course_session_selection_ids,
circle_ids=root.get_circle_ids(info),
urql_id=str(root._id),
)
def resolve_feedback_responses(root, info) -> FeedbackStatisticsResponsesType:
return feedback_responses(
course_session_selection_ids=root.course_session_selection_ids,
course_id=root.course_id,
course_slug=root.course_slug,
circle_ids=root.get_circle_ids(info),
urql_id=str(root._id),
)
def resolve_competences(root, info) -> CompetencesStatisticsType:
user_selection_ids = (
[str(user) for user in root.user_selection_ids]
if root.user_selection_ids
else None
) # noqa
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
],
user_selection_ids=user_selection_ids, # noqa
circle_ids=root.get_circle_ids(info), # noqa
urql_id_postfix=str(root._id), # noqa
)
return CompetencesStatisticsType(
_id=root._id, # noqa
records=records, # noqa
summary=CompetencePerformanceStatisticsSummaryType( # noqa
_id=root._id, # noqa
success_total=success_total, # noqa
fail_total=fail_total, # noqa
),
)
def resolve_course_session_selection_metrics(
root, info
) -> StatisticsCourseSessionsSelectionMetricType:
course_session_count = CourseSession.objects.filter(
id__in=root.course_session_selection_ids,
course_id=root.course_id,
).count()
expert_count = CourseSessionUser.objects.filter(
course_session_id__in=root.course_session_selection_ids,
role=CourseSessionUser.Role.EXPERT,
).count()
participant_count = CourseSessionUser.objects.filter(
course_session_id__in=root.course_session_selection_ids,
role=CourseSessionUser.Role.MEMBER,
).count()
return StatisticsCourseSessionsSelectionMetricType(
_id=root._id, # noqa
session_count=course_session_count, # noqa
participant_count=participant_count, # noqa
expert_count=expert_count, # noqa
)
def resolve_course_session_properties(root, info):
course_session_data = []
circle_data = []
@ -238,3 +156,87 @@ class CourseStatisticsType(BaseStatisticsType):
generations=list(generations), # noqa
circles=circle_data, # noqa
)
def get_circle_ids(self, info):
return getattr(info.context, "circle_ids", None)
class CourseStatisticsType(BaseStatisticsType):
course_session_selection_metrics = graphene.Field(
StatisticsCourseSessionsSelectionMetricType, required=True
)
attendance_day_presences = graphene.Field(
AttendanceDayPresencesStatisticsType, required=True
)
feedback_responses = graphene.Field(FeedbackStatisticsResponsesType, required=True)
competences = graphene.Field(CompetencesStatisticsType, required=True)
def resolve_attendance_day_presences(
root, info
) -> AttendanceDayPresencesStatisticsType:
return attendance_day_presences(
course_id=root.course_id,
course_session_selection_ids=root.course_session_selection_ids,
circle_ids=root.get_circle_ids(info),
urql_id=str(root._id),
)
def resolve_feedback_responses(root, info) -> FeedbackStatisticsResponsesType:
return feedback_responses(
course_session_selection_ids=root.course_session_selection_ids,
course_id=root.course_id,
course_slug=root.course_slug,
circle_ids=root.get_circle_ids(info),
urql_id=str(root._id),
)
def resolve_competences(root, info) -> CompetencesStatisticsType:
user_selection_ids = (
[str(user) for user in root.user_selection_ids]
if root.user_selection_ids
else None
) # noqa
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
],
user_selection_ids=user_selection_ids, # noqa
circle_ids=root.get_circle_ids(info), # noqa
urql_id_postfix=str(root._id), # noqa
)
return CompetencesStatisticsType(
_id=root._id, # noqa
records=records, # noqa
summary=CompetencePerformanceStatisticsSummaryType( # noqa
_id=root._id, # noqa
success_total=success_total, # noqa
fail_total=fail_total, # noqa
),
)
def resolve_course_session_selection_metrics(
root, info
) -> StatisticsCourseSessionsSelectionMetricType:
course_session_count = CourseSession.objects.filter(
id__in=root.course_session_selection_ids,
course_id=root.course_id,
).count()
expert_count = CourseSessionUser.objects.filter(
course_session_id__in=root.course_session_selection_ids,
role=CourseSessionUser.Role.EXPERT,
).count()
participant_count = CourseSessionUser.objects.filter(
course_session_id__in=root.course_session_selection_ids,
role=CourseSessionUser.Role.MEMBER,
).count()
return StatisticsCourseSessionsSelectionMetricType(
_id=root._id, # noqa
session_count=course_session_count, # noqa
participant_count=participant_count, # noqa
expert_count=expert_count, # noqa
)

View File

@ -58,6 +58,7 @@ class WidgetType(Enum):
MENTOR_COMPETENCE_WIDGET = "MentorCompetenceWidget"
COMPETENCE_CERTIFICATE_WIDGET = "CompetenceCertificateWidget"
UK_STATISTICS_WIDGET = "UKStatisticsWidget"
UK_BERUFSBILDNER_STATISTICS_WIDGET = "UKBerufsbildnerStatisticsWidget"
class RoleKeyType(Enum):
@ -379,7 +380,7 @@ def get_widgets_for_course(
widgets.append(WidgetType.MENTOR_TASKS_WIDGET.value)
if role_key in [RoleKeyType.BERUFSBILDNER]:
widgets.append(WidgetType.MENTOR_COMPETENCE_WIDGET.value)
widgets.append(WidgetType.UK_BERUFSBILDNER_STATISTICS_WIDGET.value)
return widgets