Skip to content
Snippets Groups Projects
Commit c535b6b0 authored by Dominik Seeger's avatar Dominik Seeger Committed by robinwilliam.hundt
Browse files

refactored export components for better testing

parent 69bdd24f
No related branches found
No related tags found
1 merge request!128Merge improve testing
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
@click="exportDialog = false" @click="exportDialog = false"
>close</v-btn> >close</v-btn>
<v-spacer/> <v-spacer/>
<v-btn id="export-data-download-btn" flat outline @click="getExportFile" <v-btn id="export-data-download-btn" flat outline @click="getExportFile('data')"
>{{mapFile || mapFileLoaded ? 'Download and apply mapping' : 'Download without mapping'}}</v-btn> >{{mapFile || mapFileLoaded ? 'Download and apply mapping' : 'Download without mapping'}}</v-btn>
</v-card-actions> </v-card-actions>
</v-card-text> </v-card-text>
...@@ -56,105 +56,40 @@ import { getters } from '@/store/getters' ...@@ -56,105 +56,40 @@ import { getters } from '@/store/getters'
import ax, { StudentExportItem, fetchStudentExportData } from '@/api' import ax, { StudentExportItem, fetchStudentExportData } from '@/api'
import FileSelect from '@/components/util/FileSelect.vue' import FileSelect from '@/components/util/FileSelect.vue'
import { mutations as mut } from '@/store/mutations' import { mutations as mut } from '@/store/mutations'
import { parseCSVMapMixin } from '@/components/mixins/mixins' import { exportMixin, ExportType } from '@/components/mixins/mixins'
enum ExportType {
JSON = 'JSON',
CSV = 'CSV'
}
@Component({ @Component({
components: { FileSelect } components: { FileSelect }
}) })
export default class DataExport extends Mixins(parseCSVMapMixin) { export default class DataExport extends Mixins(exportMixin) {
exportDialog = true exportDialog = true
mapFile: File | null = null mapFile: File | null = null
setPasswords = false setPasswords = false
exportType = ExportType.CSV exportType = ExportType.CSV
get corrected () { return getters.corrected }
get studentMap () { return getters.state.studentMap } get studentMap () { return getters.state.studentMap }
get mapFileLoaded () {
return Object.keys(getters.state.studentMap).length > 0
}
get exportColor () {
return this.corrected ? 'green darken-1' : 'red lighten-1'
}
get availableExportTypes (): ExportType[] {
return Object.values(ExportType)
}
showDialog () {
this.exportDialog = true
}
readMapFileAndCommit () {
const fileReader = new FileReader()
return new Promise((resolve, reject) => {
// @ts-ignore
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)
}
})
}
applyMapping (studentExport: StudentExportItem[]) { applyMapping (studentExport: StudentExportItem[]) {
return studentExport.map(student => { console.log(studentExport)
return { studentExport.forEach(student => {
...student, if (this.studentMap[student.Matrikel]) {
Matrikel: this.studentMap[student.Matrikel].matrikelNo, student = {
Name: this.studentMap[student.Matrikel].name ...student,
} Matrikel: this.studentMap[student.Matrikel].matrikelNo,
}) Name: this.studentMap[student.Matrikel].name
}
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}` } else {
}, '') this.$notify({
title: `Unknown student: ${student.Name}`,
const scoreFields = Object.values(student.Scores).reduce((acc: string, curr) => { text: `Student ${student.Name} is missing in mapping file`,
return `${acc};${curr.score}` type: 'error',
}, '') duration: -1
return normalFields + scoreFields })
}
}) })
return headerLine + lines.reduce((acc, curr) => {
return `${acc}\n${curr}`
}, '') + '\n' // add trailing newline
} }
createDownloadPopup (content: string | StudentExportItem[], fileType: ExportType) { createDownloadPopup (content: string | StudentExportItem[], fileType: ExportType): void {
const blobProperties: BlobPropertyBag = {} const blobProperties: BlobPropertyBag = {}
if (fileType === ExportType.JSON) { if (fileType === ExportType.JSON) {
blobProperties.type = 'application/json' blobProperties.type = 'application/json'
...@@ -165,39 +100,6 @@ export default class DataExport extends Mixins(parseCSVMapMixin) { ...@@ -165,39 +100,6 @@ export default class DataExport extends Mixins(parseCSVMapMixin) {
const blobData = new Blob([<string> content], blobProperties) const blobData = new Blob([<string> content], blobProperties)
window.open(window.URL.createObjectURL(blobData)) window.open(window.URL.createObjectURL(blobData))
} }
optionalConvertAndCreatePopup (studentData: StudentExportItem[]) {
const convertedData = this.exportType === ExportType.CSV
? this.jsonToCSV(studentData) : studentData
this.createDownloadPopup(convertedData, this.exportType)
}
async getMappedExportFile (studentData: StudentExportItem[]) {
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()
}
const mappedData = this.applyMapping(studentData)
this.optionalConvertAndCreatePopup(mappedData)
}
async getExportFile () {
const studentData = await fetchStudentExportData({ setPasswords: this.setPasswords })
if (this.mapFile || this.mapFileLoaded) {
this.getMappedExportFile(studentData)
} else {
this.optionalConvertAndCreatePopup(studentData)
}
}
hide () {
this.$emit('hide')
}
} }
</script> </script>
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
@click="exportDialog = false" @click="exportDialog = false"
>close</v-btn> >close</v-btn>
<v-spacer/> <v-spacer/>
<v-btn id="instance-export-dl" flat outline @click="getExportFile" <v-btn id="instance-export-dl" flat outline @click="getExportFile('instance')"
>{{mapFile || mapFileLoaded ? 'Download and apply mapping' : 'Download without mapping'}}</v-btn> >{{mapFile || mapFileLoaded ? 'Download and apply mapping' : 'Download without mapping'}}</v-btn>
</v-card-actions> </v-card-actions>
</v-card-text> </v-card-text>
...@@ -34,50 +34,17 @@ import { getters } from '@/store/getters' ...@@ -34,50 +34,17 @@ import { getters } from '@/store/getters'
import ax, { StudentExportItem, fetchStudentExportData, fetchInstanceExportData, InstanceExportData } from '@/api' import ax, { StudentExportItem, fetchStudentExportData, fetchInstanceExportData, InstanceExportData } from '@/api'
import FileSelect from '@/components/util/FileSelect.vue' import FileSelect from '@/components/util/FileSelect.vue'
import { mutations as mut } from '@/store/mutations' import { mutations as mut } from '@/store/mutations'
import { parseCSVMapMixin } from '@/components/mixins/mixins' import { exportMixin, ExportType } from '@/components/mixins/mixins'
@Component({ @Component({
components: { FileSelect } components: { FileSelect }
}) })
export default class DataExport extends Mixins(parseCSVMapMixin) { export default class DataExport extends exportMixin {
exportDialog = true exportDialog = true
mapFile: File | null = null mapFile: File | null = null
exportType = ExportType.JSON // instance export is only available as JSON
get corrected () { return getters.corrected }
get studentMap () { return getters.state.studentMap } get studentMap () { return getters.state.studentMap }
get mapFileLoaded () {
return Object.keys(getters.state.studentMap).length > 0
}
get exportColor () {
return this.corrected ? 'green darken-1' : 'red lighten-1'
}
showDialog () {
this.exportDialog = true
}
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)
}
})
}
applyMapping (instanceExport: InstanceExportData) { applyMapping (instanceExport: InstanceExportData) {
instanceExport.students.forEach(student => { instanceExport.students.forEach(student => {
...@@ -96,39 +63,13 @@ export default class DataExport extends Mixins(parseCSVMapMixin) { ...@@ -96,39 +63,13 @@ export default class DataExport extends Mixins(parseCSVMapMixin) {
}) })
} }
createDownloadPopup (content: string | InstanceExportData) { createDownloadPopup (content: string | InstanceExportData): void {
const blobProperties: BlobPropertyBag = {} const blobProperties: BlobPropertyBag = {}
blobProperties.type = 'application/json' blobProperties.type = 'application/json'
content = JSON.stringify(content) content = JSON.stringify(content)
const blobData = new Blob([<string> content], blobProperties) const blobData = new Blob([<string> content], blobProperties)
window.open(window.URL.createObjectURL(blobData)) window.open(window.URL.createObjectURL(blobData))
} }
async getMappedExportFile (studentData: 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.createDownloadPopup(studentData)
}
async getExportFile () {
const instanceData = await fetchInstanceExportData()
if (this.mapFile || this.mapFileLoaded) {
this.getMappedExportFile(instanceData)
} else {
this.createDownloadPopup(instanceData)
}
}
hide () {
this.$emit('hide')
}
} }
</script> </script>
......
import { Vue, Component } from 'vue-property-decorator' import { Vue, Component } 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 @Component
export class parseCSVMapMixin extends Vue { export class exportMixin extends Vue {
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)
}
parseCSVMap (csvMap: string) { parseCSVMap (csvMap: string) {
let lines = csvMap.split('\n') let lines = csvMap.split('\n')
lines.shift() // drop the first line since it contains only headings lines.shift() // drop the first line since it contains only headings
...@@ -14,4 +35,107 @@ export class parseCSVMapMixin extends Vue { ...@@ -14,4 +35,107 @@ export class parseCSVMapMixin extends Vue {
return acc return acc
}, {}) }, {})
} }
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.") }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment