Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 169-add-date-to-examtype
  • 233-make-exam-a-many-to-many-field-on-studentinfo-model
  • 236-improve-importer-experience
  • 243-replace-toggle-buttons-with-switches
  • 250-update-vuetify
  • 258-add-markdown-viewer
  • 265-fix-selection-changing-on-window-switching
  • 272-reviewers-should-be-able-to-assign-exercise-groups-to-tutors
  • 276-create-new-yarn-lockfile
  • 279-tutor-overview-no-scrolling
  • 282-copy-button-does-not-work-when-reviewing-corrections
  • 286-fix-misalignment-of-hide-show-sidebar-buttons
  • 287-build-test-image-constantly-failing
  • 288-add-dropdown-to-participantspage-to-set-students-groups
  • 289-fix-change-log-card
  • 291-revise-to-old-export-scheme
  • 292-update-gitlab-ci-config-for-new-runner
  • 292-update-gitlab-ci-config-for-new-runner-2
  • add-exercise-util-script
  • document-frontend-components
  • grady-exam
  • jakob.dieterle-master-patch-13835
  • master
  • parallel-test
  • test-233-branch-remove-examtype-foreign-key-on-group
  • update-export-dialogs
  • 0.0.1
  • 0.1
  • 0.2
  • 0.3
  • 0.4
  • 0.4.1
  • 0.4.2
  • 0.5.0
  • 0.5.1
  • 1.0.0
  • 1.1.0
  • 2.0.0
  • 2.0.1
  • 2.1.0
  • 2.1.1
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 4.0.0
  • 4.1.0
  • 4.2.0
  • 4.3.0
  • 4.4.0
  • 4.4.1
  • 5.0.0
  • 5.0.1
  • 5.1.0
  • 5.1.1
  • 5.1.2
  • 5.1.3
  • 5.1.4
  • 5.1.5
  • 5.1.6
  • 5.1.7
  • 5.2.0
  • 5.3.0
  • 5.3.1
  • 5.3.2
  • 5.4.0
  • 5.4.1
  • 5.4.2
  • 6.0.0
  • 6.1.0
  • legacy
70 results

Target

Select target project
  • j.michal/grady
1 result
Select Git revision
  • 169-add-date-to-examtype
  • 233-make-exam-a-many-to-many-field-on-studentinfo-model
  • 236-improve-importer-experience
  • 243-replace-toggle-buttons-with-switches
  • 250-update-vuetify
  • 258-add-markdown-viewer
  • 265-fix-selection-changing-on-window-switching
  • 272-reviewers-should-be-able-to-assign-exercise-groups-to-tutors
  • 276-create-new-yarn-lockfile
  • 279-tutor-overview-no-scrolling
  • 282-copy-button-does-not-work-when-reviewing-corrections
  • 286-fix-misalignment-of-hide-show-sidebar-buttons
  • 287-build-test-image-constantly-failing
  • 288-add-dropdown-to-participantspage-to-set-students-groups
  • 289-fix-change-log-card
  • 291-revise-to-old-export-scheme
  • 292-update-gitlab-ci-config-for-new-runner
  • 292-update-gitlab-ci-config-for-new-runner-2
  • add-exercise-util-script
  • document-frontend-components
  • grady-exam
  • jakob.dieterle-master-patch-13835
  • master
  • parallel-test
  • test-233-branch-remove-examtype-foreign-key-on-group
  • update-export-dialogs
  • 0.0.1
  • 0.1
  • 0.2
  • 0.3
  • 0.4
  • 0.4.1
  • 0.4.2
  • 0.5.0
  • 0.5.1
  • 1.0.0
  • 1.1.0
  • 2.0.0
  • 2.0.1
  • 2.1.0
  • 2.1.1
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 4.0.0
  • 4.1.0
  • 4.2.0
  • 4.3.0
  • 4.4.0
  • 4.4.1
  • 5.0.0
  • 5.0.1
  • 5.1.0
  • 5.1.1
  • 5.1.2
  • 5.1.3
  • 5.1.4
  • 5.1.5
  • 5.1.6
  • 5.1.7
  • 5.2.0
  • 5.3.0
  • 5.3.1
  • 5.3.2
  • 5.4.0
  • 5.4.1
  • 5.4.2
  • 6.0.0
  • 6.1.0
  • legacy
