Last active
April 15, 2024 00:16
-
-
Save jrr6/687a9bd495b6af46d5e5d699d747f5f6 to your computer and use it in GitHub Desktop.
Grader statistics for Gradescope
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Gradescope Grader Stats | |
| // @version 0.1 | |
| // @description Statistics about Gradescope graders. | |
| // @author jrr6 | |
| // @match https://www.gradescope.com/courses/*/questions/*/submissions | |
| // @grant none | |
| // @downloadURL https://gist.github.com/jrr6/687a9bd495b6af46d5e5d699d747f5f6/raw/GradescopeGraderStats.user.js | |
| // @updateURL https://gist.github.com/jrr6/687a9bd495b6af46d5e5d699d747f5f6/raw/GradescopeGraderStats.user.js | |
| // ==/UserScript== | |
| (function() { | |
| function hideStatsModal() { | |
| const modal = document.getElementById('grader-stats-modal') | |
| if (modal) modal.remove() | |
| } | |
| let graderInfo | |
| function reSortTable(nm) { | |
| const wasDesc = document.getElementById(nm).classList.contains('sorting_desc') | |
| const sortVal = wasDesc ? -1 : 1 | |
| const tbody = document.querySelector('#grader-stats-table tbody') | |
| tbody.innerHTML = '' | |
| graderInfo.sort((g1, g2) => g1[nm] < g2[nm] ? sortVal : -sortVal) | |
| graderInfo.forEach(({grader, numGraded, avgScore}) => { | |
| const row = document.createElement('tr') | |
| ; [grader, numGraded, avgScore.toFixed(2)].forEach(field => { | |
| const cell = document.createElement('td') | |
| cell.textContent = field | |
| row.appendChild(cell) | |
| }) | |
| tbody.appendChild(row) | |
| }) | |
| if (wasDesc) { | |
| document.getElementById(nm).classList.add('sorting_asc') | |
| document.getElementById(nm).classList.remove('sorting_desc') | |
| } else { | |
| document.getElementById(nm).classList.remove('sorting_asc') | |
| document.getElementById(nm).classList.add('sorting_desc') | |
| } | |
| Array.from(document.querySelectorAll('#grader-stats-table th')).forEach(th => { | |
| if (th.getAttribute('id') !== nm) { | |
| th.classList.remove('sorting_asc') | |
| th.classList.remove('sorting_desc') | |
| } | |
| }) | |
| } | |
| const button = document.createElement('button') | |
| button.textContent = 'Show Grader Stats' | |
| ; ['tiiBtn', 'tiiBtn-primary'].forEach(cls => button.classList.add(cls)) | |
| document.querySelector('.table--header').appendChild(button) | |
| button.addEventListener('click', () => { | |
| const rows = Array.from(document.querySelectorAll('#question_submissions tbody tr')) | |
| const graders = [...new Set(rows.map(r => r.children[2].textContent))] | |
| const modal = document.createElement('div') | |
| modal.setAttribute('id', 'grader-stats-modal') | |
| modal.classList.add('ReactModalPortal') | |
| modal.innerHTML = ` | |
| <style> | |
| #grader-stats-table > tbody > tr { | |
| height: 30px; | |
| } | |
| </style> | |
| <div class="ReactModal__Overlay ReactModal__Overlay--after-open themodal-overlay"> | |
| <div class="ReactModal__Content ReactModal__Content--after-open modal submissionsManagerStudentsModal" tabindex="-1" role="dialog" aria-label="" aria-modal="true" aria-labelledby="modal-1-heading" aria-describedby="modal-1-subheading" style="display: block;"> | |
| <button class="tiiBtn" style="position: absolute;right: 2em;" id="stats-close-button">×</button> | |
| <h1 class="modal--heading" id="modal-1-heading">Grader Statistics</h1> | |
| <div class="modal--body"> | |
| <table class="table" id="grader-stats-table"> | |
| <thead> | |
| <tr> | |
| <th>Grader</th> | |
| <th class="sorting" id="numGraded"># Graded</th> | |
| <th class="sorting" id="avgScore">Avg Score</th> | |
| </tr> | |
| </thead> | |
| <tbody></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div>` | |
| graderInfo = graders.filter(g => g !== '').map(grader => { | |
| const gradesGiven = rows | |
| .filter(e => | |
| e.children[2].textContent === grader | |
| && !isNaN(parseFloat(e.children[4].textContent))) | |
| .map(e => parseFloat(e.children[4].textContent)) | |
| return { | |
| 'grader': grader, | |
| 'numGraded': gradesGiven.length, | |
| 'avgScore': gradesGiven.reduce((e, a) => e + a, 0) / gradesGiven.length | |
| } | |
| }) | |
| modal.querySelector('#numGraded').addEventListener('click', () => reSortTable('numGraded'), false) | |
| modal.querySelector('#avgScore').addEventListener('click', () => reSortTable('avgScore'), false) | |
| modal.querySelector('#stats-close-button').addEventListener('click', () => hideStatsModal(), false) | |
| document.body.appendChild(modal) | |
| reSortTable('numGraded') | |
| }, false) | |
| })() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment