Skip to content

Commit 4bad6ca

Browse files
authored
Merge pull request #109 from pstaabp/instructor-dashboard2
Create a basic instructor dashboard
2 parents 96d631f + 79b240d commit 4bad6ca

File tree

23 files changed

+255
-212
lines changed

23 files changed

+255
-212
lines changed

conf/permissions.dist.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ db_permissions:
3535

3636
Course:
3737
getCourses:
38-
allowed_roles: ['*']
38+
authenticated: true
3939
getCourse:
40-
allowed_roles: ['*']
40+
authenticated: true
4141
updateCourse:
4242
admin_required: true
4343
addCourse:
@@ -48,7 +48,7 @@ db_permissions:
4848
getGlobalUsers:
4949
admin_required: true
5050
getGlobalUser:
51-
admin_required: true
51+
allow_self_access: true
5252
checkGlobalUser:
5353
allowed_roles: ['course_admin', 'instructor']
5454
updateGlobalUser:
@@ -78,6 +78,7 @@ db_permissions:
7878
getGlobalCourseUsers:
7979
allowed_roles: ['course_admin', 'instructor']
8080
getCourseUser:
81+
allow_self_access: true
8182
allowed_roles: ['course_admin', 'instructor']
8283
addCourseUser:
8384
allowed_roles: ['course_admin', 'instructor']
@@ -99,6 +100,7 @@ db_permissions:
99100
getAllUserSets:
100101
allowed_roles: ['course_admin', 'instructor']
101102
getUserSets:
103+
allow_self_access: true
102104
allowed_roles: ['course_admin', 'instructor']
103105
addUserSet:
104106
allowed_roles: ['course_admin', 'instructor']