70 results
Show changes
Showing
with 441 additions and 65 deletions
<template>
<v-card>
<v-card id="submission-tests">
<v-card-title class="title py-0" v-if="tests.length > 0">
Tests
<v-spacer/>
......@@ -12,15 +12,15 @@
No Tests available
</v-card-title>
<v-expansion-panel v-if="expanded">
<v-expansion-panel-content v-for="item in tests" :key="item.pk">
<div slot="header">
<v-layout row class="pr-4">
<v-expansion-panel-content v-for="item in tests" :key="item.pk" >
<div slot="header" name="test-name-label">
<v-layout row class="pr-4" >
{{item.name}}
<v-spacer/>
{{item.label}}
</v-layout>
</div>
<v-card>
<v-card name="test-output">
<v-card-text class="test-output">{{item.annotation}}</v-card-text>
</v-card>
</v-expansion-panel-content>
......
<template>
<v-layout column>
<v-card>
<v-card id="submission-type">
<v-card-title class="title mb-2">{{ name }} - Full score: {{ fullScore }}</v-card-title>
<v-expansion-panel expand v-model="expanded">
<v-expansion-panel-content
......
<template>
<v-dialog v-model="exportDialog" max-width="31vw" @update:returnValue="hide">
<v-card id="data-export-modal">
<v-card-title class="title">
Student Data Export
</v-card-title>
<v-card-text>
<div v-if="!mapFileLoaded">
If you select a mapping file, the anonymized data
will be mapped back automatically and locally on your machine.
<v-layout row align-center>
<file-select v-model="mapFile" display-text="Select map file" class="ma-3"/>
<span>Without the mapping, the data will still be obfuscated.</span>
</v-layout>
</div>
<v-layout row>
<v-flex xs4>
<v-tooltip top>
<v-checkbox
label="Set passwords"
v-model="setPasswords"
slot="activator"
/>
<span>Setting this will cause all student passwords
to be reset upon export. The new passwords will be contained in the
export file.
</span>
</v-tooltip>
</v-flex>
<v-flex xs3 offset-xs1 id="type-select">
<v-select
label="Export file format"
:items="availableExportTypes"
v-model="exportType"
/>
</v-flex>
</v-layout>
<v-card-actions>
<v-btn
flat color="blue lighten-2"
@click="exportDialog = false"
>close</v-btn>
<v-spacer/>
<v-btn id="export-data-download-btn" flat outline @click="getExportFile('data')"
>{{mapFile || mapFileLoaded ? 'Download and apply mapping' : 'Download without mapping'}}</v-btn>
</v-card-actions>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script lang="ts">
import { Vue, Component, Mixins } from 'vue-property-decorator'
import { getters } from '@/store/getters'
import ax, { StudentExportItem, fetchStudentExportData } from '@/api'
import FileSelect from '@/components/util/FileSelect.vue'
import { mutations as mut } from '@/store/mutations'
import { exportMixin, ExportType } from '@/components/mixins/mixins'
@Component({
components: { FileSelect }
})
export default class DataExport extends Mixins(exportMixin) {
exportDialog = true
mapFile: File | null = null
setPasswords = false
exportType = ExportType.CSV
get studentMap () { return getters.state.studentMap }
applyMapping (studentExport: StudentExportItem[]) {
studentExport.forEach(student => {
if (this.studentMap[student.Matrikel]) {
student.Name = this.studentMap[student.Matrikel].name
student.Matrikel = this.studentMap[student.Matrikel].matrikelNo
} else {
this.$notify({
title: `Unknown student: ${student.Name}`,
text: `Student ${student.Name} is missing in mapping file`,
type: 'error',
duration: -1
})
}
})
}
createDownloadPopup (content: string | StudentExportItem[], fileType: ExportType): void {
const blobProperties: BlobPropertyBag = {}
if (fileType === ExportType.JSON) {
blobProperties.type = 'application/json'
content = JSON.stringify(content)
} else {
blobProperties.type = 'text/csv'
}
const blobData = new Blob([<string> content], blobProperties)
window.open(window.URL.createObjectURL(blobData))
}
}
</script>
<style scoped>
</style>
<template>
<div>
<v-menu>
<v-tooltip bottom slot="activator">
<v-btn id="export-btn" :color="exportColor" slot="activator">
export
<v-icon>file_download</v-icon>
</v-btn>
<span id="corrected-tooltip" v-if="corrected">All submissions have been corrected!</span>
<span id="uncorrected-tooltip" v-else>UNCORRECTED submissions left! Export will be incomplete.</span>
</v-tooltip>
<v-list>
<v-list-tile :id="'export-list' + i" v-for="(item, i) in menuItems" :key="i" @click="item.action">{{item.display}}</v-list-tile>
</v-list>
</v-menu>
<component v-if="displayComponent" :is="displayComponent" @hide="displayComponent = null"/>
</div>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import DataExport from '@/components/export/DataExport.vue'
import InstanceExport from '@/components/export/InstanceExport.vue'
import { getters } from '@/store/getters'
@Component({
components: { DataExport, InstanceExport }
})
export default class ExportDialog extends Vue {
displayComponent: any = null
menuItems = [
{
display: 'Export student scores',
action: () => {
this.setDisplayComponent(DataExport)
}
},
{
display: 'Export whole instance data',
action: () => { this.setDisplayComponent(InstanceExport) }
}
];
get corrected () {
return getters.corrected
}
get exportColor () {
return this.corrected ? 'green darken-1' : 'red lighten-1'
}
// apparently `this` is not the same when used within a
// closure when defining data and within a method
setDisplayComponent (component: any) {
this.displayComponent = component
}
}
</script>
<template>
<v-dialog v-model="exportDialog" max-width="31vw" @update:returnValue="hide">
<v-card id="instance-export-modal">
<v-card-title class="title">
Instance Data Export
</v-card-title>
<v-card-text>
<div v-if="!mapFileLoaded">
If you select a mapping file, the anonymized data
will be mapped back automatically and locally on your machine.
<v-layout row align-center>
<file-select v-model="mapFile" display-text="Select map file" class="ma-3"/>
<span>Without the mapping, the data will still be obfuscated.</span>
</v-layout>
</div>
<v-card-actions>
<v-btn
flat color="blue lighten-2"
@click="exportDialog = false"
>close</v-btn>
<v-spacer/>
<v-btn id="instance-export-dl" flat outline @click="getExportFile('instance')"
>{{mapFile || mapFileLoaded ? 'Download and apply mapping' : 'Download without mapping'}}</v-btn>
</v-card-actions>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script lang="ts">
import { Vue, Component, Mixins } from 'vue-property-decorator'
import { getters } from '@/store/getters'
import ax, { StudentExportItem, fetchStudentExportData, fetchInstanceExportData, InstanceExportData } from '@/api'
import FileSelect from '@/components/util/FileSelect.vue'
import { mutations as mut } from '@/store/mutations'
import { exportMixin, ExportType } from '@/components/mixins/mixins'
@Component({
components: { FileSelect }
})
export default class DataExport extends exportMixin {
exportDialog = true
mapFile: File | null = null
exportType = ExportType.JSON // instance export is only available as JSON
get studentMap () { return getters.state.studentMap }
applyMapping (instanceExport: InstanceExportData) {
instanceExport.students.forEach(student => {
if (this.studentMap[student.matrikelNo]) {
const anonMatrikelNo = student.matrikelNo
student.name = this.studentMap[anonMatrikelNo].name
student.matrikelNo = this.studentMap[anonMatrikelNo].matrikelNo
} else {
this.$notify({
title: `Unknown student: ${student.name}`,
text: `Student ${student.name} is missing in mapping file`,
type: 'error',
duration: -1
})
}
})
}
createDownloadPopup (content: string | InstanceExportData): void {
const blobProperties: BlobPropertyBag = {}
blobProperties.type = 'application/json'
content = JSON.stringify(content)
const blobData = new Blob([<string> content], blobProperties)
window.open(window.URL.createObjectURL(blobData))
}
}
</script>
<style scoped>
</style>
......@@ -38,15 +38,15 @@
</template>
<script lang="ts">
import {mapState, mapGetters} from 'vuex'
import {Component, Prop, Vue} from 'vue-property-decorator'
import {getObjectValueByPath} from '@/util/helpers'
import { mapState, mapGetters } from 'vuex'
import { Component, Prop, Vue } from 'vue-property-decorator'
import { getObjectValueByPath } from '@/util/helpers'
import FeedbackSearchOptions from '@/components/feedback_list/FeedbackSearchOptions.vue'
import { FeedbackSearchOptions as OptionsModule } from '@/store/modules/feedback_list/feedback-search-options'
import { FeedbackTable as FeedbackModule, FeedbackHistoryItem} from '@/store/modules/feedback_list/feedback-table'
import { Subscription, Feedback } from '@/models';
import { actions } from '@/store/actions';
import { getters } from '@/store/getters';
import { FeedbackTable as FeedbackModule, FeedbackHistoryItem } from '@/store/modules/feedback_list/feedback-table'
import { Subscription, Feedback } from '@/models'
import { actions } from '@/store/actions'
import { getters } from '@/store/getters'
@Component({
components: {
......@@ -98,17 +98,16 @@ export default class FeedbackTable extends Vue {
}
const associatedTutors = this.stageFilterString === 'all'
? Object.values(feedback.history).filter(histEntry => !!histEntry).map(histEntry => histEntry!.owner)
: feedback.history[<Subscription.FeedbackStageEnum> this.stageFilterString] ?
[feedback.history[<Subscription.FeedbackStageEnum> this.stageFilterString]!.owner] : []
: feedback.history[<Subscription.FeedbackStageEnum> this.stageFilterString]
? [feedback.history[<Subscription.FeedbackStageEnum> this.stageFilterString]!.owner] : []
return this.filterByTutors.length === 0 ||
associatedTutors.some(tutor => this.filterByTutors.includes(tutor))
}
showSubmission (submissionPk: string) {
this.$router.push(`/feedback/${submissionPk}`)
}
prefetchSubmission (submissionPk: string) {
actions.getSubmissionFeedbackTest({pk: submissionPk})
actions.getSubmissionFeedbackTest({ pk: submissionPk })
}
prefetchFilteredItems (items: Feedback[]) {
if (items.length < this.prefetchWhenLessItems) {
......
import { Vue, Component } from 'vue-property-decorator'
import { Vue, Component, Mixins } from 'vue-property-decorator'
import { fetchStudentExportData, StudentExportItem, InstanceExportData, fetchInstanceExportData } from '@/api'
import { getters } from '@/store/getters'
import { mutations as mut } from '@/store/mutations'
export enum ExportType {
JSON = 'JSON',
CSV = 'CSV'
}
@Component
export class parseCSVMapMixin extends Vue {
......@@ -15,3 +23,122 @@ export class parseCSVMapMixin extends Vue {
}, {})
}
}
@Component
export class exportMixin extends Mixins(Vue, parseCSVMapMixin) {
exportDialog = true
mapFile: File | null = null
setPasswords = false
exportType = ExportType.CSV
get mapFileLoaded () {
return Object.keys(getters.state.studentMap).length > 0
}
get availableExportTypes (): ExportType[] {
return Object.values(ExportType)
}
async getExportFile (type: string) {
let studentData
if (type === 'data') {
studentData = await fetchStudentExportData({ setPasswords: this.setPasswords })
} else if (type === 'instance') {
studentData = await fetchInstanceExportData()
} else {
throw new Error('Unsupported export type')
}
if (this.mapFile || this.mapFileLoaded) {
this.getMappedExportFile(studentData)
} else {
this.optionalConvertAndCreatePopup(studentData)
}
}
jsonToCSV (studentExport: StudentExportItem[], delimeter = ';') {
let headerLine = Object.keys(studentExport[0]).reduce((acc: string, curr) => {
if (curr === 'Scores') {
return acc
}
return acc ? `${acc};${curr}` : `${curr}`
}, '')
headerLine += Object.values(studentExport[0].Scores)
.reduce((acc: string, curr) => {
return `${acc};${curr.type}`
}, '')
const lines = studentExport.map(student => {
const normalFields = Object.values(student).reduce((acc: string, curr): string => {
// skip the Scores field
if (typeof curr === 'object') {
return acc
}
return acc ? `${acc};${curr}` : `${curr}`
}, '')
const scoreFields = Object.values(student.Scores).reduce((acc: string, curr) => {
return `${acc};${curr.score}`
}, '')
return normalFields + scoreFields
})
return headerLine + lines.reduce((acc, curr) => {
return `${acc}\n${curr}`
}, '') + '\n' // add trailing newline
}
optionalConvertAndCreatePopup (studentData: StudentExportItem[] | InstanceExportData) {
const convertedData = this.exportType === ExportType.CSV
? this.jsonToCSV(studentData as StudentExportItem[]) : studentData
// we have a cast here because only student export may be converted to csv
this.createDownloadPopup(convertedData, this.exportType)
}
async getMappedExportFile (studentData: StudentExportItem[] | InstanceExportData) {
if (!this.mapFile && !this.mapFileLoaded) {
throw new Error('Either mapFile must be selected or already loaded ' +
'to call getMappedExportFile')
}
if (this.mapFile) {
await this.readMapFileAndCommit()
}
this.applyMapping(studentData)
this.optionalConvertAndCreatePopup(studentData)
}
readMapFileAndCommit () {
const fileReader = new FileReader()
return new Promise((resolve, reject) => {
fileReader.onload = event => {
// @ts-ignore typings of EventTarget seem to be wrong
const studentMap = this.parseCSVMap(event.target.result)
mut.SET_STUDENT_MAP(studentMap)
resolve()
}
fileReader.onerror = () => {
fileReader.abort()
reject(new Error('Problem parsing input file.'))
}
if (!this.mapFile) {
reject(new Error('Can only call' +
' readMapFileAndCommit when mapFile is not undefined'))
} else {
fileReader.readAsText(this.mapFile)
}
})
}
hide () {
this.$emit('hide')
}
showDialog () {
this.exportDialog = true
}
applyMapping (exportData: StudentExportItem[] | InstanceExportData): void { throw new Error('Not implemented.') }
createDownloadPopup (content: string | StudentExportItem[] | InstanceExportData, fileType: ExportType): void { throw new Error('Not implemented.') }
}
......@@ -33,7 +33,7 @@ export default {
text: 'Task',
align: 'left',
value: 'type.name',
sortable: false
sortable: true
},
{
text: 'Score',
......
......@@ -5,8 +5,8 @@
class="mb-1 elevation-1"
slot="header"
/>
<template slot="table-content">
<tr v-for="(code, lineNo) in submission" :key="`${submissionObj.pk}${lineNo}`">
<template slot="table-content" id='sub-lines'>
<tr v-for="(code, lineNo) in submission" :key="`${submissionObj.pk}${lineNo}`" :id="`sub-line-${lineNo}`">
<submission-line
:code="code"
:line-no="lineNo"
......@@ -134,7 +134,8 @@ export default {
this.$notify({
title: 'Feedback creation Error!',
text: err.message,
type: 'error'
type: 'error',
duration: -1
})
}).finally(() => {
this.loading = false
......
......@@ -13,8 +13,8 @@
auto-grow
hide-details
/>
<v-btn color="success" @click="submitFeedback"><v-icon>check</v-icon>Submit</v-btn>
<v-btn @click="collapseTextField"><v-icon>cancel</v-icon>cancel</v-btn>
<v-btn id="submit-comment" color="success" @click="submitFeedback"><v-icon>check</v-icon>Submit</v-btn>
<v-btn id="cancel-comment" @click="collapseTextField"><v-icon>cancel</v-icon>cancel</v-btn>
</div>
</template>
......
......@@ -3,6 +3,7 @@
<v-tooltip top v-if="skippable">
<v-btn
slot="activator"
id="skip-submission"
outline round color="grey darken-2"
@click="skipSubmission"
>Skip</v-btn>
......@@ -19,6 +20,7 @@
<input
class="score-text-field"
type="number"
id="score-input"
v-model="score"
@input="validateScore"
@change="validateScore"
......@@ -26,11 +28,13 @@
<span>&nbsp;/ {{fullScore}}</span>
<v-btn
outline round flat
id="score-zero"
@click="score = 0"
color="red lighten-1"
class="score-button">0</v-btn>
<v-btn
outline round flat
id="score-full"
@click="score = fullScore"
color="blue darken-3"
class="score-button">{{fullScore}}</v-btn>
......@@ -45,6 +49,7 @@
<v-btn
color="success"
slot="activator"
id="submit-feedback"
:loading="loading"
@click="submit"
>Submit<v-icon>chevron_right</v-icon></v-btn>
......
<template>
<v-card class="mx-auto center-page">
<v-card class="mx-auto center-page" id="subscription-ended">
<v-card-title class="title">
It seems like your subscription has (temporarily) ended.
</v-card-title>
......
<template>
<v-card>
<v-card name='subscription-list'>
<v-toolbar color="teal" :dense="sidebar">
<v-toolbar-side-icon><v-icon>assignment</v-icon></v-toolbar-side-icon>
<v-toolbar-title v-if="showDetail" style="min-width: fit-content;">
......@@ -21,7 +21,7 @@
{{item}}
</v-tab>
<v-tab-item v-for="(stage, i) in stages" :key="i">
<subscriptions-for-stage :stage="stage"/>
<subscriptions-for-stage :stage="stage" :id="`stage-${i}`"/>
</v-tab-item>
</v-tabs>
</v-card>
......
......@@ -198,7 +198,6 @@ export interface Credentials {
password: string
}
export interface JSONWebToken {
token: string
}
......
......@@ -13,18 +13,18 @@
</v-list-tile>
</v-list>
<template slot="toolbar-right">
<data-export/>
<export-dialog/>
</template>
</tutor-reviewer-base-layout>
</template>
<script>
import TutorReviewerBaseLayout from '@/pages/base/TutorReviewerBaseLayout'
import DataExport from '@/components/DataExport'
import ExportDialog from '@/components/export/ExportDialog'
export default {
components: {
DataExport,
ExportDialog,
TutorReviewerBaseLayout },
name: 'reviewer-layout',
data () {
......
......@@ -3,7 +3,7 @@
<v-layout justify center>
<template v-if="loaded">
<v-flex md10 mt-5 offset-xs1>
<h2>Submissions of {{ studentName || 'Student'}}</h2>
<h2>Your submissions:</h2>
<submission-list :submissions="submissions"/>
</v-flex>
</template>
......@@ -28,7 +28,6 @@ export default {
}
},
computed: {
studentName () { return StudentPage.state.studentName },
submissions () { return StudentPage.state.submissionsForList },
loaded () { return StudentPage.state.loaded }
}
......
......@@ -19,7 +19,7 @@ export interface AuthState {
}
function initialState (): AuthState {
return {
token: sessionStorage.getItem('token') || '',
token: window.sessionStorage.getItem('token') || '',
lastTokenRefreshTry: Date.now(),
refreshingToken: false,
jwtTimeDelta: 0,
......@@ -59,7 +59,8 @@ function SET_MESSAGE (state: AuthState, message: string) {
state.message = message
}
function SET_JWT_TOKEN (state: AuthState, token: string) {
sessionStorage.setItem('token', token)
window.sessionStorage.setItem('token', token)
api.default.defaults.headers['Authorization'] = `JWT ${token}`
state.token = token
}
function SET_JWT_TIME_DELTA (state: AuthState, timeDelta: number) {
......@@ -75,7 +76,7 @@ function SET_LAST_TOKEN_REFRESH_TRY (state: AuthState) {
state.lastTokenRefreshTry = Date.now()
}
function RESET_STATE (state: AuthState) {
sessionStorage.setItem('token', '')
window.sessionStorage.setItem('token', '')
Object.assign(state, initialState())
}
......
import Vue from 'vue'
import * as hljs from 'highlight.js'
import * as api from '@/api'
import {nameSpacer} from '@/util/helpers'
import {Feedback, FeedbackComment, Submission, SubmissionNoType} from '@/models'
import {RootState} from '@/store/store'
import {Module} from 'vuex'
import { Feedback, FeedbackComment, SubmissionNoType } from '@/models'
import { RootState } from '@/store/store'
import { getStoreBuilder, BareActionContext } from 'vuex-typex'
export const subNotesNamespace = nameSpacer('submissionNotes/')
export interface SubmissionNotesState {
submission: SubmissionNoType
ui: {
......@@ -100,7 +96,7 @@ function SET_SHOW_FEEDBACK (state: SubmissionNotesState, val: boolean) {
function UPDATE_FEEDBACK_LINE (state: SubmissionNotesState, feedback: {lineNo: number, comment: Partial<FeedbackComment>}) {
// explicit .toString() on lineNo is necessary because of Vue.set typings
if (state.updatedFeedback.feedbackLines) {
Vue.set(state.updatedFeedback.feedbackLines, feedback.lineNo.toString(), feedback.comment)
Vue.set(state.updatedFeedback.feedbackLines, feedback.lineNo.toString(), feedback.comment)
}
}
function UPDATE_FEEDBACK_SCORE (state: SubmissionNotesState, score: number) {
......@@ -112,7 +108,7 @@ function DELETE_FEEDBACK_LINE (state: SubmissionNotesState, lineNo: number) {
Vue.delete(state.updatedFeedback.feedbackLines, <string><any>lineNo)
}
}
function TOGGLE_EDITOR_ON_LINE (state: SubmissionNotesState, {lineNo, comment}: {lineNo: number, comment: FeedbackComment}) {
function TOGGLE_EDITOR_ON_LINE (state: SubmissionNotesState, { lineNo, comment }: {lineNo: number, comment: FeedbackComment}) {
Vue.set(state.ui.selectedCommentOnLine, <string><any>lineNo, comment)
Vue.set(state.ui.showEditorOnLine, <string><any> lineNo, !state.ui.showEditorOnLine[lineNo])
}
......@@ -129,32 +125,38 @@ function RESET_STATE (state: SubmissionNotesState) {
Object.assign(state, initialState())
}
async function deleteComments ({state}: BareActionContext<SubmissionNotesState, RootState>) {
async function deleteComments ({ state }: BareActionContext<SubmissionNotesState, RootState>) {
return Promise.all(
Object.values(state.commentsMarkedForDeletion).map(comment => {
return api.deleteComment(comment)
})
)
}
async function submitFeedback ({state}: BareActionContext<SubmissionNotesState, RootState>, {isFinal = false}) {
async function submitFeedback ({ state }: BareActionContext<SubmissionNotesState, RootState>, { isFinal = false }) {
let feedback: Partial<Feedback> = {
isFinal: isFinal,
ofSubmission: state.submission.pk
}
if (Object.keys(state.updatedFeedback.feedbackLines || {}).length > 0) {
feedback.feedbackLines = state.updatedFeedback.feedbackLines
}
if (state.origFeedback.score === undefined && state.updatedFeedback.score === undefined) {
throw new Error('You need to give a score.')
} else if (state.updatedFeedback.score !== undefined) {
feedback.score = state.updatedFeedback.score
} else {
feedback.score = state.origFeedback.score
}
if (Object.keys(state.updatedFeedback.feedbackLines || {}).length > 0) {
feedback.feedbackLines = state.updatedFeedback.feedbackLines
} else if (feedback.score! < SubmissionNotes.submissionType.fullScore!) {
throw new Error('You need to add or change a comment when setting a non full score.')
}
// delete those comments that have been marked for deletion
await SubmissionNotes.deleteComments()
if (!state.hasOrigFeedback) {
return api.submitFeedbackForAssignment({feedback})
return api.submitFeedbackForAssignment({ feedback })
} else {
feedback.pk = state.origFeedback.pk
return api.submitUpdatedFeedback(<{feedback: Feedback}> {feedback})
return api.submitUpdatedFeedback(<{feedback: Feedback}> { feedback })
}
}
......
import Vue from 'vue'
import * as api from '@/api'
import { cartesian, flatten, handleError, once } from '@/util/helpers'
import { cartesian, flatten, once } from '@/util/helpers'
import { Assignment, Subscription } from '@/models'
import { ActionContext, Module } from 'vuex'
import { RootState } from '@/store/store'
......
import Vuex from 'vuex'
import Vue from 'vue'
import createPersistedState from 'vuex-persistedstate'
import {getStoreBuilder} from 'vuex-typex'
import { getStoreBuilder } from 'vuex-typex'
// @ts-ignore
import './modules/ui'
// @ts-ignore
import './modules/authentication'
// @ts-ignore
import './modules/feedback_list/feedback-search-options'
// @ts-ignore
import './modules/feedback_list/feedback-table'
// @ts-ignore
import './modules/subscriptions'
// @ts-ignore
import './modules/submission-notes'
// @ts-ignore
import './modules/student-page'
// @ts-ignore
import './modules/tutor-overview'
import './mutations'
import './actions'
import './getters'
import { UIState } from './modules/ui'
import { AuthState } from './modules/authentication'
import { FeedbackSearchOptionsState } from './modules/feedback_list/feedback-search-options'
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 {UIState} from './modules/ui'
import {AuthState} from './modules/authentication'
import {FeedbackSearchOptionsState} from './modules/feedback_list/feedback-search-options'
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 { lastInteraction } from '@/store/plugins/lastInteractionPlugin'
import {
Exam, Feedback,
Statistics,
......