|
@@ -0,0 +1,185 @@
|
|
|
|
+export function processData () {
|
|
|
|
+ const folderInput = document.querySelector("#folder");
|
|
|
|
+ const header = [
|
|
|
|
+ "submissionid",
|
|
|
|
+ "file",
|
|
|
|
+ "filesize",
|
|
|
|
+ "timestamp",
|
|
|
|
+ "humandate",
|
|
|
|
+ "grade",
|
|
|
|
+ "userid",
|
|
|
|
+ "exerciseid",
|
|
|
|
+ ];
|
|
|
|
+
|
|
|
|
+ function generateSubCode (algorithmInIlm) {
|
|
|
|
+ window.program_obj.functions = JSON.parse(algorithmInIlm).functions;
|
|
|
|
+ window.program_obj.globals = JSON.parse(algorithmInIlm).globals;
|
|
|
|
+ return window.ivprogCore.generateCode();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function prepareData (map, submission) {
|
|
|
|
+ if (!submission) {
|
|
|
|
+ return map;
|
|
|
|
+ }
|
|
|
|
+ const exercID = submission["exerciseid"];
|
|
|
|
+ const studentID = submission["userid"];
|
|
|
|
+ let exercMap = null;
|
|
|
|
+ if (map.has(exercID)) {
|
|
|
|
+ exercMap = map.get(exercID);
|
|
|
|
+ } else {
|
|
|
|
+ exercMap = new Map();
|
|
|
|
+ map.set(exercID, exercMap);
|
|
|
|
+ }
|
|
|
|
+ if (exercMap.has(studentID)) {
|
|
|
|
+ exercMap.get(studentID).push(submission);
|
|
|
|
+ } else {
|
|
|
|
+ exercMap.set(studentID, [submission]);
|
|
|
|
+ }
|
|
|
|
+ return map;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ folderInput.addEventListener("change", async () => {
|
|
|
|
+ const files = Array.from(folderInput.files);
|
|
|
|
+ const idx = files.find((f) => f.name == "index.csv");
|
|
|
|
+ const folderName = idx.webkitRelativePath.replace(idx.name, "");
|
|
|
|
+ console.debug(idx);
|
|
|
|
+ console.debug(folderName);
|
|
|
|
+ const data = await idx.text();
|
|
|
|
+ console.debug(data);
|
|
|
|
+ const csvEntries = data
|
|
|
|
+ .split("\n")
|
|
|
|
+ .slice(1)
|
|
|
|
+ .filter((line) => line.length > 0)
|
|
|
|
+ .map((line) => line.split(","))
|
|
|
|
+ .map((vec) => {
|
|
|
|
+ const obj = {};
|
|
|
|
+ vec.forEach((val, i) => (obj[header[i]] = val));
|
|
|
|
+ return obj;
|
|
|
|
+ });
|
|
|
|
+ console.debug(csvEntries);
|
|
|
|
+ const blockExercMap = csvEntries.reduce(prepareData, new Map());
|
|
|
|
+ //console.log(Array.from(blockExercMatrix.entries()));
|
|
|
|
+ const getFilePath = async function (submission) {
|
|
|
|
+ const path = `${folderName}${submission["file"]}`;
|
|
|
|
+ const file = files.find((f) => f.webkitRelativePath == path);
|
|
|
|
+ const text = await file.text();
|
|
|
|
+ return text;
|
|
|
|
+ };
|
|
|
|
+ const matrix = {};
|
|
|
|
+ let counter = 0;
|
|
|
|
+ blockExercMap.forEach((studentsMap, exercID) => {
|
|
|
|
+ const column = [];
|
|
|
|
+ studentsMap.forEach(async (submissions, studentID) => {
|
|
|
|
+ submissions = submissions.sort((a, b) => {
|
|
|
|
+ return parseInt(a["timestamp"]) - parseInt(b["timestamp"]);
|
|
|
|
+ });
|
|
|
|
+ counter++;
|
|
|
|
+ submissions.forEach(async (submission, index, array) => {
|
|
|
|
+ counter++;
|
|
|
|
+ const student = {};
|
|
|
|
+ student["grade"] = Math.max(0, parseFloat(submission["grade"]));
|
|
|
|
+ student["timestamp"] = parseInt(submission["timestamp"]);
|
|
|
|
+ student["student_id"] = studentID;
|
|
|
|
+ student["TES"] = 0;
|
|
|
|
+ student["DES"] = 0;
|
|
|
|
+ student["D/T"] = 0;
|
|
|
|
+ let previousCode = "";
|
|
|
|
+ if (index > 0) {
|
|
|
|
+ student["TES"] =
|
|
|
|
+ parseInt(submission["timestamp"]) -
|
|
|
|
+ parseInt(array[index - 1]["timestamp"]);
|
|
|
|
+ const previousFile = await getFilePath(array[index - 1]);
|
|
|
|
+ const previous = window.ivprogCore
|
|
|
|
+ .prepareActivityToStudentHelper(previousFile)
|
|
|
|
+ .getOrElse(1);
|
|
|
|
+ if (previous == 1) {
|
|
|
|
+ console.error(
|
|
|
|
+ `A submission from ${studentID} to ${exercID} is invalid`
|
|
|
|
+ );
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ previousCode = generateSubCode(previous.algorithmInIlm);
|
|
|
|
+ }
|
|
|
|
+ const currentFile = await getFilePath(submission);
|
|
|
|
+ const current = window.ivprogCore
|
|
|
|
+ .prepareActivityToStudentHelper(currentFile)
|
|
|
|
+ .getOrElse(2);
|
|
|
|
+ if (current == 2) {
|
|
|
|
+ console.error(
|
|
|
|
+ `A submission from ${studentID} to ${exercID} is invalid`
|
|
|
|
+ );
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ const currentCode = generateSubCode(current.algorithmInIlm);
|
|
|
|
+ if (previousCode === "") {
|
|
|
|
+ const logs = JSON.parse(currentFile.split("::logs::")[1]);
|
|
|
|
+ const event = logs[0];
|
|
|
|
+ if (event == null) {
|
|
|
|
+ // empty submission
|
|
|
|
+ student["TES"] = 0;
|
|
|
|
+ } else if (event.length === 4) {
|
|
|
|
+ student["TES"] =
|
|
|
|
+ parseInt(submission["timestamp"]) -
|
|
|
|
+ Math.floor(parseInt(event[2]) / 1000);
|
|
|
|
+ } else {
|
|
|
|
+ student["TES"] =
|
|
|
|
+ parseInt(submission["timestamp"]) -
|
|
|
|
+ Math.floor(parseInt(event[1]) / 1000);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ student["DES"] = window.ivprogCore.levenshteinDistance(
|
|
|
|
+ previousCode,
|
|
|
|
+ currentCode
|
|
|
|
+ );
|
|
|
|
+ const ratio =
|
|
|
|
+ student["TES"] === 0 ? 0 : student["DES"] / student["TES"];
|
|
|
|
+ student["D/T"] = isNaN(ratio) ? 0 : ratio;
|
|
|
|
+ column.push(student);
|
|
|
|
+ counter--;
|
|
|
|
+ });
|
|
|
|
+ counter--;
|
|
|
|
+ });
|
|
|
|
+ matrix[exercID] = column;
|
|
|
|
+ });
|
|
|
|
+ function download (file, text) {
|
|
|
|
+ //creating an invisible element
|
|
|
|
+ const element = document.createElement("a");
|
|
|
|
+ element.setAttribute(
|
|
|
|
+ "href",
|
|
|
|
+ "data:text/plain," + encodeURIComponent(text)
|
|
|
|
+ );
|
|
|
|
+ element.setAttribute("download", file);
|
|
|
|
+ element.innerHTML = file;
|
|
|
|
+ element.classList.add("ui", "primary", "button");
|
|
|
|
+
|
|
|
|
+ // Above code is equivalent to
|
|
|
|
+ // <a href="path of file" download="file name">
|
|
|
|
+
|
|
|
|
+ document.querySelector("#downloads").appendChild(element);
|
|
|
|
+ }
|
|
|
|
+ function getExercHeader (id) {
|
|
|
|
+ const props = ["TES", "DES", "grade", "D/T", "timestamp"];
|
|
|
|
+ return props.reduce((acc, prop) => acc + `${id}_${prop},`, "");
|
|
|
|
+ }
|
|
|
|
+ const id = setInterval(() => {
|
|
|
|
+ if (counter == 0) {
|
|
|
|
+ clearInterval(id);
|
|
|
|
+ for (const exercID in matrix) {
|
|
|
|
+ let csv = "";
|
|
|
|
+ let firstLine = "student_id,";
|
|
|
|
+ firstLine += getExercHeader(exercID);
|
|
|
|
+ for (const submission of matrix[exercID]) {
|
|
|
|
+ csv += `${submission["student_id"]},`;
|
|
|
|
+ csv += `${submission["TES"]},`;
|
|
|
|
+ csv += `${submission["DES"]},`;
|
|
|
|
+ csv += `${submission["grade"]},`;
|
|
|
|
+ csv += `${submission["D/T"]},`;
|
|
|
|
+ csv += `${submission["timestamp"]}`;
|
|
|
|
+ csv += "\n";
|
|
|
|
+ }
|
|
|
|
+ download(`${exercID}.csv`, `${firstLine}\n${csv}`);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }, 1000);
|
|
|
|
+ });
|
|
|
|
+}
|