lib/WeBWorK3/Controller/Permission.pm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ sub checkPermission ($c) {
6262
} elsif ($perm_db->admin_required && !$user->{is_admin}) {
6363
$permitted = undef;
6464
$msg = 'This route requires admin privileges.';
65-
} elsif (!$course_id && $perm_db->allow_self_access && defined($c->param('user_id'))) {
65+
} elsif (!$course_id && $perm_db->allow_self_access && defined($user_id)) {
6666
# Some routes allow self access, but the course_id is not defined.
67-
$permitted = $user->{user_id} == $c->param('user_id');
67+
$permitted = $user->{user_id} == $user_id;
6868
} elsif ($course_id) {
6969
my $course_user = $c->schema->resultset('User')->getCourseUser(
7070
info => {

src/common/models/courses.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,12 @@ export class Course extends Model {
106106
*/
107107

108108
export interface ParseableUserCourse {
109-
course_id?: number;
110-
user_id?: number;
111-
course_name?: string;
109+
course_id: number;
110+
user_id: number;
111+
course_name: string;
112112
username?: string;
113113
visible?: boolean;
114-
role?: string;
114+
role: string;
115115
course_dates?: ParseableCourseDates;
116116
}
117117
export class UserCourse extends Model {
@@ -126,6 +126,13 @@ export class UserCourse extends Model {
126126
static ALL_FIELDS = ['course_id', 'course_name', 'visible', 'course_dates',
127127
'user_id', 'username', 'role'];
128128

129+
static DEFAULT_VALUES = {
130+
course_id: 0,
131+
user_id: 0,
132+
course_name: 'DEFAULT_USER_COURSE',
133+
role: 'unknown',
134+
};
135+
129136
get all_field_names(): string[] {
130137
return UserCourse.ALL_FIELDS;
131138
}
@@ -134,7 +141,7 @@ export class UserCourse extends Model {
134141
return ['course_dates'];
135142
}
136143

137-
constructor(params: ParseableUserCourse = {}) {
144+
constructor(params: ParseableUserCourse = UserCourse.DEFAULT_VALUES) {
138145
super();
139146
this.set(params);
140147
}
@@ -171,7 +178,8 @@ export class UserCourse extends Model {
171178
set role(value: string) { this._role = value; }
172179

173180
clone(): UserCourse {
174-
return new UserCourse(this.toObject());
181+
// typescript does not recognize the getters as keys when converting with .toObject()
182+
return new UserCourse(this.toObject() as unknown as ParseableUserCourse);
175183
}
176184

177185
isValid(): boolean {

src/common/models/session.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { User } from 'src/common/models/users';
1+
import type { ParseableUser, User } from 'src/common/models/users';
22
import { parseBoolean } from './parsers';
33

44
export interface SessionInfo {
5-
user: User;
5+
user: ParseableUser;
66
logged_in: boolean;
77
message: string;
88
}

src/common/views.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ export const student_views: Array<ViewInfo> = [
4949
];
5050

5151
export const instructor_views: Array<ViewInfo> = [
52+
{
53+
name: 'Dashboard',
54+
component_name: 'InstructorDashboard',
55+
icon: 'speed',
56+
route: 'dashboard',
57+
sidebars: []
58+
},
5259
{
5360
name: 'Calendar',
5461
component_name: 'Calendar',

src/components/common/Login.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,21 @@ const login = async () => {
5656
};
5757
const session_info = await checkPassword(username_info);
5858
59-
if (!session_info.logged_in) {
59+
if (!session_info.logged_in || !session_info.user.user_id) {
6060
message.value = i18n.t('authentication.failure');
6161
} else {
6262
// success
6363
session.updateSessionInfo(session_info);
6464
6565
// permissions require access to user courses and respective roles
66-
await session.fetchUserCourses(session_info.user.user_id);
66+
await session.fetchUserCourses();
6767
await permission_store.fetchRoles();
6868
await permission_store.fetchRoutePermissions();
6969
7070
let forward = localStorage.getItem('afterLogin');
7171
forward ||= (session_info.user.is_admin) ?
7272
'/admin' :
73-
`/users/${session.user.user_id}/courses`;
73+
`/users/${session_info.user.user_id}/courses`;
7474
localStorage.removeItem('afterLogin');
7575
void router.push(forward);
7676
}

src/components/common/UserCourses.vue

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,48 +36,35 @@ import { computed } from 'vue';
3636
import { useRouter } from 'vue-router';
3737
3838
import { useSessionStore } from 'src/stores/session';
39+
import { logger } from 'src/boot/logger';
3940
40-
const session = useSessionStore();
41+
const session_store = useSessionStore();
4142
const router = useRouter();
4243
44+
const user = computed(() => session_store.user);
45+
4346
// This is used to simplify the UI.
4447
const course_types = computed(() => [
45-
{ name: 'Student', courses: student_courses.value },
46-
{ name: 'Instrutor', courses: instructor_courses.value }
48+
{ name: 'Student', courses: session_store.user_courses.filter(c => c.role === 'student') },
49+
{ name: 'Instructor', courses: session_store.user_courses.filter(c => c.role === 'instructor') }
4750
]);
4851
49-
const student_courses = computed(() =>
50-
// for some reason on load the user_course.role is undefined.
51-
session.user_courses.filter(user_course => user_course.role === 'student'));
52-
53-
const instructor_courses = computed(() =>
54-
// For some reason on load the user_course.role is undefined.
55-
session.user_courses.filter(user_course => user_course.role === 'instructor')
56-
);
57-
const user = computed(() => session.user);
52+
const switchCourse = (course_id?: number) => {
53+
if (!course_id) {
54+
logger.error('[UserCourses/switchCourse]: the course_id is 0 or undefined.');
55+
return;
56+
}
57+
session_store.setCourse(course_id);
5858
59-
const switchCourse = async (course_id: number) => {
60-
const student_course = student_courses.value.find(c => c.course_id === course_id);
61-
const instructor_course = instructor_courses.value.find(c => c.course_id === course_id);
62-
if (student_course) {
63-
session.setCourse({
64-
course_name: student_course.course_name,
65-
course_id: student_course.course_id,
66-
role: 'student'
67-
});
68-
await router.push({
59+
if (session_store.course.role === 'student') {
60+
void router.push({
6961
name: 'StudentDashboard',
70-
params: { course_id: student_course.course_id }
71-
});
72-
} else if (instructor_course) {
73-
session.setCourse({
74-
course_name: instructor_course.course_name,
75-
course_id: instructor_course.course_id,
76-
role: 'instructor'
62+
params: { course_id }
7763
});
78-
await router.push({
79-
name: 'instructor',
80-
params: { course_id: instructor_course.course_id }
64+
} else if (session_store.course.role === 'instructor') {
65+
void router.push({
66+
name: 'InstructorDashboard',
67+
params: { course_id }
8168
});
8269
}
8370
};

src/components/student/Student.vue

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,31 +21,22 @@ const loadStudentSets = async () => {
2121
// Fetch only the current user info.
2222
await user_store.setSessionUser();
2323
24-
logger.debug(`[Student/loadStudenSet]: loading data for course ${session_store.course.course_id}`);
24+
logger.debug(`[Student/loadStudentSets]: loading data for course ${session_store.course.course_id}`);
2525
2626
if (session_store.course.course_id > 0) {
2727
// Fetch all problem sets and user sets
2828
await problem_set_store.fetchProblemSets(session_store.course.course_id);
29-
await problem_set_store.fetchUserSetsForUser({ user_id: session_store.user.user_id });
30-
29+
if (session_store.user.user_id) {
30+
await problem_set_store.fetchUserSetsForUser({ user_id: session_store.user.user_id });
31+
}
3132
}
3233
};
3334
3435
const course_id = parseRouteCourseID(route);
35-
const course = session_store.user_courses.find(c => c.course_id === course_id);
36-
if (course) {
37-
session_store.setCourse({
38-
course_id: course_id,
39-
course_name: course.course_name
40-
});
41-
} else {
42-
logger.warn(`Can't find ${course_id} in ${session_store.user_courses
43-
.map((c) => c.course_id).join(', ')}`);
44-
}
36+
session_store.setCourse(course_id);
4537
await loadStudentSets();
4638
4739
watch(() => session_store.course.course_id, async () => {
48-
4940
await loadStudentSets();
5041
});
5142
</script>

src/layouts/MainLayout.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
</q-drawer>
1111

1212
<q-page-container>
13-
<Suspense>
13+
<suspense>
1414
<router-view />
15-
</Suspense>
15+
</suspense>
1616
</q-page-container>
1717

1818
<!-- this only opens the first sidebar in the list

src/layouts/MenuBar.vue

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<q-list>
3030
<template v-for="course in user_courses" :key="course.course_id">
3131
<q-item clickable v-close-popup
32-
@click="changeCourse(course.course_id, course.course_name)">
32+
@click="changeCourse(course.course_id)">
3333
<q-item-section>
3434
<q-item-label>{{course.course_name}}</q-item-label>
3535
</q-item-section>
@@ -64,13 +64,16 @@
6464
<script setup lang="ts">
6565
import { computed, defineEmits, ref } from 'vue';
6666
import { useRouter } from 'vue-router';
67-
import { endSession } from 'src/common/api-requests/session';
6867
import { useI18n } from 'vue-i18n';
68+
6969
import { setI18nLanguage } from 'boot/i18n';
70+
import { logger } from 'src/boot/logger';
71+
72+
import { endSession } from 'src/common/api-requests/session';
7073
import { useSessionStore } from 'src/stores/session';
71-
import type { CourseSettingInfo } from 'src/common/models/settings';
7274
import { useSettingsStore } from 'src/stores/settings';
73-
import { logger } from 'src/boot/logger';
75+
76+
import type { CourseSettingInfo } from 'src/common/models/settings';
7477
7578
defineEmits(['toggle-menu', 'toggle-sidebar']);
7679
const session = useSessionStore();
@@ -84,18 +87,17 @@ const full_name = computed(() => session.full_name);
8487
const user_courses = computed(() =>
8588
session.user_courses.filter(course => course.course_name !== current_course_name.value));
8689
87-
const changeCourse = (course_id: number, course_name: string) => {
88-
const new_course = session.user_courses.find(course => course.course_name === course_name);
90+
const changeCourse = (course_id: number) => {
91+
logger.debug(`[MenuBar/changeCourse]: changing the course to #${course_id}`);
92+
session.setCourse(course_id);
8993
90-
if (new_course != undefined) {
91-
router.push(`/courses/${new_course.course_id}`).then(() => {
92-
session.setCourse({
93-
course_name: new_course.course_name,
94-
course_id: new_course.course_id
95-
});
96-
}).catch(() => {
97-
logger.error('[MenuBar/changeCourse]: Error occurred.');
98-
});
94+
// This sets the path to the instructor or student dashboard.
95+
// This only works currently for roles of student/instructor. We'll need to think about
96+
// the UI for other roles.
97+
if (!session.course.role || session.course.role == 'unknown') {
98+
logger.error(`[MenuBar/changeCourse]: the role is not defined for course #${course_id}`);
99+
} else {
100+
void router.push(`/courses/${course_id}/${session.course.role}`);
99101
}
100102
};
101103
@@ -106,6 +108,6 @@ const availableLocales = computed(() =>
106108
const logout = async () => {
107109
await endSession();
108110
void session.logout();
109-
void router.push('/login');
111+
void router.push({ name: 'login' });
110112
};
111113
</script>

0 commit comments

Comments
 (0)