Skip to content

Commit

Permalink
Create code view on instructor dashboard (#97)
Browse files Browse the repository at this point in the history
* Initial student-dashboard

* Create table with slug + submission

* Formatted code block

* Updating visuals

* Set last submission as default + add points + titles

* Hide label chartjs

* Fix weekly page url path

* Simplifying code

* Fix format...
  • Loading branch information
zMendes authored Oct 21, 2023
1 parent dde172b commit eb034b9
Show file tree
Hide file tree
Showing 7 changed files with 446 additions and 87 deletions.
25 changes: 24 additions & 1 deletion backend/app/dashboard/static/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,27 @@ function courseChanged(select) {
baseURL = baseURL.slice(0, -1)
baseURL = baseURL.join("/");
window.location = `${baseURL}/${newValue}`;
}
}

function getClassSelect() {
return document.getElementById("select-class");
}

function getCurrentStudents() {
const classSelect = getClassSelect();
const selectedClass = courseClasses[classSelect.selectedIndex];

return selectedClass.students;
}

function updateStudents() {
let student_datalist = document.getElementById("students");
student_datalist.innerHTML = "";
let currentStudents = getCurrentStudents();
currentStudents.forEach(item => {
let option = document.createElement("option");
option.value = item;
student_datalist.appendChild(option)
});

}
20 changes: 20 additions & 0 deletions backend/app/dashboard/static/dashboard-instructor-student.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.chart-container {
width: 50rem;
height: fit-content;
}

.code-snippet {
border: 1px solid #ddd;
margin: 20px;
padding: 10px;
position: relative;
}

#main {
display: flex;
align-items: flex-start;
justify-content: center;
}
#info {
margin-top: 2rem;
}
210 changes: 210 additions & 0 deletions backend/app/dashboard/static/dashboard-instructor-student.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
async function getStudentData() {
clearAll();
let student = selectStudent.value;
await fetch(`${activeCourse}/${student}`).then(async (response) => {
const data = await response.json();
document.getElementById("data").style.visibility = "visible";
createTagChart(data);
});
}

function createTagChart(data) {
if (tagChart != null)
tagChart.destroy();
let labels = Object.keys(data);
let values = Object.values(data);
let count = values.map(item => item.count)

tagChart = new Chart("tag-chart", {
type: "bar",
data: {
labels: labels,
datasets: [
{
data: count,
},
]
},
options: {
onClick: function handleBarClick(event, activeElements) {
if (activeElements.length > 0) {
let index = activeElements[0].index;
clearAll();
createTable(data[labels[index]].data);
}
},
indexAxis: "y",
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
display: false
}
}
}
});
}

function createTable(data) {
tableData = data;
let keys = Object.keys(data);
let formatedData = []
for (let i = 0; i < keys.length; i++) {
formatedData.push([keys[i], data[keys[i]].length]);
}

let tableDiv = document.getElementById("table");
document.getElementById("table-div").style.visibility = "visible";


table = new Handsontable(tableDiv, {
data: formatedData,
colHeaders: ["Slug", "Submissions"],
columns: [
{ data: 0 },
{ data: 1 },
],
licenseKey: "non-commercial-and-evaluation"
});
table.addHook('afterSelectionByProp', onRowClicked);
}

function onRowClicked(row, prop) {
let slug = table.getSourceDataAtRow(row)[0];
clearInfo();
createAnswerView(slug, tableData[slug]);
}

function createAnswerView(slug, data) {
let div = document.getElementById("answers");
div.innerHTML = "";
createSubmissionSelect(slug, Object.keys(data), data);
createFileSelect(data);
}

function createSubmissionSelect(slug, submissions, data) {
let div = document.getElementById("select-submission");
let name = document.createElement("p");
name.innerHTML = slug;

let title = document.createElement("h5");
title.innerHTML = "Submission";
div.appendChild(name);
div.appendChild(title);

submissionSelect = document.createElement('select');
submissionSelect.className = 'form-select';
submissionSelect.onchange = function () {
createCode(data[submissionSelect.value], fileSelect.value);
};

for (const [key, value] of Object.entries(data.reverse())) {
const option = document.createElement('option');
option.value = key;
//turn submission number in two digits minimum (formatting reasons) 1 -> 01 2 -> 02
let submissionNumber = (data.length - parseInt(key)).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
});
let optionText = `#${submissionNumber} ${value.date.slice(5, 7)}/${value.date.slice(8, 10)} - ${value.date.slice(11, 16)}`
option.textContent = optionText;
submissionSelect.appendChild(option);
};

div.appendChild(submissionSelect);
}

