From e35a5ffab6e794cd722f25b8a68302e57af144d4 Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Thu, 23 Oct 2025 12:47:34 -0600 Subject: [PATCH] log when not loading a file --- globalSettings.yml | 2 + requests/quiz.http | 5 ++ .../canvas/services/canvasQuizService.ts | 31 +++++++++++-- .../course/courseItemFileStorageService.ts | 3 +- .../quizMarkdown/numericalQuestion.test.ts | 42 +++++++++-------- .../quizDeterministicChecks.test.ts | 46 +++++++++++++++++-- .../utils/quizQuestionAnswerMarkdownUtils.ts | 25 +++++++++- 7 files changed, 125 insertions(+), 29 deletions(-) diff --git a/globalSettings.yml b/globalSettings.yml index c5ebcb5..6070626 100644 --- a/globalSettings.yml +++ b/globalSettings.yml @@ -21,3 +21,5 @@ courses: name: 1400-spring - path: ./1420/2024-fall/Modules/ name: 1420_old + - path: ./3820_BackEnd/2025-fall/Modules/ + name: jonathan-backend diff --git a/requests/quiz.http b/requests/quiz.http index 03bfdb3..b536d54 100644 --- a/requests/quiz.http +++ b/requests/quiz.http @@ -30,6 +30,11 @@ Content-Type: application/json GET https://snow.instructure.com/api/v1/courses/958185/assignments Authorization: Bearer {{$dotenv CANVAS_TOKEN}} + +### +GET https://snow.instructure.com/api/v1/courses/1155293/quizzes/4366122/questions +Authorization: Bearer {{$dotenv CANVAS_TOKEN}} + ### POST https://snow.instructure.com/api/v1/courses/958185/quizzes/3358912/questions Authorization: Bearer {{$dotenv CANVAS_TOKEN}} diff --git a/src/features/canvas/services/canvasQuizService.ts b/src/features/canvas/services/canvasQuizService.ts index 9459adc..7c806af 100644 --- a/src/features/canvas/services/canvasQuizService.ts +++ b/src/features/canvas/services/canvasQuizService.ts @@ -33,10 +33,33 @@ export const getAnswersForCanvas = ( }); if (question.questionType === QuestionType.NUMERICAL) { - return question.answers.map((answer) => ({ - numerical_answer_type: answer.numericalAnswerType, - exact: answer.numericAnswer, - })); + // if (question.answers[0].numericalAnswerType === "range_answer") { + // console.log( + // "answer range", + // question.answers.map((answer) => ({ + // numerical_answer_type: answer.numericalAnswerType, + // start: answer.numericAnswerRangeMin, + // end: answer.numericAnswerRangeMax, + // })) + // ); + // return question.answers.map((answer) => ({ + // numerical_answer_type: answer.numericalAnswerType, + // start: answer.numericAnswerRangeMin + "", + // end: answer.numericAnswerRangeMax + "", + // })); + // } + return question.answers.map((answer) => { + if (answer.numericalAnswerType === "range_answer") + return { + numerical_answer_type: answer.numericalAnswerType, + answer_range_start: answer.numericAnswerRangeMin, + answer_range_end: answer.numericAnswerRangeMax, + }; + return { + numerical_answer_type: answer.numericalAnswerType, + exact: answer.numericAnswer, + }; + }); } return question.answers.map((answer) => ({ diff --git a/src/features/local/course/courseItemFileStorageService.ts b/src/features/local/course/courseItemFileStorageService.ts index 29a9f88..e0ad9ad 100644 --- a/src/features/local/course/courseItemFileStorageService.ts +++ b/src/features/local/course/courseItemFileStorageService.ts @@ -92,7 +92,8 @@ export const courseItemFileStorageService = { try { const item = await getItem({ courseName, moduleName, name, type }); return item; - } catch { + } catch (e) { + console.log(`Error loading ${type} ${name} in module ${moduleName}:`, e); return null; } }) diff --git a/src/features/local/parsingTests/quizMarkdown/numericalQuestion.test.ts b/src/features/local/parsingTests/quizMarkdown/numericalQuestion.test.ts index 14082f4..2980c30 100644 --- a/src/features/local/parsingTests/quizMarkdown/numericalQuestion.test.ts +++ b/src/features/local/parsingTests/quizMarkdown/numericalQuestion.test.ts @@ -25,26 +25,28 @@ What is 2+3? expect(question.questionType).toBe(QuestionType.NUMERICAL); expect(question.answers[0].numericAnswer).toBe(5); }); -// it("can parse question with range answers", () => { -// const name = "Test Quiz"; -// const rawMarkdownQuiz = ` -// ShuffleAnswers: true -// OneQuestionAtATime: false -// DueAt: 08/21/2023 23:59:00 -// LockAt: 08/21/2023 23:59:00 -// AssignmentGroup: Assignments -// AllowedAttempts: -1 -// Description: quiz description -// --- -// What is 2+3? -// = 5 -// `; + it("can parse question with range answers", () => { + const name = "Test Quiz"; + const rawMarkdownQuiz = ` +ShuffleAnswers: true +OneQuestionAtATime: false +DueAt: 08/21/2023 23:59:00 +LockAt: 08/21/2023 23:59:00 +AssignmentGroup: Assignments +AllowedAttempts: -1 +Description: quiz description +--- +What is the cube root of 2? += [1.2598, 1.2600] +`; -// const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name); -// const question = quiz.questions[0]; + const quiz = quizMarkdownUtils.parseMarkdown(rawMarkdownQuiz, name); + const question = quiz.questions[0]; -// expect(question.text).toBe("What is 2+3?"); -// expect(question.questionType).toBe(QuestionType.NUMERICAL); -// expect(question.answers[0].numericAnswer).toBe(5); -// }); + expect(question.text).toBe("What is the cube root of 2?"); + expect(question.questionType).toBe(QuestionType.NUMERICAL); + expect(question.answers[0].numericalAnswerType).toBe("range_answer"); + expect(question.answers[0].numericAnswerRangeMin).toBe(1.2598); + expect(question.answers[0].numericAnswerRangeMax).toBe(1.26); + }); }); diff --git a/src/features/local/parsingTests/quizMarkdown/quizDeterministicChecks.test.ts b/src/features/local/parsingTests/quizMarkdown/quizDeterministicChecks.test.ts index 778a998..3bc550f 100644 --- a/src/features/local/parsingTests/quizMarkdown/quizDeterministicChecks.test.ts +++ b/src/features/local/parsingTests/quizMarkdown/quizDeterministicChecks.test.ts @@ -195,7 +195,6 @@ describe("QuizDeterministicChecks", () => { ], allowedAttempts: -1, showCorrectAnswers: true, - }; const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz); @@ -221,13 +220,54 @@ describe("QuizDeterministicChecks", () => { points: 1, matchDistractors: [], answers: [ - { text: "= 42", correct: true, numericalAnswerType: "exact_answer", numericAnswer: 42 }, + { + text: "= 42", + correct: true, + numericalAnswerType: "exact_answer", + numericAnswer: 42, + }, + ], + }, + ], + allowedAttempts: -1, + showCorrectAnswers: true, + }; + + const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz); + const parsedQuiz = quizMarkdownUtils.parseMarkdown(quizMarkdown, name); + + expect(parsedQuiz).toEqual(quiz); + }); + it("SerializationIsDeterministic Numeric with range answer", () => { + const name = "Test Quiz"; + const quiz: LocalQuiz = { + name, + description: "quiz description", + lockAt: "08/21/2023 23:59:00", + dueAt: "08/21/2023 23:59:00", + shuffleAnswers: true, + oneQuestionAtATime: true, + password: undefined, + localAssignmentGroupName: "Assignments", + questions: [ + { + text: "test numeric", + questionType: QuestionType.NUMERICAL, + points: 1, + matchDistractors: [], + answers: [ + { + text: "= [2, 5]", + correct: true, + numericalAnswerType: "range_answer", + numericAnswerRangeMin: 2, + numericAnswerRangeMax: 5, + }, ], }, ], allowedAttempts: -1, showCorrectAnswers: true, - }; const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz); diff --git a/src/features/local/quizzes/models/utils/quizQuestionAnswerMarkdownUtils.ts b/src/features/local/quizzes/models/utils/quizQuestionAnswerMarkdownUtils.ts index 54c6207..9e1917b 100644 --- a/src/features/local/quizzes/models/utils/quizQuestionAnswerMarkdownUtils.ts +++ b/src/features/local/quizzes/models/utils/quizQuestionAnswerMarkdownUtils.ts @@ -15,7 +15,27 @@ const _multipleChoicePrefix = ["a)", "*a)", "*)", ")"]; const _multipleAnswerPrefix = ["[ ]", "[*]", "[]"]; const parseNumericalAnswer = (input: string): LocalQuizQuestionAnswer => { - const numericValue = parseFloat(input.replace(/^=\s*/, "").trim()); + const trimmedInput = input.replace(/^=\s*/, "").trim(); + + // Check if it's a range answer: = [min, max] + const minMaxPattern = /^\[([^,]+),\s*([^\]]+)\]$/; + const rangeNumbericAnswerMatch = trimmedInput.match(minMaxPattern); + + if (rangeNumbericAnswerMatch) { + const minValue = parseFloat(rangeNumbericAnswerMatch[1].trim()); + const maxValue = parseFloat(rangeNumbericAnswerMatch[2].trim()); + const answer: LocalQuizQuestionAnswer = { + correct: true, + text: input.trim(), + numericalAnswerType: "range_answer", + numericAnswerRangeMin: minValue, + numericAnswerRangeMax: maxValue, + }; + return answer; + } + + // Otherwise, it's an exact answer + const numericValue = parseFloat(trimmedInput); const answer: LocalQuizQuestionAnswer = { correct: true, text: input.trim(), @@ -200,6 +220,9 @@ export const quizQuestionAnswerMarkdownUtils = { } else if (question.questionType === "matching") { return `^ ${answer.text} - ${answer.matchedText}`; } else if (question.questionType === "numerical") { + if (answer.numericalAnswerType === "range_answer") { + return `= [${answer.numericAnswerRangeMin}, ${answer.numericAnswerRangeMax}]`; + } return `= ${answer.numericAnswer}`; } else { const questionLetter = String.fromCharCode(97 + index);