Quiz analysis with Canvas API

I wanted to perform Item Analysis on quiz results under Canvas LMS.

Without working very hard.

I used a Web Inspector to look at the traffic, found the URL for quiz statistics and started trying to use curl to authenticate in and get the data.

When it didn't work right away, I Googled and found the Canvas REST API documentation. I spent a little time roaming through and found ...

curl -H "Authorization: Bearer REDACTED" https://canvas.instructure.com/api/v1/courses/:course_id/quizzes/:quiz_id/statistics

A single curl request delivers all the stats for a quiz. Absolutely excellent.

To create a .csv of the data, I used PHP, then translated it to Node.js

PHP

<?php
$shortopts "f:q:";
 
$longopts  = [
    "file:",
    "questions:"
];
$options getopt($shortopts$longopts);
 
$filename $options['f'];
if (is_file($filename)) {
        $questionList $options['q'];
        $questions explode(','$questionList);
        if (count($questions) < 1) {
                die('No questions');
        }       
} else {
        die('File not found');
}
 
$keys = [ 'responses''answered''correct''partially_correct''incorrect' ];
$assessmentData json_decode(file_get_contents('stats.json'));
if (json_last_error() === JSON_ERROR_NONE) {
        $quizStatistics $assessmentData->quiz_statistics[0];
        echo 'question,'.implode(','$keys).PHP_EOL;
        foreach ($questions as $q) {
                $data getData($keys$q$quizStatistics->question_statistics[$q]);
                echo implode(','$data).PHP_EOL;
        }
}
 
function getData($keys$question,$data) {
        $lineData = [ $question ];
        foreach ($keys as $k) {
                $lineData[] = isset($data->$k) ? $data->$k 0;
        }
        return $lineData;
}

Code

"use strict";
 
// Thanks to: https://stackabuse.com/command-line-arguments-in-node-js/
// Thanks to: https://flaviocopes.com/how-to-check-if-file-exists-node/
// Thanks to: https://code-maven.com/reading-a-file-with-nodejs
 
const fs = require('fs');
 
const path = process.argv[2];
try {
        if (fs.existsSync(path)) {
                let questionList = process.argv[3];
                let questions = questionList.split(',');
                if (questions.length < 1) {
                        throw new Error('No questions');
                }
                fs.readFile(path, 'utf8', function(err,contents) {
                                let csvData = createCSVData(contents,questions);
                                process.stdout.write(csvData + '\n');
                                });    
        } else {
                throw new Error('File not found');
        }
} catch (err) {
        console.error(err);
}
 
function createCSVData(fileData,questions){
        const keys = [ 'responses', 'answered', 'correct', 'partially_correct', 'incorrect' ];
        let csvData = '';
        let assessmentData = JSON.parse(fileData);
        if (assessmentData !== null && typeof assessmentData !== "undefined") {
                let quizStatistics = assessmentData.quiz_statistics[0];
                csvData = 'question,'+ keys.join(',') + '\n';
                questions.forEach((q) => {
                                let data = getData(keys, q, quizStatistics.question_statistics[q]);
                                csvData += data.join(',') + '\n';
                                });
        }
        return csvData;
}
 
function getData(keys, question, data) {
        let lineData = [ question ];
        keys.forEach(k => {
                lineData.push(typeof data[k] !== "undefined" ? data[k] : 0);
        });
        return lineData;
}

Sample output:

question,responses,answered,correct,partially_correct,incorrect
2,11,11,3,4,6

Ref: https://canvas.instructure.com/doc/api/
Ref: https://canvas.instructure.com/doc/api/file.oauth.html#manual-token-generation - get a user auth token