From cd9b3e35af5662cb20205b367052d15489d6941a Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Wed, 26 Sep 2018 16:20:06 +0200 Subject: [PATCH] type safe root actions and mutations --- frontend/src/api.ts | 4 +- frontend/src/components/DataExport.vue | 6 +- .../submission_notes/SubmissionCorrection.vue | 2 - frontend/src/store/actions.ts | 208 +++++++++--------- frontend/src/store/getters.ts | 36 +-- .../modules/feedback_list/feedback-table.ts | 5 +- .../src/store/modules/submission-notes.ts | 2 +- frontend/src/store/store.ts | 6 +- 8 files changed, 131 insertions(+), 138 deletions(-) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index efa7c9de..3d026051 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -192,12 +192,12 @@ export async function deleteAssignment ({assignment}: {assignment: Assignment}): return ax.delete(url) } -export async function deleteComment (comment = {pk: undefined}): Promise<AxiosResponse<void>> { +export async function deleteComment (comment: FeedbackComment): Promise<AxiosResponse<void>> { const url = `/api/feedback-comment/${comment.pk}/` return ax.delete(url) } -export async function patchComment (comment = {pk: undefined}): Promise<FeedbackComment> { +export async function patchComment (comment: FeedbackComment): Promise<FeedbackComment> { const url = `/api/feedback-comment/${comment.pk}/` return (await ax.patch(url, comment)).data } diff --git a/frontend/src/components/DataExport.vue b/frontend/src/components/DataExport.vue index 3daa7cc7..dc13bdf1 100644 --- a/frontend/src/components/DataExport.vue +++ b/frontend/src/components/DataExport.vue @@ -41,7 +41,7 @@ </template> <script> -import {mapGetters} from 'vuex' +import {getters} from '@/store/getters' import ax from '@/api' import FileSelect from '@/components/util/FileSelect' import { mutations as mut } from '@/store/mutations' @@ -59,9 +59,7 @@ export default { } }, computed: { - ...mapGetters([ - 'corrected' - ]), + corrected () { return getters.corrected }, exportColor () { return this.corrected ? 'green darken-1' : 'red lighten-1' }, diff --git a/frontend/src/components/submission_notes/SubmissionCorrection.vue b/frontend/src/components/submission_notes/SubmissionCorrection.vue index d7b5c17f..a23d4111 100644 --- a/frontend/src/components/submission_notes/SubmissionCorrection.vue +++ b/frontend/src/components/submission_notes/SubmissionCorrection.vue @@ -107,8 +107,6 @@ export default { 'isStudent', 'isTutor', 'isReviewer', - 'getSubmission', - 'getSubmissionType', 'workInProgress' ]), submission () { diff --git a/frontend/src/store/actions.ts b/frontend/src/store/actions.ts index 2ad5920a..e1fecc60 100644 --- a/frontend/src/store/actions.ts +++ b/frontend/src/store/actions.ts @@ -1,113 +1,103 @@ -import { mutations as mut } from './mutations' -import { authMut } from '@/store/modules/authentication' -import { subNotesMut } from '@/store/modules/submission-notes' -import * as api from '@/api' -import router from '@/router/index' -import { handleError } from '@/util/helpers' -import {ActionTree} from 'vuex' -import {RootState} from '@/store/store' +import { ActionContext } from "vuex"; +import { BareActionContext, getStoreBuilder } from "vuex-typex"; -const actions: ActionTree<RootState, RootState> = { - async getExamTypes ({commit, dispatch}) { - try { - const examTypes = await api.fetchExamTypes({}) - mut.SET_EXAM_TYPES(examTypes) - } catch (err) { - handleError(err, dispatch, 'Unable to fetch exam types') - } - }, - async updateSubmissionTypes ({commit, dispatch}, fields: Array<string> = []) { - try { - const submissionTypes = await api.fetchSubmissionTypes(fields) - submissionTypes.forEach(type => { - mut.UPDATE_SUBMISSION_TYPE(type) - }) - } catch (err) { - handleError(err, dispatch, 'Unable to get submission types') - } - }, - async getStudents ({commit, dispatch}, - opt: {studentPks: Array<string>, fields: Array<string>} = - {studentPks: [], fields: []}) { - try { - if (opt.studentPks.length === 0) { - const students = await api.fetchAllStudents() - mut.SET_STUDENTS(students) - return students - } else { - const students = await Promise.all( - opt.studentPks.map((pk: string) => api.fetchStudent({ - pk, - fields: opt.fields - })) - ) - students.forEach(student => mut.SET_STUDENT(student)) - return students - } - } catch (err) { - console.log(err) - handleError(err, dispatch, 'Unable to fetch student data') - } - }, - async getTutors ({commit, dispatch}) { - try { - const tutors = await api.fetchAllTutors() - mut.SET_TUTORS(tutors) - } catch (err) { - handleError(err, dispatch, 'Unable to fetch tutor data') - } - }, - async getFeedback ({commit, dispatch}, {ofSubmission}) { - try { - const feedback = await api.fetchFeedback({ofSubmission}) - mut.SET_FEEDBACK(feedback) - return feedback - } catch (err) { - handleError(err, dispatch, `Unable to fetch feedback ${ofSubmission}`) - } - }, - async getSubmissionFeedbackTest ({commit, dispatch}, {pk}) { - try { - const submission = await api.fetchSubmissionFeedbackTests({pk}) - mut.SET_SUBMISSION(submission) - } catch (err) { - handleError(err, dispatch, 'Unable to fetch submission') - } - }, - async getStatistics ({commit, dispatch}, opt) { - try { - const statistics = await api.fetchStatistics(opt) - mut.SET_STATISTICS(statistics) - } catch (err) { - handleError(err, dispatch, 'Unable to fetch statistics') - } - }, - async deleteComment ({commit, dispatch}, comment) { - try { - await api.deleteComment(comment) - } catch (err) { - handleError(err, dispatch, `Unable to delete comment: ${comment.text}`) - } - }, - async patchComment ({commit, dispatch}, comment) { - try { - return await api.patchComment(comment) - } catch (err) { - handleError(err, dispatch, `Unable to patch comment: ${comment.text}`) - } - }, - logout ({ commit, getters, state }, message = '') { - if (getters.isStudent) { - // change active to false when user logs out - // TODO this should belong in auth module - api.changeActiveForUser((state as any).authentication.user.pk, false) - } - mut.RESET_STATE() - commit('submissionNotes/' + subNotesMut.RESET_STATE) - commit(authMut.SET_MESSAGE, message) - router.push({name: 'login'}) - router.go(0) +import { mutations as mut } from "./mutations"; +import { authMut } from "@/store/modules/authentication"; +import { subNotesMut } from "@/store/modules/submission-notes"; +import * as api from "@/api"; +import router from "@/router/index"; +import { RootState } from "@/store/store"; + +async function getExamTypes(context: BareActionContext<RootState, RootState>) { + const examTypes = await api.fetchExamTypes({}); + mut.SET_EXAM_TYPES(examTypes); +} +async function updateSubmissionTypes( + context: BareActionContext<RootState, RootState>, + fields: Array<string> = [] +) { + const submissionTypes = await api.fetchSubmissionTypes(fields); + submissionTypes.forEach(type => { + mut.UPDATE_SUBMISSION_TYPE(type); + }); +} +async function getStudents( + context: BareActionContext<RootState, RootState>, + opt: { studentPks: Array<string>; fields: Array<string> } = { + studentPks: [], + fields: [] } +) { + if (opt.studentPks.length === 0) { + const students = await api.fetchAllStudents(); + mut.SET_STUDENTS(students); + return students; + } else { + const students = await Promise.all( + opt.studentPks.map((pk: string) => + api.fetchStudent({ + pk, + fields: opt.fields + }) + ) + ); + students.forEach(student => mut.SET_STUDENT(student)); + return students; + } +} +async function getTutors() { + const tutors = await api.fetchAllTutors(); + mut.SET_TUTORS(tutors); +} +async function getFeedback( + context: BareActionContext<RootState, RootState>, + fetchFeedbackArg: { ofSubmission: string } +) { + const feedback = await api.fetchFeedback(fetchFeedbackArg); + mut.SET_FEEDBACK(feedback); + return feedback; } +async function getSubmissionFeedbackTest( + context: BareActionContext<RootState, RootState>, + submissionPkObj: { pk: string } +) { + const submission = await api.fetchSubmissionFeedbackTests(submissionPkObj); + mut.SET_SUBMISSION(submission); +} +async function getStatistics() { + const statistics = await api.fetchStatistics(); + mut.SET_STATISTICS(statistics); +} +function logout( + context: BareActionContext<RootState, RootState>, + message = "" +) { + // logout actually receives a normal ActionContext, but for some reason vuex-typex mandates a BareActionContext + // TODO there has to be a better way + const { commit, getters, state } = <ActionContext<RootState, RootState>>( + context + ); + if (getters.isStudent) { + // change active to false when user logs out + // TODO this should belong in auth module + api.changeActiveForUser((state as any).authentication.user.pk, false); + } + mut.RESET_STATE(); + commit("submissionNotes/" + subNotesMut.RESET_STATE); + commit(authMut.SET_MESSAGE, message); + router.push({ name: "login" }); + router.go(0); +} + +const mb = getStoreBuilder<RootState>(); -export default actions +export const actions = { + updateSubmissionTypes: mb.dispatch(getExamTypes), + getExamTypes: mb.dispatch(updateSubmissionTypes), + getStudents: mb.dispatch(getStudents), + getTutors: mb.dispatch(getTutors), + getFeedback: mb.dispatch(getFeedback), + getSubmissionFeedbackTest: mb.dispatch(getSubmissionFeedbackTest), + getStatistics: mb.dispatch(getStatistics), + logout: mb.dispatch(logout) +}; diff --git a/frontend/src/store/getters.ts b/frontend/src/store/getters.ts index 44e8a56d..c36c0d45 100644 --- a/frontend/src/store/getters.ts +++ b/frontend/src/store/getters.ts @@ -1,18 +1,26 @@ import {RootState} from '@/store/store' -import {GetterTree} from 'vuex' +import {getStoreBuilder} from 'vuex-typex' -const getters: GetterTree<RootState, RootState> = { - corrected (state) { - return state.statistics.submissionTypeProgress.every(progress => { - return progress.feedbackFinal === progress.submissionCount - }) - }, - getSubmission: state => (pk: string) => { - return state.submissions[pk] - }, - getSubmissionType: state => (pk: string) => { - return state.submissionTypes[pk] +const mb = getStoreBuilder<RootState>() + +const correctedGetter = mb.read(function corrected (state) { + return state.statistics.submissionTypeProgress.every(progress => { + return progress.feedbackFinal === progress.submissionCount + }) +}) +const submissionGetter = mb.read(function submission (state) { + return (pk: string) => { + return state.submissions[pk] } -} +}) +const submissionTypeGetter = mb.read(function submissionType (state) { + return (pk: string) => { + return state.submissionTypes[pk] + } +}) -export default getters +export const getters = { + get corrected () { return correctedGetter() }, + get submission () { return submissionGetter() }, + get submissionType () { return submissionTypeGetter() } +} diff --git a/frontend/src/store/modules/feedback_list/feedback-table.ts b/frontend/src/store/modules/feedback_list/feedback-table.ts index 02337c19..010cc10e 100644 --- a/frontend/src/store/modules/feedback_list/feedback-table.ts +++ b/frontend/src/store/modules/feedback_list/feedback-table.ts @@ -3,6 +3,7 @@ import {objectifyArray} from '@/util/helpers' import {Assignment, Feedback, Subscription} from '@/models' import {Module} from 'vuex' import {RootState} from '@/store/store' +import {getters} from '@/store/getters' export const feedbackTableMut = Object.freeze({ SET_FEEDBACK_HISTORY: 'SET_FEEDBACK_HISTORY', @@ -58,9 +59,9 @@ const feedbackTable: Module<FeedbackTableState, RootState> = { [feedbackTableMut.RESET_STATE]: (state) => { Object.assign(state, initialState()) } }, actions: { - mapFeedbackHistOfSubmissionType ({getters, commit, state}) { + mapFeedbackHistOfSubmissionType ({commit, state}) { for (const feedback of Object.values(state.feedbackHist)) { - const type = getters.getSubmissionType((feedback as any).ofSubmissionType) + const type = getters.submissionType((feedback as any).ofSubmissionType) commit(feedbackTableMut.SET_FEEDBACK_OF_SUBMISSION_TYPE, {feedback, type}) } }, diff --git a/frontend/src/store/modules/submission-notes.ts b/frontend/src/store/modules/submission-notes.ts index ea23fe7b..734acc86 100644 --- a/frontend/src/store/modules/submission-notes.ts +++ b/frontend/src/store/modules/submission-notes.ts @@ -146,7 +146,7 @@ const submissionNotes: Module<SubmissionNotesState, RootState> = { deleteComments: async function ({state}) { return Promise.all( Object.values(state.commentsMarkedForDeletion).map(comment => { - return api.deleteComment((comment as any)) + return api.deleteComment(comment) }) ) }, diff --git a/frontend/src/store/store.ts b/frontend/src/store/store.ts index 1398aa63..c7379d0c 100644 --- a/frontend/src/store/store.ts +++ b/frontend/src/store/store.ts @@ -12,8 +12,8 @@ import feedbackTable from './modules/feedback_list/feedback-table' import feedbackSearchOptions from './modules/feedback_list/feedback-search-options' import './mutations' -import actions from './actions' -import getters from './getters' +import './actions' +import './getters' import {lastInteraction} from '@/store/plugins/lastInteractionPlugin' import { Exam, Feedback, @@ -81,8 +81,6 @@ const store = getStoreBuilder<RootState>().vuexStore({ 'authentication.tokenCreationTime']) }), lastInteraction], - actions, - getters, state: initialState() }) -- GitLab