function createFileSelect(data) {
let div = document.getElementById("select-file");
div.innerHTML = "";
let title = document.createElement("h5");
title.innerHTML = "File";
div.appendChild(title);
fileSelect = document.createElement('select');
fileSelect.id = 'select-file';
fileSelect.className = 'form-select';

fileSelect.onchange = function () {
createCode(data[submissionSelect.value], fileSelect.value);
};
for (key in data[0].log.student_input) {
const option = document.createElement('option');
option.value = key;
option.textContent = key;
fileSelect.appendChild(option);
}
div.appendChild(fileSelect);

//create codeBlock with the default submission and file
createCode(data[0], fileSelect.value);
}


function createCode(data, fileName) {
let body = document.getElementById("answers");
body.innerHTML = "";

let codeDiv = document.createElement("div");
let codeSnippet = document.createElement("div");
codeSnippet.className = "code-snippet";

let points = document.createElement("h4");
points.innerHTML = `Points: ${data.points.toFixed(2)}`;
codeSnippet.appendChild(points);

let pre = document.createElement("pre");
let code = document.createElement("code");
code.className = "language-python";
code.innerHTML = data.log.student_input[fileName];

pre.appendChild(code);
codeSnippet.appendChild(pre);
codeDiv.appendChild(codeSnippet);
body.appendChild(codeDiv);
Prism.highlightElement(code);
}
function clearInfo() {
document.getElementById("select-submission").innerHTML = "";
document.getElementById("select-file").innerHTML = "";
document.getElementById("answers").innerHTML = "";
}

function clearAll() {
clearInfo();
document.getElementById("table").innerHTML = "";
document.getElementById("table-div").style.visibility = "hidden";
}

var activeCourse;
var selectStudent;
var tagChart;
var table;
var tableData;
var submissionSelect;
var fileSelect;
var courseClasses;
var selectClass;

document.addEventListener("DOMContentLoaded", function () {

document.getElementById("data").style.visibility = "hidden";
document.getElementById("table-div").style.visibility = "hidden";

activeCourse = document.getElementById("select-course").value;

let activeButton = document.getElementById("student");
activeButton.className += " active";

selectStudent = document.getElementById("select-student");
selectStudent.onchange = getStudentData;

selectClass = document.getElementById("select-class");
selectClass.onchange = updateStudents;

courseClasses = selectClass.getAttribute("data-classes");
courseClasses = courseClasses.replace(/'/g, '"');
courseClasses = JSON.parse(courseClasses);
courseClasses.forEach((courseClass) => {
courseClass.students = new Set(courseClass.students);
});
});
1 change: 1 addition & 0 deletions backend/app/dashboard/templates/dashboard/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ <h1 class="h1">Dashboard</h1>
<a href="/dashboard/instructor/{{ activeCourse }}" id="semester" class="btn btn-primary"
aria-current="page">Semester</a>
<a href="/dashboard/instructor/weekly/{{ activeCourse }}" id="weekly" class="btn btn-primary">Weekly</a>
<a href="/dashboard/instructor/student/{{ activeCourse }}" id="student" class="btn btn-primary">Code</a>
</div>

{% if course_classes|length == 0 %}
Expand Down
49 changes: 49 additions & 0 deletions backend/app/dashboard/templates/dashboard/instructor-student.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{% extends "dashboard/base.html" %}
{% load static %}


{% block head-content %}

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/components/prism-python.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/themes/prism.min.css">
<script src="{% static '/dashboard-instructor-student.js' %}"></script>
<link rel="stylesheet" href="{% static '/dashboard-instructor-student.css' %}">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>

{% endblock %}

{% block specific-header %}
<div class="input-group mb-3">
<span class="input-group-text" id="basic-addon1">Student</span>
<input class="form-control" list="students" id="select-student">
<datalist id="students">{% for student in students %}
<option>{{ student }}</option>
{% endfor %}
</datalist>
</div>
{% endblock %}

{% block data %}
<div id="main">
<div class="chart-container">
<h3>Code exercises by tag</h3>
<canvas id="tag-chart">
</canvas>
</div>
<div id="table-div">
<h3>Exercise table</h3>
<div id="table">
</div>
</div>
</div>
<div id="info">
<div id="select-submission">
</div>
<div id="select-file">
</div>
<div id="answers">
</div>
</div>
{% endblock %}
4 changes: 3 additions & 1 deletion backend/app/dashboard/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
path("instructor/<str:course_name>", views.instructor_courses, name='instructor-dashboard'),
path("instructor/<str:content_type>/<str:course_name>", views.instructor_courses, name='instructor-dashboard'),
path("instructor/weekly/<str:course_name>/<str:class_name>/<str:user_nickname>/<str:week>", views.student_weekly_data),
path("instructor/weekly/<str:course_name>/<str:class_name>/<str:week>", views.weekly_exercises)
path("instructor/weekly/<str:course_name>/<str:class_name>/<str:week>", views.weekly_exercises),
path("instructor/student/<str:course_name>/<str:user_nickname>/", views.student_code_data),


]
Loading

0 comments on commit eb034b9

Please sign in to comment.