<template> <div> <v-tooltip bottom> <v-btn :color="exportColor" slot="activator" @click="getCSVFileAndProcess" > export <v-icon>file_download</v-icon> </v-btn> <span v-if="corrected">All submissions have been corrected!</span> <span v-else>UNCORRECTED submissions left! Export will be incomplete.</span> </v-tooltip> <v-dialog v-model="mapFileDialog" max-width="30vw"> <v-card> <v-card-title class="title"> Currently no student mapping file is selected </v-card-title> <v-card-text> 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> <v-card-actions> <v-btn flat color="blue lighten-2" @click="mapFileDialog = false" >close</v-btn> <v-spacer/> <v-btn flat outline @click="getCSVFileAndProcess" >{{mapFile ? 'Download and apply mapping' : 'Download without mapping'}}</v-btn> </v-card-actions> </v-card-text> </v-card> </v-dialog> </div> </template> <script> import {mapGetters} from 'vuex' import ax from '@/api' import FileSelect from '@/components/util/FileSelect' import { mut } from '@/store/mutations' import { parseCSVMapMixin } from '@/components/mixins/mixins' export default { components: {FileSelect}, name: 'data-export', mixins: [parseCSVMapMixin], data () { return { fileReader: new FileReader(), mapFile: null, mapFileDialog: false } }, computed: { ...mapGetters([ 'corrected' ]), exportColor () { return this.corrected ? 'green darken-1' : 'red lighten-1' }, downloadUrl () { let url = '' if (process.env.NODE_ENV === 'production') { const baseUrl = `https://${window.location.host}${window.location.pathname}`.replace(/\/+$/, '') url = `${baseUrl}/api/export/csv` } else { url = 'http://localhost:8000/api/export/csv/' } return url }, studentMap () { return this.$store.state.studentMap }, mapFileLoaded () { return Object.keys(this.studentMap).length > 0 } }, methods: { readMapFileAndCommit (callback) { this.fileReader.onload = event => { const studentMap = this.parseCSVMap(event.target.result) this.$store.commit(mut.SET_STUDENT_MAP, studentMap) callback() } this.fileReader.readAsText(this.mapFile) }, async download () { const response = await ax.get(this.downloadUrl, {responseType: 'blob'}) return new Blob([response.data], { type: 'text/csv' }) }, CSVToJson (csvString) { const lines = csvString.split('\n') const headers = lines.shift().split(';') return lines .filter(line => !!line) // remove empty strings .map(line => { const lineItems = line.split(';') return headers.reduce((acc, curr, i) => { acc[headers[i]] = lineItems[i] return acc }, {}) }) }, jsonToCSV (data) { const headerLine = Object.keys(data[0]).reduce((acc, curr) => { return acc ? `${acc};${curr}` : `${curr}` }, '') const lines = data.map(studentData => { return Object.values(studentData).reduce((acc, curr) => { return acc ? `${acc};${curr}` : `${curr}` }, '') }) return headerLine + lines.reduce((acc, curr) => { return `${acc}\n${curr}` }, '') + '\n' // add trailing newline }, mapStudentData (students) { return students.map(studentData => { return { ...studentData, Matrikel: this.$store.state.studentMap[studentData.Matrikel].matrikel_no, Name: this.$store.state.studentMap[studentData.Matrikel].name } }) }, getMappedCSV () { this.download().then(blobData => { if (this.mapFileLoaded) { this.fileReader.onload = event => { const jsonData = this.CSVToJson(event.target.result) const mappedData = this.mapStudentData(jsonData) const csvData = this.jsonToCSV(mappedData) const mappedBlobData = new Blob([csvData], { type: 'text/csv' }) window.open(window.URL.createObjectURL(mappedBlobData)) } this.fileReader.readAsText(blobData) } else { window.open(window.URL.createObjectURL(blobData)) } }) }, getCSVFileAndProcess () { if (!this.mapFileLoaded && !this.mapFileDialog) { this.mapFileDialog = true } else { if (this.mapFile) { this.readMapFileAndCommit(this.getMappedCSV) } else { this.getMappedCSV() } } } } } </script> <style scoped> #export-link { color: #000; text-decoration: none; } </style>