diff --git a/core/serializers/tutor.py b/core/serializers/tutor.py index c661b097e94f344909869781e672231630dc1aeb..0bcd8e61219319829f87c336ac70e28437bff063 100644 --- a/core/serializers/tutor.py +++ b/core/serializers/tutor.py @@ -16,7 +16,7 @@ user_factory = GradyUserFactory() class TutorSerializer(DynamicFieldsModelSerializer): feedback_created = serializers.SerializerMethodField() feedback_validated = serializers.SerializerMethodField() - password = password = serializers.CharField( + password = serializers.CharField( style={'input_type': 'password'}, write_only=True, required=False diff --git a/core/views/common_views.py b/core/views/common_views.py index ad08eb76b6b0a37d4c7faa8fe27bab67be25e775..4444f9e8c1e357164c0b7715cca2d71b4ea676d0 100644 --- a/core/views/common_views.py +++ b/core/views/common_views.py @@ -11,7 +11,7 @@ from django.core import exceptions from rest_framework import generics, mixins, status, viewsets from rest_framework.decorators import (api_view, list_route, throttle_classes, - detail_route) + action) from rest_framework.exceptions import PermissionDenied from rest_framework.permissions import AllowAny from rest_framework.response import Response @@ -178,7 +178,7 @@ class UserAccountViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = UserAccountSerializer queryset = models.UserAccount.objects.all() - @detail_route(methods=['patch'], permission_classes=(IsTutorOrReviewer, )) + @action(detail=True, methods=['patch'], permission_classes=(IsTutorOrReviewer, )) def change_password(self, request, *args, **kwargs): user = self.get_object() if request.user != user and not request.user.is_reviewer(): @@ -208,12 +208,7 @@ class UserAccountViewSet(viewsets.ReadOnlyModelViewSet): log.info(f"User {request.user} changed password of {user}") return Response(status=status.HTTP_200_OK) - @list_route() - def me(self, request): - serializer = self.get_serializer(request.user) - return Response(serializer.data, status=status.HTTP_200_OK) - - @detail_route(methods=['patch']) + @action(detail=True, methods=['patch']) def change_active(self, request, *args, **kwargs): active = request.data.get('is_active') req_user = request.user @@ -226,3 +221,8 @@ class UserAccountViewSet(viewsets.ReadOnlyModelViewSet): user.is_active = active user.save() return Response(status.HTTP_200_OK) + + @action(detail=False) + def me(self, request): + serializer = self.get_serializer(request.user) + return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/core/views/subscription.py b/core/views/subscription.py index 658ef588f5bdc4bad82da47db3f5c5e9323c66ec..7c60793c498fd8c8569fbea4b6f227bf92dd88c5 100644 --- a/core/views/subscription.py +++ b/core/views/subscription.py @@ -2,6 +2,7 @@ import logging from django.core.exceptions import ObjectDoesNotExist from rest_framework import mixins, status, viewsets +from rest_framework.decorators import action from rest_framework.response import Response from core import models, permissions, serializers @@ -75,7 +76,7 @@ class AssignmentApiViewSet( serializer_class = AssignmentSerializer def get_queryset(self): - if self.action == 'list': + if self.action in ['list', 'active', 'destroy']: return self.queryset.all() else: return self.queryset.filter(subscription__owner=self.request.user) @@ -101,11 +102,23 @@ class AssignmentApiViewSet( status=status.HTTP_403_FORBIDDEN) return Response(serializer.data, status=status.HTTP_201_CREATED) + @action(detail=False, permission_classes=(IsReviewer,), methods=['get', 'delete']) + def active(self, request): + print(request.method) + if request.method == 'GET': + queryset = self.get_queryset().filter(is_done=False) + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) + else: + self.get_queryset().filter(is_done=False).delete() + return Response(status=status.HTTP_204_NO_CONTENT) + def destroy(self, request, pk=None): """ Stop working on the assignment before it is finished """ instance = self.get_object() - if instance.is_done or instance.subscription.owner != request.user: + if instance.is_done or (instance.subscription.owner != request.user and + not request.user.is_reviewer()): return Response(status=status.HTTP_403_FORBIDDEN) instance.delete() diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index 240453a5e7b66ea3dc0ab20567062157a0164ca9..5cfdc93a212ef36aff5e5d1782d50e24b3df3ab1 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -15,4 +15,4 @@ module.exports = { parserOptions: { parser: 'typescript-eslint-parser' } -} \ No newline at end of file +} diff --git a/frontend/.postcssrc.js b/frontend/.postcssrc.js index 100cc01248a1ecefabb1ac255c6cdc762159b601..961986e2b11eeebe1d4ddabdf2e6c85e2a2562e0 100644 --- a/frontend/.postcssrc.js +++ b/frontend/.postcssrc.js @@ -2,4 +2,4 @@ module.exports = { plugins: { autoprefixer: {} } -} \ No newline at end of file +} diff --git a/frontend/src/api.ts b/frontend/src/api.ts index ec02ebddefa9f4bc9a5f8ec901f8df1a5e5bb5e3..1d081916116a950f5183533f96fc691cca58dbbf 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -153,7 +153,7 @@ export async function submitUpdatedFeedback ({ feedback }: {feedback: Feedback}) } export async function fetchSubmissionTypes (): Promise<Array<SubmissionType>> { - let url = '/api/submissiontype/' + const url = '/api/submissiontype/' return (await ax.get(url)).data } @@ -162,11 +162,21 @@ export async function fetchAllAssignments (): Promise<Array<Assignment>> { return (await ax.get(url)).data } +export async function fetchActiveAssignments (): Promise<Assignment[]> { + const url = '/api/assignment/active/' + return (await ax.get(url)).data +} + export async function deleteAssignment ({ assignment }: {assignment: Assignment}): Promise<AxiosResponse<void>> { const url = `/api/assignment/${assignment.pk}/` return ax.delete(url) } +export async function deleteAllActiveAssignments () { + const url = '/api/assignment/active/' + return ax.delete(url) +} + export async function deleteComment (comment: FeedbackComment): Promise<AxiosResponse<void>> { const url = `/api/feedback-comment/${comment.pk}/` return ax.delete(url) diff --git a/frontend/src/components/UserOptions.vue b/frontend/src/components/UserOptions.vue index f7fc57c0b0f5c93625b88352062d1c9f37fe078f..465288e3113d22da164e6368496fcacf04283e54 100644 --- a/frontend/src/components/UserOptions.vue +++ b/frontend/src/components/UserOptions.vue @@ -18,6 +18,7 @@ <script> import PasswordChangeDialog from '@/components/PasswordChangeDialog' import { Authentication } from '@/store/modules/authentication' +import { deleteAllActiveAssignments } from '@/api' export default { name: 'UserOptions', components: { PasswordChangeDialog }, @@ -29,6 +30,11 @@ export default { display: 'Change password', action: () => { this.displayComponent = PasswordChangeDialog }, condition: () => !Authentication.isStudent + }, + { + display: 'Free all reserved submissions', + action: deleteAllActiveAssignments, + condition: () => Authentication.isReviewer } ] } diff --git a/frontend/src/components/feedback_list/FeedbackSearchOptions.vue b/frontend/src/components/feedback_list/FeedbackSearchOptions.vue index 55c0e341a5bd7a0ee1bf30bba71348cfbac311ae..b88d8a82aeaebb7e68978a11bbfa66b4d8273aa2 100644 --- a/frontend/src/components/feedback_list/FeedbackSearchOptions.vue +++ b/frontend/src/components/feedback_list/FeedbackSearchOptions.vue @@ -68,12 +68,13 @@ import { mapStateToComputedGetterSetter } from '@/util/helpers' import { Authentication } from '@/store/modules/authentication' import { actions } from '@/store/actions' import { getters } from '@/store/getters' +import { TutorOverview } from '@/store/modules/tutor-overview' @Component export default class FeedbackSearchOptions extends Vue { feedbackStages = ['Initial feedback', 'Validation'] - get tutors () { return getters.state.tutors } + get tutors () { return TutorOverview.state.tutors } get isReviewer () { return Authentication.isReviewer } get tutorNames () { @@ -108,7 +109,7 @@ export default class FeedbackSearchOptions extends Vue { loadTutors () { if (this.tutors.length === 0 && this.isReviewer) { - actions.getTutors() + TutorOverview.getTutors() } } created () { diff --git a/frontend/src/components/mixins/mixins.ts b/frontend/src/components/mixins/mixins.ts index ba6ab61f3b80af922f4f45e0dfc51d9a901f33fa..caae87fff23b45ef41a8dcdd20a0135233e541bb 100644 --- a/frontend/src/components/mixins/mixins.ts +++ b/frontend/src/components/mixins/mixins.ts @@ -1,4 +1,4 @@ -import {Vue, Component} from 'vue-property-decorator' +import { Vue, Component } from 'vue-property-decorator' @Component export class parseCSVMapMixin extends Vue { diff --git a/frontend/src/components/student_list/StudentList.vue b/frontend/src/components/student_list/StudentList.vue index dc9b6834184929446dbf2754cfc373c9e5d83822..bc87cf04b68d269864ecea435b00941d911b833a 100644 --- a/frontend/src/components/student_list/StudentList.vue +++ b/frontend/src/components/student_list/StudentList.vue @@ -139,14 +139,16 @@ export default { ]), submissionTypeHeaders () { const subTypes = Object.values(getters.state.submissionTypes) - return subTypes.map(type => { - return { - pk: type.pk, - text: type.name.substr(0, 5), - value: `${type.pk}.score`, - align: 'right' - } - }) + return subTypes + .sort((a, b) => a.name.localeCompare(b.name)) + .map(type => { + return { + pk: type.pk, + text: type.name.substr(0, 5), + value: `${type.pk}.score`, + align: 'right' + } + }) }, dynamicHeaders () { const totalScoreHeader = { diff --git a/frontend/src/components/tutor_list/TutorList.vue b/frontend/src/components/tutor_list/TutorList.vue index 4ab4b30c5f26c2aa33091eda5b3047ccdda2492f..c5f18a442c1ee0eb8ee6f175394e2472e3553ae8 100644 --- a/frontend/src/components/tutor_list/TutorList.vue +++ b/frontend/src/components/tutor_list/TutorList.vue @@ -1,84 +1,116 @@ <template> - <v-flex md4> - <v-data-table - :headers="headers" - :items="tutors" - :search="search" - item-key="name" - hide-actions - > - <template slot="items" slot-scope="props"> - <td>{{props.item.username}}</td> - <td class="text-xs-right">{{props.item.feedbackCreated}}</td> - <td class="text-xs-right">{{props.item.feedbackValidated}}</td> - <td class="text-xs-right"> - <v-btn icon @click="changeActiveStatus(props.item)"> - <v-tooltip top> - <template slot="activator"> - <v-icon small v-if="!props.item.isActive">lock</v-icon> - <v-icon small v-else>lock_open</v-icon> - </template> - <span v-if="!props.item.isActive">Grant access</span> - <span v-else>Revoke access</span> + <v-flex xs5> + <v-card> + <v-card-title class="title"> + Tutors + <v-spacer/> + <v-btn icon @click="refresh"><v-icon>refresh</v-icon></v-btn> + </v-card-title> + <v-data-table + :headers="headers" + :items="tutors" + :search="search" + item-key="pk" + hide-actions + > + <template slot="items" slot-scope="props"> + <td>{{props.item.username}}</td> + <td class="text-xs-right">{{props.item.feedbackCreated}}</td> + <td class="text-xs-right">{{props.item.feedbackValidated}}</td> + <td class="text-xs-right"> + {{props.item.reservedSubmissions}} + <v-tooltip top v-if="props.item.reservedSubmissions"> + <template slot="activator"> + <v-icon small @click="deleteAssignmentsOfTutor(props.item)">clear</v-icon> + </template> + <span>Free reserved submissions</span> </v-tooltip> - </v-btn> - </td> - </template> - </v-data-table> + </td> + <td class="text-xs-right"> + <v-btn icon @click="changeActiveStatus(props.item)"> + <v-tooltip top> + <template slot="activator"> + <v-icon small v-if="!props.item.isActive">lock</v-icon> + <v-icon small v-else>lock_open</v-icon> + </template> + <span v-if="!props.item.isActive">Grant access</span> + <span v-else>Revoke access</span> + </v-tooltip> + </v-btn> + </td> + </template> + </v-data-table> + </v-card> </v-flex> </template> -<script> -import { mapState, mapActions } from 'vuex' +<script lang="ts"> +import { Vue, Component } from 'vue-property-decorator' import { changeActiveForUser } from '@/api' import { actions } from '@/store/actions' +import { TutorOverview } from '@/store/modules/tutor-overview' +import { Tutor } from '@/models' -export default { - name: 'tutor-list', - data () { - return { - search: '', - headers: [ - { - text: 'Name', - align: 'left', - value: 'username' - }, - { - text: '# created', - align: 'right', - value: 'feedbackCreated' - }, - { - text: '# validated', - align: 'right', - value: 'feedbackValidated' - }, - { - text: 'Has Access', - align: 'right', - value: 'isActive' - } - ] +@Component +export default class TutorList extends Vue { + search = '' + headers = [ + { + text: 'Name', + align: 'left', + value: 'username' + }, + { + text: '# created', + align: 'right', + value: 'feedbackCreated' + }, + { + text: '# validated', + align: 'right', + value: 'feedbackValidated' + }, + { + text: '# reserved submissions', + align: 'right', + value: 'reservedSubmissions' + }, + { + text: 'Has Access', + align: 'right', + value: 'isActive' } - }, - computed: { - ...mapState([ - 'tutors' - ]) - }, - methods: { - changeActiveStatus (tutor) { - changeActiveForUser(tutor.pk, !tutor.isActive).then(() => { - actions.getTutors() - }).catch(() => { - this.$notify({ - title: 'Error', - text: `Unable to change active status of ${tutor.username}`, - type: 'error' - }) + ] + + get tutors () { + return TutorOverview.state.tutors.map(tutor => { + const reservedSubmissions = TutorOverview.state.activeAssignments[tutor.username] + return { + ...tutor, + reservedSubmissions: reservedSubmissions ? reservedSubmissions.length : 0 + } + }) + } + + changeActiveStatus (tutor: Tutor) { + changeActiveForUser(tutor.pk, !tutor.isActive).then(() => { + TutorOverview.getTutors() + }).catch(() => { + this.$notify({ + title: 'Error', + text: `Unable to change active status of ${tutor.username}`, + type: 'error' }) - } + }) + } + + deleteAssignmentsOfTutor (tutor: Tutor) { + TutorOverview.deleteActiveAssignmentsOfTutor(tutor) + } + + refresh () { + TutorOverview.getTutors() + TutorOverview.getActiveAssignments() } } </script> diff --git a/frontend/src/pages/reviewer/TutorOverviewPage.vue b/frontend/src/pages/reviewer/TutorOverviewPage.vue index e97d51c2d604c105ec85a44bb4d08dfac5dec7a1..7b6039cb5663b55f56c9c8009ed345431ecffcc0 100644 --- a/frontend/src/pages/reviewer/TutorOverviewPage.vue +++ b/frontend/src/pages/reviewer/TutorOverviewPage.vue @@ -6,12 +6,14 @@ import store from '@/store/store' import TutorList from '@/components/tutor_list/TutorList' import { actions } from '@/store/actions' +import { TutorOverview } from '@/store/modules/tutor-overview' export default { components: { TutorList }, name: 'tutor-overview-page', beforeRouteEnter (to, from, next) { - actions.getTutors() + TutorOverview.getTutors() + TutorOverview.getActiveAssignments() next() } } diff --git a/frontend/src/pages/student/StudentSubmissionPage.vue b/frontend/src/pages/student/StudentSubmissionPage.vue index b1d0306d666b2b8822857e783c1ab0254766d57d..d7827230e36a20e76768e3e6efbcaa4c528bbd21 100644 --- a/frontend/src/pages/student/StudentSubmissionPage.vue +++ b/frontend/src/pages/student/StudentSubmissionPage.vue @@ -20,7 +20,7 @@ <h2>Score: {{feedback ? feedback.score : 'N/A'}} / {{submissionType.fullScore}}</h2> </v-toolbar> <template slot="table-content"> - <tr v-for="(code, lineNo) in submission" :key="lineNo"> + <tr v-for="(code, lineNo) in submissionText" :key="lineNo"> <submission-line :code="code" :lineNo="lineNo"> <template v-if="feedback"> <template v-for="(comment, index) in feedback.feedbackLines[lineNo]"> @@ -38,7 +38,7 @@ </template> </base-annotated-submission> <submission-tests - :tests="submission.tests" + :tests="tests" :expand="true" class="mt-3" /> @@ -80,7 +80,8 @@ export default { id: function () { return this.$route.params.id }, - submission () { return SubmissionNotes.submission }, + submissionText () { return SubmissionNotes.submission }, + tests () { return SubmissionNotes.state.submission.tests }, showFeedback: function (state) { return SubmissionNotes.state.ui.showFeedback }, submissionType () { return StudentPage.state.submissionData[this.id].type }, feedback () { return StudentPage.state.submissionData[this.$route.params.id].feedback } diff --git a/frontend/src/store/actions.ts b/frontend/src/store/actions.ts index 7ce8219ae8577f567239625822a05589ac88d65f..ac0982b9246e39c57531345326c100f5b9f54173 100644 --- a/frontend/src/store/actions.ts +++ b/frontend/src/store/actions.ts @@ -9,6 +9,8 @@ import router from '@/router/index' import { RootState } from '@/store/store' import { FeedbackTable } from '@/store/modules/feedback_list/feedback-table' import { Subscriptions } from '@/store/modules/subscriptions' +import { TutorOverview } from './modules/tutor-overview' +import { StudentPage } from './modules/student-page' async function getExamTypes (context: BareActionContext<RootState, RootState>) { const examTypes = await api.fetchExamTypes() @@ -42,10 +44,6 @@ async function getStudents ( return students } } -async function getTutors () { - const tutors = await api.fetchAllTutors() - mut.SET_TUTORS(tutors) -} async function getSubmissionFeedbackTest ( context: BareActionContext<RootState, RootState>, submissionPkObj: { pk: string } @@ -62,8 +60,10 @@ function resetState ({ message }: {message: string}) { FeedbackTable.RESET_STATE() Subscriptions.RESET_STATE() SubmissionNotes.RESET_STATE() + StudentPage.RESET_STATE() Authentication.RESET_STATE() Authentication.SET_MESSAGE(message) + TutorOverview.RESET_STATE() mut.RESET_STATE() } @@ -87,7 +87,6 @@ export const actions = { updateSubmissionTypes: mb.dispatch(updateSubmissionTypes), getExamTypes: mb.dispatch(getExamTypes), getStudents: mb.dispatch(getStudents), - getTutors: mb.dispatch(getTutors), getSubmissionFeedbackTest: mb.dispatch(getSubmissionFeedbackTest), getStatistics: mb.dispatch(getStatistics), logout: mb.dispatch(logout) diff --git a/frontend/src/store/modules/tutor-overview.ts b/frontend/src/store/modules/tutor-overview.ts new file mode 100644 index 0000000000000000000000000000000000000000..f616e5b068e1fec676726d99f3da256049692fde --- /dev/null +++ b/frontend/src/store/modules/tutor-overview.ts @@ -0,0 +1,71 @@ +import { fetchAllTutors, fetchActiveAssignments, deleteAssignment } from '@/api' +import { Tutor, Assignment } from '@/models' +import { RootState } from '../store' +import { getStoreBuilder, BareActionContext } from 'vuex-typex' +import { objectifyArray } from '@/util/helpers' + +export interface TutorOverviewState { + tutors: Tutor[], + activeAssignments: {[ownerName: string]: Assignment[]} +} + +function initialState (): TutorOverviewState { + return { + tutors: [], + activeAssignments: {} + } +} + +type Context = BareActionContext<TutorOverviewState, RootState> + +const mb = getStoreBuilder<RootState>().module('TutorOverview', initialState()) + +const stateGetter = mb.state() + +function SET_TUTORS (state: TutorOverviewState, tutors: Tutor[]) { + state.tutors = tutors +} + +function SET_ASSIGNMENTS (state: TutorOverviewState, assignments: Assignment[]) { + state.activeAssignments = assignments.reduce((acc: {[ownerName: string]: Assignment[]}, curr) => { + if (!curr.owner) { + throw new Error('Assignments must have owner information') + } + acc[curr.owner] ? acc[curr.owner].push(curr) : acc[curr.owner] = [curr] + return acc + }, {}) +} + +function RESET_STATE (state: TutorOverviewState) { + Object.assign(state, initialState()) +} + +async function getTutors () { + const tutors = await fetchAllTutors() + TutorOverview.SET_TUTORS(tutors) +} + +async function getActiveAssignments () { + const assignments = await fetchActiveAssignments() + TutorOverview.SET_ASSIGNMENTS(assignments) +} + +async function deleteActiveAssignmentsOfTutor ({ state }: Context, tutor: Tutor) { + const assignments = state.activeAssignments[tutor.username] + const promises = assignments.map(assignment => { return deleteAssignment({ assignment }) }) + Promise.all(promises).finally(() => { + TutorOverview.getActiveAssignments() + }) +} + +export const TutorOverview = { + get state () { return stateGetter() }, + + SET_TUTORS: mb.commit(SET_TUTORS), + SET_ASSIGNMENTS: mb.commit(SET_ASSIGNMENTS), + RESET_STATE: mb.commit(RESET_STATE), + + getTutors: mb.dispatch(getTutors), + getActiveAssignments: mb.dispatch(getActiveAssignments), + deleteActiveAssignmentsOfTutor: mb.dispatch(deleteActiveAssignmentsOfTutor) +} diff --git a/frontend/src/store/mutations.ts b/frontend/src/store/mutations.ts index c2a79d5f70dd51b92450e24f7998f047db382170..24bea75632ec7fbc8bd0d82350d7e4077ab62338 100644 --- a/frontend/src/store/mutations.ts +++ b/frontend/src/store/mutations.ts @@ -34,9 +34,6 @@ export interface StudentMap { function SET_STUDENT_MAP (state: RootState, map: StudentMap) { state.studentMap = map } -function SET_TUTORS (state: RootState, tutors: Array<Tutor>) { - state.tutors = tutors -} function SET_SUBMISSION (state: RootState, submission: SubmissionNoType) { Vue.set(state.submissions, submission.pk, submission) } @@ -80,7 +77,6 @@ export const mutations = { SET_STUDENTS: mb.commit(SET_STUDENTS), SET_STUDENT: mb.commit(SET_STUDENT), SET_STUDENT_MAP: mb.commit(SET_STUDENT_MAP), - SET_TUTORS: mb.commit(SET_TUTORS), SET_SUBMISSION: mb.commit(SET_SUBMISSION), SET_STATISTICS: mb.commit(SET_STATISTICS), UPDATE_SUBMISSION_TYPE: mb.commit(UPDATE_SUBMISSION_TYPE), diff --git a/frontend/src/store/store.ts b/frontend/src/store/store.ts index 6689ec636e2ec2f1b6c8825810dd28c0885badbd..b2e63118cc439965bf7a46b3665478162c52c8b1 100644 --- a/frontend/src/store/store.ts +++ b/frontend/src/store/store.ts @@ -11,6 +11,7 @@ import './modules/feedback_list/feedback-table' import './modules/subscriptions' import './modules/submission-notes' import './modules/student-page' +import './modules/tutor-overview' import './mutations' import './actions' @@ -24,6 +25,8 @@ import {SubscriptionsState} from './modules/subscriptions' import {FeedbackTableState} from './modules/feedback_list/feedback-table' import {SubmissionNotesState} from './modules/submission-notes' import {StudentPageState} from './modules/student-page' +import {TutorOverviewState, TutorOverview} from './modules/tutor-overview' + import {lastInteraction} from '@/store/plugins/lastInteractionPlugin' import { @@ -48,7 +51,6 @@ export interface RootInitialState { } } // is used to map obfuscated student data back to the original statistics: Statistics - tutors: Array<Tutor> } export interface RootState extends RootInitialState{ @@ -59,6 +61,7 @@ export interface RootState extends RootInitialState{ Subscriptions: SubscriptionsState, SubmissionNotes: SubmissionNotesState, StudentPage: StudentPageState + TutorOverview: TutorOverviewState } export function initialState (): RootInitialState { @@ -74,8 +77,7 @@ export function initialState (): RootInitialState { submissionsPerStudent: 0, currentMeanScore: 0, submissionTypeProgress: [] - }, - tutors: [] + } } } @@ -91,6 +93,7 @@ const store = getStoreBuilder<RootState>().vuexStore({ // when manually reloading the page paths: Object.keys(initialState()).concat( ['UI', 'StudentPage', 'SubmissionNotes', 'FeedbackSearchOptions', 'Subscriptions', + 'TutorOverview', 'Authentication.user', 'Authentication.jwtTimeDelta', 'Authentication.tokenCreationTime']) }), diff --git a/frontend/tests/unit/.eslintrc.js b/frontend/tests/unit/.eslintrc.js index 74fe62701f4124d7898e58976950739f406e4bf1..8038afe90da4952fc10d1163e06c2cc0ed68ab03 100644 --- a/frontend/tests/unit/.eslintrc.js +++ b/frontend/tests/unit/.eslintrc.js @@ -5,4 +5,4 @@ module.exports = { rules: { 'import/no-extraneous-dependencies': 'off' } -} \ No newline at end of file +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 59a075aa131f80c4080a3a6c8df151001c6a7bdd..38bf9405a4742ce190838be9196c643f81a319f5 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -17,6 +17,7 @@ }, "lib": [ "es2017", + "es2018.promise", "dom", "dom.iterable", "scripthost"