WIP: Add ui config

This commit is contained in:
Christian Cueni 2024-04-08 08:08:12 +02:00
parent 2f77bf7734
commit 6ea45eff66
10 changed files with 145 additions and 28 deletions

View File

@ -25,15 +25,18 @@ const data = ref<CourseProgressType | null>(null);
const courseSlug = computed(() => props.courseConfig?.slug);
const courseName = computed(() => props.courseConfig?.name);
const numberOfMentorWidgets = computed(() => {
return data.value?.widgets.filter((widget) => mentorWidgets.includes(widget)).length;
return data.value?.ui_config.widgets.filter((widget) =>
mentorWidgets.includes(widget)
).length;
});
const numberOfProgressWidgets = computed(() => {
return data.value?.widgets.filter((widget) => progressWidgets.includes(widget))
.length;
return data.value?.ui_config.widgets.filter((widget) =>
progressWidgets.includes(widget)
).length;
});
function hasWidget(widget: WidgetType) {
return data.value?.widgets.includes(widget);
return data.value?.ui_config.widgets.includes(widget);
}
onMounted(async () => {
@ -46,6 +49,7 @@ onMounted(async () => {
<div v-if="!isLoading && courseConfig" class="mb-14 space-y-8">
<div class="flex flex-col space-y-7 bg-white p-6">
<h3>{{ courseName }}</h3>
<p>{{ data.ui_config.role_key }}</p>
<LearningPathDiagram
v-if="hasWidget('PROGRESS_WIDGET') && data.session_to_continue_id && courseSlug"
:key="courseSlug"

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 configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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 assignment_type\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 assignment_type\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 course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\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 dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n widgets\n }\n }\n": types.DashboardCourseDataDocument,
"\n query dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n ui_config {\n _id\n role_key\n widgets\n has_preview\n }\n }\n }\n": types.DashboardCourseDataDocument,
"\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 }\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 experts\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 title\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 $learningContentType: String!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n learning_content_type: $learningContentType\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,
};
@ -85,7 +85,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 dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n widgets\n }\n }\n"): (typeof documents)["\n query dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n widgets\n }\n }\n"];
export function graphql(source: "\n query dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n ui_config {\n _id\n role_key\n widgets\n has_preview\n }\n }\n }\n"): (typeof documents)["\n query dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n ui_config {\n _id\n role_key\n widgets\n has_preview\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

@ -1,5 +1,6 @@
type Query {
course_statistics(course_id: ID!): CourseStatisticsType
mentor_course_statistics(course_id: ID!): CourseStatisticsType
course_progress(course_id: ID!): CourseProgressType
dashboard_config: [DashboardConfigType!]!
learning_path(id: ID, slug: String, course_id: ID, course_slug: String): LearningPathObjectType
@ -31,6 +32,7 @@ type CourseStatisticsType {
course_slug: String!
course_session_properties: StatisticsCourseSessionPropertiesType!
course_session_selection_ids: [ID]!
user_selection_ids: [ID]
course_session_selection_metrics: StatisticsCourseSessionsSelectionMetricType!
attendance_day_presences: AttendanceDayPresencesStatisticsType!
feedback_responses: FeedbackStatisticsResponsesType!
@ -179,7 +181,7 @@ type CourseProgressType {
session_to_continue_id: ID
competence: ProgressDashboardCompetenceType
assignment: ProgressDashboardAssignmentType
widgets: [WidgetType!]!
ui_config: UIConfigType
}
type ProgressDashboardCompetenceType {
@ -196,6 +198,21 @@ type ProgressDashboardAssignmentType {
points_achieved_count: Int!
}
type UIConfigType {
_id: ID!
role_key: RoleKeyType!
widgets: [WidgetType!]!
has_preview: Boolean
}
enum RoleKeyType {
MEMBER
MENTOR_VV
MENTOR_UK
SUPERVISOR
TRAINER
}
enum WidgetType {
PROGRESS_WIDGET
COMPETENCE_WIDGET

View File

@ -75,6 +75,7 @@ export const PresenceRecordStatisticsType = "PresenceRecordStatisticsType";
export const ProgressDashboardAssignmentType = "ProgressDashboardAssignmentType";
export const ProgressDashboardCompetenceType = "ProgressDashboardCompetenceType";
export const Query = "Query";
export const RoleKeyType = "RoleKeyType";
export const SendFeedbackMutation = "SendFeedbackMutation";
export const StatisticsCircleDataType = "StatisticsCircleDataType";
export const StatisticsCourseSessionDataType = "StatisticsCourseSessionDataType";
@ -82,6 +83,7 @@ export const StatisticsCourseSessionPropertiesType = "StatisticsCourseSessionPro
export const StatisticsCourseSessionsSelectionMetricType = "StatisticsCourseSessionsSelectionMetricType";
export const String = "String";
export const TopicObjectType = "TopicObjectType";
export const UIConfigType = "UIConfigType";
export const UUID = "UUID";
export const UserObjectType = "UserObjectType";
export const WidgetType = "WidgetType";

View File

@ -337,7 +337,12 @@ export const DASHBOARD_COURSE_DATA = graphql(`
_id
course_id
session_to_continue_id
ui_config {
_id
role_key
widgets
has_preview
}
}
}
`);

View File

@ -64,7 +64,7 @@ onMounted(dashboardStore.loadDashboardDetails);
dashboardStore.currentDashboardConfig.dashboard_type ===
'PRAXISBILDNER_DASHBOARD'
"
course-config="dashboardStore.currentDashboardConfig"
:course-config="dashboardStore.currentDashboardConfig"
/>
<component
:is="boards[dashboardStore.currentDashboardConfig.dashboard_type].main"

View File

@ -7,6 +7,7 @@ from vbv_lernwelt.assignment.models import (
AssignmentCompletionStatus,
)
from vbv_lernwelt.core.admin import User
from vbv_lernwelt.course.consts import UK_COURSE_IDS
from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
from vbv_lernwelt.dashboard.graphql.types.competence import competences
@ -15,7 +16,7 @@ from vbv_lernwelt.dashboard.graphql.types.dashboard import (
CourseStatisticsType,
DashboardConfigType,
DashboardType,
get_widgets_for_course,
get_ui_config_for_course,
ProgressDashboardAssignmentType,
ProgressDashboardCompetenceType,
)
@ -32,6 +33,10 @@ class DashboardQuery(graphene.ObjectType):
CourseStatisticsType, course_id=graphene.ID(required=True)
)
mentor_course_statistics = graphene.Field(
CourseStatisticsType, course_id=graphene.ID(required=True)
)
course_progress = graphene.Field(
CourseProgressType, course_id=graphene.ID(required=True)
)
@ -63,6 +68,28 @@ class DashboardQuery(graphene.ObjectType):
course_session_selection_ids=list(course_session_ids), # noqa
)
def resolve_mentor_course_statistics(root, info, course_id: str): # noqa
user = info.context.user
course = Course.objects.get(id=course_id)
mentees_ids = set()
course_session_ids = set()
mentees = CourseSessionUser.objects.filter(participants__mentor=user,
course_session__course=course).values_list("user", "course_session")
for user_id, course_session_id in mentees:
mentees_ids.add(user_id)
course_session_ids.add(course_session_id)
return CourseStatisticsType(
_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
user_selection_ids=list(mentees_ids), # noqa
)
def resolve_dashboard_config(root, info): # noqa
user = info.context.user
@ -167,7 +194,7 @@ class DashboardQuery(graphene.ObjectType):
points_max_count=int(points_max_count), # noqa
points_achieved_count=int(points_achieved_count), # noqa
),
widgets=get_widgets_for_course(course, user), # noqa
widgets=get_ui_config_for_course(course, user), # noqa
)
@ -205,12 +232,16 @@ def get_learning_mentor_dashboards(
for mentor in learning_mentor:
course = mentor.course_session.course
course_ids.add(course.id)
if course.id in UK_COURSE_IDS:
dashboard_type = DashboardType.PRAXISBILDNER_DASHBOARD
else:
dashboard_type = DashboardType.MENTOR_DASHBOARD
dashboards.append(
{
"id": str(course.id),
"name": course.title,
"slug": course.slug,
"dashboard_type": DashboardType.MENTOR_DASHBOARD,
"dashboard_type": dashboard_type,
"course_configuration": course.configuration,
}
)

View File

@ -74,7 +74,8 @@ def create_assignment_summary(course_id, metrics) -> AssignmentStatisticsSummary
def get_assignment_completion_metrics(
course_session: CourseSession, assignment: vbv_lernwelt.assignment.models.Assignment
course_session: CourseSession, assignment: vbv_lernwelt.assignment.models.Assignment,
user_selection_ids: List[str] | None
) -> AssignmentCompletionMetricsType:
course_session_users = CourseSessionUser.objects.filter(
course_session=course_session,
@ -111,6 +112,7 @@ def get_assignment_completion_metrics(
def create_record(
course_session_assignment: CourseSessionAssignment | CourseSessionEdoniqTest,
user_selection_ids: List[str] | None
) -> AssignmentStatisticsRecordType:
if isinstance(course_session_assignment, CourseSessionAssignment):
due_date = course_session_assignment.submission_deadline
@ -133,6 +135,7 @@ def create_record(
metrics=get_assignment_completion_metrics( # noqa
course_session=course_session_assignment.course_session, # noqa
assignment=learning_content.content_assignment, # noqa
user_selection_ids=user_selection_ids, # noqa
),
details_url=due_date.url_expert, # noqa
deadline=due_date.start, # noqa
@ -142,6 +145,7 @@ def create_record(
def assignments(
course_id: graphene.ID(required=True),
course_session_selection_ids: graphene.List(graphene.ID),
user_selection_ids: List[str] | None = None,
) -> AssignmentsStatisticsType:
course_sessions = CourseSession.objects.filter(
id__in=course_session_selection_ids,
@ -156,14 +160,14 @@ def assignments(
],
learning_content__content_assignment__competence_certificate__isnull=False,
):
record = create_record(course_session_assignment=csa)
record = create_record(course_session_assignment=csa, user_selection_ids=user_selection_ids)
records.append(record)
for cset in CourseSessionEdoniqTest.objects.filter(
course_session=course_session,
learning_content__content_assignment__competence_certificate__isnull=False,
):
record = create_record(course_session_assignment=cset)
record = create_record(course_session_assignment=cset, user_selection_ids=user_selection_ids)
records.append(record)
return AssignmentsStatisticsType(

View File

@ -8,6 +8,7 @@ from vbv_lernwelt.assignment.models import (
AssignmentCompletionStatus,
)
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.consts import UK_COURSE_IDS
from vbv_lernwelt.course.graphql.types import CourseConfigurationObjectType
from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser
from vbv_lernwelt.dashboard.graphql.types.assignment import (
@ -78,6 +79,14 @@ class WidgetType(Enum):
COMPETENCE_CERTIFICATE_WIDGET = "CompetenceCertificateWidget"
class RoleKeyType(Enum):
MEMBER = "Member"
MENTOR_VV = "MentorVV"
MENTOR_UK = "MentorUK"
SUPERVISOR = "Supervisor"
TRAINER = "Trainer"
class DashboardConfigType(graphene.ObjectType):
id = graphene.ID(required=True)
name = graphene.String(required=True)
@ -86,11 +95,6 @@ class DashboardConfigType(graphene.ObjectType):
course_configuration = graphene.Field(CourseConfigurationObjectType, required=True)
class DashboardDataType(graphene.ObjectType):
id = graphene.ID(required=True)
widgets = graphene.List(graphene.NonNull(WidgetType), required=True)
class ProgressDashboardCompetenceType(graphene.ObjectType):
_id = graphene.ID(required=True)
total_count = graphene.Int(required=True)
@ -105,13 +109,20 @@ class ProgressDashboardAssignmentType(graphene.ObjectType):
points_achieved_count = graphene.Int(required=True)
class UIConfigType(graphene.ObjectType):
_id = graphene.ID(required=True)
role_key = graphene.Field(RoleKeyType, required=True)
widgets = graphene.List(graphene.NonNull(WidgetType), required=True)
has_preview = graphene.Boolean(required=False)
class CourseProgressType(graphene.ObjectType):
_id = graphene.ID(required=True)
course_id = graphene.ID(required=True)
session_to_continue_id = graphene.ID(required=False)
competence = graphene.Field(ProgressDashboardCompetenceType, required=False)
assignment = graphene.Field(ProgressDashboardAssignmentType, required=False)
widgets = graphene.List(graphene.NonNull(WidgetType), required=True)
ui_config = graphene.Field(UIConfigType, required=False)
def resolve__id(root, info):
return info.context.course.id
@ -163,8 +174,8 @@ class CourseProgressType(graphene.ObjectType):
fail_count=fail_total, # noqa
)
def resolve_widgets(root, info):
return get_widgets_for_course(info.context.course, info.context.user)
def resolve_ui_config(root, info):
return get_ui_config_for_course(info.context.course, info.context.user)
def _get_newest_cs_and_cs_for_user(
root, info, course_id: str, user: User
@ -203,6 +214,7 @@ class CourseStatisticsType(graphene.ObjectType):
StatisticsCourseSessionPropertiesType, required=True
)
course_session_selection_ids = graphene.List(graphene.ID, required=True)
user_selection_ids = graphene.List(graphene.ID, required=False)
course_session_selection_metrics = graphene.Field(
StatisticsCourseSessionsSelectionMetricType, required=True
)
@ -229,11 +241,14 @@ class CourseStatisticsType(graphene.ObjectType):
)
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
)
return CompetencesStatisticsType(
_id=root._id, # noqa
@ -246,9 +261,12 @@ class CourseStatisticsType(graphene.ObjectType):
)
def resolve_assignments(root, info) -> AssignmentsStatisticsType:
user_selection_ids = [str(user) for user in
root.user_selection_ids] if root.user_selection_ids else None # noqa
return assignments(
course_id=root.course_id,
course_session_selection_ids=root.course_session_selection_ids,
user_selection_ids=user_selection_ids,
)
def resolve_course_session_selection_metrics(
@ -320,8 +338,10 @@ class CourseStatisticsType(graphene.ObjectType):
)
def get_widgets_for_course(course: Course, user: User) -> List[WidgetType]:
def get_ui_config_for_course(course: Course, user: User) -> UIConfigType:
widgets = []
role_key = None
has_preview = False
course_sessions = CourseSession.objects.filter(
course__id=course.id, coursesessionuser__user=user
@ -346,20 +366,32 @@ def get_widgets_for_course(course: Course, user: User) -> List[WidgetType]:
roles_by_course.setdefault(course_session.course, set())
roles_by_course[course_session.course].add(role)
# todo: use permissions
for course, roles in roles_by_course.items():
if len(roles) == 1:
course_role = roles.pop()
# members
if course_role == CourseSessionUser.Role.MEMBER:
role_key = RoleKeyType.MEMBER
widgets.append(WidgetType.PROGRESS_WIDGET)
widgets.append(WidgetType.COMPETENCE_WIDGET)
if course.configuration.enable_competence_certificates:
widgets.append(WidgetType.COMPETENCE_CERTIFICATE_WIDGET)
# mentors
if course.id in mentor_course_ids:
if not role_key:
role_key = RoleKeyType.MENTOR_UK if course.id in UK_COURSE_IDS else RoleKeyType.MENTOR_VV
has_preview = True
widgets.append(WidgetType.MENTOR_TASKS_WIDGET)
widgets.append(WidgetType.MENTOR_PERSON_WIDGET)
if course.configuration.enable_competence_certificates:
widgets.append(WidgetType.MENTOR_COMPETENCE_WIDGET)
return widgets
# todo: supervisors
return UIConfigType(
_id=f"{course.id}_{user.id}", # noqa
role_key=role_key, # noqa,
widgets=widgets, # noqa
has_preview=has_preview, # noqa
)