@afromero2
I must have missed that we were talking about ungraded surveys. They don't have assignment IDs and the normal approach doesn't work. At least I mentioned that I didn't test changing the type. Thanks for looking into that.
I didn't write a massive essay, but for an ungraded survey, I was able to go into Quiz Statistics and generate the student analysis and get the essay response there. That's not full-proof, it will strip out any HTML-ish characters like < or >. You can use the Quiz Reports API to create and monitor the progress on creating the report.
Not satisfied with that answer, I then went in and changed the ungraded survey to a graded survey. I was able to get an assignment_id that way, but when I fetched the submission_history, it was there without the submission_data that you needed. It appears that submission_data is tied to a submission and there is no assignment submission for an ungraded survey. When I look at the quiz submission endpoint, it returns a quiz_submissions object that has a quiz_submission_id (called id), but the submission_id is null.
Many things dealing with classic quizzes are delivered as part of the HTML page and not loaded via JSON through the API. After searching for things, it seems this is the case for the student answers (I may have written that at some point -- I've written too much to remember it all). I use that trick with SpeedGrader and QuizWiz where I modify the content that Canvas delivers as HTML.
What that means is that you could download the student response as HTML and then parse the page to get the student responses. The request is a non-API call (needs to be made within a browser, perhaps with a headless browser).
If you are signed in as the user taking the quiz, then the request has the form
- GET /courses/:courseID/quizzes/:quizID/history?version=1
where version is the attempt number.
That includes all of the other crap that's on the page (navigation, etc). What you really want is just the quiz and you probably want it for people other than the user who is logged in. To get that information, I borrowed some stuff I learned from SpeedGrader / Moderate Quiz.
- GET /courses/:courseID/quizzes/:quizID/history?headless=1&user_id=:userID&version=1
That gives you just the quiz results (the headless=1 does that) for the specified user (provided you have permissions to view it).
Take the quiz and convert it to a DOM object so you can refer to items. For Node JS, I use jsdom, but there are other ways. If you're using Chrome with Puppeteer, you can let Chrome do the DOM parsing for you.
The selector that will give you the answer to an essay question is
- div.display_question.question.essay_question div.user_content.quiz_response_text.enhanced
That means you can do something like one of these two lines. The first line keeps the HTML and the second one returns plaintext.
document.querySelectorAll('div.display_question.question.essay_question div.user_content.quiz_response_text.enhanced').forEach(e => console.log(e.innerHTML));
document.querySelectorAll('div.display_question.question.essay_question div.user_content.quiz_response_text.enhanced').forEach(e => console.log(e.textContent));
Now, what you'll probably want to do, unless there is just one essay question, is to break that up and stop with the selector for the first div. You can get the ID of that element to get the question ID. Alternatively, you could have an index that let you know which essay question they were answering. This could be good if the ungraded survey was consistent but in different classes where the IDs were different.
Here's some code that gives the text of the question and the text of the response. Again, you could use innerHTML if you want to keep the formatting (you likely do).
document.querySelectorAll('div.display_question.question.essay_question').forEach(e => {
const q = e.querySelector('div.question_text.user_content.enhanced').textContent.trim();
const r = e.querySelector('div.user_content.quiz_response_text.enhanced').textContent.trim();
console.log(`Question : ${q}`);
console.log(`Response : ${r}`);
});
Instead of console.log(), you would probably want to save the results somewhere.
Putting that all together, you can use the Get all quiz submissions endpoint of the API to get a list of all the submissions. You will need course_id and quiz_id to make that call. From the response, you'll need to grab the user_id and attempt. Then, in a headless browser, you iterate through all of the users who have submissions and get their quiz history. If I needed all attempts, then I would iterate through the versions 1..attempt, but if you just need the latest response, then use version=attempt.
You could do the whole thing in a headless browser, it's just that the call to get the quiz history is not an API call. By running a headless browser, you can have it do the parsing for you.
For my headless browser work, I don't actually use a headless browser (well, for some things I do). I use Puppeteer that connects to a real Chrome browser via the remote debugging port. That allows me to get logged in to the site and address any MFA that might be required. After I've logged in, then I run my script. Depending on your requirements for logging into Canvas, that may not be necessary and it might be that you could do it completely with a headless browser.