Skip to content

Commit

Permalink
Merge pull request #109 from pstaabp/instructor-dashboard2
Browse files Browse the repository at this point in the history
Create a basic instructor dashboard
  • Loading branch information
drdrew42 authored Aug 26, 2022
2 parents 96d631f + 79b240d commit 4bad6ca
Show file tree
Hide file tree
Showing 23 changed files with 255 additions and 212 deletions.
8 changes: 5 additions & 3 deletions conf/permissions.dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ db_permissions:

Course:
getCourses:
allowed_roles: ['*']
authenticated: true
getCourse:
allowed_roles: ['*']
authenticated: true
updateCourse:
admin_required: true
addCourse:
Expand All @@ -48,7 +48,7 @@ db_permissions:
getGlobalUsers:
admin_required: true
getGlobalUser:
admin_required: true
allow_self_access: true
checkGlobalUser:
allowed_roles: ['course_admin', 'instructor']
updateGlobalUser:
Expand Down Expand Up @@ -78,6 +78,7 @@ db_permissions:
getGlobalCourseUsers:
allowed_roles: ['course_admin', 'instructor']
getCourseUser:
allow_self_access: true
allowed_roles: ['course_admin', 'instructor']
addCourseUser:
allowed_roles: ['course_admin', 'instructor']
Expand All @@ -99,6 +100,7 @@ db_permissions:
getAllUserSets:
allowed_roles: ['course_admin', 'instructor']
getUserSets:
allow_self_access: true
allowed_roles: ['course_admin', 'instructor']
addUserSet:
allowed_roles: ['course_admin', 'instructor']
Expand Down
4 changes: 2 additions & 2 deletions lib/WeBWorK3/Controller/Permission.pm
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ sub checkPermission ($c) {
} elsif ($perm_db->admin_required && !$user->{is_admin}) {
$permitted = undef;
$msg = 'This route requires admin privileges.';
} elsif (!$course_id && $perm_db->allow_self_access && defined($c->param('user_id'))) {
} elsif (!$course_id && $perm_db->allow_self_access && defined($user_id)) {
# Some routes allow self access, but the course_id is not defined.
$permitted = $user->{user_id} == $c->param('user_id');
$permitted = $user->{user_id} == $user_id;
} elsif ($course_id) {
my $course_user = $c->schema->resultset('User')->getCourseUser(
info => {
Expand Down
20 changes: 14 additions & 6 deletions src/common/models/courses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ export class Course extends Model {
*/

export interface ParseableUserCourse {
course_id?: number;
user_id?: number;
course_name?: string;
course_id: number;
user_id: number;
course_name: string;
username?: string;
visible?: boolean;
role?: string;
role: string;
course_dates?: ParseableCourseDates;
}
export class UserCourse extends Model {
Expand All @@ -126,6 +126,13 @@ export class UserCourse extends Model {
static ALL_FIELDS = ['course_id', 'course_name', 'visible', 'course_dates',
'user_id', 'username', 'role'];

static DEFAULT_VALUES = {
course_id: 0,
user_id: 0,
course_name: 'DEFAULT_USER_COURSE',
role: 'unknown',
};

get all_field_names(): string[] {
return UserCourse.ALL_FIELDS;
}
Expand All @@ -134,7 +141,7 @@ export class UserCourse extends Model {
return ['course_dates'];
}

constructor(params: ParseableUserCourse = {}) {
constructor(params: ParseableUserCourse = UserCourse.DEFAULT_VALUES) {
super();
this.set(params);
}
Expand Down Expand Up @@ -171,7 +178,8 @@ export class UserCourse extends Model {
set role(value: string) { this._role = value; }

clone(): UserCourse {
return new UserCourse(this.toObject());
// typescript does not recognize the getters as keys when converting with .toObject()
return new UserCourse(this.toObject() as unknown as ParseableUserCourse);
}

isValid(): boolean {
Expand Down
4 changes: 2 additions & 2 deletions src/common/models/session.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { User } from 'src/common/models/users';
import type { ParseableUser, User } from 'src/common/models/users';
import { parseBoolean } from './parsers';

export interface SessionInfo {
user: User;
user: ParseableUser;
logged_in: boolean;
message: string;
}
Expand Down
7 changes: 7 additions & 0 deletions src/common/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ export const student_views: Array<ViewInfo> = [
];

export const instructor_views: Array<ViewInfo> = [
{
name: 'Dashboard',
component_name: 'InstructorDashboard',
icon: 'speed',
route: 'dashboard',
sidebars: []
},
{
name: 'Calendar',
component_name: 'Calendar',
Expand Down
6 changes: 3 additions & 3 deletions src/components/common/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,21 @@ const login = async () => {
};
const session_info = await checkPassword(username_info);
if (!session_info.logged_in) {
if (!session_info.logged_in || !session_info.user.user_id) {
message.value = i18n.t('authentication.failure');
} else {
// success
session.updateSessionInfo(session_info);
// permissions require access to user courses and respective roles
await session.fetchUserCourses(session_info.user.user_id);
await session.fetchUserCourses();
await permission_store.fetchRoles();
await permission_store.fetchRoutePermissions();
let forward = localStorage.getItem('afterLogin');
forward ||= (session_info.user.is_admin) ?
'/admin' :
`/users/${session.user.user_id}/courses`;
`/users/${session_info.user.user_id}/courses`;
localStorage.removeItem('afterLogin');
void router.push(forward);
}
Expand Down
51 changes: 19 additions & 32 deletions src/components/common/UserCourses.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,48 +36,35 @@ import { computed } from 'vue';
import { useRouter } from 'vue-router';
import { useSessionStore } from 'src/stores/session';
import { logger } from 'src/boot/logger';
const session = useSessionStore();
const session_store = useSessionStore();
const router = useRouter();
const user = computed(() => session_store.user);
// This is used to simplify the UI.
const course_types = computed(() => [
{ name: 'Student', courses: student_courses.value },
{ name: 'Instrutor', courses: instructor_courses.value }
{ name: 'Student', courses: session_store.user_courses.filter(c => c.role === 'student') },
{ name: 'Instructor', courses: session_store.user_courses.filter(c => c.role === 'instructor') }
]);
const student_courses = computed(() =>
// for some reason on load the user_course.role is undefined.
session.user_courses.filter(user_course => user_course.role === 'student'));
const instructor_courses = computed(() =>
// For some reason on load the user_course.role is undefined.
session.user_courses.filter(user_course => user_course.role === 'instructor')
);
const user = computed(() => session.user);
const switchCourse = (course_id?: number) => {
if (!course_id) {
logger.error('[UserCourses/switchCourse]: the course_id is 0 or undefined.');
return;
}
session_store.setCourse(course_id);
const switchCourse = async (course_id: number) => {
const student_course = student_courses.value.find(c => c.course_id === course_id);
const instructor_course = instructor_courses.value.find(c => c.course_id === course_id);
if (student_course) {
session.setCourse({
course_name: student_course.course_name,
course_id: student_course.course_id,
role: 'student'
});
await router.push({
if (session_store.course.role === 'student') {
void router.push({
name: 'StudentDashboard',
params: { course_id: student_course.course_id }
});
} else if (instructor_course) {
session.setCourse({
course_name: instructor_course.course_name,
course_id: instructor_course.course_id,
role: 'instructor'
params: { course_id }
});
await router.push({
name: 'instructor',
params: { course_id: instructor_course.course_id }
} else if (session_store.course.role === 'instructor') {
void router.push({
name: 'InstructorDashboard',
params: { course_id }
});
}
};
Expand Down
19 changes: 5 additions & 14 deletions src/components/student/Student.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,22 @@ const loadStudentSets = async () => {
// Fetch only the current user info.
await user_store.setSessionUser();
logger.debug(`[Student/loadStudenSet]: loading data for course ${session_store.course.course_id}`);
logger.debug(`[Student/loadStudentSets]: loading data for course ${session_store.course.course_id}`);
if (session_store.course.course_id > 0) {
// Fetch all problem sets and user sets
await problem_set_store.fetchProblemSets(session_store.course.course_id);
await problem_set_store.fetchUserSetsForUser({ user_id: session_store.user.user_id });
if (session_store.user.user_id) {
await problem_set_store.fetchUserSetsForUser({ user_id: session_store.user.user_id });
}
}
};
const course_id = parseRouteCourseID(route);
const course = session_store.user_courses.find(c => c.course_id === course_id);
if (course) {
session_store.setCourse({
course_id: course_id,
course_name: course.course_name
});
} else {
logger.warn(`Can't find ${course_id} in ${session_store.user_courses
.map((c) => c.course_id).join(', ')}`);
}
session_store.setCourse(course_id);
await loadStudentSets();
watch(() => session_store.course.course_id, async () => {
await loadStudentSets();
});
</script>
4 changes: 2 additions & 2 deletions src/layouts/MainLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
</q-drawer>

<q-page-container>
<Suspense>
<suspense>
<router-view />
</Suspense>
</suspense>
</q-page-container>

<!-- this only opens the first sidebar in the list
Expand Down
34 changes: 18 additions & 16 deletions src/layouts/MenuBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<q-list>
<template v-for="course in user_courses" :key="course.course_id">
<q-item clickable v-close-popup
@click="changeCourse(course.course_id, course.course_name)">
@click="changeCourse(course.course_id)">
<q-item-section>
<q-item-label>{{course.course_name}}</q-item-label>
</q-item-section>
Expand Down Expand Up @@ -64,13 +64,16 @@
<script setup lang="ts">
import { computed, defineEmits, ref } from 'vue';
import { useRouter } from 'vue-router';
import { endSession } from 'src/common/api-requests/session';
import { useI18n } from 'vue-i18n';
import { setI18nLanguage } from 'boot/i18n';
import { logger } from 'src/boot/logger';
import { endSession } from 'src/common/api-requests/session';
import { useSessionStore } from 'src/stores/session';
import type { CourseSettingInfo } from 'src/common/models/settings';
import { useSettingsStore } from 'src/stores/settings';
import { logger } from 'src/boot/logger';
import type { CourseSettingInfo } from 'src/common/models/settings';
defineEmits(['toggle-menu', 'toggle-sidebar']);
const session = useSessionStore();
Expand All @@ -84,18 +87,17 @@ const full_name = computed(() => session.full_name);
const user_courses = computed(() =>
session.user_courses.filter(course => course.course_name !== current_course_name.value));
const changeCourse = (course_id: number, course_name: string) => {
const new_course = session.user_courses.find(course => course.course_name === course_name);
const changeCourse = (course_id: number) => {
logger.debug(`[MenuBar/changeCourse]: changing the course to #${course_id}`);
session.setCourse(course_id);
if (new_course != undefined) {
router.push(`/courses/${new_course.course_id}`).then(() => {
session.setCourse({
course_name: new_course.course_name,
course_id: new_course.course_id
});
}).catch(() => {
logger.error('[MenuBar/changeCourse]: Error occurred.');
});
// This sets the path to the instructor or student dashboard.
// This only works currently for roles of student/instructor. We'll need to think about
// the UI for other roles.
if (!session.course.role || session.course.role == 'unknown') {
logger.error(`[MenuBar/changeCourse]: the role is not defined for course #${course_id}`);
} else {
void router.push(`/courses/${course_id}/${session.course.role}`);
}
};
Expand All @@ -106,6 +108,6 @@ const availableLocales = computed(() =>
const logout = async () => {
await endSession();
void session.logout();
void router.push('/login');
void router.push({ name: 'login' });
};
</script>
8 changes: 6 additions & 2 deletions src/layouts/MenuSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,25 @@
import { defineComponent, ref, computed } from 'vue';
import { instructor_views, admin_views, student_views, ViewInfo } from 'src/common/views';
import { useRouter, useRoute } from 'vue-router';
import { useSessionStore } from 'src/stores/session';
export default defineComponent({
name: 'MenuSidebar',
setup() {
const route = useRoute();
const router = useRouter();
const session = useSessionStore();
const sidebar_open = ref<boolean>(false);
return {
sidebar_open,
views: computed(() =>
/^\/admin/.exec(route.path)
? admin_views
: /^\/courses\/\d+\/instructor/.exec(route.path)
: session.course.role === 'instructor'
? instructor_views
: student_views
: session.course.role === 'student'
? student_views
: []
),
changeView: (view: ViewInfo) => {
void router.push({ name: view.component_name, params: route.params });
Expand Down
8 changes: 8 additions & 0 deletions src/pages/instructor/Dashboard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<div class="q-pa-md">
<div class="text-h6">Instructor Dashboard</div>
<p>This is the instructor landing page/dashboard. To get started select a tool
from the list on the left.
</p>
</div>
</template>
Loading

0 comments on commit 4bad6ca

Please sign in to comment.